Skip to content

Commit

Permalink
refactor(*): change the term user to learner
Browse files Browse the repository at this point in the history
- also move database logic from schemas to services
  • Loading branch information
Veirt committed Nov 15, 2024
1 parent c6cf9cf commit 77c1c7d
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 112 deletions.
2 changes: 1 addition & 1 deletion src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const register: Controller<RegisterSchema> = async (req, res) => {
try {
const { name, email, password } = req.body;

const result = await authService.registerUser(name, email, password);
const result = await authService.registerLearner(name, email, password);

res.status(201).json({
status: "success",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Controller } from "@/types";
import { logger } from "@middleware/logging.middleware";
import { updateProfileSchema } from "@schemas/user.schema";
import { updateProfileSchema } from "@schemas/learner.schema";
import { changePasswordSchema } from "@schemas/auth.schema";
import * as userService from "@services/user.service";
import * as learnerService from "@services/learner.service";
import { RequestHandler } from "express";
import { z } from "zod";

Expand All @@ -12,27 +12,27 @@ export const updateProfile: Controller<UpdateProfileSchema> = async (
res,
) => {
try {
await userService.updateUserProfile(req.user.id, req.body);
await learnerService.updateLearnerProfile(req.learner.id, req.body);

res.status(200).json({
status: "success",
message: "User profile updated successfully",
message: "Learner profile updated successfully",
});
} catch (error) {
logger.debug(`Failed to update user profile: ${error}`);
logger.debug(`Failed to update learner profile: ${error}`);

res.status(500).json({
status: "error",
message: "Failed to update user profile",
message: "Failed to update learner profile",
});
}
};

export const updateProfilePicture: RequestHandler = async (req, res) => {
try {
const url = await userService.updateUserProfilePicture(
const url = await learnerService.updateLearnerProfilePicture(
req.file!,
req.user.id,
req.learner.id,
);

res.json({
Expand All @@ -59,7 +59,7 @@ export const changePassword: Controller<ChangePasswordSchema> = async (
res,
) => {
try {
await userService.changePassword(req.user.id, req.body.newPassword);
await learnerService.changePassword(req.learner.id, req.body.newPassword);

res.json({
status: "success",
Expand Down
4 changes: 2 additions & 2 deletions src/express.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { User, Tutor } from "./types";
import type { Learner, Tutor } from "./types";

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

Expand All @@ -17,21 +17,29 @@ export const firebaseAuthMiddleware: RequestHandler = async (
// Validating the 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),
// Find out whether the 'user' here is a learner or tutor
const learnerData = await firestore
.collection("learners")
.doc(user.uid)
.get();
if (learnerData.exists) {
req.learner = {
...(learnerData.data() as Learner),
id: user.uid,
};
} else if (tutorData.exists) {
return;
}

const tutorData = await firestore.collection("tutors").doc(user.uid).get();
if (tutorData.exists) {
req.tutor = {
...(tutorData.data() as Tutor),
id: user.uid,
};
} else {
return;
}

if (!learnerData.exists && !tutorData.exists) {
throw new Error("User not found");
}
} catch (error) {
Expand All @@ -53,8 +61,8 @@ export const verifyTutor: RequestHandler = (req, res, next) => {
next();
};

export const verifyUser: RequestHandler = (req, res, next) => {
if (!req.user) {
export const verifyLearner: RequestHandler = (req, res, next) => {
if (!req.learner) {
res.status(403).json({ status: "fail", message: "Forbidden" });
return;
}
Expand Down
4 changes: 2 additions & 2 deletions src/routes/v1/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Router } from "express";
import authRouter from "./auth.route";
import subjectRouter from "./subject.route";
import userRouter from "./user.route";
import learnerRouter from "./learner.route";
import tutorRouter from "./tutor.route";

const v1Router = Router();

v1Router.use("/auth", authRouter);
v1Router.use("/subjects", subjectRouter);
v1Router.use("/users", userRouter);
v1Router.use("/learners", learnerRouter);
v1Router.use("/tutors", tutorRouter);

export default v1Router;
28 changes: 14 additions & 14 deletions src/routes/v1/user.route.ts → src/routes/v1/learner.route.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
import * as userController from "@controllers/user.controller";
import * as learnerController from "@controllers/learner.controller";
import {
firebaseAuthMiddleware,
verifyUser,
verifyLearner,
} from "@middleware/auth.middleware";
import {
validateProfilePictureUpload,
validator,
} from "@middleware/validation.middleware";
import { changePasswordSchema } from "@schemas/auth.schema";
import { updateProfileSchema } from "@schemas/user.schema";
import { updateProfileSchema } from "@schemas/learner.schema";
import { Router } from "express";

// /api/v1/users
const userRouter = Router();
userRouter.use(firebaseAuthMiddleware);
userRouter.use(verifyUser);
// /api/v1/learners
const learnerRouter = Router();
learnerRouter.use(firebaseAuthMiddleware);
learnerRouter.use(verifyLearner);

userRouter.patch(
learnerRouter.patch(
"/profile",
validator(updateProfileSchema),
userController.updateProfile,
learnerController.updateProfile,
);

userRouter.put(
learnerRouter.put(
"/profile/picture",
validateProfilePictureUpload,
userController.updateProfilePicture,
learnerController.updateProfilePicture,
);

// TODO: harus validasi dulu password lamanya. Di firebase-admin ga ada kayak compare password
// jadi harus di handle di client atau generate password reset link yang dikirim di email
userRouter.put(
learnerRouter.put(
"/password",
validator(changePasswordSchema),
userController.changePassword,
learnerController.changePassword,
);

export default userRouter;
export default learnerRouter;
23 changes: 6 additions & 17 deletions src/schemas/user.schema.ts → src/schemas/learner.schema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { firestore } from "@/config";
import { validateInterests } from "@services/learner.service";
import { z } from "zod";

export const userSchema = z.object({
export const learnerSchema = z.object({
id: z.string(),
name: z.string().min(3, "Name must be at least 3 characters"),
phoneNum: z
Expand All @@ -23,23 +23,12 @@ export const userSchema = z.object({
interests: z
.array(z.string())
.superRefine(async (interests, ctx) => {
try {
const subjectsSnapshot = await firestore.collection("subjects").get();
const validSubjects = subjectsSnapshot.docs.map((doc) => doc.id);
const isValid = await validateInterests(interests);

const invalidInterests = interests.filter(
(interest) => !validSubjects.includes(interest),
);
if (invalidInterests.length > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Invalid interests found: ${invalidInterests.join(", ")}`,
});
}
} catch (error) {
if (!isValid) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Failed to validate interests due to an internal error",
message: "Invalid interests",
});
}
})
Expand All @@ -56,7 +45,7 @@ export const userSchema = z.object({
});

export const updateProfileSchema = z.object({
body: userSchema.omit({
body: learnerSchema.omit({
id: true,
createdAt: true,
updatedAt: true,
Expand Down
58 changes: 11 additions & 47 deletions src/schemas/tutor.schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { firestore } from "@/config";
import { checkSubjectExists } from "@services/subject.service";
import { checkTutorExists, validateServices } from "@services/tutor.service";
import { z } from "zod";

export const tutorSchema = z.object({
Expand All @@ -23,26 +24,11 @@ export const tutorSchema = z.object({
services: z
.array(z.string())
.superRefine(async (services, ctx) => {
// Validate that the services are valid by checking the tutor_services collection
try {
const servicesSnapshot = await firestore
.collection("tutor_services")
.get();
const validServices = servicesSnapshot.docs.map((doc) => doc.id);
const invalidServices = services.filter(
(service) => !validServices.includes(service),
);

if (invalidServices.length > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Invalid services found: ${invalidServices.join(", ")}`,
});
}
} catch (error) {
const isServicesValid = await validateServices(services);
if (!isServicesValid) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Failed to validate services due to an internal error",
message: "Invalid services",
});
}
})
Expand All @@ -67,42 +53,20 @@ export const updateProfileSchema = z.object({
export const tutorServiceSchema = z.object({
id: z.string(),
tutorId: z.string().superRefine(async (tutorId, ctx) => {
// Validate that the tutor exists by checking the tutors collection
try {
const tutorSnapshot = await firestore
.collection("tutors")
.doc(tutorId)
.get();
if (!tutorSnapshot.exists) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Tutor does not exist",
});
}
} catch (error) {
const exists = await checkTutorExists(tutorId);
if (!exists) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Failed to validate tutor due to an internal error",
message: "Tutor does not exist",
});
}
}),
subjectId: z.string().superRefine(async (subjectId, ctx) => {
// Validate that the subject exists by checking the subjects collection
try {
const subjectSnapshot = await firestore
.collection("subjects")
.doc(subjectId)
.get();
if (!subjectSnapshot.exists) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Subject does not exist",
});
}
} catch (error) {
const exists = await checkSubjectExists(subjectId);
if (!exists) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Failed to validate subject due to an internal error",
message: "Subject does not exist",
});
}
}),
Expand Down
4 changes: 2 additions & 2 deletions src/services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { auth, firestore } from "@/config";

export const registerUser = async (
export const registerLearner = async (
name: string,
email: string,
password: string,
) => {
const user = await auth.createUser({ displayName: name, email, password });

await firestore.collection("users").doc(user.uid).set({
await firestore.collection("learners").doc(user.uid).set({
name,
createdAt: new Date(),
});
Expand Down
Loading

0 comments on commit 77c1c7d

Please sign in to comment.