lapeh 2.6.17 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.env.example +1 -6
  2. package/README.md +19 -85
  3. package/bin/index.js +84 -180
  4. package/dist/lib/bootstrap.d.ts.map +1 -1
  5. package/dist/lib/bootstrap.js +17 -16
  6. package/dist/lib/core/store.d.ts +55 -0
  7. package/dist/lib/core/store.d.ts.map +1 -0
  8. package/dist/lib/core/store.js +66 -0
  9. package/dist/lib/middleware/error.d.ts.map +1 -1
  10. package/dist/lib/middleware/error.js +1 -20
  11. package/dist/lib/utils/validator.d.ts.map +1 -1
  12. package/dist/lib/utils/validator.js +3 -32
  13. package/dist/src/modules/Auth/auth.controller.d.ts.map +1 -1
  14. package/dist/src/modules/Auth/auth.controller.js +118 -105
  15. package/dist/src/modules/Rbac/rbac.controller.d.ts.map +1 -1
  16. package/dist/src/modules/Rbac/rbac.controller.js +141 -140
  17. package/dist/src/routes/index.d.ts.map +1 -1
  18. package/dist/src/routes/index.js +0 -5
  19. package/doc/en/CHEATSHEET.md +3 -7
  20. package/doc/en/CLI.md +16 -41
  21. package/doc/en/DEPLOYMENT.md +171 -245
  22. package/doc/en/GETTING_STARTED.md +1 -25
  23. package/doc/en/PACKAGES.md +2 -3
  24. package/doc/en/STRUCTURE.md +1 -11
  25. package/doc/en/TUTORIAL.md +61 -119
  26. package/doc/id/CHANGELOG.md +16 -0
  27. package/doc/id/CHEATSHEET.md +0 -4
  28. package/doc/id/CLI.md +19 -54
  29. package/doc/id/DEPLOYMENT.md +171 -245
  30. package/doc/id/GETTING_STARTED.md +91 -115
  31. package/doc/id/PACKAGES.md +0 -1
  32. package/doc/id/STRUCTURE.md +1 -11
  33. package/doc/id/TUTORIAL.md +51 -109
  34. package/gitignore.template +0 -10
  35. package/lib/bootstrap.ts +39 -38
  36. package/lib/core/store.ts +116 -0
  37. package/lib/middleware/error.ts +1 -21
  38. package/lib/utils/validator.ts +3 -39
  39. package/package.json +4 -18
  40. package/scripts/init-project.js +2 -108
  41. package/scripts/make-module.js +1 -12
  42. package/scripts/seed-json.js +158 -0
  43. package/src/modules/Auth/auth.controller.ts +156 -106
  44. package/src/modules/Rbac/rbac.controller.ts +193 -138
  45. package/src/routes/index.ts +0 -3
  46. package/src/routes/rbac.ts +42 -42
  47. package/storage/logs/.0337f5062fe676994d1dc340156e089444e3d6e0-audit.json +5 -10
  48. package/storage/logs/lapeh-2025-12-30.log +1093 -0
  49. package/tsconfig.build.json +1 -3
  50. package/tsconfig.json +0 -1
  51. package/lib/core/database.ts +0 -5
  52. package/prisma/base.prisma.template +0 -8
  53. package/prisma/migrations/20251225163737_init/migration.sql +0 -236
  54. package/prisma/migrations/20251226000329_create_pets_table/migration.sql +0 -11
  55. package/prisma/migrations/20251226001249_create_pets_table/migration.sql +0 -82
  56. package/prisma/migrations/20251226001717_restore_core_models/migration.sql +0 -236
  57. package/prisma/migrations/migration_lock.toml +0 -3
  58. package/prisma/schema.prisma +0 -197
  59. package/prisma/seed.ts +0 -411
  60. package/scripts/compile-schema.js +0 -64
  61. package/src/modules/Auth/auth.prisma +0 -106
  62. package/src/modules/Pets/pets.controller.ts +0 -238
  63. package/src/modules/Pets/pets.prisma +0 -9
  64. package/src/modules/Rbac/rbac.prisma +0 -68
  65. package/src/routes/pets.ts +0 -13
  66. package/storage/logs/lapeh-2025-12-26.log +0 -88
  67. package/storage/logs/lapeh-2025-12-27.log +0 -217
@@ -9,7 +9,6 @@ Untuk memahami Lapeh Framework sepenuhnya, Anda perlu tahu apa fungsi setiap fil
9
9
  | `bin/` | Berisi script eksekusi untuk CLI (`npx lapeh`). Anda jarang menyentuh ini. |
10
10
  | `doc/` | Dokumentasi proyek ini berada. |
11
11
  | `lib/` | **Framework Core**. Bagian internal framework yang jarang Anda sentuh. |
12
- | `prisma/` | Jantung konfigurasi Database. |
13
12
  | `scripts/` | Kumpulan script Node.js untuk utility (generator, compiler schema, dll). |
14
13
  | `src/` | **Source Code Utama**. 99% kodingan Anda ada di sini. |
15
14
  | `.env` | Variabel rahasia (Database URL, API Keys). **Jangan commit file ini ke Git!** |
@@ -29,7 +28,6 @@ Lapeh menggunakan pendekatan **Modular**. Setiap fitur dikelompokkan dalam satu
29
28
  Contoh struktur modul `Auth`:
30
29
 
31
30
  - `Auth/auth.controller.ts`: Logika aplikasi (Controller).
32
- - `Auth/auth.prisma`: Definisi tabel database (Model).
33
31
 
34
32
  ### `src/routes/`
35
33
 
@@ -54,7 +52,6 @@ Bagian ini mirip dengan `node_modules` atau folder `.next` di Next.js. Ini adala
54
52
  Bagian "Mesin" framework.
55
53
 
56
54
  - `server.ts`: Setup Express App.
57
- - `database.ts`: Instance Prisma Client.
58
55
  - `redis.ts`: Koneksi Redis.
59
56
  - `serializer.ts`: Logic caching JSON Schema.
60
57
 
@@ -74,18 +71,11 @@ Fungsi bantuan (Helper) bawaan.
74
71
  - `response.ts`: Standar format JSON response (`sendFastSuccess`, `sendError`).
75
72
  - `logger.ts`: Sistem logging (Winston).
76
73
 
77
- ## Folder `prisma/`
78
-
79
- - `migrations/`: History perubahan database (SQL file). Jangan diedit manual.
80
- - `base.prisma.template`: Header dari schema database (berisi konfigurasi datasource db).
81
- - `seed.ts`: Script untuk mengisi data awal (Data Seeding).
82
-
83
74
  ## Folder `scripts/`
84
75
 
85
76
  Script-script "Magic" yang dijalankan `npm run`.
86
77
 
87
- - `make-module.js`: Generator modul baru (Controller + Prisma Model).
88
- - `compile-schema.js`: Penggabung file `.prisma` dari setiap modul menjadi satu `schema.prisma`.
78
+ - `make-module.js`: Generator modul baru (Controller).
89
79
  - `init-project.js`: Wizard setup awal.
90
80
  - `generate-jwt-secret.js`: Generator kunci rahasia JWT otomatis.
91
81
 
@@ -4,44 +4,12 @@ Dalam tutorial ini, kita akan membangun fitur "Manajemen Buku" sederhana menggun
4
4
  1. **CLI** untuk generate kode.
5
5
  2. **Validator** untuk validasi input.
6
6
  3. **Fast Serialization** untuk respon cepat.
7
- 4. **RBAC** untuk proteksi delete (Admin only).
8
7
 
9
- ## Langkah 1: Generate Model Database
8
+ > **Catatan**: Tutorial ini menggunakan array in-memory untuk penyimpanan data agar tetap sederhana. Lapeh v3.0.0 bersifat database-agnostic, jadi Anda bebas menggantinya dengan Prisma, TypeORM, atau library database lainnya.
10
9
 
11
- Kita butuh tabel `books`. Gunakan CLI `make:model`.
10
+ ## Langkah 1: Generate Module (Controller & Route)
12
11
 
13
- ```bash
14
- npm run make:model Book
15
- ```
16
-
17
- File baru akan muncul di `src/models/book.prisma`. Edit file tersebut:
18
-
19
- ```prisma
20
- // src/models/book.prisma
21
-
22
- model Book {
23
- id BigInt @id @default(autoincrement())
24
- title String
25
- author String
26
- isbn String @unique
27
- publishedAt DateTime
28
- stock Int @default(0)
29
- created_at DateTime @default(now())
30
- updated_at DateTime @updatedAt
31
-
32
- @@map("books")
33
- }
34
- ```
35
-
36
- Terapkan perubahan ke database:
37
-
38
- ```bash
39
- npm run prisma:migrate
40
- ```
41
-
42
- ## Langkah 2: Generate Module (Controller & Route)
43
-
44
- Kita buat controller dan route sekaligus.
12
+ Kita akan membuat controller dan route untuk fitur Buku.
45
13
 
46
14
  ```bash
47
15
  npm run make:module Book
@@ -51,24 +19,33 @@ Framework akan membuat:
51
19
  - `src/controllers/bookController.ts`
52
20
  - `src/routes/book.ts`
53
21
 
54
- ## Langkah 3: Implementasi Controller
22
+ ## Langkah 2: Implementasi Controller
55
23
 
56
- Buka `src/controllers/bookController.ts` dan kita implementasikan fitur **Create** dan **List** dengan standar framework.
24
+ Buka `src/controllers/bookController.ts` dan kita implementasikan fitur **Create** dan **List**.
57
25
 
58
- ### Setup Import & Serializer
26
+ ### Setup Import & Data Store
59
27
 
60
28
  ```typescript
61
29
  import { Request, Response } from "express";
62
- import { prisma } from "@/core/database";
63
30
  import { sendFastSuccess, sendError } from "@/utils/response";
64
31
  import { Validator } from "@/utils/validator";
65
32
  import { getSerializer, createResponseSchema } from "@/core/serializer";
66
33
 
34
+ // Simpan data di memory (Array sederhana)
35
+ interface Book {
36
+ id: string;
37
+ title: string;
38
+ author: string;
39
+ isbn: string;
40
+ stock: number;
41
+ }
42
+ const books: Book[] = [];
43
+
67
44
  // 1. Definisikan Schema Output (untuk Fastify Serialization)
68
45
  const bookSchema = {
69
46
  type: "object",
70
47
  properties: {
71
- id: { type: "string" }, // BigInt -> String
48
+ id: { type: "string" },
72
49
  title: { type: "string" },
73
50
  author: { type: "string" },
74
51
  isbn: { type: "string" },
@@ -92,101 +69,66 @@ export async function createBook(req: Request, res: Response) {
92
69
  const validator = await Validator.make(req.body, {
93
70
  title: "required|string|min:3",
94
71
  author: "required|string",
95
- isbn: "required|string|unique:books,isbn", // Cek unik di tabel books
96
- stock: "required|number|min:1",
97
- publishedAt: "required|string" // Format tanggal ISO
72
+ isbn: "required|string",
73
+ stock: "required|number|min:1"
98
74
  });
99
75
 
100
76
  if (validator.fails()) {
101
77
  return sendError(res, 400, "Validation Error", validator.errors());
102
78
  }
103
79
 
104
- const data = validator.validated();
105
-
106
- // 2. Simpan ke Database
107
- const book = await prisma.book.create({
108
- data: {
109
- title: data.title,
110
- author: data.author,
111
- isbn: data.isbn,
112
- stock: data.stock,
113
- publishedAt: new Date(data.publishedAt)
114
- }
115
- });
80
+ // 2. Simpan Data (In-Memory)
81
+ const newBook: Book = {
82
+ id: Date.now().toString(),
83
+ ...validator.validated()
84
+ };
85
+ books.push(newBook);
116
86
 
117
- // 3. Return Response Cepat
118
- return sendFastSuccess(res, 201, bookDetailSerializer, {
119
- status: "success",
120
- message: "Buku berhasil ditambahkan",
121
- data: { ...book, id: book.id.toString() } // Konversi BigInt manual jika perlu
122
- });
87
+ // 3. Kirim Response (Serialized)
88
+ return sendFastSuccess(res, bookDetailSerializer(newBook), 201);
123
89
  }
124
- ```
125
-
126
- ### Implementasi List (High Performance)
127
90
 
128
- ```typescript
129
- export async function getBooks(req: Request, res: Response) {
130
- const books = await prisma.book.findMany({
131
- take: 50, // Limit 50
132
- orderBy: { created_at: "desc" }
133
- });
134
-
135
- // Convert BigInt to string sebelum passing ke serializer (opsional, tapi aman)
136
- const safeBooks = books.map(b => ({ ...b, id: b.id.toString() }));
137
-
138
- return sendFastSuccess(res, 200, bookListSerializer, {
139
- status: "success",
140
- message: "Daftar buku",
141
- data: safeBooks
142
- });
91
+ export async function listBooks(req: Request, res: Response) {
92
+ // Return semua buku
93
+ return sendFastSuccess(res, bookListSerializer(books));
143
94
  }
144
95
  ```
145
96
 
146
- ## Langkah 4: Daftarkan Route & Proteksi
97
+ ## Langkah 3: Register Route
147
98
 
148
- Buka `src/routes/book.ts` (atau file yang digenerate). Pastikan route terhubung dan tambahkan middleware auth.
99
+ Buka `src/routes/book.ts`. CLI sudah membuat struktur dasarnya. Kita hanya perlu menghubungkannya dengan fungsi controller kita.
149
100
 
150
101
  ```typescript
151
102
  import { Router } from "express";
152
- import { createBook, getBooks } from "../controllers/bookController";
153
- import { requireAuth, requireAdmin } from "../middleware/auth";
103
+ import { createBook, listBooks } from "../controllers/bookController";
154
104
 
155
- export const bookRouter = Router();
105
+ const router = Router();
156
106
 
157
- // Public route (bisa diakses siapa saja)
158
- bookRouter.get("/", getBooks);
107
+ router.post("/", createBook);
108
+ router.get("/", listBooks);
159
109
 
160
- // Admin only (Butuh login + role admin)
161
- bookRouter.post("/", requireAuth, requireAdmin, createBook);
110
+ export default router;
162
111
  ```
163
112
 
164
- Terakhir, daftarkan router ini di `src/routes/index.ts` (jika belum otomatis):
165
-
166
- ```typescript
167
- import { bookRouter } from "./book";
168
- // ...
169
- router.use("/books", bookRouter);
170
- ```
171
-
172
- ## Langkah 5: Testing
113
+ ## Langkah 4: Test API Anda
173
114
 
174
115
  Jalankan server:
175
116
  ```bash
176
117
  npm run dev
177
118
  ```
178
119
 
179
- Coba hit endpoint:
180
- 1. **POST /api/books** (Tanpa token) -> 401 Unauthorized.
181
- 2. **POST /api/books** (Token User Biasa) -> 403 Forbidden.
182
- 3. **POST /api/books** (Token Admin + Data Invalid) -> 400 Validation Error.
183
- 4. **POST /api/books** (Token Admin + Data Valid) -> 201 Created.
184
- 5. **GET /api/books** -> 200 OK (Super Cepat).
120
+ Test dengan curl atau Postman:
121
+
122
+ **Buat Buku Baru:**
123
+ ```bash
124
+ curl -X POST http://localhost:4000/api/book \
125
+ -H "Content-Type: application/json" \
126
+ -d '{"title":"Panduan Lapeh", "author":"Tim Lapeh", "isbn":"12345", "stock":10}'
127
+ ```
185
128
 
186
- ## Kesimpulan
129
+ **List Buku:**
130
+ ```bash
131
+ curl http://localhost:4000/api/book
132
+ ```
187
133
 
188
- Dengan Lapeh Framework, Anda telah membuat API yang:
189
- - **Aman** (Validasi, Auth, RBAC).
190
- - **Cepat** (Fast Serialization).
191
- - **Rapi** (Struktur terstandarisasi).
192
- - **Mudah** (CLI Generator).
134
+ Selamat! Anda telah membangun API yang cepat dan tervalidasi tanpa terjebak dalam konfigurasi database yang rumit.
@@ -23,16 +23,6 @@ coverage
23
23
  *.swo
24
24
  .DS_Store
25
25
 
26
- # Prisma
27
- generated/prisma
28
- *.sqlite
29
- *.db
30
- *.db-journal
31
- dev.db
32
-
33
- # OS
34
- Thumbs.db
35
-
36
26
  # Logs
37
27
  testing/*
38
28
  # Logs
package/lib/bootstrap.ts CHANGED
@@ -10,7 +10,6 @@ import http from "http";
10
10
  import path from "path";
11
11
  import { initRealtime } from "./core/realtime";
12
12
  import { initRedis, redis } from "./core/redis";
13
- import { prisma } from "./core/database";
14
13
  import { visitorCounter } from "./middleware/visitor";
15
14
  import { errorHandler } from "./middleware/error";
16
15
  import { apiLimiter } from "./middleware/rateLimit";
@@ -23,31 +22,41 @@ export async function createApp() {
23
22
  // We map '@lapeh' to the directory containing this file (lib/ or dist/lib/)
24
23
  moduleAlias.addAlias("@lapeh", __dirname);
25
24
 
26
- // LOAD USER CONFIG
25
+ // Register alias for src directory (@/) to support imports in controllers/routes
27
26
  const isProduction = process.env.NODE_ENV === "production";
27
+ moduleAlias.addAlias(
28
+ "@",
29
+ isProduction
30
+ ? path.join(process.cwd(), "dist", "src")
31
+ : path.join(process.cwd(), "src")
32
+ );
33
+
34
+ // LOAD USER CONFIG
28
35
  const configPath = isProduction
29
- ? path.join(process.cwd(), "dist", "src", "config")
30
- : path.join(process.cwd(), "src", "config");
36
+ ? path.join(process.cwd(), "dist", "src", "config")
37
+ : path.join(process.cwd(), "src", "config");
31
38
 
32
39
  let appConfig: any = { timeout: 30000, jsonLimit: "10mb" };
33
- let corsConfig: any = {
34
- origin: process.env.CORS_ORIGIN || "*",
35
- credentials: true,
36
- exposedHeaders: ["x-access-token", "x-access-expires-at"],
40
+ let corsConfig: any = {
41
+ origin: process.env.CORS_ORIGIN || "*",
42
+ credentials: true,
43
+ exposedHeaders: ["x-access-token", "x-access-expires-at"],
37
44
  };
38
45
 
39
46
  try {
40
- const appConfModule = require(path.join(configPath, "app"));
41
- if (appConfModule.appConfig) appConfig = { ...appConfig, ...appConfModule.appConfig };
47
+ const appConfModule = require(path.join(configPath, "app"));
48
+ if (appConfModule.appConfig)
49
+ appConfig = { ...appConfig, ...appConfModule.appConfig };
42
50
  } catch (e) {
43
- // ignore
51
+ // ignore
44
52
  }
45
53
 
46
54
  try {
47
- const corsConfModule = require(path.join(configPath, "cors"));
48
- if (corsConfModule.corsConfig) corsConfig = { ...corsConfig, ...corsConfModule.corsConfig };
55
+ const corsConfModule = require(path.join(configPath, "cors"));
56
+ if (corsConfModule.corsConfig)
57
+ corsConfig = { ...corsConfig, ...corsConfModule.corsConfig };
49
58
  } catch (e) {
50
- // ignore
59
+ // ignore
51
60
  }
52
61
 
53
62
  const app = express();
@@ -61,7 +70,7 @@ export async function createApp() {
61
70
  res.setTimeout(timeout, () => {
62
71
  res.status(408).send({
63
72
  status: "error",
64
- message: `Request Timeout (${timeout/1000}s limit)`,
73
+ message: `Request Timeout (${timeout / 1000}s limit)`,
65
74
  });
66
75
  });
67
76
  next();
@@ -78,7 +87,9 @@ export async function createApp() {
78
87
 
79
88
  app.use(requestLogger);
80
89
  app.use(express.json({ limit: appConfig.jsonLimit || "10mb" }));
81
- app.use(express.urlencoded({ extended: true, limit: appConfig.jsonLimit || "10mb" }));
90
+ app.use(
91
+ express.urlencoded({ extended: true, limit: appConfig.jsonLimit || "10mb" })
92
+ );
82
93
  app.use(apiLimiter);
83
94
  app.use(visitorCounter);
84
95
 
@@ -93,35 +104,24 @@ export async function createApp() {
93
104
 
94
105
  // DYNAMIC ROUTE LOADING
95
106
  try {
107
+ console.log("BOOTSTRAP: Loading routes. NODE_ENV=", process.env.NODE_ENV);
96
108
  const isProduction = process.env.NODE_ENV === "production";
97
- const userRoutesPath = isProduction
109
+ let userRoutesPath = isProduction
98
110
  ? path.join(process.cwd(), "dist", "src", "routes")
99
111
  : path.join(process.cwd(), "src", "routes");
100
112
 
113
+ // In test environment, explicitly point to index to ensure resolution
114
+ if (process.env.NODE_ENV === "test") {
115
+ // In test environment (ts-jest), we need to point to the TS file
116
+ // And we might need to use the full path with extension
117
+ userRoutesPath = path.join(process.cwd(), "src", "routes", "index.ts");
118
+ }
119
+
101
120
  // Gunakan require agar sinkron dan mudah dicatch
102
121
  // Check if file exists before requiring to avoid crash in tests/clean env
103
122
  try {
104
- // In test environment, we might need to point to src/routes explicitly if not compiled
105
- // const routesPath = process.env.NODE_ENV === 'test'
106
- // ? path.join(process.cwd(), "src", "routes", "index.ts")
107
- // : userRoutesPath;
108
-
109
- // Note: For TS files in jest, we rely on ts-jest handling 'require' if it points to .ts or we need to use 'import'
110
- // But 'require' in jest with ts-jest should work if configured.
111
-
112
- // However, require(path) with .ts extension might be tricky.
113
- // Let's stick to userRoutesPath but maybe adjust for test env.
114
-
115
- // Check if we are in test environment and using ts-jest
116
- // If so, we might need to import the TS file directly via relative path if alias is not working for require
117
-
118
123
  const { apiRouter } = require(userRoutesPath);
119
124
  app.use("/api", apiRouter);
120
- console.log(
121
- `✅ User routes loaded successfully from ${
122
- isProduction ? "dist/" : ""
123
- }src/routes`
124
- );
125
125
  } catch (e) {
126
126
  // If it's just missing module, maybe we are in test mode or fresh install
127
127
  if (process.env.NODE_ENV !== "test") {
@@ -134,10 +134,12 @@ export async function createApp() {
134
134
  `Error loading routes in test mode from ${userRoutesPath}:`,
135
135
  e
136
136
  );
137
+ throw e;
137
138
  }
138
139
  }
139
140
  } catch (error) {
140
141
  console.error(error);
142
+ if (process.env.NODE_ENV === "test") throw error;
141
143
  }
142
144
 
143
145
  app.use(errorHandler);
@@ -147,7 +149,7 @@ export async function createApp() {
147
149
 
148
150
  export async function bootstrap() {
149
151
  // Validasi Environment Variables
150
- const requiredEnvs = ["DATABASE_URL", "JWT_SECRET"];
152
+ const requiredEnvs = ["JWT_SECRET"];
151
153
  const missingEnvs = requiredEnvs.filter((key) => !process.env[key]);
152
154
  if (missingEnvs.length > 0) {
153
155
  console.error(
@@ -186,7 +188,6 @@ export async function bootstrap() {
186
188
  console.log(`\n🛑 ${signal} received. Closing resources...`);
187
189
  server.close(() => console.log("Http server closed."));
188
190
  try {
189
- await prisma.$disconnect();
190
191
  if (redis && redis.status === "ready") await redis.quit();
191
192
  process.exit(0);
192
193
  } catch (err) {
@@ -0,0 +1,116 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ export interface User {
5
+ id: string;
6
+ email: string;
7
+ name: string;
8
+ password?: string;
9
+ uuid: string;
10
+ avatar?: string | null;
11
+ avatar_url?: string | null;
12
+ email_verified_at?: string | Date | null;
13
+ created_at: string | Date;
14
+ updated_at: string | Date;
15
+ }
16
+
17
+ export interface Role {
18
+ id: string;
19
+ name: string;
20
+ slug: string;
21
+ description?: string | null;
22
+ created_at: string | Date;
23
+ updated_at: string | Date;
24
+ }
25
+
26
+ export interface Permission {
27
+ id: string;
28
+ name: string;
29
+ slug: string;
30
+ description?: string | null;
31
+ created_at: string | Date;
32
+ updated_at: string | Date;
33
+ }
34
+
35
+ export interface UserRole {
36
+ id: string;
37
+ user_id: string;
38
+ role_id: string;
39
+ created_at: string | Date;
40
+ }
41
+
42
+ export interface RolePermission {
43
+ id: string;
44
+ role_id: string;
45
+ permission_id: string;
46
+ created_at: string | Date;
47
+ }
48
+
49
+ export interface UserPermission {
50
+ id: string;
51
+ user_id: string;
52
+ permission_id: string;
53
+ created_at: string | Date;
54
+ }
55
+
56
+ // Database file path
57
+ const dbPath = path.resolve(process.cwd(), "database.json");
58
+
59
+ // Load data function
60
+ function loadData() {
61
+ if (fs.existsSync(dbPath)) {
62
+ const raw = fs.readFileSync(dbPath, "utf-8");
63
+ return JSON.parse(raw);
64
+ }
65
+ return {
66
+ users: [],
67
+ roles: [
68
+ {
69
+ id: "1",
70
+ name: "Admin",
71
+ slug: "admin",
72
+ description: "Administrator",
73
+ created_at: new Date(),
74
+ updated_at: new Date(),
75
+ },
76
+ {
77
+ id: "2",
78
+ name: "User",
79
+ slug: "user",
80
+ description: "Standard User",
81
+ created_at: new Date(),
82
+ updated_at: new Date(),
83
+ },
84
+ ],
85
+ permissions: [],
86
+ user_roles: [],
87
+ role_permissions: [],
88
+ user_permissions: [],
89
+ };
90
+ }
91
+
92
+ const data = loadData();
93
+
94
+ // Export mutable arrays
95
+ export const users: User[] = data.users;
96
+ export const roles: Role[] = data.roles;
97
+ export const permissions: Permission[] = data.permissions;
98
+ export const user_roles: UserRole[] = data.user_roles;
99
+ export const role_permissions: RolePermission[] = data.role_permissions;
100
+ export const user_permissions: UserPermission[] = data.user_permissions;
101
+
102
+ // Helper to save data
103
+ export function saveStore() {
104
+ const payload = {
105
+ users,
106
+ roles,
107
+ permissions,
108
+ user_roles,
109
+ role_permissions,
110
+ user_permissions,
111
+ };
112
+ fs.writeFileSync(dbPath, JSON.stringify(payload, null, 2), "utf-8");
113
+ }
114
+
115
+ // Helper to generate IDs
116
+ export const generateId = () => Math.random().toString(36).substr(2, 9);
@@ -1,6 +1,5 @@
1
1
  import { Request, Response, NextFunction } from "express";
2
2
  import { ZodError } from "zod";
3
- import { Prisma } from "@prisma/client";
4
3
  import { sendError } from "../utils/response";
5
4
  import { Log } from "../utils/logger";
6
5
 
@@ -19,26 +18,7 @@ export function errorHandler(
19
18
  return sendError(res, 400, "Validation Error", formattedErrors);
20
19
  }
21
20
 
22
- // 2. Prisma Errors
23
- if (err instanceof Prisma.PrismaClientKnownRequestError) {
24
- // P2002: Unique constraint failed
25
- if (err.code === "P2002") {
26
- const target = (err.meta?.target as string[]) || [];
27
- const fields = target.length > 0 ? target.join(", ") : "field";
28
- return sendError(res, 409, `Unique constraint failed on: ${fields}`);
29
- }
30
- // P2003: Foreign key constraint failed
31
- if (err.code === "P2003") {
32
- const field = err.meta?.field_name || "unknown field";
33
- return sendError(res, 400, `Foreign key constraint failed on: ${field}`);
34
- }
35
- // P2025: Record not found
36
- if (err.code === "P2025") {
37
- return sendError(res, 404, "Record not found");
38
- }
39
- }
40
-
41
- // 3. JWT Errors
21
+ // 2. JWT Errors
42
22
  if (err.name === "JsonWebTokenError") {
43
23
  return sendError(res, 401, "Invalid token");
44
24
  }
@@ -1,5 +1,4 @@
1
1
  import { z, ZodSchema, ZodError, ZodIssue } from "zod";
2
- import { prisma } from "../core/database";
3
2
 
4
3
  export class Validator {
5
4
  private data: any;
@@ -242,44 +241,9 @@ export class Validator {
242
241
  break;
243
242
  case "unique":
244
243
  // unique:table,column,ignore,idColumn
245
- const [table, column = "id", ignoreValue, ignoreColumn = "id"] =
246
- params;
247
- schema = schema.refine(
248
- async (val: any) => {
249
- if (!val) return true;
250
- const where: any = { [column]: val };
251
- if (ignoreValue && ignoreValue !== "null") {
252
- // Try to handle numeric IDs if ignoreValue looks numeric
253
- const ignoreVal = !isNaN(Number(ignoreValue))
254
- ? Number(ignoreValue)
255
- : ignoreValue;
256
- // But Prisma uses BigInt for IDs often in this project?
257
- // Let's assume string or number is fine, user can cast if needed.
258
- // In this project, IDs are BigInt.
259
- if (
260
- typeof ignoreVal === "number" ||
261
- /^\d+$/.test(String(ignoreValue))
262
- ) {
263
- where[ignoreColumn] = { not: BigInt(ignoreValue) };
264
- } else {
265
- where[ignoreColumn] = { not: ignoreValue };
266
- }
267
- }
268
-
269
- try {
270
- // @ts-ignore
271
- const count = await prisma[table].count({ where });
272
- return count === 0;
273
- } catch (e) {
274
- console.error(
275
- `Validator unique check failed for table ${table}:`,
276
- e
277
- );
278
- return false;
279
- }
280
- },
281
- { message: `The ${column} has already been taken.` }
282
- );
244
+ // NOTE: Unique check requires Database implementation.
245
+ // Since v3.0.0 (No-ORM), this rule is disabled by default.
246
+ // You should implement your own uniqueness check manually in the controller.
283
247
  break;
284
248
  }
285
249
  }