lapeh 2.6.17 → 3.0.2
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/.env.example +1 -6
- package/README.md +19 -85
- package/bin/index.js +84 -180
- package/dist/lib/bootstrap.d.ts.map +1 -1
- package/dist/lib/bootstrap.js +17 -16
- package/dist/lib/core/store.d.ts +55 -0
- package/dist/lib/core/store.d.ts.map +1 -0
- package/dist/lib/core/store.js +66 -0
- package/dist/lib/middleware/error.d.ts.map +1 -1
- package/dist/lib/middleware/error.js +1 -20
- package/dist/lib/utils/validator.d.ts.map +1 -1
- package/dist/lib/utils/validator.js +3 -32
- package/dist/src/modules/Auth/auth.controller.d.ts.map +1 -1
- package/dist/src/modules/Auth/auth.controller.js +118 -105
- package/dist/src/modules/Rbac/rbac.controller.d.ts.map +1 -1
- package/dist/src/modules/Rbac/rbac.controller.js +141 -140
- package/dist/src/routes/index.d.ts.map +1 -1
- package/dist/src/routes/index.js +0 -5
- package/doc/en/CHEATSHEET.md +3 -7
- package/doc/en/CLI.md +16 -41
- package/doc/en/DEPLOYMENT.md +171 -245
- package/doc/en/GETTING_STARTED.md +1 -25
- package/doc/en/PACKAGES.md +2 -3
- package/doc/en/STRUCTURE.md +1 -11
- package/doc/en/TUTORIAL.md +61 -119
- package/doc/id/CHANGELOG.md +16 -0
- package/doc/id/CHEATSHEET.md +0 -4
- package/doc/id/CLI.md +19 -54
- package/doc/id/DEPLOYMENT.md +171 -245
- package/doc/id/GETTING_STARTED.md +91 -115
- package/doc/id/PACKAGES.md +0 -1
- package/doc/id/STRUCTURE.md +1 -11
- package/doc/id/TUTORIAL.md +51 -109
- package/gitignore.template +0 -10
- package/lib/bootstrap.ts +39 -38
- package/lib/core/store.ts +116 -0
- package/lib/middleware/error.ts +1 -21
- package/lib/utils/validator.ts +3 -39
- package/package.json +4 -18
- package/scripts/init-project.js +2 -108
- package/scripts/make-module.js +1 -12
- package/scripts/seed-json.js +158 -0
- package/src/modules/Auth/auth.controller.ts +156 -106
- package/src/modules/Rbac/rbac.controller.ts +193 -138
- package/src/routes/index.ts +0 -3
- package/src/routes/rbac.ts +42 -42
- package/storage/logs/.0337f5062fe676994d1dc340156e089444e3d6e0-audit.json +5 -10
- package/storage/logs/lapeh-2025-12-30.log +1093 -0
- package/tsconfig.build.json +1 -3
- package/tsconfig.json +0 -1
- package/lib/core/database.ts +0 -5
- package/prisma/base.prisma.template +0 -8
- package/prisma/migrations/20251225163737_init/migration.sql +0 -236
- package/prisma/migrations/20251226000329_create_pets_table/migration.sql +0 -11
- package/prisma/migrations/20251226001249_create_pets_table/migration.sql +0 -82
- package/prisma/migrations/20251226001717_restore_core_models/migration.sql +0 -236
- package/prisma/migrations/migration_lock.toml +0 -3
- package/prisma/schema.prisma +0 -197
- package/prisma/seed.ts +0 -411
- package/scripts/compile-schema.js +0 -64
- package/src/modules/Auth/auth.prisma +0 -106
- package/src/modules/Pets/pets.controller.ts +0 -238
- package/src/modules/Pets/pets.prisma +0 -9
- package/src/modules/Rbac/rbac.prisma +0 -68
- package/src/routes/pets.ts +0 -13
- package/storage/logs/lapeh-2025-12-26.log +0 -88
- package/storage/logs/lapeh-2025-12-27.log +0 -217
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface User {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name: string;
|
|
5
|
+
password?: string;
|
|
6
|
+
uuid: string;
|
|
7
|
+
avatar?: string | null;
|
|
8
|
+
avatar_url?: string | null;
|
|
9
|
+
email_verified_at?: string | Date | null;
|
|
10
|
+
created_at: string | Date;
|
|
11
|
+
updated_at: string | Date;
|
|
12
|
+
}
|
|
13
|
+
export interface Role {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
description?: string | null;
|
|
18
|
+
created_at: string | Date;
|
|
19
|
+
updated_at: string | Date;
|
|
20
|
+
}
|
|
21
|
+
export interface Permission {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
slug: string;
|
|
25
|
+
description?: string | null;
|
|
26
|
+
created_at: string | Date;
|
|
27
|
+
updated_at: string | Date;
|
|
28
|
+
}
|
|
29
|
+
export interface UserRole {
|
|
30
|
+
id: string;
|
|
31
|
+
user_id: string;
|
|
32
|
+
role_id: string;
|
|
33
|
+
created_at: string | Date;
|
|
34
|
+
}
|
|
35
|
+
export interface RolePermission {
|
|
36
|
+
id: string;
|
|
37
|
+
role_id: string;
|
|
38
|
+
permission_id: string;
|
|
39
|
+
created_at: string | Date;
|
|
40
|
+
}
|
|
41
|
+
export interface UserPermission {
|
|
42
|
+
id: string;
|
|
43
|
+
user_id: string;
|
|
44
|
+
permission_id: string;
|
|
45
|
+
created_at: string | Date;
|
|
46
|
+
}
|
|
47
|
+
export declare const users: User[];
|
|
48
|
+
export declare const roles: Role[];
|
|
49
|
+
export declare const permissions: Permission[];
|
|
50
|
+
export declare const user_roles: UserRole[];
|
|
51
|
+
export declare const role_permissions: RolePermission[];
|
|
52
|
+
export declare const user_permissions: UserPermission[];
|
|
53
|
+
export declare function saveStore(): void;
|
|
54
|
+
export declare const generateId: () => string;
|
|
55
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../lib/core/store.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACzC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAyCD,eAAO,MAAM,KAAK,EAAE,IAAI,EAAe,CAAC;AACxC,eAAO,MAAM,KAAK,EAAE,IAAI,EAAe,CAAC;AACxC,eAAO,MAAM,WAAW,EAAE,UAAU,EAAqB,CAAC;AAC1D,eAAO,MAAM,UAAU,EAAE,QAAQ,EAAoB,CAAC;AACtD,eAAO,MAAM,gBAAgB,EAAE,cAAc,EAA0B,CAAC;AACxE,eAAO,MAAM,gBAAgB,EAAE,cAAc,EAA0B,CAAC;AAGxE,wBAAgB,SAAS,SAUxB;AAGD,eAAO,MAAM,UAAU,cAAgD,CAAC"}
|
|
@@ -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(), "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;
|
|
@@ -1 +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;
|
|
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"}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.errorHandler = errorHandler;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
-
const client_1 = require("@prisma/client");
|
|
6
5
|
const response_1 = require("../utils/response");
|
|
7
6
|
const logger_1 = require("../utils/logger");
|
|
8
7
|
function errorHandler(err, req, res, _next) {
|
|
@@ -14,25 +13,7 @@ function errorHandler(err, req, res, _next) {
|
|
|
14
13
|
}));
|
|
15
14
|
return (0, response_1.sendError)(res, 400, "Validation Error", formattedErrors);
|
|
16
15
|
}
|
|
17
|
-
// 2.
|
|
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
|
|
16
|
+
// 2. JWT Errors
|
|
36
17
|
if (err.name === "JsonWebTokenError") {
|
|
37
18
|
return (0, response_1.sendError)(res, 401, "Invalid token");
|
|
38
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../lib/utils/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,SAAS,EAAsB,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../lib/utils/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,SAAS,EAAsB,MAAM,KAAK,CAAC;AAEvD,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAkB;gBAG9B,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5C,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAavC;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CACT,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5C,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAiDvC,OAAO,CAAC,MAAM,CAAC,eAAe;IAsL9B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAK/B;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAIhC;;OAEG;IACH,MAAM;IAKN;;OAEG;IACG,SAAS;YAUD,GAAG;IAgBjB,OAAO,CAAC,YAAY;IAuBpB,OAAO,CAAC,gBAAgB;CAoDzB"}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Validator = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
-
const database_1 = require("../core/database");
|
|
6
5
|
class Validator {
|
|
7
6
|
constructor(data, schema, messages = {}) {
|
|
8
7
|
this.result = null;
|
|
@@ -205,37 +204,9 @@ class Validator {
|
|
|
205
204
|
break;
|
|
206
205
|
case "unique":
|
|
207
206
|
// unique:table,column,ignore,idColumn
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return true;
|
|
212
|
-
const where = { [column]: val };
|
|
213
|
-
if (ignoreValue && ignoreValue !== "null") {
|
|
214
|
-
// Try to handle numeric IDs if ignoreValue looks numeric
|
|
215
|
-
const ignoreVal = !isNaN(Number(ignoreValue))
|
|
216
|
-
? Number(ignoreValue)
|
|
217
|
-
: ignoreValue;
|
|
218
|
-
// But Prisma uses BigInt for IDs often in this project?
|
|
219
|
-
// Let's assume string or number is fine, user can cast if needed.
|
|
220
|
-
// In this project, IDs are BigInt.
|
|
221
|
-
if (typeof ignoreVal === "number" ||
|
|
222
|
-
/^\d+$/.test(String(ignoreValue))) {
|
|
223
|
-
where[ignoreColumn] = { not: BigInt(ignoreValue) };
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
where[ignoreColumn] = { not: ignoreValue };
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
try {
|
|
230
|
-
// @ts-ignore
|
|
231
|
-
const count = await database_1.prisma[table].count({ where });
|
|
232
|
-
return count === 0;
|
|
233
|
-
}
|
|
234
|
-
catch (e) {
|
|
235
|
-
console.error(`Validator unique check failed for table ${table}:`, e);
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
}, { message: `The ${column} has already been taken.` });
|
|
207
|
+
// NOTE: Unique check requires Database implementation.
|
|
208
|
+
// Since v3.0.0 (No-ORM), this rule is disabled by default.
|
|
209
|
+
// You should implement your own uniqueness check manually in the controller.
|
|
239
210
|
break;
|
|
240
211
|
}
|
|
241
212
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../../../src/modules/Auth/auth.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../../../src/modules/Auth/auth.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAU5C,eAAO,MAAM,+BAA+B,QAAmB,CAAC;AA4EhE,wBAAsB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBA0DzD;AAED,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBA6EtD;AAED,wBAAsB,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAuDnD;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBASxD;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAoE7D;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAwD7D;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBA6C/D;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAkD9D"}
|
|
@@ -15,10 +15,11 @@ exports.updateProfile = updateProfile;
|
|
|
15
15
|
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
16
16
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
17
17
|
const uuid_1 = require("uuid");
|
|
18
|
-
const database_1 = require("../../../lib/core/database");
|
|
19
18
|
const response_1 = require("../../../lib/utils/response");
|
|
20
19
|
const validator_1 = require("../../../lib/utils/validator");
|
|
21
20
|
const serializer_1 = require("../../../lib/core/serializer");
|
|
21
|
+
const store_1 = require("../../../lib/core/store");
|
|
22
|
+
const redis_1 = require("../../../lib/core/redis");
|
|
22
23
|
exports.ACCESS_TOKEN_EXPIRES_IN_SECONDS = 7 * 24 * 60 * 60;
|
|
23
24
|
// --- Serializers ---
|
|
24
25
|
const registerSchema = {
|
|
@@ -83,37 +84,43 @@ async function register(req, res) {
|
|
|
83
84
|
return;
|
|
84
85
|
}
|
|
85
86
|
const { email, name, password } = await validator.validated();
|
|
86
|
-
// Manual unique check
|
|
87
|
+
// Manual unique check (In-Memory)
|
|
88
|
+
if (store_1.users.find((u) => u.email === email)) {
|
|
89
|
+
(0, response_1.sendError)(res, 422, "Validation error", { email: "Email already taken" });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
87
92
|
const hash = await bcryptjs_1.default.hash(password, 10);
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
93
|
+
const newUser = {
|
|
94
|
+
id: (store_1.users.length + 1).toString(), // Simple ID generation
|
|
95
|
+
email,
|
|
96
|
+
name,
|
|
97
|
+
password: hash,
|
|
98
|
+
uuid: (0, uuid_1.v4)(),
|
|
99
|
+
created_at: new Date(),
|
|
100
|
+
updated_at: new Date(),
|
|
101
|
+
avatar: null,
|
|
102
|
+
avatar_url: null,
|
|
103
|
+
email_verified_at: null,
|
|
104
|
+
remember_token: null,
|
|
105
|
+
};
|
|
106
|
+
store_1.users.push(newUser);
|
|
107
|
+
const defaultRole = store_1.roles.find((r) => r.slug === "user");
|
|
101
108
|
if (defaultRole) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
},
|
|
109
|
+
store_1.user_roles.push({
|
|
110
|
+
id: (store_1.user_roles.length + 1).toString(),
|
|
111
|
+
user_id: newUser.id,
|
|
112
|
+
role_id: defaultRole.id,
|
|
113
|
+
created_at: new Date(),
|
|
108
114
|
});
|
|
109
115
|
}
|
|
116
|
+
(0, store_1.saveStore)();
|
|
110
117
|
(0, response_1.sendFastSuccess)(res, 201, registerSerializer, {
|
|
111
118
|
status: "success",
|
|
112
|
-
message: "Registration successful",
|
|
119
|
+
message: "Registration successful. You can now login.",
|
|
113
120
|
data: {
|
|
114
|
-
id:
|
|
115
|
-
email:
|
|
116
|
-
name:
|
|
121
|
+
id: newUser.id.toString(),
|
|
122
|
+
email: newUser.email,
|
|
123
|
+
name: newUser.name,
|
|
117
124
|
role: defaultRole ? defaultRole.slug : "user",
|
|
118
125
|
},
|
|
119
126
|
});
|
|
@@ -128,16 +135,7 @@ async function login(req, res) {
|
|
|
128
135
|
return;
|
|
129
136
|
}
|
|
130
137
|
const { email, password } = await validator.validated();
|
|
131
|
-
const user =
|
|
132
|
-
where: { email },
|
|
133
|
-
include: {
|
|
134
|
-
user_roles: {
|
|
135
|
-
include: {
|
|
136
|
-
role: true,
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
});
|
|
138
|
+
const user = store_1.users.find((u) => u.email === email);
|
|
141
139
|
if (!user) {
|
|
142
140
|
(0, response_1.sendError)(res, 401, "Email not registered", {
|
|
143
141
|
field: "email",
|
|
@@ -145,7 +143,7 @@ async function login(req, res) {
|
|
|
145
143
|
});
|
|
146
144
|
return;
|
|
147
145
|
}
|
|
148
|
-
const ok = await bcryptjs_1.default.compare(password, user.password);
|
|
146
|
+
const ok = await bcryptjs_1.default.compare(password, user.password || "");
|
|
149
147
|
if (!ok) {
|
|
150
148
|
(0, response_1.sendError)(res, 401, "Invalid credentials", {
|
|
151
149
|
field: "password",
|
|
@@ -158,8 +156,13 @@ async function login(req, res) {
|
|
|
158
156
|
(0, response_1.sendError)(res, 500, "Server misconfigured");
|
|
159
157
|
return;
|
|
160
158
|
}
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
// Find user roles
|
|
160
|
+
const userRoleLinks = store_1.user_roles.filter((ur) => ur.user_id === user.id);
|
|
161
|
+
const userRoleObjects = userRoleLinks
|
|
162
|
+
.map((ur) => store_1.roles.find((r) => r.id === ur.role_id))
|
|
163
|
+
.filter((r) => r);
|
|
164
|
+
const primaryUserRole = userRoleObjects.length > 0 && userRoleObjects[0]
|
|
165
|
+
? userRoleObjects[0].slug
|
|
163
166
|
: "user";
|
|
164
167
|
const accessExpiresInSeconds = exports.ACCESS_TOKEN_EXPIRES_IN_SECONDS;
|
|
165
168
|
const accessExpiresAt = new Date(Date.now() + accessExpiresInSeconds * 1000).toISOString();
|
|
@@ -189,31 +192,40 @@ async function me(req, res) {
|
|
|
189
192
|
(0, response_1.sendError)(res, 401, "Unauthorized");
|
|
190
193
|
return;
|
|
191
194
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
195
|
+
// Try to get from Redis
|
|
196
|
+
const cachedUser = await redis_1.redis.get(`user:${payload.userId}`);
|
|
197
|
+
if (cachedUser) {
|
|
198
|
+
const user = JSON.parse(cachedUser);
|
|
199
|
+
(0, response_1.sendFastSuccess)(res, 200, userProfileSerializer, {
|
|
200
|
+
status: "success",
|
|
201
|
+
message: "User profile (cached)",
|
|
202
|
+
data: user,
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const user = store_1.users.find((u) => u.id === payload.userId);
|
|
202
207
|
if (!user) {
|
|
203
208
|
(0, response_1.sendError)(res, 404, "User not found");
|
|
204
209
|
return;
|
|
205
210
|
}
|
|
206
|
-
|
|
211
|
+
// Find user roles
|
|
212
|
+
const userRoleLinks = store_1.user_roles.filter((ur) => ur.user_id === user.id);
|
|
213
|
+
const userRoleObjects = userRoleLinks
|
|
214
|
+
.map((ur) => store_1.roles.find((r) => r.id === ur.role_id))
|
|
215
|
+
.filter((r) => r);
|
|
216
|
+
const primaryRoleSlug = userRoleObjects.length > 0 ? userRoleObjects[0]?.slug : "user";
|
|
217
|
+
const { password, ...rest } = user;
|
|
218
|
+
const userData = {
|
|
219
|
+
...rest,
|
|
220
|
+
id: user.id.toString(),
|
|
221
|
+
role: primaryRoleSlug,
|
|
222
|
+
};
|
|
223
|
+
// Cache in Redis for 1 hour
|
|
224
|
+
await redis_1.redis.set(`user:${payload.userId}`, JSON.stringify(userData), "EX", 3600);
|
|
207
225
|
(0, response_1.sendFastSuccess)(res, 200, userProfileSerializer, {
|
|
208
226
|
status: "success",
|
|
209
227
|
message: "User profile",
|
|
210
|
-
data:
|
|
211
|
-
...rest,
|
|
212
|
-
id: user.id.toString(),
|
|
213
|
-
role: user.user_roles && user.user_roles.length > 0 && user.user_roles[0].role
|
|
214
|
-
? user.user_roles[0].role.slug
|
|
215
|
-
: "user",
|
|
216
|
-
},
|
|
228
|
+
data: userData,
|
|
217
229
|
});
|
|
218
230
|
}
|
|
219
231
|
async function logout(_req, res) {
|
|
@@ -246,22 +258,18 @@ async function refreshToken(req, res) {
|
|
|
246
258
|
(0, response_1.sendError)(res, 401, "Invalid refresh token");
|
|
247
259
|
return;
|
|
248
260
|
}
|
|
249
|
-
const user =
|
|
250
|
-
where: { id: decoded.userId },
|
|
251
|
-
include: {
|
|
252
|
-
user_roles: {
|
|
253
|
-
include: {
|
|
254
|
-
role: true,
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
});
|
|
261
|
+
const user = store_1.users.find((u) => u.id === decoded.userId);
|
|
259
262
|
if (!user) {
|
|
260
263
|
(0, response_1.sendError)(res, 401, "Invalid refresh token");
|
|
261
264
|
return;
|
|
262
265
|
}
|
|
263
|
-
|
|
264
|
-
|
|
266
|
+
// Find user roles
|
|
267
|
+
const userRoleLinks = store_1.user_roles.filter((ur) => ur.user_id === user.id);
|
|
268
|
+
const userRoleObjects = userRoleLinks
|
|
269
|
+
.map((ur) => store_1.roles.find((r) => r.id === ur.role_id))
|
|
270
|
+
.filter((r) => r);
|
|
271
|
+
const primaryUserRole = userRoleObjects.length > 0 && userRoleObjects[0]
|
|
272
|
+
? userRoleObjects[0].slug
|
|
265
273
|
: "user";
|
|
266
274
|
const accessExpiresInSeconds = exports.ACCESS_TOKEN_EXPIRES_IN_SECONDS;
|
|
267
275
|
const accessExpiresAt = new Date(Date.now() + accessExpiresInSeconds * 1000).toISOString();
|
|
@@ -306,21 +314,19 @@ async function updateAvatar(req, res) {
|
|
|
306
314
|
const userId = payload.userId;
|
|
307
315
|
const avatar = file.filename;
|
|
308
316
|
const avatar_url = process.env.AVATAR_BASE_URL || `/uploads/avatars/${file.filename}`;
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
// Actually `update` returns what was updated. Relations are not included unless specified.
|
|
323
|
-
// For now we will return it compatible with userProfileSchema.
|
|
317
|
+
const userIndex = store_1.users.findIndex((u) => u.id === userId);
|
|
318
|
+
if (userIndex === -1) {
|
|
319
|
+
(0, response_1.sendError)(res, 404, "User not found");
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
store_1.users[userIndex] = {
|
|
323
|
+
...store_1.users[userIndex],
|
|
324
|
+
avatar,
|
|
325
|
+
avatar_url,
|
|
326
|
+
updated_at: new Date(),
|
|
327
|
+
};
|
|
328
|
+
const updated = store_1.users[userIndex];
|
|
329
|
+
const { password, ...rest } = updated;
|
|
324
330
|
(0, response_1.sendFastSuccess)(res, 200, userProfileSerializer, {
|
|
325
331
|
status: "success",
|
|
326
332
|
message: "Avatar updated successfully",
|
|
@@ -347,14 +353,13 @@ async function updatePassword(req, res) {
|
|
|
347
353
|
return;
|
|
348
354
|
}
|
|
349
355
|
const { currentPassword, newPassword } = await validator.validated();
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
});
|
|
353
|
-
if (!user) {
|
|
356
|
+
const userIndex = store_1.users.findIndex((u) => u.id === payload.userId);
|
|
357
|
+
if (userIndex === -1) {
|
|
354
358
|
(0, response_1.sendError)(res, 404, "User not found");
|
|
355
359
|
return;
|
|
356
360
|
}
|
|
357
|
-
const
|
|
361
|
+
const user = store_1.users[userIndex];
|
|
362
|
+
const ok = await bcryptjs_1.default.compare(currentPassword, user.password || "");
|
|
358
363
|
if (!ok) {
|
|
359
364
|
(0, response_1.sendError)(res, 401, "Invalid credentials", {
|
|
360
365
|
field: "currentPassword",
|
|
@@ -363,13 +368,11 @@ async function updatePassword(req, res) {
|
|
|
363
368
|
return;
|
|
364
369
|
}
|
|
365
370
|
const hash = await bcryptjs_1.default.hash(newPassword, 10);
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
},
|
|
372
|
-
});
|
|
371
|
+
store_1.users[userIndex] = {
|
|
372
|
+
...user,
|
|
373
|
+
password: hash,
|
|
374
|
+
updated_at: new Date(),
|
|
375
|
+
};
|
|
373
376
|
(0, response_1.sendFastSuccess)(res, 200, voidSerializer, {
|
|
374
377
|
status: "success",
|
|
375
378
|
message: "Password updated successfully",
|
|
@@ -392,16 +395,26 @@ async function updateProfile(req, res) {
|
|
|
392
395
|
}
|
|
393
396
|
const { name, email } = await validator.validated();
|
|
394
397
|
const userId = payload.userId;
|
|
395
|
-
// Manual unique check
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
398
|
+
// Manual unique check (In-Memory)
|
|
399
|
+
if (store_1.users.find((u) => u.email === email && u.id !== userId)) {
|
|
400
|
+
(0, response_1.sendError)(res, 422, "Validation error", { email: "Email already taken" });
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const userIndex = store_1.users.findIndex((u) => u.id === userId);
|
|
404
|
+
if (userIndex === -1) {
|
|
405
|
+
(0, response_1.sendError)(res, 404, "User not found");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
store_1.users[userIndex] = {
|
|
409
|
+
...store_1.users[userIndex],
|
|
410
|
+
name,
|
|
411
|
+
email,
|
|
412
|
+
updated_at: new Date(),
|
|
413
|
+
};
|
|
414
|
+
(0, store_1.saveStore)();
|
|
415
|
+
await redis_1.redis.del(`user:${userId}`);
|
|
416
|
+
const updated = store_1.users[userIndex];
|
|
417
|
+
const { password, ...rest } = updated;
|
|
405
418
|
(0, response_1.sendFastSuccess)(res, 200, userProfileSerializer, {
|
|
406
419
|
status: "success",
|
|
407
420
|
message: "Profile updated successfully",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rbac.controller.d.ts","sourceRoot":"","sources":["../../../../src/modules/Rbac/rbac.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"rbac.controller.d.ts","sourceRoot":"","sources":["../../../../src/modules/Rbac/rbac.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAkE5C,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAoC3D;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAO3D;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBA+C3D;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBA+B3D;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAmCjE;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAUjE;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBA+CjE;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBA+BjE;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAyCjE;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAuBnE;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAyCvE;AAED,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBA0BzE;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAwCvE;AAED,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAyBzE"}
|