@appxdigital/appx-core 0.1.120 → 0.1.121-beta.10

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/LICENSE +21 -21
  3. package/PRERELEASES.md +142 -0
  4. package/README.md +627 -533
  5. package/RELEASING.md +168 -0
  6. package/dist/common/config/permissionsConfigTypes.d.ts +8 -0
  7. package/dist/common/config/permissionsConfigTypes.d.ts.map +1 -1
  8. package/dist/common/config/permissionsConfigTypes.js +13 -1
  9. package/dist/common/decorators/permission.decorator.d.ts +14 -0
  10. package/dist/common/decorators/permission.decorator.d.ts.map +1 -1
  11. package/dist/common/decorators/permission.decorator.js +16 -1
  12. package/dist/common/guards/rbac.guard.d.ts.map +1 -1
  13. package/dist/common/guards/rbac.guard.js +8 -1
  14. package/dist/common/interceptors/prisma.interceptor.d.ts.map +1 -1
  15. package/dist/common/interceptors/prisma.interceptor.js +24 -8
  16. package/dist/common/setup/core-config.d.ts +66 -0
  17. package/dist/common/setup/core-config.d.ts.map +1 -0
  18. package/dist/common/setup/core-config.js +67 -0
  19. package/dist/common/setup/setup-core-security.d.ts +62 -0
  20. package/dist/common/setup/setup-core-security.d.ts.map +1 -0
  21. package/dist/common/setup/setup-core-security.js +40 -0
  22. package/dist/config/generate-all.js +4 -0
  23. package/dist/config/generate-controllers.js +19 -2
  24. package/dist/config/generate-dtos.d.ts +2 -0
  25. package/dist/config/generate-dtos.d.ts.map +1 -0
  26. package/dist/config/generate-dtos.js +285 -0
  27. package/dist/config/utils.d.ts +14 -0
  28. package/dist/config/utils.d.ts.map +1 -1
  29. package/dist/config/utils.js +35 -2
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -0
  33. package/dist/modules/auth/auth.controller.d.ts.map +1 -1
  34. package/dist/modules/auth/auth.controller.js +6 -2
  35. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  36. package/dist/modules/core/core.controller.d.ts.map +1 -1
  37. package/dist/modules/core/core.controller.js +12 -5
  38. package/dist/modules/user/user.service.d.ts.map +1 -1
  39. package/dist/modules/user/user.service.js +7 -3
  40. package/dist/prisma/prisma.service.d.ts +94 -2
  41. package/dist/prisma/prisma.service.d.ts.map +1 -1
  42. package/dist/prisma/prisma.service.js +338 -8
  43. package/docs/README.md +21 -0
  44. package/docs/data-access.md +108 -0
  45. package/docs/dtos.md +147 -0
  46. package/docs/graphql.md +72 -0
  47. package/docs/limitations.md +55 -0
  48. package/docs/permissions.md +155 -0
  49. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=generate-dtos.d.ts.map
@@ -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.');
@@ -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":"AAEA,eAAO,MAAM,iBAAiB,QAAS,MAAM,WAK5C,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAAc,MAAM,WAAW,MAAM,SAQtE,CAAC;AAEF,eAAO,MAAM,cAAc,UAK1B,CAAC"}
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"}
@@ -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';
@@ -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;IAK1D,cAAc,CAAkB,MAAM,EAAE,MAAM,EAAS,GAAG,EAAE,OAAO;;;IAUnE,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"}
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
- //TODO - Implement a more dynamic way to handle role access
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
- //TODO - Implement a more dynamic way to handle role access
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,EAAC,MAAM,aAAa,CAAC;AAKvC,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
+ {"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;AAQ3E,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;IAoBtD,MAAM,CAAc,EAAE,EAAE,MAAM,EAAU,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IAoBxD,MAAM,CAAc,EAAE,EAAE,MAAM;CAGvC"}
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
- actionPermission?.setUserIdField) {
44
- const userIdField = actionPermission.setUserIdField;
45
- data[userIdField] = user.id;
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
- const actionPermission = rolePermissions?.['update'];
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;IAM3D,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAUpD,WAAW,CAAC,KAAK,EAAE,MAAM;CAYlC"}
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
- return this.prisma.user.create({
23
- data: createUserInput,
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 = {