feat: menambahkan manajemen akun mentor oleh admin
This commit is contained in:
@@ -3,7 +3,9 @@ const { response, data, error } = require('../../../helpers/utils/wrapper');
|
||||
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
|
||||
const { ForbiddenError, BadRequestError, NotFoundError, InternalServerError } = require('../../../helpers/error');
|
||||
const { isValidPayload } = require('../../../helpers/utils/validator');
|
||||
const { createAdminModel, updateAdminModel } = require('../models/admin.model');
|
||||
const { createAdminModel, updateAdminModel, createMentorModel, updateMentorModel } = require('../models/admin.model');
|
||||
const fs = require('fs');
|
||||
const { cleanupUploadedFile } = require('../helpers/cleanupUploadedFiles.helper')
|
||||
|
||||
class AdminController {
|
||||
async addAdmin(req, res) {
|
||||
@@ -109,6 +111,150 @@ class AdminController {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async createMentor(req, res) {
|
||||
try {
|
||||
const validatePayload = isValidPayload(req.body, createMentorModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
cleanupUploadedFile(req.file);
|
||||
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data mentor tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await adminService.createMentor(validatePayload.data, req.file);
|
||||
|
||||
if (result.err) {
|
||||
cleanupUploadedFile(req.file);
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Mentor berhasil dibuat', SUCCESS.CREATED);
|
||||
} catch (err) {
|
||||
cleanupUploadedFile(req.file);
|
||||
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
error(new InternalServerError(err.message)),
|
||||
'Terjadi kesalahan yang tidak terduga',
|
||||
ERROR.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getMentors(req, res) {
|
||||
try {
|
||||
const result = await adminService.getMentors();
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Mentor berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
error(new InternalServerError(err.message)),
|
||||
'Terjadi kesalahan yang tidak terduga',
|
||||
ERROR.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getMentorById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await adminService.getMentorById(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Detail mentor berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
error(new InternalServerError(err.message)),
|
||||
'Terjadi kesalahan yang tidak terduga',
|
||||
ERROR.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async updateMentor(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, updateMentorModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
cleanupUploadedFile(req.file);
|
||||
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data mentor tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await adminService.updateMentor(
|
||||
id,
|
||||
validatePayload.data,
|
||||
req.file
|
||||
);
|
||||
|
||||
if (result.err) {
|
||||
cleanupUploadedFile(req.file);
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Mentor berhasil diperbarui', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
cleanupUploadedFile(req.file);
|
||||
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
error(new InternalServerError(err.message)),
|
||||
'Terjadi kesalahan yang tidak terduga',
|
||||
ERROR.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMentor(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await adminService.deleteMentor(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Mentor berhasil dihapus', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
error(new InternalServerError(err.message)),
|
||||
'Terjadi kesalahan yang tidak terduga',
|
||||
ERROR.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AdminController();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const cleanupUploadedFile = (file) => {
|
||||
if (file?.path && fs.existsSync(file.path)) {
|
||||
fs.unlinkSync(file.path);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
cleanupUploadedFile,
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const multer = require('multer');
|
||||
const { response, error } = require('../../../helpers/utils/wrapper');
|
||||
const { BadRequestError } = require('../../../helpers/error');
|
||||
const { ERROR } = require('../../../helpers/http-status/status_code');
|
||||
|
||||
const mentorProfileUploadDir = path.join(
|
||||
process.cwd(),
|
||||
'src',
|
||||
'uploads',
|
||||
'mentor-profiles'
|
||||
);
|
||||
|
||||
if (!fs.existsSync(mentorProfileUploadDir)) {
|
||||
fs.mkdirSync(mentorProfileUploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'];
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, mentorProfileUploadDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const safeOriginalName = file.originalname
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^a-zA-Z0-9.\-_]/g, '');
|
||||
|
||||
cb(null, `${Date.now()}-${Math.round(Math.random() * 1e9)}-${safeOriginalName}`);
|
||||
},
|
||||
});
|
||||
|
||||
const fileFilter = (req, file, cb) => {
|
||||
if (!allowedMimeTypes.includes(file.mimetype)) {
|
||||
return cb(
|
||||
new BadRequestError('Profile picture must be a JPEG, JPG, PNG, or WEBP image'),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
cb(null, true);
|
||||
};
|
||||
|
||||
const mentorProfileUpload = multer({
|
||||
storage,
|
||||
fileFilter,
|
||||
limits: {
|
||||
fileSize: 5 * 1024 * 1024,
|
||||
},
|
||||
});
|
||||
|
||||
const mentorProfileUploadErrorHandler = (err, req, res, next) => {
|
||||
if (!err) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (err instanceof multer.MulterError) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
error(new BadRequestError(err.message)),
|
||||
err.message,
|
||||
ERROR.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
error(err),
|
||||
err.message || 'Invalid profile picture upload',
|
||||
ERROR.BAD_REQUEST
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
mentorProfileUpload,
|
||||
mentorProfileUploadErrorHandler,
|
||||
mentorProfileUploadDir,
|
||||
};
|
||||
@@ -69,7 +69,81 @@ const updateAdminModel = joi.object().keys({
|
||||
}),
|
||||
});
|
||||
|
||||
const createMentorModel = joi.object().keys({
|
||||
nama_depan: joi.string().max(255).required().messages({
|
||||
'string.base': 'Nama depan harus berupa teks.',
|
||||
'string.empty': 'Nama depan tidak boleh kosong.',
|
||||
'string.max': 'Nama depan maksimal {#limit} karakter.',
|
||||
'any.required': 'Nama depan wajib diisi.',
|
||||
}),
|
||||
|
||||
nama_belakang: joi.string().max(255).required().messages({
|
||||
'string.base': 'Nama belakang harus berupa teks.',
|
||||
'string.empty': 'Nama belakang tidak boleh kosong.',
|
||||
'string.max': 'Nama belakang maksimal {#limit} karakter.',
|
||||
'any.required': 'Nama belakang wajib diisi.',
|
||||
}),
|
||||
|
||||
email: joi.string().email({ tlds: { allow: false } }).max(255).required().messages({
|
||||
'string.base': 'Email harus berupa teks.',
|
||||
'string.empty': 'Email tidak boleh kosong.',
|
||||
'string.email': 'Format email tidak valid.',
|
||||
'string.max': 'Email maksimal {#limit} karakter.',
|
||||
'any.required': 'Email wajib diisi.',
|
||||
}),
|
||||
|
||||
password: joi.string().min(8).required().messages({
|
||||
'string.base': 'Password harus berupa teks.',
|
||||
'string.empty': 'Password tidak boleh kosong.',
|
||||
'string.min': 'Password minimal {#limit} karakter.',
|
||||
'any.required': 'Password wajib diisi.',
|
||||
}),
|
||||
|
||||
signature: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Signature harus berupa teks.',
|
||||
}),
|
||||
|
||||
professional_bio: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Professional bio harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
const updateMentorModel = joi.object().keys({
|
||||
nama_depan: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Nama depan harus berupa teks.',
|
||||
'string.empty': 'Nama depan tidak boleh kosong.',
|
||||
'string.max': 'Nama depan maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
nama_belakang: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Nama belakang harus berupa teks.',
|
||||
'string.empty': 'Nama belakang tidak boleh kosong.',
|
||||
'string.max': 'Nama belakang maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
email: joi.string().email({ tlds: { allow: false } }).max(255).optional().messages({
|
||||
'string.base': 'Email harus berupa teks.',
|
||||
'string.email': 'Format email tidak valid.',
|
||||
'string.max': 'Email maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
password: joi.string().min(8).optional().messages({
|
||||
'string.base': 'Password harus berupa teks.',
|
||||
'string.min': 'Password minimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
signature: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Signature harus berupa teks.',
|
||||
}),
|
||||
|
||||
professional_bio: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Professional bio harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createAdminModel,
|
||||
updateAdminModel,
|
||||
createMentorModel,
|
||||
updateMentorModel,
|
||||
};
|
||||
|
||||
@@ -35,6 +35,57 @@ class AdminRepository {
|
||||
where: { id: parseInt(id) }
|
||||
});
|
||||
}
|
||||
|
||||
async createMentor(mentorData) {
|
||||
return prisma.admin.create({
|
||||
data: {
|
||||
...mentorData,
|
||||
role: 'mentor',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findMentors() {
|
||||
return prisma.admin.findMany({
|
||||
where: {
|
||||
role: 'mentor',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async findMentorById(id) {
|
||||
return prisma.admin.findFirst({
|
||||
where: {
|
||||
id: parseInt(id),
|
||||
role: 'mentor',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async updateMentorById(id, mentorData) {
|
||||
return prisma.admin.update({
|
||||
where: {
|
||||
id: parseInt(id),
|
||||
},
|
||||
data: mentorData,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteMentorById(id) {
|
||||
return prisma.admin.delete({
|
||||
where: {
|
||||
id: parseInt(id),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async countProjectsByAdminId(idAdmin) {
|
||||
return prisma.project.count({
|
||||
where: {
|
||||
id_admin: parseInt(idAdmin),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AdminRepository();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const bcrypt = require('bcrypt');
|
||||
const adminRepository = require('../repositories/admin.repository');
|
||||
const { data, error } = require('../../../helpers/utils/wrapper');
|
||||
const { BadRequestError, NotFoundError } = require('../../../helpers/error');
|
||||
const { BadRequestError, NotFoundError, ConflictError } = require('../../../helpers/error');
|
||||
|
||||
class AdminService {
|
||||
async createAdmin(adminData) {
|
||||
@@ -109,6 +111,177 @@ class AdminService {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async createMentor(mentorData, file = null) {
|
||||
try {
|
||||
const existingAdmin = await adminRepository.findByEmail(mentorData.email);
|
||||
|
||||
if (existingAdmin) {
|
||||
return error(new ConflictError('Email sudah terdaftar'));
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(mentorData.password, 10);
|
||||
const profilePicturePath = this.buildMentorProfilePicturePath(file);
|
||||
|
||||
const newMentorData = {
|
||||
nama_depan: mentorData.nama_depan,
|
||||
nama_belakang: mentorData.nama_belakang,
|
||||
email: mentorData.email,
|
||||
password: hashedPassword,
|
||||
role: 'mentor',
|
||||
signature: mentorData.signature || `Mentor-${Date.now()}`,
|
||||
profile_picture: profilePicturePath,
|
||||
professional_bio: mentorData.professional_bio || null,
|
||||
};
|
||||
|
||||
const mentor = await adminRepository.createMentor(newMentorData);
|
||||
|
||||
return data(this.mapMentor(mentor));
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getMentors() {
|
||||
try {
|
||||
const mentors = await adminRepository.findMentors();
|
||||
|
||||
return data(mentors.map((mentor) => this.mapMentor(mentor)));
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getMentorById(id) {
|
||||
try {
|
||||
const mentor = await adminRepository.findMentorById(id);
|
||||
|
||||
if (!mentor) {
|
||||
return error(new NotFoundError('Mentor tidak ditemukan'));
|
||||
}
|
||||
|
||||
return data(this.mapMentor(mentor));
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async updateMentor(id, mentorData, file = null) {
|
||||
try {
|
||||
const mentor = await adminRepository.findMentorById(id);
|
||||
|
||||
if (!mentor) {
|
||||
return error(new NotFoundError('Mentor tidak ditemukan'));
|
||||
}
|
||||
|
||||
if ((!mentorData || Object.keys(mentorData).length === 0) && !file) {
|
||||
return error(new BadRequestError('Minimal satu field atau foto profil harus dikirim untuk update mentor'));
|
||||
}
|
||||
|
||||
const updateData = { ...mentorData };
|
||||
|
||||
if (updateData.email && updateData.email !== mentor.email) {
|
||||
const existingAdmin = await adminRepository.findByEmail(updateData.email);
|
||||
|
||||
if (existingAdmin && existingAdmin.id !== parseInt(id)) {
|
||||
return error(new ConflictError('Email sudah terdaftar'));
|
||||
}
|
||||
}
|
||||
|
||||
if (updateData.password) {
|
||||
updateData.password = await bcrypt.hash(updateData.password, 10);
|
||||
}
|
||||
|
||||
if (file) {
|
||||
updateData.profile_picture = this.buildMentorProfilePicturePath(file);
|
||||
}
|
||||
|
||||
delete updateData.role;
|
||||
|
||||
const updatedMentor = await adminRepository.updateMentorById(id, updateData);
|
||||
|
||||
if (file) {
|
||||
this.deleteLocalMentorProfilePicture(mentor.profile_picture);
|
||||
}
|
||||
|
||||
return data(this.mapMentor(updatedMentor));
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMentor(id) {
|
||||
try {
|
||||
const mentor = await adminRepository.findMentorById(id);
|
||||
|
||||
if (!mentor) {
|
||||
return error(new NotFoundError('Mentor tidak ditemukan'));
|
||||
}
|
||||
|
||||
const projectCount = await adminRepository.countProjectsByAdminId(id);
|
||||
|
||||
if (projectCount > 0) {
|
||||
return error(
|
||||
new ConflictError('Mentor tidak dapat dihapus karena masih memiliki project')
|
||||
);
|
||||
}
|
||||
|
||||
await adminRepository.deleteMentorById(id);
|
||||
|
||||
this.deleteLocalMentorProfilePicture(mentor.profile_picture);
|
||||
|
||||
return data(null);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
mapMentor(mentor) {
|
||||
if (!mentor) return null;
|
||||
|
||||
return {
|
||||
id: mentor.id,
|
||||
nama_depan: mentor.nama_depan,
|
||||
nama_belakang: mentor.nama_belakang,
|
||||
full_name: `${mentor.nama_depan}${mentor.nama_belakang ? ` ${mentor.nama_belakang}` : ''}`,
|
||||
email: mentor.email,
|
||||
role: mentor.role,
|
||||
signature: mentor.signature || null,
|
||||
profile_picture: mentor.profile_picture || null,
|
||||
professional_bio: mentor.professional_bio || null,
|
||||
created_at: mentor.created_at,
|
||||
updated_at: mentor.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
buildMentorProfilePicturePath(file) {
|
||||
if (!file) return null;
|
||||
|
||||
return `/uploads/mentor-profiles/${file.filename}`;
|
||||
}
|
||||
|
||||
deleteLocalMentorProfilePicture(filePath) {
|
||||
if (!filePath || typeof filePath !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!filePath.startsWith('/uploads/mentor-profiles/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filename = path.basename(filePath);
|
||||
const absolutePath = path.join(
|
||||
process.cwd(),
|
||||
'src',
|
||||
'uploads',
|
||||
'mentor-profiles',
|
||||
filename
|
||||
);
|
||||
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
fs.unlinkSync(absolutePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AdminService();
|
||||
|
||||
Reference in New Issue
Block a user