support can look up users by email, not just name

This commit is contained in:
Emi Matchu 2020-11-18 10:32:49 -08:00
parent 7c9313f4a6
commit d219750ea1
3 changed files with 90 additions and 3 deletions

View file

@ -265,10 +265,12 @@ function UserItemsPage() {
function UserSearchForm() { function UserSearchForm() {
const [query, setQuery] = React.useState(""); const [query, setQuery] = React.useState("");
const { isSupportUser, supportSecret } = useSupport();
const history = useHistory(); const history = useHistory();
const toast = useToast(); const toast = useToast();
const [loadUserSearch, { loading }] = useLazyQuery( const [loadUserSearch, { loading: loading1 }] = useLazyQuery(
gql` gql`
query UserSearchForm($name: String!) { query UserSearchForm($name: String!) {
userByName(name: $name) { userByName(name: $name) {
@ -302,11 +304,58 @@ function UserSearchForm() {
} }
); );
const [loadUserByEmail, { loading: loading2 }] = useLazyQuery(
gql`
query UserSearchFormByEmail($email: String!, $supportSecret: String!) {
userByEmail(email: $email, supportSecret: $supportSecret) {
id
# Consider preloading UserItemsPage fields here, too?
}
}
`,
{
onCompleted: (data) => {
const user = data.userByEmail;
if (!user) {
toast({
status: "warning",
title: "We couldn't find that email address!",
description: "Check the spelling and try again?",
});
return;
}
history.push(`/user/${user.id}/items`);
},
onError: (error) => {
console.error(error);
toast({
status: "error",
title: "Error loading user by email!",
description: "Check your connection and try again?",
});
},
}
);
return ( return (
<Box <Box
as="form" as="form"
onSubmit={(e) => { onSubmit={(e) => {
loadUserSearch({ variables: { name: query } }); const isSupportOnlyEmailSearch =
isSupportUser && query.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
if (isSupportOnlyEmailSearch) {
toast({
status: "info",
title: "Searching by email! (💖 Support-only)",
description: "The email field is protected from most users.",
});
loadUserByEmail({ variables: { email: query, supportSecret } });
} else {
loadUserSearch({ variables: { name: query } });
}
e.preventDefault(); e.preventDefault();
}} }}
> >
@ -326,7 +375,7 @@ function UserSearchForm() {
variant="ghost" variant="ghost"
icon={<ArrowForwardIcon />} icon={<ArrowForwardIcon />}
aria-label="Search" aria-label="Search"
isLoading={loading} isLoading={loading1 || loading2}
minWidth="1.5rem" minWidth="1.5rem"
minHeight="1.5rem" minHeight="1.5rem"
width="1.5rem" width="1.5rem"

View file

@ -774,6 +774,29 @@ const buildUserByNameLoader = (db) =>
); );
}); });
const buildUserByEmailLoader = (db) =>
new DataLoader(async (emails) => {
const qs = emails.map((_) => "?").join(",");
const [rows, _] = await db.execute(
{
sql: `
SELECT users.*, id_users.email FROM users
INNER JOIN openneo_id.users id_users ON id_users.id = users.remote_id
WHERE id_users.email IN (${qs})
`,
nestTables: true,
},
emails
);
const entities = rows.map((row) => ({
user: normalizeRow(row.users),
email: row.id_users.email,
}));
return emails.map((email) => entities.find((e) => e.email === email).user);
});
const buildUserClosetHangersLoader = (db) => const buildUserClosetHangersLoader = (db) =>
new DataLoader(async (userIds) => { new DataLoader(async (userIds) => {
const qs = userIds.map((_) => "?").join(","); const qs = userIds.map((_) => "?").join(",");
@ -907,6 +930,7 @@ function buildLoaders(db) {
loaders.speciesTranslationLoader = buildSpeciesTranslationLoader(db); loaders.speciesTranslationLoader = buildSpeciesTranslationLoader(db);
loaders.userLoader = buildUserLoader(db); loaders.userLoader = buildUserLoader(db);
loaders.userByNameLoader = buildUserByNameLoader(db); loaders.userByNameLoader = buildUserByNameLoader(db);
loaders.userByEmailLoader = buildUserByEmailLoader(db);
loaders.userClosetHangersLoader = buildUserClosetHangersLoader(db); loaders.userClosetHangersLoader = buildUserClosetHangersLoader(db);
loaders.userClosetListsLoader = buildUserClosetListsLoader(db); loaders.userClosetListsLoader = buildUserClosetListsLoader(db);
loaders.zoneLoader = buildZoneLoader(db); loaders.zoneLoader = buildZoneLoader(db);

View file

@ -41,6 +41,7 @@ const typeDefs = gql`
extend type Query { extend type Query {
user(id: ID!): User user(id: ID!): User
userByName(name: String!): User userByName(name: String!): User
userByEmail(email: String!, supportSecret: String!): User
currentUser: User currentUser: User
} }
`; `;
@ -224,6 +225,19 @@ const resolvers = {
return { id: user.id }; return { id: user.id };
}, },
userByEmail: async (_, { email, supportSecret }, { userByEmailLoader }) => {
if (supportSecret !== process.env["SUPPORT_SECRET"]) {
throw new Error(`Support secret is incorrect. Try setting up again?`);
}
const user = await userByEmailLoader.load(email);
if (!user) {
return null;
}
return { id: user.id };
},
currentUser: async (_, __, { currentUserId, userLoader }) => { currentUser: async (_, __, { currentUserId, userLoader }) => {
if (currentUserId == null) { if (currentUserId == null) {
return null; return null;