Skip to content

Commit 9044abd

Browse files
authored
Merge pull request #30 from hack4impact-upenn/batch_service
batch service added
2 parents f1895b9 + 2b5e5c4 commit 9044abd

File tree

2 files changed

+89
-27
lines changed

2 files changed

+89
-27
lines changed

client/src/components/buttons/InviteUserButton.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { postData } from '../../util/api';
1111

1212
function InviteUserButton() {
1313
const [open, setOpen] = useState(false);
14-
const [email, setEmail] = useState('');
14+
const [emails, setEmails] = useState('');
1515
const [error, setError] = useState('');
1616
const [loading, setLoading] = useState(false);
1717

@@ -27,11 +27,11 @@ function InviteUserButton() {
2727

2828
const handleInvite = async () => {
2929
setLoading(true);
30-
postData('admin/invite', { email }).then((res) => {
30+
postData('admin/invite', { emails }).then((res) => {
3131
if (res.error) {
3232
setError(res.error.message);
3333
} else {
34-
setAlert(`${email} successfully invited!`, AlertType.SUCCESS);
34+
setAlert(`${emails} successfully invited!`, AlertType.SUCCESS);
3535
setOpen(false);
3636
}
3737
setLoading(false);
@@ -40,7 +40,7 @@ function InviteUserButton() {
4040

4141
const updateEmail = (event: React.ChangeEvent<HTMLInputElement>) => {
4242
setError('');
43-
setEmail(event.target.value);
43+
setEmails(event.target.value);
4444
};
4545

4646
return (
@@ -51,13 +51,14 @@ function InviteUserButton() {
5151
<Dialog open={open} onClose={handleClose}>
5252
<DialogContent>
5353
<DialogContentText>
54-
Please enter the email address of the user you would like to invite.
54+
Please enter one or more email addresses separated by commas. (ex.
55+
5556
</DialogContentText>
5657
<TextField
5758
autoFocus
5859
margin="dense"
5960
id="name"
60-
label="Email Address"
61+
label="Email Addresses"
6162
type="email"
6263
fullWidth
6364
variant="standard"

server/src/controllers/admin.controller.ts

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -140,37 +140,98 @@ const inviteUser = async (
140140
res: express.Response,
141141
next: express.NextFunction,
142142
) => {
143-
const { email } = req.body;
143+
const { emails } = req.body;
144+
if (!emails) {
145+
next(ApiError.missingFields(['email']));
146+
return;
147+
}
148+
const emailList = emails.replaceAll(' ', '').split(',');
144149
const emailRegex =
145150
/^[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;
146-
if (!email.match(emailRegex)) {
147-
next(ApiError.badRequest('Invalid email'));
151+
152+
function validateEmail(email: string) {
153+
if (!email.match(emailRegex)) {
154+
next(ApiError.badRequest(`Invalid email: ${email}`));
155+
}
148156
}
149-
const lowercaseEmail = email.toLowerCase();
150-
const existingUser: IUser | null = await getUserByEmail(lowercaseEmail);
151-
if (existingUser) {
152-
next(
153-
ApiError.badRequest(
154-
`An account with email ${lowercaseEmail} already exists.`,
155-
),
156-
);
157-
return;
157+
158+
function combineEmailToken(email: string, invite: IInvite | null) {
159+
const verificationToken = crypto.randomBytes(32).toString('hex');
160+
return [email, invite, verificationToken];
161+
}
162+
163+
async function makeInvite(combinedList: any[]) {
164+
try {
165+
const email = combinedList[0];
166+
const existingInvite = combinedList[1];
167+
const verificationToken = combinedList[2];
168+
if (existingInvite) {
169+
await updateInvite(existingInvite, verificationToken);
170+
} else {
171+
await createInvite(email, verificationToken);
172+
}
173+
} catch (err: any) {
174+
next(ApiError.internal(`Error creating invite: ${err.message}`));
175+
}
158176
}
159177

160-
const existingInvite: IInvite | null = await getInviteByEmail(lowercaseEmail);
178+
function sendInvite(combinedList: any[]) {
179+
try {
180+
const email = combinedList[0];
181+
const verificationToken = combinedList[2];
182+
183+
emailInviteLink(email, verificationToken);
184+
return;
185+
} catch (err: any) {
186+
next(ApiError.internal(`Error sending invite: ${err.message}`));
187+
}
188+
}
161189

162190
try {
163-
const verificationToken = crypto.randomBytes(32).toString('hex');
164-
if (existingInvite) {
165-
await updateInvite(existingInvite, verificationToken);
166-
} else {
167-
await createInvite(lowercaseEmail, verificationToken);
191+
if (emailList.length === 0) {
192+
next(ApiError.missingFields(['email']));
193+
return;
168194
}
195+
emailList.forEach(validateEmail);
196+
const lowercaseEmailList: string[] = emailList.map((email: string) =>
197+
email.toLowerCase(),
198+
);
199+
200+
const userPromises = lowercaseEmailList.map(getUserByEmail);
201+
const existingUserList = await Promise.all(userPromises);
202+
203+
const invitePromises = lowercaseEmailList.map(getInviteByEmail);
204+
const existingInviteList = await Promise.all(invitePromises);
205+
206+
const existingUserEmails = existingUserList.map((user) =>
207+
user ? user.email : '',
208+
);
209+
const existingInviteEmails = existingInviteList.map((invite) =>
210+
invite ? invite.email : '',
211+
);
212+
213+
const emailInviteList = lowercaseEmailList.filter((email) => {
214+
if (existingUserEmails.includes(email)) {
215+
throw ApiError.badRequest(`User with email ${email} already exists`);
216+
}
217+
return !existingUserEmails.includes(email);
218+
});
219+
220+
const combinedList = emailInviteList.map((email) => {
221+
const existingInvite =
222+
existingInviteList[existingInviteEmails.indexOf(email)];
223+
return combineEmailToken(email, existingInvite);
224+
});
225+
226+
const makeInvitePromises = combinedList.map(makeInvite);
227+
await Promise.all(makeInvitePromises);
228+
229+
const sendInvitePromises = combinedList.map(sendInvite);
230+
await Promise.all(sendInvitePromises);
169231

170-
await emailInviteLink(lowercaseEmail, verificationToken);
171232
res.sendStatus(StatusCode.CREATED);
172-
} catch (err) {
173-
next(ApiError.internal('Unable to invite user.'));
233+
} catch (err: any) {
234+
next(ApiError.internal(`Unable to invite user: ${err.message}`));
174235
}
175236
};
176237

0 commit comments

Comments
 (0)