diff --git a/bun.lockb b/bun.lockb
index c59dbad6..6d3d1e76 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/next.config.ts b/next.config.ts
index ac27ff93..0d4e98c2 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -4,17 +4,46 @@ const nextConfig: NextConfig = {
async headers() {
return [
{
- source: '/assets/:path*', // Path ke folder gambar
+ source: '/assets/:path*',
headers: [
{
key: 'Cache-Control',
- value: 'public, max-age=3600, stale-while-revalidate=600', // Cache selama 1 jam, validasi ulang setelah 10 menit
+ value: 'public, max-age=3600, stale-while-revalidate=600',
+ },
+ ],
+ },
+ // Security headers
+ {
+ source: '/(.*)',
+ headers: [
+ {
+ key: 'X-Content-Type-Options',
+ value: 'nosniff',
+ },
+ {
+ key: 'X-Frame-Options',
+ value: 'DENY',
+ },
+ {
+ key: 'X-XSS-Protection',
+ value: '1; mode=block',
},
],
},
];
},
-
+ // Enable React Strict Mode for development
+ reactStrictMode: true,
+ // Enable experimental features
+ experimental: {
+ serverActions: {
+ bodySizeLimit: '2mb',
+ },
+ },
+ // Configure images
+ images: {
+ domains: ['localhost'],
+ },
};
export default nextConfig;
diff --git a/package.json b/package.json
index ca12628a..19b7fd3c 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"seed": "bun run prisma/seed.ts"
},
"dependencies": {
+ "@auth/prisma-adapter": "^2.10.0",
"@cubejs-client/core": "^0.31.0",
"@elysiajs/cookie": "^0.8.0",
"@elysiajs/cors": "^1.2.0",
@@ -29,8 +30,9 @@
"@mantine/form": "^8.1.0",
"@mantine/hooks": "^7.17.4",
"@mantine/tiptap": "^7.17.4",
+ "@next-auth/prisma-adapter": "^1.0.7",
"@paljs/types": "^8.1.0",
- "@prisma/client": "^6.3.1",
+ "@prisma/client": "^6.15.0",
"@tabler/icons-react": "^3.30.0",
"@tiptap/extension-highlight": "^2.11.7",
"@tiptap/extension-link": "^2.11.7",
@@ -41,11 +43,13 @@
"@tiptap/pm": "^2.11.7",
"@tiptap/react": "^2.11.7",
"@tiptap/starter-kit": "^2.11.7",
+ "@types/bcrypt": "^6.0.0",
"@types/bun": "^1.2.2",
"@types/leaflet": "^1.9.20",
"@types/lodash": "^4.17.16",
"add": "^2.0.6",
"animate.css": "^4.1.1",
+ "bcrypt": "^6.0.0",
"bcryptjs": "^3.0.2",
"bun": "^1.2.2",
"chart.js": "^4.4.8",
@@ -65,6 +69,7 @@
"motion": "^12.4.1",
"nanoid": "^5.1.5",
"next": "15.1.6",
+ "next-auth": "^4.24.11",
"next-view-transitions": "^0.3.4",
"node-fetch": "^3.3.2",
"p-limit": "^6.2.0",
@@ -87,8 +92,8 @@
"devDependencies": {
"@eslint/eslintrc": "^3",
"@types/jsonwebtoken": "^9.0.10",
- "@types/node": "^20",
- "@types/react": "^19",
+ "@types/node": "^24.3.0",
+ "@types/react": "^19.1.12",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.1.6",
diff --git a/prisma/data/user/role.json b/prisma/data/user/role.json
deleted file mode 100644
index 6091de17..00000000
--- a/prisma/data/user/role.json
+++ /dev/null
@@ -1,6 +0,0 @@
-[
- {
- "id": "cmdpm429r0000vnndkcwslt0h",
- "name": "warga"
- }
-]
\ No newline at end of file
diff --git a/prisma/data/user/roles.json b/prisma/data/user/roles.json
new file mode 100644
index 00000000..266d3fdf
--- /dev/null
+++ b/prisma/data/user/roles.json
@@ -0,0 +1,30 @@
+[
+ {
+ "id": "role_admin_desa",
+ "name": "ADMIN_DESA",
+ "description": "Administrator Desa",
+ "permissions": ["manage_users", "manage_content", "view_reports"],
+ "isActive": true,
+ "createdAt": "2025-09-01T00:00:00.000Z",
+ "updatedAt": "2025-09-01T00:00:00.000Z"
+ },
+ {
+ "id": "role_admin_kesehatan",
+ "name": "ADMIN_KESEHATAN",
+ "description": "Administrator Bidang Kesehatan",
+ "permissions": ["manage_health_data", "view_reports"],
+ "isActive": true,
+ "createdAt": "2025-09-01T00:00:00.000Z",
+ "updatedAt": "2025-09-01T00:00:00.000Z"
+ },
+ {
+ "id": "role_admin_sekolah",
+ "name": "ADMIN_SEKOLAH",
+ "description": "Administrator Sekolah",
+ "permissions": ["manage_school_data", "view_reports"],
+ "isActive": true,
+ "createdAt": "2025-09-01T00:00:00.000Z",
+ "updatedAt": "2025-09-01T00:00:00.000Z"
+ }
+ ]
+
\ No newline at end of file
diff --git a/prisma/data/user/users.json b/prisma/data/user/users.json
new file mode 100644
index 00000000..f9ab996a
--- /dev/null
+++ b/prisma/data/user/users.json
@@ -0,0 +1,36 @@
+[
+ {
+ "id": "user_admin_desa",
+ "nama": "Admin Desa",
+ "email": "admin.desa@example.com",
+ "password": "$2b$10$XFDWYOJFxQ7ZM5bA0N4Z0O8u0eKYv58wLsaR7h6XK9bqWJ1YQJQ9q",
+ "roleId": "role_admin_desa",
+ "isActive": true,
+ "lastLogin": "2025-08-31T10:00:00.000Z",
+ "createdAt": "2025-09-01T00:00:00.000Z",
+ "updatedAt": "2025-09-01T00:00:00.000Z"
+ },
+ {
+ "id": "user_admin_puskesmas",
+ "nama": "Admin Kesehatan",
+ "email": "admin.kesehatan@example.com",
+ "password": "$2b$10$XFDWYOJFxQ7ZM5bA0N4Z0O8u0eKYv58wLsaR7h6XK9bqWJ1YQJQ9q",
+ "roleId": "role_admin_kesehatan",
+ "isActive": true,
+ "lastLogin": null,
+ "createdAt": "2025-09-01T00:00:00.000Z",
+ "updatedAt": "2025-09-01T00:00:00.000Z"
+ },
+ {
+ "id": "user_admin_sekolah",
+ "nama": "Admin Sekolah",
+ "email": "admin.sekolah@example.com",
+ "password": "$2b$10$XFDWYOJFxQ7ZM5bA0N4Z0O8u0eKYv58wLsaR7h6XK9bqWJ1YQJQ9q",
+ "roleId": "role_admin_sekolah",
+ "isActive": true,
+ "lastLogin": null,
+ "createdAt": "2025-09-01T00:00:00.000Z",
+ "updatedAt": "2025-09-01T00:00:00.000Z"
+ }
+ ]
+
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index d2e4cc06..2e997e47 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -2103,25 +2103,43 @@ model KategoriBuku {
}
model User {
- id String @id @default(cuid())
- nama String
- email String @unique
- password String
- role Role @relation(fields: [roleId], references: [id])
- roleId String
- isActive Boolean @default(true)
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
+ id String @id @default(cuid())
+ nama String
+ email String @unique
+ password String
+ role Role @relation(fields: [roleId], references: [id])
+ roleId String
+ instansi String? // Nama instansi (Puskesmas, Sekolah, dll)
+ isActive Boolean @default(true)
+ lastLogin DateTime?
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ deletedAt DateTime?
}
model Role {
- id String @id @default(cuid())
- name String
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- deletedAt DateTime @default(now())
- isActive Boolean @default(true)
- User User[]
+ id String @id @default(cuid())
+ name String @unique // ADMIN_DESA, ADMIN_KESEHATAN, ADMIN_SEKOLAH
+ description String?
+ permissions Json // Menyimpan permission dalam format JSON
+ isActive Boolean @default(true)
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ deletedAt DateTime?
+ users User[]
+
+ @@map("roles")
+}
+
+// Tabel untuk menyimpan permission
+model Permission {
+ id String @id @default(cuid())
+ name String @unique
+ description String?
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@map("permissions")
}
// ========================================= DATA PENDIDIKAN ========================================= //
diff --git a/prisma/seed.ts b/prisma/seed.ts
index 4d3fabc6..a21ef4ad 100644
--- a/prisma/seed.ts
+++ b/prisma/seed.ts
@@ -52,8 +52,55 @@ import tempatKegiatan from "./data/pendidikan/pendidikan-non-formal/tempat-kegia
import tujuanProgram2 from "./data/pendidikan/pendidikan-non-formal/tujuan-program2.json";
import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-unggulan.json";
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
+import roles from "./data/user/roles.json";
+import users from "./data/user/users.json";
(async () => {
+ //roles
+ for (const r of roles) {
+ await prisma.role.upsert({
+ where: { id: r.id },
+ update: {
+ name: r.name,
+ description: r.description,
+ permissions: r.permissions,
+ isActive: true,
+ },
+ create: {
+ id: r.id,
+ name: r.name,
+ description: r.description,
+ permissions: r.permissions,
+ isActive: true,
+ },
+ });
+ }
+ console.log("✅ Roles seeded");
+
+ //users
+ for (const u of users) {
+ await prisma.user.upsert({
+ where: { id: u.id },
+ update: {
+ nama: u.nama,
+ email: u.email,
+ password: u.password,
+ roleId: u.roleId,
+ isActive: true,
+ },
+ create: {
+ id: u.id,
+ nama: u.nama,
+ email: u.email,
+ password: u.password,
+ roleId: u.roleId,
+ isActive: true,
+ },
+ });
+ }
+ console.log("✅ Users seeded");
+
+
// =========== LANDING PAGE ===========
// =========== SUBMENU PROFILE ===========
// =========== PROFILE PEJABAT DESA ===========
diff --git a/scripts/list-users.ts b/scripts/list-users.ts
new file mode 100644
index 00000000..bcf820f9
--- /dev/null
+++ b/scripts/list-users.ts
@@ -0,0 +1,33 @@
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient();
+
+async function listUsers() {
+ try {
+ const users = await prisma.user.findMany({
+ include: {
+ role: true
+ }
+ });
+
+ console.log('Daftar Pengguna:');
+ console.log('================');
+
+ users.forEach((user, index) => {
+ console.log(`\n[${index + 1}] ${user.nama} (${user.email})`);
+ console.log(` Role: ${user.role.name} (${user.role.id})`);
+ console.log(` Status: ${user.isActive ? 'Aktif' : 'Tidak Aktif'}`);
+ console.log(` Terakhir Login: ${user.lastLogin || 'Belum pernah login'}`);
+ console.log(` Dibuat pada: ${user.createdAt}`);
+ });
+
+ console.log('\nTotal pengguna:', users.length);
+
+ } catch (error) {
+ console.error('Error:', error);
+ } finally {
+ await prisma.$disconnect();
+ }
+}
+
+listUsers();
diff --git a/scripts/reset-passwords.ts b/scripts/reset-passwords.ts
new file mode 100644
index 00000000..aad090c4
--- /dev/null
+++ b/scripts/reset-passwords.ts
@@ -0,0 +1,39 @@
+import { PrismaClient } from '@prisma/client';
+import bcrypt from 'bcryptjs';
+
+const prisma = new PrismaClient();
+
+async function resetPasswords() {
+ try {
+ // Password to set for all users
+ const newPassword = 'password123';
+ const hashedPassword = await bcrypt.hash(newPassword, 10);
+
+ // Get all users
+ const users = await prisma.user.findMany();
+
+ console.log('Resetting passwords for all users...');
+
+ // Update each user's password
+ for (const user of users) {
+ await prisma.user.update({
+ where: { id: user.id },
+ data: {
+ password: hashedPassword,
+ isActive: true // Ensure all users are active
+ }
+ });
+ console.log(`✅ Reset password for ${user.email}`);
+ }
+
+ console.log('\nSemua password telah direset ke: password123');
+ console.log('Silakan login dengan email yang ada dan password: password123');
+
+ } catch (error) {
+ console.error('Error:', error);
+ } finally {
+ await prisma.$disconnect();
+ }
+}
+
+resetPasswords();
diff --git a/src/app/admin/(dashboard)/_state/user/user-state.ts b/src/app/admin/(dashboard)/_state/user/user-state.ts
index f13e46a1..f79afc56 100644
--- a/src/app/admin/(dashboard)/_state/user/user-state.ts
+++ b/src/app/admin/(dashboard)/_state/user/user-state.ts
@@ -16,62 +16,62 @@ const defaultForm = { nama: '', email: '', password: '', roleId: '' }
// 2. State Valtio
const userState = proxy({
- // Register
- register: {
- form: { ...defaultForm },
- loading: false,
- async submit() {
- const valid = userSchema.omit({ roleId: true }).safeParse(userState.register.form)
- if (!valid.success) {
- const err = valid.error.issues.map(i => i.message).join(', ')
- return toast.error(err)
- }
- try {
- userState.register.loading = true
- const res = await ApiFetch.api.user.register.post(userState.register.form)
- if (res.status === 200) {
- toast.success('Registrasi berhasil, silakan login')
- userState.register.form = { ...defaultForm } // reset
- } else {
- toast.error(res.data?.message || 'Gagal registrasi')
- }
- } catch (e) {
- console.error(e)
- toast.error('Terjadi kesalahan saat registrasi')
- } finally {
- userState.register.loading = false
- }
- },
- },
+ // // Register
+ // register: {
+ // form: { ...defaultForm },
+ // loading: false,
+ // async submit() {
+ // const valid = userSchema.omit({ roleId: true }).safeParse(userState.register.form)
+ // if (!valid.success) {
+ // const err = valid.error.issues.map(i => i.message).join(', ')
+ // return toast.error(err)
+ // }
+ // try {
+ // userState.register.loading = true
+ // const res = await ApiFetch.api.user.register.post(userState.register.form)
+ // if (res.status === 200) {
+ // toast.success('Registrasi berhasil, silakan login')
+ // userState.register.form = { ...defaultForm } // reset
+ // } else {
+ // toast.error(res.data?.message || 'Gagal registrasi')
+ // }
+ // } catch (e) {
+ // console.error(e)
+ // toast.error('Terjadi kesalahan saat registrasi')
+ // } finally {
+ // userState.register.loading = false
+ // }
+ // },
+ // },
- // Login
- login: {
- form: { email: '', password: '' },
- loading: false,
- async submit() {
- try {
- userState.login.loading = true
- const res = await ApiFetch.api.user.login.post(userState.login.form)
- if (res.status === 200) {
- toast.success('Login berhasil')
- const token = res.data?.data?.token
- if (typeof token === 'string') {
- localStorage.setItem('token', token)
- // Optional: simpan user role untuk otorisasi
- const user = res.data?.data?.user
- localStorage.setItem('user', JSON.stringify(user))
- }
- } else {
- toast.error(res.data?.message || 'Login gagal')
- }
- } catch (e) {
- console.error(e)
- toast.error('Terjadi kesalahan saat login')
- } finally {
- userState.login.loading = false
- }
- },
- },
+ // // Login
+ // login: {
+ // form: { email: '', password: '' },
+ // loading: false,
+ // async submit() {
+ // try {
+ // userState.login.loading = true
+ // const res = await ApiFetch.api.user.login.post(userState.login.form)
+ // if (res.status === 200) {
+ // toast.success('Login berhasil')
+ // const token = res.data?.data?.token
+ // if (typeof token === 'string') {
+ // localStorage.setItem('token', token)
+ // // Optional: simpan user role untuk otorisasi
+ // const user = res.data?.data?.user
+ // localStorage.setItem('user', JSON.stringify(user))
+ // }
+ // } else {
+ // toast.error(res.data?.message || 'Login gagal')
+ // }
+ // } catch (e) {
+ // console.error(e)
+ // toast.error('Terjadi kesalahan saat login')
+ // } finally {
+ // userState.login.loading = false
+ // }
+ // },
+ // },
// CRUD User (untuk admin)
create: {
diff --git a/src/app/admin/(dashboard)/layout.tsx b/src/app/admin/(dashboard)/layout.tsx
new file mode 100644
index 00000000..6bab9b05
--- /dev/null
+++ b/src/app/admin/(dashboard)/layout.tsx
@@ -0,0 +1,28 @@
+import { ReactNode } from 'react';
+import { AdminProvider } from '@/components/admin/admin-provider';
+import { AdminRoute } from '@/components/auth/protected-route';
+import { Box } from '@mantine/core';
+import { AdminNavbar } from '@/components/admin/navbar';
+import { AdminHeader } from '@/components/admin/header';
+
+export default function AdminLayout({
+ children,
+}: {
+ children: ReactNode;
+}) {
+ return (
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
diff --git a/src/app/admin/dashboard/page.tsx b/src/app/admin/dashboard/page.tsx
new file mode 100644
index 00000000..89bfdb0a
--- /dev/null
+++ b/src/app/admin/dashboard/page.tsx
@@ -0,0 +1,78 @@
+'use client';
+
+import { Title, Text, Card, SimpleGrid, Container, Group, Stack } from '@mantine/core';
+import { IconUsers, IconBuildingHospital, IconSchool, IconNews, IconCalendarEvent } from '@tabler/icons-react';
+import { useSession } from 'next-auth/react';
+import { ROLES } from '@/lib/auth/config';
+
+const stats = [
+ { title: 'Total Penduduk', value: '15,234', diff: 34, icon: IconUsers },
+ { title: 'Fasilitas Kesehatan', value: '4', diff: -13, icon: IconBuildingHospital },
+ { title: 'Sekolah', value: '8', diff: 18, icon: IconSchool },
+ { title: 'Berita', value: '156', diff: 30, icon: IconNews },
+ { title: 'Kegiatan Mendatang', value: '7', diff: 8, icon: IconCalendarEvent },
+];
+
+export default function DashboardPage() {
+ const { data: session } = useSession();
+ const userRole = session?.user?.role?.name;
+
+ return (
+
+
+ Dashboard
+
+
+
+ Selamat datang kembali, {session?.user?.name || 'Admin'}
+
+
+
+ {stats.map((stat) => (
+
+
+
+ {stat.title}
+
+
+
+
+
+ {stat.value}
+
+ 0 ? 'teal' : 'red'} fz="sm" fw={500}>
+ {stat.diff > 0 ? '+' : ''}{stat.diff}%
+
+
+
+ Dibandingkan bulan sebelumnya
+
+
+ ))}
+
+
+
+ {userRole === ROLES.ADMIN_DESA && (
+
+ Admin Desa
+ Anda memiliki akses penuh untuk mengelola konten dan data desa.
+
+ )}
+
+ {userRole === ROLES.ADMIN_KESEHATAN && (
+
+ Admin Kesehatan
+ Kelola data kesehatan dan layanan kesehatan di desa.
+
+ )}
+
+ {userRole === ROLES.ADMIN_SEKOLAH && (
+
+ Admin Sekolah
+ Kelola data pendidikan dan aktivitas sekolah di desa.
+
+ )}
+
+
+ );
+}
diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index 06a78cd1..55d0d1df 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -1,9 +1,6 @@
-import { Text } from "@mantine/core";
+// /admin/page.tsx
+import { redirect } from 'next/navigation';
-export default function Page() {
- return(
-
- Test
-
- )
+export default function AdminPage() {
+ redirect('/admin');
}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/user/create.ts b/src/app/api/[[...slugs]]/_lib/user/create.ts
index 1365fbcc..8b043329 100644
--- a/src/app/api/[[...slugs]]/_lib/user/create.ts
+++ b/src/app/api/[[...slugs]]/_lib/user/create.ts
@@ -7,6 +7,7 @@ type FormCreateUser = {
email: string;
password: string;
roleId: string;
+ instansi?: string;
isActive?: boolean;
};
@@ -18,15 +19,11 @@ export default async function userCreate(context: Context) {
}
try {
- // Cek apakah email sudah terdaftar
const existing = await prisma.user.findUnique({
where: { email: body.email },
});
- if (existing) {
- throw new Error("Email sudah terdaftar");
- }
+ if (existing) throw new Error("Email sudah terdaftar");
- // Hash password sebelum simpan
const hashedPassword = await bcrypt.hash(body.password, 10);
const result = await prisma.user.create({
@@ -35,15 +32,13 @@ export default async function userCreate(context: Context) {
email: body.email,
password: hashedPassword,
roleId: body.roleId,
+ instansi: body.instansi ?? null,
isActive: body.isActive ?? true,
},
+ include: { role: true },
});
- return {
- success: true,
- message: "User berhasil dibuat",
- data: result,
- };
+ return { success: true, message: "User berhasil dibuat", data: result };
} catch (error) {
console.error("Error creating user:", error);
throw new Error("Gagal membuat user: " + (error as Error).message);
diff --git a/src/app/api/[[...slugs]]/_lib/user/del.ts b/src/app/api/[[...slugs]]/_lib/user/del.ts
index 021960a0..fdabf5f2 100644
--- a/src/app/api/[[...slugs]]/_lib/user/del.ts
+++ b/src/app/api/[[...slugs]]/_lib/user/del.ts
@@ -1,28 +1,23 @@
-// /api/user/delete.ts
-import prisma from '@/lib/prisma';
-import { Context } from 'elysia';
+import { Context } from "elysia";
+import prisma from "@/lib/prisma";
export default async function userDelete(context: Context) {
const { id } = context.params as { id: string };
+ if (!id) throw new Error("ID user wajib diisi");
+
try {
+ const existing = await prisma.user.findUnique({ where: { id } });
+ if (!existing) throw new Error("User tidak ditemukan");
+
const deleted = await prisma.user.update({
where: { id },
- data: {
- isActive: false,
- },
+ data: { deletedAt: new Date(), isActive: false },
});
- return {
- success: true,
- message: 'User berhasil dinonaktifkan',
- data: deleted,
- };
+ return { success: true, message: "User berhasil dihapus", data: deleted };
} catch (error) {
- console.error(error);
- return {
- success: false,
- message: 'Gagal menghapus user',
- };
+ console.error("Error deleting user:", error);
+ throw new Error("Gagal menghapus user: " + (error as Error).message);
}
}
diff --git a/src/app/api/[[...slugs]]/_lib/user/findMany.ts b/src/app/api/[[...slugs]]/_lib/user/findMany.ts
index 88c74834..75d77c84 100644
--- a/src/app/api/[[...slugs]]/_lib/user/findMany.ts
+++ b/src/app/api/[[...slugs]]/_lib/user/findMany.ts
@@ -2,27 +2,15 @@ import prisma from "@/lib/prisma";
export default async function userFindMany() {
try {
- const data = await prisma.user.findMany({
- include: {
- role: true,
- },
- orderBy: {
- createdAt: "desc",
- },
+ const users = await prisma.user.findMany({
+ where: { deletedAt: null },
+ include: { role: true },
+ orderBy: { createdAt: "desc" },
});
- return {
- success: true,
- message: "Success get all user",
- data,
- };
+ return { success: true, data: users };
} catch (error) {
- console.error("Find many error:", error);
- return {
- success: false,
- message:
- "Gagal mengambil data: " +
- (error instanceof Error ? error.message : "Unknown error"),
- };
+ console.error("Error fetching users:", error);
+ throw new Error("Gagal mengambil data user");
}
}
diff --git a/src/app/api/[[...slugs]]/_lib/user/findUnique.ts b/src/app/api/[[...slugs]]/_lib/user/findUnique.ts
index 7d0adc7f..1e27606e 100644
--- a/src/app/api/[[...slugs]]/_lib/user/findUnique.ts
+++ b/src/app/api/[[...slugs]]/_lib/user/findUnique.ts
@@ -1,31 +1,22 @@
-import prisma from '@/lib/prisma';
-import { Context } from 'elysia';
+import { Context } from "elysia";
+import prisma from "@/lib/prisma";
export default async function userFindUnique(context: Context) {
const { id } = context.params as { id: string };
+ if (!id) throw new Error("ID user wajib diisi");
+
try {
const user = await prisma.user.findUnique({
where: { id },
- include: {
- role: true,
- },
+ include: { role: true },
});
- if (!user) {
- return { success: false, message: 'User tidak ditemukan' };
- }
+ if (!user) throw new Error("User tidak ditemukan");
- return {
- success: true,
- message: 'Berhasil mendapatkan user',
- data: user,
- };
+ return { success: true, data: user };
} catch (error) {
- console.error(error);
- return {
- success: false,
- message: 'Gagal mengambil data user',
- };
+ console.error("Error finding user:", error);
+ throw new Error("Gagal menemukan user: " + (error as Error).message);
}
}
diff --git a/src/app/api/[[...slugs]]/_lib/user/index.ts b/src/app/api/[[...slugs]]/_lib/user/index.ts
index 51f80f13..af84090f 100644
--- a/src/app/api/[[...slugs]]/_lib/user/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/user/index.ts
@@ -6,8 +6,8 @@ import userFindMany from "./findMany";
import userFindUnique from "./findUnique";
import userUpdate from "./updt";
import userDelete from "./del"; // `delete` nggak boleh jadi nama file JS langsung, jadi biasanya `del.ts`
-import userLogin from "./login";
-import userRegister from "./register";
+import { login as userLogin } from "./login";
+import { register as userRegister } from "./register";
const User = new Elysia({ prefix: "/api/user" })
.post("/register", userRegister, {
diff --git a/src/app/api/[[...slugs]]/_lib/user/login.ts b/src/app/api/[[...slugs]]/_lib/user/login.ts
index c4938a26..e0164b64 100644
--- a/src/app/api/[[...slugs]]/_lib/user/login.ts
+++ b/src/app/api/[[...slugs]]/_lib/user/login.ts
@@ -1,81 +1,67 @@
-import { Context } from "elysia";
+/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
-// ENV atau secret key untuk token
-const JWT_SECRET = process.env.JWT_SECRET || "super-secret-key"; // ganti di env production
-
-type LoginForm = {
- email: string;
- password: string;
+export const base64 = (str: string): string => {
+ return Buffer.from(str).toString('base64').replace(/\//g, '_').replace(/\+/g, '-');
};
-export default async function userLogin(context: Context) {
- const body = (await context.body) as LoginForm;
+export const login = async ({ body, set }: any) => {
+ const { email, password } = body;
try {
- // 1. Cari user berdasarkan email
- const user = await prisma.user.findUnique({
- where: { email: body.email },
- include: { role: true }, // include role untuk otorisasi
+ console.log('Login attempt for email:', email);
+
+ const user = await prisma.user.findUnique({
+ where: { email },
+ include: { role: true }
});
-
- // 2. Jika tidak ada user
+
if (!user) {
- return {
- success: false,
- message: "Email tidak ditemukan",
- };
+ console.log('User not found:', email);
+ set.status = 401;
+ return { error: "Email atau password salah" };
}
- // 3. Cek apakah user aktif
- if (!user.isActive) {
- return {
- success: false,
- message: "Akun tidak aktif",
- };
+ console.log('User found, comparing password...');
+ const isPasswordValid = await bcrypt.compare(password, user.password);
+
+ if (!isPasswordValid) {
+ console.log('Invalid password for user:', email);
+ set.status = 401;
+ return { error: "Email atau password salah" };
}
- // 4. Verifikasi password
- const isMatch = await bcrypt.compare(body.password, user.password);
- if (!isMatch) {
- return {
- success: false,
- message: "Password salah",
- };
- }
-
- // 5. Buat JWT token
+ // Generate JWT token
const token = jwt.sign(
- {
- id: user.id,
+ {
+ id: user.id,
email: user.email,
- role: user.role.name,
+ role: user.role?.name || 'user',
+ name: user.nama
},
- JWT_SECRET,
- { expiresIn: "7d" } // expire 7 hari
+ process.env.NEXTAUTH_SECRET || 'your-secret-key',
+ { expiresIn: '7d' }
);
- // 6. Kirim response
- return {
- success: true,
- message: "Login berhasil",
- data: {
- user: {
- id: user.id,
- nama: user.nama,
- email: user.email,
- role: user.role.name,
- },
- token,
+ // Set secure, HTTP-only cookies
+ set.headers['Set-Cookie'] = `__Secure-next-auth.session-token=${token}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=604800`;
+
+ console.log('Login successful for user:', email);
+ return {
+ message: "Login berhasil",
+ user: {
+ id: user.id,
+ email: user.email,
+ name: user.nama,
+ role: user.role?.name
},
+ token
};
} catch (error) {
- console.error("Login error:", error);
- return {
- success: false,
- message: "Terjadi kesalahan saat login",
- };
+ console.error('Login error:', error);
+ set.status = 500;
+ return { error: "Terjadi kesalahan saat login" };
}
-}
+};
diff --git a/src/app/api/[[...slugs]]/_lib/user/register.ts b/src/app/api/[[...slugs]]/_lib/user/register.ts
index af88b575..f153907a 100644
--- a/src/app/api/[[...slugs]]/_lib/user/register.ts
+++ b/src/app/api/[[...slugs]]/_lib/user/register.ts
@@ -1,88 +1,31 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
-import bcrypt from "bcryptjs";
-import { Context } from "elysia";
+import bcrypt from "bcrypt";
-interface RegisterBody {
- nama: string;
- email: string;
- password: string;
-}
+export const register = async ({ body, set }: any) => {
+ const { email, password, nama } = body;
-export default async function userRegister(context: Context) {
- try {
- const body = (await context.body) as RegisterBody;
-
- // Validasi input
- if (!body.nama || !body.email || !body.password) {
- context.set.status = 400;
- return {
- success: false,
- message: "Semua field harus diisi",
- data: null
- };
- }
-
- // Cek email sudah terdaftar
- const existingUser = await prisma.user.findUnique({
- where: { email: body.email },
- });
-
- if (existingUser) {
- context.set.status = 400;
- return {
- success: false,
- message: "Email sudah terdaftar",
- data: null
- };
- }
-
- // Dapatkan role warga
- const role = await prisma.role.findFirst({
- where: { name: "warga" }
- });
-
- if (!role) {
- context.set.status = 500;
- return {
- success: false,
- message: "Role warga tidak ditemukan",
- data: null
- };
- }
-
- // Hash password
- const hashedPassword = await bcrypt.hash(body.password, 10);
-
- // Buat user baru
- const user = await prisma.user.create({
- data: {
- nama: body.nama,
- email: body.email,
- password: hashedPassword,
- roleId: role.id,
- },
- select: {
- id: true,
- nama: true,
- email: true,
- roleId: true,
- createdAt: true,
- updatedAt: true
- }
- });
-
- return {
- success: true,
- message: "Berhasil mendaftar",
- data: user,
- };
- } catch (error) {
- console.error("Registration error:", error);
- context.set.status = 500;
- return {
- success: false,
- message: "Terjadi kesalahan saat mendaftar",
- data: null
- };
+ // cek email udah ada belum
+ const existing = await prisma.user.findUnique({ where: { email } });
+ if (existing) {
+ set.status = 400;
+ return { error: "Email sudah terdaftar" };
}
-}
+
+ // hash password
+ const hashed = await bcrypt.hash(password, 10);
+
+ // Default role ID for regular users (you might want to get this from config or database)
+ const defaultRoleId = 'YOUR_DEFAULT_ROLE_ID'; // Replace with actual default role ID
+
+ const user = await prisma.user.create({
+ data: {
+ email,
+ password: hashed,
+ nama,
+ roleId: defaultRoleId,
+ },
+ });
+
+ return { message: "Registrasi berhasil", user: { id: user.id, email: user.email } };
+};
diff --git a/src/app/api/[[...slugs]]/_lib/user/role/create.ts b/src/app/api/[[...slugs]]/_lib/user/role/create.ts
index 6b74c529..791968f2 100644
--- a/src/app/api/[[...slugs]]/_lib/user/role/create.ts
+++ b/src/app/api/[[...slugs]]/_lib/user/role/create.ts
@@ -12,6 +12,7 @@ export default async function roleCreate(context: Context) {
const result = await prisma.role.create({
data: {
name: body.name,
+ permissions: [],
},
});
return {
diff --git a/src/app/api/[[...slugs]]/_lib/user/updt.ts b/src/app/api/[[...slugs]]/_lib/user/updt.ts
index 60855a7d..41edbce3 100644
--- a/src/app/api/[[...slugs]]/_lib/user/updt.ts
+++ b/src/app/api/[[...slugs]]/_lib/user/updt.ts
@@ -1,35 +1,47 @@
-// /api/user/update.ts
-import prisma from '@/lib/prisma';
-import { Context } from 'elysia';
+import { Context } from "elysia";
+import prisma from "@/lib/prisma";
+import bcrypt from "bcryptjs";
-export default async function userUpdate(context: Context) {
+type FormEditUser = {
+ nama?: string;
+ email?: string;
+ password?: string;
+ roleId?: string;
+ instansi?: string;
+ isActive?: boolean;
+};
+
+export default async function userEdit(context: Context) {
const { id } = context.params as { id: string };
- const body = await context.body as {
- nama?: string;
- email?: string;
- password?: string;
- roleId?: string;
- isActive?: boolean;
- };
+ const body = (await context.body) as FormEditUser;
+
+ if (!id) throw new Error("ID user wajib diisi");
try {
+ const existing = await prisma.user.findUnique({ where: { id } });
+ if (!existing) throw new Error("User tidak ditemukan");
+
+ let hashedPassword: string | undefined;
+ if (body.password) {
+ hashedPassword = await bcrypt.hash(body.password, 10);
+ }
+
const updated = await prisma.user.update({
where: { id },
data: {
- ...body,
+ nama: body.nama ?? existing.nama,
+ email: body.email ?? existing.email,
+ password: hashedPassword ?? existing.password,
+ roleId: body.roleId ?? existing.roleId,
+ instansi: body.instansi ?? existing.instansi,
+ isActive: body.isActive ?? existing.isActive,
},
+ include: { role: true },
});
- return {
- success: true,
- message: 'User berhasil diupdate',
- data: updated,
- };
+ return { success: true, message: "User berhasil diperbarui", data: updated };
} catch (error) {
- console.error(error);
- return {
- success: false,
- message: 'Gagal mengupdate user',
- };
+ console.error("Error updating user:", error);
+ throw new Error("Gagal mengedit user: " + (error as Error).message);
}
}
diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts
new file mode 100644
index 00000000..968e7150
--- /dev/null
+++ b/src/app/api/auth/[...nextauth]/route.ts
@@ -0,0 +1,6 @@
+import NextAuth from 'next-auth';
+import { authOptions } from '@/lib/auth/options';
+
+const handler = NextAuth(authOptions);
+
+export { handler as GET, handler as POST };
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index a12ceacd..95e0c17c 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -13,7 +13,10 @@ import '@mantine/tiptap/styles.css';
import "primereact/resources/themes/lara-light-blue/theme.css";
import "primereact/resources/primereact.min.css";
import "primeicons/primeicons.css";
+import 'react-toastify/dist/ReactToastify.css';
+import { AuthProvider } from '@/components/providers/session-provider';
+import { authOptions } from '@/lib/auth/options';
import LoadDataFirstClient from "@/app/darmasaba/_com/LoadDataFirstClient";
@@ -25,6 +28,7 @@ import {
} from "@mantine/core";
import { ViewTransitions } from "next-view-transitions";
import { ToastContainer } from "react-toastify";
+import { getServerSession } from "next-auth";
export const metadata = {
title: "Desa Darmasaba",
@@ -39,11 +43,13 @@ const theme = createTheme({
headings: { fontFamily: "San Francisco, sans-serif" },
});
-export default function RootLayout({
+export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
+ const session = await getServerSession(authOptions);
+
return (
@@ -56,15 +62,16 @@ export default function RootLayout({
/>
-
- {children}
-
-
-
+
+
+
+
+ {children}
+
+
-