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.
- package/bin/index.js +39 -56
- package/dist/generated/prisma/browser.d.ts +80 -0
- package/dist/generated/prisma/browser.d.ts.map +1 -0
- package/dist/generated/prisma/browser.js +56 -0
- package/dist/generated/prisma/client.d.ts +97 -0
- package/dist/generated/prisma/client.d.ts.map +1 -0
- package/dist/generated/prisma/client.js +68 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +486 -0
- package/dist/generated/prisma/commonInputTypes.d.ts.map +1 -0
- package/dist/generated/prisma/commonInputTypes.js +11 -0
- package/dist/generated/prisma/enums.d.ts +2 -0
- package/dist/generated/prisma/enums.d.ts.map +1 -0
- package/dist/generated/prisma/enums.js +11 -0
- package/dist/generated/prisma/internal/class.d.ts +281 -0
- package/dist/generated/prisma/internal/class.d.ts.map +1 -0
- package/dist/generated/prisma/internal/class.js +76 -0
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +1734 -0
- package/dist/generated/prisma/internal/prismaNamespace.d.ts.map +1 -0
- package/dist/generated/prisma/internal/prismaNamespace.js +260 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +200 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts.map +1 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +231 -0
- package/dist/generated/prisma/models/cache.d.ts +986 -0
- package/dist/generated/prisma/models/cache.d.ts.map +1 -0
- package/dist/generated/prisma/models/cache.js +2 -0
- package/dist/generated/prisma/models/cache_locks.d.ts +976 -0
- package/dist/generated/prisma/models/cache_locks.d.ts.map +1 -0
- package/dist/generated/prisma/models/cache_locks.js +2 -0
- package/dist/generated/prisma/models/failed_jobs.d.ts +1098 -0
- package/dist/generated/prisma/models/failed_jobs.d.ts.map +1 -0
- package/dist/generated/prisma/models/failed_jobs.js +2 -0
- package/dist/generated/prisma/models/job_batches.d.ts +1212 -0
- package/dist/generated/prisma/models/job_batches.d.ts.map +1 -0
- package/dist/generated/prisma/models/job_batches.js +2 -0
- package/dist/generated/prisma/models/jobs.d.ts +1112 -0
- package/dist/generated/prisma/models/jobs.d.ts.map +1 -0
- package/dist/generated/prisma/models/jobs.js +2 -0
- package/dist/generated/prisma/models/migrations.d.ts +979 -0
- package/dist/generated/prisma/models/migrations.d.ts.map +1 -0
- package/dist/generated/prisma/models/migrations.js +2 -0
- package/dist/generated/prisma/models/password_reset_tokens.d.ts +941 -0
- package/dist/generated/prisma/models/password_reset_tokens.d.ts.map +1 -0
- package/dist/generated/prisma/models/password_reset_tokens.js +2 -0
- package/dist/generated/prisma/models/permissions.d.ts +1333 -0
- package/dist/generated/prisma/models/permissions.d.ts.map +1 -0
- package/dist/generated/prisma/models/permissions.js +2 -0
- package/dist/generated/prisma/models/personal_access_tokens.d.ts +1178 -0
- package/dist/generated/prisma/models/personal_access_tokens.d.ts.map +1 -0
- package/dist/generated/prisma/models/personal_access_tokens.js +2 -0
- package/dist/generated/prisma/models/role_permissions.d.ts +1291 -0
- package/dist/generated/prisma/models/role_permissions.d.ts.map +1 -0
- package/dist/generated/prisma/models/role_permissions.js +2 -0
- package/dist/generated/prisma/models/roles.d.ts +1333 -0
- package/dist/generated/prisma/models/roles.d.ts.map +1 -0
- package/dist/generated/prisma/models/roles.js +2 -0
- package/dist/generated/prisma/models/sessions.d.ts +1073 -0
- package/dist/generated/prisma/models/sessions.d.ts.map +1 -0
- package/dist/generated/prisma/models/sessions.js +2 -0
- package/dist/generated/prisma/models/user_permissions.d.ts +1291 -0
- package/dist/generated/prisma/models/user_permissions.d.ts.map +1 -0
- package/dist/generated/prisma/models/user_permissions.js +2 -0
- package/dist/generated/prisma/models/user_roles.d.ts +1291 -0
- package/dist/generated/prisma/models/user_roles.d.ts.map +1 -0
- package/dist/generated/prisma/models/user_roles.js +2 -0
- package/dist/generated/prisma/models/users.d.ts +1513 -0
- package/dist/generated/prisma/models/users.d.ts.map +1 -0
- package/dist/generated/prisma/models/users.js +2 -0
- package/dist/generated/prisma/models.d.ts +17 -0
- package/dist/generated/prisma/models.d.ts.map +1 -0
- package/dist/generated/prisma/models.js +2 -0
- package/dist/lib/bootstrap.d.ts +2 -0
- package/dist/lib/bootstrap.d.ts.map +1 -0
- package/dist/lib/bootstrap.js +133 -0
- package/dist/lib/core/database.d.ts +3 -0
- package/dist/lib/core/database.d.ts.map +1 -0
- package/dist/lib/core/database.js +34 -0
- package/dist/lib/core/realtime.d.ts +3 -0
- package/dist/lib/core/realtime.d.ts.map +1 -0
- package/dist/lib/core/realtime.js +36 -0
- package/dist/lib/core/redis.d.ts +8 -0
- package/dist/lib/core/redis.d.ts.map +1 -0
- package/dist/lib/core/redis.js +123 -0
- package/dist/lib/core/serializer.d.ts +43 -0
- package/dist/lib/core/serializer.d.ts.map +1 -0
- package/dist/lib/core/serializer.js +66 -0
- package/dist/lib/core/server.d.ts +2 -0
- package/dist/lib/core/server.d.ts.map +1 -0
- package/dist/lib/core/server.js +60 -0
- package/dist/lib/middleware/auth.d.ts +4 -0
- package/dist/lib/middleware/auth.d.ts.map +1 -0
- package/dist/lib/middleware/auth.js +55 -0
- package/dist/lib/middleware/error.d.ts +3 -0
- package/dist/lib/middleware/error.d.ts.map +1 -0
- package/dist/lib/middleware/error.js +60 -0
- package/dist/lib/middleware/multipart.d.ts +4 -0
- package/dist/lib/middleware/multipart.d.ts.map +1 -0
- package/dist/lib/middleware/multipart.js +17 -0
- package/dist/lib/middleware/rateLimit.d.ts +2 -0
- package/dist/lib/middleware/rateLimit.d.ts.map +1 -0
- package/dist/lib/middleware/rateLimit.js +19 -0
- package/dist/lib/middleware/requestLogger.d.ts +3 -0
- package/dist/lib/middleware/requestLogger.d.ts.map +1 -0
- package/dist/lib/middleware/requestLogger.js +22 -0
- package/dist/lib/middleware/visitor.d.ts +3 -0
- package/dist/lib/middleware/visitor.d.ts.map +1 -0
- package/dist/lib/middleware/visitor.js +144 -0
- package/dist/lib/utils/logger.d.ts +11 -0
- package/dist/lib/utils/logger.d.ts.map +1 -0
- package/dist/lib/utils/logger.js +81 -0
- package/dist/lib/utils/pagination.d.ts +19 -0
- package/dist/lib/utils/pagination.d.ts.map +1 -0
- package/dist/lib/utils/pagination.js +34 -0
- package/dist/lib/utils/response.d.ts +11 -0
- package/dist/lib/utils/response.d.ts.map +1 -0
- package/dist/lib/utils/response.js +57 -0
- package/dist/lib/utils/validator.d.ts +38 -0
- package/dist/lib/utils/validator.d.ts.map +1 -0
- package/dist/lib/utils/validator.js +369 -0
- package/dist/prisma/seed.d.ts +2 -0
- package/dist/prisma/seed.d.ts.map +1 -0
- package/dist/prisma/seed.js +381 -0
- package/dist/src/controllers/authController.d.ts +11 -0
- package/dist/src/controllers/authController.d.ts.map +1 -0
- package/dist/src/controllers/authController.js +414 -0
- package/dist/src/controllers/petController.d.ts +7 -0
- package/dist/src/controllers/petController.d.ts.map +1 -0
- package/dist/src/controllers/petController.js +163 -0
- package/dist/src/controllers/rbacController.d.ts +16 -0
- package/dist/src/controllers/rbacController.d.ts.map +1 -0
- package/dist/src/controllers/rbacController.js +437 -0
- package/dist/src/core/database.d.ts +3 -0
- package/dist/src/core/database.d.ts.map +1 -0
- package/dist/src/core/database.js +34 -0
- package/dist/src/core/realtime.d.ts +3 -0
- package/dist/src/core/realtime.d.ts.map +1 -0
- package/dist/src/core/realtime.js +36 -0
- package/dist/src/core/redis.d.ts +8 -0
- package/dist/src/core/redis.d.ts.map +1 -0
- package/dist/src/core/redis.js +123 -0
- package/dist/src/core/serializer.d.ts +43 -0
- package/dist/src/core/serializer.d.ts.map +1 -0
- package/dist/src/core/serializer.js +66 -0
- package/dist/src/core/server.d.ts +2 -0
- package/dist/src/core/server.d.ts.map +1 -0
- package/dist/src/core/server.js +60 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +98 -0
- package/dist/src/middleware/auth.d.ts +4 -0
- package/dist/src/middleware/auth.d.ts.map +1 -0
- package/dist/src/middleware/auth.js +48 -0
- package/dist/src/middleware/error.d.ts +3 -0
- package/dist/src/middleware/error.d.ts.map +1 -0
- package/dist/src/middleware/error.js +60 -0
- package/dist/src/middleware/multipart.d.ts +4 -0
- package/dist/src/middleware/multipart.d.ts.map +1 -0
- package/dist/src/middleware/multipart.js +17 -0
- package/dist/src/middleware/rateLimit.d.ts +2 -0
- package/dist/src/middleware/rateLimit.d.ts.map +1 -0
- package/dist/src/middleware/rateLimit.js +19 -0
- package/dist/src/middleware/requestLogger.d.ts +3 -0
- package/dist/src/middleware/requestLogger.d.ts.map +1 -0
- package/dist/src/middleware/requestLogger.js +22 -0
- package/dist/src/middleware/visitor.d.ts +3 -0
- package/dist/src/middleware/visitor.d.ts.map +1 -0
- package/dist/src/middleware/visitor.js +144 -0
- package/dist/src/prisma.d.ts +3 -0
- package/dist/src/prisma.d.ts.map +1 -0
- package/dist/src/prisma.js +34 -0
- package/dist/src/realtime.d.ts +3 -0
- package/dist/src/realtime.d.ts.map +1 -0
- package/dist/src/realtime.js +36 -0
- package/dist/src/redis.d.ts +8 -0
- package/dist/src/redis.d.ts.map +1 -0
- package/dist/src/redis.js +122 -0
- package/dist/src/routes/auth.d.ts +2 -0
- package/dist/src/routes/auth.d.ts.map +1 -0
- package/dist/src/routes/auth.js +45 -0
- package/dist/src/routes/index.d.ts +2 -0
- package/dist/src/routes/index.d.ts.map +1 -0
- package/dist/src/routes/index.js +14 -0
- package/dist/src/routes/pets.d.ts +3 -0
- package/dist/src/routes/pets.d.ts.map +1 -0
- package/dist/src/routes/pets.js +45 -0
- package/dist/src/routes/rbac.d.ts +2 -0
- package/dist/src/routes/rbac.d.ts.map +1 -0
- package/dist/src/routes/rbac.js +23 -0
- package/dist/src/schema/auth-schema.d.ts +76 -0
- package/dist/src/schema/auth-schema.d.ts.map +1 -0
- package/dist/src/schema/auth-schema.js +63 -0
- package/dist/src/schema/pet-schema.d.ts +28 -0
- package/dist/src/schema/pet-schema.d.ts.map +1 -0
- package/dist/src/schema/pet-schema.js +14 -0
- package/dist/src/server.d.ts +2 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +31 -0
- package/dist/src/utils/logger.d.ts +11 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +81 -0
- package/dist/src/utils/pagination.d.ts +19 -0
- package/dist/src/utils/pagination.d.ts.map +1 -0
- package/dist/src/utils/pagination.js +34 -0
- package/dist/src/utils/response.d.ts +11 -0
- package/dist/src/utils/response.d.ts.map +1 -0
- package/dist/src/utils/response.js +57 -0
- package/dist/src/utils/validator.d.ts +38 -0
- package/dist/src/utils/validator.d.ts.map +1 -0
- package/dist/src/utils/validator.js +369 -0
- package/lib/bootstrap.ts +6 -0
- package/package.json +26 -14
- package/.env.example +0 -19
- package/doc/ARCHITECTURE_GUIDE.md +0 -73
- package/doc/CHANGELOG.md +0 -77
- package/doc/CHEATSHEET.md +0 -94
- package/doc/CLI.md +0 -139
- package/doc/CONTRIBUTING.md +0 -105
- package/doc/DEPLOYMENT.md +0 -122
- package/doc/FAQ.md +0 -81
- package/doc/FEATURES.md +0 -165
- package/doc/GETTING_STARTED.md +0 -108
- package/doc/INTRODUCTION.md +0 -60
- package/doc/PACKAGES.md +0 -66
- package/doc/PERFORMANCE.md +0 -91
- package/doc/ROADMAP.md +0 -93
- package/doc/SECURITY.md +0 -93
- package/doc/STRUCTURE.md +0 -90
- package/doc/TUTORIAL.md +0 -192
- package/docker-compose.yml +0 -24
- package/eslint.config.mjs +0 -26
- package/framework.md +0 -168
- package/nodemon.json +0 -6
- package/prisma.config.ts +0 -15
- package/src/controllers/authController.ts +0 -469
- package/src/controllers/petController.ts +0 -194
- package/src/controllers/rbacController.ts +0 -478
- package/src/models/core.prisma +0 -163
- package/src/models/pets.prisma +0 -9
- package/src/routes/auth.ts +0 -74
- package/src/routes/index.ts +0 -10
- package/src/routes/pets.ts +0 -13
- package/src/routes/rbac.ts +0 -42
- package/storage/logs/.gitkeep +0 -0
- package/tsconfig.json +0 -30
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Membuat atau mengambil serializer yang sudah dicompile.
|
|
3
|
+
*
|
|
4
|
+
* @param key Identifier unik untuk schema (misal: 'UserResponse', 'ProductList')
|
|
5
|
+
* @param schema JSON Schema definition (Standard JSON Schema)
|
|
6
|
+
* @returns Fungsi yang mengubah object menjadi JSON string dengan sangat cepat
|
|
7
|
+
*/
|
|
8
|
+
export declare function getSerializer(key: string, schema: any): <TDoc = any>(doc: TDoc) => any;
|
|
9
|
+
/**
|
|
10
|
+
* Helper untuk mendefinisikan schema standar response Lapeh
|
|
11
|
+
* { status: "success", message: string, data: T }
|
|
12
|
+
*/
|
|
13
|
+
export declare function createResponseSchema(dataSchema: any): {
|
|
14
|
+
title: string;
|
|
15
|
+
type: string;
|
|
16
|
+
properties: {
|
|
17
|
+
status: {
|
|
18
|
+
type: string;
|
|
19
|
+
};
|
|
20
|
+
message: {
|
|
21
|
+
type: string;
|
|
22
|
+
};
|
|
23
|
+
data: any;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Helper khusus untuk response paginasi
|
|
28
|
+
* { status: "success", message: string, data: { data: T[], meta: ... } }
|
|
29
|
+
*/
|
|
30
|
+
export declare function createPaginatedResponseSchema(itemSchema: any): {
|
|
31
|
+
title: string;
|
|
32
|
+
type: string;
|
|
33
|
+
properties: {
|
|
34
|
+
status: {
|
|
35
|
+
type: string;
|
|
36
|
+
};
|
|
37
|
+
message: {
|
|
38
|
+
type: string;
|
|
39
|
+
};
|
|
40
|
+
data: any;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=serializer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../../../src/core/serializer.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,kCAQrD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,GAAG;;;;;;;;;;;;EAUnD;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,GAAG;;;;;;;;;;;;EAmB5D"}
|
|
@@ -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.getSerializer = getSerializer;
|
|
7
|
+
exports.createResponseSchema = createResponseSchema;
|
|
8
|
+
exports.createPaginatedResponseSchema = createPaginatedResponseSchema;
|
|
9
|
+
const fast_json_stringify_1 = __importDefault(require("fast-json-stringify"));
|
|
10
|
+
// Cache untuk menyimpan fungsi stringify yang sudah dicompile
|
|
11
|
+
// Key: Nama schema/Identifier, Value: Fungsi stringify
|
|
12
|
+
const serializerCache = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Membuat atau mengambil serializer yang sudah dicompile.
|
|
15
|
+
*
|
|
16
|
+
* @param key Identifier unik untuk schema (misal: 'UserResponse', 'ProductList')
|
|
17
|
+
* @param schema JSON Schema definition (Standard JSON Schema)
|
|
18
|
+
* @returns Fungsi yang mengubah object menjadi JSON string dengan sangat cepat
|
|
19
|
+
*/
|
|
20
|
+
function getSerializer(key, schema) {
|
|
21
|
+
if (serializerCache.has(key)) {
|
|
22
|
+
return serializerCache.get(key);
|
|
23
|
+
}
|
|
24
|
+
const stringify = (0, fast_json_stringify_1.default)(schema);
|
|
25
|
+
serializerCache.set(key, stringify);
|
|
26
|
+
return stringify;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Helper untuk mendefinisikan schema standar response Lapeh
|
|
30
|
+
* { status: "success", message: string, data: T }
|
|
31
|
+
*/
|
|
32
|
+
function createResponseSchema(dataSchema) {
|
|
33
|
+
return {
|
|
34
|
+
title: "StandardResponse",
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
status: { type: "string" },
|
|
38
|
+
message: { type: "string" },
|
|
39
|
+
data: dataSchema,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Helper khusus untuk response paginasi
|
|
45
|
+
* { status: "success", message: string, data: { data: T[], meta: ... } }
|
|
46
|
+
*/
|
|
47
|
+
function createPaginatedResponseSchema(itemSchema) {
|
|
48
|
+
return createResponseSchema({
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
data: {
|
|
52
|
+
type: "array",
|
|
53
|
+
items: itemSchema,
|
|
54
|
+
},
|
|
55
|
+
meta: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
page: { type: "integer" },
|
|
59
|
+
perPage: { type: "integer" },
|
|
60
|
+
total: { type: "integer" },
|
|
61
|
+
lastPage: { type: "integer" },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/core/server.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,GAAG,6CAAY,CAAC"}
|
|
@@ -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
|
+
const routes_1 = require("../routes"); // Import unified routes
|
|
12
|
+
const visitor_1 = require("../middleware/visitor");
|
|
13
|
+
const error_1 = require("../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
|
|
58
|
+
exports.app.use("/api", routes_1.apiRouter);
|
|
59
|
+
// Global Error Handler
|
|
60
|
+
exports.app.use(error_1.errorHandler);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
7
|
+
dotenv_1.default.config();
|
|
8
|
+
// Validasi Environment Variables
|
|
9
|
+
const requiredEnvs = ["DATABASE_URL", "JWT_SECRET"];
|
|
10
|
+
const missingEnvs = requiredEnvs.filter((key) => !process.env[key]);
|
|
11
|
+
if (missingEnvs.length > 0) {
|
|
12
|
+
console.error(`❌ Missing required environment variables: ${missingEnvs.join(", ")}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const server_1 = require("./core/server");
|
|
16
|
+
const http_1 = __importDefault(require("http"));
|
|
17
|
+
const realtime_1 = require("./core/realtime");
|
|
18
|
+
const redis_1 = require("./core/redis"); // Pastikan redis diexport di redis.ts
|
|
19
|
+
const database_1 = require("./core/database");
|
|
20
|
+
const port = process.env.PORT ? Number(process.env.PORT) : 4000;
|
|
21
|
+
const server = http_1.default.createServer(server_1.app);
|
|
22
|
+
(0, realtime_1.initRealtime)(server);
|
|
23
|
+
const startServer = async () => {
|
|
24
|
+
try {
|
|
25
|
+
// Initialize Redis transparently (no logs if missing)
|
|
26
|
+
await (0, redis_1.initRedis)();
|
|
27
|
+
// Handle specific listen errors
|
|
28
|
+
server.on("error", (e) => {
|
|
29
|
+
if (e.code === "EADDRINUSE") {
|
|
30
|
+
console.log(`\n❌ Error: Port ${port} is already in use.`);
|
|
31
|
+
console.log(`💡 Suggestion: Run this command to kill the conflicting process:\n`);
|
|
32
|
+
if (process.platform === "win32") {
|
|
33
|
+
console.log(` npx kill-port ${port}`);
|
|
34
|
+
console.log(` (PowerShell): Stop-Process -Id (Get-NetTCPConnection -LocalPort ${port}).OwningProcess -Force`);
|
|
35
|
+
}
|
|
36
|
+
else if (process.platform === "darwin") {
|
|
37
|
+
console.log(` npx kill-port ${port}`);
|
|
38
|
+
console.log(` (Terminal): kill -9 $(lsof -t -i:${port})`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Linux
|
|
42
|
+
console.log(` npx kill-port ${port}`);
|
|
43
|
+
console.log(` (Terminal): fuser -k ${port}/tcp`);
|
|
44
|
+
}
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
server.listen(port, () => {
|
|
49
|
+
console.log(`✅ API running at http://localhost:${port}`);
|
|
50
|
+
console.log(`🛡️ Environment: ${process.env.NODE_ENV || "development"}`);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error("❌ Failed to start server:", error);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
startServer();
|
|
59
|
+
// Graceful Shutdown
|
|
60
|
+
const shutdown = async (signal) => {
|
|
61
|
+
console.log(`\n🛑 ${signal} received. Closing resources...`);
|
|
62
|
+
server.close(() => {
|
|
63
|
+
console.log("Http server closed.");
|
|
64
|
+
});
|
|
65
|
+
try {
|
|
66
|
+
await database_1.prisma.$disconnect();
|
|
67
|
+
console.log("Prisma disconnected.");
|
|
68
|
+
// Jika redis object available, disconnect
|
|
69
|
+
if (redis_1.redis && redis_1.redis.status === "ready") {
|
|
70
|
+
await redis_1.redis.quit();
|
|
71
|
+
console.log("Redis disconnected.");
|
|
72
|
+
}
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.error("Error during shutdown:", err);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
81
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
82
|
+
// Menangani Uncaught Exceptions (Error sinkron yang tidak tertangkap)
|
|
83
|
+
process.on("uncaughtException", (error) => {
|
|
84
|
+
console.error("❌ Uncaught Exception:", error);
|
|
85
|
+
// Disarankan untuk restart process di production (gunakan PM2 atau Docker)
|
|
86
|
+
shutdown("uncaughtException");
|
|
87
|
+
});
|
|
88
|
+
// Menangani Unhandled Rejection (Promise yang error tapi tidak di-catch)
|
|
89
|
+
process.on("unhandledRejection", (reason) => {
|
|
90
|
+
console.error("❌ Unhandled Rejection:", reason);
|
|
91
|
+
// Tidak perlu langsung exit, tapi harus dicatat
|
|
92
|
+
// shutdown("unhandledRejection");
|
|
93
|
+
});
|
|
94
|
+
// Menangani Unhandled Rejection (Promise yang reject tapi tidak di-catch)
|
|
95
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
96
|
+
console.error("❌ Unhandled Rejection at:", promise, "reason:", reason);
|
|
97
|
+
// Log error tapi jangan matikan server jika tidak kritikal, atau shutdown jika perlu
|
|
98
|
+
});
|
|
@@ -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":["../../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK1D,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,48 @@
|
|
|
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
|
+
const authController_1 = require("../controllers/authController");
|
|
11
|
+
function requireAuth(req, res, next) {
|
|
12
|
+
const header = req.headers.authorization;
|
|
13
|
+
if (!header || !header.startsWith("Bearer ")) {
|
|
14
|
+
(0, response_1.sendError)(res, 401, "Unauthorized");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const token = header.slice(7);
|
|
18
|
+
const secret = process.env.JWT_SECRET;
|
|
19
|
+
if (!secret) {
|
|
20
|
+
(0, response_1.sendError)(res, 500, "Server misconfigured");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const payload = jsonwebtoken_1.default.verify(token, secret);
|
|
25
|
+
req.user = { userId: payload.userId, role: payload.role };
|
|
26
|
+
const accessExpiresInSeconds = authController_1.ACCESS_TOKEN_EXPIRES_IN_SECONDS;
|
|
27
|
+
const accessExpiresAt = new Date(Date.now() + accessExpiresInSeconds * 1000).toISOString();
|
|
28
|
+
const newToken = jsonwebtoken_1.default.sign({ userId: payload.userId, role: payload.role }, secret, { expiresIn: accessExpiresInSeconds });
|
|
29
|
+
res.setHeader("x-access-token", newToken);
|
|
30
|
+
res.setHeader("x-access-expires-at", accessExpiresAt);
|
|
31
|
+
next();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
(0, response_1.sendError)(res, 401, "Invalid token");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function requireAdmin(req, res, next) {
|
|
38
|
+
const user = req.user;
|
|
39
|
+
if (!user) {
|
|
40
|
+
(0, response_1.sendError)(res, 401, "Unauthorized");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (user.role !== "admin" && user.role !== "super_admin") {
|
|
44
|
+
(0, response_1.sendError)(res, 403, "Forbidden");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
next();
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../../src/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":["../../../src/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 @@
|
|
|
1
|
+
{"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../../../src/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 @@
|
|
|
1
|
+
{"version":3,"file":"requestLogger.d.ts","sourceRoot":"","sources":["../../../src/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 @@
|
|
|
1
|
+
{"version":3,"file":"visitor.d.ts","sourceRoot":"","sources":["../../../src/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
|
+
}
|