@appxdigital/appx-core 0.1.119 → 0.1.121-beta.4
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/LICENSE +21 -21
- package/README.md +564 -533
- package/dist/backoffice/appx-core-admin.module.d.ts.map +1 -1
- package/dist/backoffice/appx-core-admin.module.js +3 -3
- package/dist/common/interceptors/prisma.interceptor.d.ts.map +1 -1
- package/dist/common/interceptors/prisma.interceptor.js +18 -6
- package/dist/common/setup/core-config.d.ts +66 -0
- package/dist/common/setup/core-config.d.ts.map +1 -0
- package/dist/common/setup/core-config.js +67 -0
- package/dist/common/setup/setup-core-security.d.ts +62 -0
- package/dist/common/setup/setup-core-security.d.ts.map +1 -0
- package/dist/common/setup/setup-core-security.js +40 -0
- package/dist/config/generate-all.js +4 -0
- package/dist/config/generate-controllers.js +19 -2
- package/dist/config/generate-dtos.d.ts +2 -0
- package/dist/config/generate-dtos.d.ts.map +1 -0
- package/dist/config/generate-dtos.js +199 -0
- package/dist/config/utils.d.ts +14 -0
- package/dist/config/utils.d.ts.map +1 -1
- package/dist/config/utils.js +35 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/modules/auth/auth.controller.d.ts.map +1 -1
- package/dist/modules/auth/auth.controller.js +6 -2
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/core/core.controller.d.ts.map +1 -1
- package/dist/modules/core/core.controller.js +12 -5
- package/dist/modules/user/user.service.d.ts.map +1 -1
- package/dist/modules/user/user.service.js +7 -3
- package/dist/prisma/prisma.service.d.ts +45 -0
- package/dist/prisma/prisma.service.d.ts.map +1 -1
- package/dist/prisma/prisma.service.js +208 -3
- package/package.json +108 -86
package/dist/config/utils.js
CHANGED
|
@@ -23,8 +23,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.IGNORE_FOLDERS = exports.createFileIfNotExists = exports.kebabToPascalCase = void 0;
|
|
26
|
+
exports.IGNORE_FOLDERS = exports.writeGeneratedFile = exports.GENERATED_BANNER = exports.createFileIfNotExists = exports.pascalToKebabCase = exports.kebabToPascalCase = void 0;
|
|
27
27
|
const fs = __importStar(require("fs"));
|
|
28
|
+
const path = __importStar(require("path"));
|
|
28
29
|
const kebabToPascalCase = (str) => {
|
|
29
30
|
return str
|
|
30
31
|
.split("-")
|
|
@@ -32,9 +33,17 @@ const kebabToPascalCase = (str) => {
|
|
|
32
33
|
.join("");
|
|
33
34
|
};
|
|
34
35
|
exports.kebabToPascalCase = kebabToPascalCase;
|
|
36
|
+
const pascalToKebabCase = (str) => {
|
|
37
|
+
return str
|
|
38
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
39
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
40
|
+
.toLowerCase();
|
|
41
|
+
};
|
|
42
|
+
exports.pascalToKebabCase = pascalToKebabCase;
|
|
35
43
|
const createFileIfNotExists = (filePath, content) => {
|
|
36
44
|
console.log(`Creating ${filePath}...`);
|
|
37
45
|
if (!fs.existsSync(filePath)) {
|
|
46
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
38
47
|
fs.writeFileSync(filePath, content);
|
|
39
48
|
console.log(`${filePath} created.`);
|
|
40
49
|
}
|
|
@@ -43,9 +52,33 @@ const createFileIfNotExists = (filePath, content) => {
|
|
|
43
52
|
}
|
|
44
53
|
};
|
|
45
54
|
exports.createFileIfNotExists = createFileIfNotExists;
|
|
55
|
+
/**
|
|
56
|
+
* Banner stamped on every fully-generated file. These files live under
|
|
57
|
+
* `src/generated/` (gitignored) and are OVERWRITTEN on every `appx generate`.
|
|
58
|
+
* Never hand-edit them — customize the hand-owned subclass under
|
|
59
|
+
* `src/modules/<model>/dto/` instead.
|
|
60
|
+
*/
|
|
61
|
+
exports.GENERATED_BANNER = '// @Generated by appx-core — do not edit.\n' +
|
|
62
|
+
'// Regenerated by `appx generate`; lives under src/generated (gitignored).\n' +
|
|
63
|
+
'// To customize, edit the hand-owned subclass in src/modules/<model>/dto.\n';
|
|
64
|
+
/**
|
|
65
|
+
* Writes a fully-generated file, always overwriting. Use only for artifacts
|
|
66
|
+
* under `src/generated/` that carry {@link GENERATED_BANNER}. Creates parent
|
|
67
|
+
* directories as needed.
|
|
68
|
+
*/
|
|
69
|
+
const writeGeneratedFile = (filePath, content) => {
|
|
70
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
71
|
+
fs.writeFileSync(filePath, content);
|
|
72
|
+
console.log(`Generated ${filePath}`);
|
|
73
|
+
};
|
|
74
|
+
exports.writeGeneratedFile = writeGeneratedFile;
|
|
46
75
|
exports.IGNORE_FOLDERS = [
|
|
47
76
|
'prisma',
|
|
48
77
|
'schema.gql',
|
|
49
78
|
'session',
|
|
50
|
-
'user-refresh-token'
|
|
79
|
+
'user-refresh-token',
|
|
80
|
+
// Generated DTOs live under src/generated/dto/. The model generators scan
|
|
81
|
+
// src/generated's top-level dirs as model names, so this must be skipped or
|
|
82
|
+
// they emit a bogus `dto` module.
|
|
83
|
+
'dto',
|
|
51
84
|
];
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export * from './common/interfaces/storage-service.interface';
|
|
|
22
22
|
export * from './modules/file/file-upload.controller';
|
|
23
23
|
export * from './modules/file/file-upload.service';
|
|
24
24
|
export * from './common/decorators/permission.decorator';
|
|
25
|
+
export * from './common/setup/setup-core-security';
|
|
26
|
+
export * from './common/setup/core-config';
|
|
25
27
|
export * from './common/interceptors/file.interceptor';
|
|
26
28
|
export * from './modules/auth/authenticated.guard';
|
|
27
29
|
export * from './modules/auth/jwt-auth.guard';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sCAAsC,CAAC;AACrD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uCAAuC,CAAC;AACtD,cAAc,yBAAyB,CAAC;AACxC,cAAc,0CAA0C,CAAC;AACzD,cAAc,wCAAwC,CAAC;AACvD,cAAc,iCAAiC,CAAC;AAChD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,oBAAoB,CAAC;AACnC,cAAc,mCAAmC,CAAC;AAClD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qCAAqC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sCAAsC,CAAC;AACrD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uCAAuC,CAAC;AACtD,cAAc,yBAAyB,CAAC;AACxC,cAAc,0CAA0C,CAAC;AACzD,cAAc,wCAAwC,CAAC;AACvD,cAAc,iCAAiC,CAAC;AAChD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,oBAAoB,CAAC;AACnC,cAAc,mCAAmC,CAAC;AAClD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qCAAqC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,8 @@ __exportStar(require("./common/interfaces/storage-service.interface"), exports);
|
|
|
38
38
|
__exportStar(require("./modules/file/file-upload.controller"), exports);
|
|
39
39
|
__exportStar(require("./modules/file/file-upload.service"), exports);
|
|
40
40
|
__exportStar(require("./common/decorators/permission.decorator"), exports);
|
|
41
|
+
__exportStar(require("./common/setup/setup-core-security"), exports);
|
|
42
|
+
__exportStar(require("./common/setup/core-config"), exports);
|
|
41
43
|
__exportStar(require("./common/interceptors/file.interceptor"), exports);
|
|
42
44
|
__exportStar(require("./modules/auth/authenticated.guard"), exports);
|
|
43
45
|
__exportStar(require("./modules/auth/jwt-auth.guard"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAG/C,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AAI1C,qBACa,cAAc;IACX,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW;gBAAxB,WAAW,EAAE,WAAW;IAGjD,QAAQ,CAAS,WAAW,EAAE,WAAW;;;;IAMzC,KAAK,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;IAW/C,MAAM,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhE,EAAE,CAAQ,GAAG,EAAE,OAAO;;;IAQtB,iBAAiB,CAAQ,GAAG,EAAE,OAAO;;;IAOrC,gBAAgB,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;
|
|
1
|
+
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAG/C,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AAI1C,qBACa,cAAc;IACX,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW;gBAAxB,WAAW,EAAE,WAAW;IAGjD,QAAQ,CAAS,WAAW,EAAE,WAAW;;;;IAMzC,KAAK,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;IAW/C,MAAM,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhE,EAAE,CAAQ,GAAG,EAAE,OAAO;;;IAQtB,iBAAiB,CAAQ,GAAG,EAAE,OAAO;;;IAOrC,gBAAgB,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;IAM1D,cAAc,CAAkB,MAAM,EAAE,MAAM,EAAS,GAAG,EAAE,OAAO;;;IAanE,oBAAoB,CACF,SAAS,EAAE,MAAM,EAC9B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ;IAgBlB,QAAQ,CAAQ,GAAG,EAAE,OAAO;;;;;IAM5B,aAAa,CAAQ,GAAG,EAAE,OAAO;;;;IAOjC,SAAS,CAAQ,GAAG,EAAE,OAAO;;;CAQtC"}
|
|
@@ -64,7 +64,9 @@ let AuthController = exports.AuthController = class AuthController {
|
|
|
64
64
|
return this.authService.closeAllUserSessions(req, res);
|
|
65
65
|
}
|
|
66
66
|
async getAllSessions(userId, req) {
|
|
67
|
-
//
|
|
67
|
+
// AuthenticatedGuard gates authentication explicitly; the role check
|
|
68
|
+
// below is the authorization step. De-hardcoding 'ADMIN' into the
|
|
69
|
+
// permissions system is deferred (see ROADMAP / ).
|
|
68
70
|
if (req.user?.role !== 'ADMIN') {
|
|
69
71
|
throw new common_1.ForbiddenException('Access denied.');
|
|
70
72
|
}
|
|
@@ -72,7 +74,7 @@ let AuthController = exports.AuthController = class AuthController {
|
|
|
72
74
|
return { sessions };
|
|
73
75
|
}
|
|
74
76
|
async closeSpecificSession(sessionId, req, res) {
|
|
75
|
-
//
|
|
77
|
+
// See getAllSessions: AuthenticatedGuard = authn, role check = authz.
|
|
76
78
|
if (req.user?.role !== 'ADMIN') {
|
|
77
79
|
throw new common_1.ForbiddenException('Access denied.');
|
|
78
80
|
}
|
|
@@ -151,6 +153,7 @@ __decorate([
|
|
|
151
153
|
__metadata("design:returntype", Promise)
|
|
152
154
|
], AuthController.prototype, "closeAllSessions", null);
|
|
153
155
|
__decorate([
|
|
156
|
+
(0, common_1.UseGuards)(authenticated_guard_1.AuthenticatedGuard),
|
|
154
157
|
(0, common_1.Get)('sessions/:userId'),
|
|
155
158
|
__param(0, (0, common_1.Param)('userId')),
|
|
156
159
|
__param(1, (0, common_1.Req)()),
|
|
@@ -159,6 +162,7 @@ __decorate([
|
|
|
159
162
|
__metadata("design:returntype", Promise)
|
|
160
163
|
], AuthController.prototype, "getAllSessions", null);
|
|
161
164
|
__decorate([
|
|
165
|
+
(0, common_1.UseGuards)(authenticated_guard_1.AuthenticatedGuard),
|
|
162
166
|
(0, common_1.Post)('sessions/:sessionId/close'),
|
|
163
167
|
__param(0, (0, common_1.Param)('sessionId')),
|
|
164
168
|
__param(1, (0, common_1.Req)()),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAC,UAAU,
|
|
1
|
+
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAC,UAAU,EAAiB,MAAM,aAAa,CAAC;AAKvD,qBACa,WAAW;IAIhB,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW;IAC3C,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,aAAa;IACxC,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,UAAU;IACzC,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,aAAa;IANnD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;gBAGpB,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,aAAa;IAK7C,QAAQ,CAAC,WAAW,EAAE,WAAW;;;;IA2BjC,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAC,CAAC;IA8B1D,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBlD,YAAY,CACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACtB,OAAO,CAAC,GAAG,CAAC;IAuBT,cAAc,CAAC,GAAG,EAAE,OAAO;IAW3B,iBAAiB,CAAC,GAAG,EAAE,OAAO;IAW9B,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ;IA0BhD,mBAAmB,CAAC,MAAM,EAAE,MAAM;IAMlC,oBAAoB,CAAC,SAAS,EAAE,MAAM;IAOtC,kBAAkB,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;YActC,cAAc;IAkCtB,aAAa,CAAC,yBAAyB,EAAE,GAAG,GAAG,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAC,CAAC;IA4B3G,OAAO,CAAC,WAAW;IAUb,QAAQ,CAAC,iBAAiB,EAAE,GAAG,GAAG,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAC,CAAC;IAcnG,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAUlE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.controller.d.ts","sourceRoot":"","sources":["../../../src/modules/core/core.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAI3C,OAAO,EAAC,kBAAkB,EAAC,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"core.controller.d.ts","sourceRoot":"","sources":["../../../src/modules/core/core.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAI3C,OAAO,EAAC,kBAAkB,EAAC,MAAM,yCAAyC,CAAC;AAS3E,8BACsB,cAAc,CAAC,CAAC;IAM9B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;IAL9C,MAAM,KAAK,UAAU,IAAI,MAAM,CAE9B;IAED,SAAS,aACc,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;IAK9C,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAG,kBAAkB,CAAC;IAIrD,OAAO;IAMP,OAAO,CAAc,EAAE,EAAE,MAAM;IAM/B,MAAM,CAAS,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAC;IAuBtD,MAAM,CAAc,EAAE,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IAsBxD,MAAM,CAAc,EAAE,EAAE,MAAM;CAGvC"}
|
|
@@ -39,10 +39,15 @@ let CoreController = exports.CoreController = CoreController_1 = class CoreContr
|
|
|
39
39
|
const model = this.constructor.entityName;
|
|
40
40
|
const rolePermissions = this.permissionsService.getPermissionsConfig()[model]?.[role];
|
|
41
41
|
const actionPermission = rolePermissions?.['create'];
|
|
42
|
-
if (typeof actionPermission !== 'string' &&
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
if (typeof actionPermission !== 'string' && actionPermission) {
|
|
43
|
+
// Strip restricted fields first, then force the server-owned user id
|
|
44
|
+
// (so setUserIdField cannot be clobbered by a restricted-field strip).
|
|
45
|
+
for (const field of (actionPermission.restrictedFields ?? [])) {
|
|
46
|
+
delete data[field];
|
|
47
|
+
}
|
|
48
|
+
if (actionPermission.setUserIdField) {
|
|
49
|
+
data[actionPermission.setUserIdField] = user.id;
|
|
50
|
+
}
|
|
46
51
|
}
|
|
47
52
|
return this.service.create(data);
|
|
48
53
|
}
|
|
@@ -51,7 +56,9 @@ let CoreController = exports.CoreController = CoreController_1 = class CoreContr
|
|
|
51
56
|
const role = user?.role || 'GUEST';
|
|
52
57
|
const model = this.constructor.entityName;
|
|
53
58
|
const rolePermissions = this.permissionsService.getPermissionsConfig()[model]?.[role];
|
|
54
|
-
|
|
59
|
+
// The action key is 'updateMany' (matches @Permission('updateMany') and
|
|
60
|
+
// the proxy) — reading 'update' here meant restrictedFields never applied.
|
|
61
|
+
const actionPermission = rolePermissions?.['updateMany'];
|
|
55
62
|
let restrictedFields = [];
|
|
56
63
|
if (typeof actionPermission !== 'string' && actionPermission?.restrictedFields) {
|
|
57
64
|
restrictedFields = actionPermission.restrictedFields;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.service.d.ts","sourceRoot":"","sources":["../../../src/modules/user/user.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAC,eAAe,EAAC,MAAM,wCAAwC,CAAC;AACvE,OAAO,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAC;AAEjD,OAAO,EAAC,IAAI,EAAC,MAAM,gBAAgB,CAAC;AAEpC,qBACa,WAAY,SAAQ,WAAW,CAAC,IAAI,CAAC;IAClC,SAAS,CAAC,MAAM,EAAE,aAAa;gBAArB,MAAM,EAAE,aAAa;IAIrC,UAAU,CAAC,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"user.service.d.ts","sourceRoot":"","sources":["../../../src/modules/user/user.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAC,eAAe,EAAC,MAAM,wCAAwC,CAAC;AACvE,OAAO,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAC;AAEjD,OAAO,EAAC,IAAI,EAAC,MAAM,gBAAgB,CAAC;AAEpC,qBACa,WAAY,SAAQ,WAAW,CAAC,IAAI,CAAC;IAClC,SAAS,CAAC,MAAM,EAAE,aAAa;gBAArB,MAAM,EAAE,aAAa;IAIrC,UAAU,CAAC,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAY3D,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAUpD,WAAW,CAAC,KAAK,EAAE,MAAM;CAYlC"}
|
|
@@ -19,9 +19,13 @@ let UserService = exports.UserService = class UserService extends core_service_1
|
|
|
19
19
|
this.prisma = prisma;
|
|
20
20
|
}
|
|
21
21
|
async createUser(createUserInput) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// Registration is an unauthenticated framework flow (role GUEST has no
|
|
23
|
+
// create permission). Its input is already constrained by RegisterDto,
|
|
24
|
+
// so bypass access filtering — otherwise the create default-deny would
|
|
25
|
+
// reject sign-up.
|
|
26
|
+
return this.prisma.user.create({ data: createUserInput },
|
|
27
|
+
// @ts-ignore — options arg is accepted by the proxy delegate
|
|
28
|
+
{ BYPASS_FILTERING: true });
|
|
25
29
|
}
|
|
26
30
|
async findByField(field, value) {
|
|
27
31
|
const where = {
|
|
@@ -17,6 +17,13 @@ export declare class PrismaService {
|
|
|
17
17
|
private readonly permissionsConfig;
|
|
18
18
|
private fieldConfigs;
|
|
19
19
|
prismaClient: PrismaClient;
|
|
20
|
+
/**
|
|
21
|
+
* The un-proxied Prisma client. Used internally (e.g. create-condition
|
|
22
|
+
* checks) to look up an already-existing parent row without re-applying
|
|
23
|
+
* that model's access filtering — matching "would a findFirst with these
|
|
24
|
+
* conditions match".
|
|
25
|
+
*/
|
|
26
|
+
private rawClient;
|
|
20
27
|
constructor(prismaClient: PrismaClient, permissionsConfig: PermissionsConfigType);
|
|
21
28
|
debugQueries(enable: boolean): void;
|
|
22
29
|
withExposedModels(models: string[], callback: () => Promise<void>): Promise<void>;
|
|
@@ -62,6 +69,44 @@ export declare class PrismaService {
|
|
|
62
69
|
* @throws ForbiddenException if no valid `where` conditions are present after applying permissions.
|
|
63
70
|
*/
|
|
64
71
|
private applyWhereConditions;
|
|
72
|
+
/**
|
|
73
|
+
* Enforces permission `conditions` on `create` / `createMany`.
|
|
74
|
+
*
|
|
75
|
+
* Conditions cannot be pushed into a `WHERE` on insert, so instead we verify
|
|
76
|
+
* the incoming data *would* satisfy them — i.e. a `findFirst` with those
|
|
77
|
+
* conditions would return the new row. Own-scalar fields are checked against
|
|
78
|
+
* `data`; relation conditions are checked by looking up the referenced parent
|
|
79
|
+
* (which lets Prisma evaluate arbitrarily-nested relation conditions for us).
|
|
80
|
+
*
|
|
81
|
+
* Also default-denies: create is only allowed for a role/action that has an
|
|
82
|
+
* explicit permission (or 'ALL'), mirroring find/update/delete. Framework
|
|
83
|
+
* flows that must create without a permission (registration, sessions,
|
|
84
|
+
* tokens) pass BYPASS_FILTERING and never reach here.
|
|
85
|
+
*
|
|
86
|
+
* Fails **closed**: throws on a violation and on any condition shape it
|
|
87
|
+
* cannot evaluate, rather than silently allowing the insert.
|
|
88
|
+
*/
|
|
89
|
+
private enforceCreateConditions;
|
|
90
|
+
/**
|
|
91
|
+
* True only in an explicit local-development environment. Deliberately NOT
|
|
92
|
+
* `!== 'production'` — staging and other envs should behave like production
|
|
93
|
+
* (no extra diagnostic queries/logging). Only `development` / `dev` opt in.
|
|
94
|
+
*/
|
|
95
|
+
private isDevMode;
|
|
96
|
+
/**
|
|
97
|
+
* Dev-only diagnostic. After a create that passed its create-conditions,
|
|
98
|
+
* checks whether the new row also satisfies the model's *find* conditions —
|
|
99
|
+
* if not, the creator can't read it back (create rule broader than find
|
|
100
|
+
* rule). Logs a warning; never affects the create. Runs only in development
|
|
101
|
+
* (gated by the caller).
|
|
102
|
+
*/
|
|
103
|
+
private warnIfCreatedRowNotFindable;
|
|
104
|
+
/** Recursively evaluates a (placeholder-resolved) condition object against a to-be-created row. */
|
|
105
|
+
private matchesCreateConditions;
|
|
106
|
+
/** Evaluates a scalar-field condition (value or Prisma operator object) against the row's value. */
|
|
107
|
+
private matchesCreateScalar;
|
|
108
|
+
/** Verifies the parent referenced by a belongsTo condition exists and satisfies the nested condition. */
|
|
109
|
+
private matchesCreateRelation;
|
|
65
110
|
/**
|
|
66
111
|
* Builds dynamic conditions based on the type of clause (OR, AND, or field conditions).
|
|
67
112
|
* Each condition is processed, and placeholders (like `$USER_ID`) are replaced with actual values.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prisma.service.d.ts","sourceRoot":"","sources":["../../src/prisma/prisma.service.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,MAAM,EAAE,YAAY,EAAC,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAC,qBAAqB,EAAC,MAAM,yCAAyC,CAAC;AAE9E,OAAO,KAAK,EAAC,YAAY,IAAI,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAGlE,OAAO,EAAC,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAEnD,eAAO,MAAM,iBAAiB;mBACX,MAAM,EAAE;EACvB,CAAC;AAEL,KAAK,QAAQ,GAAG,MAAM,aAAa,CAAC;AAEpC,oDAAoD;AACpD,MAAM,MAAM,iBAAiB,GAAG;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAGF,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,YAAY,GAAG,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC;AAEtI,qBACa,aAAa;
|
|
1
|
+
{"version":3,"file":"prisma.service.d.ts","sourceRoot":"","sources":["../../src/prisma/prisma.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -54,6 +54,10 @@ let PrismaService = exports.PrismaService = PrismaService_1 = class PrismaServic
|
|
|
54
54
|
return this.prismaClient.$transaction(fn);
|
|
55
55
|
}
|
|
56
56
|
proxyModels() {
|
|
57
|
+
// Capture the un-proxied client before wrapping it (used by internal
|
|
58
|
+
// lookups that must bypass this proxy's access filtering — see
|
|
59
|
+
// enforceCreateConditions).
|
|
60
|
+
this.rawClient = this.prismaClient;
|
|
57
61
|
// Proxy client to intercept model calls
|
|
58
62
|
this.prismaClient = new Proxy(this.prismaClient, {
|
|
59
63
|
get: (target, propKey) => {
|
|
@@ -85,8 +89,15 @@ let PrismaService = exports.PrismaService = PrismaService_1 = class PrismaServic
|
|
|
85
89
|
else {
|
|
86
90
|
this.debug(`Skipping field omission for ${String(propKey)}.${String(methodKey)}()`);
|
|
87
91
|
}
|
|
88
|
-
if (!options?.BYPASS_FILTERING
|
|
89
|
-
|
|
92
|
+
if (!options?.BYPASS_FILTERING) {
|
|
93
|
+
if (['create', 'createMany'].includes(methodKey.toString())) {
|
|
94
|
+
// Conditions can't be pushed into a WHERE on insert, so
|
|
95
|
+
// validate the incoming data satisfies them before insert.
|
|
96
|
+
await this.enforceCreateConditions(String(propKey), userRole, params, user, methodKey.toString());
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
params = this.applyWhereConditions(String(propKey), userRole, params, user, methodKey);
|
|
100
|
+
}
|
|
90
101
|
}
|
|
91
102
|
else {
|
|
92
103
|
this.debug(`Skipping permission filtering for ${String(propKey)}.${String(methodKey)}()`);
|
|
@@ -95,7 +106,14 @@ let PrismaService = exports.PrismaService = PrismaService_1 = class PrismaServic
|
|
|
95
106
|
delete params.select;
|
|
96
107
|
}
|
|
97
108
|
this.debug(`Executing ${String(propKey)}.${String(methodKey)}() with params: ${JSON.stringify(params)}`);
|
|
98
|
-
|
|
109
|
+
const result = await contextModel[methodKey](params);
|
|
110
|
+
// Dev-only: warn if a just-created row wouldn't be readable by its
|
|
111
|
+
// creator (create conditions broader than find conditions). Skipped
|
|
112
|
+
// outside development so no extra query runs on the hot path.
|
|
113
|
+
if (methodKey === 'create' && !options?.BYPASS_FILTERING && this.isDevMode()) {
|
|
114
|
+
await this.warnIfCreatedRowNotFindable(String(propKey), userRole, result);
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
99
117
|
};
|
|
100
118
|
}
|
|
101
119
|
return model[methodKey];
|
|
@@ -380,6 +398,193 @@ let PrismaService = exports.PrismaService = PrismaService_1 = class PrismaServic
|
|
|
380
398
|
}
|
|
381
399
|
return args;
|
|
382
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Enforces permission `conditions` on `create` / `createMany`.
|
|
403
|
+
*
|
|
404
|
+
* Conditions cannot be pushed into a `WHERE` on insert, so instead we verify
|
|
405
|
+
* the incoming data *would* satisfy them — i.e. a `findFirst` with those
|
|
406
|
+
* conditions would return the new row. Own-scalar fields are checked against
|
|
407
|
+
* `data`; relation conditions are checked by looking up the referenced parent
|
|
408
|
+
* (which lets Prisma evaluate arbitrarily-nested relation conditions for us).
|
|
409
|
+
*
|
|
410
|
+
* Also default-denies: create is only allowed for a role/action that has an
|
|
411
|
+
* explicit permission (or 'ALL'), mirroring find/update/delete. Framework
|
|
412
|
+
* flows that must create without a permission (registration, sessions,
|
|
413
|
+
* tokens) pass BYPASS_FILTERING and never reach here.
|
|
414
|
+
*
|
|
415
|
+
* Fails **closed**: throws on a violation and on any condition shape it
|
|
416
|
+
* cannot evaluate, rather than silently allowing the insert.
|
|
417
|
+
*/
|
|
418
|
+
async enforceCreateConditions(modelName, userRole, params, user, action) {
|
|
419
|
+
const normalizedName = modelName.toLowerCase().trim();
|
|
420
|
+
const exposedModels = (exports.CorePrismaContext.getStore()?.exposedModels || []).map((m) => m.toLowerCase());
|
|
421
|
+
if (exposedModels.includes(normalizedName))
|
|
422
|
+
return;
|
|
423
|
+
const permissions = this.normalizedPermissionsConfig[normalizedName]?.[userRole];
|
|
424
|
+
if (!permissions) {
|
|
425
|
+
throw new common_1.HttpException(`No permissions defined for role ${userRole} on model '${modelName}' — create is denied by default. Define a '${action}' permission (or 'ALL').`, common_1.HttpStatus.FORBIDDEN);
|
|
426
|
+
}
|
|
427
|
+
const actionPermissions = permissions[action] ?? permissions['create'];
|
|
428
|
+
if (!actionPermissions) {
|
|
429
|
+
throw new common_1.HttpException(`Role ${userRole} has no '${action}' permission on model '${modelName}' — create is denied. Define it (or 'ALL').`, common_1.HttpStatus.FORBIDDEN);
|
|
430
|
+
}
|
|
431
|
+
if (actionPermissions === 'ALL')
|
|
432
|
+
return;
|
|
433
|
+
const conditions = actionPermissions.conditions;
|
|
434
|
+
if (!conditions)
|
|
435
|
+
return; // permission defined but no conditions (e.g. setUserIdField only) → allow
|
|
436
|
+
const condObj = Array.isArray(conditions) ? { AND: conditions } : conditions;
|
|
437
|
+
const resolved = PrismaService_1._buildConditions(condObj, user);
|
|
438
|
+
const data = params?.data;
|
|
439
|
+
const rows = Array.isArray(data) ? data : [data ?? {}];
|
|
440
|
+
for (const row of rows) {
|
|
441
|
+
const ok = await this.matchesCreateConditions(resolved, row || {}, modelName);
|
|
442
|
+
if (!ok) {
|
|
443
|
+
throw new common_1.ForbiddenException(`Not allowed to create ${modelName}: the record does not satisfy the '${action}' permission conditions for role ${userRole}.`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* True only in an explicit local-development environment. Deliberately NOT
|
|
449
|
+
* `!== 'production'` — staging and other envs should behave like production
|
|
450
|
+
* (no extra diagnostic queries/logging). Only `development` / `dev` opt in.
|
|
451
|
+
*/
|
|
452
|
+
isDevMode() {
|
|
453
|
+
const env = process.env.NODE_ENV;
|
|
454
|
+
return env === 'development' || env === 'dev';
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Dev-only diagnostic. After a create that passed its create-conditions,
|
|
458
|
+
* checks whether the new row also satisfies the model's *find* conditions —
|
|
459
|
+
* if not, the creator can't read it back (create rule broader than find
|
|
460
|
+
* rule). Logs a warning; never affects the create. Runs only in development
|
|
461
|
+
* (gated by the caller).
|
|
462
|
+
*/
|
|
463
|
+
async warnIfCreatedRowNotFindable(modelKey, userRole, created) {
|
|
464
|
+
try {
|
|
465
|
+
if (!created || created.id === undefined || created.id === null)
|
|
466
|
+
return;
|
|
467
|
+
const permissions = this.normalizedPermissionsConfig[modelKey.toLowerCase()]?.[userRole];
|
|
468
|
+
const findPerm = permissions?.['findFirst'] ?? permissions?.['findMany'];
|
|
469
|
+
if (!findPerm || findPerm === 'ALL' || !findPerm.conditions)
|
|
470
|
+
return;
|
|
471
|
+
const found = await this.prismaClient[modelKey].findFirst({ where: { id: created.id } });
|
|
472
|
+
if (!found) {
|
|
473
|
+
console.warn('\x1b[33m', `[APPX-CORE PRISMA] Created ${modelKey} id=${created.id} as role ${userRole}, but it does not satisfy the model's find conditions — the creator cannot read it back. Ensure your find conditions are at least as permissive as your create conditions.`, '\x1b[0m');
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
// Dev diagnostic only — never let it affect the create.
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/** Recursively evaluates a (placeholder-resolved) condition object against a to-be-created row. */
|
|
481
|
+
async matchesCreateConditions(cond, row, modelName) {
|
|
482
|
+
for (const key of Object.keys(cond)) {
|
|
483
|
+
const val = cond[key];
|
|
484
|
+
if (key === 'AND') {
|
|
485
|
+
const arr = Array.isArray(val) ? val : [val];
|
|
486
|
+
for (const c of arr)
|
|
487
|
+
if (!(await this.matchesCreateConditions(c, row, modelName)))
|
|
488
|
+
return false;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (key === 'OR') {
|
|
492
|
+
const arr = Array.isArray(val) ? val : [val];
|
|
493
|
+
let any = false;
|
|
494
|
+
for (const c of arr)
|
|
495
|
+
if (await this.matchesCreateConditions(c, row, modelName)) {
|
|
496
|
+
any = true;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
if (!any)
|
|
500
|
+
return false;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (key === 'NOT') {
|
|
504
|
+
const arr = Array.isArray(val) ? val : [val];
|
|
505
|
+
for (const c of arr)
|
|
506
|
+
if (await this.matchesCreateConditions(c, row, modelName))
|
|
507
|
+
return false;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const relation = this.getRelation(modelName, key);
|
|
511
|
+
if (relation) {
|
|
512
|
+
if (!(await this.matchesCreateRelation(relation, key, val, row, modelName)))
|
|
513
|
+
return false;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (!this.matchesCreateScalar(row[key], val, key, modelName))
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
/** Evaluates a scalar-field condition (value or Prisma operator object) against the row's value. */
|
|
522
|
+
matchesCreateScalar(actual, cond, key, modelName) {
|
|
523
|
+
if (cond === null)
|
|
524
|
+
return actual === null || actual === undefined;
|
|
525
|
+
if (cond instanceof Date || typeof cond !== 'object')
|
|
526
|
+
return actual === cond;
|
|
527
|
+
for (const op of Object.keys(cond)) {
|
|
528
|
+
const opv = cond[op];
|
|
529
|
+
switch (op) {
|
|
530
|
+
case 'equals':
|
|
531
|
+
if (actual !== opv)
|
|
532
|
+
return false;
|
|
533
|
+
break;
|
|
534
|
+
case 'not':
|
|
535
|
+
if (actual === opv)
|
|
536
|
+
return false;
|
|
537
|
+
break;
|
|
538
|
+
case 'in':
|
|
539
|
+
if (!Array.isArray(opv) || !opv.includes(actual))
|
|
540
|
+
return false;
|
|
541
|
+
break;
|
|
542
|
+
case 'notIn':
|
|
543
|
+
if (Array.isArray(opv) && opv.includes(actual))
|
|
544
|
+
return false;
|
|
545
|
+
break;
|
|
546
|
+
case 'lt':
|
|
547
|
+
if (!(actual < opv))
|
|
548
|
+
return false;
|
|
549
|
+
break;
|
|
550
|
+
case 'lte':
|
|
551
|
+
if (!(actual <= opv))
|
|
552
|
+
return false;
|
|
553
|
+
break;
|
|
554
|
+
case 'gt':
|
|
555
|
+
if (!(actual > opv))
|
|
556
|
+
return false;
|
|
557
|
+
break;
|
|
558
|
+
case 'gte':
|
|
559
|
+
if (!(actual >= opv))
|
|
560
|
+
return false;
|
|
561
|
+
break;
|
|
562
|
+
default:
|
|
563
|
+
throw new common_1.ForbiddenException(`create on ${modelName}: unsupported operator '${op}' on field '${key}' in a create condition. Use a simpler condition or setUserIdField.`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
/** Verifies the parent referenced by a belongsTo condition exists and satisfies the nested condition. */
|
|
569
|
+
async matchesCreateRelation(relation, field, nestedCond, row, modelName) {
|
|
570
|
+
if (relation.relation !== 'belongsTo' || !relation.referencingColumn) {
|
|
571
|
+
throw new common_1.ForbiddenException(`create on ${modelName}: unsupported relation condition on '${field}' — a new row has no related '${field}' yet to match. Constrain the scalar foreign key instead.`);
|
|
572
|
+
}
|
|
573
|
+
const fkValue = row[relation.referencingColumn];
|
|
574
|
+
if (fkValue === undefined || fkValue === null)
|
|
575
|
+
return false;
|
|
576
|
+
const delegate = relation.model.charAt(0).toLowerCase() + relation.model.slice(1);
|
|
577
|
+
// Use the request's transaction client if present (to see rows created
|
|
578
|
+
// earlier in the same request), else the raw client. Either way, access
|
|
579
|
+
// filtering on the parent is intentionally NOT re-applied — we check
|
|
580
|
+
// only the condition.
|
|
581
|
+
const client = nestjs_request_context_1.RequestContext.currentContext?.req.prisma || this.rawClient;
|
|
582
|
+
const found = await client[delegate].findFirst({
|
|
583
|
+
where: { AND: [{ [relation.foreignKey]: fkValue }, nestedCond] },
|
|
584
|
+
select: { [relation.foreignKey]: true },
|
|
585
|
+
});
|
|
586
|
+
return !!found;
|
|
587
|
+
}
|
|
383
588
|
/**
|
|
384
589
|
* Builds dynamic conditions based on the type of clause (OR, AND, or field conditions).
|
|
385
590
|
* Each condition is processed, and placeholders (like `$USER_ID`) are replaced with actual values.
|