feat: nambahin API assign member, list interns, update profile, serta list projects/tasks

This commit is contained in:
Rafi Athallah
2026-06-08 17:28:37 +07:00
parent f0df564499
commit edc287968d
14 changed files with 1295 additions and 36 deletions
@@ -3,7 +3,9 @@ const { response, data, error } = require('../../../helpers/utils/wrapper');
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
const { BadRequestError, UnauthorizedError, InternalServerError } = require('../../../helpers/error');
const { isValidPayload } = require('../../../helpers/utils/validator');
const { loginModel } = require('../models/auth.model');
const { loginModel, updateProfileModel } = require('../models/auth.model');
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
class AuthController {
async login(req, res) {
@@ -48,7 +50,7 @@ class AuthController {
return response(res, 'fail', error(new UnauthorizedError('Tidak diotorisasi: ID pengguna tidak ditemukan')));
}
const result = await authService.getCurrentUser(id);
const result = await authService.getCurrentUser(id, req.role);
if (result.err) {
return response(res, 'fail', result);
@@ -67,6 +69,48 @@ class AuthController {
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
}
}
async updateProfile(req, res) {
try {
const validatePayload = isValidPayload(req.body, updateProfileModel);
if (validatePayload.err) {
if (req.file?.filename) {
await deleteFileIfExists(req.file.filename).catch(() => { });
}
return response(
res,
'fail',
{ err: validatePayload.err, data: null },
'Data tidak valid',
ERROR.EXPECTATION_FAILED
);
}
const id = req.id;
const role = req.role;
if (!id || !role) {
if (req.file?.filename) {
await deleteFileIfExists(req.file.filename).catch(() => { });
}
return response(res, 'fail', error(new UnauthorizedError('Tidak diotorisasi: ID pengguna tidak ditemukan')));
}
const result = await authService.updateProfile(id, role, validatePayload.data, req.file);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Profil berhasil diperbarui', SUCCESS.OK);
} catch (err) {
if (req.file?.filename) {
await deleteFileIfExists(req.file.filename).catch(() => { });
}
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
}
}
}
module.exports = new AuthController();
+19
View File
@@ -66,9 +66,28 @@ const resetPasswordModel = joi.object().keys({
}),
});
const updateProfileModel = joi.object().keys({
full_name: joi.string().max(255).optional().messages({
'string.base': 'Nama lengkap harus berupa teks.',
'string.max': 'Nama lengkap tidak boleh lebih dari 255 karakter.'
}),
email: joi.string().email({ tlds: { allow: false } }).optional().messages({
'string.base': 'Email harus berupa teks.',
'string.email': 'Format email tidak valid.'
}),
password: joi.string().min(8).optional().messages({
'string.base': 'Password harus berupa teks.',
'string.min': 'Password minimal {#limit} karakter.'
}),
professional_bio: joi.string().allow('', null).optional().messages({
'string.base': 'Bio harus berupa teks.'
})
});
module.exports = {
loginModel,
registerModel,
forgotPasswordModel,
resetPasswordModel,
updateProfileModel,
};
+246 -31
View File
@@ -2,58 +2,133 @@ const bcrypt = require('bcrypt');
const adminService = require('../../admin/services/admin.service');
const { createToken } = require('../../../middleware/verifyJWT');
const { data, error } = require('../../../helpers/utils/wrapper');
const { UnauthorizedError, NotFoundError } = require('../../../helpers/error');
const { UnauthorizedError, NotFoundError, ForbiddenError, ConflictError, BadRequestError } = require('../../../helpers/error');
const prisma = require('../../../helpers/db/db_connection');
class AuthService {
async login(email, password) {
try {
const result = await adminService.getAdminByEmail(email);
if (result.err || !result.data) {
let userRecord = null;
let isUserAdmin = false;
const adminResult = await adminService.getAdminByEmail(email);
if (adminResult.data) {
userRecord = adminResult.data;
isUserAdmin = true;
} else {
const internRecord = await prisma.user.findUnique({
where: {
email: email,
is_active: true
}
});
if (internRecord) {
userRecord = internRecord;
isUserAdmin = false;
}
}
if (!userRecord) {
return error(new UnauthorizedError('Email atau password tidak valid'));
}
const admin = result.data;
const isPasswordValid = await bcrypt.compare(password, admin.password);
const isPasswordValid = await bcrypt.compare(password, userRecord.password);
if (!isPasswordValid) {
return error(new UnauthorizedError('Email atau password tidak valid'));
}
const token = createToken({
id: admin.id.toString(),
email: admin.email,
role: admin.role,
signature: admin.signature
});
const role = isUserAdmin ? (userRecord.role || 'admin') : 'intern';
return data({
token,
admin: {
id: admin.id.toString(),
nama_depan: admin.nama_depan,
nama_belakang: admin.nama_belakang,
email: admin.email,
role: admin.role,
signature: admin.signature,
},
});
const tokenPayload = {
id: userRecord.id.toString(),
email: userRecord.email,
role: role,
signature: isUserAdmin ? userRecord.signature : null
};
const token = createToken(tokenPayload);
if (isUserAdmin) {
return data({
token,
user: {
id: userRecord.id.toString(),
full_name: `${userRecord.nama_depan}${userRecord.nama_belakang ? ' ' + userRecord.nama_belakang : ''}`,
email: userRecord.email,
role: role,
signature: userRecord.signature
},
admin: {
id: userRecord.id.toString(),
nama_depan: userRecord.nama_depan,
nama_belakang: userRecord.nama_belakang,
email: userRecord.email,
role: role,
signature: userRecord.signature,
}
});
} else {
return data({
token,
user: {
id: userRecord.id.toString(),
full_name: userRecord.full_name,
email: userRecord.email,
role: role,
}
});
}
} catch (err) {
return error(err);
}
}
async getCurrentUser(userId) {
async getCurrentUser(userId, role) {
try {
const result = await adminService.getAdminById(userId);
if (role === 'admin') {
const result = await adminService.getAdminById(userId);
if (result.err) {
return result;
if (result.err) {
return result;
}
const { password, ...adminWithoutPassword } = result.data;
return data({
...adminWithoutPassword,
role: 'admin'
});
} else if (role === 'intern') {
const user = await prisma.user.findUnique({
where: {
id: parseInt(userId),
is_active: true
},
include: {
lamaran_magang: {
include: {
lowongan_magang: {
select: {
posisi: true,
kelompok_peminatan: true
}
}
}
}
}
});
if (!user) {
return error(new NotFoundError('Pengguna tidak ditemukan'));
}
const { password, ...userWithoutPassword } = user;
return data(userWithoutPassword);
} else {
return error(new NotFoundError('Role tidak valid'));
}
const { password, ...adminWithoutPassword } = result.data;
return data(adminWithoutPassword);
} catch (err) {
return error(new NotFoundError('Pengguna tidak ditemukan'));
}
@@ -67,6 +142,146 @@ class AuthService {
return error(err);
}
}
async updateProfile(userId, role, payload, imageFile) {
try {
const { full_name, email, password, professional_bio } = payload;
if (email) {
const existingAdmin = await prisma.admin.findUnique({
where: { email }
});
if (existingAdmin && (role !== 'admin' || existingAdmin.id !== parseInt(userId))) {
if (imageFile?.filename) {
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
await deleteFileIfExists(imageFile.filename).catch(() => { });
}
return error(new ConflictError('Email sudah terdaftar'));
}
const existingUser = await prisma.user.findUnique({
where: { email }
});
if (existingUser && (role !== 'intern' || existingUser.id !== parseInt(userId))) {
if (imageFile?.filename) {
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
await deleteFileIfExists(imageFile.filename).catch(() => { });
}
return error(new ConflictError('Email sudah terdaftar'));
}
}
let hashedPassword = undefined;
if (password) {
hashedPassword = await bcrypt.hash(password, 10);
}
if (role === 'admin') {
const adminId = parseInt(userId);
const existingAdmin = await prisma.admin.findUnique({
where: { id: adminId }
});
if (!existingAdmin) {
if (imageFile?.filename) {
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
await deleteFileIfExists(imageFile.filename).catch(() => { });
}
return error(new NotFoundError('Admin tidak ditemukan'));
}
const updateData = {};
if (full_name) {
const nameParts = full_name.trim().split(/\s+/);
updateData.nama_depan = nameParts[0];
updateData.nama_belakang = nameParts.slice(1).join(' ') || '';
}
if (email) updateData.email = email;
if (hashedPassword) updateData.password = hashedPassword;
if (professional_bio !== undefined) updateData.professional_bio = professional_bio;
if (imageFile) {
const imageUploadHelper = require('../../../helpers/utils/imageUpload.helper');
const imagePath = imageUploadHelper.uploadImage(imageFile);
updateData.profile_picture = imagePath;
if (existingAdmin.profile_picture) {
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
const path = require('path');
const oldFileName = path.basename(existingAdmin.profile_picture);
await deleteFileIfExists(oldFileName).catch(() => { });
}
}
const updatedAdmin = await prisma.admin.update({
where: { id: adminId },
data: updateData
});
const { password: _, ...adminWithoutPassword } = updatedAdmin;
return data({
...adminWithoutPassword,
role: 'admin'
});
} else if (role === 'intern') {
if (imageFile) {
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
await deleteFileIfExists(imageFile.filename).catch(() => { });
return error(new ForbiddenError('Hanya admin yang dapat mengupload foto profil'));
}
const internId = parseInt(userId);
const existingUser = await prisma.user.findUnique({
where: { id: internId }
});
if (!existingUser) {
return error(new NotFoundError('User tidak ditemukan'));
}
const updateData = {};
if (full_name) updateData.full_name = full_name;
if (email) updateData.email = email;
if (hashedPassword) updateData.password = hashedPassword;
if (professional_bio !== undefined) updateData.professional_bio = professional_bio;
const updatedUser = await prisma.user.update({
where: { id: internId },
data: updateData,
include: {
lamaran_magang: {
include: {
lowongan_magang: {
select: {
posisi: true,
kelompok_peminatan: true
}
}
}
}
}
});
const { password: _, ...userWithoutPassword } = updatedUser;
return data(userWithoutPassword);
} else {
if (imageFile?.filename) {
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
await deleteFileIfExists(imageFile.filename).catch(() => { });
}
return error(new NotFoundError('Role tidak valid'));
}
} catch (err) {
if (imageFile?.filename) {
try {
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
await deleteFileIfExists(imageFile.filename);
} catch (cleanupErr) {
console.error('Failed to cleanup file:', cleanupErr);
}
}
return error(err);
}
}
}
module.exports = new AuthService();