Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(user): update user profile endpoint #1

Merged
merged 3 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +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";

type UpdateProfileSchema = z.infer<typeof userSchema>;
export const updateProfile: Controller<UpdateProfileSchema> = 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" });
};
10 changes: 10 additions & 0 deletions src/express.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { User, Tutor } from "./types";

declare global {
namespace Express {
interface Request {
user: User;
tutor: Tutor;
}
}
}
41 changes: 39 additions & 2 deletions src/middleware/auth.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { RequestHandler } from "express";
import { auth } from "../config";
import type { Tutor, User } from "../types";
import { auth, firestore } from "../config";
import { logger } from "./logging.middleware";

export const firebaseAuthMiddleware: RequestHandler = async (
Expand All @@ -14,7 +15,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 = {
...(userData.data() as User),
id: user.uid,
};
} else if (tutorData.exists) {
req.tutor = {
...(tutorData.data() as Tutor),
id: user.uid,
};
} else {
throw new Error("User not found");
}
} catch (error) {
logger.debug(`Failed to verify token: ${error}`);

Expand All @@ -24,3 +43,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();
};
2 changes: 2 additions & 0 deletions src/routes/v1/index.ts
Original file line number Diff line number Diff line change
@@ -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;
21 changes: 21 additions & 0 deletions src/routes/v1/user.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Router } from "express";
import * as userController from "@controllers/user.controller";
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",
validator(userSchema),
userController.updateProfile,
);

export default userRouter;
15 changes: 15 additions & 0 deletions src/schemas/user.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { z } from "zod";

export const userSchema = z.object({
body: z.object({
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
// learningStyle
}),
});
12 changes: 11 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ enum LearningStyle {
KINESTHETIC = "KINESTHETIC",
}

interface User {
export interface User {
id: string;
phoneNum?: string;
city?: string;
Expand All @@ -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;
Expand Down