From 61f5866ced8e69a42d72d39693714e88ad29c94c Mon Sep 17 00:00:00 2001 From: Muhammad Zhafran Ilham Date: Mon, 13 Jul 2026 18:02:23 +0700 Subject: [PATCH] feat: limit update nama untuk user intern maksimal sekali --- .../migration.sql | 2 + prisma/schema.prisma | 1 + src/modules/auth/services/auth.service.js | 20 +- src/routes/auth.routes.js | 230 ++++++++++++++++-- 4 files changed, 226 insertions(+), 27 deletions(-) create mode 100644 prisma/migrations/20260713102228_add_user_name_change_counter/migration.sql diff --git a/prisma/migrations/20260713102228_add_user_name_change_counter/migration.sql b/prisma/migrations/20260713102228_add_user_name_change_counter/migration.sql new file mode 100644 index 0000000..6ce5731 --- /dev/null +++ b/prisma/migrations/20260713102228_add_user_name_change_counter/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE `user` ADD COLUMN `changes` INTEGER NOT NULL DEFAULT 0; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 566af11..379715d 100755 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -189,6 +189,7 @@ model User { professional_bio String? @db.Text intern_position String? @db.VarChar(255) intern_interest_group String? @db.VarChar(255) + changes Int @default(0) is_active Boolean @default(true) created_at DateTime @default(now()) updated_at DateTime @updatedAt diff --git a/src/modules/auth/services/auth.service.js b/src/modules/auth/services/auth.service.js index 8040a69..23e306d 100755 --- a/src/modules/auth/services/auth.service.js +++ b/src/modules/auth/services/auth.service.js @@ -244,7 +244,25 @@ class AuthService { } const updateData = {}; - if (full_name) updateData.full_name = full_name; + if (full_name !== undefined) { + const nextFullName = full_name.trim(); + const currentFullName = existingUser.full_name?.trim(); + + if (!nextFullName) { + return error(new BadRequestError('Nama lengkap tidak boleh kosong')); + } + + if (nextFullName !== currentFullName) { + if (existingUser.changes >= 1) { + return error(new ConflictError('Nama hanya dapat diubah satu kali')); + } + + updateData.full_name = nextFullName; + updateData.changes = { + increment: 1, + }; + } + } if (email) updateData.email = email; if (hashedPassword) updateData.password = hashedPassword; if (professional_bio !== undefined) updateData.professional_bio = professional_bio; diff --git a/src/routes/auth.routes.js b/src/routes/auth.routes.js index f660492..90ba201 100755 --- a/src/routes/auth.routes.js +++ b/src/routes/auth.routes.js @@ -50,45 +50,223 @@ router.post("/login", authController.login); * @swagger * /auth-api/me: * get: - * summary: Get authenticated admin details + * summary: Get authenticated user details + * description: Retrieve the currently authenticated user profile. Response data depends on the authenticated user's role. * tags: [Auth] * security: * - bearerAuth: [] * responses: * 200: - * description: Get admin details successfully + * description: Get authenticated user details successfully * content: * application/json: * schema: - * type: object - * properties: - * message: - * type: string - * example: "Get admin successfully" - * data: - * type: object + * oneOf: + * - type: object * properties: - * id: + * status: + * type: boolean + * example: true + * data: + * type: object + * description: Intern profile data + * properties: + * id: + * type: integer + * example: 1 + * id_mahasiswa: + * type: integer + * nullable: true + * example: 1 + * id_lamaran_magang: + * type: integer + * nullable: true + * example: 1 + * full_name: + * type: string + * example: "Rafi Athallah" + * email: + * type: string + * example: "rafi@student.com" + * role: + * type: string + * example: "intern" + * professional_bio: + * type: string + * nullable: true + * example: null + * intern_position: + * type: string + * nullable: true + * example: null + * intern_interest_group: + * type: string + * nullable: true + * example: null + * changes: + * type: integer + * example: 0 + * description: Number of times the intern has changed their full name. + * is_active: + * type: boolean + * example: true + * created_at: + * type: string + * format: date-time + * example: "2026-07-10T22:45:24.305Z" + * updated_at: + * type: string + * format: date-time + * example: "2026-07-10T22:45:24.305Z" + * lamaran_magang: + * type: object + * nullable: true + * properties: + * id: + * type: integer + * example: 1 + * id_mahasiswa: + * type: integer + * example: 1 + * id_lowongan_magang: + * type: string + * example: "LOW-001" + * status: + * type: string + * example: "diterima" + * batch: + * type: integer + * example: 3 + * created_at: + * type: string + * format: date-time + * example: "2026-07-10T22:45:24.186Z" + * update_at: + * type: string + * format: date-time + * example: "2026-07-10T22:45:24.185Z" + * lowongan_magang: + * type: object + * nullable: true + * properties: + * posisi: + * type: string + * example: "Web Developer" + * kelompok_peminatan: + * type: string + * example: "Software Engineering" + * position: + * type: string + * nullable: true + * example: "Web Developer" + * kelompok_peminatan: + * type: string + * nullable: true + * example: "Software Engineering" + * message: + * type: string + * example: "Pengguna berhasil diambil" + * code: * type: integer - * example: 1 - * nama_depan: + * example: 200 + * - type: object + * properties: + * status: + * type: boolean + * example: true + * data: + * type: object + * description: Admin or mentor profile data + * properties: + * id: + * type: integer + * example: 1 + * nama_depan: + * type: string + * example: "Admin" + * nama_belakang: + * type: string + * nullable: true + * example: "One" + * role: + * type: string + * example: "admin" + * email: + * type: string + * example: "admin1@internify.com" + * signature: + * type: string + * nullable: true + * example: "Admin-One-1783723523926" + * profile_picture: + * type: string + * nullable: true + * example: "https://placehold.co/150" + * professional_bio: + * type: string + * nullable: true + * example: "Senior Program Manager at Internify." + * message: * type: string - * example: "John" - * nama_belakang: - * type: string - * example: "Doe" - * email: - * type: string - * example: "admin@example.com" - * role: - * type: string - * example: "admin" + * example: "Pengguna berhasil diambil" + * code: + * type: integer + * example: 200 + * examples: + * intern: + * summary: Intern response + * value: + * status: true + * data: + * id: 1 + * id_mahasiswa: 1 + * id_lamaran_magang: 1 + * full_name: "Rafi Athallah" + * email: "rafi@student.com" + * role: "intern" + * professional_bio: null + * intern_position: null + * intern_interest_group: null + * changes: 0 + * is_active: true + * created_at: "2026-07-10T22:45:24.305Z" + * updated_at: "2026-07-10T22:45:24.305Z" + * lamaran_magang: + * id: 1 + * id_mahasiswa: 1 + * id_lowongan_magang: "LOW-001" + * status: "diterima" + * batch: 3 + * created_at: "2026-07-10T22:45:24.186Z" + * update_at: "2026-07-10T22:45:24.185Z" + * lowongan_magang: + * posisi: "Web Developer" + * kelompok_peminatan: "Software Engineering" + * position: "Web Developer" + * kelompok_peminatan: "Software Engineering" + * message: "Pengguna berhasil diambil" + * code: 200 + * admin: + * summary: Admin response + * value: + * status: true + * data: + * id: 1 + * nama_depan: "Admin" + * nama_belakang: "One" + * role: "admin" + * email: "admin1@internify.com" + * signature: "Admin-One-1783723523926" + * profile_picture: "https://placehold.co/150" + * professional_bio: "Senior Program Manager at Internify." + * message: "Pengguna berhasil diambil" + * code: 200 * 401: * description: Authorization header missing or invalid * 403: * description: Invalid or expired token * 404: - * description: Admin not found + * description: User not found * 500: * description: Internal server error */ @@ -99,7 +277,7 @@ router.get("/me", verifyJWT, authController.me); * /auth-api/update-profile: * patch: * summary: Update authenticated user profile - * description: Update profile details (full name, email, password, professional bio). Admins can also upload a profile picture. + * description: Update profile details (full name, email, password, professional bio). Admins can also upload a profile picture. Interns can only change full_name once. * tags: [Auth] * security: * - bearerAuth: [] @@ -136,10 +314,10 @@ router.get("/me", verifyJWT, authController.me); * 403: * description: Forbidden (Interns uploading images) * 409: - * description: Conflict (Email already in use) + * description: Conflict. Email already in use or intern has already changed full name once. * 500: * description: Internal server error */ router.patch('/update-profile', verifyJWT, multer.single('profile_picture'), multerErrorHandler, authController.updateProfile); -module.exports = router; \ No newline at end of file +module.exports = router;