lapeh 2.2.6 → 2.2.7

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 (52) hide show
  1. package/api-testing-sepuluh/.env.example +19 -0
  2. package/api-testing-sepuluh/doc/ARCHITECTURE_GUIDE.md +73 -0
  3. package/api-testing-sepuluh/doc/CHANGELOG.md +77 -0
  4. package/api-testing-sepuluh/doc/CHEATSHEET.md +94 -0
  5. package/api-testing-sepuluh/doc/CLI.md +106 -0
  6. package/api-testing-sepuluh/doc/CONTRIBUTING.md +105 -0
  7. package/api-testing-sepuluh/doc/DEPLOYMENT.md +122 -0
  8. package/api-testing-sepuluh/doc/FAQ.md +81 -0
  9. package/api-testing-sepuluh/doc/FEATURES.md +165 -0
  10. package/api-testing-sepuluh/doc/GETTING_STARTED.md +108 -0
  11. package/api-testing-sepuluh/doc/INTRODUCTION.md +60 -0
  12. package/api-testing-sepuluh/doc/PACKAGES.md +66 -0
  13. package/api-testing-sepuluh/doc/PERFORMANCE.md +91 -0
  14. package/api-testing-sepuluh/doc/ROADMAP.md +93 -0
  15. package/api-testing-sepuluh/doc/SECURITY.md +93 -0
  16. package/api-testing-sepuluh/doc/STRUCTURE.md +90 -0
  17. package/api-testing-sepuluh/doc/TUTORIAL.md +192 -0
  18. package/api-testing-sepuluh/docker-compose.yml +24 -0
  19. package/api-testing-sepuluh/eslint.config.mjs +26 -0
  20. package/api-testing-sepuluh/framework.md +168 -0
  21. package/api-testing-sepuluh/nodemon.json +6 -0
  22. package/api-testing-sepuluh/package-lock.json +5539 -0
  23. package/api-testing-sepuluh/package.json +103 -0
  24. package/api-testing-sepuluh/prisma/base.prisma.template +7 -0
  25. package/api-testing-sepuluh/prisma/migrations/20251227034737_init_setup/migration.sql +248 -0
  26. package/api-testing-sepuluh/prisma/migrations/migration_lock.toml +3 -0
  27. package/api-testing-sepuluh/prisma/schema.prisma +183 -0
  28. package/api-testing-sepuluh/prisma/seed.ts +411 -0
  29. package/api-testing-sepuluh/prisma.config.ts +15 -0
  30. package/api-testing-sepuluh/readme.md +414 -0
  31. package/api-testing-sepuluh/scripts/check-update.js +92 -0
  32. package/api-testing-sepuluh/scripts/compile-schema.js +29 -0
  33. package/api-testing-sepuluh/scripts/config-clear.js +45 -0
  34. package/api-testing-sepuluh/scripts/generate-jwt-secret.js +38 -0
  35. package/api-testing-sepuluh/scripts/init-project.js +178 -0
  36. package/api-testing-sepuluh/scripts/make-controller.js +205 -0
  37. package/api-testing-sepuluh/scripts/make-model.js +42 -0
  38. package/api-testing-sepuluh/scripts/make-module.js +158 -0
  39. package/api-testing-sepuluh/scripts/verify-rbac-functional.js +187 -0
  40. package/api-testing-sepuluh/src/controllers/authController.ts +469 -0
  41. package/api-testing-sepuluh/src/controllers/petController.ts +194 -0
  42. package/api-testing-sepuluh/src/controllers/rbacController.ts +478 -0
  43. package/api-testing-sepuluh/src/models/core.prisma +163 -0
  44. package/api-testing-sepuluh/src/models/pets.prisma +9 -0
  45. package/api-testing-sepuluh/src/routes/auth.ts +74 -0
  46. package/api-testing-sepuluh/src/routes/index.ts +10 -0
  47. package/api-testing-sepuluh/src/routes/pets.ts +13 -0
  48. package/api-testing-sepuluh/src/routes/rbac.ts +42 -0
  49. package/api-testing-sepuluh/storage/logs/.gitkeep +0 -0
  50. package/api-testing-sepuluh/tsconfig.json +39 -0
  51. package/bin/index.js +68 -13
  52. package/package.json +1 -1
@@ -0,0 +1,163 @@
1
+ model cache {
2
+ key String @id
3
+ value String
4
+ expiration Int
5
+ }
6
+
7
+ model cache_locks {
8
+ key String @id
9
+ owner String
10
+ expiration Int
11
+ }
12
+
13
+ model failed_jobs {
14
+ id BigInt @id @default(autoincrement())
15
+ uuid String @unique
16
+ connection String
17
+ queue String
18
+ payload String
19
+ exception String
20
+ failed_at DateTime @default(now())
21
+ }
22
+
23
+ model job_batches {
24
+ id String @id
25
+ name String
26
+ total_jobs Int
27
+ pending_jobs Int
28
+ failed_jobs Int
29
+ failed_job_ids String
30
+ options String?
31
+ cancelled_at Int?
32
+ created_at Int
33
+ finished_at Int?
34
+ }
35
+
36
+ model jobs {
37
+ id BigInt @id @default(autoincrement())
38
+ queue String
39
+ payload String
40
+ attempts Int
41
+ reserved_at Int?
42
+ available_at Int
43
+ created_at Int
44
+
45
+ @@index([queue])
46
+ }
47
+
48
+ model migrations {
49
+ id Int @id @default(autoincrement())
50
+ migration String
51
+ batch Int
52
+ }
53
+
54
+ model password_reset_tokens {
55
+ email String @id
56
+ token String
57
+ created_at DateTime?
58
+ }
59
+
60
+ model personal_access_tokens {
61
+ id BigInt @id @default(autoincrement())
62
+ tokenable_type String
63
+ tokenable_id BigInt
64
+ name String
65
+ token String @unique
66
+ abilities String?
67
+ last_used_at DateTime?
68
+ expires_at DateTime?
69
+ created_at DateTime?
70
+ updated_at DateTime?
71
+
72
+ @@index([expires_at])
73
+ @@index([tokenable_type, tokenable_id])
74
+ }
75
+
76
+ model sessions {
77
+ id String @id
78
+ user_id BigInt?
79
+ ip_address String?
80
+ user_agent String?
81
+ payload String
82
+ last_activity Int
83
+
84
+ @@index([last_activity])
85
+ @@index([user_id])
86
+ }
87
+
88
+ model users {
89
+ id BigInt @id @default(autoincrement())
90
+ uuid String @unique
91
+ name String
92
+ email String @unique
93
+ avatar String?
94
+ avatar_url String?
95
+ email_verified_at DateTime?
96
+ password String
97
+ remember_token String?
98
+ created_at DateTime?
99
+ updated_at DateTime?
100
+
101
+ user_roles user_roles[]
102
+ user_permissions user_permissions[]
103
+ }
104
+
105
+ model roles {
106
+ id BigInt @id @default(autoincrement())
107
+ name String
108
+ slug String @unique
109
+ description String?
110
+ created_at DateTime?
111
+ updated_at DateTime?
112
+
113
+ user_roles user_roles[]
114
+ role_permissions role_permissions[]
115
+ }
116
+
117
+ model permissions {
118
+ id BigInt @id @default(autoincrement())
119
+ name String
120
+ slug String @unique
121
+ description String?
122
+ created_at DateTime?
123
+ updated_at DateTime?
124
+
125
+ role_permissions role_permissions[]
126
+ user_permissions user_permissions[]
127
+ }
128
+
129
+ model user_roles {
130
+ id BigInt @id @default(autoincrement())
131
+ user_id BigInt
132
+ role_id BigInt
133
+ created_at DateTime?
134
+
135
+ user users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
136
+ role roles @relation(fields: [role_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
137
+
138
+ @@unique([user_id, role_id])
139
+ }
140
+
141
+ model role_permissions {
142
+ id BigInt @id @default(autoincrement())
143
+ role_id BigInt
144
+ permission_id BigInt
145
+ created_at DateTime?
146
+
147
+ role roles @relation(fields: [role_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
148
+ permission permissions @relation(fields: [permission_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
149
+
150
+ @@unique([role_id, permission_id])
151
+ }
152
+
153
+ model user_permissions {
154
+ id BigInt @id @default(autoincrement())
155
+ user_id BigInt
156
+ permission_id BigInt
157
+ created_at DateTime?
158
+
159
+ user users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
160
+ permission permissions @relation(fields: [permission_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
161
+
162
+ @@unique([user_id, permission_id])
163
+ }
@@ -0,0 +1,9 @@
1
+
2
+ model pets {
3
+ id BigInt @id @default(autoincrement())
4
+ name String
5
+ species String
6
+ age Int
7
+ created_at DateTime?
8
+ updated_at DateTime?
9
+ }
@@ -0,0 +1,74 @@
1
+ import { Router } from "express";
2
+ import rateLimit from "express-rate-limit";
3
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
4
+ const multer = require("multer");
5
+ import path from "path";
6
+ import fs from "fs";
7
+ import {
8
+ register,
9
+ login,
10
+ me,
11
+ logout,
12
+ refreshToken,
13
+ updatePassword,
14
+ updateProfile,
15
+ updateAvatar,
16
+ } from "@/controllers/authController";
17
+ import { requireAuth } from "@lapeh/middleware/auth";
18
+
19
+ const authLimiter = rateLimit({
20
+ windowMs: 15 * 60 * 1000,
21
+ max: 50,
22
+ standardHeaders: true,
23
+ legacyHeaders: false,
24
+ });
25
+
26
+ const avatarUploadDir = process.env.AVATAR_UPLOAD_DIR || "uploads/avatars";
27
+ if (!fs.existsSync(avatarUploadDir)) {
28
+ fs.mkdirSync(avatarUploadDir, { recursive: true });
29
+ }
30
+
31
+ const storage = (multer as any).diskStorage({
32
+ destination(
33
+ _req: any,
34
+ _file: any,
35
+ cb: (error: Error | null, destination: string) => void
36
+ ) {
37
+ cb(null, avatarUploadDir);
38
+ },
39
+ filename(
40
+ _req: any,
41
+ file: any,
42
+ cb: (error: Error | null, filename: string) => void
43
+ ) {
44
+ const ext = path.extname(file.originalname);
45
+ const base = path.basename(file.originalname, ext);
46
+ const unique = Date.now() + "-" + Math.round(Math.random() * 1e9);
47
+ cb(null, base + "-" + unique + ext);
48
+ },
49
+ });
50
+
51
+ const uploadAvatar = multer({ storage });
52
+
53
+ export const authRouter = Router();
54
+
55
+ authRouter.post("/register", authLimiter, register);
56
+
57
+ authRouter.post("/login", authLimiter, login);
58
+
59
+ authRouter.get("/me", requireAuth, me);
60
+
61
+ authRouter.post("/logout", requireAuth, logout);
62
+
63
+ authRouter.post("/refresh", authLimiter, refreshToken);
64
+
65
+ authRouter.put("/password", requireAuth, updatePassword);
66
+
67
+ authRouter.put("/profile", requireAuth, updateProfile);
68
+
69
+ authRouter.post(
70
+ "/avatar",
71
+ requireAuth,
72
+ uploadAvatar.single("avatar"),
73
+ updateAvatar
74
+ );
@@ -0,0 +1,10 @@
1
+ import { Router } from "express";
2
+ import { authRouter } from "@/routes/auth";
3
+ import { rbacRouter } from "@/routes/rbac";
4
+ import petRouter from "@/routes/pets";
5
+
6
+ export const apiRouter = Router();
7
+
8
+ apiRouter.use("/auth", authRouter);
9
+ apiRouter.use("/rbac", rbacRouter);
10
+ apiRouter.use("/pets", petRouter);
@@ -0,0 +1,13 @@
1
+ import { Router } from "express";
2
+ import * as PetController from "@/controllers/petController";
3
+ import { parseMultipart } from "@lapeh/middleware/multipart";
4
+
5
+ const router = Router();
6
+
7
+ router.get("/", PetController.index);
8
+ router.get("/:id", PetController.show);
9
+ router.post("/", parseMultipart, PetController.store);
10
+ router.put("/:id", parseMultipart, PetController.update);
11
+ router.delete("/:id", PetController.destroy);
12
+
13
+ export default router;
@@ -0,0 +1,42 @@
1
+ import { Router } from "express";
2
+ import { requireAdmin, requireAuth } from "@lapeh/middleware/auth";
3
+ import {
4
+ createRole,
5
+ listRoles,
6
+ updateRole,
7
+ deleteRole,
8
+ createPermission,
9
+ listPermissions,
10
+ updatePermission,
11
+ deletePermission,
12
+ assignRoleToUser,
13
+ removeRoleFromUser,
14
+ assignPermissionToRole,
15
+ removePermissionFromRole,
16
+ assignPermissionToUser,
17
+ removePermissionFromUser,
18
+ } from "@/controllers/rbacController";
19
+
20
+ export const rbacRouter = Router();
21
+
22
+ rbacRouter.use(requireAuth);
23
+ rbacRouter.use(requireAdmin);
24
+
25
+ rbacRouter.post("/roles", createRole);
26
+ rbacRouter.get("/roles", listRoles);
27
+ rbacRouter.put("/roles/:id", updateRole);
28
+ rbacRouter.delete("/roles/:id", deleteRole);
29
+
30
+ rbacRouter.post("/permissions", createPermission);
31
+ rbacRouter.get("/permissions", listPermissions);
32
+ rbacRouter.put("/permissions/:id", updatePermission);
33
+ rbacRouter.delete("/permissions/:id", deletePermission);
34
+
35
+ rbacRouter.post("/users/assign-role", assignRoleToUser);
36
+ rbacRouter.post("/users/remove-role", removeRoleFromUser);
37
+
38
+ rbacRouter.post("/roles/assign-permission", assignPermissionToRole);
39
+ rbacRouter.post("/roles/remove-permission", removePermissionFromRole);
40
+
41
+ rbacRouter.post("/users/assign-permission", assignPermissionToUser);
42
+ rbacRouter.post("/users/remove-permission", removePermissionFromUser);
File without changes
@@ -0,0 +1,39 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "CommonJS",
5
+ "outDir": "dist",
6
+ "rootDir": ".",
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "noUnusedLocals": true,
14
+ "noUnusedParameters": true,
15
+ "paths": {
16
+ "@lapeh/*": [
17
+ "./node_modules/lapeh/lib/*"
18
+ ],
19
+ "@/*": [
20
+ "./src/*"
21
+ ]
22
+ }
23
+ },
24
+ "include": [
25
+ "lib",
26
+ "src",
27
+ "prisma",
28
+ "generated"
29
+ ],
30
+ "exclude": [
31
+ "node_modules",
32
+ "dist"
33
+ ],
34
+ "ts-node": {
35
+ "ignore": [
36
+ "node_modules/(?!lapeh)"
37
+ ]
38
+ }
39
+ }
package/bin/index.js CHANGED
@@ -183,7 +183,7 @@ async function upgradeProject() {
183
183
 
184
184
  // Update paths
185
185
  if (tsconfig.compilerOptions && tsconfig.compilerOptions.paths) {
186
- tsconfig.compilerOptions.paths["@lapeh/*"] = ["node_modules/lapeh/lib/*"];
186
+ tsconfig.compilerOptions.paths["@lapeh/*"] = ["./node_modules/lapeh/lib/*"];
187
187
  }
188
188
 
189
189
  // Add ts-node ignore configuration
@@ -210,6 +210,7 @@ async function upgradeProject() {
210
210
  function createProject() {
211
211
  const projectName = args.find(arg => !arg.startsWith('-'));
212
212
  const isFull = args.includes('--full');
213
+ const useDefaults = args.includes('--defaults');
213
214
 
214
215
  if (!projectName) {
215
216
  console.error('❌ Please specify the project name:');
@@ -266,27 +267,45 @@ function createProject() {
266
267
 
267
268
  // --- DATABASE SELECTION ---
268
269
  console.log("\n--- Database Configuration ---");
269
- const dbType = await selectOption("Database apa yang akan digunakan?", [
270
- { key: "pgsql", label: "PostgreSQL", provider: "postgresql", defaultPort: "5432" },
271
- { key: "mysql", label: "MySQL", provider: "mysql", defaultPort: "3306" },
272
- ]);
270
+ let dbType, host, port, user, password, dbName;
271
+
272
+ if (useDefaults) {
273
+ console.log("ℹ️ Using default configuration (--defaults)...");
274
+ dbType = { key: "pgsql", label: "PostgreSQL", provider: "postgresql", defaultPort: "5432" };
275
+ host = "localhost";
276
+ port = "5432";
277
+ user = "postgres"; // Default postgres user is usually postgres, not root
278
+ password = "password"; // Default password
279
+ dbName = projectName.replace(/-/g, '_');
280
+ } else {
281
+ dbType = await selectOption("Database apa yang akan digunakan?", [
282
+ { key: "pgsql", label: "PostgreSQL", provider: "postgresql", defaultPort: "5432" },
283
+ { key: "mysql", label: "MySQL", provider: "mysql", defaultPort: "3306" },
284
+ ]);
285
+
286
+ host = await ask("Database Host", "localhost");
287
+ port = await ask("Database Port", dbType.defaultPort);
288
+ user = await ask("Database User", "root");
289
+ password = await ask("Database Password", "");
290
+ dbName = await ask("Database Name", projectName.replace(/-/g, '_')); // Default db name based on project name
291
+ }
273
292
 
274
293
  let dbUrl = "";
275
294
  let dbProvider = dbType.provider;
276
295
 
277
- const host = await ask("Database Host", "localhost");
278
- const port = await ask("Database Port", dbType.defaultPort);
279
- const user = await ask("Database User", "root");
280
- const password = await ask("Database Password", "");
281
- const dbName = await ask("Database Name", projectName.replace(/-/g, '_')); // Default db name based on project name
282
-
283
296
  if (dbType.key === "pgsql") {
284
297
  dbUrl = `postgresql://${user}:${password}@${host}:${port}/${dbName}?schema=public`;
285
298
  } else {
286
299
  dbUrl = `mysql://${user}:${password}@${host}:${port}/${dbName}`;
287
300
  }
288
301
 
289
- rl.close();
302
+ if (!useDefaults) {
303
+ rl.close();
304
+ } else {
305
+ // If we didn't use rl, we might not need to close it if we didn't open it?
306
+ // Actually rl is created at the top. We should close it.
307
+ rl.close();
308
+ }
290
309
 
291
310
  // List of files/folders to exclude
292
311
  const ignoreList = [
@@ -346,6 +365,10 @@ function createProject() {
346
365
  // Ensure prisma CLI is available in devDependencies for the new project
347
366
  packageJson.devDependencies = packageJson.devDependencies || {};
348
367
  packageJson.devDependencies["prisma"] = "7.2.0";
368
+
369
+ // Add missing types for dev
370
+ packageJson.devDependencies["@types/express"] = "^5.0.0";
371
+ packageJson.devDependencies["@types/compression"] = "^1.7.5";
349
372
 
350
373
  packageJson.version = '1.0.0';
351
374
  packageJson.description = 'Generated by lapeh';
@@ -373,7 +396,7 @@ function createProject() {
373
396
 
374
397
  // Update paths
375
398
  if (tsconfig.compilerOptions && tsconfig.compilerOptions.paths) {
376
- tsconfig.compilerOptions.paths["@lapeh/*"] = ["node_modules/lapeh/lib/*"];
399
+ tsconfig.compilerOptions.paths["@lapeh/*"] = ["./node_modules/lapeh/lib/*"];
377
400
  }
378
401
 
379
402
  // Add ts-node ignore configuration
@@ -384,6 +407,38 @@ function createProject() {
384
407
  fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
385
408
  }
386
409
 
410
+ // Configure prisma.config.ts to use tsconfig-paths
411
+ const prismaConfigPath = path.join(projectDir, 'prisma.config.ts');
412
+ if (fs.existsSync(prismaConfigPath)) {
413
+ console.log('🔧 Configuring prisma.config.ts...');
414
+ let prismaConfigContent = fs.readFileSync(prismaConfigPath, 'utf8');
415
+ prismaConfigContent = prismaConfigContent.replace(
416
+ /seed:\s*"ts-node\s+prisma\/seed\.ts"/g,
417
+ 'seed: "ts-node -r tsconfig-paths/register prisma/seed.ts"'
418
+ );
419
+ fs.writeFileSync(prismaConfigPath, prismaConfigContent);
420
+ }
421
+
422
+ // Configure prisma/seed.ts imports
423
+ const prismaSeedPath = path.join(projectDir, 'prisma', 'seed.ts');
424
+ if (fs.existsSync(prismaSeedPath)) {
425
+ console.log('🔧 Configuring prisma/seed.ts...');
426
+ let seedContent = fs.readFileSync(prismaSeedPath, 'utf8');
427
+
428
+ // Add dotenv config if missing
429
+ if (!seedContent.includes('dotenv.config()')) {
430
+ seedContent = 'import dotenv from "dotenv";\ndotenv.config();\n\n' + seedContent;
431
+ }
432
+
433
+ // Update import path
434
+ seedContent = seedContent.replace(
435
+ /import\s+{\s*prisma\s*}\s+from\s+["']@lapeh\/core\/database["']/,
436
+ 'import { prisma } from "@lapeh/core/database"'
437
+ );
438
+
439
+ fs.writeFileSync(prismaSeedPath, seedContent);
440
+ }
441
+
387
442
  // Create .env from .env.example with correct DB config
388
443
  console.log('⚙️ Configuring environment...');
389
444
  const envExamplePath = path.join(projectDir, '.env.example');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lapeh",
3
- "version": "2.2.6",
3
+ "version": "2.2.7",
4
4
  "description": "Framework API Express yang siap pakai (Standardized)",
5
5
  "main": "index.js",
6
6
  "bin": {