admin roadmap filter & search, user management, reopen rejected, system health

This commit is contained in:
2026-05-18 19:45:14 +07:00
parent c52b12daad
commit 6e02f2f0d7
36 changed files with 2013 additions and 339 deletions
+54
View File
@@ -0,0 +1,54 @@
import { userRepo } from "@/server/repositories/user.repo";
export const userService = {
/**
* Suspend user oleh admin. Idempotent — kalau sudah suspended, tolak supaya
* admin tahu (cegah race condition multiple admin suspend sekaligus).
* Wajib reason untuk audit (min 10 char).
*/
async suspendUser(input: {
userId: string;
adminId: string;
reason: string;
}) {
if (input.userId === input.adminId) {
throw new Error("Tidak bisa suspend akun sendiri");
}
const trimmedReason = input.reason.trim();
if (trimmedReason.length < 10) {
throw new Error("Alasan suspend wajib min 10 karakter untuk audit");
}
if (trimmedReason.length > 500) {
throw new Error("Alasan suspend maksimal 500 karakter");
}
const target = await userRepo.findById(input.userId);
if (!target) {
throw new Error("User tidak ditemukan");
}
if (target.suspended) {
throw new Error("User sudah dalam status suspended");
}
return userRepo.setSuspension(input.userId, {
suspended: true,
suspendedById: input.adminId,
suspendedReason: trimmedReason,
});
},
async unsuspendUser(input: { userId: string; adminId: string }) {
if (input.userId === input.adminId) {
throw new Error("Tidak bisa modifikasi akun sendiri");
}
const target = await userRepo.findById(input.userId);
if (!target) {
throw new Error("User tidak ditemukan");
}
if (!target.suspended) {
throw new Error("User tidak dalam status suspended");
}
return userRepo.setSuspension(input.userId, { suspended: false });
},
};