lapeh 2.3.6 → 2.3.8

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 (243) hide show
  1. package/bin/index.js +39 -56
  2. package/dist/generated/prisma/browser.d.ts +80 -0
  3. package/dist/generated/prisma/browser.d.ts.map +1 -0
  4. package/dist/generated/prisma/browser.js +56 -0
  5. package/dist/generated/prisma/client.d.ts +97 -0
  6. package/dist/generated/prisma/client.d.ts.map +1 -0
  7. package/dist/generated/prisma/client.js +68 -0
  8. package/dist/generated/prisma/commonInputTypes.d.ts +486 -0
  9. package/dist/generated/prisma/commonInputTypes.d.ts.map +1 -0
  10. package/dist/generated/prisma/commonInputTypes.js +11 -0
  11. package/dist/generated/prisma/enums.d.ts +2 -0
  12. package/dist/generated/prisma/enums.d.ts.map +1 -0
  13. package/dist/generated/prisma/enums.js +11 -0
  14. package/dist/generated/prisma/internal/class.d.ts +281 -0
  15. package/dist/generated/prisma/internal/class.d.ts.map +1 -0
  16. package/dist/generated/prisma/internal/class.js +76 -0
  17. package/dist/generated/prisma/internal/prismaNamespace.d.ts +1734 -0
  18. package/dist/generated/prisma/internal/prismaNamespace.d.ts.map +1 -0
  19. package/dist/generated/prisma/internal/prismaNamespace.js +260 -0
  20. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +200 -0
  21. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts.map +1 -0
  22. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +231 -0
  23. package/dist/generated/prisma/models/cache.d.ts +986 -0
  24. package/dist/generated/prisma/models/cache.d.ts.map +1 -0
  25. package/dist/generated/prisma/models/cache.js +2 -0
  26. package/dist/generated/prisma/models/cache_locks.d.ts +976 -0
  27. package/dist/generated/prisma/models/cache_locks.d.ts.map +1 -0
  28. package/dist/generated/prisma/models/cache_locks.js +2 -0
  29. package/dist/generated/prisma/models/failed_jobs.d.ts +1098 -0
  30. package/dist/generated/prisma/models/failed_jobs.d.ts.map +1 -0
  31. package/dist/generated/prisma/models/failed_jobs.js +2 -0
  32. package/dist/generated/prisma/models/job_batches.d.ts +1212 -0
  33. package/dist/generated/prisma/models/job_batches.d.ts.map +1 -0
  34. package/dist/generated/prisma/models/job_batches.js +2 -0
  35. package/dist/generated/prisma/models/jobs.d.ts +1112 -0
  36. package/dist/generated/prisma/models/jobs.d.ts.map +1 -0
  37. package/dist/generated/prisma/models/jobs.js +2 -0
  38. package/dist/generated/prisma/models/migrations.d.ts +979 -0
  39. package/dist/generated/prisma/models/migrations.d.ts.map +1 -0
  40. package/dist/generated/prisma/models/migrations.js +2 -0
  41. package/dist/generated/prisma/models/password_reset_tokens.d.ts +941 -0
  42. package/dist/generated/prisma/models/password_reset_tokens.d.ts.map +1 -0
  43. package/dist/generated/prisma/models/password_reset_tokens.js +2 -0
  44. package/dist/generated/prisma/models/permissions.d.ts +1333 -0
  45. package/dist/generated/prisma/models/permissions.d.ts.map +1 -0
  46. package/dist/generated/prisma/models/permissions.js +2 -0
  47. package/dist/generated/prisma/models/personal_access_tokens.d.ts +1178 -0
  48. package/dist/generated/prisma/models/personal_access_tokens.d.ts.map +1 -0
  49. package/dist/generated/prisma/models/personal_access_tokens.js +2 -0
  50. package/dist/generated/prisma/models/role_permissions.d.ts +1291 -0
  51. package/dist/generated/prisma/models/role_permissions.d.ts.map +1 -0
  52. package/dist/generated/prisma/models/role_permissions.js +2 -0
  53. package/dist/generated/prisma/models/roles.d.ts +1333 -0
  54. package/dist/generated/prisma/models/roles.d.ts.map +1 -0
  55. package/dist/generated/prisma/models/roles.js +2 -0
  56. package/dist/generated/prisma/models/sessions.d.ts +1073 -0
  57. package/dist/generated/prisma/models/sessions.d.ts.map +1 -0
  58. package/dist/generated/prisma/models/sessions.js +2 -0
  59. package/dist/generated/prisma/models/user_permissions.d.ts +1291 -0
  60. package/dist/generated/prisma/models/user_permissions.d.ts.map +1 -0
  61. package/dist/generated/prisma/models/user_permissions.js +2 -0
  62. package/dist/generated/prisma/models/user_roles.d.ts +1291 -0
  63. package/dist/generated/prisma/models/user_roles.d.ts.map +1 -0
  64. package/dist/generated/prisma/models/user_roles.js +2 -0
  65. package/dist/generated/prisma/models/users.d.ts +1513 -0
  66. package/dist/generated/prisma/models/users.d.ts.map +1 -0
  67. package/dist/generated/prisma/models/users.js +2 -0
  68. package/dist/generated/prisma/models.d.ts +17 -0
  69. package/dist/generated/prisma/models.d.ts.map +1 -0
  70. package/dist/generated/prisma/models.js +2 -0
  71. package/dist/lib/bootstrap.d.ts +2 -0
  72. package/dist/lib/bootstrap.d.ts.map +1 -0
  73. package/dist/lib/bootstrap.js +133 -0
  74. package/dist/lib/core/database.d.ts +3 -0
  75. package/dist/lib/core/database.d.ts.map +1 -0
  76. package/dist/lib/core/database.js +34 -0
  77. package/dist/lib/core/realtime.d.ts +3 -0
  78. package/dist/lib/core/realtime.d.ts.map +1 -0
  79. package/dist/lib/core/realtime.js +36 -0
  80. package/dist/lib/core/redis.d.ts +8 -0
  81. package/dist/lib/core/redis.d.ts.map +1 -0
  82. package/dist/lib/core/redis.js +123 -0
  83. package/dist/lib/core/serializer.d.ts +43 -0
  84. package/dist/lib/core/serializer.d.ts.map +1 -0
  85. package/dist/lib/core/serializer.js +66 -0
  86. package/dist/lib/core/server.d.ts +2 -0
  87. package/dist/lib/core/server.d.ts.map +1 -0
  88. package/dist/lib/core/server.js +60 -0
  89. package/dist/lib/middleware/auth.d.ts +4 -0
  90. package/dist/lib/middleware/auth.d.ts.map +1 -0
  91. package/dist/lib/middleware/auth.js +55 -0
  92. package/dist/lib/middleware/error.d.ts +3 -0
  93. package/dist/lib/middleware/error.d.ts.map +1 -0
  94. package/dist/lib/middleware/error.js +60 -0
  95. package/dist/lib/middleware/multipart.d.ts +4 -0
  96. package/dist/lib/middleware/multipart.d.ts.map +1 -0
  97. package/dist/lib/middleware/multipart.js +17 -0
  98. package/dist/lib/middleware/rateLimit.d.ts +2 -0
  99. package/dist/lib/middleware/rateLimit.d.ts.map +1 -0
  100. package/dist/lib/middleware/rateLimit.js +19 -0
  101. package/dist/lib/middleware/requestLogger.d.ts +3 -0
  102. package/dist/lib/middleware/requestLogger.d.ts.map +1 -0
  103. package/dist/lib/middleware/requestLogger.js +22 -0
  104. package/dist/lib/middleware/visitor.d.ts +3 -0
  105. package/dist/lib/middleware/visitor.d.ts.map +1 -0
  106. package/dist/lib/middleware/visitor.js +144 -0
  107. package/dist/lib/utils/logger.d.ts +11 -0
  108. package/dist/lib/utils/logger.d.ts.map +1 -0
  109. package/dist/lib/utils/logger.js +81 -0
  110. package/dist/lib/utils/pagination.d.ts +19 -0
  111. package/dist/lib/utils/pagination.d.ts.map +1 -0
  112. package/dist/lib/utils/pagination.js +34 -0
  113. package/dist/lib/utils/response.d.ts +11 -0
  114. package/dist/lib/utils/response.d.ts.map +1 -0
  115. package/dist/lib/utils/response.js +57 -0
  116. package/dist/lib/utils/validator.d.ts +38 -0
  117. package/dist/lib/utils/validator.d.ts.map +1 -0
  118. package/dist/lib/utils/validator.js +369 -0
  119. package/dist/prisma/seed.d.ts +2 -0
  120. package/dist/prisma/seed.d.ts.map +1 -0
  121. package/dist/prisma/seed.js +381 -0
  122. package/dist/src/controllers/authController.d.ts +11 -0
  123. package/dist/src/controllers/authController.d.ts.map +1 -0
  124. package/dist/src/controllers/authController.js +414 -0
  125. package/dist/src/controllers/petController.d.ts +7 -0
  126. package/dist/src/controllers/petController.d.ts.map +1 -0
  127. package/dist/src/controllers/petController.js +163 -0
  128. package/dist/src/controllers/rbacController.d.ts +16 -0
  129. package/dist/src/controllers/rbacController.d.ts.map +1 -0
  130. package/dist/src/controllers/rbacController.js +437 -0
  131. package/dist/src/core/database.d.ts +3 -0
  132. package/dist/src/core/database.d.ts.map +1 -0
  133. package/dist/src/core/database.js +34 -0
  134. package/dist/src/core/realtime.d.ts +3 -0
  135. package/dist/src/core/realtime.d.ts.map +1 -0
  136. package/dist/src/core/realtime.js +36 -0
  137. package/dist/src/core/redis.d.ts +8 -0
  138. package/dist/src/core/redis.d.ts.map +1 -0
  139. package/dist/src/core/redis.js +123 -0
  140. package/dist/src/core/serializer.d.ts +43 -0
  141. package/dist/src/core/serializer.d.ts.map +1 -0
  142. package/dist/src/core/serializer.js +66 -0
  143. package/dist/src/core/server.d.ts +2 -0
  144. package/dist/src/core/server.d.ts.map +1 -0
  145. package/dist/src/core/server.js +60 -0
  146. package/dist/src/index.d.ts +2 -0
  147. package/dist/src/index.d.ts.map +1 -0
  148. package/dist/src/index.js +98 -0
  149. package/dist/src/middleware/auth.d.ts +4 -0
  150. package/dist/src/middleware/auth.d.ts.map +1 -0
  151. package/dist/src/middleware/auth.js +48 -0
  152. package/dist/src/middleware/error.d.ts +3 -0
  153. package/dist/src/middleware/error.d.ts.map +1 -0
  154. package/dist/src/middleware/error.js +60 -0
  155. package/dist/src/middleware/multipart.d.ts +4 -0
  156. package/dist/src/middleware/multipart.d.ts.map +1 -0
  157. package/dist/src/middleware/multipart.js +17 -0
  158. package/dist/src/middleware/rateLimit.d.ts +2 -0
  159. package/dist/src/middleware/rateLimit.d.ts.map +1 -0
  160. package/dist/src/middleware/rateLimit.js +19 -0
  161. package/dist/src/middleware/requestLogger.d.ts +3 -0
  162. package/dist/src/middleware/requestLogger.d.ts.map +1 -0
  163. package/dist/src/middleware/requestLogger.js +22 -0
  164. package/dist/src/middleware/visitor.d.ts +3 -0
  165. package/dist/src/middleware/visitor.d.ts.map +1 -0
  166. package/dist/src/middleware/visitor.js +144 -0
  167. package/dist/src/prisma.d.ts +3 -0
  168. package/dist/src/prisma.d.ts.map +1 -0
  169. package/dist/src/prisma.js +34 -0
  170. package/dist/src/realtime.d.ts +3 -0
  171. package/dist/src/realtime.d.ts.map +1 -0
  172. package/dist/src/realtime.js +36 -0
  173. package/dist/src/redis.d.ts +8 -0
  174. package/dist/src/redis.d.ts.map +1 -0
  175. package/dist/src/redis.js +122 -0
  176. package/dist/src/routes/auth.d.ts +2 -0
  177. package/dist/src/routes/auth.d.ts.map +1 -0
  178. package/dist/src/routes/auth.js +45 -0
  179. package/dist/src/routes/index.d.ts +2 -0
  180. package/dist/src/routes/index.d.ts.map +1 -0
  181. package/dist/src/routes/index.js +14 -0
  182. package/dist/src/routes/pets.d.ts +3 -0
  183. package/dist/src/routes/pets.d.ts.map +1 -0
  184. package/dist/src/routes/pets.js +45 -0
  185. package/dist/src/routes/rbac.d.ts +2 -0
  186. package/dist/src/routes/rbac.d.ts.map +1 -0
  187. package/dist/src/routes/rbac.js +23 -0
  188. package/dist/src/schema/auth-schema.d.ts +76 -0
  189. package/dist/src/schema/auth-schema.d.ts.map +1 -0
  190. package/dist/src/schema/auth-schema.js +63 -0
  191. package/dist/src/schema/pet-schema.d.ts +28 -0
  192. package/dist/src/schema/pet-schema.d.ts.map +1 -0
  193. package/dist/src/schema/pet-schema.js +14 -0
  194. package/dist/src/server.d.ts +2 -0
  195. package/dist/src/server.d.ts.map +1 -0
  196. package/dist/src/server.js +31 -0
  197. package/dist/src/utils/logger.d.ts +11 -0
  198. package/dist/src/utils/logger.d.ts.map +1 -0
  199. package/dist/src/utils/logger.js +81 -0
  200. package/dist/src/utils/pagination.d.ts +19 -0
  201. package/dist/src/utils/pagination.d.ts.map +1 -0
  202. package/dist/src/utils/pagination.js +34 -0
  203. package/dist/src/utils/response.d.ts +11 -0
  204. package/dist/src/utils/response.d.ts.map +1 -0
  205. package/dist/src/utils/response.js +57 -0
  206. package/dist/src/utils/validator.d.ts +38 -0
  207. package/dist/src/utils/validator.d.ts.map +1 -0
  208. package/dist/src/utils/validator.js +369 -0
  209. package/lib/bootstrap.ts +6 -0
  210. package/package.json +26 -14
  211. package/.env.example +0 -19
  212. package/doc/ARCHITECTURE_GUIDE.md +0 -73
  213. package/doc/CHANGELOG.md +0 -77
  214. package/doc/CHEATSHEET.md +0 -94
  215. package/doc/CLI.md +0 -139
  216. package/doc/CONTRIBUTING.md +0 -105
  217. package/doc/DEPLOYMENT.md +0 -122
  218. package/doc/FAQ.md +0 -81
  219. package/doc/FEATURES.md +0 -165
  220. package/doc/GETTING_STARTED.md +0 -108
  221. package/doc/INTRODUCTION.md +0 -60
  222. package/doc/PACKAGES.md +0 -66
  223. package/doc/PERFORMANCE.md +0 -91
  224. package/doc/ROADMAP.md +0 -93
  225. package/doc/SECURITY.md +0 -93
  226. package/doc/STRUCTURE.md +0 -90
  227. package/doc/TUTORIAL.md +0 -192
  228. package/docker-compose.yml +0 -24
  229. package/eslint.config.mjs +0 -26
  230. package/framework.md +0 -168
  231. package/nodemon.json +0 -6
  232. package/prisma.config.ts +0 -15
  233. package/src/controllers/authController.ts +0 -469
  234. package/src/controllers/petController.ts +0 -194
  235. package/src/controllers/rbacController.ts +0 -478
  236. package/src/models/core.prisma +0 -163
  237. package/src/models/pets.prisma +0 -9
  238. package/src/routes/auth.ts +0 -74
  239. package/src/routes/index.ts +0 -10
  240. package/src/routes/pets.ts +0 -13
  241. package/src/routes/rbac.ts +0 -42
  242. package/storage/logs/.gitkeep +0 -0
  243. package/tsconfig.json +0 -30
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Log = void 0;
7
+ const winston_1 = __importDefault(require("winston"));
8
+ require("winston-daily-rotate-file");
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const logDirectory = path_1.default.join(process.cwd(), "storage", "logs");
12
+ // Ensure log directory exists
13
+ if (!fs_1.default.existsSync(logDirectory)) {
14
+ fs_1.default.mkdirSync(logDirectory, { recursive: true });
15
+ }
16
+ const dailyRotateFileTransport = new winston_1.default.transports.DailyRotateFile({
17
+ filename: "lapeh-%DATE%.log",
18
+ dirname: logDirectory,
19
+ datePattern: "YYYY-MM-DD",
20
+ zippedArchive: true,
21
+ maxSize: "20m",
22
+ maxFiles: "3d",
23
+ format: winston_1.default.format.combine(winston_1.default.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), winston_1.default.format.printf((info) => {
24
+ let log = `[${info.timestamp}] ${info.level.toUpperCase()}: ${info.message}`;
25
+ // Handle metadata (errors, etc)
26
+ const { timestamp, level, message, service, ...meta } = info;
27
+ if (Object.keys(meta).length > 0) {
28
+ // If meta has 'errors', nicely format it
29
+ if (meta.errors) {
30
+ log += `\nErrors: ${JSON.stringify(meta.errors, null, 2)}`;
31
+ delete meta.errors;
32
+ }
33
+ // If there are other meta properties remaining, log them
34
+ if (Object.keys(meta).length > 0 && !meta.stack && !meta.error) {
35
+ log += `\nMeta: ${JSON.stringify(meta)}`;
36
+ }
37
+ }
38
+ if (info.stack) {
39
+ log += `\n${info.stack}`;
40
+ }
41
+ else if (info.error && info.error.stack) {
42
+ log += `\n${info.error.stack}`;
43
+ }
44
+ return log;
45
+ })),
46
+ });
47
+ const logger = winston_1.default.createLogger({
48
+ level: process.env.LOG_LEVEL || "info",
49
+ format: winston_1.default.format.combine(winston_1.default.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), winston_1.default.format.errors({ stack: true }), winston_1.default.format.splat(), winston_1.default.format.json()),
50
+ defaultMeta: { service: "lapeh-service" },
51
+ transports: [
52
+ // Write all logs with importance level of `error` or less to `error.log`
53
+ // new winston.transports.File({ filename: 'error.log', level: 'error' }),
54
+ // Write all logs to `combined.log`
55
+ // new winston.transports.File({ filename: 'combined.log' }),
56
+ dailyRotateFileTransport,
57
+ ],
58
+ });
59
+ // If we're not in production then log to the `console` with the format:
60
+ // `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
61
+ if (process.env.NODE_ENV !== "production") {
62
+ logger.add(new winston_1.default.transports.Console({
63
+ format: winston_1.default.format.combine(winston_1.default.format.colorize(), winston_1.default.format.simple()),
64
+ }));
65
+ }
66
+ class Log {
67
+ static info(message, meta) {
68
+ logger.info(message, meta);
69
+ }
70
+ static error(message, meta) {
71
+ logger.error(message, meta);
72
+ }
73
+ static warn(message, meta) {
74
+ logger.warn(message, meta);
75
+ }
76
+ static debug(message, meta) {
77
+ logger.debug(message, meta);
78
+ }
79
+ }
80
+ exports.Log = Log;
81
+ exports.default = logger;
@@ -0,0 +1,19 @@
1
+ export type PaginationQuery = {
2
+ page?: string | string[] | number;
3
+ per_page?: string | string[] | number;
4
+ };
5
+ export type PaginationParams = {
6
+ page: number;
7
+ perPage: number;
8
+ skip: number;
9
+ take: number;
10
+ };
11
+ export type PaginationMeta = {
12
+ page: number;
13
+ perPage: number;
14
+ total: number;
15
+ lastPage: number;
16
+ };
17
+ export declare function getPagination(query: PaginationQuery): PaginationParams;
18
+ export declare function buildPaginationMeta(page: number, perPage: number, total: number): PaginationMeta;
19
+ //# sourceMappingURL=pagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../../src/utils/pagination.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAmBF,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAStE;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,cAAc,CAGhB"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPagination = getPagination;
4
+ exports.buildPaginationMeta = buildPaginationMeta;
5
+ function toNumber(value) {
6
+ if (Array.isArray(value)) {
7
+ if (value.length === 0)
8
+ return undefined;
9
+ return toNumber(value[0]);
10
+ }
11
+ if (typeof value === "number") {
12
+ return value;
13
+ }
14
+ if (typeof value === "string") {
15
+ const n = parseInt(value, 10);
16
+ if (!Number.isNaN(n)) {
17
+ return n;
18
+ }
19
+ }
20
+ return undefined;
21
+ }
22
+ function getPagination(query) {
23
+ const pageRaw = toNumber(query.page);
24
+ const perPageRaw = toNumber(query.per_page);
25
+ const page = pageRaw && pageRaw > 0 ? pageRaw : 1;
26
+ const perPage = perPageRaw && perPageRaw > 0 && perPageRaw <= 100 ? perPageRaw : 10;
27
+ const skip = (page - 1) * perPage;
28
+ const take = perPage;
29
+ return { page, perPage, skip, take };
30
+ }
31
+ function buildPaginationMeta(page, perPage, total) {
32
+ const lastPage = total === 0 ? 1 : Math.ceil(total / perPage);
33
+ return { page, perPage, total, lastPage };
34
+ }
@@ -0,0 +1,11 @@
1
+ import { Response } from "express";
2
+ export declare function sendSuccess<T>(res: Response, statusCode: number, message: string, data: T): Response<any, Record<string, any>>;
3
+ /**
4
+ * Mengirim response sukses dengan performa tinggi menggunakan Schema Serialization (Fastify-style).
5
+ * Melewati proses JSON.stringify standar yang lambat.
6
+ *
7
+ * @param serializer Fungsi serializer yang sudah dicompile dari src/core/serializer
8
+ */
9
+ export declare function sendFastSuccess(res: Response, statusCode: number, serializer: (doc: any) => string, data: any): Response<any, Record<string, any>>;
10
+ export declare function sendError<T = unknown>(res: Response, statusCode: number, message: string, errors?: T): Response<any, Record<string, any>>;
11
+ //# sourceMappingURL=response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../../src/utils/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAsCnC,wBAAgB,WAAW,CAAC,CAAC,EAC3B,GAAG,EAAE,QAAQ,EACb,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,CAAC,sCAIR;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,QAAQ,EACb,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,EAChC,IAAI,EAAE,GAAG,sCASV;AAED,wBAAgB,SAAS,CAAC,CAAC,GAAG,OAAO,EACnC,GAAG,EAAE,QAAQ,EACb,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,CAAC,sCAcX"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendSuccess = sendSuccess;
4
+ exports.sendFastSuccess = sendFastSuccess;
5
+ exports.sendError = sendError;
6
+ const logger_1 = require("../utils/logger");
7
+ function toJsonSafe(value) {
8
+ if (value instanceof Date) {
9
+ return value.toISOString();
10
+ }
11
+ if (typeof value === "bigint") {
12
+ return value.toString();
13
+ }
14
+ if (Array.isArray(value)) {
15
+ return value.map((item) => toJsonSafe(item));
16
+ }
17
+ if (value && typeof value === "object") {
18
+ const result = {};
19
+ for (const [key, val] of Object.entries(value)) {
20
+ result[key] = toJsonSafe(val);
21
+ }
22
+ return result;
23
+ }
24
+ return value;
25
+ }
26
+ function sendSuccess(res, statusCode, message, data) {
27
+ const body = { status: "success", message, data };
28
+ return res.status(statusCode).json(toJsonSafe(body));
29
+ }
30
+ /**
31
+ * Mengirim response sukses dengan performa tinggi menggunakan Schema Serialization (Fastify-style).
32
+ * Melewati proses JSON.stringify standar yang lambat.
33
+ *
34
+ * @param serializer Fungsi serializer yang sudah dicompile dari src/core/serializer
35
+ */
36
+ function sendFastSuccess(res, statusCode, serializer, data) {
37
+ // Set header manual karena kita mengirim raw string
38
+ res.setHeader("Content-Type", "application/json");
39
+ res.status(statusCode);
40
+ // Serializer mengembalikan string JSON
41
+ const jsonString = serializer(data);
42
+ return res.send(jsonString);
43
+ }
44
+ function sendError(res, statusCode, message, errors) {
45
+ // Log the error
46
+ if (statusCode >= 500) {
47
+ logger_1.Log.error(message, { statusCode, errors });
48
+ }
49
+ else if (statusCode >= 400) {
50
+ logger_1.Log.warn(message, { statusCode, errors });
51
+ }
52
+ const body = { status: "error", message };
53
+ if (errors !== undefined) {
54
+ body.errors = errors;
55
+ }
56
+ return res.status(statusCode).json(toJsonSafe(body));
57
+ }
@@ -0,0 +1,38 @@
1
+ import { ZodSchema } from "zod";
2
+ export declare class Validator {
3
+ private data;
4
+ private schema;
5
+ private customMessages;
6
+ private result;
7
+ private errorResult;
8
+ private hasRun;
9
+ constructor(data: any, schema: ZodSchema<any> | Record<string, any>, messages?: Record<string, string>);
10
+ /**
11
+ * Create a new Validator instance
12
+ * @param data The input data to validate
13
+ * @param schema Zod schema or object of Zod schemas / Laravel-style rules
14
+ * @param messages Optional custom error messages (Laravel style: 'field.rule' => 'message')
15
+ */
16
+ static make(data: any, schema: ZodSchema<any> | Record<string, any>, messages?: Record<string, string>): Validator;
17
+ private static parseLaravelRule;
18
+ /**
19
+ * Check if validation fails
20
+ */
21
+ fails(): Promise<boolean>;
22
+ /**
23
+ * Check if validation passes
24
+ */
25
+ passes(): Promise<boolean>;
26
+ /**
27
+ * Get the validation errors
28
+ */
29
+ errors(): any;
30
+ /**
31
+ * Get the validated data
32
+ */
33
+ validated(): Promise<any>;
34
+ private run;
35
+ private formatErrors;
36
+ private getCustomMessage;
37
+ }
38
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/utils/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,SAAS,EAAsB,MAAM,KAAK,CAAC;AAGvD,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAkB;gBAG9B,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5C,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAavC;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CACT,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5C,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAiDvC,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAyN/B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAK/B;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAIhC;;OAEG;IACH,MAAM;IAKN;;OAEG;IACG,SAAS;YAUD,GAAG;IAgBjB,OAAO,CAAC,YAAY;IAuBpB,OAAO,CAAC,gBAAgB;CAoDzB"}
@@ -0,0 +1,369 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Validator = void 0;
4
+ const zod_1 = require("zod");
5
+ const database_1 = require("../core/database");
6
+ class Validator {
7
+ constructor(data, schema, messages = {}) {
8
+ this.result = null;
9
+ this.errorResult = null;
10
+ this.hasRun = false;
11
+ this.data = data;
12
+ this.customMessages = messages;
13
+ // If it's a raw object, wrap it in z.object()
14
+ if (schema instanceof zod_1.ZodSchema) {
15
+ this.schema = schema;
16
+ }
17
+ else {
18
+ this.schema = zod_1.z.object(schema);
19
+ }
20
+ }
21
+ /**
22
+ * Create a new Validator instance
23
+ * @param data The input data to validate
24
+ * @param schema Zod schema or object of Zod schemas / Laravel-style rules
25
+ * @param messages Optional custom error messages (Laravel style: 'field.rule' => 'message')
26
+ */
27
+ static make(data, schema, messages = {}) {
28
+ if (schema instanceof zod_1.ZodSchema) {
29
+ return new Validator(data, schema, messages);
30
+ }
31
+ const parsedSchema = {};
32
+ const sameRules = [];
33
+ for (const [key, rule] of Object.entries(schema)) {
34
+ if (rule instanceof zod_1.ZodSchema) {
35
+ parsedSchema[key] = rule;
36
+ }
37
+ else if (typeof rule === "string" || Array.isArray(rule)) {
38
+ const ruleStr = Array.isArray(rule) ? rule.join("|") : rule;
39
+ // Check for 'same:target'
40
+ const parts = ruleStr.split("|");
41
+ for (const part of parts) {
42
+ const [name, args] = part.split(":");
43
+ if (name === "same" && args) {
44
+ sameRules.push({ field: key, target: args });
45
+ }
46
+ }
47
+ parsedSchema[key] = Validator.parseLaravelRule(rule);
48
+ }
49
+ else {
50
+ parsedSchema[key] = rule;
51
+ }
52
+ }
53
+ let objectSchema = zod_1.z.object(parsedSchema);
54
+ if (sameRules.length > 0) {
55
+ objectSchema = objectSchema.superRefine((val, ctx) => {
56
+ sameRules.forEach(({ field, target }) => {
57
+ if (val[field] !== val[target]) {
58
+ ctx.addIssue({
59
+ code: zod_1.z.ZodIssueCode.custom,
60
+ message: `The ${field} and ${target} must match.`,
61
+ path: [field],
62
+ });
63
+ }
64
+ });
65
+ });
66
+ }
67
+ return new Validator(data, objectSchema, messages);
68
+ }
69
+ static parseLaravelRule(rule) {
70
+ const rules = Array.isArray(rule)
71
+ ? rule
72
+ : rule.split("|").map((r) => r.trim());
73
+ let schema = zod_1.z.string(); // Default base
74
+ let isOptional = false;
75
+ let isNullable = false;
76
+ // 1. Determine Base Type
77
+ const isFile = rules.includes("file") ||
78
+ rules.includes("image") ||
79
+ rules.includes("mimes");
80
+ if (rules.includes("numeric") || rules.includes("integer")) {
81
+ schema = zod_1.z.coerce.number();
82
+ if (rules.includes("integer"))
83
+ schema = schema.int();
84
+ }
85
+ else if (rules.includes("boolean")) {
86
+ schema = zod_1.z.boolean();
87
+ }
88
+ else if (rules.includes("array")) {
89
+ schema = zod_1.z.array(zod_1.z.any());
90
+ }
91
+ else if (rules.includes("date")) {
92
+ schema = zod_1.z.coerce.date();
93
+ }
94
+ else if (isFile) {
95
+ schema = zod_1.z.any().refine((val) => {
96
+ // Check if it looks like a Multer file object
97
+ return (typeof val === "object" &&
98
+ val !== null &&
99
+ "fieldname" in val &&
100
+ "originalname" in val &&
101
+ "encoding" in val &&
102
+ "mimetype" in val &&
103
+ "size" in val);
104
+ }, { message: "The field must be a file." });
105
+ }
106
+ else {
107
+ schema = zod_1.z.string();
108
+ }
109
+ // 2. Constraints & Modifiers
110
+ for (const r of rules) {
111
+ const [name, args] = r.split(":");
112
+ const params = args ? args.split(",") : [];
113
+ switch (name) {
114
+ case "email":
115
+ if (schema instanceof zod_1.z.ZodString)
116
+ schema = schema.email();
117
+ break;
118
+ case "url":
119
+ if (schema instanceof zod_1.z.ZodString)
120
+ schema = schema.url();
121
+ break;
122
+ case "uuid":
123
+ if (schema instanceof zod_1.z.ZodString)
124
+ schema = schema.uuid();
125
+ break;
126
+ case "min":
127
+ if (!isNaN(Number(params[0])))
128
+ schema = schema.min(Number(params[0]));
129
+ break;
130
+ case "max":
131
+ if (!isNaN(Number(params[0]))) {
132
+ if (isFile) {
133
+ schema = schema.refine((val) => {
134
+ if (!val)
135
+ return true; // Let nullable/optional handle nulls
136
+ // But wait, if we are in strict mode (not nullable), we should fail?
137
+ // No, max() usually doesn't enforce existence. required() does.
138
+ // But here val is the file object.
139
+ // If val is missing, and it's required, Base Type would have failed (if I revert Base Type).
140
+ // So here val is likely a file object.
141
+ return val.size <= Number(params[0]) * 1024;
142
+ }, {
143
+ message: `The file may not be greater than ${params[0]} kilobytes.`,
144
+ });
145
+ }
146
+ else {
147
+ schema = schema.max(Number(params[0]));
148
+ }
149
+ }
150
+ break;
151
+ case "image":
152
+ schema = schema.refine((val) => {
153
+ if (!val || typeof val !== "object" || !val.mimetype)
154
+ return false;
155
+ return val.mimetype.startsWith("image/");
156
+ }, { message: "The field must be an image." });
157
+ break;
158
+ case "in":
159
+ if (params.length > 0) {
160
+ // For strings, we can use regex or refine
161
+ schema = schema.refine((val) => params.includes(String(val)), {
162
+ message: `The selected ${name} is invalid.`,
163
+ });
164
+ }
165
+ break;
166
+ case "not_in":
167
+ if (params.length > 0) {
168
+ schema = schema.refine((val) => !params.includes(String(val)), {
169
+ message: `The selected ${name} is invalid.`,
170
+ });
171
+ }
172
+ break;
173
+ case "alpha":
174
+ if (schema instanceof zod_1.z.ZodString)
175
+ schema = schema.regex(/^[a-zA-Z]+$/, "The field must only contain letters.");
176
+ break;
177
+ case "alpha_dash":
178
+ if (schema instanceof zod_1.z.ZodString)
179
+ schema = schema.regex(/^[a-zA-Z0-9_-]+$/, "The field must only contain letters, numbers, dashes, and underscores.");
180
+ break;
181
+ case "alpha_num":
182
+ if (schema instanceof zod_1.z.ZodString)
183
+ schema = schema.regex(/^[a-zA-Z0-9]+$/, "The field must only contain letters and numbers.");
184
+ break;
185
+ case "mimes":
186
+ // For file validation
187
+ schema = schema.refine((val) => {
188
+ if (!val || typeof val !== "object" || !val.mimetype)
189
+ return false;
190
+ // params are extensions (jpg, png). We need to map to mimetypes or check loosely.
191
+ // Simplification: check if mimetype includes one of the params
192
+ // Or better: map extensions to mimetypes? Too big.
193
+ // Let's assume params are extensions, and we check if mimetype matches.
194
+ // Actually, typically mimes:jpg,png means we check extension or mime.
195
+ // For now, let's just check if mimetype contains the string (imperfect but simple)
196
+ // OR check originalname extension.
197
+ const extension = val.originalname
198
+ ?.split(".")
199
+ .pop()
200
+ ?.toLowerCase();
201
+ return params.includes(extension);
202
+ }, {
203
+ message: `The file must be a file of type: ${params.join(", ")}.`,
204
+ });
205
+ break;
206
+ case "unique":
207
+ // unique:table,column,ignore,idColumn
208
+ const [table, column = "id", ignoreValue, ignoreColumn = "id"] = params;
209
+ schema = schema.refine(async (val) => {
210
+ if (!val)
211
+ return true;
212
+ const where = { [column]: val };
213
+ if (ignoreValue && ignoreValue !== "null") {
214
+ // Try to handle numeric IDs if ignoreValue looks numeric
215
+ const ignoreVal = !isNaN(Number(ignoreValue))
216
+ ? Number(ignoreValue)
217
+ : ignoreValue;
218
+ // But Prisma uses BigInt for IDs often in this project?
219
+ // Let's assume string or number is fine, user can cast if needed.
220
+ // In this project, IDs are BigInt.
221
+ if (typeof ignoreVal === "number" ||
222
+ /^\d+$/.test(String(ignoreValue))) {
223
+ where[ignoreColumn] = { not: BigInt(ignoreValue) };
224
+ }
225
+ else {
226
+ where[ignoreColumn] = { not: ignoreValue };
227
+ }
228
+ }
229
+ try {
230
+ // @ts-ignore
231
+ const count = await database_1.prisma[table].count({ where });
232
+ return count === 0;
233
+ }
234
+ catch (e) {
235
+ console.error(`Validator unique check failed for table ${table}:`, e);
236
+ return false;
237
+ }
238
+ }, { message: `The ${column} has already been taken.` });
239
+ break;
240
+ }
241
+ }
242
+ // 3. Modifiers
243
+ const isRequired = rules.includes("required");
244
+ isNullable = rules.includes("nullable");
245
+ isOptional = rules.includes("sometimes") || !isRequired;
246
+ if (isNullable) {
247
+ schema = schema.nullable();
248
+ }
249
+ if (isOptional) {
250
+ schema = schema.optional();
251
+ }
252
+ return schema;
253
+ }
254
+ /**
255
+ * Check if validation fails
256
+ */
257
+ async fails() {
258
+ await this.run();
259
+ return this.errorResult !== null;
260
+ }
261
+ /**
262
+ * Check if validation passes
263
+ */
264
+ async passes() {
265
+ return !(await this.fails());
266
+ }
267
+ /**
268
+ * Get the validation errors
269
+ */
270
+ errors() {
271
+ // Should be called after fails()
272
+ return this.errorResult || {};
273
+ }
274
+ /**
275
+ * Get the validated data
276
+ */
277
+ async validated() {
278
+ await this.run();
279
+ if (this.errorResult) {
280
+ throw new Error("Validation failed. Check errors() before calling validated().");
281
+ }
282
+ return this.result;
283
+ }
284
+ async run() {
285
+ if (this.hasRun)
286
+ return;
287
+ this.hasRun = true;
288
+ // safeParseAsync to handle async refinements (like unique)
289
+ const parseResult = await this.schema.safeParseAsync(this.data);
290
+ if (parseResult.success) {
291
+ this.result = parseResult.data;
292
+ this.errorResult = null;
293
+ }
294
+ else {
295
+ this.result = null;
296
+ this.errorResult = this.formatErrors(parseResult.error);
297
+ }
298
+ }
299
+ formatErrors(error) {
300
+ // If we have custom messages, we try to apply them
301
+ if (Object.keys(this.customMessages).length > 0) {
302
+ error.issues = error.issues.map((issue) => {
303
+ const message = this.getCustomMessage(issue);
304
+ if (message) {
305
+ return { ...issue, message };
306
+ }
307
+ return issue;
308
+ });
309
+ }
310
+ const flattened = error.flatten();
311
+ const errors = flattened.fieldErrors;
312
+ // Add formErrors if any
313
+ if (flattened.formErrors.length > 0) {
314
+ errors.formErrors = flattened.formErrors;
315
+ }
316
+ return errors;
317
+ }
318
+ getCustomMessage(issue) {
319
+ const path = issue.path.join(".");
320
+ let rule = "";
321
+ // Map Zod issue codes to "Laravel-like" rules for key lookup
322
+ switch (issue.code) {
323
+ case "invalid_type":
324
+ if (issue.received === "undefined" || issue.received === "null") {
325
+ rule = "required";
326
+ }
327
+ else {
328
+ // e.g. string, number, boolean
329
+ rule = issue.expected;
330
+ }
331
+ break;
332
+ case "invalid_string":
333
+ if (typeof issue.validation === "string") {
334
+ rule = issue.validation; // email, url, uuid, etc.
335
+ }
336
+ else {
337
+ rule = "string";
338
+ }
339
+ break;
340
+ case "too_small":
341
+ rule = "min";
342
+ break;
343
+ case "too_big":
344
+ rule = "max";
345
+ break;
346
+ case "custom":
347
+ // Custom rules often have messages already, but we can override
348
+ // For unique, mimes, etc.
349
+ // We might need to inspect the message to guess the rule?
350
+ // Or just use the path.
351
+ rule = "custom";
352
+ break;
353
+ // Add more mappings as needed
354
+ default:
355
+ rule = issue.code;
356
+ }
357
+ // Try keys: "field.rule" -> "field"
358
+ const specificKey = `${path}.${rule}`;
359
+ if (this.customMessages[specificKey]) {
360
+ return this.customMessages[specificKey];
361
+ }
362
+ // Fallback for generic field message "field"
363
+ if (this.customMessages[path]) {
364
+ return this.customMessages[path];
365
+ }
366
+ return undefined;
367
+ }
368
+ }
369
+ exports.Validator = Validator;
package/lib/bootstrap.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import dotenv from "dotenv";
2
2
  dotenv.config();
3
3
 
4
+ import moduleAlias from "module-alias";
4
5
  import express, { Request, Response, NextFunction } from "express";
5
6
  import cors from "cors";
6
7
  import helmet from "helmet";
@@ -17,6 +18,11 @@ import { requestLogger } from "./middleware/requestLogger";
17
18
  import { sendSuccess } from "./utils/response";
18
19
 
19
20
  export async function bootstrap() {
21
+ // Register aliases for production runtime
22
+ // Since user code (compiled JS) uses require('@lapeh/...')
23
+ // We map '@lapeh' to the directory containing this file (lib/ or dist/lib/)
24
+ moduleAlias.addAlias("@lapeh", __dirname);
25
+
20
26
  // Validasi Environment Variables
21
27
  const requiredEnvs = ["DATABASE_URL", "JWT_SECRET"];
22
28
  const missingEnvs = requiredEnvs.filter((key) => !process.env[key]);