lapeeh 1.0.11 → 1.0.13

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 (76) hide show
  1. package/dist/lib/bootstrap.d.ts +3 -0
  2. package/dist/lib/bootstrap.d.ts.map +1 -0
  3. package/dist/lib/bootstrap.js +184 -0
  4. package/dist/lib/core/realtime.d.ts +3 -0
  5. package/dist/lib/core/realtime.d.ts.map +1 -0
  6. package/dist/lib/core/realtime.js +36 -0
  7. package/dist/lib/core/redis.d.ts +9 -0
  8. package/dist/lib/core/redis.d.ts.map +1 -0
  9. package/dist/lib/core/redis.js +133 -0
  10. package/dist/lib/core/serializer.d.ts +43 -0
  11. package/dist/lib/core/serializer.d.ts.map +1 -0
  12. package/dist/lib/core/serializer.js +66 -0
  13. package/dist/lib/core/server.d.ts +2 -0
  14. package/dist/lib/core/server.d.ts.map +1 -0
  15. package/dist/lib/core/server.js +60 -0
  16. package/dist/lib/core/store.d.ts +55 -0
  17. package/dist/lib/core/store.d.ts.map +1 -0
  18. package/dist/lib/core/store.js +66 -0
  19. package/dist/lib/middleware/auth.d.ts +4 -0
  20. package/dist/lib/middleware/auth.d.ts.map +1 -0
  21. package/dist/lib/middleware/auth.js +55 -0
  22. package/dist/lib/middleware/error.d.ts +3 -0
  23. package/dist/lib/middleware/error.d.ts.map +1 -0
  24. package/dist/lib/middleware/error.js +41 -0
  25. package/dist/lib/middleware/multipart.d.ts +4 -0
  26. package/dist/lib/middleware/multipart.d.ts.map +1 -0
  27. package/dist/lib/middleware/multipart.js +17 -0
  28. package/dist/lib/middleware/rateLimit.d.ts +2 -0
  29. package/dist/lib/middleware/rateLimit.d.ts.map +1 -0
  30. package/dist/lib/middleware/rateLimit.js +19 -0
  31. package/dist/lib/middleware/requestLogger.d.ts +3 -0
  32. package/dist/lib/middleware/requestLogger.d.ts.map +1 -0
  33. package/dist/lib/middleware/requestLogger.js +22 -0
  34. package/dist/lib/middleware/visitor.d.ts +3 -0
  35. package/dist/lib/middleware/visitor.d.ts.map +1 -0
  36. package/dist/lib/middleware/visitor.js +144 -0
  37. package/dist/lib/utils/logger.d.ts +11 -0
  38. package/dist/lib/utils/logger.d.ts.map +1 -0
  39. package/dist/lib/utils/logger.js +81 -0
  40. package/dist/lib/utils/pagination.d.ts +19 -0
  41. package/dist/lib/utils/pagination.d.ts.map +1 -0
  42. package/dist/lib/utils/pagination.js +34 -0
  43. package/dist/lib/utils/response.d.ts +11 -0
  44. package/dist/lib/utils/response.d.ts.map +1 -0
  45. package/dist/lib/utils/response.js +57 -0
  46. package/dist/lib/utils/validator.d.ts +38 -0
  47. package/dist/lib/utils/validator.d.ts.map +1 -0
  48. package/dist/lib/utils/validator.js +346 -0
  49. package/dist/src/config/app.d.ts +10 -0
  50. package/dist/src/config/app.d.ts.map +1 -0
  51. package/dist/src/config/app.js +12 -0
  52. package/dist/src/config/cors.d.ts +6 -0
  53. package/dist/src/config/cors.d.ts.map +1 -0
  54. package/dist/src/config/cors.js +8 -0
  55. package/dist/src/modules/Auth/auth.controller.d.ts +11 -0
  56. package/dist/src/modules/Auth/auth.controller.d.ts.map +1 -0
  57. package/dist/src/modules/Auth/auth.controller.js +427 -0
  58. package/dist/src/modules/Auth/auth.routes.d.ts +2 -0
  59. package/dist/src/modules/Auth/auth.routes.d.ts.map +1 -0
  60. package/dist/src/modules/Auth/auth.routes.js +45 -0
  61. package/dist/src/modules/Auth/auth.service.d.ts +3 -0
  62. package/dist/src/modules/Auth/auth.service.d.ts.map +1 -0
  63. package/dist/src/modules/Auth/auth.service.js +8 -0
  64. package/dist/src/modules/Rbac/rbac.controller.d.ts +16 -0
  65. package/dist/src/modules/Rbac/rbac.controller.d.ts.map +1 -0
  66. package/dist/src/modules/Rbac/rbac.controller.js +438 -0
  67. package/dist/src/modules/Rbac/rbac.routes.d.ts +2 -0
  68. package/dist/src/modules/Rbac/rbac.routes.d.ts.map +1 -0
  69. package/dist/src/modules/Rbac/rbac.routes.js +23 -0
  70. package/dist/src/modules/Rbac/rbac.service.d.ts +3 -0
  71. package/dist/src/modules/Rbac/rbac.service.d.ts.map +1 -0
  72. package/dist/src/modules/Rbac/rbac.service.js +8 -0
  73. package/dist/src/routes/index.d.ts +2 -0
  74. package/dist/src/routes/index.d.ts.map +1 -0
  75. package/dist/src/routes/index.js +9 -0
  76. package/package.json +23 -1
@@ -0,0 +1,66 @@
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.generateId = exports.user_permissions = exports.role_permissions = exports.user_roles = exports.permissions = exports.roles = exports.users = void 0;
7
+ exports.saveStore = saveStore;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ // Database file path
11
+ const dbPath = path_1.default.resolve(process.cwd(), process.env.DB_FILE || "database.json");
12
+ // Load data function
13
+ function loadData() {
14
+ if (fs_1.default.existsSync(dbPath)) {
15
+ const raw = fs_1.default.readFileSync(dbPath, "utf-8");
16
+ return JSON.parse(raw);
17
+ }
18
+ return {
19
+ users: [],
20
+ roles: [
21
+ {
22
+ id: "1",
23
+ name: "Admin",
24
+ slug: "admin",
25
+ description: "Administrator",
26
+ created_at: new Date(),
27
+ updated_at: new Date(),
28
+ },
29
+ {
30
+ id: "2",
31
+ name: "User",
32
+ slug: "user",
33
+ description: "Standard User",
34
+ created_at: new Date(),
35
+ updated_at: new Date(),
36
+ },
37
+ ],
38
+ permissions: [],
39
+ user_roles: [],
40
+ role_permissions: [],
41
+ user_permissions: [],
42
+ };
43
+ }
44
+ const data = loadData();
45
+ // Export mutable arrays
46
+ exports.users = data.users;
47
+ exports.roles = data.roles;
48
+ exports.permissions = data.permissions;
49
+ exports.user_roles = data.user_roles;
50
+ exports.role_permissions = data.role_permissions;
51
+ exports.user_permissions = data.user_permissions;
52
+ // Helper to save data
53
+ function saveStore() {
54
+ const payload = {
55
+ users: exports.users,
56
+ roles: exports.roles,
57
+ permissions: exports.permissions,
58
+ user_roles: exports.user_roles,
59
+ role_permissions: exports.role_permissions,
60
+ user_permissions: exports.user_permissions,
61
+ };
62
+ fs_1.default.writeFileSync(dbPath, JSON.stringify(payload, null, 2), "utf-8");
63
+ }
64
+ // Helper to generate IDs
65
+ const generateId = () => Math.random().toString(36).substr(2, 9);
66
+ exports.generateId = generateId;
@@ -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;AAK1D,wBAAgB,YAAY,CAC1B,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,YAAY,sCAwCpB"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.errorHandler = errorHandler;
4
+ const zod_1 = require("zod");
5
+ const response_1 = require("../utils/response");
6
+ const logger_1 = require("../utils/logger");
7
+ function errorHandler(err, req, res, _next) {
8
+ // 1. Zod Validation Error
9
+ if (err instanceof zod_1.ZodError) {
10
+ const formattedErrors = err.errors.map((e) => ({
11
+ field: e.path.join("."),
12
+ message: e.message,
13
+ }));
14
+ return (0, response_1.sendError)(res, 400, "Validation Error", formattedErrors);
15
+ }
16
+ // 2. JWT Errors
17
+ if (err.name === "JsonWebTokenError") {
18
+ return (0, response_1.sendError)(res, 401, "Invalid token");
19
+ }
20
+ if (err.name === "TokenExpiredError") {
21
+ return (0, response_1.sendError)(res, 401, "Token expired");
22
+ }
23
+ // 4. Syntax Error (JSON body parsing)
24
+ if (err instanceof SyntaxError && "body" in err) {
25
+ return (0, response_1.sendError)(res, 400, "Invalid JSON format");
26
+ }
27
+ // 5. Default / Custom Error
28
+ const code = err.statusCode || 500;
29
+ const msg = err.message || "Internal Server Error";
30
+ // Log error (file log for production, console for dev)
31
+ if (code === 500) {
32
+ logger_1.Log.error(msg, {
33
+ error: err,
34
+ path: req.path,
35
+ method: req.method,
36
+ ip: req.ip,
37
+ stack: err.stack,
38
+ });
39
+ }
40
+ return (0, response_1.sendError)(res, code, msg);
41
+ }
@@ -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: "lapeeh-%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: "lapeeh-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 = any>(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,GAAG,GAAG,EACjC,GAAG,EAAE,QAAQ,EACb,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,CAAC,sCAIT;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("./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
+ }