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,60 @@
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.app = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const cors_1 = __importDefault(require("cors"));
9
+ const helmet_1 = __importDefault(require("helmet"));
10
+ const compression_1 = __importDefault(require("compression"));
11
+ // import { apiRouter } from "../../src/routes"; // Routes are now loaded dynamically in bootstrap.ts
12
+ const visitor_1 = require("../middleware/visitor");
13
+ // import { errorHandler } from "../middleware/error";
14
+ const rateLimit_1 = require("../middleware/rateLimit");
15
+ const requestLogger_1 = require("../middleware/requestLogger");
16
+ const response_1 = require("../utils/response");
17
+ exports.app = (0, express_1.default)();
18
+ exports.app.disable("x-powered-by");
19
+ // Compression (Gzip)
20
+ exports.app.use((0, compression_1.default)());
21
+ // Request Timeout Middleware (30s)
22
+ exports.app.use((_req, res, next) => {
23
+ res.setTimeout(30000, () => {
24
+ res.status(408).send({
25
+ status: "error",
26
+ message: "Request Timeout (30s limit)",
27
+ });
28
+ });
29
+ next();
30
+ });
31
+ // Security Headers
32
+ exports.app.use((0, helmet_1.default)({
33
+ contentSecurityPolicy: false, // Disarankan true jika menggunakan frontend di domain yang sama
34
+ crossOriginResourcePolicy: { policy: "cross-origin" },
35
+ }));
36
+ const corsOrigin = process.env.CORS_ORIGIN || "*";
37
+ exports.app.use((0, cors_1.default)({
38
+ origin: corsOrigin,
39
+ credentials: true,
40
+ exposedHeaders: ["x-access-token", "x-access-expires-at"],
41
+ }));
42
+ // Logging & Parsing
43
+ exports.app.use(requestLogger_1.requestLogger);
44
+ exports.app.use(express_1.default.json({ limit: "10mb" })); // Limit dinaikkan untuk upload file base64/besar
45
+ exports.app.use(express_1.default.urlencoded({ extended: true, limit: "10mb" }));
46
+ // Rate Limiting (Global)
47
+ exports.app.use(rateLimit_1.apiLimiter);
48
+ exports.app.use(visitor_1.visitorCounter);
49
+ // Health Check Endpoint
50
+ exports.app.get("/", (_req, res) => {
51
+ (0, response_1.sendSuccess)(res, 200, "Lapeh API is running", {
52
+ status: "active",
53
+ timestamp: new Date(),
54
+ version: process.env.npm_package_version || "2.1.6",
55
+ });
56
+ });
57
+ // Routes are loaded in bootstrap.ts via app.use('/api', userApiRouter)
58
+ // Global Error Handler
59
+ // Note: We don't attach error handler here because we want to attach it AFTER routes are loaded in bootstrap
60
+ // app.use(errorHandler);
@@ -0,0 +1,4 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ export declare function requireAuth(req: Request, res: Response, next: NextFunction): void;
3
+ export declare function requireAdmin(req: Request, res: Response, next: NextFunction): void;
4
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../lib/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAY1D,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAmC1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAa3E"}
@@ -0,0 +1,55 @@
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.requireAuth = requireAuth;
7
+ exports.requireAdmin = requireAdmin;
8
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
9
+ const response_1 = require("../utils/response");
10
+ // Note: We should ideally avoid importing from controllers in middleware
11
+ // But for now we'll keep it to maintain functionality, but point to src if needed
12
+ // However, authController is in src (user land) or lib?
13
+ // Wait, authController was NOT moved to lib. It is in src/controllers.
14
+ // So this import will fail if we use relative paths.
15
+ // But we are in lib.
16
+ // We should probably move ACCESS_TOKEN_EXPIRES_IN_SECONDS to a config or constants file in lib.
17
+ const ACCESS_TOKEN_EXPIRES_IN_SECONDS = 7 * 24 * 60 * 60;
18
+ function requireAuth(req, res, next) {
19
+ const header = req.headers.authorization;
20
+ if (!header || !header.startsWith("Bearer ")) {
21
+ (0, response_1.sendError)(res, 401, "Unauthorized");
22
+ return;
23
+ }
24
+ const token = header.slice(7);
25
+ const secret = process.env.JWT_SECRET;
26
+ if (!secret) {
27
+ (0, response_1.sendError)(res, 500, "Server misconfigured");
28
+ return;
29
+ }
30
+ try {
31
+ const payload = jsonwebtoken_1.default.verify(token, secret);
32
+ req.user = { userId: payload.userId, role: payload.role };
33
+ const accessExpiresInSeconds = ACCESS_TOKEN_EXPIRES_IN_SECONDS;
34
+ const accessExpiresAt = new Date(Date.now() + accessExpiresInSeconds * 1000).toISOString();
35
+ const newToken = jsonwebtoken_1.default.sign({ userId: payload.userId, role: payload.role }, secret, { expiresIn: accessExpiresInSeconds });
36
+ res.setHeader("x-access-token", newToken);
37
+ res.setHeader("x-access-expires-at", accessExpiresAt);
38
+ next();
39
+ }
40
+ catch (err) {
41
+ (0, response_1.sendError)(res, 401, "Invalid token");
42
+ }
43
+ }
44
+ function requireAdmin(req, res, next) {
45
+ const user = req.user;
46
+ if (!user) {
47
+ (0, response_1.sendError)(res, 401, "Unauthorized");
48
+ return;
49
+ }
50
+ if (user.role !== "admin" && user.role !== "super_admin") {
51
+ (0, response_1.sendError)(res, 403, "Forbidden");
52
+ return;
53
+ }
54
+ next();
55
+ }
@@ -0,0 +1,3 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ export declare function errorHandler(err: any, req: Request, res: Response, _next: NextFunction): Response<any, Record<string, any>>;
3
+ //# sourceMappingURL=error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../../lib/middleware/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAM1D,wBAAgB,YAAY,CAC1B,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,YAAY,sCA2DpB"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.errorHandler = errorHandler;
4
+ const zod_1 = require("zod");
5
+ const client_1 = require("@prisma/client");
6
+ const response_1 = require("../utils/response");
7
+ const logger_1 = require("../utils/logger");
8
+ function errorHandler(err, req, res, _next) {
9
+ // 1. Zod Validation Error
10
+ if (err instanceof zod_1.ZodError) {
11
+ const formattedErrors = err.errors.map((e) => ({
12
+ field: e.path.join("."),
13
+ message: e.message,
14
+ }));
15
+ return (0, response_1.sendError)(res, 400, "Validation Error", formattedErrors);
16
+ }
17
+ // 2. Prisma Errors
18
+ if (err instanceof client_1.Prisma.PrismaClientKnownRequestError) {
19
+ // P2002: Unique constraint failed
20
+ if (err.code === "P2002") {
21
+ const target = err.meta?.target || [];
22
+ const fields = target.length > 0 ? target.join(", ") : "field";
23
+ return (0, response_1.sendError)(res, 409, `Unique constraint failed on: ${fields}`);
24
+ }
25
+ // P2003: Foreign key constraint failed
26
+ if (err.code === "P2003") {
27
+ const field = err.meta?.field_name || "unknown field";
28
+ return (0, response_1.sendError)(res, 400, `Foreign key constraint failed on: ${field}`);
29
+ }
30
+ // P2025: Record not found
31
+ if (err.code === "P2025") {
32
+ return (0, response_1.sendError)(res, 404, "Record not found");
33
+ }
34
+ }
35
+ // 3. JWT Errors
36
+ if (err.name === "JsonWebTokenError") {
37
+ return (0, response_1.sendError)(res, 401, "Invalid token");
38
+ }
39
+ if (err.name === "TokenExpiredError") {
40
+ return (0, response_1.sendError)(res, 401, "Token expired");
41
+ }
42
+ // 4. Syntax Error (JSON body parsing)
43
+ if (err instanceof SyntaxError && "body" in err) {
44
+ return (0, response_1.sendError)(res, 400, "Invalid JSON format");
45
+ }
46
+ // 5. Default / Custom Error
47
+ const code = err.statusCode || 500;
48
+ const msg = err.message || "Internal Server Error";
49
+ // Log error (file log for production, console for dev)
50
+ if (code === 500) {
51
+ logger_1.Log.error(msg, {
52
+ error: err,
53
+ path: req.path,
54
+ method: req.method,
55
+ ip: req.ip,
56
+ stack: err.stack,
57
+ });
58
+ }
59
+ return (0, response_1.sendError)(res, code, msg);
60
+ }
@@ -0,0 +1,4 @@
1
+ import multer from "multer";
2
+ export declare const parseMultipart: import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
3
+ export declare const upload: multer.Multer;
4
+ //# sourceMappingURL=multipart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multipart.d.ts","sourceRoot":"","sources":["../../../lib/middleware/multipart.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B,eAAO,MAAM,cAAc,8IAAkB,CAAC;AAI9C,eAAO,MAAM,MAAM,eAKjB,CAAC"}
@@ -0,0 +1,17 @@
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.upload = exports.parseMultipart = void 0;
7
+ const multer_1 = __importDefault(require("multer"));
8
+ // Middleware for parsing multipart/form-data (text fields only)
9
+ exports.parseMultipart = (0, multer_1.default)().none();
10
+ // Middleware for parsing multipart/form-data with files
11
+ // You can configure storage/limits here as needed
12
+ exports.upload = (0, multer_1.default)({
13
+ dest: "storage/uploads/",
14
+ limits: {
15
+ fileSize: 5 * 1024 * 1024, // 5MB
16
+ },
17
+ });
@@ -0,0 +1,2 @@
1
+ export declare const apiLimiter: import("express-rate-limit").RateLimitRequestHandler;
2
+ //# sourceMappingURL=rateLimit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../../../lib/middleware/rateLimit.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,UAAU,sDASrB,CAAC"}
@@ -0,0 +1,19 @@
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.apiLimiter = void 0;
7
+ const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
8
+ // import { redis } from "../core/redis"; // Optional: Use Redis for distributed rate limiting
9
+ // Rate limiting untuk mencegah brute force dan DDoS ringan
10
+ exports.apiLimiter = (0, express_rate_limit_1.default)({
11
+ windowMs: 15 * 60 * 1000, // 15 menit
12
+ max: 100, // Batas 100 request per window per IP
13
+ standardHeaders: true, // Return rate limit info di `RateLimit-*` headers
14
+ legacyHeaders: false, // Disable `X-RateLimit-*` headers
15
+ message: {
16
+ success: false,
17
+ message: "Terlalu banyak permintaan, silakan coba lagi nanti.",
18
+ },
19
+ });
@@ -0,0 +1,3 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ export declare const requestLogger: (req: Request, res: Response, next: NextFunction) => void;
3
+ //# sourceMappingURL=requestLogger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requestLogger.d.ts","sourceRoot":"","sources":["../../../lib/middleware/requestLogger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG1D,eAAO,MAAM,aAAa,GACxB,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,MAAM,YAAY,SAoBnB,CAAC"}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requestLogger = void 0;
4
+ const logger_1 = require("../utils/logger");
5
+ const requestLogger = (req, res, next) => {
6
+ const start = Date.now();
7
+ const { method, url, ip } = req;
8
+ // Log saat response selesai
9
+ res.on("finish", () => {
10
+ const duration = Date.now() - start;
11
+ const { statusCode } = res;
12
+ const message = `${method} ${url} ${statusCode} - ${duration}ms - ${ip}`;
13
+ if (statusCode >= 400) {
14
+ logger_1.Log.warn(message);
15
+ }
16
+ else {
17
+ logger_1.Log.info(message);
18
+ }
19
+ });
20
+ next();
21
+ };
22
+ exports.requestLogger = requestLogger;
@@ -0,0 +1,3 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ export declare function visitorCounter(req: Request, res: Response, next: NextFunction): Promise<void>;
3
+ //# sourceMappingURL=visitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor.d.ts","sourceRoot":"","sources":["../../../lib/middleware/visitor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AA0C1D,wBAAsB,cAAc,CAClC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,iBAoInB"}
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.visitorCounter = visitorCounter;
4
+ const uuid_1 = require("uuid");
5
+ const redis_1 = require("../core/redis");
6
+ const memoryStats = new Map();
7
+ const globalVisitors = new Set();
8
+ function formatDateKey(d) {
9
+ const dd = String(d.getDate()).padStart(2, "0");
10
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
11
+ const yyyy = d.getFullYear();
12
+ return `${dd}-${mm}-${yyyy}`;
13
+ }
14
+ function parseCookies(header) {
15
+ const cookies = {};
16
+ if (!header)
17
+ return cookies;
18
+ const parts = header.split(";");
19
+ for (const part of parts) {
20
+ const [k, v] = part.split("=").map((s) => s.trim());
21
+ if (k && v)
22
+ cookies[k] = decodeURIComponent(v);
23
+ }
24
+ return cookies;
25
+ }
26
+ function isMobileUserAgent(ua) {
27
+ if (!ua)
28
+ return false;
29
+ return /Mobile|Android|iPhone|iPad|iPod/i.test(ua);
30
+ }
31
+ async function visitorCounter(req, res, next) {
32
+ const now = new Date();
33
+ const dateKey = formatDateKey(now);
34
+ const ip = req.ip ||
35
+ req.headers["x-forwarded-for"] ||
36
+ req.socket.remoteAddress ||
37
+ "";
38
+ const userAgent = req.headers["user-agent"];
39
+ const mobile = isMobileUserAgent(userAgent);
40
+ const cookies = parseCookies(req.headers.cookie);
41
+ let visitorId = cookies["visitor_id"];
42
+ if (!visitorId) {
43
+ visitorId = (0, uuid_1.v4)();
44
+ res.cookie("visitor_id", visitorId, {
45
+ httpOnly: true,
46
+ sameSite: "lax",
47
+ maxAge: 365 * 24 * 60 * 60 * 1000,
48
+ });
49
+ }
50
+ let sessionId = cookies["visitor_session_id"];
51
+ if (!sessionId) {
52
+ sessionId = (0, uuid_1.v4)();
53
+ res.cookie("visitor_session_id", sessionId, {
54
+ httpOnly: true,
55
+ sameSite: "lax",
56
+ });
57
+ }
58
+ if (redis_1.redis && redis_1.redis.status === "ready") {
59
+ const base = dateKey;
60
+ const kRequests = `requests-${base}`;
61
+ const kNewVisitors = `new-visitors-${base}`;
62
+ const kVisitors = `visitors-${base}`;
63
+ const kNewVisitorsMobile = `new-visitors-from-mobile-${base}`;
64
+ const kVisitorsMobile = `visitors-from-mobile-${base}`;
65
+ const kIpAddresses = `ip-addresses-${base}`;
66
+ const kSessions = `sessions-${base}`;
67
+ const kVisitorsSet = `visitors-set-${base}`;
68
+ const kVisitorsMobileSet = `visitors-from-mobile-set-${base}`;
69
+ const kIpSet = `ip-addresses-set-${base}`;
70
+ const kSessionsSet = `sessions-set-${base}`;
71
+ const kVisitorsAll = `visitors-all`;
72
+ try {
73
+ await redis_1.redis.incr(kRequests);
74
+ const isNewEver = await redis_1.redis.sadd(kVisitorsAll, visitorId);
75
+ if (isNewEver === 1) {
76
+ await redis_1.redis.incr(kNewVisitors);
77
+ }
78
+ const addedVisitor = await redis_1.redis.sadd(kVisitorsSet, visitorId);
79
+ if (addedVisitor === 1) {
80
+ await redis_1.redis.incr(kVisitors);
81
+ }
82
+ if (mobile) {
83
+ const addedMobileVisitor = await redis_1.redis.sadd(kVisitorsMobileSet, visitorId);
84
+ if (addedMobileVisitor === 1) {
85
+ await redis_1.redis.incr(kVisitorsMobile);
86
+ }
87
+ if (isNewEver === 1) {
88
+ await redis_1.redis.incr(kNewVisitorsMobile);
89
+ }
90
+ }
91
+ if (ip) {
92
+ const addedIp = await redis_1.redis.sadd(kIpSet, ip);
93
+ if (addedIp === 1) {
94
+ await redis_1.redis.incr(kIpAddresses);
95
+ }
96
+ }
97
+ const addedSession = await redis_1.redis.sadd(kSessionsSet, sessionId);
98
+ if (addedSession === 1) {
99
+ await redis_1.redis.incr(kSessions);
100
+ }
101
+ }
102
+ catch { }
103
+ }
104
+ else {
105
+ let stats = memoryStats.get(dateKey);
106
+ if (!stats) {
107
+ stats = {
108
+ requests: 0,
109
+ newVisitors: 0,
110
+ visitors: new Set(),
111
+ newVisitorsMobile: 0,
112
+ visitorsMobile: new Set(),
113
+ ipAddresses: 0,
114
+ ipSet: new Set(),
115
+ sessions: 0,
116
+ sessionSet: new Set(),
117
+ };
118
+ memoryStats.set(dateKey, stats);
119
+ }
120
+ stats.requests += 1;
121
+ if (!globalVisitors.has(visitorId)) {
122
+ globalVisitors.add(visitorId);
123
+ stats.newVisitors += 1;
124
+ if (mobile) {
125
+ stats.newVisitorsMobile += 1;
126
+ }
127
+ }
128
+ if (!stats.visitors.has(visitorId)) {
129
+ stats.visitors.add(visitorId);
130
+ }
131
+ if (mobile && !stats.visitorsMobile.has(visitorId)) {
132
+ stats.visitorsMobile.add(visitorId);
133
+ }
134
+ if (ip && !stats.ipSet.has(ip)) {
135
+ stats.ipSet.add(ip);
136
+ stats.ipAddresses += 1;
137
+ }
138
+ if (!stats.sessionSet.has(sessionId)) {
139
+ stats.sessionSet.add(sessionId);
140
+ stats.sessions += 1;
141
+ }
142
+ }
143
+ next();
144
+ }
@@ -0,0 +1,11 @@
1
+ import winston from "winston";
2
+ import "winston-daily-rotate-file";
3
+ declare const logger: winston.Logger;
4
+ export declare class Log {
5
+ static info(message: string, meta?: any): void;
6
+ static error(message: string, meta?: any): void;
7
+ static warn(message: string, meta?: any): void;
8
+ static debug(message: string, meta?: any): void;
9
+ }
10
+ export default logger;
11
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../lib/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,2BAA2B,CAAC;AAiDnC,QAAA,MAAM,MAAM,gBAgBV,CAAC;AAeH,qBAAa,GAAG;IACd,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG;IAIvC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG;IAIxC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG;IAIvC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG;CAGzC;AAED,eAAe,MAAM,CAAC"}
@@ -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":["../../../lib/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":["../../../lib/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"}