From a887abd5d3ff7b4f3d74ddd36a2f8fffa31fcb4e Mon Sep 17 00:00:00 2001 From: Veirt Date: Fri, 15 Nov 2024 01:52:14 +0800 Subject: [PATCH] feat(user): change password endpoint --- src/controllers/user.controller.ts | 23 +++++++++++++++++++++++ src/middleware/validation.middleware.ts | 4 ++-- src/routes/v1/user.route.ts | 9 +++++++++ src/schemas/auth.schema.ts | 13 +++++++++++++ src/schemas/tutor.schema.ts | 1 - src/schemas/user.schema.ts | 1 - src/services/user.service.ts | 12 +++++++++++- 7 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts index e4498ec..e94cf05 100644 --- a/src/controllers/user.controller.ts +++ b/src/controllers/user.controller.ts @@ -1,6 +1,7 @@ import { Controller } from "@/types"; import { logger } from "@middleware/logging.middleware"; import { updateProfileSchema } from "@schemas/user.schema"; +import { changePasswordSchema } from "@schemas/auth.schema"; import * as userService from "@services/user.service"; import { RequestHandler } from "express"; import { z } from "zod"; @@ -51,3 +52,25 @@ export const updateProfilePicture: RequestHandler = async (req, res) => { }); } }; + +type ChangePasswordSchema = z.infer; +export const changePassword: Controller = async ( + req, + res, +) => { + try { + await userService.changePassword(req.user.id, req.body.newPassword); + + res.json({ + status: "success", + message: "Password changed successfully", + }); + } catch (error) { + logger.error(`Failed to change password: ${error}`); + + res.status(500).json({ + status: "error", + message: "Failed to change password", + }); + } +}; diff --git a/src/middleware/validation.middleware.ts b/src/middleware/validation.middleware.ts index a12323e..b949f9f 100644 --- a/src/middleware/validation.middleware.ts +++ b/src/middleware/validation.middleware.ts @@ -1,10 +1,10 @@ import { RequestHandler } from "express"; -import { AnyZodObject, z } from "zod"; +import { z, ZodTypeAny } from "zod"; import { logger } from "./logging.middleware"; import { imageUpload } from "./multer.middleware"; export const validator = - (schema: AnyZodObject): RequestHandler => + (schema: ZodTypeAny): RequestHandler => async (req, res, next) => { try { const result = await schema.parseAsync({ diff --git a/src/routes/v1/user.route.ts b/src/routes/v1/user.route.ts index 7adc661..b319b61 100644 --- a/src/routes/v1/user.route.ts +++ b/src/routes/v1/user.route.ts @@ -7,6 +7,7 @@ import { validateProfilePictureUpload, validator, } from "@middleware/validation.middleware"; +import { changePasswordSchema } from "@schemas/auth.schema"; import { updateProfileSchema } from "@schemas/user.schema"; import { Router } from "express"; @@ -27,4 +28,12 @@ userRouter.put( userController.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( + "/password", + validator(changePasswordSchema), + userController.changePassword, +); + export default userRouter; diff --git a/src/schemas/auth.schema.ts b/src/schemas/auth.schema.ts index 14127a9..87c2956 100644 --- a/src/schemas/auth.schema.ts +++ b/src/schemas/auth.schema.ts @@ -7,3 +7,16 @@ export const registerSchema = z.object({ password: z.string().min(8, "Password must be at least 8 characters"), }), }); + +export const changePasswordSchema = z + .object({ + body: z.object({ + currentPassword: z.string(), + newPassword: z.string().min(8, "Password must be at least 8 characters"), + confirmPassword: z.string(), + }), + }) + .refine((data) => data.body.newPassword === data.body.confirmPassword, { + message: "Passwords do not match", + path: ["confirmPassword"], + }); diff --git a/src/schemas/tutor.schema.ts b/src/schemas/tutor.schema.ts index 2212945..2a3d54d 100644 --- a/src/schemas/tutor.schema.ts +++ b/src/schemas/tutor.schema.ts @@ -19,7 +19,6 @@ export const tutorSchema = z.object({ message: "Gender must be one of the following: male, female, or prefer not to say", }) - .default("prefer not to say") .optional(), services: z .array(z.string()) diff --git a/src/schemas/user.schema.ts b/src/schemas/user.schema.ts index 6068c00..f0b2a08 100644 --- a/src/schemas/user.schema.ts +++ b/src/schemas/user.schema.ts @@ -19,7 +19,6 @@ export const userSchema = z.object({ message: "Gender must be one of the following: male, female, or prefer not to say", }) - .default("prefer not to say") .optional(), interests: z .array(z.string()) diff --git a/src/services/user.service.ts b/src/services/user.service.ts index 3ede66a..4985c75 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -1,4 +1,4 @@ -import { firestore, GCS_BUCKET_NAME } from "@/config"; +import { auth, firestore, GCS_BUCKET_NAME } from "@/config"; import { downscaleImage } from "@/helpers/image.helper"; import { Storage } from "@google-cloud/storage"; import { logger } from "@middleware/logging.middleware"; @@ -57,3 +57,13 @@ export const updateUserProfilePicture = async ( return undefined; } }; + +export const changePassword = async (userId: string, newPassword: string) => { + try { + await auth.updateUser(userId, { + password: newPassword, + }); + } catch (error) { + throw new Error(`Failed to change password: ${error}`); + } +};