diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index cf615716..76e0ea78 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -5,6 +5,8 @@ import { BadRequest, ResourceNotFound } from "../middleware"; import { sendJsonResponse } from "../helpers"; import asyncHandler from "../middleware/asyncHandler"; +const userService = new UserService(); + class UserController { /** * @swagger @@ -57,48 +59,46 @@ class UserController { * description: Internal Server Error * */ - static getProfile = asyncHandler( - async (req: Request, res: Response, next: NextFunction) => { - const { id } = req.user; + static getProfile = asyncHandler(async (req: Request, res: Response, next: NextFunction) => { + const { id } = req.user; + + if (!id) { + throw new BadRequest("Unauthorized! No ID provided"); + } - if (!id) { - throw new BadRequest("Unauthorized! No ID provided"); - } + if (!validate(id)) { + throw new BadRequest("Unauthorized! Invalid User Id Format"); + } - if (!validate(id)) { - throw new BadRequest("Unauthorized! Invalid User Id Format"); - } + const user = await UserService.getUserById(id); + if (!user) { + throw new ResourceNotFound("User Not Found!"); + } - const user = await UserService.getUserById(id); - if (!user) { - throw new ResourceNotFound("User Not Found!"); - } + if (user?.deletedAt || user?.is_deleted) { + throw new ResourceNotFound("User not found!"); + } - if (user?.deletedAt || user?.is_deleted) { - throw new ResourceNotFound("User not found!"); - } + sendJsonResponse(res, 200, "User profile details retrieved successfully", { + id: user.id, + first_name: user?.first_name, + last_name: user?.last_name, + profile_id: user?.profile?.id, + username: user?.profile?.username, + bio: user?.profile?.bio, + job_title: user?.profile?.jobTitle, + language: user?.profile?.language, + pronouns: user?.profile?.pronouns, + department: user?.profile?.department, + social_links: user?.profile?.social_links, + timezones: user?.profile?.timezones, + }); + }); - sendJsonResponse( - res, - 200, - "User profile details retrieved successfully", - { - id: user.id, - first_name: user?.first_name, - last_name: user?.last_name, - profile_id: user?.profile?.id, - username: user?.profile?.username, - bio: user?.profile?.bio, - job_title: user?.profile?.jobTitle, - language: user?.profile?.language, - pronouns: user?.profile?.pronouns, - department: user?.profile?.department, - social_links: user?.profile?.social_links, - timezones: user?.profile?.timezones, - }, - ); - }, - ); + static updateUser = asyncHandler(async (req: Request, res: Response) => { + const user = await userService.updateUserProfile(req.params.id, req.body, req.file); + sendJsonResponse(res, 200, "Profile successfully updated", user); + }); } export { UserController }; diff --git a/src/routes/user.ts b/src/routes/user.ts index aecabdfd..e4f13de0 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -1,9 +1,13 @@ import { Router } from "express"; import { UserController } from "../controllers"; import { authMiddleware } from "../middleware"; +import { multerConfig } from "../config/multer"; + +const upload = multerConfig.single("profile_pic_url"); const userRoute = Router(); userRoute.get("/users/me", authMiddleware, UserController.getProfile); +userRoute.put("/users/:id", authMiddleware, upload, UserController.updateUser); export { userRoute }; diff --git a/src/services/userservice.ts b/src/services/userservice.ts index c598fe6b..a1762b6e 100644 --- a/src/services/userservice.ts +++ b/src/services/userservice.ts @@ -1,8 +1,19 @@ -import { User } from "../models/user"; +import { Repository } from "typeorm"; +import { User, Profile } from "../models"; import AppDataSource from "../data-source"; -import { ResourceNotFound } from "../middleware"; +import { ResourceNotFound, HttpError } from "../middleware"; +import { cloudinary } from "../config/multer"; +import { IUserProfileUpdate } from "../types"; export class UserService { + private userRepository: Repository; + private profileRepository: Repository; + + constructor() { + this.userRepository = AppDataSource.getRepository(User); + this.profileRepository = AppDataSource.getRepository(Profile); + } + static async getUserById(id: string): Promise { const userRepository = AppDataSource.getRepository(User); const user = await userRepository.findOne({ @@ -17,4 +28,63 @@ export class UserService { return user; } + + public async updateUserProfile(id: string, payload: IUserProfileUpdate, file: Express.Multer.File): Promise { + try { + const user = await this.userRepository.findOne({ + where: { id }, + relations: ["profile"], + }); + + if (!user) { + throw new ResourceNotFound("User not found"); + } + + const userUpdates: Partial = {}; + if (payload.first_name) userUpdates.first_name = payload.first_name; + if (payload.last_name) userUpdates.last_name = payload.last_name; + if (payload.phone) userUpdates.phone = payload.phone; + + const profileUpdates: Partial = {}; + if (payload.username) profileUpdates.username = payload.username; + if (payload.jobTitle) profileUpdates.jobTitle = payload.jobTitle; + if (payload.pronouns) profileUpdates.pronouns = payload.pronouns; + if (payload.department) profileUpdates.department = payload.department; + if (payload.bio) profileUpdates.bio = payload.bio; + if (payload.social_links) profileUpdates.social_links = payload.social_links; + if (payload.language) profileUpdates.language = payload.language; + if (payload.region) profileUpdates.region = payload.region; + if (payload.timezones) profileUpdates.timezones = payload.timezones; + + if (file) { + const oldImageUrl = user.profile.profile_pic_url; + if (oldImageUrl) { + const publicId = oldImageUrl.split("/").pop()?.split(".")[0]; + await cloudinary.uploader.destroy(publicId); + } + + const { secure_url } = await cloudinary.uploader.upload(file.path); + profileUpdates.profile_pic_url = secure_url; + } + + await this.userRepository.update(user.id, userUpdates); + await this.profileRepository.update(user.profile.id, profileUpdates); + + const updatedUser = await this.userRepository.findOne({ + where: { id }, + relations: ["profile"], + }); + + if (updatedUser) { + delete updatedUser.password; + } + + return updatedUser as User; + } catch (error) { + if (error instanceof HttpError) { + throw error; + } + throw new HttpError(error.status || 500, error.message || error); + } + } } diff --git a/src/types/index.ts b/src/types/index.ts index 5e7870db..85782cc7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -91,3 +91,19 @@ export interface GoogleUser { picture: string; sub: string; } + +export interface IUserProfileUpdate { + first_name: string; + last_name: string; + phone: string; + username: string; + jobTitle: string; + pronouns: string; + department: string; + bio: string; + social_links: string[]; + language: string; + region: string; + timezones: string; + profile_pic_url: string; +}