From f0e1614709a0ad38b679e871afa3d46cd0608ed9 Mon Sep 17 00:00:00 2001 From: rafiarrafif Date: Mon, 16 Jun 2025 21:37:53 +0700 Subject: [PATCH] add:module:user:repository:checkUserEmailAndUsernameAvailabillity | add repository for check username and email availabillity --- .gitignore | 6 +++- src/helpers/http/jwt/decode/index.ts | 8 +++-- src/modules/debug/debug.controller.ts | 13 ++++++-- src/modules/debug/index.ts | 4 +-- .../user/controller/createUser.controller.ts | 2 +- .../user/controller/editUser.controller.ts | 31 +++++++++++++++++++ .../user/controller/getAllUser.controller.ts | 2 +- src/modules/user/index.ts | 10 +++--- ...EmailAndUsernameAvailability.repository.ts | 23 ++++++++++++++ .../repositories/updateUser.repository.ts | 23 ++++++++++++++ .../user/services/createUser.service.ts | 9 +++--- src/modules/user/services/editUser.service.ts | 24 ++++++++++++++ src/routes.ts | 12 +++---- 13 files changed, 143 insertions(+), 24 deletions(-) create mode 100644 src/modules/user/controller/editUser.controller.ts create mode 100644 src/modules/user/repositories/checkUserEmailAndUsernameAvailability.repository.ts create mode 100644 src/modules/user/repositories/updateUser.repository.ts create mode 100644 src/modules/user/services/editUser.service.ts diff --git a/.gitignore b/.gitignore index d35a8f6..1b5d52f 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,8 @@ docker-compose.override.yml # compiled output server -server.exe \ No newline at end of file +server.exe + +# debug and cached routes +/src/routes.ts +/src/modules/debug \ No newline at end of file diff --git a/src/helpers/http/jwt/decode/index.ts b/src/helpers/http/jwt/decode/index.ts index 539228c..262b083 100644 --- a/src/helpers/http/jwt/decode/index.ts +++ b/src/helpers/http/jwt/decode/index.ts @@ -1,14 +1,16 @@ import jwt from "jsonwebtoken"; +import { JWTSessionPayload } from "../../../../modules/auth/auth.types"; +import { AppError } from "../../../error/instances/app"; export const jwtDecode = (payload: string) => { // return payload; - if (!payload) throw "JWT decode payload not found"; + if (!payload) throw new AppError(401, "Unauthorized"); const JWTKey = process.env.JWT_SECRET!; try { const decodedPayload = jwt.verify(payload, JWTKey); - return decodedPayload; + return decodedPayload as JWTSessionPayload; } catch (error) { - throw "JWT expired or not valid"; + throw new AppError(401, "Invalid or expired token"); } }; diff --git a/src/modules/debug/debug.controller.ts b/src/modules/debug/debug.controller.ts index 08c39d6..c5c2351 100644 --- a/src/modules/debug/debug.controller.ts +++ b/src/modules/debug/debug.controller.ts @@ -2,11 +2,20 @@ import { Context } from "elysia"; import { mainErrorHandler } from "../../helpers/error/handler"; import { debugService } from "./debug.service"; import { returnWriteResponse } from "../../helpers/callback/httpResponse"; +import { getCookie } from "../../helpers/http/userHeader/cookies/getCookies"; +import { checkUserEmailAndUsernameAvailabillity } from "../user/repositories/checkUserEmailAndUsernameAvailability.repository"; +import { jwtDecode } from "../../helpers/http/jwt/decode"; export const debugController = async (ctx: Context) => { try { - const dataFromService = await debugService(); - return returnWriteResponse(ctx.set, 200, "Message Sent", dataFromService); + const userCookie = getCookie(ctx); + const jwtSession = jwtDecode(userCookie.auth_token!); + jwtSession.user.email = ctx.params.email; + jwtSession.user.username = ctx.params.username; + const checkAvailabillity = await checkUserEmailAndUsernameAvailabillity( + jwtSession.user + ); + return checkAvailabillity; } catch (error) { return mainErrorHandler(ctx.set, error); } diff --git a/src/modules/debug/index.ts b/src/modules/debug/index.ts index 465b3a8..edb9692 100644 --- a/src/modules/debug/index.ts +++ b/src/modules/debug/index.ts @@ -1,7 +1,7 @@ import Elysia from "elysia"; import { debugController } from "./debug.controller"; -export const debugModule = new Elysia({ prefix: "/debug" }).get( - "/", +export const debugModule = new Elysia({ prefix: "/debug" }).post( + "/:username/:email", debugController ); diff --git a/src/modules/user/controller/createUser.controller.ts b/src/modules/user/controller/createUser.controller.ts index 6f197ea..3c4dead 100644 --- a/src/modules/user/controller/createUser.controller.ts +++ b/src/modules/user/controller/createUser.controller.ts @@ -27,7 +27,7 @@ import { createUserSchema } from "../schemas/createUser.schema"; * "password": "password123" * } */ -export const createUser = async ( +export const createUserController = async ( ctx: Context & { body: Prisma.UserCreateInput } ) => { // Validate the user input using a validation schema diff --git a/src/modules/user/controller/editUser.controller.ts b/src/modules/user/controller/editUser.controller.ts new file mode 100644 index 0000000..dc90db3 --- /dev/null +++ b/src/modules/user/controller/editUser.controller.ts @@ -0,0 +1,31 @@ +import { Context } from "elysia"; +import { + returnErrorResponse, + returnWriteResponse, +} from "../../../helpers/callback/httpResponse"; +import { mainErrorHandler } from "../../../helpers/error/handler"; +import { Prisma } from "@prisma/client"; +import { editUserService } from "../services/editUser.service"; +import { getCookie } from "../../../helpers/http/userHeader/cookies/getCookies"; + +export const editUserController = async ( + ctx: Context & { + params: { username: string }; + body: Prisma.UserUncheckedCreateInput; + } +) => { + try { + const userCookie = getCookie(ctx); + if (!userCookie.auth_token) + return returnErrorResponse(ctx.set, 401, "User Unauthenticated"); + + const editUser = await editUserService( + ctx.params.username, + userCookie.auth_token, + ctx.body + ); + return editUser; + } catch (error) { + return mainErrorHandler(ctx.set, error); + } +}; diff --git a/src/modules/user/controller/getAllUser.controller.ts b/src/modules/user/controller/getAllUser.controller.ts index 56be4e5..b23f05a 100644 --- a/src/modules/user/controller/getAllUser.controller.ts +++ b/src/modules/user/controller/getAllUser.controller.ts @@ -6,7 +6,7 @@ import { Context } from "elysia"; import { getAllUsersService } from "../services/getAllUser.service"; import { mainErrorHandler } from "../../../helpers/error/handler"; -export const getAllUser = async (ctx: Context) => { +export const getAllUserController = async (ctx: Context) => { try { const allUser = await getAllUsersService(); return returnReadResponse( diff --git a/src/modules/user/index.ts b/src/modules/user/index.ts index b13d1da..e787bda 100644 --- a/src/modules/user/index.ts +++ b/src/modules/user/index.ts @@ -1,7 +1,9 @@ import Elysia from "elysia"; -import { getAllUser } from "./controller/getAllUser.controller"; -import { createUser } from "./controller/createUser.controller"; +import { getAllUserController } from "./controller/getAllUser.controller"; +import { createUserController } from "./controller/createUser.controller"; +import { editUserController } from "./controller/editUser.controller"; export const userModule = new Elysia({ prefix: "/users" }) - .get("/", getAllUser) - .post("/", createUser); + .get("/", getAllUserController) + .post("/", createUserController) + .put("/:username", editUserController); diff --git a/src/modules/user/repositories/checkUserEmailAndUsernameAvailability.repository.ts b/src/modules/user/repositories/checkUserEmailAndUsernameAvailability.repository.ts new file mode 100644 index 0000000..bb89b51 --- /dev/null +++ b/src/modules/user/repositories/checkUserEmailAndUsernameAvailability.repository.ts @@ -0,0 +1,23 @@ +import { Prisma } from "@prisma/client"; +import { userModel } from "../user.model"; + +export const checkUserEmailAndUsernameAvailabillity = async ( + payload: Partial>> +) => { + try { + const checkUsernameAndEmailAvailabillity = await userModel.findFirst({ + where: { + OR: [ + { username: payload.username ?? undefined }, + { email: payload.email ?? undefined }, + ], + NOT: { + id: payload.id, + }, + }, + }); + return checkUsernameAndEmailAvailabillity; + } catch (error) { + throw error; + } +}; diff --git a/src/modules/user/repositories/updateUser.repository.ts b/src/modules/user/repositories/updateUser.repository.ts new file mode 100644 index 0000000..31e77d1 --- /dev/null +++ b/src/modules/user/repositories/updateUser.repository.ts @@ -0,0 +1,23 @@ +import { Prisma } from "@prisma/client"; +import { userModel } from "../user.model"; + +export const updateUserRepo = async ( + identifier: string, + payload: Prisma.UserUncheckedCreateInput +) => { + try { + const userData = await userModel.update({ + where: { + username: identifier, + }, + data: { + username: payload.username, + name: payload.name, + birthDate: payload.name, + }, + }); + return userData; + } catch (error) { + throw error; + } +}; diff --git a/src/modules/user/services/createUser.service.ts b/src/modules/user/services/createUser.service.ts index 5bd847c..d60ce18 100644 --- a/src/modules/user/services/createUser.service.ts +++ b/src/modules/user/services/createUser.service.ts @@ -1,18 +1,19 @@ import { Prisma } from "@prisma/client"; import { hashPassword } from "../../../helpers/security/password/hash"; import { createUserRepo } from "../repositories/createUser.repository"; +import { ErrorForwarder } from "../../../helpers/error/instances/forwarder"; export const createUserService = async (userData: Prisma.UserCreateInput) => { - const { password, ...rest } = userData; // Destructure the password and the rest of the user data - const hashedPassword = await hashPassword(password); // Hash the password before saving to the database - try { + const { password, ...rest } = userData; // Destructure the password and the rest of the user data + const hashedPassword = await hashPassword(password); // Hash the password before saving to the database + const newUser = await createUserRepo({ ...rest, password: hashedPassword, }); return newUser; } catch (error) { - throw error; + ErrorForwarder(error, 500, "Internal server error"); } }; diff --git a/src/modules/user/services/editUser.service.ts b/src/modules/user/services/editUser.service.ts new file mode 100644 index 0000000..ad5eb96 --- /dev/null +++ b/src/modules/user/services/editUser.service.ts @@ -0,0 +1,24 @@ +import { Prisma } from "@prisma/client"; +import { jwtDecode } from "../../../helpers/http/jwt/decode"; +import { AppError } from "../../../helpers/error/instances/app"; +import { ErrorForwarder } from "../../../helpers/error/instances/forwarder"; +import { updateUserRepo } from "../repositories/updateUser.repository"; + +export const editUserService = async ( + identifier: string, + cookie: string, + payload: Prisma.UserUncheckedCreateInput +) => { + try { + // Decode the JWT token and verify the user, if the user is not the same as the identifier, throw an error + const jwtSession = jwtDecode(cookie); + if (jwtSession.user.username !== identifier) { + throw new AppError(401, "Unauthorized"); + } + + const updateUser = updateUserRepo(identifier, payload); + return updateUser; + } catch (error) { + ErrorForwarder(error, 500, "Internal server error"); + } +}; diff --git a/src/routes.ts b/src/routes.ts index 599c090..3a7a0b1 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,14 +1,14 @@ import Elysia from "elysia"; -import {authModule} from './modules/auth'; +import {userSessionModule} from './modules/userSession'; +import {userRoleModule} from './modules/userRole'; import {debugModule} from './modules/debug'; import {userModule} from './modules/user'; -import {userRoleModule} from './modules/userRole'; -import {userSessionModule} from './modules/userSession'; +import {authModule} from './modules/auth'; const routes = new Elysia() -.use(authModule) +.use(userSessionModule) +.use(userRoleModule) .use(debugModule) .use(userModule) -.use(userRoleModule) -.use(userSessionModule); +.use(authModule); export { routes };