Lewati ke konten

Perbaikan create route

Validasi, hash asinkron, select tanpa password.

Berkas: alat/hari-3/Praktik 9/perbaikan/create-route.js

Praktik 9/perbaikan/create-route.js
// =====================================================================
// Praktik 9 - versi perbaikan create/route.js
//
// Perubahan dari versi modul:
//
// 1. requireAuth() dijalankan SEBELUM request.json(), supaya beban
// parsing body tidak bisa dipicu tanpa token.
//
// 2. Body divalidasi dan dinormalkan lebih dahulu. Versi modul memakai
// data.role yang bisa undefined; di sini role punya nilai bawaan dan
// hanya nilai yang dikenal yang diterima.
//
// 3. Daftar role yang boleh dibuat diperbaiki. Versi modul menulis
// ["editor", "admin"] padahal komentar di roles.js menyebut viewer,
// admin, super_admin. Sekarang daftarnya diambil dari satu sumber di
// roles.js supaya tidak ada dua daftar yang saling bertentangan.
//
// 4. Kolom yang salah tulis (nama dua kali) dan pemakaian hashSync
// diperbaiki. bcrypt.hashSync memblokir event loop Node selama proses
// hashing; versi async tidak.
//
// 5. Respons tidak lagi mengembalikan objek user apa adanya. Prisma create
// mengembalikan SELURUH kolom termasuk password berisi hash bcrypt.
//
// 6. Pesan galat database tidak lagi diteruskan mentah ke klien.
//
// Letakkan berkas ini di: src/app/api/users/create/route.js
// =====================================================================
import { NextResponse } from "next/server";
import bcrypt from "bcryptjs";
import { randomUUID } from "node:crypto";
import { requireAuth } from "../../../../../lib/auth/verifyBearerToken";
import { db } from "../../../../../lib/db";
import { ROLE_DAPAT_DIBUAT } from "../../../../../lib/auth/roles";
const EMAIL_POLA = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
// Panjang minimum password yang bisa diterima akun baru. Modul tidak
// menetapkan apa pun, sehingga "123" bisa menjadi password admin.
const PASSWORD_MIN = 8;
export async function POST(request) {
// 1. Otorisasi lebih dahulu — jangan sentuh body sebelum token sah.
const { error, status } = requireAuth(request, "super_admin");
if (error) {
return NextResponse.json({ message: error }, { status });
}
let data;
try {
data = await request.json();
} catch {
return NextResponse.json({ message: "Body harus berupa JSON" }, { status: 400 });
}
const nama = typeof data.nama === "string" ? data.nama.trim() : "";
const email = typeof data.email === "string" ? data.email.trim().toLowerCase() : "";
const password = typeof data.password === "string" ? data.password : "";
const role = data.role ?? ROLE_DAPAT_DIBUAT[0];
const isActive = data.is_active === true;
if (!nama || !email || !password) {
return NextResponse.json(
{ message: "Nama, email, dan password wajib diisi" },
{ status: 400 },
);
}
if (!EMAIL_POLA.test(email)) {
return NextResponse.json({ message: "Format email tidak valid" }, { status: 400 });
}
if (password.length < PASSWORD_MIN) {
return NextResponse.json(
{ message: `Password minimal ${PASSWORD_MIN} karakter` },
{ status: 400 },
);
}
if (!ROLE_DAPAT_DIBUAT.includes(role)) {
return NextResponse.json(
{ message: `Role tidak valid. Pilihan: ${ROLE_DAPAT_DIBUAT.join(", ")}` },
{ status: 400 },
);
}
try {
// Cek email lebih dahulu supaya pesannya jelas. Tetap ada UNIQUE
// constraint di database sebagai penjaga terakhir kalau ada dua
// permintaan datang bersamaan.
const sudahAda = await db.users.findUnique({
where: { email },
select: { user_id: true },
});
if (sudahAda) {
return NextResponse.json({ message: "Email sudah terdaftar" }, { status: 409 });
}
const passwordHash = await bcrypt.hash(password, 12);
const penggunaBaru = await db.users.create({
data: {
user_id: randomUUID(),
nama,
email,
password: passwordHash,
role,
is_active: isActive,
},
// Hanya kolom ini yang keluar dari database.
select: {
user_id: true,
nama: true,
email: true,
role: true,
is_active: true,
created_at: true,
},
});
return NextResponse.json(
{ message: "Berhasil membuat user baru", data: penggunaBaru },
{ status: 201 },
);
} catch (err) {
// Galat database dicatat di server, tidak dikirim ke klien.
console.error("[users/create]", err);
return NextResponse.json({ message: "Terjadi kesalahan pada server" }, { status: 500 });
}
}