From 8317c7300e69f74f852fc03c9f44bd0e17cd628a Mon Sep 17 00:00:00 2001 From: Veirt Date: Wed, 13 Nov 2024 04:28:33 +0800 Subject: [PATCH 1/3] feat(auth): set user/tutor data to request --- src/controllers/user.controller.ts | 6 +++++ src/express.d.ts | 6 +++++ src/middleware/auth.middleware.ts | 40 ++++++++++++++++++++++++++++-- src/routes/v1/index.ts | 2 ++ src/routes/v1/user.route.ts | 15 +++++++++++ src/schemas/user.schema.ts | 12 +++++++++ src/types.ts | 12 ++++++++- 7 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/controllers/user.controller.ts create mode 100644 src/express.d.ts create mode 100644 src/routes/v1/user.route.ts create mode 100644 src/schemas/user.schema.ts diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts new file mode 100644 index 0000000..f3c3605 --- /dev/null +++ b/src/controllers/user.controller.ts @@ -0,0 +1,6 @@ +import { Controller } from "../types"; + +export const updateProfile: Controller = async (req, res) => { + // TODO + res.json(); +}; diff --git a/src/express.d.ts b/src/express.d.ts new file mode 100644 index 0000000..7e7d25b --- /dev/null +++ b/src/express.d.ts @@ -0,0 +1,6 @@ +declare namespace Express { + export interface Request { + user: User; + tutor: Tutor; + } +} diff --git a/src/middleware/auth.middleware.ts b/src/middleware/auth.middleware.ts index a525a46..a55c6a2 100644 --- a/src/middleware/auth.middleware.ts +++ b/src/middleware/auth.middleware.ts @@ -1,5 +1,5 @@ import type { RequestHandler } from "express"; -import { auth } from "../config"; +import { auth, firestore } from "../config"; import { logger } from "./logging.middleware"; export const firebaseAuthMiddleware: RequestHandler = async ( @@ -14,7 +14,25 @@ export const firebaseAuthMiddleware: RequestHandler = async ( const token = authHeader.split("Bearer ")[1]; // Validating the token. - await auth.verifyIdToken(token); + const user = await auth.verifyIdToken(token); + + // Find out whether the 'user' here is a regular user or tutor + const userData = await firestore.collection("users").doc(user.uid).get(); + const tutorData = await firestore.collection("tutors").doc(user.uid).get(); + + if (userData.exists) { + req.user = { + uid: user.uid, + ...userData.data(), + }; + } else if (tutorData.exists) { + req.tutor = { + uid: user.uid, + ...tutorData.data(), + }; + } else { + throw new Error("User not found"); + } } catch (error) { logger.debug(`Failed to verify token: ${error}`); @@ -24,3 +42,21 @@ export const firebaseAuthMiddleware: RequestHandler = async ( next(); }; + +export const verifyTutor: RequestHandler = (req, res, next) => { + if (!req.tutor) { + res.status(403).json({ status: "fail", message: "Forbidden" }); + return; + } + + next(); +}; + +export const verifyUser: RequestHandler = (req, res, next) => { + if (!req.user) { + res.status(403).json({ status: "fail", message: "Forbidden" }); + return; + } + + next(); +}; diff --git a/src/routes/v1/index.ts b/src/routes/v1/index.ts index c990ae3..2895c0d 100644 --- a/src/routes/v1/index.ts +++ b/src/routes/v1/index.ts @@ -1,10 +1,12 @@ import { Router } from "express"; import authRouter from "./auth.route"; import subjectRouter from "./subject.route"; +import userRouter from "./user.route"; const v1Router = Router(); v1Router.use("/auth", authRouter); v1Router.use("/subjects", subjectRouter); +v1Router.use("/users", userRouter); export default v1Router; diff --git a/src/routes/v1/user.route.ts b/src/routes/v1/user.route.ts new file mode 100644 index 0000000..0cce725 --- /dev/null +++ b/src/routes/v1/user.route.ts @@ -0,0 +1,15 @@ +import { Router } from "express"; +import * as userController from "@controllers/user.controller"; +import { + firebaseAuthMiddleware, + verifyUser, +} from "../../middleware/auth.middleware"; + +// /api/v1/users +const userRouter = Router(); +userRouter.use(firebaseAuthMiddleware); +userRouter.use(verifyUser); + +userRouter.patch("/profile", userController.updateProfile); + +export default userRouter; diff --git a/src/schemas/user.schema.ts b/src/schemas/user.schema.ts new file mode 100644 index 0000000..9592973 --- /dev/null +++ b/src/schemas/user.schema.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +export const userSchema = z.object({ + body: z.object({ + name: z.string().min(3, "Name must be at least 3 characters"), + phoneNum: z.string().min(10, "Phone number must be at least 10 characters"), + // TODO: + // city + // interests + // learningStyle + }), +}); diff --git a/src/types.ts b/src/types.ts index 44ed52c..2ab613c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,7 +15,7 @@ enum LearningStyle { KINESTHETIC = "KINESTHETIC", } -interface User { +export interface User { id: string; phoneNum?: string; city?: string; @@ -26,6 +26,16 @@ interface User { lastSeen?: Date; } +export interface Tutor { + id: string; + phoneNum?: string; + location?: unknown; // not sure now + coverageRange: number; + createdAt: Date; + updatedAt: Date; + lastSeen?: Date; +} + export interface Subject { name: string; iconUrl: string; From 2819bc898a5b4cad741012a0e8d17cfea7bcdec5 Mon Sep 17 00:00:00 2001 From: Veirt Date: Wed, 13 Nov 2024 04:54:33 +0800 Subject: [PATCH 2/3] fix(type): use declare global and fix type - change uid to id in both `req.user` and `req.tutor` --- src/express.d.ts | 12 ++++++++---- src/middleware/auth.middleware.ts | 9 +++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/express.d.ts b/src/express.d.ts index 7e7d25b..342c62f 100644 --- a/src/express.d.ts +++ b/src/express.d.ts @@ -1,6 +1,10 @@ -declare namespace Express { - export interface Request { - user: User; - tutor: Tutor; +import type { User, Tutor } from "./types"; + +declare global { + namespace Express { + interface Request { + user: User; + tutor: Tutor; + } } } diff --git a/src/middleware/auth.middleware.ts b/src/middleware/auth.middleware.ts index a55c6a2..04d341f 100644 --- a/src/middleware/auth.middleware.ts +++ b/src/middleware/auth.middleware.ts @@ -1,4 +1,5 @@ import type { RequestHandler } from "express"; +import type { Tutor, User } from "../types"; import { auth, firestore } from "../config"; import { logger } from "./logging.middleware"; @@ -22,13 +23,13 @@ export const firebaseAuthMiddleware: RequestHandler = async ( if (userData.exists) { req.user = { - uid: user.uid, - ...userData.data(), + ...(userData.data() as User), + id: user.uid, }; } else if (tutorData.exists) { req.tutor = { - uid: user.uid, - ...tutorData.data(), + ...(tutorData.data() as Tutor), + id: user.uid, }; } else { throw new Error("User not found"); From 1e67601d6f04875c5a58fae1e6ba947c36a2cb10 Mon Sep 17 00:00:00 2001 From: Veirt Date: Wed, 13 Nov 2024 05:03:22 +0800 Subject: [PATCH 3/3] feat(user/profile): add update user profile endpoint - make all the body optional --- src/controllers/user.controller.ts | 32 +++++++++++++++++++++++++++--- src/routes/v1/user.route.ts | 8 +++++++- src/schemas/user.schema.ts | 7 +++++-- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts index f3c3605..c28479c 100644 --- a/src/controllers/user.controller.ts +++ b/src/controllers/user.controller.ts @@ -1,6 +1,32 @@ +import { z } from "zod"; +import { userSchema } from "../schemas/user.schema"; import { Controller } from "../types"; +import { firestore } from "../config"; +import { logger } from "../middleware/logging.middleware"; -export const updateProfile: Controller = async (req, res) => { - // TODO - res.json(); +type UpdateProfileSchema = z.infer; +export const updateProfile: Controller = async ( + req, + res, +) => { + try { + firestore + .collection("users") + .doc(req.user.id) + .update({ + ...req.body, + updatedAt: new Date(), + }); + } catch (error) { + logger.debug("Failed to update profile", error); + + res + .status(500) + .json({ status: "error", message: "Failed to update profile" }); + return; + } + + res + .status(200) + .json({ status: "success", message: "User profile updated successfully" }); }; diff --git a/src/routes/v1/user.route.ts b/src/routes/v1/user.route.ts index 0cce725..f623af8 100644 --- a/src/routes/v1/user.route.ts +++ b/src/routes/v1/user.route.ts @@ -4,12 +4,18 @@ import { firebaseAuthMiddleware, verifyUser, } from "../../middleware/auth.middleware"; +import { validator } from "../../middleware/validation.middleware"; +import { userSchema } from "../../schemas/user.schema"; // /api/v1/users const userRouter = Router(); userRouter.use(firebaseAuthMiddleware); userRouter.use(verifyUser); -userRouter.patch("/profile", userController.updateProfile); +userRouter.patch( + "/profile", + validator(userSchema), + userController.updateProfile, +); export default userRouter; diff --git a/src/schemas/user.schema.ts b/src/schemas/user.schema.ts index 9592973..be2876d 100644 --- a/src/schemas/user.schema.ts +++ b/src/schemas/user.schema.ts @@ -2,8 +2,11 @@ import { z } from "zod"; export const userSchema = z.object({ body: z.object({ - name: z.string().min(3, "Name must be at least 3 characters"), - phoneNum: z.string().min(10, "Phone number must be at least 10 characters"), + name: z.string().min(3, "Name must be at least 3 characters").optional(), + phoneNum: z + .string() + .min(10, "Phone number must be at least 10 characters") + .optional(), // TODO: // city // interests