Skip to content
This repository was archived by the owner on Apr 19, 2023. It is now read-only.

Commit cf70881

Browse files
✨ Resend email verification link
1 parent b51c0bf commit cf70881

File tree

9 files changed

+82
-12
lines changed

9 files changed

+82
-12
lines changed

src/crud/email.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { query, tableValues, setValues, removeReadOnlyValues } from "../helpers/mysql";
1+
import {
2+
query,
3+
tableValues,
4+
setValues,
5+
removeReadOnlyValues
6+
} from "../helpers/mysql";
27
import { Email } from "../interfaces/tables/emails";
38
import { dateToDateTime } from "../helpers/utils";
49
import { KeyValue } from "../interfaces/general";
@@ -41,6 +46,15 @@ export const sendEmailVerification = async (
4146
return;
4247
};
4348

49+
export const resendEmailVerification = async (id: number) => {
50+
const token = await emailVerificationToken(id);
51+
const emailObject = await getEmail(id);
52+
const email = emailObject.email;
53+
const user = await getUser(emailObject.userId);
54+
await mail(email, Templates.EMAIL_VERIFY, { name: user.name, email, token });
55+
return;
56+
};
57+
4458
export const updateEmail = async (id: number, email: KeyValue) => {
4559
email.updatedAt = dateToDateTime(new Date());
4660
email = removeReadOnlyValues(email);
@@ -87,7 +101,7 @@ export const getEmailObject = async (email: string) => {
87101
};
88102

89103
export const getUserVerifiedEmails = async (userId: number) => {
90-
return <Email>(
104+
return <Email[]>(
91105
await query("SELECT * FROM emails WHERE userId = ? AND isVerified = 1", [
92106
userId
93107
])

src/crud/membership.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { query, tableValues, setValues, removeReadOnlyValues } from "../helpers/mysql";
1+
import {
2+
query,
3+
tableValues,
4+
setValues,
5+
removeReadOnlyValues
6+
} from "../helpers/mysql";
27
import { Membership } from "../interfaces/tables/memberships";
38
import { dateToDateTime } from "../helpers/utils";
49
import { KeyValue } from "../interfaces/general";

src/crud/organization.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { query, tableValues, setValues, removeReadOnlyValues } from "../helpers/mysql";
1+
import {
2+
query,
3+
tableValues,
4+
setValues,
5+
removeReadOnlyValues
6+
} from "../helpers/mysql";
27
import { Organization } from "../interfaces/tables/organization";
38
import { capitalizeFirstAndLastLetter, dateToDateTime } from "../helpers/utils";
49
import { KeyValue } from "../interfaces/general";

src/crud/user.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { query, tableValues, setValues, removeReadOnlyValues } from "../helpers/mysql";
1+
import {
2+
query,
3+
tableValues,
4+
setValues,
5+
removeReadOnlyValues
6+
} from "../helpers/mysql";
27
import { User } from "../interfaces/tables/user";
38
import {
49
capitalizeFirstAndLastLetter,

src/helpers/mysql.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,4 @@ export const removeReadOnlyValues = (object: KeyValue) => {
9797
if (object[value]) delete object[value];
9898
});
9999
return object;
100-
}
100+
};

src/interfaces/enum.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ export enum EventType {
3434
EMAIL_CREATED = "email.created",
3535
EMAIL_UPDATED = "email.updated",
3636
EMAIL_DELETED = "email.deleted",
37-
EMAIL_VERIFIED = "email.verified"
37+
EMAIL_VERIFIED = "email.verified",
38+
EMAIL_CANNOT_DELETE = "email.cannotDelete"
3839
}
3940

4041
export enum ErrorCode {

src/rest/user.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@ import {
44
UserRole,
55
EventType
66
} from "../interfaces/enum";
7-
import { getUser } from "../crud/user";
7+
import { getUser, updateUser } from "../crud/user";
88
import {
99
getUserOrganizationId,
1010
getUserMembershipObject
1111
} from "../crud/membership";
12-
import { createEmail, deleteEmail, getEmail } from "../crud/email";
12+
import {
13+
createEmail,
14+
deleteEmail,
15+
getEmail,
16+
getUserVerifiedEmails,
17+
getUserPrimaryEmail,
18+
getUserPrimaryEmailObject
19+
} from "../crud/email";
1320
import { Locals } from "../interfaces/general";
1421
import { createEvent } from "../crud/event";
1522

@@ -55,6 +62,18 @@ export const deleteEmailFromUser = async (
5562
const email = await getEmail(emailId);
5663
if (email.userId != userId)
5764
throw new Error(ErrorCode.INSUFFICIENT_PERMISSION);
65+
const verifiedEmails = await getUserVerifiedEmails(userId);
66+
if (verifiedEmails.length > 1) {
67+
const currentPrimaryEmailId = (await getUserPrimaryEmailObject(userId)).id;
68+
if (currentPrimaryEmailId == emailId) {
69+
const nextVerifiedEmail = verifiedEmails.filter(
70+
emailObject => emailObject.id != emailId
71+
)[0];
72+
await updateUser(userId, { primaryEmail: nextVerifiedEmail });
73+
}
74+
} else {
75+
throw new Error(EventType.EMAIL_CANNOT_DELETE);
76+
}
5877
await deleteEmail(emailId);
5978
await createEvent(
6079
{ userId, type: EventType.EMAIL_DELETED, data: { email: email.email } },

src/routes/emails.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Request, Response } from "express";
22
import { verifyEmail } from "../rest/auth";
33
import { ErrorCode } from "../interfaces/enum";
44
import { addEmailToUser, deleteEmailFromUser } from "../rest/user";
5+
import { resendEmailVerification } from "../crud/email";
56

67
export const routeEmailAdd = async (req: Request, res: Response) => {
78
const email = req.body.email;
@@ -21,3 +22,10 @@ export const routeEmailVerify = async (req: Request, res: Response) => {
2122
await verifyEmail(req.body.token || req.params.token, res.locals);
2223
res.json({ success: true });
2324
};
25+
26+
export const routeEmailVerifyResend = async (req: Request, res: Response) => {
27+
const emailId = req.params.id || req.body.id;
28+
if (!emailId) throw new Error(ErrorCode.MISSING_FIELD);
29+
await resendEmailVerification(emailId);
30+
res.json({ success: true });
31+
};

src/routes/index.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import { Application } from "express";
22
import asyncHandler from "express-async-handler";
33
import { routeUserPut, routeUserId } from "./users";
4-
import { routeEmailVerify, routeEmailAdd, routeEmailDelete } from "./emails";
5-
import { routeOrganizationCreate, routeOrganizationUpdate } from "./organizations";
4+
import {
5+
routeEmailVerify,
6+
routeEmailAdd,
7+
routeEmailDelete,
8+
routeEmailVerifyResend
9+
} from "./emails";
10+
import {
11+
routeOrganizationCreate,
12+
routeOrganizationUpdate
13+
} from "./organizations";
614
import { authHandler } from "../helpers/middleware";
715
import {
816
routeAuthVerifyToken,
@@ -43,10 +51,15 @@ const routesUser = (app: Application) => {
4351
const routesEmail = (app: Application) => {
4452
app.put("/emails", authHandler, asyncHandler(routeEmailAdd));
4553
app.delete("/emails/:id", authHandler, asyncHandler(routeEmailDelete));
54+
app.post("/emails/:id/resend", asyncHandler(routeEmailVerifyResend));
4655
app.post("/emails/verify", asyncHandler(routeEmailVerify));
4756
};
4857

4958
const routesOrganization = (app: Application) => {
5059
app.put("/organizations", authHandler, asyncHandler(routeOrganizationCreate));
51-
app.patch("/organizations/:id", authHandler, asyncHandler(routeOrganizationUpdate));
60+
app.patch(
61+
"/organizations/:id",
62+
authHandler,
63+
asyncHandler(routeOrganizationUpdate)
64+
);
5265
};

0 commit comments

Comments
 (0)