@rhino-dev/rhino-nestjs 0.2.5
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 -0
- package/README.md +427 -0
- package/dist/blueprint/blueprint-parser.d.ts +130 -0
- package/dist/blueprint/blueprint-parser.js +276 -0
- package/dist/blueprint/blueprint-parser.js.map +1 -0
- package/dist/blueprint/blueprint-runner.d.ts +51 -0
- package/dist/blueprint/blueprint-runner.js +238 -0
- package/dist/blueprint/blueprint-runner.js.map +1 -0
- package/dist/blueprint/blueprint-validator.d.ts +19 -0
- package/dist/blueprint/blueprint-validator.js +163 -0
- package/dist/blueprint/blueprint-validator.js.map +1 -0
- package/dist/blueprint/generators/policy-generator.d.ts +40 -0
- package/dist/blueprint/generators/policy-generator.js +157 -0
- package/dist/blueprint/generators/policy-generator.js.map +1 -0
- package/dist/blueprint/generators/prisma-schema-generator.d.ts +43 -0
- package/dist/blueprint/generators/prisma-schema-generator.js +197 -0
- package/dist/blueprint/generators/prisma-schema-generator.js.map +1 -0
- package/dist/blueprint/generators/resource-definition-generator.d.ts +52 -0
- package/dist/blueprint/generators/resource-definition-generator.js +229 -0
- package/dist/blueprint/generators/resource-definition-generator.js.map +1 -0
- package/dist/blueprint/generators/seeder-generator.d.ts +21 -0
- package/dist/blueprint/generators/seeder-generator.js +135 -0
- package/dist/blueprint/generators/seeder-generator.js.map +1 -0
- package/dist/blueprint/generators/test-generator.d.ts +28 -0
- package/dist/blueprint/generators/test-generator.js +275 -0
- package/dist/blueprint/generators/test-generator.js.map +1 -0
- package/dist/blueprint/manifest-manager.d.ts +58 -0
- package/dist/blueprint/manifest-manager.js +137 -0
- package/dist/blueprint/manifest-manager.js.map +1 -0
- package/dist/cli/commands/blueprint.command.d.ts +16 -0
- package/dist/cli/commands/blueprint.command.js +35 -0
- package/dist/cli/commands/blueprint.command.js.map +1 -0
- package/dist/cli/commands/export-postman.command.d.ts +5 -0
- package/dist/cli/commands/export-postman.command.js +39 -0
- package/dist/cli/commands/export-postman.command.js.map +1 -0
- package/dist/cli/commands/export-types.command.d.ts +4 -0
- package/dist/cli/commands/export-types.command.js +36 -0
- package/dist/cli/commands/export-types.command.js.map +1 -0
- package/dist/cli/commands/generate.command.d.ts +6 -0
- package/dist/cli/commands/generate.command.js +212 -0
- package/dist/cli/commands/generate.command.js.map +1 -0
- package/dist/cli/commands/install.command.d.ts +8 -0
- package/dist/cli/commands/install.command.js +205 -0
- package/dist/cli/commands/install.command.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.js +123 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/io.d.ts +12 -0
- package/dist/cli/utils/io.js +74 -0
- package/dist/cli/utils/io.js.map +1 -0
- package/dist/cli/utils/prompt.d.ts +12 -0
- package/dist/cli/utils/prompt.js +78 -0
- package/dist/cli/utils/prompt.js.map +1 -0
- package/dist/constants/tokens.d.ts +3 -0
- package/dist/constants/tokens.js +7 -0
- package/dist/constants/tokens.js.map +1 -0
- package/dist/controllers/auth.controller.d.ts +59 -0
- package/dist/controllers/auth.controller.js +121 -0
- package/dist/controllers/auth.controller.js.map +1 -0
- package/dist/controllers/global.controller.d.ts +44 -0
- package/dist/controllers/global.controller.js +357 -0
- package/dist/controllers/global.controller.js.map +1 -0
- package/dist/controllers/invitation.controller.d.ts +30 -0
- package/dist/controllers/invitation.controller.js +111 -0
- package/dist/controllers/invitation.controller.js.map +1 -0
- package/dist/controllers/nested.controller.d.ts +12 -0
- package/dist/controllers/nested.controller.js +69 -0
- package/dist/controllers/nested.controller.js.map +1 -0
- package/dist/decorators/index.d.ts +33 -0
- package/dist/decorators/index.js +79 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/errors/rhino-exception.d.ts +24 -0
- package/dist/errors/rhino-exception.js +47 -0
- package/dist/errors/rhino-exception.js.map +1 -0
- package/dist/exporters/postman-exporter.d.ts +21 -0
- package/dist/exporters/postman-exporter.js +396 -0
- package/dist/exporters/postman-exporter.js.map +1 -0
- package/dist/exporters/typescript-exporter.d.ts +23 -0
- package/dist/exporters/typescript-exporter.js +129 -0
- package/dist/exporters/typescript-exporter.js.map +1 -0
- package/dist/guards/jwt-auth.guard.d.ts +16 -0
- package/dist/guards/jwt-auth.guard.js +61 -0
- package/dist/guards/jwt-auth.guard.js.map +1 -0
- package/dist/guards/resource-policy.guard.d.ts +12 -0
- package/dist/guards/resource-policy.guard.js +111 -0
- package/dist/guards/resource-policy.guard.js.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +171 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/hidden-columns.interceptor.d.ts +18 -0
- package/dist/interceptors/hidden-columns.interceptor.js +66 -0
- package/dist/interceptors/hidden-columns.interceptor.js.map +1 -0
- package/dist/interceptors/response.interceptor.d.ts +19 -0
- package/dist/interceptors/response.interceptor.js +47 -0
- package/dist/interceptors/response.interceptor.js.map +1 -0
- package/dist/interfaces/rhino-config.interface.d.ts +103 -0
- package/dist/interfaces/rhino-config.interface.js +3 -0
- package/dist/interfaces/rhino-config.interface.js.map +1 -0
- package/dist/interfaces/rhino-request.interface.d.ts +12 -0
- package/dist/interfaces/rhino-request.interface.js +3 -0
- package/dist/interfaces/rhino-request.interface.js.map +1 -0
- package/dist/middleware/resolve-organization.middleware.d.ts +15 -0
- package/dist/middleware/resolve-organization.middleware.js +38 -0
- package/dist/middleware/resolve-organization.middleware.js.map +1 -0
- package/dist/middleware/route-group.middleware.d.ts +17 -0
- package/dist/middleware/route-group.middleware.js +60 -0
- package/dist/middleware/route-group.middleware.js.map +1 -0
- package/dist/middleware/tenant-route-rewrite.d.ts +76 -0
- package/dist/middleware/tenant-route-rewrite.js +124 -0
- package/dist/middleware/tenant-route-rewrite.js.map +1 -0
- package/dist/policies/resource-policy.d.ts +25 -0
- package/dist/policies/resource-policy.js +77 -0
- package/dist/policies/resource-policy.js.map +1 -0
- package/dist/prisma/prisma-soft-delete.extension.d.ts +16 -0
- package/dist/prisma/prisma-soft-delete.extension.js +57 -0
- package/dist/prisma/prisma-soft-delete.extension.js.map +1 -0
- package/dist/prisma/prisma-uuid.extension.d.ts +9 -0
- package/dist/prisma/prisma-uuid.extension.js +28 -0
- package/dist/prisma/prisma-uuid.extension.js.map +1 -0
- package/dist/prisma/prisma.service.d.ts +38 -0
- package/dist/prisma/prisma.service.js +100 -0
- package/dist/prisma/prisma.service.js.map +1 -0
- package/dist/rhino.config.d.ts +43 -0
- package/dist/rhino.config.js +130 -0
- package/dist/rhino.config.js.map +1 -0
- package/dist/rhino.module.d.ts +61 -0
- package/dist/rhino.module.js +268 -0
- package/dist/rhino.module.js.map +1 -0
- package/dist/services/audit.service.d.ts +35 -0
- package/dist/services/audit.service.js +92 -0
- package/dist/services/audit.service.js.map +1 -0
- package/dist/services/auth.service.d.ts +33 -0
- package/dist/services/auth.service.js +157 -0
- package/dist/services/auth.service.js.map +1 -0
- package/dist/services/invitation.service.d.ts +37 -0
- package/dist/services/invitation.service.js +143 -0
- package/dist/services/invitation.service.js.map +1 -0
- package/dist/services/nested.service.d.ts +34 -0
- package/dist/services/nested.service.js +163 -0
- package/dist/services/nested.service.js.map +1 -0
- package/dist/services/organization.service.d.ts +13 -0
- package/dist/services/organization.service.js +60 -0
- package/dist/services/organization.service.js.map +1 -0
- package/dist/services/query-builder.service.d.ts +31 -0
- package/dist/services/query-builder.service.js +151 -0
- package/dist/services/query-builder.service.js.map +1 -0
- package/dist/services/resource.service.d.ts +38 -0
- package/dist/services/resource.service.js +230 -0
- package/dist/services/resource.service.js.map +1 -0
- package/dist/services/route-registration.service.d.ts +40 -0
- package/dist/services/route-registration.service.js +68 -0
- package/dist/services/route-registration.service.js.map +1 -0
- package/dist/services/scope.service.d.ts +16 -0
- package/dist/services/scope.service.js +32 -0
- package/dist/services/scope.service.js.map +1 -0
- package/dist/services/serializer.service.d.ts +36 -0
- package/dist/services/serializer.service.js +90 -0
- package/dist/services/serializer.service.js.map +1 -0
- package/dist/services/validation.service.d.ts +50 -0
- package/dist/services/validation.service.js +203 -0
- package/dist/services/validation.service.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/utils/fk-chain-walker.d.ts +30 -0
- package/dist/utils/fk-chain-walker.js +23 -0
- package/dist/utils/fk-chain-walker.js.map +1 -0
- package/dist/utils/format.d.ts +7 -0
- package/dist/utils/format.js +28 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/model-builder.d.ts +64 -0
- package/dist/utils/model-builder.js +67 -0
- package/dist/utils/model-builder.js.map +1 -0
- package/dist/utils/permission-matcher.d.ts +46 -0
- package/dist/utils/permission-matcher.js +120 -0
- package/dist/utils/permission-matcher.js.map +1 -0
- package/dist/utils/scope-discovery.d.ts +34 -0
- package/dist/utils/scope-discovery.js +102 -0
- package/dist/utils/scope-discovery.js.map +1 -0
- package/package.json +64 -0
- package/stubs/skills/rhino-audit.md +184 -0
- package/stubs/skills/rhino-bugfix.md +117 -0
- package/stubs/skills/rhino-deploy.md +5 -0
- package/stubs/skills/rhino-docs.md +147 -0
- package/stubs/skills/rhino-feature.md +146 -0
- package/stubs/skills/rhino-migrate.md +181 -0
- package/stubs/skills/rhino-model.md +180 -0
- package/stubs/skills/rhino-plan.md +5 -0
- package/stubs/skills/rhino-policy.md +183 -0
- package/stubs/skills/rhino-refactor.md +147 -0
- package/stubs/skills/rhino-review.md +113 -0
- package/stubs/skills/rhino-scope.md +140 -0
- package/stubs/skills/rhino-test.md +233 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.InvitationService = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const crypto_1 = require("crypto");
|
|
15
|
+
const prisma_service_1 = require("../prisma/prisma.service");
|
|
16
|
+
const rhino_config_1 = require("../rhino.config");
|
|
17
|
+
/**
|
|
18
|
+
* Invitation lifecycle: create/resend/cancel/accept with token-based acceptance.
|
|
19
|
+
* Mirrors Laravel's InvitationController + OrganizationInvitation model.
|
|
20
|
+
*/
|
|
21
|
+
let InvitationService = class InvitationService {
|
|
22
|
+
prisma;
|
|
23
|
+
config;
|
|
24
|
+
constructor(prisma, config) {
|
|
25
|
+
this.prisma = prisma;
|
|
26
|
+
this.config = config;
|
|
27
|
+
}
|
|
28
|
+
delegate() {
|
|
29
|
+
return this.prisma.model('organizationInvitation');
|
|
30
|
+
}
|
|
31
|
+
generateToken() {
|
|
32
|
+
return (0, crypto_1.randomBytes)(32).toString('hex'); // 64 hex chars
|
|
33
|
+
}
|
|
34
|
+
expiresAt() {
|
|
35
|
+
const days = this.config.invitationsConfig().expiresDays;
|
|
36
|
+
const d = new Date();
|
|
37
|
+
d.setDate(d.getDate() + days);
|
|
38
|
+
return d;
|
|
39
|
+
}
|
|
40
|
+
async list(organizationId, status) {
|
|
41
|
+
const where = { organizationId };
|
|
42
|
+
if (status && status !== 'all')
|
|
43
|
+
where.status = status;
|
|
44
|
+
return this.delegate().findMany({ where, orderBy: { createdAt: 'desc' } });
|
|
45
|
+
}
|
|
46
|
+
async create(input) {
|
|
47
|
+
const cfg = this.config.invitationsConfig();
|
|
48
|
+
if (cfg.allowedRoles) {
|
|
49
|
+
const role = await this.prisma.model('role').findUnique({ where: { id: input.roleId } });
|
|
50
|
+
if (!role || !cfg.allowedRoles.includes(role.slug)) {
|
|
51
|
+
throw new common_1.BadRequestException('Role not allowed for invitations');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const token = this.generateToken();
|
|
55
|
+
const invitation = await this.delegate().create({
|
|
56
|
+
data: {
|
|
57
|
+
organizationId: input.organization.id,
|
|
58
|
+
email: input.email,
|
|
59
|
+
roleId: input.roleId,
|
|
60
|
+
token,
|
|
61
|
+
status: 'pending',
|
|
62
|
+
invitedById: input.invitedBy.id,
|
|
63
|
+
expiresAt: this.expiresAt(),
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
if (cfg.notificationHandler) {
|
|
67
|
+
await cfg.notificationHandler(invitation);
|
|
68
|
+
}
|
|
69
|
+
return invitation;
|
|
70
|
+
}
|
|
71
|
+
async resend(id) {
|
|
72
|
+
const invitation = await this.delegate().findUnique({ where: { id } });
|
|
73
|
+
if (!invitation)
|
|
74
|
+
throw new common_1.NotFoundException('Invitation not found');
|
|
75
|
+
if (invitation.status !== 'pending') {
|
|
76
|
+
throw new common_1.BadRequestException('Only pending invitations can be resent');
|
|
77
|
+
}
|
|
78
|
+
const refreshed = await this.delegate().update({
|
|
79
|
+
where: { id },
|
|
80
|
+
data: { token: this.generateToken(), expiresAt: this.expiresAt() },
|
|
81
|
+
});
|
|
82
|
+
const cfg = this.config.invitationsConfig();
|
|
83
|
+
if (cfg.notificationHandler)
|
|
84
|
+
await cfg.notificationHandler(refreshed);
|
|
85
|
+
return refreshed;
|
|
86
|
+
}
|
|
87
|
+
async cancel(id) {
|
|
88
|
+
const invitation = await this.delegate().findUnique({ where: { id } });
|
|
89
|
+
if (!invitation)
|
|
90
|
+
throw new common_1.NotFoundException('Invitation not found');
|
|
91
|
+
if (invitation.status !== 'pending') {
|
|
92
|
+
throw new common_1.BadRequestException('Only pending invitations can be cancelled');
|
|
93
|
+
}
|
|
94
|
+
return this.delegate().update({
|
|
95
|
+
where: { id },
|
|
96
|
+
data: { status: 'cancelled' },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async accept(token, userId) {
|
|
100
|
+
const invitation = await this.delegate().findUnique({
|
|
101
|
+
where: { token },
|
|
102
|
+
include: { organization: true, role: true },
|
|
103
|
+
});
|
|
104
|
+
if (!invitation)
|
|
105
|
+
throw new common_1.NotFoundException('Invitation not found');
|
|
106
|
+
const inv = invitation;
|
|
107
|
+
if (inv.status !== 'pending')
|
|
108
|
+
throw new common_1.BadRequestException('Invitation no longer valid');
|
|
109
|
+
if (inv.expiresAt < new Date()) {
|
|
110
|
+
await this.delegate().update({ where: { id: inv.id }, data: { status: 'expired' } });
|
|
111
|
+
throw new common_1.BadRequestException('Invitation expired');
|
|
112
|
+
}
|
|
113
|
+
if (!userId) {
|
|
114
|
+
// User is unauthenticated — return org/role so client can register
|
|
115
|
+
return {
|
|
116
|
+
requiresRegistration: true,
|
|
117
|
+
organization: inv.organization,
|
|
118
|
+
role: inv.role,
|
|
119
|
+
email: inv.email,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
await this.prisma.model('userRole').create({
|
|
123
|
+
data: {
|
|
124
|
+
userId,
|
|
125
|
+
organizationId: inv.organizationId,
|
|
126
|
+
roleId: inv.roleId,
|
|
127
|
+
permissions: [],
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
await this.delegate().update({
|
|
131
|
+
where: { id: inv.id },
|
|
132
|
+
data: { status: 'accepted', acceptedAt: new Date() },
|
|
133
|
+
});
|
|
134
|
+
return { accepted: true, organization: inv.organization };
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
exports.InvitationService = InvitationService;
|
|
138
|
+
exports.InvitationService = InvitationService = __decorate([
|
|
139
|
+
(0, common_1.Injectable)(),
|
|
140
|
+
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
|
|
141
|
+
rhino_config_1.RhinoConfigService])
|
|
142
|
+
], InvitationService);
|
|
143
|
+
//# sourceMappingURL=invitation.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invitation.service.js","sourceRoot":"","sources":["../../src/services/invitation.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PrismaService } from '../prisma/prisma.service';
|
|
2
|
+
import { RhinoConfigService } from '../rhino.config';
|
|
3
|
+
import { ValidationService } from './validation.service';
|
|
4
|
+
export interface NestedOperation {
|
|
5
|
+
model: string;
|
|
6
|
+
action: 'create' | 'update' | 'delete';
|
|
7
|
+
id?: number | string;
|
|
8
|
+
data?: Record<string, any>;
|
|
9
|
+
}
|
|
10
|
+
export interface NestedExecContext {
|
|
11
|
+
user?: any;
|
|
12
|
+
organization?: any;
|
|
13
|
+
}
|
|
14
|
+
export interface NestedResult {
|
|
15
|
+
index: number;
|
|
16
|
+
model: string;
|
|
17
|
+
action: 'create' | 'update' | 'delete';
|
|
18
|
+
id: any;
|
|
19
|
+
data: any;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Executes a batch of nested CRUD operations atomically.
|
|
23
|
+
* Supports `$N.field` references to prior operations' return values.
|
|
24
|
+
*/
|
|
25
|
+
export declare class NestedService {
|
|
26
|
+
private readonly prisma;
|
|
27
|
+
private readonly config;
|
|
28
|
+
private readonly validation;
|
|
29
|
+
constructor(prisma: PrismaService, config: RhinoConfigService, validation: ValidationService);
|
|
30
|
+
execute(operations: NestedOperation[], ctx: NestedExecContext): Promise<NestedResult[]>;
|
|
31
|
+
private camelize;
|
|
32
|
+
resolveReferences(data: Record<string, any>, results: NestedResult[]): Record<string, any>;
|
|
33
|
+
private readPath;
|
|
34
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.NestedService = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const prisma_service_1 = require("../prisma/prisma.service");
|
|
15
|
+
const rhino_config_1 = require("../rhino.config");
|
|
16
|
+
const validation_service_1 = require("./validation.service");
|
|
17
|
+
const REF_PATTERN = /^\$(\d+)\.([\w.]+)$/;
|
|
18
|
+
/**
|
|
19
|
+
* Executes a batch of nested CRUD operations atomically.
|
|
20
|
+
* Supports `$N.field` references to prior operations' return values.
|
|
21
|
+
*/
|
|
22
|
+
let NestedService = class NestedService {
|
|
23
|
+
prisma;
|
|
24
|
+
config;
|
|
25
|
+
validation;
|
|
26
|
+
constructor(prisma, config, validation) {
|
|
27
|
+
this.prisma = prisma;
|
|
28
|
+
this.config = config;
|
|
29
|
+
this.validation = validation;
|
|
30
|
+
}
|
|
31
|
+
async execute(operations, ctx) {
|
|
32
|
+
const maxOps = this.config.nestedConfig().maxOperations;
|
|
33
|
+
const allowed = this.config.nestedConfig().allowedModels;
|
|
34
|
+
if (operations.length === 0) {
|
|
35
|
+
throw new common_1.BadRequestException('No operations provided');
|
|
36
|
+
}
|
|
37
|
+
if (operations.length > maxOps) {
|
|
38
|
+
throw new common_1.BadRequestException(`Too many operations (max ${maxOps})`);
|
|
39
|
+
}
|
|
40
|
+
for (const op of operations) {
|
|
41
|
+
if (!op.model || !op.action) {
|
|
42
|
+
throw new common_1.BadRequestException('Each operation must have model and action');
|
|
43
|
+
}
|
|
44
|
+
if (!['create', 'update', 'delete'].includes(op.action)) {
|
|
45
|
+
throw new common_1.BadRequestException(`Unsupported action: ${op.action}`);
|
|
46
|
+
}
|
|
47
|
+
if ((op.action === 'update' || op.action === 'delete') && op.id == null) {
|
|
48
|
+
throw new common_1.BadRequestException(`Action ${op.action} requires an id`);
|
|
49
|
+
}
|
|
50
|
+
if (!this.config.hasModel(op.model)) {
|
|
51
|
+
throw new common_1.BadRequestException(`Unknown model: ${op.model}`);
|
|
52
|
+
}
|
|
53
|
+
if (allowed && !allowed.includes(op.model)) {
|
|
54
|
+
throw new common_1.BadRequestException(`Model not allowed for nested: ${op.model}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return this.prisma.$transaction(async (tx) => {
|
|
58
|
+
const results = [];
|
|
59
|
+
for (let i = 0; i < operations.length; i++) {
|
|
60
|
+
const op = operations[i];
|
|
61
|
+
const reg = this.config.model(op.model);
|
|
62
|
+
const delegate = tx[this.camelize(reg.model)] ?? tx[reg.model];
|
|
63
|
+
if (!delegate) {
|
|
64
|
+
throw new common_1.BadRequestException(`Prisma delegate missing: ${reg.model}`);
|
|
65
|
+
}
|
|
66
|
+
if (op.action === 'delete') {
|
|
67
|
+
const where = { id: op.id };
|
|
68
|
+
if (reg.belongsToOrganization && ctx.organization) {
|
|
69
|
+
where.organizationId = ctx.organization.id;
|
|
70
|
+
}
|
|
71
|
+
if (reg.softDeletes) {
|
|
72
|
+
const res = await delegate.updateMany({ where, data: { deletedAt: new Date() } });
|
|
73
|
+
if (res.count === 0) {
|
|
74
|
+
throw new common_1.BadRequestException({
|
|
75
|
+
message: 'Record not found or cross-tenant',
|
|
76
|
+
operationIndex: i,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const res = await delegate.deleteMany({ where });
|
|
82
|
+
if (res.count === 0) {
|
|
83
|
+
throw new common_1.BadRequestException({
|
|
84
|
+
message: 'Record not found or cross-tenant',
|
|
85
|
+
operationIndex: i,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
results.push({ index: i, model: op.model, action: 'delete', id: op.id, data: null });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const resolvedData = this.resolveReferences(op.data ?? {}, results);
|
|
93
|
+
const validationResult = this.validation.validateForAction(resolvedData, reg, {
|
|
94
|
+
action: op.action === 'create' ? 'store' : 'update',
|
|
95
|
+
user: ctx.user,
|
|
96
|
+
organization: ctx.organization,
|
|
97
|
+
});
|
|
98
|
+
if (!validationResult.valid) {
|
|
99
|
+
throw new common_1.BadRequestException({
|
|
100
|
+
message: 'Validation failed',
|
|
101
|
+
operationIndex: i,
|
|
102
|
+
errors: validationResult.errors,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const payload = validationResult.data;
|
|
106
|
+
if (reg.belongsToOrganization && ctx.organization && op.action === 'create') {
|
|
107
|
+
payload.organizationId = ctx.organization.id;
|
|
108
|
+
}
|
|
109
|
+
if (op.action === 'create') {
|
|
110
|
+
const record = await delegate.create({ data: payload });
|
|
111
|
+
results.push({ index: i, model: op.model, action: 'create', id: record.id, data: record });
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const where = { id: op.id };
|
|
115
|
+
if (reg.belongsToOrganization && ctx.organization) {
|
|
116
|
+
where.organizationId = ctx.organization.id;
|
|
117
|
+
}
|
|
118
|
+
const res = await delegate.updateMany({ where, data: payload });
|
|
119
|
+
if (res.count === 0) {
|
|
120
|
+
throw new common_1.BadRequestException({
|
|
121
|
+
message: 'Record not found or cross-tenant',
|
|
122
|
+
operationIndex: i,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const record = await delegate.findFirst({ where });
|
|
126
|
+
results.push({ index: i, model: op.model, action: 'update', id: record.id, data: record });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return results;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
camelize(name) {
|
|
133
|
+
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
134
|
+
}
|
|
135
|
+
resolveReferences(data, results) {
|
|
136
|
+
const out = {};
|
|
137
|
+
for (const [k, v] of Object.entries(data)) {
|
|
138
|
+
if (typeof v === 'string') {
|
|
139
|
+
const match = v.match(REF_PATTERN);
|
|
140
|
+
if (match) {
|
|
141
|
+
const idx = parseInt(match[1], 10);
|
|
142
|
+
const path = match[2];
|
|
143
|
+
const prior = results[idx]?.data;
|
|
144
|
+
out[k] = prior ? this.readPath(prior, path) : undefined;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
out[k] = v;
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
readPath(obj, path) {
|
|
153
|
+
return path.split('.').reduce((acc, p) => (acc == null ? acc : acc[p]), obj);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
exports.NestedService = NestedService;
|
|
157
|
+
exports.NestedService = NestedService = __decorate([
|
|
158
|
+
(0, common_1.Injectable)(),
|
|
159
|
+
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
|
|
160
|
+
rhino_config_1.RhinoConfigService,
|
|
161
|
+
validation_service_1.ValidationService])
|
|
162
|
+
], NestedService);
|
|
163
|
+
//# sourceMappingURL=nested.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nested.service.js","sourceRoot":"","sources":["../../src/services/nested.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PrismaService } from '../prisma/prisma.service';
|
|
2
|
+
import { RhinoConfigService } from '../rhino.config';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a route's `organization` parameter to a concrete organization record.
|
|
5
|
+
* Mirrors Laravel's `ResolveOrganizationFromRoute` middleware.
|
|
6
|
+
*/
|
|
7
|
+
export declare class OrganizationService {
|
|
8
|
+
private readonly prisma;
|
|
9
|
+
private readonly config;
|
|
10
|
+
constructor(prisma: PrismaService, config: RhinoConfigService);
|
|
11
|
+
resolve(identifier: string | number, user?: any): Promise<any>;
|
|
12
|
+
ensureMembership(user: any, org: any): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.OrganizationService = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const prisma_service_1 = require("../prisma/prisma.service");
|
|
15
|
+
const rhino_config_1 = require("../rhino.config");
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a route's `organization` parameter to a concrete organization record.
|
|
18
|
+
* Mirrors Laravel's `ResolveOrganizationFromRoute` middleware.
|
|
19
|
+
*/
|
|
20
|
+
let OrganizationService = class OrganizationService {
|
|
21
|
+
prisma;
|
|
22
|
+
config;
|
|
23
|
+
constructor(prisma, config) {
|
|
24
|
+
this.prisma = prisma;
|
|
25
|
+
this.config = config;
|
|
26
|
+
}
|
|
27
|
+
async resolve(identifier, user) {
|
|
28
|
+
const column = this.config.organizationIdentifierColumn();
|
|
29
|
+
const modelName = this.config.raw().multiTenant?.organizationModel ?? 'organization';
|
|
30
|
+
const value = column === 'id' || /^\d+$/.test(String(identifier))
|
|
31
|
+
? column === 'id'
|
|
32
|
+
? Number(identifier)
|
|
33
|
+
: identifier
|
|
34
|
+
: identifier;
|
|
35
|
+
const delegate = this.prisma.model(modelName);
|
|
36
|
+
const org = await delegate.findFirst({ where: { [column]: value } });
|
|
37
|
+
if (!org)
|
|
38
|
+
throw new common_1.NotFoundException('Organization not found');
|
|
39
|
+
if (user) {
|
|
40
|
+
await this.ensureMembership(user, org);
|
|
41
|
+
}
|
|
42
|
+
return org;
|
|
43
|
+
}
|
|
44
|
+
async ensureMembership(user, org) {
|
|
45
|
+
const userOrgModel = this.config.raw().multiTenant?.userOrganizationModel ?? 'userRole';
|
|
46
|
+
const delegate = this.prisma.model(userOrgModel);
|
|
47
|
+
const membership = await delegate.findFirst({
|
|
48
|
+
where: { userId: user.id, organizationId: org.id },
|
|
49
|
+
});
|
|
50
|
+
if (!membership)
|
|
51
|
+
throw new common_1.NotFoundException('Organization not found');
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
exports.OrganizationService = OrganizationService;
|
|
55
|
+
exports.OrganizationService = OrganizationService = __decorate([
|
|
56
|
+
(0, common_1.Injectable)(),
|
|
57
|
+
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
|
|
58
|
+
rhino_config_1.RhinoConfigService])
|
|
59
|
+
], OrganizationService);
|
|
60
|
+
//# sourceMappingURL=organization.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization.service.js","sourceRoot":"","sources":["../../src/services/organization.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA+D;AAC/D,6DAAyD;AACzD,kDAAqD;AAErD;;;GAGG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAEX;IACA;IAFnB,YACmB,MAAqB,EACrB,MAA0B;QAD1B,WAAM,GAAN,MAAM,CAAe;QACrB,WAAM,GAAN,MAAM,CAAoB;IAC1C,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,UAA2B,EAAE,IAAU;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,4BAA4B,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,iBAAiB,IAAI,cAAc,CAAC;QACrF,MAAM,KAAK,GACT,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACjD,CAAC,CAAC,MAAM,KAAK,IAAI;gBACf,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC;gBACpB,CAAC,CAAC,UAAU;YACd,CAAC,CAAC,UAAU,CAAC;QAEjB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACrE,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,0BAAiB,CAAC,wBAAwB,CAAC,CAAC;QAEhE,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACzC,CAAC;QAED,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,IAAS,EAAE,GAAQ;QACxC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,qBAAqB,IAAI,UAAU,CAAC;QACxF,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC;YAC1C,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,EAAE;SACnD,CAAC,CAAC;QACH,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,0BAAiB,CAAC,wBAAwB,CAAC,CAAC;IACzE,CAAC;CACF,CAAA;AAnCY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;qCAGgB,8BAAa;QACb,iCAAkB;GAHlC,mBAAmB,CAmC/B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ModelRegistration } from '../interfaces/rhino-config.interface';
|
|
2
|
+
export interface ParsedQuery {
|
|
3
|
+
where: Record<string, any>;
|
|
4
|
+
orderBy: any;
|
|
5
|
+
include?: Record<string, any>;
|
|
6
|
+
select?: Record<string, any>;
|
|
7
|
+
page?: number;
|
|
8
|
+
perPage?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parses query string parameters into Prisma-compatible `findMany` args.
|
|
12
|
+
*
|
|
13
|
+
* Supported shape (match Laravel + Spatie QueryBuilder semantics):
|
|
14
|
+
* ?filter[field]=value — AND match, comma-separated values become IN
|
|
15
|
+
* ?sort=-created_at,title — leading `-` reverses direction
|
|
16
|
+
* ?search=term — fuzzy search across allowedSearch fields
|
|
17
|
+
* ?per_page=25&page=2 — pagination
|
|
18
|
+
* ?fields[slug]=id,title — field selection (Prisma `select`)
|
|
19
|
+
* ?include=user,comments.author — eager loading with dot-notation
|
|
20
|
+
*/
|
|
21
|
+
export declare class QueryBuilderService {
|
|
22
|
+
build(query: Record<string, any>, reg: ModelRegistration): ParsedQuery;
|
|
23
|
+
buildWhere(query: Record<string, any>, reg: ModelRegistration): Record<string, any>;
|
|
24
|
+
private parseFilterValue;
|
|
25
|
+
private buildRelationContains;
|
|
26
|
+
buildOrderBy(query: Record<string, any>, reg: ModelRegistration): any;
|
|
27
|
+
buildInclude(query: Record<string, any>, reg: ModelRegistration): Record<string, any> | undefined;
|
|
28
|
+
private assignInclude;
|
|
29
|
+
buildSelect(query: Record<string, any>, reg: ModelRegistration): Record<string, any> | undefined;
|
|
30
|
+
private parseInt;
|
|
31
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.QueryBuilderService = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
/**
|
|
12
|
+
* Parses query string parameters into Prisma-compatible `findMany` args.
|
|
13
|
+
*
|
|
14
|
+
* Supported shape (match Laravel + Spatie QueryBuilder semantics):
|
|
15
|
+
* ?filter[field]=value — AND match, comma-separated values become IN
|
|
16
|
+
* ?sort=-created_at,title — leading `-` reverses direction
|
|
17
|
+
* ?search=term — fuzzy search across allowedSearch fields
|
|
18
|
+
* ?per_page=25&page=2 — pagination
|
|
19
|
+
* ?fields[slug]=id,title — field selection (Prisma `select`)
|
|
20
|
+
* ?include=user,comments.author — eager loading with dot-notation
|
|
21
|
+
*/
|
|
22
|
+
let QueryBuilderService = class QueryBuilderService {
|
|
23
|
+
build(query, reg) {
|
|
24
|
+
return {
|
|
25
|
+
where: this.buildWhere(query, reg),
|
|
26
|
+
orderBy: this.buildOrderBy(query, reg),
|
|
27
|
+
include: this.buildInclude(query, reg),
|
|
28
|
+
select: this.buildSelect(query, reg),
|
|
29
|
+
page: this.parseInt(query.page),
|
|
30
|
+
perPage: this.parseInt(query.per_page ?? query.perPage),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
buildWhere(query, reg) {
|
|
34
|
+
const where = {};
|
|
35
|
+
const filter = query.filter ?? {};
|
|
36
|
+
const allowed = new Set(reg.allowedFilters ?? []);
|
|
37
|
+
if (filter && typeof filter === 'object') {
|
|
38
|
+
for (const [key, rawVal] of Object.entries(filter)) {
|
|
39
|
+
if (!allowed.has(key))
|
|
40
|
+
continue;
|
|
41
|
+
where[key] = this.parseFilterValue(rawVal);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const search = query.search;
|
|
45
|
+
if (search && reg.allowedSearch && reg.allowedSearch.length > 0) {
|
|
46
|
+
const fragments = reg.allowedSearch.map((field) => {
|
|
47
|
+
if (field.includes('.')) {
|
|
48
|
+
return this.buildRelationContains(field, String(search));
|
|
49
|
+
}
|
|
50
|
+
return { [field]: { contains: String(search), mode: 'insensitive' } };
|
|
51
|
+
});
|
|
52
|
+
where.OR = fragments;
|
|
53
|
+
}
|
|
54
|
+
return where;
|
|
55
|
+
}
|
|
56
|
+
parseFilterValue(v) {
|
|
57
|
+
if (Array.isArray(v))
|
|
58
|
+
return { in: v };
|
|
59
|
+
if (typeof v === 'string' && v.includes(',')) {
|
|
60
|
+
return { in: v.split(',').filter(Boolean) };
|
|
61
|
+
}
|
|
62
|
+
return v;
|
|
63
|
+
}
|
|
64
|
+
buildRelationContains(path, value) {
|
|
65
|
+
const parts = path.split('.');
|
|
66
|
+
const leaf = parts.pop();
|
|
67
|
+
let inner = { [leaf]: { contains: value, mode: 'insensitive' } };
|
|
68
|
+
while (parts.length) {
|
|
69
|
+
const seg = parts.pop();
|
|
70
|
+
inner = { [seg]: { is: inner } };
|
|
71
|
+
}
|
|
72
|
+
return inner;
|
|
73
|
+
}
|
|
74
|
+
buildOrderBy(query, reg) {
|
|
75
|
+
const raw = query.sort ?? reg.defaultSort;
|
|
76
|
+
if (!raw)
|
|
77
|
+
return undefined;
|
|
78
|
+
const allowed = new Set(reg.allowedSorts ?? []);
|
|
79
|
+
const tokens = String(raw).split(',').map((s) => s.trim()).filter(Boolean);
|
|
80
|
+
const orderings = [];
|
|
81
|
+
for (const tok of tokens) {
|
|
82
|
+
const desc = tok.startsWith('-');
|
|
83
|
+
const field = desc ? tok.slice(1) : tok;
|
|
84
|
+
if (allowed.size > 0 && !allowed.has(field)) {
|
|
85
|
+
throw new common_1.BadRequestException(`Sort not allowed: ${field}`);
|
|
86
|
+
}
|
|
87
|
+
orderings.push({ [field]: desc ? 'desc' : 'asc' });
|
|
88
|
+
}
|
|
89
|
+
return orderings;
|
|
90
|
+
}
|
|
91
|
+
buildInclude(query, reg) {
|
|
92
|
+
const raw = query.include;
|
|
93
|
+
if (!raw)
|
|
94
|
+
return undefined;
|
|
95
|
+
const allowed = new Set(reg.allowedIncludes ?? []);
|
|
96
|
+
const paths = String(raw).split(',').map((s) => s.trim()).filter(Boolean);
|
|
97
|
+
const include = {};
|
|
98
|
+
for (const path of paths) {
|
|
99
|
+
if (allowed.size > 0 && !allowed.has(path) && !allowed.has(path.split('.')[0])) {
|
|
100
|
+
throw new common_1.BadRequestException(`Include not allowed: ${path}`);
|
|
101
|
+
}
|
|
102
|
+
this.assignInclude(include, path.split('.'));
|
|
103
|
+
}
|
|
104
|
+
return include;
|
|
105
|
+
}
|
|
106
|
+
assignInclude(target, parts) {
|
|
107
|
+
const [head, ...rest] = parts;
|
|
108
|
+
if (!head)
|
|
109
|
+
return;
|
|
110
|
+
if (rest.length === 0) {
|
|
111
|
+
if (!target[head])
|
|
112
|
+
target[head] = true;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (target[head] === true || !target[head])
|
|
116
|
+
target[head] = { include: {} };
|
|
117
|
+
if (!target[head].include)
|
|
118
|
+
target[head].include = {};
|
|
119
|
+
this.assignInclude(target[head].include, rest);
|
|
120
|
+
}
|
|
121
|
+
buildSelect(query, reg) {
|
|
122
|
+
const fields = query.fields;
|
|
123
|
+
if (!fields || typeof fields !== 'object')
|
|
124
|
+
return undefined;
|
|
125
|
+
// Support either ?fields[slug]=... or ?fields[_]=...; pick first non-empty
|
|
126
|
+
const raw = Object.values(fields)[0];
|
|
127
|
+
if (!raw)
|
|
128
|
+
return undefined;
|
|
129
|
+
const allowed = new Set(reg.allowedFields ?? []);
|
|
130
|
+
const names = String(raw).split(',').map((s) => s.trim()).filter(Boolean);
|
|
131
|
+
const select = { id: true };
|
|
132
|
+
for (const name of names) {
|
|
133
|
+
if (allowed.size > 0 && !allowed.has(name)) {
|
|
134
|
+
throw new common_1.BadRequestException(`Field not allowed: ${name}`);
|
|
135
|
+
}
|
|
136
|
+
select[name] = true;
|
|
137
|
+
}
|
|
138
|
+
return select;
|
|
139
|
+
}
|
|
140
|
+
parseInt(v) {
|
|
141
|
+
if (v == null)
|
|
142
|
+
return undefined;
|
|
143
|
+
const n = parseInt(String(v), 10);
|
|
144
|
+
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
exports.QueryBuilderService = QueryBuilderService;
|
|
148
|
+
exports.QueryBuilderService = QueryBuilderService = __decorate([
|
|
149
|
+
(0, common_1.Injectable)()
|
|
150
|
+
], QueryBuilderService);
|
|
151
|
+
//# sourceMappingURL=query-builder.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-builder.service.js","sourceRoot":"","sources":["../../src/services/query-builder.service.ts"],"names":[],"mappings":"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"}
|