@appxdigital/appx-core 0.1.120 → 0.1.121-beta.11
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/CHANGELOG.md +123 -0
- package/LICENSE +21 -21
- package/PRERELEASES.md +151 -0
- package/README.md +627 -533
- package/RELEASING.md +168 -0
- package/dist/common/config/permissionsConfigTypes.d.ts +8 -0
- package/dist/common/config/permissionsConfigTypes.d.ts.map +1 -1
- package/dist/common/config/permissionsConfigTypes.js +13 -1
- package/dist/common/decorators/permission.decorator.d.ts +14 -0
- package/dist/common/decorators/permission.decorator.d.ts.map +1 -1
- package/dist/common/decorators/permission.decorator.js +16 -1
- package/dist/common/guards/rbac.guard.d.ts.map +1 -1
- package/dist/common/guards/rbac.guard.js +8 -1
- package/dist/common/interceptors/prisma.interceptor.d.ts.map +1 -1
- package/dist/common/interceptors/prisma.interceptor.js +24 -8
- 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 +285 -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 +94 -2
- package/dist/prisma/prisma.service.d.ts.map +1 -1
- package/dist/prisma/prisma.service.js +338 -8
- package/docs/README.md +21 -0
- package/docs/data-access.md +108 -0
- package/docs/dtos.md +163 -0
- package/docs/graphql.md +72 -0
- package/docs/limitations.md +59 -0
- package/docs/permissions.md +155 -0
- package/package.json +112 -86
|
@@ -34,10 +34,12 @@ const modulesOutputPath = path.join(process.cwd(), 'src/modules');
|
|
|
34
34
|
* @param folder
|
|
35
35
|
*/
|
|
36
36
|
const genericControllerTemplate = (model, folder) => `
|
|
37
|
-
import { Controller } from '@nestjs/common';
|
|
37
|
+
import { Body, Controller, Param, Post, Put } from '@nestjs/common';
|
|
38
38
|
import { ${model}Service } from './${folder}.service';
|
|
39
39
|
import { ${model} } from '@prisma/client';
|
|
40
|
-
import { CoreController } from '@appxdigital/appx-core';
|
|
40
|
+
import { CoreController, Permission } from '@appxdigital/appx-core';
|
|
41
|
+
import { Create${model}Dto } from './dto/create-${folder}.dto';
|
|
42
|
+
import { Update${model}Dto } from './dto/update-${folder}.dto';
|
|
41
43
|
|
|
42
44
|
@Controller('${model.toLowerCase()}s')
|
|
43
45
|
export class ${model}Controller extends CoreController<${model}> {
|
|
@@ -48,6 +50,21 @@ export class ${model}Controller extends CoreController<${model}> {
|
|
|
48
50
|
static get entityName(): string {
|
|
49
51
|
return '${model}';
|
|
50
52
|
}
|
|
53
|
+
|
|
54
|
+
// Overridden so the @Body() type is a concrete DTO — this is what lets
|
|
55
|
+
// ValidationPipe (whitelist) strip unknown fields.
|
|
56
|
+
// Custom validation goes in the DTO subclass, not here.
|
|
57
|
+
@Post()
|
|
58
|
+
@Permission('create')
|
|
59
|
+
async create(@Body() data: Create${model}Dto) {
|
|
60
|
+
return super.create(data as any);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Put(':id')
|
|
64
|
+
@Permission('updateMany')
|
|
65
|
+
async update(@Param('id') id: string, @Body() data: Update${model}Dto) {
|
|
66
|
+
return super.update(id, data as any);
|
|
67
|
+
}
|
|
51
68
|
}
|
|
52
69
|
`;
|
|
53
70
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-dtos.d.ts","sourceRoot":"","sources":["../../src/config/generate-dtos.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
const path = __importStar(require("path"));
|
|
27
|
+
const utils_1 = require("./utils");
|
|
28
|
+
/**
|
|
29
|
+
* DTO generator.
|
|
30
|
+
*
|
|
31
|
+
* Generated CRUD controllers type `@Body()` as a generic, so NestJS's
|
|
32
|
+
* ValidationPipe has no concrete class to validate against and unknown fields
|
|
33
|
+
* are accepted. This generator emits a class-validator DTO per model+action so
|
|
34
|
+
* the controller can type its body concretely.
|
|
35
|
+
*
|
|
36
|
+
* Two-file, schema-is-source-of-truth model:
|
|
37
|
+
* - `src/generated/dto/<model>/create-<model>.generated.dto.ts` — the base
|
|
38
|
+
* class, derived from the Prisma schema, ALWAYS overwritten (gitignored).
|
|
39
|
+
* - `src/modules/<model>/dto/create-<model>.dto.ts` — a hand-owned subclass,
|
|
40
|
+
* generated ONCE, where the developer adds custom validation. Never
|
|
41
|
+
* overwritten. The controller imports this subclass.
|
|
42
|
+
*
|
|
43
|
+
* A scalar field is writable (appears in the DTO) unless it is:
|
|
44
|
+
* - the primary key (`@id`),
|
|
45
|
+
* - a server-managed timestamp (`@default(now())` or `@updatedAt`),
|
|
46
|
+
* - annotated `/// @NoWrite`, or
|
|
47
|
+
* - annotated `/// @Role(none)` (readable by nobody ⇒ not CRUD-writable).
|
|
48
|
+
*
|
|
49
|
+
* Nested relation writes (CREATE DTOs only — `updateMany` takes scalar data):
|
|
50
|
+
* Each relation gets a nested-write member exposing ONLY the operators the
|
|
51
|
+
* data-access proxy authorizes: `create` (validated against the related
|
|
52
|
+
* model's writable scalars) and `connect` (validated against the related
|
|
53
|
+
* model's unique fields). Other operators (`set`/`disconnect`/`update`/…) are
|
|
54
|
+
* deliberately NOT emitted, so the whitelist pipe rejects them. The proxy
|
|
55
|
+
* enforces the matching ABAC rule for each (`create` rule / `connect` rule).
|
|
56
|
+
*/
|
|
57
|
+
const cwd = process.cwd();
|
|
58
|
+
// Read the consumer's generated Prisma client for the DMMF (authoritative
|
|
59
|
+
// field metadata incl. `documentation`, which carries /// @Role / @NoWrite).
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
61
|
+
const { Prisma } = require(path.join(cwd, 'node_modules', '@prisma/client'));
|
|
62
|
+
const generatedDtoRoot = path.join(cwd, 'src/generated/dto');
|
|
63
|
+
const modulesRoot = path.join(cwd, 'src/modules');
|
|
64
|
+
const modelsByName = {};
|
|
65
|
+
for (const m of Prisma.dmmf.datamodel.models)
|
|
66
|
+
modelsByName[m.name] = m;
|
|
67
|
+
function isNoWrite(doc) {
|
|
68
|
+
if (!doc)
|
|
69
|
+
return false;
|
|
70
|
+
if (/@NoWrite\b/i.test(doc))
|
|
71
|
+
return true;
|
|
72
|
+
// @Role(none): readable by nobody ⇒ treat as not CRUD-writable.
|
|
73
|
+
const roleMatch = doc.match(/@Role\((.*?)\)/);
|
|
74
|
+
if (roleMatch) {
|
|
75
|
+
const roles = roleMatch[1].split(',').map((r) => r.trim().toLowerCase());
|
|
76
|
+
if (roles.includes('none'))
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
function isServerManagedTimestamp(field) {
|
|
82
|
+
if (field.isUpdatedAt)
|
|
83
|
+
return true;
|
|
84
|
+
const def = field.default;
|
|
85
|
+
return !!def && typeof def === 'object' && def.name === 'now';
|
|
86
|
+
}
|
|
87
|
+
/** Decision for a scalar/enum field: base validator, TS type, optional enum arg. */
|
|
88
|
+
function mapField(field) {
|
|
89
|
+
if (field.kind === 'enum') {
|
|
90
|
+
return { validator: 'IsEnum', tsType: field.type, enumArg: field.type };
|
|
91
|
+
}
|
|
92
|
+
switch (field.type) {
|
|
93
|
+
case 'String':
|
|
94
|
+
return { validator: 'IsString', tsType: 'string' };
|
|
95
|
+
case 'Boolean':
|
|
96
|
+
return { validator: 'IsBoolean', tsType: 'boolean' };
|
|
97
|
+
case 'Int':
|
|
98
|
+
case 'BigInt':
|
|
99
|
+
return { validator: 'IsInt', tsType: 'number' };
|
|
100
|
+
case 'Float':
|
|
101
|
+
case 'Decimal':
|
|
102
|
+
return { validator: 'IsNumber', tsType: 'number' };
|
|
103
|
+
case 'DateTime':
|
|
104
|
+
return { validator: 'IsDateString', tsType: 'string' };
|
|
105
|
+
default:
|
|
106
|
+
// Json, Bytes, and anything unrecognised: keep the field (so it isn't
|
|
107
|
+
// stripped by whitelist) without a strict type validator.
|
|
108
|
+
return { validator: 'Allow', tsType: 'any' };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function writableFields(model, omit = []) {
|
|
112
|
+
return model.fields.filter((f) => {
|
|
113
|
+
if (f.kind === 'object')
|
|
114
|
+
return false; // relation navigation
|
|
115
|
+
if (f.isId)
|
|
116
|
+
return false;
|
|
117
|
+
if (isServerManagedTimestamp(f))
|
|
118
|
+
return false;
|
|
119
|
+
if (isNoWrite(f.documentation))
|
|
120
|
+
return false;
|
|
121
|
+
if (omit.includes(f.name))
|
|
122
|
+
return false;
|
|
123
|
+
return true;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/** id + unique scalar fields (for a connect-by DTO). */
|
|
127
|
+
function connectFields(model) {
|
|
128
|
+
return model.fields.filter((f) => f.kind !== 'object' && (f.isId || f.isUnique));
|
|
129
|
+
}
|
|
130
|
+
/** The scalar FK column(s) the parent sets automatically — omit from a nested create. */
|
|
131
|
+
function backFkColumns(relatedModel, relationName) {
|
|
132
|
+
const inverse = relatedModel.fields.find((f) => f.kind === 'object' && f.relationName === relationName && (f.relationFromFields || []).length > 0);
|
|
133
|
+
return inverse ? inverse.relationFromFields || [] : [];
|
|
134
|
+
}
|
|
135
|
+
/** Renders class-body lines for a set of scalar/enum fields; collects imports. */
|
|
136
|
+
function renderScalarLines(fields, mode, validatorImports, enumImports) {
|
|
137
|
+
const lines = [];
|
|
138
|
+
for (const field of fields) {
|
|
139
|
+
const m = mapField(field);
|
|
140
|
+
validatorImports.add(m.validator);
|
|
141
|
+
if (m.enumArg)
|
|
142
|
+
enumImports.add(m.enumArg);
|
|
143
|
+
// update / connect: everything optional. create: optional if not required or defaulted.
|
|
144
|
+
const optional = mode !== 'create' || !field.isRequired || field.hasDefaultValue;
|
|
145
|
+
const isList = field.isList;
|
|
146
|
+
const tsType = isList ? `${m.tsType}[]` : m.tsType;
|
|
147
|
+
let validatorDecorator;
|
|
148
|
+
if (m.validator === 'Allow') {
|
|
149
|
+
validatorDecorator = '@Allow()';
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const args = [];
|
|
153
|
+
if (m.enumArg)
|
|
154
|
+
args.push(m.enumArg);
|
|
155
|
+
if (isList)
|
|
156
|
+
args.push('{ each: true }');
|
|
157
|
+
validatorDecorator = `@${m.validator}(${args.join(', ')})`;
|
|
158
|
+
}
|
|
159
|
+
const decoratorLines = [];
|
|
160
|
+
if (optional) {
|
|
161
|
+
validatorImports.add('IsOptional');
|
|
162
|
+
decoratorLines.push(' @IsOptional()');
|
|
163
|
+
}
|
|
164
|
+
if (isList && m.validator !== 'Allow') {
|
|
165
|
+
validatorImports.add('IsArray');
|
|
166
|
+
decoratorLines.push(' @IsArray()');
|
|
167
|
+
}
|
|
168
|
+
decoratorLines.push(` ${validatorDecorator}`);
|
|
169
|
+
lines.push(decoratorLines.join('\n'));
|
|
170
|
+
lines.push(` ${field.name}${optional ? '?' : '!'}: ${tsType};`);
|
|
171
|
+
lines.push('');
|
|
172
|
+
}
|
|
173
|
+
return lines;
|
|
174
|
+
}
|
|
175
|
+
function renderClass(className, bodyLines) {
|
|
176
|
+
return `export class ${className} {\n` + (bodyLines.length ? bodyLines.join('\n') : '') + `}\n`;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* For a model's CREATE DTO, render the nested-write classes for each relation
|
|
180
|
+
* plus the property lines to add to the parent class. Nested create classes are
|
|
181
|
+
* scalars-only (no deeper relations) so there are no circular imports.
|
|
182
|
+
*/
|
|
183
|
+
function renderRelationNestedWrites(model, validatorImports, enumImports, transformerImports) {
|
|
184
|
+
const classes = [];
|
|
185
|
+
const parentLines = [];
|
|
186
|
+
const relations = model.fields.filter((f) => f.kind === 'object');
|
|
187
|
+
for (const rel of relations) {
|
|
188
|
+
const related = modelsByName[rel.type];
|
|
189
|
+
if (!related)
|
|
190
|
+
continue;
|
|
191
|
+
const relCap = rel.name.charAt(0).toUpperCase() + rel.name.slice(1);
|
|
192
|
+
const createCls = `${model.name}${relCap}CreateNestedDto`;
|
|
193
|
+
const connectCls = `${model.name}${relCap}ConnectNestedDto`;
|
|
194
|
+
const writeCls = `${model.name}${relCap}NestedWriteDto`;
|
|
195
|
+
// create: related writable scalars minus the back-reference FK.
|
|
196
|
+
const omit = backFkColumns(related, rel.relationName);
|
|
197
|
+
classes.push(renderClass(createCls, renderScalarLines(writableFields(related, omit), 'create', validatorImports, enumImports)));
|
|
198
|
+
// connect: related unique fields (all optional).
|
|
199
|
+
const conn = connectFields(related);
|
|
200
|
+
classes.push(renderClass(connectCls, renderScalarLines(conn, 'connect', validatorImports, enumImports)));
|
|
201
|
+
// nested-write: create? / connect? only.
|
|
202
|
+
transformerImports.add('Type');
|
|
203
|
+
validatorImports.add('IsOptional');
|
|
204
|
+
validatorImports.add('ValidateNested');
|
|
205
|
+
const eachArg = rel.isList ? '{ each: true }' : '';
|
|
206
|
+
const arrSuffix = rel.isList ? '[]' : '';
|
|
207
|
+
if (rel.isList)
|
|
208
|
+
validatorImports.add('IsArray');
|
|
209
|
+
const opLines = [];
|
|
210
|
+
for (const [op, cls] of [['create', createCls], ['connect', connectCls]]) {
|
|
211
|
+
if (rel.isList)
|
|
212
|
+
opLines.push(' @IsArray()');
|
|
213
|
+
opLines.push(' @IsOptional()');
|
|
214
|
+
opLines.push(` @ValidateNested(${eachArg})`);
|
|
215
|
+
opLines.push(` @Type(() => ${cls})`);
|
|
216
|
+
opLines.push(` ${op}?: ${cls}${arrSuffix};`);
|
|
217
|
+
opLines.push('');
|
|
218
|
+
}
|
|
219
|
+
classes.push(renderClass(writeCls, opLines));
|
|
220
|
+
// property on the parent CREATE DTO.
|
|
221
|
+
parentLines.push(' @IsOptional()');
|
|
222
|
+
parentLines.push(' @ValidateNested()');
|
|
223
|
+
parentLines.push(` @Type(() => ${writeCls})`);
|
|
224
|
+
parentLines.push(` ${rel.name}?: ${writeCls};`);
|
|
225
|
+
parentLines.push('');
|
|
226
|
+
}
|
|
227
|
+
return { classes, parentLines };
|
|
228
|
+
}
|
|
229
|
+
/** Renders a base DTO file (nested relation writes on create only). */
|
|
230
|
+
function renderBaseDto(className, model, mode) {
|
|
231
|
+
const validatorImports = new Set();
|
|
232
|
+
const enumImports = new Set();
|
|
233
|
+
const transformerImports = new Set();
|
|
234
|
+
const scalarLines = renderScalarLines(writableFields(model), mode, validatorImports, enumImports);
|
|
235
|
+
let nested = { classes: [], parentLines: [] };
|
|
236
|
+
if (mode === 'create') {
|
|
237
|
+
nested = renderRelationNestedWrites(model, validatorImports, enumImports, transformerImports);
|
|
238
|
+
}
|
|
239
|
+
const parentClass = renderClass(className, [...scalarLines, ...nested.parentLines]);
|
|
240
|
+
const importLines = [];
|
|
241
|
+
if (validatorImports.size > 0) {
|
|
242
|
+
importLines.push(`import { ${[...validatorImports].sort().join(', ')} } from 'class-validator';`);
|
|
243
|
+
}
|
|
244
|
+
if (transformerImports.size > 0) {
|
|
245
|
+
importLines.push(`import { ${[...transformerImports].sort().join(', ')} } from 'class-transformer';`);
|
|
246
|
+
}
|
|
247
|
+
if (enumImports.size > 0) {
|
|
248
|
+
importLines.push(`import { ${[...enumImports].sort().join(', ')} } from '@prisma/client';`);
|
|
249
|
+
}
|
|
250
|
+
return (utils_1.GENERATED_BANNER +
|
|
251
|
+
'\n' +
|
|
252
|
+
(importLines.length ? importLines.join('\n') + '\n\n' : '') +
|
|
253
|
+
// nested classes first (referenced by @Type in the parent).
|
|
254
|
+
(nested.classes.length ? nested.classes.join('\n') + '\n' : '') +
|
|
255
|
+
parentClass);
|
|
256
|
+
}
|
|
257
|
+
/** Renders the hand-owned subclass (generated once, then customizable). */
|
|
258
|
+
function renderSubclassDto(subclass, baseClass, importPath) {
|
|
259
|
+
return (`import { ${baseClass} } from '${importPath}';\n\n` +
|
|
260
|
+
`/**\n` +
|
|
261
|
+
` * Hand-owned DTO for the CRUD endpoint. Add custom class-validator rules\n` +
|
|
262
|
+
` * here; this file is generated once and never overwritten by \`appx generate\`.\n` +
|
|
263
|
+
` * The base class is regenerated from the Prisma schema.\n` +
|
|
264
|
+
` */\n` +
|
|
265
|
+
`export class ${subclass} extends ${baseClass} {}\n`);
|
|
266
|
+
}
|
|
267
|
+
for (const model of Prisma.dmmf.datamodel.models) {
|
|
268
|
+
const modelName = model.name; // Pascal, e.g. ProjectMember
|
|
269
|
+
const folder = (0, utils_1.pascalToKebabCase)(modelName); // kebab, e.g. project-member
|
|
270
|
+
if (utils_1.IGNORE_FOLDERS.includes(folder))
|
|
271
|
+
continue;
|
|
272
|
+
for (const mode of ['create', 'update']) {
|
|
273
|
+
const capMode = mode.charAt(0).toUpperCase() + mode.slice(1);
|
|
274
|
+
const baseClass = `${capMode}${modelName}GeneratedDto`;
|
|
275
|
+
const subclass = `${capMode}${modelName}Dto`;
|
|
276
|
+
// Base (overwritten, gitignored).
|
|
277
|
+
const basePath = path.join(generatedDtoRoot, folder, `${mode}-${folder}.generated.dto.ts`);
|
|
278
|
+
(0, utils_1.writeGeneratedFile)(basePath, renderBaseDto(baseClass, model, mode));
|
|
279
|
+
// Subclass (once, hand-owned, committed).
|
|
280
|
+
const subPath = path.join(modulesRoot, folder, 'dto', `${mode}-${folder}.dto.ts`);
|
|
281
|
+
const importPath = `../../../generated/dto/${folder}/${mode}-${folder}.generated.dto`;
|
|
282
|
+
(0, utils_1.createFileIfNotExists)(subPath, renderSubclassDto(subclass, baseClass, importPath));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
console.log('DTOs generated.');
|
package/dist/config/utils.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
export declare const kebabToPascalCase: (str: string) => string;
|
|
2
|
+
export declare const pascalToKebabCase: (str: string) => string;
|
|
2
3
|
export declare const createFileIfNotExists: (filePath: string, content: string) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Banner stamped on every fully-generated file. These files live under
|
|
6
|
+
* `src/generated/` (gitignored) and are OVERWRITTEN on every `appx generate`.
|
|
7
|
+
* Never hand-edit them — customize the hand-owned subclass under
|
|
8
|
+
* `src/modules/<model>/dto/` instead.
|
|
9
|
+
*/
|
|
10
|
+
export declare const GENERATED_BANNER: string;
|
|
11
|
+
/**
|
|
12
|
+
* Writes a fully-generated file, always overwriting. Use only for artifacts
|
|
13
|
+
* under `src/generated/` that carry {@link GENERATED_BANNER}. Creates parent
|
|
14
|
+
* directories as needed.
|
|
15
|
+
*/
|
|
16
|
+
export declare const writeGeneratedFile: (filePath: string, content: string) => void;
|
|
3
17
|
export declare const IGNORE_FOLDERS: string[];
|
|
4
18
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/config/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/config/utils.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,QAAS,MAAM,WAK5C,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAS,MAAM,WAK5C,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAAc,MAAM,WAAW,MAAM,SAStE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAGoD,CAAC;AAElF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,aAAc,MAAM,WAAW,MAAM,SAInE,CAAC;AAEF,eAAO,MAAM,cAAc,UAS1B,CAAC"}
|
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 = {
|