Skip to content

Commit

Permalink
Merge pull request #30 from hack4impact-upenn/batch_service
Browse files Browse the repository at this point in the history
batch service added
  • Loading branch information
rosewang01 authored May 17, 2024
2 parents f1895b9 + 2b5e5c4 commit 9044abd
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 27 deletions.
13 changes: 7 additions & 6 deletions client/src/components/buttons/InviteUserButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { postData } from '../../util/api';

function InviteUserButton() {
const [open, setOpen] = useState(false);
const [email, setEmail] = useState('');
const [emails, setEmails] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);

Expand All @@ -27,11 +27,11 @@ function InviteUserButton() {

const handleInvite = async () => {
setLoading(true);
postData('admin/invite', { email }).then((res) => {
postData('admin/invite', { emails }).then((res) => {
if (res.error) {
setError(res.error.message);
} else {
setAlert(`${email} successfully invited!`, AlertType.SUCCESS);
setAlert(`${emails} successfully invited!`, AlertType.SUCCESS);
setOpen(false);
}
setLoading(false);
Expand All @@ -40,7 +40,7 @@ function InviteUserButton() {

const updateEmail = (event: React.ChangeEvent<HTMLInputElement>) => {
setError('');
setEmail(event.target.value);
setEmails(event.target.value);
};

return (
Expand All @@ -51,13 +51,14 @@ function InviteUserButton() {
<Dialog open={open} onClose={handleClose}>
<DialogContent>
<DialogContentText>
Please enter the email address of the user you would like to invite.
Please enter one or more email addresses separated by commas. (ex.
a@gmail.com, b@outlook.com)
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
label="Email Address"
label="Email Addresses"
type="email"
fullWidth
variant="standard"
Expand Down
103 changes: 82 additions & 21 deletions server/src/controllers/admin.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,37 +140,98 @@ const inviteUser = async (
res: express.Response,
next: express.NextFunction,
) => {
const { email } = req.body;
const { emails } = req.body;
if (!emails) {
next(ApiError.missingFields(['email']));
return;
}
const emailList = emails.replaceAll(' ', '').split(',');
const emailRegex =
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/g;
if (!email.match(emailRegex)) {
next(ApiError.badRequest('Invalid email'));

function validateEmail(email: string) {
if (!email.match(emailRegex)) {
next(ApiError.badRequest(`Invalid email: ${email}`));
}
}
const lowercaseEmail = email.toLowerCase();
const existingUser: IUser | null = await getUserByEmail(lowercaseEmail);
if (existingUser) {
next(
ApiError.badRequest(
`An account with email ${lowercaseEmail} already exists.`,
),
);
return;

function combineEmailToken(email: string, invite: IInvite | null) {
const verificationToken = crypto.randomBytes(32).toString('hex');
return [email, invite, verificationToken];
}

async function makeInvite(combinedList: any[]) {
try {
const email = combinedList[0];
const existingInvite = combinedList[1];
const verificationToken = combinedList[2];
if (existingInvite) {
await updateInvite(existingInvite, verificationToken);
} else {
await createInvite(email, verificationToken);
}
} catch (err: any) {
next(ApiError.internal(`Error creating invite: ${err.message}`));
}
}

const existingInvite: IInvite | null = await getInviteByEmail(lowercaseEmail);
function sendInvite(combinedList: any[]) {
try {
const email = combinedList[0];
const verificationToken = combinedList[2];

emailInviteLink(email, verificationToken);
return;
} catch (err: any) {
next(ApiError.internal(`Error sending invite: ${err.message}`));
}
}

try {
const verificationToken = crypto.randomBytes(32).toString('hex');
if (existingInvite) {
await updateInvite(existingInvite, verificationToken);
} else {
await createInvite(lowercaseEmail, verificationToken);
if (emailList.length === 0) {
next(ApiError.missingFields(['email']));
return;
}
emailList.forEach(validateEmail);
const lowercaseEmailList: string[] = emailList.map((email: string) =>
email.toLowerCase(),
);

const userPromises = lowercaseEmailList.map(getUserByEmail);
const existingUserList = await Promise.all(userPromises);

const invitePromises = lowercaseEmailList.map(getInviteByEmail);
const existingInviteList = await Promise.all(invitePromises);

const existingUserEmails = existingUserList.map((user) =>
user ? user.email : '',
);
const existingInviteEmails = existingInviteList.map((invite) =>
invite ? invite.email : '',
);

const emailInviteList = lowercaseEmailList.filter((email) => {
if (existingUserEmails.includes(email)) {
throw ApiError.badRequest(`User with email ${email} already exists`);
}
return !existingUserEmails.includes(email);
});

const combinedList = emailInviteList.map((email) => {
const existingInvite =
existingInviteList[existingInviteEmails.indexOf(email)];
return combineEmailToken(email, existingInvite);
});

const makeInvitePromises = combinedList.map(makeInvite);
await Promise.all(makeInvitePromises);

const sendInvitePromises = combinedList.map(sendInvite);
await Promise.all(sendInvitePromises);

await emailInviteLink(lowercaseEmail, verificationToken);
res.sendStatus(StatusCode.CREATED);
} catch (err) {
next(ApiError.internal('Unable to invite user.'));
} catch (err: any) {
next(ApiError.internal(`Unable to invite user: ${err.message}`));
}
};

Expand Down

0 comments on commit 9044abd

Please sign in to comment.