From 9a0334658b05d35ee940da0a54ff8bdbcf95c74b Mon Sep 17 00:00:00 2001 From: Veirt Date: Thu, 14 Nov 2024 22:56:14 +0800 Subject: [PATCH] refactor(controllers): move controllers logic to separate services --- src/controllers/auth.controller.ts | 21 +++----- src/controllers/subject.controller.ts | 17 ++----- src/controllers/user.controller.ts | 73 +++++++++++---------------- src/helpers/image.helper.ts | 6 +++ src/services/auth.service.ts | 16 ++++++ src/services/subject.service.ts | 20 ++++++++ src/services/upload.service.ts | 31 ------------ src/services/user.service.ts | 59 ++++++++++++++++++++++ 8 files changed, 140 insertions(+), 103 deletions(-) create mode 100644 src/helpers/image.helper.ts create mode 100644 src/services/auth.service.ts create mode 100644 src/services/subject.service.ts delete mode 100644 src/services/upload.service.ts create mode 100644 src/services/user.service.ts diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index bc83fb3..772215f 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -1,28 +1,19 @@ -import { Request, Response } from "express"; +import type { Controller } from "@/types"; import { registerSchema } from "@schemas/auth.schema"; -import { auth, firestore } from "@/config"; +import * as authService from "@services/auth.service"; import { z } from "zod"; -import type { Controller } from "@/types"; - -export const helloAuth = (_req: Request, res: Response) => { - res.json({ message: "hello auth" }); -}; type RegisterSchema = z.infer; export const register: Controller = async (req, res) => { try { - const { name: displayName, email, password } = req.body; + const { name, email, password } = req.body; - const user = await auth.createUser({ displayName, email, password }); - firestore.collection("users").doc(user.uid).set({ - name: displayName, - createdAt: new Date(), - }); + const result = await authService.registerUser(name, email, password); res.status(201).json({ status: "success", - message: "register success", - data: { userId: user.uid }, + message: "Registration successful", + data: result, }); } catch (error) { if (error instanceof Error) { diff --git a/src/controllers/subject.controller.ts b/src/controllers/subject.controller.ts index 81c4ca9..77c2afb 100644 --- a/src/controllers/subject.controller.ts +++ b/src/controllers/subject.controller.ts @@ -1,26 +1,17 @@ -import { firestore } from "@/config"; -import { Controller, Subject } from "@/types"; +import * as subjectService from "@/services/subject.service"; +import { Controller } from "@/types"; import { logger } from "@middleware/logging.middleware"; export const getAllSubjects: Controller = async (_req, res) => { try { - const subjectsRef = firestore.collection("subjects"); - const snapshot = await subjectsRef.get(); - - const subjects = snapshot.docs.map((doc) => { - return { - id: doc.id, - name: doc.data().name, - iconUrl: doc.data().iconUrl, - }; - }); + const subjects = await subjectService.getAllSubjects(); res.json({ status: "success", data: subjects }); } catch (error) { logger.error(`Error when getting all subjects: ${error}`); res.status(400).json({ status: "fail", - message: "Failed to get all subjects.", + message: "Failed to get all subjects", }); } }; diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts index 8d203ba..9ff4d87 100644 --- a/src/controllers/user.controller.ts +++ b/src/controllers/user.controller.ts @@ -1,10 +1,7 @@ -import { firestore } from "@/config"; import { Controller } from "@/types"; import { logger } from "@middleware/logging.middleware"; import { updateProfileSchema } from "@schemas/user.schema"; -import { uploadProfilePicture } from "@services/upload.service"; -import type { RequestHandler } from "express"; -import firebase from "firebase-admin"; +import * as userService from "@services/user.service"; import { z } from "zod"; type UpdateProfileSchema = z.infer; @@ -13,56 +10,44 @@ export const updateProfile: Controller = async ( res, ) => { try { - const { location, ...restOfData } = req.body; + await userService.updateUserProfile(req.user.id, req.body); - // Handle location separately if present - const newData = location - ? { - ...restOfData, - location: new firebase.firestore.GeoPoint( - location.latitude, - location.longitude, - ), - } - : restOfData; - - firestore - .collection("users") - .doc(req.user.id) - .update({ - ...newData, - updatedAt: new Date(), - }); + res.status(200).json({ + status: "success", + message: "User profile updated successfully", + }); } catch (error) { logger.debug("Failed to update profile", error); - res - .status(500) - .json({ status: "error", message: "Failed to update profile" }); - return; + res.status(500).json({ + status: "error", + message: "Failed to update profile", + }); } - - res - .status(200) - .json({ status: "success", message: "User profile updated successfully" }); }; -export const updateProfilePicture: RequestHandler = async (req, res) => { - const url = await uploadProfilePicture(req.file!, req.user.id); +export const updateProfilePicture: Controller = async (req, res) => { + try { + // Extract the file and call the service to upload the profile picture + const url = await userService.updateUserProfilePicture( + req.file!, + req.user.id, + ); + + res.json({ + status: "success", + message: "Profile picture updated successfully", + data: { url }, + }); + } catch (error) { + logger.debug("Failed to upload profile picture", error); - if (!url) { res.status(500).json({ status: "error", - message: "Failed to upload profile picture", + message: + error instanceof Error + ? error.message + : "Failed to upload profile picture", }); - return; } - - res.json({ - status: "success", - message: "Profile picture updated successfully", - data: { - url, - }, - }); }; diff --git a/src/helpers/image.helper.ts b/src/helpers/image.helper.ts new file mode 100644 index 0000000..231e4ea --- /dev/null +++ b/src/helpers/image.helper.ts @@ -0,0 +1,6 @@ +import sharp from "sharp"; + +// Downscale image to 1024x1024 and convert to JPEG +export const downscaleImage = async (buffer: Buffer) => { + return sharp(buffer).resize(1024, 1024).jpeg({ quality: 80 }).toBuffer(); +}; diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts new file mode 100644 index 0000000..5e0a1d4 --- /dev/null +++ b/src/services/auth.service.ts @@ -0,0 +1,16 @@ +import { auth, firestore } from "@/config"; + +export const registerUser = async ( + name: string, + email: string, + password: string, +) => { + const user = await auth.createUser({ displayName: name, email, password }); + + await firestore.collection("users").doc(user.uid).set({ + name, + createdAt: new Date(), + }); + + return { userId: user.uid }; +}; diff --git a/src/services/subject.service.ts b/src/services/subject.service.ts new file mode 100644 index 0000000..ca027f7 --- /dev/null +++ b/src/services/subject.service.ts @@ -0,0 +1,20 @@ +import { firestore } from "@/config"; + +export const getAllSubjects = async () => { + try { + const subjectsRef = firestore.collection("subjects"); + const snapshot = await subjectsRef.get(); + + const subjects = snapshot.docs.map((doc) => { + return { + id: doc.id, + name: doc.data().name, + iconUrl: doc.data().iconUrl, + }; + }); + + return subjects; + } catch (error) { + throw new Error(`Error when getting all subjects: ${error}`); + } +}; diff --git a/src/services/upload.service.ts b/src/services/upload.service.ts deleted file mode 100644 index 4615ced..0000000 --- a/src/services/upload.service.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { GCS_BUCKET_NAME } from "@/config"; -import { Storage } from "@google-cloud/storage"; -import { logger } from "@middleware/logging.middleware"; -import sharp from "sharp"; - -const storage = new Storage(); - -// Downscale image to 1024x1024 and convert to JPEG -const downscaleImage = async (buffer: Buffer) => { - return sharp(buffer).resize(1024, 1024).jpeg({ quality: 80 }).toBuffer(); -}; - -// Upload to: profile-pictures/{uid}.jpg -export const uploadProfilePicture = async ( - file: Express.Multer.File, - uid: string, -) => { - try { - const image = await downscaleImage(file.buffer); - - const bucket = storage.bucket(GCS_BUCKET_NAME); - const bucketFile = bucket.file(`profile-pictures/${uid}.jpg`); - await bucketFile.save(image, { public: true }); - - return `https://storage.googleapis.com/${GCS_BUCKET_NAME}/${bucketFile.name}`; - } catch (error) { - logger.error(`Failed to upload profile picture: ${error}`); - - return undefined; - } -}; diff --git a/src/services/user.service.ts b/src/services/user.service.ts new file mode 100644 index 0000000..3ede66a --- /dev/null +++ b/src/services/user.service.ts @@ -0,0 +1,59 @@ +import { firestore, GCS_BUCKET_NAME } from "@/config"; +import { downscaleImage } from "@/helpers/image.helper"; +import { Storage } from "@google-cloud/storage"; +import { logger } from "@middleware/logging.middleware"; +import { updateProfileSchema } from "@schemas/user.schema"; +import firebase from "firebase-admin"; +import { z } from "zod"; + +const storage = new Storage(); + +export const updateUserProfile = async ( + userId: string, + data: z.infer["body"], +) => { + const { location, ...restOfData } = data; + + // Handle location separately if present + const newData = location + ? { + ...restOfData, + location: new firebase.firestore.GeoPoint( + location.latitude, + location.longitude, + ), + } + : restOfData; + + try { + await firestore + .collection("users") + .doc(userId) + .update({ + ...newData, + updatedAt: new Date(), + }); + } catch (error) { + throw new Error("Failed to update profile"); + } +}; + +// Upload to: profile-pictures/{uid}.jpg +export const updateUserProfilePicture = async ( + file: Express.Multer.File, + userId: string, +) => { + try { + const image = await downscaleImage(file.buffer); + const bucket = storage.bucket(GCS_BUCKET_NAME); + const bucketFile = bucket.file(`profile-pictures/${userId}.jpg`); + + await bucketFile.save(image, { public: true }); + + return `https://storage.googleapis.com/${GCS_BUCKET_NAME}/${bucketFile.name}`; + } catch (error) { + logger.error(`Failed to upload profile picture: ${error}`); + + return undefined; + } +};