refactor: rewrite ke codebase lama
This commit is contained in:
+114
@@ -0,0 +1,114 @@
|
||||
const adminService = require('../services/admin.service');
|
||||
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');
|
||||
|
||||
class AdminController {
|
||||
async addAdmin(req, res) {
|
||||
try {
|
||||
const validatePayload = isValidPayload(req.body, createAdminModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await adminService.createAdmin(validatePayload.data);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', data(), 'Admin berhasil dibuat', SUCCESS.CREATED);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getAdmins(req, res) {
|
||||
try {
|
||||
const result = await adminService.getAllAdmins();
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
if (!result.data || result.data.length === 0) {
|
||||
return response(res, 'fail', error(new NotFoundError('Tidak ada admin ditemukan')));
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Admin berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getAdminById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await adminService.getAdminById(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Admin berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async updateAdmin(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, updateAdminModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await adminService.updateAdmin(id, validatePayload.data);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', data(), 'Admin berhasil diperbarui', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAdmin(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await adminService.deleteAdmin(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', data(), 'Admin 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();
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
const adminController = require('./controllers/admin.controller');
|
||||
const adminService = require('./services/admin.service');
|
||||
const adminRepository = require('./repositories/admin.repository');
|
||||
|
||||
module.exports = {
|
||||
adminController,
|
||||
adminService,
|
||||
adminRepository
|
||||
};
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
const joi = require('joi');
|
||||
|
||||
const createAdminModel = 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.',
|
||||
}),
|
||||
|
||||
role: joi.string().valid('admin').default('admin').messages({
|
||||
'string.base': 'Role harus berupa teks.',
|
||||
'any.only': 'Role hanya boleh "admin".',
|
||||
}),
|
||||
|
||||
signature: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Signature harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
const updateAdminModel = 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.',
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createAdminModel,
|
||||
updateAdminModel,
|
||||
};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
const prisma = require('../../../helpers/db/db_connection');
|
||||
|
||||
class AdminRepository {
|
||||
async create(adminData) {
|
||||
return prisma.admin.create({
|
||||
data: adminData
|
||||
});
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return prisma.admin.findMany();
|
||||
}
|
||||
|
||||
async findByEmail(email) {
|
||||
return prisma.admin.findUnique({
|
||||
where: { email }
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id) {
|
||||
return prisma.admin.findUnique({
|
||||
where: { id: parseInt(id) }
|
||||
});
|
||||
}
|
||||
|
||||
async updateById(id, adminData) {
|
||||
return prisma.admin.update({
|
||||
where: { id: parseInt(id) },
|
||||
data: adminData
|
||||
});
|
||||
}
|
||||
|
||||
async deleteById(id) {
|
||||
return prisma.admin.delete({
|
||||
where: { id: parseInt(id) }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AdminRepository();
|
||||
Executable
+114
@@ -0,0 +1,114 @@
|
||||
const bcrypt = require('bcrypt');
|
||||
const adminRepository = require('../repositories/admin.repository');
|
||||
const { data, error } = require('../../../helpers/utils/wrapper');
|
||||
const { BadRequestError, NotFoundError } = require('../../../helpers/error');
|
||||
|
||||
class AdminService {
|
||||
async createAdmin(adminData) {
|
||||
try {
|
||||
const { nama_depan, nama_belakang, email, password, role } = adminData;
|
||||
|
||||
const existingAdmin = await adminRepository.findByEmail(email);
|
||||
|
||||
if (existingAdmin) {
|
||||
return error(new BadRequestError('Email sudah terdaftar'));
|
||||
}
|
||||
|
||||
const saltRounds = 10;
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||||
|
||||
const newAdminData = {
|
||||
nama_depan,
|
||||
nama_belakang,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
role,
|
||||
signature: `${nama_depan}-${Date.now()}`,
|
||||
};
|
||||
|
||||
await adminRepository.create(newAdminData);
|
||||
return data();
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllAdmins() {
|
||||
try {
|
||||
const admins = await adminRepository.findAll();
|
||||
if (!admins || admins.length === 0) {
|
||||
return error(new NotFoundError('Tidak ada admin ditemukan'));
|
||||
}
|
||||
return data(admins);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAdminById(id) {
|
||||
try {
|
||||
const admin = await adminRepository.findById(id);
|
||||
if (!admin) {
|
||||
return error(new NotFoundError('Admin tidak ditemukan'));
|
||||
}
|
||||
return data(admin);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAdminByEmail(email) {
|
||||
try {
|
||||
const admin = await adminRepository.findByEmail(email);
|
||||
return data(admin);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async verifyPassword(plainPassword, hashedPassword) {
|
||||
try {
|
||||
const isValid = await bcrypt.compare(plainPassword, hashedPassword);
|
||||
return data(isValid);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async updateAdmin(id, adminData) {
|
||||
try {
|
||||
const adminResult = await this.getAdminById(id);
|
||||
if (adminResult.err) {
|
||||
return adminResult;
|
||||
}
|
||||
|
||||
if (adminData.email) {
|
||||
const existingAdmin = await adminRepository.findByEmail(adminData.email);
|
||||
if (existingAdmin && existingAdmin.id !== id) {
|
||||
return error(new BadRequestError('Email sudah terdaftar'));
|
||||
}
|
||||
}
|
||||
|
||||
await adminRepository.updateById(id, adminData);
|
||||
return data();
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAdmin(id) {
|
||||
try {
|
||||
const adminResult = await this.getAdminById(id);
|
||||
if (adminResult.err) {
|
||||
return adminResult;
|
||||
}
|
||||
|
||||
await adminRepository.deleteById(id);
|
||||
return data();
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AdminService();
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
const authService = require('../services/auth.service');
|
||||
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');
|
||||
|
||||
class AuthController {
|
||||
async login(req, res) {
|
||||
try {
|
||||
const validatePayload = isValidPayload(req.body, loginModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const { email, password } = validatePayload.data;
|
||||
const result = await authService.login(email, password);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
console.log(result.data.token)
|
||||
res.cookie("token", result.data.token, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'None',
|
||||
maxAge: 7 * 24 * 60 * 60,
|
||||
});
|
||||
|
||||
return response(res, 'success', result, 'Login berhasil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async me(req, res) {
|
||||
try {
|
||||
const id = req.id;
|
||||
|
||||
if (!id) {
|
||||
return response(res, 'fail', error(new UnauthorizedError('Tidak diotorisasi: ID pengguna tidak ditemukan')));
|
||||
}
|
||||
|
||||
const result = await authService.getCurrentUser(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Pengguna berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async logout(req, res) {
|
||||
try {
|
||||
return response(res, 'success', data(), 'Logout berhasil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AuthController();
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
const authController = require('./controllers/auth.controller');
|
||||
const authService = require('./services/auth.service');
|
||||
|
||||
module.exports = {
|
||||
authController,
|
||||
authService
|
||||
};
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
const joi = require('joi');
|
||||
|
||||
const loginModel = joi.object().keys({
|
||||
email: joi.string().email({ tlds: { allow: false } }).required().messages({
|
||||
'string.base': 'Email harus berupa teks.',
|
||||
'string.empty': 'Email tidak boleh kosong.',
|
||||
'string.email': 'Format email tidak valid.',
|
||||
'any.required': 'Email wajib diisi.',
|
||||
}),
|
||||
|
||||
password: joi.string().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.',
|
||||
}),
|
||||
});
|
||||
|
||||
const registerModel = joi.object().keys({
|
||||
email: joi.string().email({ tlds: { allow: false } }).required().messages({
|
||||
'string.base': 'Email harus berupa teks.',
|
||||
'string.empty': 'Email tidak boleh kosong.',
|
||||
'string.email': 'Format email tidak valid.',
|
||||
'any.required': 'Email wajib diisi.',
|
||||
}),
|
||||
|
||||
password: joi.string()
|
||||
.min(8)
|
||||
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
|
||||
.required()
|
||||
.messages({
|
||||
'string.base': 'Password harus berupa teks.',
|
||||
'string.empty': 'Password tidak boleh kosong.',
|
||||
'string.min': 'Password minimal {#limit} karakter.',
|
||||
'string.pattern.base': 'Password harus mengandung huruf besar, huruf kecil, dan angka.',
|
||||
'any.required': 'Password wajib diisi.',
|
||||
}),
|
||||
});
|
||||
|
||||
const forgotPasswordModel = joi.object().keys({
|
||||
email: joi.string().email({ tlds: { allow: false } }).required().messages({
|
||||
'string.base': 'Email harus berupa teks.',
|
||||
'string.empty': 'Email tidak boleh kosong.',
|
||||
'string.email': 'Format email tidak valid.',
|
||||
'any.required': 'Email wajib diisi.',
|
||||
}),
|
||||
});
|
||||
|
||||
const resetPasswordModel = joi.object().keys({
|
||||
token: joi.string().required().messages({
|
||||
'string.base': 'Token harus berupa teks.',
|
||||
'string.empty': 'Token tidak boleh kosong.',
|
||||
'any.required': 'Token wajib diisi.',
|
||||
}),
|
||||
|
||||
password: joi.string()
|
||||
.min(8)
|
||||
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
|
||||
.required()
|
||||
.messages({
|
||||
'string.base': 'Password harus berupa teks.',
|
||||
'string.empty': 'Password tidak boleh kosong.',
|
||||
'string.min': 'Password minimal {#limit} karakter.',
|
||||
'string.pattern.base': 'Password harus mengandung huruf besar, huruf kecil, dan angka.',
|
||||
'any.required': 'Password wajib diisi.',
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
loginModel,
|
||||
registerModel,
|
||||
forgotPasswordModel,
|
||||
resetPasswordModel,
|
||||
};
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
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');
|
||||
|
||||
class AuthService {
|
||||
async login(email, password) {
|
||||
try {
|
||||
const result = await adminService.getAdminByEmail(email);
|
||||
if (result.err || !result.data) {
|
||||
return error(new UnauthorizedError('Email atau password tidak valid'));
|
||||
}
|
||||
|
||||
const admin = result.data;
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(password, admin.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
|
||||
});
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getCurrentUser(userId) {
|
||||
try {
|
||||
const result = await adminService.getAdminById(userId);
|
||||
|
||||
if (result.err) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const { password, ...adminWithoutPassword } = result.data;
|
||||
|
||||
return data(adminWithoutPassword);
|
||||
} catch (err) {
|
||||
return error(new NotFoundError('Pengguna tidak ditemukan'));
|
||||
}
|
||||
}
|
||||
|
||||
generateToken(payload) {
|
||||
try {
|
||||
const token = createToken(payload);
|
||||
return data(token);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AuthService();
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
const batchService = require('../services/batch.service');
|
||||
const { response, error } = require('../../../helpers/utils/wrapper');
|
||||
const { BadRequestError, InternalServerError } = require('../../../helpers/error');
|
||||
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
|
||||
const { isValidPayload } = require('../../../helpers/utils/validator');
|
||||
const { createBatchModel, updateBatchModel } = require('../models/batch.model');
|
||||
|
||||
class BatchController {
|
||||
async createBatch(req, res) {
|
||||
try {
|
||||
const validatePayload = isValidPayload(req.body, createBatchModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await batchService.createBatch(validatePayload.data);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Batch berhasil dibuat', SUCCESS.CREATED);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllBatches(req, res) {
|
||||
try {
|
||||
const result = await batchService.getAllBatches();
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Berhasil mengambil semua batch', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async updateBatch(req, res) {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
|
||||
const validatePayload = isValidPayload(req.body, updateBatchModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await batchService.updateBatch(id, validatePayload.data);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Batch berhasil diperbarui', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteBatch(req, res) {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
|
||||
if (isNaN(id)) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
error(new BadRequestError('ID batch harus berupa angka')),
|
||||
'ID batch tidak valid',
|
||||
ERROR.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
const result = await batchService.deleteBatch(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Batch berhasil dihapus', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async switchBatch(req, res) {
|
||||
try {
|
||||
const batch_number = parseInt(req.params.batch_number);
|
||||
|
||||
if (isNaN(batch_number)) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
error(new BadRequestError('Nomor batch harus berupa angka')),
|
||||
'Nomor batch tidak valid',
|
||||
ERROR.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
const result = await batchService.switchBatch(batch_number);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Batch berhasil diaktifkan', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new BatchController();
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
const batchController = require('./controllers/batch.controller');
|
||||
const batchService = require('./services/batch.service');
|
||||
const batchRepository = require('./repositories/batch.repository');
|
||||
|
||||
module.exports = {
|
||||
batchController,
|
||||
batchService,
|
||||
batchRepository,
|
||||
};
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
const joi = require('joi');
|
||||
|
||||
const createBatchModel = joi.object().keys({
|
||||
batch_number: joi.number().integer().positive().messages({
|
||||
'number.base': 'Nomor batch harus berupa angka.',
|
||||
'number.integer': 'Nomor batch harus berupa bilangan bulat.',
|
||||
'number.positive': 'Nomor batch harus berupa angka positif.',
|
||||
}),
|
||||
|
||||
is_active: joi.boolean().default(true).messages({
|
||||
'boolean.base': 'Status aktif harus berupa boolean.',
|
||||
}),
|
||||
});
|
||||
|
||||
const updateBatchModel = joi.object().keys({
|
||||
batch_number: joi.number().integer().positive().messages({
|
||||
'number.base': 'Nomor batch harus berupa angka.',
|
||||
'number.integer': 'Nomor batch harus berupa bilangan bulat.',
|
||||
'number.positive': 'Nomor batch harus berupa angka positif.',
|
||||
}),
|
||||
|
||||
is_active: joi.boolean().messages({
|
||||
'boolean.base': 'Status aktif harus berupa boolean.',
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createBatchModel,
|
||||
updateBatchModel,
|
||||
};
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
const prisma = require('../../../helpers/db/db_connection');
|
||||
|
||||
class BatchRepository {
|
||||
async create(batchData) {
|
||||
return prisma.batch.create({
|
||||
data: batchData,
|
||||
});
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return prisma.batch.findMany({
|
||||
orderBy: {
|
||||
batch_number: 'desc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id) {
|
||||
return prisma.batch.findUnique({
|
||||
where: { id: id },
|
||||
});
|
||||
}
|
||||
|
||||
async findByBatchNumber(batch_number) {
|
||||
return prisma.batch.findUnique({
|
||||
where: { batch_number: batch_number },
|
||||
});
|
||||
}
|
||||
|
||||
async findActiveBatch() {
|
||||
return prisma.batch.findFirst({
|
||||
where: { is_active: true },
|
||||
});
|
||||
}
|
||||
|
||||
async findLatestBatch() {
|
||||
return prisma.batch.findFirst({
|
||||
orderBy: {
|
||||
batch_number: 'desc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async updateById(id, batchData) {
|
||||
return prisma.batch.update({
|
||||
where: { id: id },
|
||||
data: batchData,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteById(id) {
|
||||
return prisma.batch.delete({
|
||||
where: { id: id },
|
||||
});
|
||||
}
|
||||
|
||||
async deactivateAllBatches() {
|
||||
return prisma.batch.updateMany({
|
||||
data: { is_active: false },
|
||||
});
|
||||
}
|
||||
|
||||
async countBatches() {
|
||||
return prisma.batch.count();
|
||||
}
|
||||
|
||||
async findByStatus(is_active) {
|
||||
return prisma.batch.findMany({
|
||||
where: { is_active: is_active },
|
||||
orderBy: {
|
||||
batch_number: 'desc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getBatchById(batchId) {
|
||||
if (!batchId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.findById(parseInt(batchId));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new BatchRepository();
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
const batchRepository = require('../repositories/batch.repository');
|
||||
const { data, error } = require('../../../helpers/utils/wrapper');
|
||||
const { NotFoundError, ConflictError } = require('../../../helpers/error');
|
||||
|
||||
class BatchService {
|
||||
async createBatch(batchData) {
|
||||
try {
|
||||
let batchNumber;
|
||||
|
||||
if (batchData.batch_number) {
|
||||
const existingBatch = await batchRepository.findByBatchNumber(batchData.batch_number);
|
||||
if (existingBatch) {
|
||||
return error(new ConflictError('Nomor batch sudah digunakan'));
|
||||
}
|
||||
batchNumber = batchData.batch_number;
|
||||
} else {
|
||||
const latestBatch = await batchRepository.findLatestBatch();
|
||||
batchNumber = latestBatch ? latestBatch.batch_number + 1 : 1;
|
||||
}
|
||||
|
||||
await batchRepository.deactivateAllBatches();
|
||||
|
||||
const newBatchData = {
|
||||
...batchData,
|
||||
batch_number: batchNumber,
|
||||
is_active: true,
|
||||
};
|
||||
|
||||
const result = await batchRepository.create(newBatchData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllBatches() {
|
||||
try {
|
||||
const batchList = await batchRepository.findAll();
|
||||
return data(batchList);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async switchBatch(batch_number) {
|
||||
try {
|
||||
const batch = await batchRepository.findByBatchNumber(batch_number);
|
||||
|
||||
if (!batch) {
|
||||
return error(new NotFoundError('Batch tidak ditemukan'));
|
||||
}
|
||||
|
||||
await batchRepository.deactivateAllBatches();
|
||||
|
||||
const result = await batchRepository.updateById(batch.id, { is_active: true });
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getBatchById(id) {
|
||||
try {
|
||||
const batch = await batchRepository.findById(id);
|
||||
|
||||
if (!batch) {
|
||||
return error(new NotFoundError('Batch tidak ditemukan'));
|
||||
}
|
||||
|
||||
return data(batch);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async updateBatch(id, batchData) {
|
||||
try {
|
||||
const checkResult = await this.getBatchById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
console.log(batchData, id);
|
||||
|
||||
const existingBatch = checkResult.data;
|
||||
|
||||
if (batchData.batch_number && batchData.batch_number !== existingBatch.batch_number) {
|
||||
const duplicateBatch = await batchRepository.findByBatchNumber(batchData.batch_number);
|
||||
if (duplicateBatch) {
|
||||
return error(new ConflictError('Nomor batch sudah digunakan'));
|
||||
}
|
||||
}
|
||||
|
||||
if (batchData.is_active === true) {
|
||||
await batchRepository.deactivateAllBatches();
|
||||
}
|
||||
|
||||
const result = await batchRepository.updateById(id, batchData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteBatch(id) {
|
||||
try {
|
||||
const checkResult = await this.getBatchById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const result = await batchRepository.deleteById(id);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new BatchService();
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
const faqService = require('../services/faq.services');
|
||||
const { createFaqModel, updateFaqModel } = require('../models/faq.models');
|
||||
const { response } = require('../../../helpers/utils/wrapper');
|
||||
const { isValidPayload } = require('../../../helpers/utils/validator');
|
||||
const { InternalServerError } = require('../../../helpers/error');
|
||||
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
|
||||
|
||||
class FaqController {
|
||||
async addFaq(req, res) {
|
||||
try {
|
||||
const payload = req.body;
|
||||
|
||||
const validatePayload = isValidPayload(payload, createFaqModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
validatePayload,
|
||||
'Validasi gagal',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await faqService.createFaq(validatePayload.data);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(
|
||||
res,
|
||||
'success',
|
||||
result,
|
||||
'FAQ berhasil ditambahkan',
|
||||
SUCCESS.CREATED
|
||||
);
|
||||
} catch (err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: new InternalServerError(err.message) },
|
||||
'Terjadi kesalahan yang tidak terduga',
|
||||
ERROR.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllFaq(req, res) {
|
||||
try {
|
||||
const result = await faqService.getAllFaq();
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(
|
||||
res,
|
||||
'success',
|
||||
result,
|
||||
'Data FAQ berhasil diambil',
|
||||
SUCCESS.OK
|
||||
);
|
||||
} catch (err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: new InternalServerError(err.message) },
|
||||
'Terjadi kesalahan yang tidak terduga',
|
||||
ERROR.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getFaqById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await faqService.getFaqById(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(
|
||||
res,
|
||||
'success',
|
||||
result,
|
||||
'Data FAQ berhasil diambil',
|
||||
SUCCESS.OK
|
||||
);
|
||||
} catch (err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: new InternalServerError(err.message) },
|
||||
'Terjadi kesalahan yang tidak terduga',
|
||||
ERROR.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async updateFaq(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const payload = req.body;
|
||||
|
||||
const validatePayload = isValidPayload(payload, updateFaqModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
validatePayload,
|
||||
'Validasi gagal',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await faqService.updateFaq(id, validatePayload.data);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(
|
||||
res,
|
||||
'success',
|
||||
result,
|
||||
'FAQ berhasil diperbarui',
|
||||
SUCCESS.OK
|
||||
);
|
||||
} catch (err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: new InternalServerError(err.message) },
|
||||
'Terjadi kesalahan yang tidak terduga',
|
||||
ERROR.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFaq(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await faqService.deleteFaq(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(
|
||||
res,
|
||||
'success',
|
||||
result,
|
||||
'FAQ berhasil dihapus',
|
||||
SUCCESS.OK
|
||||
);
|
||||
} catch (err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: new InternalServerError(err.message) },
|
||||
'Terjadi kesalahan yang tidak terduga',
|
||||
ERROR.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FaqController();
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
const faqController = require('./controllers/faq.controllers');
|
||||
|
||||
module.exports = {
|
||||
faqController,
|
||||
};
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
const joi = require('joi');
|
||||
|
||||
const createFaqModel = joi.object({
|
||||
pertanyaan: joi.string().required().messages({
|
||||
'string.base': 'Pertanyaan harus berupa teks.',
|
||||
'any.required': 'Pertanyaan wajib diisi.',
|
||||
}),
|
||||
jawaban: joi.string().required().messages({
|
||||
'string.base': 'Jawaban harus berupa teks.',
|
||||
'any.required': 'Jawaban wajib diisi.',
|
||||
}),
|
||||
});
|
||||
|
||||
const updateFaqModel = joi.object({
|
||||
pertanyaan: joi.string().messages({
|
||||
'string.base': 'Pertanyaan harus berupa teks.',
|
||||
}),
|
||||
jawaban: joi.string().messages({
|
||||
'string.base': 'Jawaban harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createFaqModel,
|
||||
updateFaqModel,
|
||||
};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
const prisma = require('../../../helpers/db/db_connection');
|
||||
|
||||
class FaqRepository {
|
||||
async create(data) {
|
||||
return prisma.faq.create({
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return prisma.faq.findMany({
|
||||
orderBy: {
|
||||
created_at: 'desc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id) {
|
||||
return prisma.faq.findUnique({
|
||||
where: { id: parseInt(id) },
|
||||
});
|
||||
}
|
||||
|
||||
async updateById(id, data) {
|
||||
return prisma.faq.update({
|
||||
where: { id: parseInt(id) },
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteById(id) {
|
||||
return prisma.faq.delete({
|
||||
where: { id: parseInt(id) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FaqRepository();
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
const faqRepository = require('../repositories/faq.repositories');
|
||||
const { data, error } = require('../../../helpers/utils/wrapper');
|
||||
const { NotFoundError, BadRequestError } = require('../../../helpers/error');
|
||||
|
||||
class FaqService {
|
||||
async createFaq(faqData) {
|
||||
try {
|
||||
const result = await faqRepository.create(faqData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllFaq() {
|
||||
try {
|
||||
const results = await faqRepository.findAll();
|
||||
return data(results);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getFaqById(id) {
|
||||
try {
|
||||
const result = await faqRepository.findById(id);
|
||||
|
||||
if (!result) {
|
||||
return error(new NotFoundError('FAQ Tidak Ditemukan'));
|
||||
}
|
||||
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async updateFaq(id, faqData) {
|
||||
try {
|
||||
const checkResult = await this.getFaqById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const result = await faqRepository.updateById(id, faqData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFaq(id) {
|
||||
try {
|
||||
const checkResult = await this.getFaqById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const result = await faqRepository.deleteById(id);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FaqService();
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
const feedbackService = require('../services/feedback.service');
|
||||
const { response, error } = require('../../../helpers/utils/wrapper');
|
||||
const { BadRequestError, InternalServerError } = require('../../../helpers/error');
|
||||
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
|
||||
const { isValidPayload } = require('../../../helpers/utils/validator');
|
||||
const { createFeedbackModel, updateFeedbackModel, getAllFeedbackModel } = require('../models/feedback.model');
|
||||
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
|
||||
|
||||
class FeedbackController {
|
||||
async addFeedback(req, res) {
|
||||
try {
|
||||
const image_path = req.file;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, createFeedbackModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
if (image_path?.filename) {
|
||||
await deleteFileIfExists(image_path.filename);
|
||||
}
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await feedbackService.createFeedback(validatePayload.data, image_path);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Feedback berhasil dibuat', SUCCESS.CREATED);
|
||||
} catch (err) {
|
||||
if (req.file?.filename) {
|
||||
await deleteFileIfExists(req.file.filename);
|
||||
}
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllFeedback(req, res) {
|
||||
try {
|
||||
const validatePayload = isValidPayload(req.query, getAllFeedbackModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Query parameter tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await feedbackService.getAllFeedback(validatePayload.data);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Berhasil mengambil semua feedback', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getFeedbackById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await feedbackService.getFeedbackById(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Berhasil mengambil feedback berdasarkan ID', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async updateFeedback(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const image_path = req.file;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, updateFeedbackModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
if (image_path?.filename) {
|
||||
await deleteFileIfExists(image_path.filename);
|
||||
}
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await feedbackService.updateFeedback(id, validatePayload.data, image_path);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Feedback berhasil diperbarui', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
if (req.file?.filename) {
|
||||
await deleteFileIfExists(req.file.filename);
|
||||
}
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFeedback(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await feedbackService.deleteFeedback(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Feedback 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 FeedbackController();
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
const feedbackController = require('./controllers/feedback.controller');
|
||||
|
||||
module.exports = {
|
||||
feedbackController,
|
||||
};
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
const joi = require('joi');
|
||||
|
||||
const createFeedbackModel = joi.object().keys({
|
||||
nama: joi.string().max(255).required().messages({
|
||||
'string.base': 'Nama harus berupa teks.',
|
||||
'string.empty': 'Nama tidak boleh kosong.',
|
||||
'string.max': 'Nama maksimal {#limit} karakter.',
|
||||
'any.required': 'Nama wajib diisi.',
|
||||
}),
|
||||
|
||||
universitas: joi.string().max(255).required().messages({
|
||||
'string.base': 'Universitas harus berupa teks.',
|
||||
'string.empty': 'Universitas tidak boleh kosong.',
|
||||
'string.max': 'Universitas maksimal {#limit} karakter.',
|
||||
'any.required': 'Universitas wajib diisi.',
|
||||
}),
|
||||
|
||||
pesan: joi.string().required().messages({
|
||||
'string.base': 'Pesan harus berupa teks.',
|
||||
'string.empty': 'Pesan tidak boleh kosong.',
|
||||
'any.required': 'Pesan wajib diisi.',
|
||||
}),
|
||||
|
||||
batch: joi.number().integer().min(1).required().messages({
|
||||
'number.base': 'Batch harus berupa angka.',
|
||||
'number.integer': 'Batch harus berupa bilangan bulat.',
|
||||
'number.min': 'Batch minimal {#limit}.',
|
||||
'any.required': 'Batch wajib diisi.',
|
||||
}),
|
||||
|
||||
posisi: joi.string().max(255).required().messages({
|
||||
'string.base': 'Posisi harus berupa teks.',
|
||||
'string.empty': 'Posisi tidak boleh kosong.',
|
||||
'string.max': 'Posisi maksimal {#limit} karakter.',
|
||||
'any.required': 'Posisi wajib diisi.',
|
||||
}),
|
||||
|
||||
tahun: joi.number().integer().min(2000).max(2100).required().messages({
|
||||
'number.base': 'Tahun harus berupa angka.',
|
||||
'number.integer': 'Tahun harus berupa bilangan bulat.',
|
||||
'number.min': 'Tahun minimal {#limit}.',
|
||||
'number.max': 'Tahun maksimal {#limit}.',
|
||||
'any.required': 'Tahun wajib diisi.',
|
||||
}),
|
||||
|
||||
image_path: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Path gambar harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
const updateFeedbackModel = joi.object().keys({
|
||||
nama: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Nama harus berupa teks.',
|
||||
'string.max': 'Nama maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
universitas: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Universitas harus berupa teks.',
|
||||
'string.max': 'Universitas maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
pesan: joi.string().optional().messages({
|
||||
'string.base': 'Pesan harus berupa teks.',
|
||||
}),
|
||||
|
||||
batch: joi.number().integer().min(1).optional().messages({
|
||||
'number.base': 'Batch harus berupa angka.',
|
||||
'number.integer': 'Batch harus berupa bilangan bulat.',
|
||||
'number.min': 'Batch minimal {#limit}.',
|
||||
}),
|
||||
|
||||
posisi: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Posisi harus berupa teks.',
|
||||
'string.max': 'Posisi maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
tahun: joi.number().integer().min(2000).max(2100).optional().messages({
|
||||
'number.base': 'Tahun harus berupa angka.',
|
||||
'number.integer': 'Tahun harus berupa bilangan bulat.',
|
||||
'number.min': 'Tahun minimal {#limit}.',
|
||||
'number.max': 'Tahun maksimal {#limit}.',
|
||||
}),
|
||||
|
||||
image_path: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Path gambar harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
const getAllFeedbackModel = joi.object().keys({
|
||||
batch: joi.number().integer().min(1).optional().messages({
|
||||
'number.base': 'Batch harus berupa angka.',
|
||||
'number.integer': 'Batch harus berupa bilangan bulat.',
|
||||
'number.min': 'Batch minimal {#limit}.',
|
||||
}),
|
||||
|
||||
tahun: joi.number().integer().min(2000).max(2100).optional().messages({
|
||||
'number.base': 'Tahun harus berupa angka.',
|
||||
'number.integer': 'Tahun harus berupa bilangan bulat.',
|
||||
'number.min': 'Tahun minimal {#limit}.',
|
||||
'number.max': 'Tahun maksimal {#limit}.',
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createFeedbackModel,
|
||||
updateFeedbackModel,
|
||||
getAllFeedbackModel,
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
const prisma = require('../../../helpers/db/db_connection');
|
||||
|
||||
class FeedbackRepository {
|
||||
async create(feedbackData) {
|
||||
return prisma.feedback.create({
|
||||
data: {
|
||||
...feedbackData,
|
||||
created_at: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return prisma.feedback.findMany({
|
||||
orderBy: {
|
||||
created_at: 'desc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findAllWithFilters({ batch, tahun }) {
|
||||
const where = {};
|
||||
|
||||
if (batch) {
|
||||
where.batch = parseInt(batch);
|
||||
}
|
||||
|
||||
if (tahun) {
|
||||
where.tahun = parseInt(tahun);
|
||||
}
|
||||
|
||||
return prisma.feedback.findMany({
|
||||
where,
|
||||
orderBy: {
|
||||
created_at: 'desc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id) {
|
||||
return prisma.feedback.findUnique({
|
||||
where: { id: parseInt(id) },
|
||||
});
|
||||
}
|
||||
|
||||
async updateById(id, feedbackData) {
|
||||
return prisma.feedback.update({
|
||||
where: { id: parseInt(id) },
|
||||
data: feedbackData,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteById(id) {
|
||||
return prisma.feedback.delete({
|
||||
where: { id: parseInt(id) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FeedbackRepository();
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
const path = require('path');
|
||||
const feedbackRepository = require('../repositories/feedback.repository');
|
||||
const imageUploadHelper = require('../../../helpers/utils/imageUpload.helper');
|
||||
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
|
||||
const { data, error } = require('../../../helpers/utils/wrapper');
|
||||
const { NotFoundError } = require('../../../helpers/error');
|
||||
|
||||
class FeedbackService {
|
||||
async createFeedback(feedbackData, imageFile) {
|
||||
try {
|
||||
const imagePath = imageUploadHelper.uploadImage(imageFile);
|
||||
|
||||
const newFeedbackData = {
|
||||
...feedbackData,
|
||||
image_path: imagePath,
|
||||
};
|
||||
|
||||
const result = await feedbackRepository.create(newFeedbackData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
if (imageFile?.filename) {
|
||||
try {
|
||||
await deleteFileIfExists(imageFile.filename);
|
||||
} catch (cleanupErr) {
|
||||
console.error('Failed to cleanup file:', cleanupErr);
|
||||
}
|
||||
}
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllFeedback(query = {}) {
|
||||
try {
|
||||
const feedbackList = await feedbackRepository.findAllWithFilters(query);
|
||||
|
||||
if (feedbackList.length === 0) {
|
||||
return data([]);
|
||||
}
|
||||
|
||||
return data(feedbackList);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getFeedbackById(id) {
|
||||
try {
|
||||
const feedback = await feedbackRepository.findById(id);
|
||||
|
||||
if (!feedback) {
|
||||
return error(new NotFoundError('Feedback tidak ditemukan'));
|
||||
}
|
||||
|
||||
return data(feedback);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async updateFeedback(id, feedbackData, imageFile) {
|
||||
let oldImagePath = null;
|
||||
let newImageUploaded = false;
|
||||
|
||||
try {
|
||||
const checkResult = await this.getFeedbackById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const existingFeedback = checkResult.data;
|
||||
|
||||
if (imageFile) {
|
||||
oldImagePath = existingFeedback.image_path;
|
||||
|
||||
feedbackData.image_path = imageUploadHelper.uploadImage(imageFile);
|
||||
newImageUploaded = true;
|
||||
|
||||
if (oldImagePath) {
|
||||
const oldFileName = path.basename(oldImagePath);
|
||||
await deleteFileIfExists(oldFileName);
|
||||
}
|
||||
}
|
||||
|
||||
const result = await feedbackRepository.updateById(id, feedbackData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
if (newImageUploaded && imageFile?.filename) {
|
||||
try {
|
||||
await deleteFileIfExists(imageFile.filename);
|
||||
} catch (cleanupErr) {
|
||||
console.error('Failed to cleanup new file:', cleanupErr);
|
||||
}
|
||||
}
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFeedback(id) {
|
||||
try {
|
||||
const checkResult = await this.getFeedbackById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const feedback = checkResult.data;
|
||||
|
||||
if (feedback.image_path) {
|
||||
const fileName = path.basename(feedback.image_path);
|
||||
await deleteFileIfExists(fileName);
|
||||
}
|
||||
|
||||
const result = await feedbackRepository.deleteById(id);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FeedbackService();
|
||||
@@ -0,0 +1,126 @@
|
||||
const hasilResearchService = require('../services/hasilResearch.service');
|
||||
const { response, error } = require('../../../helpers/utils/wrapper');
|
||||
const { BadRequestError, InternalServerError } = require('../../../helpers/error');
|
||||
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
|
||||
const { isValidPayload } = require('../../../helpers/utils/validator');
|
||||
const { createHasilResearchModel, updateHasilResearchModel } = require('../models/hasilResearch.model');
|
||||
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
|
||||
|
||||
class HasilResearchController {
|
||||
async addHasilResearch(req, res) {
|
||||
try {
|
||||
const image = req.file;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, createHasilResearchModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
if (image?.filename) {
|
||||
await deleteFileIfExists(image.filename);
|
||||
}
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await hasilResearchService.createHasilResearch(validatePayload.data, image);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Data hasil research berhasil ditambahkan', SUCCESS.CREATED);
|
||||
} catch (err) {
|
||||
if (req.file?.filename) {
|
||||
await deleteFileIfExists(req.file.filename);
|
||||
}
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getHasilResearch(req, res) {
|
||||
try {
|
||||
const result = await hasilResearchService.getAllHasilResearch();
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Data research berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getHasilResearchById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await hasilResearchService.getHasilResearchById(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Data research berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async updateHasilResearch(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const image = req.file;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, updateHasilResearchModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
if (image?.filename) {
|
||||
await deleteFileIfExists(image.filename);
|
||||
}
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await hasilResearchService.updateHasilResearch(id, validatePayload.data, image);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Data hasil research berhasil diperbarui', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
if (req.file?.filename) {
|
||||
await deleteFileIfExists(req.file.filename);
|
||||
}
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteHasilResearch(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await hasilResearchService.deleteHasilResearch(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Data hasil research 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 HasilResearchController();
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
const hasilResearchController = require('./controllers/hasilResearch.controller');
|
||||
const hasilResearchService = require('./services/hasilResearch.service');
|
||||
const hasilResearchRepository = require('./repositories/hasilResearch.repository');
|
||||
|
||||
module.exports = {
|
||||
hasilResearchController,
|
||||
hasilResearchService,
|
||||
hasilResearchRepository
|
||||
};
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
joi = require('joi');
|
||||
|
||||
const urlRegex = /^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;
|
||||
|
||||
const createHasilResearchModel = joi.object().keys({
|
||||
nama_project: joi.string().max(255).required().messages({
|
||||
'string.base': 'Nama project harus berupa teks.',
|
||||
'string.empty': 'Nama project tidak boleh kosong.',
|
||||
'string.max': 'Nama project maksimal {#limit} karakter.',
|
||||
'any.required': 'Nama project wajib diisi.',
|
||||
}),
|
||||
|
||||
deskripsi: joi.string().required().messages({
|
||||
'string.base': 'Deskripsi harus berupa teks.',
|
||||
'string.empty': 'Deskripsi tidak boleh kosong.',
|
||||
'any.required': 'Deskripsi wajib diisi.',
|
||||
}),
|
||||
|
||||
link_project: joi.string().pattern(urlRegex).optional().allow('', null).messages({
|
||||
'string.base': 'Link project harus berupa teks.',
|
||||
'string.pattern.base': 'Format URL tidak valid.',
|
||||
}),
|
||||
|
||||
image_path: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Path gambar harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
const updateHasilResearchModel = joi.object().keys({
|
||||
nama_project: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Nama project harus berupa teks.',
|
||||
'string.max': 'Nama project maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
deskripsi: joi.string().optional().messages({
|
||||
'string.base': 'Deskripsi harus berupa teks.',
|
||||
}),
|
||||
|
||||
link_project: joi.string().pattern(urlRegex).optional().allow('', null).messages({
|
||||
'string.base': 'Link project harus berupa teks.',
|
||||
'string.pattern.base': 'Format URL tidak valid.',
|
||||
}),
|
||||
|
||||
image_path: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Path gambar harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createHasilResearchModel,
|
||||
updateHasilResearchModel,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
const prisma = require('../../../helpers/db/db_connection');
|
||||
|
||||
class HasilResearchRepository {
|
||||
async create(researchData) {
|
||||
return prisma.hasilResearch.create({
|
||||
data: researchData,
|
||||
});
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return prisma.hasilResearch.findMany();
|
||||
}
|
||||
|
||||
async findById(id) {
|
||||
return prisma.hasilResearch.findUnique({
|
||||
where: { id: parseInt(id) },
|
||||
});
|
||||
}
|
||||
|
||||
async updateById(id, researchData) {
|
||||
return prisma.hasilResearch.update({
|
||||
where: { id: parseInt(id) },
|
||||
data: researchData,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteById(id) {
|
||||
return prisma.hasilResearch.delete({
|
||||
where: { id: parseInt(id) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new HasilResearchRepository();
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
const hasilResearchRepository = require('../repositories/hasilResearch.repository');
|
||||
const path = require('path');
|
||||
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
|
||||
const imageUploadHelper = require('../../../helpers/utils/imageUpload.helper');
|
||||
const { data, error } = require('../../../helpers/utils/wrapper');
|
||||
const { NotFoundError } = require('../../../helpers/error');
|
||||
|
||||
class HasilResearchService {
|
||||
async createHasilResearch(researchData, imageFile) {
|
||||
try {
|
||||
const imagePath = imageUploadHelper.uploadImage(imageFile);
|
||||
|
||||
const newResearchData = {
|
||||
...researchData,
|
||||
image_path: imagePath,
|
||||
};
|
||||
|
||||
const result = await hasilResearchRepository.create(newResearchData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
if (imageFile?.filename) {
|
||||
try {
|
||||
await deleteFileIfExists(imageFile.filename);
|
||||
} catch (cleanupErr) {
|
||||
console.error('Failed to cleanup file:', cleanupErr);
|
||||
}
|
||||
}
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllHasilResearch() {
|
||||
try {
|
||||
const researchList = await hasilResearchRepository.findAll();
|
||||
return data(researchList);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getHasilResearchById(id) {
|
||||
try {
|
||||
const research = await hasilResearchRepository.findById(id);
|
||||
|
||||
if (!research) {
|
||||
return error(new NotFoundError('Hasil Research Tidak Ditemukan'));
|
||||
}
|
||||
|
||||
return data(research);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async updateHasilResearch(id, researchData, imageFile) {
|
||||
let oldImagePath = null;
|
||||
let newImageUploaded = false;
|
||||
|
||||
try {
|
||||
const checkResult = await this.getHasilResearchById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const existingResearch = checkResult.data;
|
||||
let imagePath = existingResearch.image_path;
|
||||
|
||||
if (imageFile) {
|
||||
oldImagePath = existingResearch.image_path;
|
||||
|
||||
imagePath = imageUploadHelper.uploadImage(imageFile);
|
||||
newImageUploaded = true;
|
||||
|
||||
if (oldImagePath) {
|
||||
const oldFileName = path.basename(oldImagePath);
|
||||
await deleteFileIfExists(oldFileName);
|
||||
}
|
||||
}
|
||||
|
||||
const updatedResearchData = {
|
||||
...researchData,
|
||||
image_path: imagePath,
|
||||
};
|
||||
|
||||
const result = await hasilResearchRepository.updateById(id, updatedResearchData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
if (newImageUploaded && imageFile?.filename) {
|
||||
try {
|
||||
await deleteFileIfExists(imageFile.filename);
|
||||
} catch (cleanupErr) {
|
||||
console.error('Failed to cleanup new file:', cleanupErr);
|
||||
}
|
||||
}
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteHasilResearch(id) {
|
||||
try {
|
||||
const checkResult = await this.getHasilResearchById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const research = checkResult.data;
|
||||
|
||||
if (research.image_path) {
|
||||
const fileName = path.basename(research.image_path);
|
||||
await deleteFileIfExists(fileName);
|
||||
}
|
||||
|
||||
const result = await hasilResearchRepository.deleteById(id);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new HasilResearchService();
|
||||
@@ -0,0 +1,312 @@
|
||||
const lamaranMagangService = require('../services/lamaranMagang.service');
|
||||
const { response, error, paginationResponse } = require('../../../helpers/utils/wrapper');
|
||||
const { InternalServerError } = require('../../../helpers/error');
|
||||
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
|
||||
const { isValidPayload } = require('../../../helpers/utils/validator');
|
||||
const { createLamaranMagangModel, updateLamaranStatusModel, getAllLamaranMagangModel, statisticModel } = require('../models/lamaranMagang.model');
|
||||
const { cleanupUploadedFiles } = require('../helpers/cleanUpFiles');
|
||||
|
||||
|
||||
class LamaranMagangController {
|
||||
async addLamaranMagang(req, res) {
|
||||
try {
|
||||
const { id_lowongan_magang } = req.params;
|
||||
const uploadedFiles = {
|
||||
cv: req.files?.cv?.[0],
|
||||
portofolio: req.files?.portofolio?.[0],
|
||||
};
|
||||
|
||||
const payloadWithFiles = {
|
||||
...req.body,
|
||||
cv: uploadedFiles.cv?.filename,
|
||||
portofolio: uploadedFiles.portofolio?.filename,
|
||||
id_lowongan_magang,
|
||||
};
|
||||
|
||||
const validatePayload = isValidPayload(payloadWithFiles, createLamaranMagangModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
await cleanupUploadedFiles(uploadedFiles);
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const { id_lowongan_magang: validatedLowonganId, cv, portofolio, ...mahasiswaFormData } = validatePayload.data;
|
||||
|
||||
const result = await lamaranMagangService.createLamaranMagang(
|
||||
validatedLowonganId,
|
||||
mahasiswaFormData,
|
||||
uploadedFiles
|
||||
);
|
||||
|
||||
if (result.err) {
|
||||
await cleanupUploadedFiles(uploadedFiles);
|
||||
return response(res, 'fail', result, 'Gagal mengajukan lamaran magang', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Lamaran magang berhasil diajukan', SUCCESS.CREATED);
|
||||
} catch (err) {
|
||||
|
||||
await cleanupUploadedFiles({
|
||||
cv: req.files?.cv?.[0],
|
||||
portofolio: req.files?.portofolio?.[0],
|
||||
});
|
||||
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
error(new InternalServerError(err.message)),
|
||||
'Terjadi kesalahan yang tidak terduga',
|
||||
ERROR.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllLamaranMagang(req, res) {
|
||||
try {
|
||||
const validatePayload = isValidPayload(req.query, getAllLamaranMagangModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const query = validatePayload.data;
|
||||
|
||||
const result = await lamaranMagangService.getAllLamaranMagang(query);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return paginationResponse(res, 'success', result, 'Lamaran magang berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getLamaranByID(req, res) {
|
||||
try {
|
||||
const { id_lamaran_magang } = req.params;
|
||||
|
||||
const result = await lamaranMagangService.getLamaranById(id_lamaran_magang);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Lamaran magang berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getDetailLamaranById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await lamaranMagangService.getDetailLamaranById(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Detail lamaran magang berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async updateStatusLamaran(req, res) {
|
||||
try {
|
||||
const { id_lamaran_magang } = req.params;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, updateLamaranStatusModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const { status } = validatePayload.data;
|
||||
const result = await lamaranMagangService.updateLamaranStatus(id_lamaran_magang, status);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Status lamaran berhasil diperbarui', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteLamaran(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await lamaranMagangService.deleteLamaran(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Lamaran magang berhasil dihapus', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async exportLamaranToExcel(req, res) {
|
||||
try {
|
||||
const result = await lamaranMagangService.exportToExcel();
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
const workbook = result.data;
|
||||
|
||||
res.setHeader(
|
||||
'Content-Type',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
);
|
||||
res.setHeader(
|
||||
'Content-Disposition',
|
||||
'attachment; filename=lamaran_magang.xlsx'
|
||||
);
|
||||
|
||||
await workbook.xlsx.write(res);
|
||||
res.end();
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getDashboardStatistics(req, res) {
|
||||
try {
|
||||
const validateQuery = isValidPayload(req.query, statisticModel);
|
||||
|
||||
if (validateQuery.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validateQuery.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const query = validateQuery.data
|
||||
const result = await lamaranMagangService.getDashboardStatistics(query);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result, 'Gagal mengambil statistik dashboard', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Statistik dashboard berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getStatisticsByPosition(req, res) {
|
||||
try {
|
||||
|
||||
const validateQuery = isValidPayload(req.query, statisticModel);
|
||||
|
||||
if (validateQuery.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validateQuery.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const query = validateQuery.data;
|
||||
const result = await lamaranMagangService.getStatisticsByPosition(query);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result, 'Gagal mengambil statistik berdasarkan posisi', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Statistik berdasarkan posisi berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getStatisticsByCountry(req, res) {
|
||||
try {
|
||||
const validateQuery = isValidPayload(req.query, statisticModel);
|
||||
|
||||
if (validateQuery.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validateQuery.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const query = validateQuery.data;
|
||||
const result = await lamaranMagangService.getStatisticsByCountry(query);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result, 'Gagal mengambil statistik berdasarkan negara', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Statistik berdasarkan negara berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getStatisticsByUniversity(req, res) {
|
||||
try {
|
||||
const validateQuery = isValidPayload(req.query, statisticModel);
|
||||
|
||||
if (validateQuery.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validateQuery.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
const query = validateQuery.data;
|
||||
const result = await lamaranMagangService.getStatisticsByUniversity(query);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result, 'Gagal mengambil statistik berdasarkan universitas', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Statistik berdasarkan universitas berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = new LamaranMagangController();
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
|
||||
|
||||
const cleanupUploadedFiles = async (files) => {
|
||||
if (files?.cv?.filename) {
|
||||
await deleteFileIfExists(files.cv.filename);
|
||||
}
|
||||
if (files?.portofolio?.filename) {
|
||||
await deleteFileIfExists(files.portofolio.filename);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
cleanupUploadedFiles,
|
||||
};
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
const transporter = require('../../../helpers/utils/mail');
|
||||
|
||||
const sendConfirmationEmail = async (mahasiswa, lowongan) => {
|
||||
try {
|
||||
const { nama_depan, nama_belakang, email } = mahasiswa;
|
||||
const { posisi } = lowongan;
|
||||
const fullName = `${nama_depan}${nama_belakang ? ' ' + nama_belakang : ''}`;
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.MAIL_USER,
|
||||
to: email,
|
||||
subject: 'Konfirmasi Penerimaan Lamaran Anda',
|
||||
headers: {
|
||||
'X-Priority': '1',
|
||||
'X-MSMail-Priority': 'High',
|
||||
Importance: 'high',
|
||||
},
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: auto; padding: 20px; border: 1px solid #e0e0e0; border-radius: 10px;">
|
||||
<h2 style="color: #333;">Lamaran Anda Telah Kami Terima</h2>
|
||||
<p>Halo ${fullName},</p>
|
||||
|
||||
<p>Terima kasih telah mengirimkan lamaran untuk posisi <strong>${posisi}</strong> di <strong>Humic Engineering</strong>.</p>
|
||||
|
||||
<p>Kami telah menerima dokumen dan informasi yang Anda kirimkan, dan saat ini tim rekrutmen kami sedang meninjaunya dengan seksama.</p>
|
||||
|
||||
<p>Jika profil Anda sesuai dengan kebutuhan kami, tim kami akan menghubungi Anda untuk tahapan seleksi berikutnya. Proses ini dapat memakan waktu hingga beberapa minggu.</p>
|
||||
|
||||
<p>Kami sangat menghargai minat Anda untuk bergabung bersama tim kami.</p>
|
||||
|
||||
<p>Salam hangat,</p>
|
||||
<p><strong>Tim Rekrutmen Humic Engineering</strong></p>
|
||||
|
||||
<hr style="margin: 30px 0;">
|
||||
<p style="font-size: 12px; color: #888;">Email ini dikirim secara otomatis. Jika Anda memiliki pertanyaan lebih lanjut, silakan hubungi kami melalui email resmi yang tersedia di website kami.</p>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
} catch (error) {
|
||||
console.error('Error sending confirmation email:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const sendStatusEmail = async (lamaran, status) => {
|
||||
try {
|
||||
const { mahasiswa, lowongan_magang } = lamaran;
|
||||
const { nama_depan, nama_belakang, email } = mahasiswa;
|
||||
const { posisi } = lowongan_magang;
|
||||
|
||||
const isAccepted = status === 'diterima';
|
||||
const fullName = `${nama_depan}${nama_belakang ? ' ' + nama_belakang : ''}`;
|
||||
|
||||
const subject = isAccepted
|
||||
? 'Selamat! Lamaran Anda Diterima'
|
||||
: 'Pemberitahuan Status Lamaran Anda';
|
||||
|
||||
const message = isAccepted
|
||||
? `
|
||||
<p>Halo ${fullName},</p>
|
||||
<p>Selamat! Lamaran Anda untuk posisi <strong>${posisi}</strong> di <strong>Humic Engineering</strong> telah diterima.</p>
|
||||
<p>Tim kami sangat terkesan dengan profil dan kualifikasi Anda. Kami akan segera menghubungi Anda terkait tahapan selanjutnya.</p>
|
||||
<p>Terima kasih telah melamar dan kami menantikan kerja sama yang luar biasa bersama Anda.</p>
|
||||
`
|
||||
: `
|
||||
<p>Halo ${fullName},</p>
|
||||
<p>Terima kasih atas lamaran Anda untuk posisi <strong>${posisi}</strong> di <strong>Humic Engineering</strong>.</p>
|
||||
<p>Setelah mempertimbangkan secara seksama, kami memutuskan untuk tidak melanjutkan proses lamaran Anda ke tahap berikutnya.</p>
|
||||
<p>Jangan berkecil hati—kami sangat menghargai waktu dan usaha Anda. Kami mendorong Anda untuk terus mencoba dan semoga sukses dalam perjalanan karier Anda.</p>
|
||||
`;
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.MAIL_USER,
|
||||
to: email,
|
||||
subject,
|
||||
headers: {
|
||||
'X-Priority': '1',
|
||||
'X-MSMail-Priority': 'High',
|
||||
Importance: 'high',
|
||||
},
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: auto; padding: 20px; border: 1px solid #e0e0e0; border-radius: 10px;">
|
||||
${message}
|
||||
<p>Salam hangat,</p>
|
||||
<p><strong>Tim Rekrutmen Humic Engineering</strong></p>
|
||||
<hr style="margin: 30px 0;">
|
||||
<p style="font-size: 12px; color: #888;">Email ini dikirim secara otomatis. Untuk informasi lebih lanjut, silakan hubungi kami melalui kontak resmi.</p>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
} catch (error) {
|
||||
console.error('Gagal mengirim email status lamaran:', error);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
sendConfirmationEmail,
|
||||
sendStatusEmail,
|
||||
};
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
const lamaranMagangController = require('./controllers/lamaranMagang.controller');
|
||||
|
||||
module.exports = {
|
||||
lamaranMagangController,
|
||||
};
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
const joi = require('joi');
|
||||
|
||||
const phoneRegex = /^(\+62|62|0)8[1-9][0-9]{6,11}$/;
|
||||
|
||||
const createLamaranMagangModel = joi.object().keys({
|
||||
id_lowongan_magang: joi.string().uuid().required().messages({
|
||||
'string.base': 'ID lowongan magang harus berupa teks.',
|
||||
'string.empty': 'ID lowongan magang tidak boleh kosong.',
|
||||
'string.guid': 'Format ID lowongan magang tidak valid.',
|
||||
'any.required': 'ID lowongan magang wajib diisi.',
|
||||
}),
|
||||
|
||||
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).optional().allow('', null).messages({
|
||||
'string.base': 'Nama belakang harus berupa teks.',
|
||||
'string.max': 'Nama belakang maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
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.',
|
||||
}),
|
||||
|
||||
kontak: joi.string().pattern(phoneRegex).max(255).required().messages({
|
||||
'string.base': 'Kontak harus berupa teks.',
|
||||
'string.empty': 'Kontak tidak boleh kosong.',
|
||||
'string.pattern.base': 'Format nomor kontak tidak valid. Gunakan format Indonesia (08xx atau +628xx).',
|
||||
'string.max': 'Kontak maksimal {#limit} karakter.',
|
||||
'any.required': 'Kontak wajib diisi.',
|
||||
}),
|
||||
|
||||
jurusan: joi.string().max(255).required().messages({
|
||||
'string.base': 'Jurusan harus berupa teks.',
|
||||
'string.empty': 'Jurusan tidak boleh kosong.',
|
||||
'string.max': 'Jurusan maksimal {#limit} karakter.',
|
||||
'any.required': 'Jurusan wajib diisi.',
|
||||
}),
|
||||
|
||||
universitas: joi.string().max(255).required().messages({
|
||||
'string.base': 'Universitas harus berupa teks.',
|
||||
'string.empty': 'Universitas tidak boleh kosong.',
|
||||
'string.max': 'Universitas maksimal {#limit} karakter.',
|
||||
'any.required': 'Universitas wajib diisi.',
|
||||
}),
|
||||
|
||||
negara: joi.string().max(255).required().messages({
|
||||
'string.base': 'Negara harus berupa teks.',
|
||||
'string.empty': 'Negara tidak boleh kosong.',
|
||||
'string.max': 'Negara maksimal {#limit} karakter.',
|
||||
'any.required': 'Negara wajib diisi.',
|
||||
}),
|
||||
|
||||
motivasi: joi.string().required().messages({
|
||||
'string.base': 'Motivasi harus berupa teks.',
|
||||
'string.empty': 'Motivasi tidak boleh kosong.',
|
||||
'any.required': 'Motivasi wajib diisi.',
|
||||
}),
|
||||
|
||||
relevant_skills: joi.string().max(255).required().messages({
|
||||
'string.base': 'Keahlian yang relevan harus berupa teks.',
|
||||
'string.empty': 'Keahlian yang relevan tidak boleh kosong.',
|
||||
'string.max': 'Keahlian yang relevan maksimal {#limit} karakter.',
|
||||
'any.required': 'Keahlian yang relevan wajib diisi.',
|
||||
}),
|
||||
|
||||
cv: joi.string().required().messages({
|
||||
'string.base': 'Path CV harus berupa teks.',
|
||||
'string.empty': 'Path CV tidak boleh kosong.',
|
||||
'any.required': 'CV wajib diupload.',
|
||||
}),
|
||||
|
||||
portofolio: joi.string().required().messages({
|
||||
'string.base': 'Path portofolio harus berupa teks.',
|
||||
'string.empty': 'Path portofolio tidak boleh kosong.',
|
||||
'any.required': 'Portofolio wajib diupload.',
|
||||
}),
|
||||
|
||||
status: joi.string().valid('diterima', 'diproses', 'ditolak').default('pending').messages({
|
||||
'string.base': 'Status harus berupa teks.',
|
||||
'any.only': 'Status hanya boleh "diterima", "diproses", atau "ditolak".',
|
||||
}),
|
||||
});
|
||||
|
||||
const updateLamaranStatusModel = joi.object().keys({
|
||||
status: joi.string().valid('diterima', 'diproses', 'ditolak').required().messages({
|
||||
'string.base': 'Status harus berupa teks.',
|
||||
'string.empty': 'Status tidak boleh kosong.',
|
||||
'any.only': 'Status hanya boleh "diterima", "diproses", atau "ditolak".',
|
||||
'any.required': 'Status wajib diisi.',
|
||||
}),
|
||||
});
|
||||
|
||||
const updateLamaranMagangModel = joi.object().keys({
|
||||
id_lowongan_magang: joi.string().uuid().optional().messages({
|
||||
'string.base': 'ID lowongan magang harus berupa teks.',
|
||||
'string.guid': 'Format ID lowongan magang tidak valid.',
|
||||
}),
|
||||
|
||||
nama_depan: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Nama depan harus berupa teks.',
|
||||
'string.max': 'Nama depan maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
nama_belakang: joi.string().max(255).optional().allow('', null).messages({
|
||||
'string.base': 'Nama belakang harus berupa teks.',
|
||||
'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.',
|
||||
}),
|
||||
|
||||
kontak: joi.string().pattern(phoneRegex).max(255).optional().messages({
|
||||
'string.base': 'Kontak harus berupa teks.',
|
||||
'string.pattern.base': 'Format nomor kontak tidak valid. Gunakan format Indonesia (08xx atau +628xx).',
|
||||
'string.max': 'Kontak maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
jurusan: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Jurusan harus berupa teks.',
|
||||
'string.max': 'Jurusan maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
universitas: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Universitas harus berupa teks.',
|
||||
'string.max': 'Universitas maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
negara: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Negara harus berupa teks.',
|
||||
'string.max': 'Negara maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
motivasi: joi.string().optional().messages({
|
||||
'string.base': 'Motivasi harus berupa teks.',
|
||||
}),
|
||||
|
||||
relevant_skills: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Keahlian yang relevan harus berupa teks.',
|
||||
'string.max': 'Keahlian yang relevan maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
cv_path: joi.string().optional().messages({
|
||||
'string.base': 'Path CV harus berupa teks.',
|
||||
}),
|
||||
|
||||
portofolio_path: joi.string().optional().messages({
|
||||
'string.base': 'Path portofolio harus berupa teks.',
|
||||
}),
|
||||
|
||||
status: joi.string().valid('diterima', 'diproses', 'ditolak').optional().messages({
|
||||
'string.base': 'Status harus berupa teks.',
|
||||
'any.only': 'Status hanya boleh "diterima", "diproses", atau "ditolak".',
|
||||
}),
|
||||
});
|
||||
|
||||
const getAllLamaranMagangModel = joi.object().keys({
|
||||
page: joi.number().integer().min(0).optional().messages({
|
||||
'number.base': 'page harus berupa angka.',
|
||||
}),
|
||||
limit: joi.number().integer().min(1).optional().messages({
|
||||
'number.base': 'Limit harus berupa angka.',
|
||||
}),
|
||||
posisi: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Posisi harus berupa teks.',
|
||||
'string.max': 'Posisi maksimal {#limit} karakter.',
|
||||
}),
|
||||
status: joi.string().valid('diterima', 'diproses', 'ditolak').optional().messages({
|
||||
'string.base': 'Status harus berupa teks.',
|
||||
'any.only': 'Status hanya boleh "diterima", "diproses", atau "ditolak".',
|
||||
}),
|
||||
universitas: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Universitas harus berupa teks.',
|
||||
'string.max': 'Universitas maksimal {#limit} karakter.',
|
||||
}),
|
||||
id_lowongan: joi.string().uuid().optional().messages({
|
||||
'string.base': 'ID lowongan magang harus berupa teks.',
|
||||
'string.guid': 'Format ID lowongan magang tidak valid.',
|
||||
}),
|
||||
});
|
||||
|
||||
const statisticModel = joi.object().keys({
|
||||
batch: joi.number().integer().min(1).optional().messages({
|
||||
'number.base': 'Batch harus berupa angka.',
|
||||
'number.min': 'Batch minimal adalah {#limit}.',
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createLamaranMagangModel,
|
||||
updateLamaranStatusModel,
|
||||
updateLamaranMagangModel,
|
||||
getAllLamaranMagangModel,
|
||||
statisticModel,
|
||||
};
|
||||
@@ -0,0 +1,292 @@
|
||||
const prisma = require('../../../helpers/db/db_connection');
|
||||
const { Prisma } = require('../../../generated/prisma')
|
||||
|
||||
class LamaranMagangRepository {
|
||||
async create(lamaranData) {
|
||||
return prisma.lamaranMagang.create({
|
||||
data: {
|
||||
...lamaranData,
|
||||
created_at: new Date(),
|
||||
update_at: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findAll({ offset, limit, status, posisi, universitas, id_lowongan }) {
|
||||
const where = {};
|
||||
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
if (posisi) {
|
||||
where.lowongan_magang = { posisi: posisi };
|
||||
}
|
||||
if (universitas) {
|
||||
where.mahasiswa = { universitas: universitas };
|
||||
}
|
||||
if (id_lowongan) {
|
||||
where.id_lowongan_magang = id_lowongan;
|
||||
}
|
||||
|
||||
const lamaranList = await prisma.lamaranMagang.findMany({
|
||||
skip: offset,
|
||||
take: limit,
|
||||
where,
|
||||
include: {
|
||||
mahasiswa: {
|
||||
select: { nama_depan: true, nama_belakang: true },
|
||||
},
|
||||
lowongan_magang: {
|
||||
select: { posisi: true, kelompok_peminatan: true },
|
||||
},
|
||||
batch_data: {
|
||||
select: {
|
||||
id: true,
|
||||
batch_number: true,
|
||||
is_active: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return lamaranList;
|
||||
}
|
||||
|
||||
async countAllLamaran({ status, posisi, universitas }) {
|
||||
|
||||
const where = {};
|
||||
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
if (posisi) {
|
||||
where.lowongan_magang = { posisi: posisi };
|
||||
}
|
||||
if (universitas) {
|
||||
where.mahasiswa = { universitas: universitas };
|
||||
}
|
||||
|
||||
return prisma.lamaranMagang.count(
|
||||
{ where }
|
||||
);
|
||||
}
|
||||
|
||||
async findById(id_lamaran_magang) {
|
||||
return prisma.lamaranMagang.findUnique({
|
||||
where: { id: parseInt(id_lamaran_magang) },
|
||||
include: {
|
||||
mahasiswa: {
|
||||
select: {
|
||||
nama_depan: true,
|
||||
nama_belakang: true,
|
||||
email: true,
|
||||
kontak: true,
|
||||
jurusan: true,
|
||||
cv_path: true,
|
||||
portofolio_path: true,
|
||||
motivasi: true,
|
||||
relevant_skills: true,
|
||||
universitas: true,
|
||||
negara: true,
|
||||
},
|
||||
},
|
||||
lowongan_magang: {
|
||||
select: {
|
||||
posisi: true,
|
||||
},
|
||||
},
|
||||
batch_data: {
|
||||
select: {
|
||||
id: true,
|
||||
batch_number: true,
|
||||
is_active: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByMahasiswaAndLowongan(id_mahasiswa, id_lowongan_magang) {
|
||||
return prisma.lamaranMagang.findFirst({
|
||||
where: {
|
||||
id_mahasiswa: parseInt(id_mahasiswa),
|
||||
id_lowongan_magang: id_lowongan_magang,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findAnyLamaranByMahasiswa(id_mahasiswa) {
|
||||
return prisma.lamaranMagang.findFirst({
|
||||
where: {
|
||||
id_mahasiswa: parseInt(id_mahasiswa),
|
||||
},
|
||||
include: {
|
||||
lowongan_magang: {
|
||||
select: {
|
||||
posisi: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findDetailById(idLamaran) {
|
||||
return prisma.lamaranMagang.findUnique({
|
||||
where: { id: parseInt(idLamaran) },
|
||||
include: {
|
||||
mahasiswa: true,
|
||||
lowongan_magang: true,
|
||||
batch_data: {
|
||||
select: {
|
||||
id: true,
|
||||
batch_number: true,
|
||||
is_active: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async updateStatus(id_lamaran_magang, status) {
|
||||
return prisma.lamaranMagang.update({
|
||||
where: { id: parseInt(id_lamaran_magang) },
|
||||
data: {
|
||||
status,
|
||||
update_at: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async deleteById(id) {
|
||||
return prisma.lamaranMagang.delete({
|
||||
where: { id: parseInt(id) },
|
||||
});
|
||||
}
|
||||
|
||||
async getStatistics({ query }) {
|
||||
let where = {};
|
||||
|
||||
if (query && query.batch) {
|
||||
where.batch = parseInt(query.batch);
|
||||
}
|
||||
|
||||
const [total, diterima, ditolak, diproses] = await Promise.all([
|
||||
prisma.lamaranMagang.count({ where }),
|
||||
prisma.lamaranMagang.count({ where: { ...where, status: 'diterima' } }),
|
||||
prisma.lamaranMagang.count({ where: { ...where, status: 'ditolak' } }),
|
||||
prisma.lamaranMagang.count({ where: { ...where, status: 'diproses' } }),
|
||||
]);
|
||||
|
||||
return { total, diterima, ditolak, diproses };
|
||||
}
|
||||
|
||||
async getStatisticsByPosition({ query }) {
|
||||
let where = {};
|
||||
|
||||
if (query && query.batch) {
|
||||
where.batch = parseInt(query.batch);
|
||||
}
|
||||
|
||||
const data = await prisma.lamaranMagang.groupBy({
|
||||
by: ['id_lowongan_magang'],
|
||||
where,
|
||||
_count: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const positions = await Promise.all(
|
||||
data.map(async (item) => {
|
||||
const lowongan = await prisma.lowonganMagang.findUnique({
|
||||
where: { id: item.id_lowongan_magang },
|
||||
select: { posisi: true },
|
||||
});
|
||||
|
||||
const [total, diterima, ditolak, diproses] = await Promise.all([
|
||||
prisma.lamaranMagang.count({
|
||||
where: { id_lowongan_magang: item.id_lowongan_magang },
|
||||
}),
|
||||
prisma.lamaranMagang.count({
|
||||
where: { id_lowongan_magang: item.id_lowongan_magang, status: 'diterima' },
|
||||
}),
|
||||
prisma.lamaranMagang.count({
|
||||
where: { id_lowongan_magang: item.id_lowongan_magang, status: 'ditolak' },
|
||||
}),
|
||||
prisma.lamaranMagang.count({
|
||||
where: { id_lowongan_magang: item.id_lowongan_magang, status: 'diproses' },
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
posisi: lowongan?.posisi || 'Tidak Diketahui',
|
||||
total,
|
||||
diterima,
|
||||
ditolak,
|
||||
diproses,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
async getStatisticsByCountry({ query }) {
|
||||
|
||||
// Note : Menggunakan Raw Query karena Prisma ngga dukung join yang kompleks (lebih mudah juga :b)
|
||||
const whereClause = query.batch
|
||||
? Prisma.sql`WHERE lm.batch = ${parseInt(query.batch)}`
|
||||
: Prisma.sql``;
|
||||
|
||||
const data = await prisma.$queryRaw`
|
||||
SELECT
|
||||
m.negara,
|
||||
COUNT(*) as total,
|
||||
SUM(lm.status = 'diterima') as diterima,
|
||||
SUM(lm.status = 'ditolak') as ditolak,
|
||||
SUM(lm.status = 'diproses') as diproses
|
||||
FROM lamaran_magang lm
|
||||
JOIN mahasiswa m ON lm.id_mahasiswa = m.id
|
||||
${whereClause}
|
||||
GROUP BY m.negara
|
||||
ORDER BY total DESC
|
||||
`;
|
||||
|
||||
return data.map((item) => ({
|
||||
negara: item.negara,
|
||||
total: Number(item.total),
|
||||
diterima: Number(item.diterima),
|
||||
ditolak: Number(item.ditolak),
|
||||
diproses: Number(item.diproses),
|
||||
}));
|
||||
}
|
||||
|
||||
async getStatisticsByUniversity({ query }) {
|
||||
const whereClause = query.batch
|
||||
? Prisma.sql`WHERE lm.batch = ${parseInt(query.batch)}`
|
||||
: Prisma.sql``;
|
||||
|
||||
const data = await prisma.$queryRaw`
|
||||
SELECT
|
||||
m.universitas,
|
||||
COUNT(*) as total,
|
||||
SUM(lm.status = 'diterima') as diterima,
|
||||
SUM(lm.status = 'ditolak') as ditolak,
|
||||
SUM(lm.status = 'diproses') as diproses
|
||||
FROM lamaran_magang lm
|
||||
JOIN mahasiswa m ON lm.id_mahasiswa = m.id
|
||||
${whereClause}
|
||||
GROUP BY m.universitas
|
||||
ORDER BY total DESC
|
||||
`;
|
||||
|
||||
return data.map((item) => ({
|
||||
universitas: item.universitas,
|
||||
total: Number(item.total),
|
||||
diterima: Number(item.diterima),
|
||||
ditolak: Number(item.ditolak),
|
||||
diproses: Number(item.diproses),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new LamaranMagangRepository();
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
const lamaranMagangRepository = require('../repositories/lamaranMagang.repository');
|
||||
const mahasiswaRepository = require('../../mahasiswa/repositories/mahasiswa.repository');
|
||||
const lowonganMagangRepository = require('../../lowonganMagang/repositories/lowonganMagang.repository');
|
||||
const batchRepository = require('../../batch/repositories/batch.repository');
|
||||
const imageUploadHelper = require('../../../helpers/utils/imageUpload.helper');
|
||||
const { sendConfirmationEmail, sendStatusEmail } = require('../helpers/email.helper');
|
||||
const ExcelJS = require('exceljs');
|
||||
const path = require('path');
|
||||
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
|
||||
const { data, error, paginationData } = require('../../../helpers/utils/wrapper');
|
||||
const { NotFoundError, ConflictError } = require('../../../helpers/error');
|
||||
|
||||
class LamaranMagangService {
|
||||
async createLamaranMagang(id_lowongan_magang, mahasiswaFormData, uploadedFiles) {
|
||||
try {
|
||||
const lowonganMagang = await lowonganMagangRepository.findById(id_lowongan_magang);
|
||||
|
||||
if (!lowonganMagang) {
|
||||
return error(new NotFoundError('Lowongan Magang Tidak Ditemukan'));
|
||||
}
|
||||
|
||||
const batchId = lowonganMagang.batch?.id || null;
|
||||
|
||||
const existingMahasiswa = await mahasiswaRepository.findByEmail(mahasiswaFormData.email);
|
||||
|
||||
if (existingMahasiswa) {
|
||||
|
||||
const existingLamaran = await lamaranMagangRepository.findAnyLamaranByMahasiswa(
|
||||
existingMahasiswa.id
|
||||
);
|
||||
|
||||
if (existingLamaran) {
|
||||
const posisiYangSudahDilamar = existingLamaran.lowongan_magang?.posisi || 'posisi tertentu';
|
||||
return error(new ConflictError(
|
||||
`Anda sudah pernah melamar untuk posisi ${posisiYangSudahDilamar}. Setiap pengguna hanya dapat melamar satu posisi.`
|
||||
));
|
||||
}
|
||||
|
||||
const lamaranData = {
|
||||
id_mahasiswa: existingMahasiswa.id,
|
||||
id_lowongan_magang,
|
||||
batch: batchId,
|
||||
status: 'diproses',
|
||||
};
|
||||
|
||||
await lamaranMagangRepository.create(lamaranData);
|
||||
|
||||
return data({
|
||||
id_mahasiswa: existingMahasiswa.id,
|
||||
id_lowongan_magang,
|
||||
batch: batchId,
|
||||
status: 'diproses',
|
||||
});
|
||||
}
|
||||
|
||||
const cv_path = imageUploadHelper.uploadCV(uploadedFiles.cv);
|
||||
const portofolio_path = imageUploadHelper.uploadPortofolio(uploadedFiles.portofolio);
|
||||
|
||||
const namaBelakangFinal = mahasiswaFormData.nama_belakang?.trim() || null;
|
||||
|
||||
const mahasiswaData = {
|
||||
nama_depan: mahasiswaFormData.nama_depan,
|
||||
nama_belakang: namaBelakangFinal,
|
||||
email: mahasiswaFormData.email,
|
||||
kontak: mahasiswaFormData.kontak,
|
||||
jurusan: mahasiswaFormData.jurusan,
|
||||
universitas: mahasiswaFormData.universitas,
|
||||
negara: mahasiswaFormData.negara,
|
||||
motivasi: mahasiswaFormData.motivasi,
|
||||
relevant_skills: mahasiswaFormData.relevant_skills,
|
||||
role: 'student',
|
||||
cv_path,
|
||||
portofolio_path,
|
||||
};
|
||||
|
||||
const createdMahasiswa = await mahasiswaRepository.create(mahasiswaData);
|
||||
const id_mahasiswa = createdMahasiswa.id;
|
||||
|
||||
const lamaranData = {
|
||||
id_mahasiswa,
|
||||
id_lowongan_magang,
|
||||
batch: batchId,
|
||||
status: 'diproses',
|
||||
};
|
||||
|
||||
await lamaranMagangRepository.create(lamaranData);
|
||||
|
||||
await sendConfirmationEmail(mahasiswaData, lowonganMagang);
|
||||
|
||||
return data({
|
||||
id_mahasiswa,
|
||||
id_lowongan_magang,
|
||||
batch: batchId,
|
||||
status: 'diproses',
|
||||
});
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllLamaranMagang(query = {}) {
|
||||
try {
|
||||
const { page = 1, limit = 10, posisi, status, universitas, id_lowongan } = query;
|
||||
const offset = (page - 1) * limit;
|
||||
const lamaranList = await lamaranMagangRepository.findAll({ offset, limit, status, posisi, universitas, id_lowongan });
|
||||
const total = await lamaranMagangRepository.countAllLamaran({ status, posisi, universitas, id_lowongan });
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
const meta = { page: Number(page), limit: Number(limit), total, totalPages };
|
||||
return paginationData(lamaranList, meta);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getLamaranById(id_lamaran_magang) {
|
||||
try {
|
||||
const lamaran = await lamaranMagangRepository.findById(id_lamaran_magang);
|
||||
|
||||
if (!lamaran) {
|
||||
return error(new NotFoundError('Lamaran Magang Tidak Ditemukan'));
|
||||
}
|
||||
|
||||
return data(lamaran);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getDetailLamaranById(id_lamaran) {
|
||||
try {
|
||||
const lamaran = await lamaranMagangRepository.findDetailById(id_lamaran);
|
||||
|
||||
if (!lamaran) {
|
||||
return error(new NotFoundError('Lamaran Magang Tidak Ditemukan'));
|
||||
}
|
||||
|
||||
return data(lamaran);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async updateLamaranStatus(id_lamaran_magang, status) {
|
||||
try {
|
||||
const checkResult = await this.getDetailLamaranById(id_lamaran_magang);
|
||||
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const result = await lamaranMagangRepository.updateStatus(id_lamaran_magang, status);
|
||||
|
||||
await sendStatusEmail(checkResult.data, status);
|
||||
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteLamaran(id) {
|
||||
try {
|
||||
const checkResult = await this.getDetailLamaranById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const lamaran = checkResult.data;
|
||||
|
||||
if (lamaran.cv_path) {
|
||||
const cvFileName = path.basename(lamaran.cv_path);
|
||||
await deleteFileIfExists(cvFileName);
|
||||
}
|
||||
|
||||
if (lamaran.portofolio_path) {
|
||||
const portofolioFileName = path.basename(lamaran.portofolio_path);
|
||||
await deleteFileIfExists(portofolioFileName);
|
||||
}
|
||||
|
||||
const result = await lamaranMagangRepository.deleteById(id);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async exportToExcel() {
|
||||
try {
|
||||
const lamaranList = await lamaranMagangRepository.findAll({});
|
||||
|
||||
if (!lamaranList || lamaranList.length === 0) {
|
||||
return error(new NotFoundError("Tidak ada data lamaran untuk diekspor"));
|
||||
}
|
||||
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet('Lamaran Magang');
|
||||
|
||||
worksheet.columns = [
|
||||
{ header: 'ID', key: 'id', width: 10 },
|
||||
{ header: 'Nama Depan', key: 'nama_depan', width: 20 },
|
||||
{ header: 'Nama Belakang', key: 'nama_belakang', width: 20 },
|
||||
{ header: 'Posisi', key: 'posisi', width: 25 },
|
||||
{ header: 'Kelompok Peminatan', key: 'kelompok_peminatan', width: 25 },
|
||||
{ header: 'Status', key: 'status', width: 15 },
|
||||
{ header: 'Tanggal Dibuat', key: 'created_at', width: 20 },
|
||||
];
|
||||
|
||||
lamaranList.forEach((lamaran) => {
|
||||
worksheet.addRow({
|
||||
id: lamaran.id,
|
||||
nama_depan: lamaran.mahasiswa?.nama_depan || '',
|
||||
nama_belakang: lamaran.mahasiswa?.nama_belakang || '',
|
||||
posisi: lamaran.lowongan_magang?.posisi || '',
|
||||
kelompok_peminatan: lamaran.lowongan_magang?.kelompok_peminatan || '',
|
||||
status: lamaran.status,
|
||||
created_at: lamaran.created_at,
|
||||
});
|
||||
});
|
||||
|
||||
worksheet.getRow(1).font = { bold: true };
|
||||
worksheet.getRow(1).fill = {
|
||||
type: 'pattern',
|
||||
pattern: 'solid',
|
||||
fgColor: { argb: 'FFD3D3D3' },
|
||||
};
|
||||
|
||||
return data(workbook);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getDashboardStatistics(query) {
|
||||
try {
|
||||
const batch = await batchRepository.getBatchById(query.batch);
|
||||
const batchDisplay = batch ? batch.batch_number : 'Data Seluruh Batch';
|
||||
const stats = await lamaranMagangRepository.getStatistics({ query });
|
||||
|
||||
const acceptanceRate = stats.total > 0 ? ((stats.diterima / stats.total) * 100).toFixed(1) : 0;
|
||||
const rejectionRate = stats.total > 0 ? ((stats.ditolak / stats.total) * 100).toFixed(1) : 0;
|
||||
const pendingRate = stats.total > 0 ? ((stats.diproses / stats.total) * 100).toFixed(1) : 0;
|
||||
|
||||
if (stats.total === 0) {
|
||||
return error(new NotFoundError(`Tidak ada data lamaran ditemukan pada batch ${batchDisplay}`));
|
||||
}
|
||||
|
||||
return data({
|
||||
total_pendaftar: stats.total,
|
||||
total_diterima: stats.diterima,
|
||||
total_ditolak: stats.ditolak,
|
||||
sedang_diproses: stats.diproses,
|
||||
presentase_diterima: `${acceptanceRate}%`,
|
||||
presentase_ditolak: `${rejectionRate}%`,
|
||||
presentase_diproses: `${pendingRate}%`,
|
||||
batch: batchDisplay,
|
||||
});
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getStatisticsByPosition(query) {
|
||||
try {
|
||||
const batch = await batchRepository.getBatchById(query.batch);
|
||||
const batchDisplay = batch ? batch.batch_number : 'semua batch';
|
||||
const positions = await lamaranMagangRepository.getStatisticsByPosition({ query });
|
||||
|
||||
if (positions.length === 0) {
|
||||
return error(new NotFoundError(`Tidak ada data lamaran ditemukan pada batch ${batchDisplay}`));
|
||||
}
|
||||
|
||||
return data(positions);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getStatisticsByCountry(query) {
|
||||
try {
|
||||
const batch = await batchRepository.getBatchById(query.batch);
|
||||
const batchDisplay = batch ? batch.batch_number : 'semua batch';
|
||||
const countries = await lamaranMagangRepository.getStatisticsByCountry({ query });
|
||||
if (countries.length === 0) {
|
||||
return error(new NotFoundError(`Tidak ada data lamaran ditemukan pada batch ${batchDisplay}`));
|
||||
}
|
||||
|
||||
return data(countries);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getStatisticsByUniversity(query) {
|
||||
try {
|
||||
const batch = await batchRepository.getBatchById(query.batch);
|
||||
const batchDisplay = batch ? batch.batch_number : 'semua batch';
|
||||
const universities = await lamaranMagangRepository.getStatisticsByUniversity({ query });
|
||||
if (universities.length === 0) {
|
||||
return error(new NotFoundError(`Tidak ada data lamaran ditemukan pada batch ${batchDisplay}`));
|
||||
}
|
||||
return data(universities);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = new LamaranMagangService();
|
||||
@@ -0,0 +1,157 @@
|
||||
const lowonganMagangService = require('../services/lowonganMagang.service');
|
||||
const { response, error } = require('../../../helpers/utils/wrapper');
|
||||
const { BadRequestError, ForbiddenError, InternalServerError } = require('../../../helpers/error');
|
||||
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
|
||||
const { isValidPayload } = require('../../../helpers/utils/validator');
|
||||
const { createLowonganMagangModel, updateLowonganMagangModel } = require('../models/lowonganMagang.model');
|
||||
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
|
||||
|
||||
class LowonganMagangController {
|
||||
async addLowonganMagang(req, res) {
|
||||
try {
|
||||
const image_path = req.file;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, createLowonganMagangModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
if (image_path?.filename) {
|
||||
await deleteFileIfExists(image_path.filename);
|
||||
}
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await lowonganMagangService.createLowonganMagang(validatePayload.data, image_path);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Lowongan magang berhasil dibuat', SUCCESS.CREATED);
|
||||
} catch (err) {
|
||||
if (req.file?.filename) {
|
||||
await deleteFileIfExists(req.file.filename);
|
||||
}
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllLowonganMagang(req, res) {
|
||||
try {
|
||||
const result = await lowonganMagangService.getAllLowonganMagang();
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Berhasil mengambil semua lowongan magang', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)),
|
||||
'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getLowonganMagangById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await lowonganMagangService.getLowonganMagangById(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Berhasil mengambil lowongan magang berdasarkan ID', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getLowonganByKelompokPeminatan(req, res) {
|
||||
try {
|
||||
const { kelompok_peminatan } = req.params;
|
||||
|
||||
const result = await lowonganMagangService.getLowonganByKelompokPeminatan(kelompok_peminatan);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Berhasil mengambil lowongan magang berdasarkan kelompok peminatan', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllKelompokPeminatan(req, res) {
|
||||
try {
|
||||
const result = await lowonganMagangService.getAllKelompokPeminatan();
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Berhasil mengambil semua kelompok peminatan', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async updateLowonganMagang(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const image_path = req.file;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, updateLowonganMagangModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
if (image_path?.filename) {
|
||||
await deleteFileIfExists(image_path.filename);
|
||||
}
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await lowonganMagangService.updateLowonganMagang(id, validatePayload.data, image_path);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Lowongan magang berhasil diperbarui', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
if (req.file?.filename) {
|
||||
await deleteFileIfExists(req.file.filename);
|
||||
}
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteLowonganMagangById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await lowonganMagangService.deleteLowonganMagang(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Lowongan magang 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 LowonganMagangController();
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
class FileUploadHelper {
|
||||
uploadImage(file) {
|
||||
if (!file) {
|
||||
throw new Error('Please upload an image!');
|
||||
}
|
||||
|
||||
const filePath = `/uploads/${file.filename}`;
|
||||
return filePath;
|
||||
}
|
||||
|
||||
validateImage(file) {
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const allowedTypes = ['image/jpeg', 'image/png', 'image/jpg', 'image/webp'];
|
||||
return allowedTypes.includes(file.mimetype);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FileUploadHelper();
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
class StatusHelper {
|
||||
calculateStatus(durasi_awal, durasi_akhir) {
|
||||
const now = new Date();
|
||||
const startDate = new Date(durasi_awal);
|
||||
const endDate = new Date(durasi_akhir);
|
||||
|
||||
if (now >= startDate && now <= endDate) {
|
||||
return 'dibuka';
|
||||
}
|
||||
return 'ditutup';
|
||||
}
|
||||
|
||||
updateLowonganStatus(lowongan) {
|
||||
const status = this.calculateStatus(lowongan.durasi_awal, lowongan.durasi_akhir);
|
||||
return {
|
||||
...lowongan,
|
||||
status_lowongan: status,
|
||||
};
|
||||
}
|
||||
|
||||
updateMultipleLowonganStatus(lowonganList) {
|
||||
return lowonganList.map((lowongan) => this.updateLowonganStatus(lowongan));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new StatusHelper();
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
const lowonganMagangController = require('./controllers/lowonganMagang.controller');
|
||||
const lowonganMagangService = require('./services/lowonganMagang.service');
|
||||
const lowonganMagangRepository = require('./repositories/lowonganMagang.repository');
|
||||
const statusHelper = require('./helpers/status.helper');
|
||||
|
||||
module.exports = {
|
||||
lowonganMagangController,
|
||||
lowonganMagangService,
|
||||
lowonganMagangRepository,
|
||||
statusHelper
|
||||
};
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
const joi = require('joi');
|
||||
|
||||
const createLowonganMagangModel = joi.object().keys({
|
||||
posisi: joi.string().max(255).required().messages({
|
||||
'string.base': 'Posisi harus berupa teks.',
|
||||
'string.empty': 'Posisi tidak boleh kosong.',
|
||||
'string.max': 'Posisi maksimal {#limit} karakter.',
|
||||
'any.required': 'Posisi wajib diisi.',
|
||||
}),
|
||||
|
||||
kelompok_peminatan: joi.string().max(255).required().messages({
|
||||
'string.base': 'Kelompok peminatan harus berupa teks.',
|
||||
'string.empty': 'Kelompok peminatan tidak boleh kosong.',
|
||||
'string.max': 'Kelompok peminatan maksimal {#limit} karakter.',
|
||||
'any.required': 'Kelompok peminatan wajib diisi.',
|
||||
}),
|
||||
|
||||
jobdesk: joi.string().required().messages({
|
||||
'string.base': 'Jobdesk harus berupa teks.',
|
||||
'string.empty': 'Jobdesk tidak boleh kosong.',
|
||||
'any.required': 'Jobdesk wajib diisi.',
|
||||
}),
|
||||
|
||||
lokasi: joi.string().max(255).required().messages({
|
||||
'string.base': 'Lokasi harus berupa teks.',
|
||||
'string.empty': 'Lokasi tidak boleh kosong.',
|
||||
'string.max': 'Lokasi maksimal {#limit} karakter.',
|
||||
'any.required': 'Lokasi wajib diisi.',
|
||||
}),
|
||||
|
||||
kualifikasi: joi.string().required().messages({
|
||||
'string.base': 'Kualifikasi harus berupa teks.',
|
||||
'string.empty': 'Kualifikasi tidak boleh kosong.',
|
||||
'any.required': 'Kualifikasi wajib diisi.',
|
||||
}),
|
||||
|
||||
benefit: joi.string().required().messages({
|
||||
'string.base': 'Benefit harus berupa teks.',
|
||||
'string.empty': 'Benefit tidak boleh kosong.',
|
||||
'any.required': 'Benefit wajib diisi.',
|
||||
}),
|
||||
|
||||
durasi_awal: joi.date().required().messages({
|
||||
'date.base': 'Durasi awal harus berupa tanggal yang valid.',
|
||||
'any.required': 'Durasi awal wajib diisi.',
|
||||
}),
|
||||
|
||||
durasi_akhir: joi.date().min(joi.ref('durasi_awal')).required().messages({
|
||||
'date.base': 'Durasi akhir harus berupa tanggal yang valid.',
|
||||
'date.min': 'Durasi akhir harus setelah atau sama dengan durasi awal.',
|
||||
'any.required': 'Durasi akhir wajib diisi.',
|
||||
}),
|
||||
|
||||
paid: joi.string().valid('paid', 'unpaid').required().messages({
|
||||
'string.empty': 'Status paid tidak boleh kosong.',
|
||||
'string.base': 'Status paid harus berupa teks.',
|
||||
'any.only': 'Status paid hanya boleh "paid" atau "unpaid".',
|
||||
'any.required': 'Status paid wajib diisi.',
|
||||
}),
|
||||
|
||||
batch: joi.number().integer().min(1).optional().messages({
|
||||
'number.base': 'Batch harus berupa angka.',
|
||||
'number.integer': 'Batch harus berupa bilangan bulat.',
|
||||
'number.min': 'Batch minimal {#limit}.',
|
||||
}),
|
||||
|
||||
image_path: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Path gambar harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
const updateLowonganMagangModel = joi.object().keys({
|
||||
posisi: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Posisi harus berupa teks.',
|
||||
'string.max': 'Posisi maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
kelompok_peminatan: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Kelompok peminatan harus berupa teks.',
|
||||
'string.max': 'Kelompok peminatan maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
jobdesk: joi.string().optional().messages({
|
||||
'string.base': 'Jobdesk harus berupa teks.',
|
||||
}),
|
||||
|
||||
lokasi: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Lokasi harus berupa teks.',
|
||||
'string.max': 'Lokasi maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
kualifikasi: joi.string().optional().messages({
|
||||
'string.base': 'Kualifikasi harus berupa teks.',
|
||||
}),
|
||||
|
||||
benefit: joi.string().optional().messages({
|
||||
'string.base': 'Benefit harus berupa teks.',
|
||||
}),
|
||||
|
||||
durasi_awal: joi.date().optional().messages({
|
||||
'date.base': 'Durasi awal harus berupa tanggal yang valid.',
|
||||
}),
|
||||
|
||||
durasi_akhir: joi.date().min(joi.ref('durasi_awal')).optional().messages({
|
||||
'date.base': 'Durasi akhir harus berupa tanggal yang valid.',
|
||||
'date.min': 'Durasi akhir harus setelah atau sama dengan durasi awal.',
|
||||
}),
|
||||
|
||||
paid: joi.string().valid('paid', 'unpaid').optional().messages({
|
||||
'string.base': 'Status paid harus berupa teks.',
|
||||
'any.only': 'Status paid hanya boleh "paid" atau "unpaid".',
|
||||
}),
|
||||
|
||||
batch: joi.number().integer().min(1).optional().messages({
|
||||
'number.base': 'Batch harus berupa angka.',
|
||||
'number.integer': 'Batch harus berupa bilangan bulat.',
|
||||
'number.min': 'Batch minimal {#limit}.',
|
||||
}),
|
||||
|
||||
image_path: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Path gambar harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createLowonganMagangModel,
|
||||
updateLowonganMagangModel,
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
const prisma = require('../../../helpers/db/db_connection');
|
||||
|
||||
|
||||
class LowonganMagangRepository {
|
||||
async create(lowonganData) {
|
||||
return prisma.lowonganMagang.create({
|
||||
data: lowonganData,
|
||||
});
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return prisma.lowonganMagang.findMany({
|
||||
include: {
|
||||
batch: {
|
||||
select: {
|
||||
id: true,
|
||||
batch_number: true,
|
||||
is_active: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id) {
|
||||
return prisma.lowonganMagang.findUnique({
|
||||
where: { id: id },
|
||||
include: {
|
||||
batch: {
|
||||
select: {
|
||||
id: true,
|
||||
batch_number: true,
|
||||
is_active: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByKelompokPeminatan(kelompok_peminatan) {
|
||||
return prisma.lowonganMagang.findMany({
|
||||
where: { kelompok_peminatan },
|
||||
include: {
|
||||
batch: {
|
||||
select: {
|
||||
id: true,
|
||||
batch_number: true,
|
||||
is_active: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findAllKelompokPeminatan() {
|
||||
return prisma.lowonganMagang.findMany({
|
||||
select: {
|
||||
kelompok_peminatan: true,
|
||||
},
|
||||
distinct: ['kelompok_peminatan'],
|
||||
});
|
||||
}
|
||||
|
||||
async updateById(id, lowonganData) {
|
||||
return prisma.lowonganMagang.update({
|
||||
where: { id: id },
|
||||
data: lowonganData,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteById(id) {
|
||||
return prisma.lowonganMagang.delete({
|
||||
where: { id: id },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new LowonganMagangRepository();
|
||||
@@ -0,0 +1,191 @@
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const path = require('path');
|
||||
const lowonganMagangRepository = require('../repositories/lowonganMagang.repository');
|
||||
const batchRepository = require('../../batch/repositories/batch.repository');
|
||||
const imageUploadHelper = require('../../../helpers/utils/imageUpload.helper');
|
||||
const statusHelper = require('../helpers/status.helper');
|
||||
const { deleteFileIfExists, validateImagePath } = require('../../../helpers/utils/fileHelper');
|
||||
const { data, error } = require('../../../helpers/utils/wrapper');
|
||||
const { NotFoundError } = require('../../../helpers/error');
|
||||
|
||||
class LowonganMagangService {
|
||||
async createLowonganMagang(lowonganData, imageFile) {
|
||||
try {
|
||||
const activeBatch = await batchRepository.findActiveBatch();
|
||||
|
||||
if (!activeBatch) {
|
||||
return error(new NotFoundError('Tidak ada batch yang aktif'));
|
||||
}
|
||||
|
||||
const id = uuidv4();
|
||||
const imagePath = imageUploadHelper.uploadImage(imageFile);
|
||||
const status = statusHelper.calculateStatus(
|
||||
lowonganData.durasi_awal,
|
||||
lowonganData.durasi_akhir
|
||||
);
|
||||
|
||||
const newLowonganData = {
|
||||
id,
|
||||
...lowonganData,
|
||||
id_batch: activeBatch.id,
|
||||
status_lowongan: status,
|
||||
image_path: imagePath,
|
||||
};
|
||||
|
||||
const result = await lowonganMagangRepository.create(newLowonganData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
if (imageFile?.filename) {
|
||||
try {
|
||||
await deleteFileIfExists(imageFile.filename);
|
||||
} catch (cleanupErr) {
|
||||
console.error('Failed to cleanup file:', cleanupErr);
|
||||
}
|
||||
}
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllLowonganMagang() {
|
||||
try {
|
||||
const lowonganList = await lowonganMagangRepository.findAll();
|
||||
|
||||
if (lowonganList.length === 0) {
|
||||
return data([]);
|
||||
}
|
||||
|
||||
const updatedList = statusHelper.updateMultipleLowonganStatus(lowonganList);
|
||||
|
||||
const validatedList = updatedList.map(lowongan => ({
|
||||
...lowongan,
|
||||
image_path: validateImagePath(lowongan.image_path)
|
||||
}));
|
||||
|
||||
return data(validatedList);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getLowonganMagangById(id) {
|
||||
try {
|
||||
const lowongan = await lowonganMagangRepository.findById(id);
|
||||
|
||||
if (!lowongan) {
|
||||
return error(new NotFoundError('Lowongan Magang Tidak Ditemukan'));
|
||||
}
|
||||
|
||||
const updatedLowongan = statusHelper.updateLowonganStatus(lowongan);
|
||||
|
||||
updatedLowongan.image_path = validateImagePath(updatedLowongan.image_path);
|
||||
|
||||
return data(updatedLowongan);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getLowonganByKelompokPeminatan(kelompok_peminatan) {
|
||||
try {
|
||||
const lowonganList = await lowonganMagangRepository.findByKelompokPeminatan(
|
||||
kelompok_peminatan
|
||||
);
|
||||
|
||||
if (lowonganList.length === 0) {
|
||||
return data([]);
|
||||
}
|
||||
|
||||
const updatedList = statusHelper.updateMultipleLowonganStatus(lowonganList);
|
||||
|
||||
const validatedList = updatedList.map(lowongan => ({
|
||||
...lowongan,
|
||||
image_path: validateImagePath(lowongan.image_path)
|
||||
}));
|
||||
|
||||
return data(validatedList);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllKelompokPeminatan() {
|
||||
try {
|
||||
const kelompokList = await lowonganMagangRepository.findAllKelompokPeminatan();
|
||||
const kelompokNames = kelompokList.map((row) => row.kelompok_peminatan);
|
||||
return data(kelompokNames);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async updateLowonganMagang(id, lowonganData, imageFile) {
|
||||
let oldImagePath = null;
|
||||
let newImageUploaded = false;
|
||||
|
||||
try {
|
||||
const checkResult = await this.getLowonganMagangById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const existingLowongan = checkResult.data;
|
||||
|
||||
if (imageFile) {
|
||||
oldImagePath = existingLowongan.image_path;
|
||||
|
||||
lowonganData.image_path = imageUploadHelper.uploadImage(imageFile);
|
||||
newImageUploaded = true;
|
||||
|
||||
if (oldImagePath) {
|
||||
const oldFileName = path.basename(oldImagePath);
|
||||
await deleteFileIfExists(oldFileName);
|
||||
}
|
||||
}
|
||||
|
||||
if (lowonganData.durasi_awal || lowonganData.durasi_akhir) {
|
||||
const durasi_awal = lowonganData.durasi_awal || existingLowongan.durasi_awal;
|
||||
const durasi_akhir = lowonganData.durasi_akhir || existingLowongan.durasi_akhir;
|
||||
|
||||
lowonganData.status_lowongan = statusHelper.calculateStatus(
|
||||
durasi_awal,
|
||||
durasi_akhir
|
||||
);
|
||||
}
|
||||
|
||||
const result = await lowonganMagangRepository.updateById(id, lowonganData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
if (newImageUploaded && imageFile?.filename) {
|
||||
try {
|
||||
await deleteFileIfExists(imageFile.filename);
|
||||
} catch (cleanupErr) {
|
||||
console.error('Failed to cleanup new file:', cleanupErr);
|
||||
}
|
||||
}
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteLowonganMagang(id) {
|
||||
try {
|
||||
const checkResult = await this.getLowonganMagangById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const lowongan = checkResult.data;
|
||||
|
||||
if (lowongan.image_path) {
|
||||
const fileName = path.basename(lowongan.image_path);
|
||||
await deleteFileIfExists(fileName);
|
||||
}
|
||||
|
||||
const result = await lowonganMagangRepository.deleteById(id);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new LowonganMagangService();
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
const mahasiswaService = require('../services/mahasiswa.service');
|
||||
const { response, error, paginationResponse } = require('../../../helpers/utils/wrapper');
|
||||
const { InternalServerError } = require('../../../helpers/error');
|
||||
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
|
||||
const { isValidPayload } = require('../../../helpers/utils/validator');
|
||||
const { createMahasiswaModel, updateMahasiswaModel, getAllMahasiswaModel } = require('../models/mahasiswa.model');
|
||||
|
||||
class MahasiswaController {
|
||||
async createMahasiswa(req, res) {
|
||||
try {
|
||||
const validatePayload = isValidPayload(req.body, createMahasiswaModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await mahasiswaService.createMahasiswa(validatePayload.data);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Mahasiswa berhasil dibuat', SUCCESS.CREATED);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllMahasiswa(req, res) {
|
||||
try {
|
||||
const validatePayload = isValidPayload(req.query, getAllMahasiswaModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const query = validatePayload.data;
|
||||
const result = await mahasiswaService.getAllMahasiswa({ query });
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return paginationResponse(res, 'success', result, 'Data mahasiswa berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getMahasiswaById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await mahasiswaService.getMahasiswaById(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Mahasiswa berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async updateMahasiswa(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, updateMahasiswaModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const result = await mahasiswaService.updateMahasiswa(id, validatePayload.data);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Mahasiswa berhasil diperbarui', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMahasiswa(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await mahasiswaService.deleteMahasiswa(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Mahasiswa berhasil dihapus', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAllMahasiswa(req, res) {
|
||||
try {
|
||||
const result = await mahasiswaService.deleteAllMahasiswa();
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Semua mahasiswa 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 MahasiswaController();
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
const mahasiswaController = require('./controllers/mahasiswa.controller');
|
||||
const mahasiswaService = require('./services/mahasiswa.service');
|
||||
const mahasiswaRepository = require('./repositories/mahasiswa.repository');
|
||||
|
||||
module.exports = {
|
||||
mahasiswaController,
|
||||
mahasiswaService,
|
||||
mahasiswaRepository
|
||||
};
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
const joi = require('joi');
|
||||
|
||||
const phoneRegex = /^(\+62|62|0)8[1-9][0-9]{6,11}$/;
|
||||
|
||||
const createMahasiswaModel = 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).optional().allow('', null).messages({
|
||||
'string.base': 'Nama belakang harus berupa teks.',
|
||||
'string.max': 'Nama belakang maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
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.',
|
||||
}),
|
||||
|
||||
kontak: joi.string().pattern(phoneRegex).max(255).required().messages({
|
||||
'string.base': 'Kontak harus berupa teks.',
|
||||
'string.empty': 'Kontak tidak boleh kosong.',
|
||||
'string.pattern.base': 'Format nomor kontak tidak valid. Gunakan format Indonesia (08xx atau +628xx).',
|
||||
'string.max': 'Kontak maksimal {#limit} karakter.',
|
||||
'any.required': 'Kontak wajib diisi.',
|
||||
}),
|
||||
|
||||
jurusan: joi.string().max(255).required().messages({
|
||||
'string.base': 'Jurusan harus berupa teks.',
|
||||
'string.empty': 'Jurusan tidak boleh kosong.',
|
||||
'string.max': 'Jurusan maksimal {#limit} karakter.',
|
||||
'any.required': 'Jurusan wajib diisi.',
|
||||
}),
|
||||
|
||||
universitas: joi.string().max(255).required().messages({
|
||||
'string.base': 'Universitas harus berupa teks.',
|
||||
'string.empty': 'Universitas tidak boleh kosong.',
|
||||
'string.max': 'Universitas maksimal {#limit} karakter.',
|
||||
'any.required': 'Universitas wajib diisi.',
|
||||
}),
|
||||
|
||||
negara: joi.string().max(255).required().messages({
|
||||
'string.base': 'Negara harus berupa teks.',
|
||||
'string.empty': 'Negara tidak boleh kosong.',
|
||||
'string.max': 'Negara maksimal {#limit} karakter.',
|
||||
'any.required': 'Negara wajib diisi.',
|
||||
}),
|
||||
|
||||
role: joi.string().valid('student').default('student').messages({
|
||||
'string.base': 'Role harus berupa teks.',
|
||||
'any.only': 'Role hanya boleh "student".',
|
||||
}),
|
||||
|
||||
cv_path: joi.string().required().messages({
|
||||
'string.base': 'Path CV harus berupa teks.',
|
||||
'string.empty': 'Path CV tidak boleh kosong.',
|
||||
'any.required': 'CV wajib diupload.',
|
||||
}),
|
||||
|
||||
portofolio_path: joi.string().required().messages({
|
||||
'string.base': 'Path portofolio harus berupa teks.',
|
||||
'string.empty': 'Path portofolio tidak boleh kosong.',
|
||||
'any.required': 'Portofolio wajib diupload.',
|
||||
}),
|
||||
|
||||
motivasi: joi.string().required().messages({
|
||||
'string.base': 'Motivasi harus berupa teks.',
|
||||
'string.empty': 'Motivasi tidak boleh kosong.',
|
||||
'any.required': 'Motivasi wajib diisi.',
|
||||
}),
|
||||
|
||||
relevant_skills: joi.string().max(255).required().messages({
|
||||
'string.base': 'Keahlian yang relevan harus berupa teks.',
|
||||
'string.empty': 'Keahlian yang relevan tidak boleh kosong.',
|
||||
'string.max': 'Keahlian yang relevan maksimal {#limit} karakter.',
|
||||
'any.required': 'Keahlian yang relevan wajib diisi.',
|
||||
}),
|
||||
});
|
||||
|
||||
const updateMahasiswaModel = joi.object().keys({
|
||||
nama_depan: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Nama depan harus berupa teks.',
|
||||
'string.max': 'Nama depan maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
nama_belakang: joi.string().max(255).optional().allow('', null).messages({
|
||||
'string.base': 'Nama belakang harus berupa teks.',
|
||||
'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.',
|
||||
}),
|
||||
|
||||
kontak: joi.string().pattern(phoneRegex).max(255).optional().messages({
|
||||
'string.base': 'Kontak harus berupa teks.',
|
||||
'string.pattern.base': 'Format nomor kontak tidak valid. Gunakan format Indonesia (08xx atau +628xx).',
|
||||
'string.max': 'Kontak maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
jurusan: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Jurusan harus berupa teks.',
|
||||
'string.max': 'Jurusan maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
universitas: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Universitas harus berupa teks.',
|
||||
'string.max': 'Universitas maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
negara: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Negara harus berupa teks.',
|
||||
'string.max': 'Negara maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
cv_path: joi.string().optional().messages({
|
||||
'string.base': 'Path CV harus berupa teks.',
|
||||
}),
|
||||
|
||||
portofolio_path: joi.string().optional().messages({
|
||||
'string.base': 'Path portofolio harus berupa teks.',
|
||||
}),
|
||||
|
||||
motivasi: joi.string().optional().messages({
|
||||
'string.base': 'Motivasi harus berupa teks.',
|
||||
}),
|
||||
|
||||
relevant_skills: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Keahlian yang relevan harus berupa teks.',
|
||||
'string.max': 'Keahlian yang relevan maksimal {#limit} karakter.',
|
||||
}),
|
||||
});
|
||||
|
||||
const getAllMahasiswaModel = joi.object().keys({
|
||||
page: joi.number().integer().min(1).default(1).messages({
|
||||
'number.base': 'Halaman harus berupa angka.',
|
||||
'number.integer': 'Halaman harus berupa angka bulat.',
|
||||
'number.min': 'Halaman minimal adalah {#limit}.',
|
||||
}),
|
||||
limit: joi.number().integer().min(1).default(10).messages({
|
||||
'number.base': 'Limit harus berupa angka.',
|
||||
'number.integer': 'Limit harus berupa angka bulat.',
|
||||
'number.min': 'Limit minimal adalah {#limit}.',
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createMahasiswaModel,
|
||||
updateMahasiswaModel,
|
||||
getAllMahasiswaModel
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
const prisma = require('../../../helpers/db/db_connection');
|
||||
|
||||
class MahasiswaRepository {
|
||||
async create(mahasiswaData) {
|
||||
return prisma.mahasiswa.create({
|
||||
data: mahasiswaData,
|
||||
});
|
||||
}
|
||||
|
||||
async findAll({ offset, limit }) {
|
||||
return prisma.mahasiswa.findMany({
|
||||
skip: offset,
|
||||
take: limit,
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id) {
|
||||
return prisma.mahasiswa.findUnique({
|
||||
where: { id: parseInt(id) },
|
||||
include: {
|
||||
lamaran_magang: {
|
||||
include: {
|
||||
lowongan_magang: {
|
||||
select: {
|
||||
posisi: true,
|
||||
kelompok_peminatan: true,
|
||||
durasi_awal: true,
|
||||
durasi_akhir: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByEmail(email) {
|
||||
return prisma.mahasiswa.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
}
|
||||
|
||||
async updateById(id, mahasiswaData) {
|
||||
return prisma.mahasiswa.update({
|
||||
where: { id: parseInt(id) },
|
||||
data: mahasiswaData,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteById(id) {
|
||||
return prisma.mahasiswa.delete({
|
||||
where: { id: parseInt(id) },
|
||||
});
|
||||
}
|
||||
|
||||
async deleteAll() {
|
||||
return prisma.mahasiswa.deleteMany();
|
||||
}
|
||||
|
||||
async countAllMahasiswa() {
|
||||
return prisma.mahasiswa.count();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new MahasiswaRepository();
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
const mahasiswaRepository = require('../repositories/mahasiswa.repository');
|
||||
const { data, error } = require('../../../helpers/utils/wrapper');
|
||||
const { BadRequestError, NotFoundError } = require('../../../helpers/error');
|
||||
const { paginationData } = require("../../../helpers/utils/wrapper");
|
||||
|
||||
class MahasiswaService {
|
||||
async createMahasiswa(mahasiswaData) {
|
||||
try {
|
||||
const existingMahasiswa = await mahasiswaRepository.findByEmail(mahasiswaData.email);
|
||||
|
||||
if (existingMahasiswa) {
|
||||
return error(new BadRequestError('Email Sudah Terdaftar'));
|
||||
}
|
||||
|
||||
if (!mahasiswaData.role) {
|
||||
mahasiswaData.role = 'student';
|
||||
}
|
||||
|
||||
const result = await mahasiswaRepository.create(mahasiswaData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllMahasiswa({ page = 1, limit = 10 }) {
|
||||
try {
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const mahasiswa = await mahasiswaRepository.findAll({ offset, limit });
|
||||
|
||||
if (!mahasiswa || mahasiswa.length === 0) {
|
||||
return error(new NotFoundError('Tidak ada mahasiswa ditemukan'));
|
||||
}
|
||||
|
||||
const total = await mahasiswaRepository.countAllMahasiswa();
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
const meta = { page: Number(page), limit: Number(limit), total, totalPages };
|
||||
return paginationData(mahasiswa, meta);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getMahasiswaById(id) {
|
||||
try {
|
||||
const mahasiswa = await mahasiswaRepository.findById(id);
|
||||
|
||||
if (!mahasiswa) {
|
||||
return error(new NotFoundError('Mahasiswa Tidak Ditemukan'));
|
||||
}
|
||||
|
||||
return data(mahasiswa);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getMahasiswaByEmail(email) {
|
||||
try {
|
||||
const mahasiswa = await mahasiswaRepository.findByEmail(email);
|
||||
|
||||
if (!mahasiswa) {
|
||||
return error(new NotFoundError('Mahasiswa Tidak Ditemukan'));
|
||||
}
|
||||
|
||||
return data(mahasiswa);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async updateMahasiswa(id, mahasiswaData) {
|
||||
try {
|
||||
const checkResult = await this.getMahasiswaById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
if (mahasiswaData.email) {
|
||||
const existingMahasiswa = await mahasiswaRepository.findByEmail(mahasiswaData.email);
|
||||
|
||||
if (existingMahasiswa && existingMahasiswa.id !== id) {
|
||||
return error(new BadRequestError('Email Sudah Terdaftar'));
|
||||
}
|
||||
}
|
||||
|
||||
const result = await mahasiswaRepository.updateById(id, mahasiswaData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMahasiswa(id) {
|
||||
try {
|
||||
const checkResult = await this.getMahasiswaById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const result = await mahasiswaRepository.deleteById(id);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAllMahasiswa() {
|
||||
try {
|
||||
const result = await mahasiswaRepository.deleteAll();
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new MahasiswaService();
|
||||
@@ -0,0 +1,128 @@
|
||||
const partnershipService = require('../services/partnership.service');
|
||||
const { response, error } = require('../../../helpers/utils/wrapper');
|
||||
const { InternalServerError } = require('../../../helpers/error');
|
||||
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
|
||||
const { isValidPayload } = require('../../../helpers/utils/validator');
|
||||
const { createPartnershipModel, updatePartnershipModel } = require('../models/partnership.model');
|
||||
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
|
||||
|
||||
class PartnershipController {
|
||||
async addPartnership(req, res) {
|
||||
try {
|
||||
const image = req.file;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, createPartnershipModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
if (image?.filename) {
|
||||
await deleteFileIfExists(image.filename);
|
||||
}
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const { nama_partner } = validatePayload.data;
|
||||
const result = await partnershipService.createPartnership(nama_partner, image);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Data partnership berhasil ditambahkan', SUCCESS.CREATED);
|
||||
} catch (err) {
|
||||
if (req.file?.filename) {
|
||||
await deleteFileIfExists(req.file.filename);
|
||||
}
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getPartnership(req, res) {
|
||||
try {
|
||||
const result = await partnershipService.getAllPartnerships();
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Data partnership berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getPartnershipById(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await partnershipService.getPartnershipById(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Data partnership berhasil diambil', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async updatePartnership(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const image = req.file;
|
||||
|
||||
const validatePayload = isValidPayload(req.body, updatePartnershipModel);
|
||||
|
||||
if (validatePayload.err) {
|
||||
if (image?.filename) {
|
||||
await deleteFileIfExists(image.filename);
|
||||
}
|
||||
return response(
|
||||
res,
|
||||
'fail',
|
||||
{ err: validatePayload.err, data: null },
|
||||
'Data tidak valid',
|
||||
ERROR.EXPECTATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
const { nama_partner } = validatePayload.data;
|
||||
const result = await partnershipService.updatePartnership(id, nama_partner, image);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Data partnership berhasil diperbarui', SUCCESS.OK);
|
||||
} catch (err) {
|
||||
if (req.file?.filename) {
|
||||
await deleteFileIfExists(req.file.filename);
|
||||
}
|
||||
return response(res, 'fail', error(new InternalServerError(err.message)), 'Terjadi kesalahan yang tidak terduga', ERROR.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async deletePartnership(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await partnershipService.deletePartnership(id);
|
||||
|
||||
if (result.err) {
|
||||
return response(res, 'fail', result);
|
||||
}
|
||||
|
||||
return response(res, 'success', result, 'Data partnership 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 PartnershipController();
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
const partnershipController = require('./controllers/partnership.controller');
|
||||
const partnershipService = require('./services/partnership.service');
|
||||
const partnershipRepository = require('./repositories/partnership.repository');
|
||||
|
||||
module.exports = {
|
||||
partnershipController,
|
||||
partnershipService,
|
||||
partnershipRepository
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
const joi = require('joi');
|
||||
|
||||
const createPartnershipModel = joi.object().keys({
|
||||
nama_partner: joi.string().max(255).required().messages({
|
||||
'string.base': 'Nama partner harus berupa teks.',
|
||||
'string.empty': 'Nama partner tidak boleh kosong.',
|
||||
'string.max': 'Nama partner maksimal {#limit} karakter.',
|
||||
'any.required': 'Nama partner wajib diisi.',
|
||||
}),
|
||||
|
||||
image_path: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Path gambar harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
const updatePartnershipModel = joi.object().keys({
|
||||
nama_partner: joi.string().max(255).optional().messages({
|
||||
'string.base': 'Nama partner harus berupa teks.',
|
||||
'string.max': 'Nama partner maksimal {#limit} karakter.',
|
||||
}),
|
||||
|
||||
image_path: joi.string().optional().allow('', null).messages({
|
||||
'string.base': 'Path gambar harus berupa teks.',
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createPartnershipModel,
|
||||
updatePartnershipModel,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
const prisma = require('../../../helpers/db/db_connection');
|
||||
|
||||
class PartnershipRepository {
|
||||
async create(partnershipData) {
|
||||
return prisma.partnership.create({
|
||||
data: partnershipData,
|
||||
});
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return prisma.partnership.findMany();
|
||||
}
|
||||
|
||||
async findById(id) {
|
||||
return prisma.partnership.findUnique({
|
||||
where: { id: parseInt(id) },
|
||||
});
|
||||
}
|
||||
|
||||
async updateById(id, partnershipData) {
|
||||
return prisma.partnership.update({
|
||||
where: { id: parseInt(id) },
|
||||
data: partnershipData,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteById(id) {
|
||||
return prisma.partnership.delete({
|
||||
where: { id: parseInt(id) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new PartnershipRepository();
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
const partnershipRepository = require('../repositories/partnership.repository');
|
||||
const path = require('path');
|
||||
const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper');
|
||||
const imageUploadHelper = require('../../../helpers/utils/imageUpload.helper');
|
||||
const { data, error } = require('../../../helpers/utils/wrapper');
|
||||
const { NotFoundError } = require('../../../helpers/error');
|
||||
|
||||
class PartnershipService {
|
||||
async createPartnership(nama_partner, imageFile) {
|
||||
try {
|
||||
const imagePath = imageUploadHelper.uploadImage(imageFile);
|
||||
|
||||
const partnershipData = {
|
||||
nama_partner,
|
||||
image_path: imagePath,
|
||||
};
|
||||
|
||||
const result = await partnershipRepository.create(partnershipData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
if (imageFile?.filename) {
|
||||
try {
|
||||
await deleteFileIfExists(imageFile.filename);
|
||||
} catch (cleanupErr) {
|
||||
console.error('Failed to cleanup file:', cleanupErr);
|
||||
}
|
||||
}
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllPartnerships() {
|
||||
try {
|
||||
const partnerships = await partnershipRepository.findAll();
|
||||
return data(partnerships);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async getPartnershipById(id) {
|
||||
try {
|
||||
const partnership = await partnershipRepository.findById(id);
|
||||
|
||||
if (!partnership) {
|
||||
return error(new NotFoundError('Partnership Tidak Ditemukan'));
|
||||
}
|
||||
|
||||
return data(partnership);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async updatePartnership(id, nama_partner, imageFile) {
|
||||
let oldImagePath = null;
|
||||
let newImageUploaded = false;
|
||||
|
||||
try {
|
||||
const checkResult = await this.getPartnershipById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const existingPartnership = checkResult.data;
|
||||
let imagePath = existingPartnership.image_path;
|
||||
|
||||
if (imageFile) {
|
||||
oldImagePath = existingPartnership.image_path;
|
||||
|
||||
imagePath = imageUploadHelper.uploadImage(imageFile);
|
||||
newImageUploaded = true;
|
||||
|
||||
if (oldImagePath) {
|
||||
const oldFileName = path.basename(oldImagePath);
|
||||
await deleteFileIfExists(oldFileName);
|
||||
}
|
||||
}
|
||||
|
||||
const partnershipData = {
|
||||
nama_partner,
|
||||
image_path: imagePath,
|
||||
};
|
||||
|
||||
const result = await partnershipRepository.updateById(id, partnershipData);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
if (newImageUploaded && imageFile?.filename) {
|
||||
try {
|
||||
await deleteFileIfExists(imageFile.filename);
|
||||
} catch (cleanupErr) {
|
||||
console.error('Failed to cleanup new file:', cleanupErr);
|
||||
}
|
||||
}
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deletePartnership(id) {
|
||||
try {
|
||||
const checkResult = await this.getPartnershipById(id);
|
||||
if (checkResult.err) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
const partnership = checkResult.data;
|
||||
|
||||
if (partnership.image_path) {
|
||||
const fileName = path.basename(partnership.image_path);
|
||||
await deleteFileIfExists(fileName);
|
||||
}
|
||||
|
||||
const result = await partnershipRepository.deleteById(id);
|
||||
return data(result);
|
||||
} catch (err) {
|
||||
return error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new PartnershipService();
|
||||
Reference in New Issue
Block a user