@comet/api-generator 8.0.0-beta.0

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 (27) hide show
  1. package/LICENSE +24 -0
  2. package/bin/api-generator.js +5 -0
  3. package/lib/apiGenerator.d.ts +1 -0
  4. package/lib/apiGenerator.js +7 -0
  5. package/lib/commands/generate/generate-command.d.ts +2 -0
  6. package/lib/commands/generate/generate-command.js +45 -0
  7. package/lib/commands/generate/generateCrud/generate-crud.d.ts +24 -0
  8. package/lib/commands/generate/generateCrud/generate-crud.js +1212 -0
  9. package/lib/commands/generate/generateCrudInput/generate-crud-input.d.ts +11 -0
  10. package/lib/commands/generate/generateCrudInput/generate-crud-input.js +463 -0
  11. package/lib/commands/generate/generateCrudSingle/generate-crud-single.d.ts +4 -0
  12. package/lib/commands/generate/generateCrudSingle/generate-crud-single.js +153 -0
  13. package/lib/commands/generate/utils/build-name-variants.d.ts +10 -0
  14. package/lib/commands/generate/utils/build-name-variants.js +25 -0
  15. package/lib/commands/generate/utils/constants.d.ts +1 -0
  16. package/lib/commands/generate/utils/constants.js +4 -0
  17. package/lib/commands/generate/utils/generate-imports-code.d.ts +5 -0
  18. package/lib/commands/generate/utils/generate-imports-code.js +32 -0
  19. package/lib/commands/generate/utils/test-helper.d.ts +5 -0
  20. package/lib/commands/generate/utils/test-helper.js +72 -0
  21. package/lib/commands/generate/utils/ts-morph-helper.d.ts +10 -0
  22. package/lib/commands/generate/utils/ts-morph-helper.js +191 -0
  23. package/lib/commands/generate/utils/write-generated-file.d.ts +1 -0
  24. package/lib/commands/generate/utils/write-generated-file.js +69 -0
  25. package/lib/commands/generate/utils/write-generated-files.d.ts +8 -0
  26. package/lib/commands/generate/utils/write-generated-files.js +20 -0
  27. package/package.json +65 -0
@@ -0,0 +1,1212 @@
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 () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.buildOptions = buildOptions;
46
+ exports.generateCrud = generateCrud;
47
+ /* eslint-disable @typescript-eslint/no-explicit-any */
48
+ const cms_api_1 = require("@comet/cms-api");
49
+ const postgresql_1 = require("@mikro-orm/postgresql");
50
+ const path = __importStar(require("path"));
51
+ const pluralize_1 = require("pluralize");
52
+ const generate_crud_input_1 = require("../generateCrudInput/generate-crud-input");
53
+ const build_name_variants_1 = require("../utils/build-name-variants");
54
+ const constants_1 = require("../utils/constants");
55
+ const generate_imports_code_1 = require("../utils/generate-imports-code");
56
+ const ts_morph_helper_1 = require("../utils/ts-morph-helper");
57
+ // TODO move into own file
58
+ function buildOptions(metadata, generatorOptions) {
59
+ var _a, _b, _c, _d;
60
+ const { classNameSingular, classNamePlural, fileNameSingular, fileNamePlural } = (0, build_name_variants_1.buildNameVariants)(metadata);
61
+ const dedicatedResolverArgProps = metadata.props.filter((prop) => {
62
+ if ((0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "dedicatedResolverArg")) {
63
+ if (prop.kind == "m:1") {
64
+ return true;
65
+ }
66
+ else {
67
+ console.warn(`${metadata.className} ${prop.name} can't use dedicatedResolverArg as it's not a m:1 relation`);
68
+ return false;
69
+ }
70
+ }
71
+ return false;
72
+ });
73
+ const crudSearchPropNames = (0, cms_api_1.getCrudSearchFieldsFromMetadata)(metadata);
74
+ const hasSearchArg = crudSearchPropNames.length > 0;
75
+ let statusProp = metadata.props.find((prop) => prop.name == "status");
76
+ if (statusProp) {
77
+ if (!statusProp.enum) {
78
+ console.warn(`${metadata.className} status prop must be an enum to be supported by crud generator`);
79
+ statusProp = undefined;
80
+ }
81
+ else if (statusProp.nullable) {
82
+ console.warn(`${metadata.className} status prop must not be nullable to be supported by crud generator`);
83
+ statusProp = undefined;
84
+ }
85
+ else if (((_a = (0, ts_morph_helper_1.morphTsProperty)(statusProp.name, metadata).getInitializer()) === null || _a === void 0 ? void 0 : _a.getText()) == "") {
86
+ console.warn(`${metadata.className} status prop must have a default value to be supported by crud generator`);
87
+ statusProp = undefined;
88
+ }
89
+ }
90
+ let statusActiveItems = undefined;
91
+ if (statusProp) {
92
+ if (!statusProp.items)
93
+ throw new Error("Status enum prop has not items");
94
+ statusActiveItems = statusProp.items.filter((item) => {
95
+ if (typeof item == "number") {
96
+ console.warn(`${metadata.className} status prop must not have numeric items to be supported by crud generator`);
97
+ return false;
98
+ }
99
+ return ["Active", "Visible", "Invisible", "Published", "Unpublished"].includes(item);
100
+ });
101
+ }
102
+ const hasStatusFilter = statusProp && statusActiveItems && statusActiveItems.length != ((_b = statusProp.items) === null || _b === void 0 ? void 0 : _b.length); //if all items are active ones, no need for status filter
103
+ const crudFilterProps = metadata.props.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "filter") &&
104
+ !prop.name.startsWith("scope_") &&
105
+ prop.name != "position" &&
106
+ (!prop.embedded || (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.embedded[0], "filter")) && // the whole embeddable has filter disabled
107
+ (prop.enum ||
108
+ prop.type === "string" ||
109
+ prop.type === "text" ||
110
+ prop.type === "DecimalType" ||
111
+ prop.type === "number" ||
112
+ constants_1.integerTypes.includes(prop.type) ||
113
+ prop.type === "BooleanType" ||
114
+ prop.type === "boolean" ||
115
+ prop.type === "DateType" ||
116
+ prop.type === "Date" ||
117
+ prop.kind === "m:1" ||
118
+ prop.kind === "1:m" ||
119
+ prop.kind === "m:n" ||
120
+ prop.type === "EnumArrayType") &&
121
+ !dedicatedResolverArgProps.some((dedicatedResolverArgProp) => dedicatedResolverArgProp.name == prop.name));
122
+ const hasFilterArg = crudFilterProps.length > 0;
123
+ const crudSortProps = metadata.props.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "sort") &&
124
+ !prop.name.startsWith("scope_") &&
125
+ (!prop.embedded || (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.embedded[0], "sort")) && // the whole embeddable has sort disabled
126
+ (prop.type === "string" ||
127
+ prop.type === "text" ||
128
+ prop.type === "DecimalType" ||
129
+ prop.type === "number" ||
130
+ constants_1.integerTypes.includes(prop.type) ||
131
+ prop.type === "BooleanType" ||
132
+ prop.type === "boolean" ||
133
+ prop.type === "DateType" ||
134
+ prop.type === "Date" ||
135
+ prop.kind === "m:1" ||
136
+ prop.type === "EnumArrayType"));
137
+ const hasSortArg = crudSortProps.length > 0;
138
+ const hasSlugProp = metadata.props.some((prop) => prop.name == "slug");
139
+ const scopeProp = metadata.props.find((prop) => prop.name == "scope");
140
+ if (scopeProp && !scopeProp.targetMeta)
141
+ throw new Error("Scope prop has no targetMeta");
142
+ const hasPositionProp = metadata.props.some((prop) => prop.name == "position");
143
+ const positionGroupPropNames = hasPositionProp
144
+ ? ((_d = (_c = generatorOptions.position) === null || _c === void 0 ? void 0 : _c.groupByFields) !== null && _d !== void 0 ? _d : [
145
+ ...(scopeProp ? [scopeProp.name] : []), // if there is a scope prop it's effecting position-group, if not groupByFields should be used
146
+ ])
147
+ : [];
148
+ const positionGroupProps = hasPositionProp ? metadata.props.filter((prop) => positionGroupPropNames.includes(prop.name)) : [];
149
+ const scopedEntity = Reflect.getMetadata("scopedEntity", metadata.class);
150
+ const skipScopeCheck = !scopeProp && !scopedEntity;
151
+ const argsClassName = `${classNameSingular != classNamePlural ? classNamePlural : `${classNamePlural}List`}Args`;
152
+ const argsFileName = `${fileNameSingular != fileNamePlural ? fileNamePlural : `${fileNameSingular}-list`}.args`;
153
+ const blockProps = metadata.props.filter((prop) => {
154
+ return (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "input") && prop.type === "RootBlockType";
155
+ });
156
+ return {
157
+ crudSearchPropNames,
158
+ hasSearchArg,
159
+ crudFilterProps,
160
+ hasFilterArg,
161
+ crudSortProps,
162
+ hasSortArg,
163
+ hasSlugProp,
164
+ hasPositionProp,
165
+ positionGroupProps,
166
+ statusProp,
167
+ statusActiveItems,
168
+ hasStatusFilter,
169
+ scopeProp,
170
+ skipScopeCheck,
171
+ argsClassName,
172
+ argsFileName,
173
+ blockProps,
174
+ dedicatedResolverArgProps,
175
+ };
176
+ }
177
+ function generateFilterDto({ generatorOptions, metadata }) {
178
+ const { classNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
179
+ const { crudFilterProps } = buildOptions(metadata, generatorOptions);
180
+ let importsOut = "";
181
+ let enumFiltersOut = "";
182
+ const generatedEnumNames = new Set();
183
+ const generatedEnumsNames = new Set();
184
+ crudFilterProps.map((prop) => {
185
+ if (prop.type == "EnumArrayType") {
186
+ const enumName = (0, ts_morph_helper_1.findEnumName)(prop.name, metadata);
187
+ const importPath = (0, ts_morph_helper_1.findEnumImportPath)(enumName, `${generatorOptions.targetDirectory}/dto`, metadata);
188
+ if (!generatedEnumNames.has(enumName)) {
189
+ generatedEnumNames.add(enumName);
190
+ enumFiltersOut += `@InputType()
191
+ class ${enumName}EnumsFilter extends createEnumsFilter(${enumName}) {}
192
+ `;
193
+ importsOut += `import { ${enumName} } from "${importPath}";`;
194
+ }
195
+ }
196
+ else if (prop.enum) {
197
+ const enumName = (0, ts_morph_helper_1.findEnumName)(prop.name, metadata);
198
+ const importPath = (0, ts_morph_helper_1.findEnumImportPath)(enumName, `${generatorOptions.targetDirectory}/dto`, metadata);
199
+ if (!generatedEnumsNames.has(enumName)) {
200
+ generatedEnumsNames.add(enumName);
201
+ enumFiltersOut += `@InputType()
202
+ class ${enumName}EnumFilter extends createEnumFilter(${enumName}) {}
203
+ `;
204
+ importsOut += `import { ${enumName} } from "${importPath}";`;
205
+ }
206
+ }
207
+ });
208
+ const filterOut = `import { StringFilter, NumberFilter, BooleanFilter, DateFilter, DateTimeFilter, ManyToOneFilter, OneToManyFilter, ManyToManyFilter, createEnumFilter, createEnumsFilter } from "@comet/cms-api";
209
+ import { Field, InputType } from "@nestjs/graphql";
210
+ import { Type } from "class-transformer";
211
+ import { IsNumber, IsOptional, IsString, ValidateNested } from "class-validator";
212
+ ${importsOut}
213
+
214
+ ${enumFiltersOut}
215
+
216
+ @InputType()
217
+ export class ${classNameSingular}Filter {
218
+ ${crudFilterProps
219
+ .map((prop) => {
220
+ if (prop.type == "EnumArrayType") {
221
+ const enumName = (0, ts_morph_helper_1.findEnumName)(prop.name, metadata);
222
+ return `@Field(() => ${enumName}EnumsFilter, { nullable: true })
223
+ @ValidateNested()
224
+ @IsOptional()
225
+ @Type(() => ${enumName}EnumsFilter)
226
+ ${prop.name}?: ${enumName}EnumsFilter;
227
+ `;
228
+ }
229
+ else if (prop.enum) {
230
+ const enumName = (0, ts_morph_helper_1.findEnumName)(prop.name, metadata);
231
+ return `@Field(() => ${enumName}EnumFilter, { nullable: true })
232
+ @ValidateNested()
233
+ @IsOptional()
234
+ @Type(() => ${enumName}EnumFilter)
235
+ ${prop.name}?: ${enumName}EnumFilter;
236
+ `;
237
+ }
238
+ else if (prop.type === "string" || prop.type === "text") {
239
+ return `@Field(() => StringFilter, { nullable: true })
240
+ @ValidateNested()
241
+ @IsOptional()
242
+ @Type(() => StringFilter)
243
+ ${prop.name}?: StringFilter;
244
+ `;
245
+ }
246
+ else if (prop.type === "DecimalType" || prop.type == "number" || constants_1.integerTypes.includes(prop.type)) {
247
+ return `@Field(() => NumberFilter, { nullable: true })
248
+ @ValidateNested()
249
+ @IsOptional()
250
+ @Type(() => NumberFilter)
251
+ ${prop.name}?: NumberFilter;
252
+ `;
253
+ }
254
+ else if (prop.type === "boolean" || prop.type === "BooleanType") {
255
+ return `@Field(() => BooleanFilter, { nullable: true })
256
+ @ValidateNested()
257
+ @IsOptional()
258
+ @Type(() => BooleanFilter)
259
+ ${prop.name}?: BooleanFilter;
260
+ `;
261
+ }
262
+ else if (prop.type === "DateType") {
263
+ // ISO Date without time
264
+ return `@Field(() => DateFilter, { nullable: true })
265
+ @ValidateNested()
266
+ @IsOptional()
267
+ @Type(() => DateFilter)
268
+ ${prop.name}?: DateFilter;
269
+ `;
270
+ }
271
+ else if (prop.type === "Date") {
272
+ // DateTime
273
+ return `@Field(() => DateTimeFilter, { nullable: true })
274
+ @ValidateNested()
275
+ @IsOptional()
276
+ @Type(() => DateTimeFilter)
277
+ ${prop.name}?: DateTimeFilter;
278
+ `;
279
+ }
280
+ else if (prop.kind === "m:1") {
281
+ return `@Field(() => ManyToOneFilter, { nullable: true })
282
+ @ValidateNested()
283
+ @IsOptional()
284
+ @Type(() => ManyToOneFilter)
285
+ ${prop.name}?: ManyToOneFilter;
286
+ `;
287
+ }
288
+ else if (prop.kind === "1:m") {
289
+ return `@Field(() => OneToManyFilter, { nullable: true })
290
+ @ValidateNested()
291
+ @IsOptional()
292
+ @Type(() => OneToManyFilter)
293
+ ${prop.name}?: OneToManyFilter;
294
+ `;
295
+ }
296
+ else if (prop.kind === "m:n") {
297
+ return `@Field(() => ManyToManyFilter, { nullable: true })
298
+ @ValidateNested()
299
+ @IsOptional()
300
+ @Type(() => ManyToManyFilter)
301
+ ${prop.name}?: ManyToManyFilter;
302
+ `;
303
+ }
304
+ else {
305
+ //unsupported type TODO support more
306
+ }
307
+ return "";
308
+ })
309
+ .join("\n")}
310
+
311
+ @Field(() => [${classNameSingular}Filter], { nullable: true })
312
+ @Type(() => ${classNameSingular}Filter)
313
+ @ValidateNested({ each: true })
314
+ @IsOptional()
315
+ and?: ${classNameSingular}Filter[];
316
+
317
+ @Field(() => [${classNameSingular}Filter], { nullable: true })
318
+ @Type(() => ${classNameSingular}Filter)
319
+ @ValidateNested({ each: true })
320
+ @IsOptional()
321
+ or?: ${classNameSingular}Filter[];
322
+ }
323
+ `;
324
+ return filterOut;
325
+ }
326
+ function generateSortDto({ generatorOptions, metadata }) {
327
+ const { classNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
328
+ const { crudSortProps } = buildOptions(metadata, generatorOptions);
329
+ const sortOut = `import { SortDirection } from "@comet/cms-api";
330
+ import { Field, InputType, registerEnumType } from "@nestjs/graphql";
331
+ import { Type } from "class-transformer";
332
+ import { IsEnum } from "class-validator";
333
+
334
+ export enum ${classNameSingular}SortField {
335
+ ${crudSortProps
336
+ .map((prop) => {
337
+ return `${prop.name} = "${prop.name}",`;
338
+ })
339
+ .join("\n")}
340
+ }
341
+ registerEnumType(${classNameSingular}SortField, {
342
+ name: "${classNameSingular}SortField",
343
+ });
344
+
345
+ @InputType()
346
+ export class ${classNameSingular}Sort {
347
+ @Field(() => ${classNameSingular}SortField)
348
+ @IsEnum(${classNameSingular}SortField)
349
+ field: ${classNameSingular}SortField;
350
+
351
+ @Field(() => SortDirection, { defaultValue: SortDirection.ASC })
352
+ @IsEnum(SortDirection)
353
+ direction: SortDirection = SortDirection.ASC;
354
+ }
355
+ `;
356
+ return sortOut;
357
+ }
358
+ function generatePaginatedDto({ generatorOptions, metadata }) {
359
+ const { classNamePlural } = (0, build_name_variants_1.buildNameVariants)(metadata);
360
+ const paginatedOut = `import { ObjectType } from "@nestjs/graphql";
361
+ import { PaginatedResponseFactory } from "@comet/cms-api";
362
+
363
+ import { ${metadata.className} } from "${path.relative(`${generatorOptions.targetDirectory}/dto`, metadata.path).replace(/\.ts$/, "")}";
364
+
365
+ @ObjectType()
366
+ export class Paginated${classNamePlural} extends PaginatedResponseFactory.create(${metadata.className}) {}
367
+ `;
368
+ return paginatedOut;
369
+ }
370
+ function generateArgsDto({ generatorOptions, metadata }) {
371
+ var _a;
372
+ const { classNameSingular, fileNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
373
+ const { scopeProp, argsClassName, hasSearchArg, hasSortArg, hasFilterArg, statusProp, statusActiveItems, hasStatusFilter, dedicatedResolverArgProps, } = buildOptions(metadata, generatorOptions);
374
+ const imports = [];
375
+ if (scopeProp && scopeProp.targetMeta) {
376
+ imports.push(generateEntityImport(scopeProp.targetMeta, `${generatorOptions.targetDirectory}/dto`));
377
+ }
378
+ let statusFilterClassName = undefined;
379
+ let statusFilterDefaultValue;
380
+ if (hasStatusFilter && statusProp) {
381
+ statusFilterClassName = (0, ts_morph_helper_1.findEnumName)(statusProp.name, metadata);
382
+ const importPath = (0, ts_morph_helper_1.findEnumImportPath)(statusFilterClassName, `${generatorOptions.targetDirectory}/dto`, metadata);
383
+ imports.push({
384
+ name: statusFilterClassName,
385
+ importPath,
386
+ });
387
+ if (statusActiveItems && statusActiveItems.length > 1) {
388
+ statusFilterDefaultValue = `[${statusActiveItems.map((i) => `${statusFilterClassName}.${i}`).join(", ")}]`;
389
+ }
390
+ else {
391
+ statusFilterDefaultValue = `[${(_a = (0, ts_morph_helper_1.morphTsProperty)(statusProp.name, metadata).getInitializer()) === null || _a === void 0 ? void 0 : _a.getText()}]`;
392
+ }
393
+ }
394
+ const argsOut = `import { ArgsType, Field, IntersectionType, registerEnumType, ID } from "@nestjs/graphql";
395
+ import { Type } from "class-transformer";
396
+ import { IsOptional, IsString, ValidateNested, IsEnum, IsUUID } from "class-validator";
397
+ import { OffsetBasedPaginationArgs } from "@comet/cms-api";
398
+ import { ${classNameSingular}Filter } from "./${fileNameSingular}.filter";
399
+ import { ${classNameSingular}Sort } from "./${fileNameSingular}.sort";
400
+
401
+ ${(0, generate_imports_code_1.generateImportsCode)(imports)}
402
+
403
+ @ArgsType()
404
+ export class ${argsClassName} extends OffsetBasedPaginationArgs {
405
+ ${scopeProp
406
+ ? `
407
+ @Field(() => ${scopeProp.type})
408
+ @ValidateNested()
409
+ @Type(() => ${scopeProp.type})
410
+ scope: ${scopeProp.type};
411
+ `
412
+ : ""}
413
+
414
+ ${dedicatedResolverArgProps
415
+ .map((dedicatedResolverArgProp) => {
416
+ if (constants_1.integerTypes.includes(dedicatedResolverArgProp.type)) {
417
+ return `@Field(() => ID)
418
+ @Transform(({ value }) => value.map((id: string) => parseInt(id)))
419
+ @IsInt()
420
+ ${dedicatedResolverArgProp.name}: number;`;
421
+ }
422
+ else {
423
+ return `@Field(() => ID)
424
+ @IsUUID()
425
+ ${dedicatedResolverArgProp.name}: string;`;
426
+ }
427
+ })
428
+ .join("")}
429
+
430
+ ${hasStatusFilter
431
+ ? `
432
+ @Field(() => [${statusFilterClassName}], { defaultValue: ${statusFilterDefaultValue} })
433
+ @IsEnum(${statusFilterClassName}, { each: true })
434
+ status: ${statusFilterClassName}[];
435
+ `
436
+ : ""}
437
+
438
+ ${hasSearchArg
439
+ ? `
440
+ @Field({ nullable: true })
441
+ @IsOptional()
442
+ @IsString()
443
+ search?: string;
444
+ `
445
+ : ""}
446
+
447
+ ${hasFilterArg
448
+ ? `
449
+ @Field(() => ${classNameSingular}Filter, { nullable: true })
450
+ @ValidateNested()
451
+ @Type(() => ${classNameSingular}Filter)
452
+ @IsOptional()
453
+ filter?: ${classNameSingular}Filter;
454
+ `
455
+ : ""}
456
+
457
+ ${hasSortArg
458
+ ? `
459
+ @Field(() => [${classNameSingular}Sort], { nullable: true })
460
+ @ValidateNested({ each: true })
461
+ @Type(() => ${classNameSingular}Sort)
462
+ @IsOptional()
463
+ sort?: ${classNameSingular}Sort[];
464
+ `
465
+ : ""}
466
+ }
467
+ `;
468
+ return argsOut;
469
+ }
470
+ function generateService({ generatorOptions, metadata }) {
471
+ const { classNameSingular, fileNameSingular, classNamePlural } = (0, build_name_variants_1.buildNameVariants)(metadata);
472
+ const { hasPositionProp, positionGroupProps } = buildOptions(metadata, generatorOptions);
473
+ const positionGroupType = positionGroupProps.length
474
+ ? `{ ${positionGroupProps
475
+ .map((prop) => {
476
+ const notSupportedReferenceKinds = [postgresql_1.ReferenceKind.ONE_TO_MANY, postgresql_1.ReferenceKind.MANY_TO_MANY];
477
+ if (notSupportedReferenceKinds.includes(prop.kind)) {
478
+ throw new Error(`Not supported reference-type for position-group. ${prop.name}`);
479
+ }
480
+ return `${prop.name}${prop.nullable ? `?` : ``}: ${[postgresql_1.ReferenceKind.MANY_TO_ONE, postgresql_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind) ? "string" : prop.type}`;
481
+ })
482
+ .join(",")} }`
483
+ : false;
484
+ const serviceOut = `import { FilterQuery } from "@mikro-orm/postgresql";
485
+ import { InjectRepository } from "@mikro-orm/nestjs";
486
+ import { EntityRepository, EntityManager, raw } from "@mikro-orm/postgresql";
487
+ import { Injectable } from "@nestjs/common";
488
+
489
+ ${(0, generate_imports_code_1.generateImportsCode)([generateEntityImport(metadata, generatorOptions.targetDirectory)])}
490
+ ${(0, generate_imports_code_1.generateImportsCode)(positionGroupProps.reduce((acc, prop) => {
491
+ if (prop.targetMeta) {
492
+ acc.push(generateEntityImport(prop.targetMeta, generatorOptions.targetDirectory));
493
+ }
494
+ return acc;
495
+ }, []))}
496
+ import { ${classNameSingular}Filter } from "./dto/${fileNameSingular}.filter";
497
+
498
+ @Injectable()
499
+ export class ${classNamePlural}Service {
500
+ ${hasPositionProp
501
+ ? `constructor(
502
+ private readonly entityManager: EntityManager,
503
+ @InjectRepository(${metadata.className}) private readonly repository: EntityRepository<${metadata.className}>,
504
+ ) {}`
505
+ : ""}
506
+
507
+ ${hasPositionProp
508
+ ? `
509
+ async incrementPositions(${positionGroupProps.length ? `group: ${positionGroupType},` : ``}lowestPosition: number, highestPosition?: number) {
510
+ // Increment positions between newPosition (inclusive) and oldPosition (exclusive)
511
+ await this.repository.nativeUpdate(
512
+ ${positionGroupProps.length
513
+ ? `{
514
+ $and: [
515
+ { position: { $gte: lowestPosition, ...(highestPosition ? { $lt: highestPosition } : {}) } },
516
+ this.getPositionGroupCondition(group),
517
+ ],
518
+ },`
519
+ : `{ position: { $gte: lowestPosition, ...(highestPosition ? { $lt: highestPosition } : {}) } },`}
520
+ { position: raw("position + 1") },
521
+ );
522
+ }
523
+
524
+ async decrementPositions(${positionGroupProps.length ? `group: ${positionGroupType},` : ``}lowestPosition: number, highestPosition?: number) {
525
+ // Decrement positions between oldPosition (exclusive) and newPosition (inclusive)
526
+ await this.repository.nativeUpdate(
527
+ ${positionGroupProps.length
528
+ ? `{
529
+ $and: [
530
+ { position: { $gt: lowestPosition, ...(highestPosition ? { $lte: highestPosition } : {}) } },
531
+ this.getPositionGroupCondition(group),
532
+ ],
533
+ },`
534
+ : `{ position: { $gt: lowestPosition, ...(highestPosition ? { $lte: highestPosition } : {}) } },`}
535
+ { position: raw("position - 1") },
536
+ );
537
+ }
538
+
539
+ async getLastPosition(${positionGroupProps.length ? `group: ${positionGroupType}` : ``}) {
540
+ return this.repository.count(${positionGroupProps.length ? `this.getPositionGroupCondition(group)` : `{}`});
541
+ }
542
+
543
+ ${positionGroupProps.length
544
+ ? `getPositionGroupCondition(group: ${positionGroupType}): FilterQuery<${metadata.className}> {
545
+ return {
546
+ ${positionGroupProps.map((field) => `${field.name}: { $eq: group.${field.name} }`).join(",")}
547
+ };
548
+ }`
549
+ : ``}
550
+ `
551
+ : ""}
552
+ }
553
+ `;
554
+ return serviceOut;
555
+ }
556
+ function generateEntityImport(targetMetadata, relativeTo) {
557
+ return {
558
+ name: targetMetadata.className,
559
+ importPath: path.relative(relativeTo, targetMetadata.path).replace(/\.ts$/, ""),
560
+ };
561
+ }
562
+ function generateInputHandling(options, metadata, generatorOptions) {
563
+ const { instanceNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
564
+ const { blockProps, scopeProp, hasPositionProp, dedicatedResolverArgProps } = buildOptions(metadata, generatorOptions);
565
+ const injectRepositories = [];
566
+ const props = metadata.props.filter((prop) => !options.excludeFields || !options.excludeFields.includes(prop.name));
567
+ const relationManyToOneProps = props.filter((prop) => prop.kind === "m:1");
568
+ const relationOneToManyProps = props.filter((prop) => prop.kind === "1:m");
569
+ const relationManyToManyProps = props.filter((prop) => prop.kind === "m:n");
570
+ const relationOneToOneProps = props.filter((prop) => prop.kind === "1:1");
571
+ const inputRelationManyToOneProps = relationManyToOneProps
572
+ .filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "input"))
573
+ .filter((prop) => {
574
+ //filter out props that are dedicatedResolverArgProp
575
+ return !dedicatedResolverArgProps.some((dedicatedResolverArgProp) => dedicatedResolverArgProp.name === prop.name);
576
+ })
577
+ .map((prop) => {
578
+ const targetMeta = prop.targetMeta;
579
+ if (!targetMeta)
580
+ throw new Error("targetMeta is not set for relation");
581
+ injectRepositories.push(targetMeta);
582
+ return {
583
+ name: prop.name,
584
+ singularName: (0, pluralize_1.singular)(prop.name),
585
+ nullable: prop.nullable,
586
+ type: prop.type,
587
+ repositoryName: `${(0, build_name_variants_1.classNameToInstanceName)(prop.type)}Repository`,
588
+ };
589
+ });
590
+ const inputRelationOneToOneProps = relationOneToOneProps
591
+ .filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "input"))
592
+ .map((prop) => {
593
+ const targetMeta = prop.targetMeta;
594
+ if (!targetMeta)
595
+ throw new Error("targetMeta is not set for relation");
596
+ injectRepositories.push(targetMeta);
597
+ return {
598
+ name: prop.name,
599
+ singularName: (0, pluralize_1.singular)(prop.name),
600
+ nullable: prop.nullable,
601
+ type: prop.type,
602
+ repositoryName: `${(0, build_name_variants_1.classNameToInstanceName)(prop.type)}Repository`,
603
+ targetMeta,
604
+ };
605
+ });
606
+ const inputRelationToManyProps = [...relationOneToManyProps, ...relationManyToManyProps]
607
+ .filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "input"))
608
+ .map((prop) => {
609
+ const targetMeta = prop.targetMeta;
610
+ if (!targetMeta)
611
+ throw new Error("targetMeta is not set for relation");
612
+ injectRepositories.push(targetMeta);
613
+ return {
614
+ name: prop.name,
615
+ singularName: (0, pluralize_1.singular)(prop.name),
616
+ nullable: prop.nullable,
617
+ type: prop.type,
618
+ repositoryName: `${(0, build_name_variants_1.classNameToInstanceName)(prop.type)}Repository`,
619
+ orphanRemoval: prop.orphanRemoval,
620
+ targetMeta,
621
+ };
622
+ });
623
+ function innerGenerateInputHandling(...args) {
624
+ const ret = generateInputHandling(...args);
625
+ injectRepositories.push(...ret.injectRepositories);
626
+ return ret.code;
627
+ }
628
+ const noAssignProps = [...inputRelationToManyProps, ...inputRelationManyToOneProps, ...inputRelationOneToOneProps, ...blockProps];
629
+ const code = `
630
+ ${noAssignProps.length
631
+ ? `const { ${noAssignProps.map((prop) => `${prop.name}: ${prop.name}Input`).join(", ")}, ...assignInput } = ${options.inputName};`
632
+ : ""}
633
+ ${options.assignEntityCode}
634
+ ...${noAssignProps.length ? `assignInput` : options.inputName},
635
+ ${options.mode == "create" && scopeProp ? `scope,` : ""}${options.mode == "create" && hasPositionProp ? `position,` : ""}
636
+ ${options.mode == "create"
637
+ ? dedicatedResolverArgProps
638
+ .map((dedicatedResolverArgProp) => {
639
+ return `${dedicatedResolverArgProp.name}: Reference.create(await this.${(0, build_name_variants_1.classNameToInstanceName)(dedicatedResolverArgProp.type)}Repository.findOneOrFail(${dedicatedResolverArgProp.name})), `;
640
+ })
641
+ .join("")
642
+ : ""}
643
+ ${options.mode == "create" || options.mode == "updateNested"
644
+ ? inputRelationManyToOneProps
645
+ .map((prop) => `${prop.name}: ${prop.nullable ? `${prop.name}Input ? ` : ""}Reference.create(await this.${prop.repositoryName}.findOneOrFail(${prop.name}Input))${prop.nullable ? ` : undefined` : ""}, `)
646
+ .join("")
647
+ : ""}
648
+ ${options.mode == "create" || options.mode == "updateNested"
649
+ ? blockProps.map((prop) => `${prop.name}: ${prop.name}Input.transformToBlockData(),`).join("")
650
+ : ""}
651
+ });
652
+ ${inputRelationToManyProps
653
+ .map((prop) => {
654
+ if (prop.orphanRemoval) {
655
+ const code = innerGenerateInputHandling({
656
+ mode: "updateNested",
657
+ inputName: `${prop.singularName}Input`,
658
+ // alternative `return this.${prop.repositoryName}.create({` requires back relation to be set
659
+ assignEntityCode: `return this.${prop.repositoryName}.assign(new ${prop.type}(), {`,
660
+ excludeFields: prop.targetMeta.props
661
+ .filter((prop) => prop.kind == "m:1" && prop.targetMeta == metadata) //filter out referencing back to this entity
662
+ .map((prop) => prop.name),
663
+ }, prop.targetMeta, generatorOptions);
664
+ const isAsync = code.includes("await ");
665
+ return `if (${prop.name}Input) {
666
+ await ${instanceNameSingular}.${prop.name}.loadItems();
667
+ ${instanceNameSingular}.${prop.name}.set(
668
+ ${isAsync ? `await Promise.all(` : ""}
669
+ ${prop.name}Input.map(${isAsync ? `async ` : ""}(${prop.singularName}Input) => {
670
+ ${code}
671
+ })
672
+ ${isAsync ? `)` : ""}
673
+ );
674
+ }`;
675
+ }
676
+ else {
677
+ return `
678
+ if (${prop.name}Input) {
679
+ const ${prop.name} = await this.${prop.repositoryName}.find({ id: ${prop.name}Input });
680
+ if (${prop.name}.length != ${prop.name}Input.length) throw new Error("Couldn't find all ${prop.name} that were passed as input");
681
+ await ${instanceNameSingular}.${prop.name}.loadItems();
682
+ ${instanceNameSingular}.${prop.name}.set(${prop.name}.map((${prop.singularName}) => Reference.create(${prop.singularName})));
683
+ }`;
684
+ }
685
+ })
686
+ .join("")}
687
+
688
+ ${inputRelationOneToOneProps
689
+ .map((prop) => `
690
+ ${options.mode != "create" || prop.nullable ? `if (${prop.name}Input) {` : "{"}
691
+ const ${prop.singularName} = ${(options.mode == "update" || options.mode == "updateNested") && prop.nullable
692
+ ? `${instanceNameSingular}.${prop.name} ? await ${instanceNameSingular}.${prop.name}.loadOrFail() : new ${prop.type}();`
693
+ : `new ${prop.type}();`}
694
+ ${innerGenerateInputHandling({
695
+ mode: "updateNested",
696
+ inputName: `${prop.name}Input`,
697
+ assignEntityCode: `this.${prop.repositoryName}.assign(${prop.singularName}, {`,
698
+ excludeFields: prop.targetMeta.props
699
+ .filter((prop) => prop.kind == "1:1" && prop.targetMeta == metadata) //filter out referencing back to this entity
700
+ .map((prop) => prop.name),
701
+ }, prop.targetMeta, generatorOptions)}
702
+ ${options.mode != "create" || prop.nullable ? `}` : "}"}`)
703
+ .join("")}
704
+ ${options.mode == "update"
705
+ ? inputRelationManyToOneProps
706
+ .map((prop) => `if (${prop.name}Input !== undefined) {
707
+ ${instanceNameSingular}.${prop.name} =
708
+ ${prop.nullable ? `${prop.name}Input ? ` : ""}
709
+ Reference.create(await this.${prop.repositoryName}.findOneOrFail(${prop.name}Input))
710
+ ${prop.nullable ? ` : undefined` : ""};
711
+ }`)
712
+ .join("")
713
+ : ""}
714
+ ${options.mode == "update"
715
+ ? blockProps
716
+ .map((prop) => `
717
+ if (${prop.name}Input) {
718
+ ${instanceNameSingular}.${prop.name} = ${prop.name}Input.transformToBlockData();
719
+ }`)
720
+ .join("")
721
+ : ""}
722
+ `;
723
+ return { code, injectRepositories };
724
+ }
725
+ function generateNestedEntityResolver({ generatorOptions, metadata }) {
726
+ const { classNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
727
+ const { skipScopeCheck } = buildOptions(metadata, generatorOptions);
728
+ const imports = [];
729
+ const { imports: fieldImports, code, hasOutputRelations, needsBlocksTransformer, } = generateRelationsFieldResolver({ generatorOptions, metadata });
730
+ if (!hasOutputRelations)
731
+ return null;
732
+ imports.push(...fieldImports);
733
+ imports.push(generateEntityImport(metadata, generatorOptions.targetDirectory));
734
+ return `
735
+ import { RequiredPermission, RootBlockDataScalar, BlocksTransformerService } from "@comet/cms-api";
736
+ import { Args, ID, Info, Mutation, Query, Resolver, ResolveField, Parent } from "@nestjs/graphql";
737
+ ${(0, generate_imports_code_1.generateImportsCode)(imports)}
738
+
739
+ @Resolver(() => ${metadata.className})
740
+ @RequiredPermission(${JSON.stringify(generatorOptions.requiredPermission)}${skipScopeCheck ? `, { skipScopeCheck: true }` : ""})
741
+ export class ${classNameSingular}Resolver {
742
+ ${needsBlocksTransformer ? `constructor(private readonly blocksTransformer: BlocksTransformerService) {}` : ""}
743
+ ${code}
744
+ }
745
+ `;
746
+ }
747
+ function generateRelationsFieldResolver({ generatorOptions, metadata }) {
748
+ const { instanceNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
749
+ const relationManyToOneProps = metadata.props.filter((prop) => prop.kind === "m:1");
750
+ const relationOneToManyProps = metadata.props.filter((prop) => prop.kind === "1:m");
751
+ const relationManyToManyProps = metadata.props.filter((prop) => prop.kind === "m:n");
752
+ const relationOneToOneProps = metadata.props.filter((prop) => prop.kind === "1:1");
753
+ const outputRelationManyToOneProps = relationManyToOneProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
754
+ const outputRelationOneToManyProps = relationOneToManyProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
755
+ const outputRelationManyToManyProps = relationManyToManyProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
756
+ const outputRelationOneToOneProps = relationOneToOneProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
757
+ for (const prop of metadata.props) {
758
+ if (!(0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField") &&
759
+ !relationManyToOneProps.includes(prop) &&
760
+ !relationOneToManyProps.includes(prop) &&
761
+ !relationManyToManyProps.includes(prop) &&
762
+ !relationOneToOneProps.includes(prop)) {
763
+ throw new Error(`${prop.name}: @CrudField resolveField=false is only used for relations, for other props simply remove @Field() to disable its output`);
764
+ }
765
+ }
766
+ const resolveFieldBlockProps = metadata.props.filter((prop) => {
767
+ return (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField") && prop.type === "RootBlockType";
768
+ });
769
+ const hasOutputRelations = outputRelationManyToOneProps.length > 0 ||
770
+ outputRelationOneToManyProps.length > 0 ||
771
+ outputRelationManyToManyProps.length > 0 ||
772
+ outputRelationOneToOneProps.length > 0;
773
+ const imports = [];
774
+ for (const prop of [...relationManyToOneProps, ...relationOneToManyProps, ...relationManyToManyProps, ...relationOneToOneProps]) {
775
+ if (!prop.targetMeta)
776
+ throw new Error(`Relation ${prop.name} has targetMeta not set`);
777
+ imports.push(generateEntityImport(prop.targetMeta, generatorOptions.targetDirectory));
778
+ }
779
+ for (const prop of resolveFieldBlockProps) {
780
+ const blockName = (0, ts_morph_helper_1.findBlockName)(prop.name, metadata);
781
+ const importPath = (0, ts_morph_helper_1.findBlockImportPath)(blockName, `${generatorOptions.targetDirectory}`, metadata);
782
+ imports.push({ name: blockName, importPath });
783
+ }
784
+ const code = `
785
+ ${outputRelationManyToOneProps
786
+ .map((prop) => `
787
+ @ResolveField(() => ${prop.type}${prop.nullable ? `, { nullable: true }` : ""})
788
+ async ${prop.name}(@Parent() ${instanceNameSingular}: ${metadata.className}): Promise<${prop.type}${prop.nullable ? ` | undefined` : ""}> {
789
+ return ${instanceNameSingular}.${prop.name}${prop.nullable ? `?` : ""}.loadOrFail();
790
+ }
791
+ `)
792
+ .join("\n")}
793
+
794
+ ${outputRelationOneToManyProps
795
+ .map((prop) => `
796
+ @ResolveField(() => [${prop.type}])
797
+ async ${prop.name}(@Parent() ${instanceNameSingular}: ${metadata.className}): Promise<${prop.type}[]> {
798
+ return ${instanceNameSingular}.${prop.name}.loadItems();
799
+ }
800
+ `)
801
+ .join("\n")}
802
+
803
+ ${outputRelationManyToManyProps
804
+ .map((prop) => `
805
+ @ResolveField(() => [${prop.type}])
806
+ async ${prop.name}(@Parent() ${instanceNameSingular}: ${metadata.className}): Promise<${prop.type}[]> {
807
+ return ${instanceNameSingular}.${prop.name}.loadItems();
808
+ }
809
+ `)
810
+ .join("\n")}
811
+
812
+ ${outputRelationOneToOneProps
813
+ .map((prop) => `
814
+ @ResolveField(() => ${prop.type}${prop.nullable ? `, { nullable: true }` : ""})
815
+ async ${prop.name}(@Parent() ${instanceNameSingular}: ${metadata.className}): Promise<${prop.type}${prop.nullable ? ` | undefined` : ""}> {
816
+ return ${instanceNameSingular}.${prop.name}${prop.nullable ? `?` : ""}.loadOrFail();
817
+ }
818
+ `)
819
+ .join("\n")}
820
+
821
+ ${resolveFieldBlockProps
822
+ .map((prop) => `
823
+ @ResolveField(() => RootBlockDataScalar(${(0, ts_morph_helper_1.findBlockName)(prop.name, metadata)}))
824
+ async ${prop.name}(@Parent() ${instanceNameSingular}: ${metadata.className}): Promise<object> {
825
+ return this.blocksTransformer.transformToPlain(${instanceNameSingular}.${prop.name});
826
+ }
827
+ `)
828
+ .join("\n")}
829
+
830
+ `.trim();
831
+ return {
832
+ code,
833
+ imports,
834
+ hasOutputRelations,
835
+ needsBlocksTransformer: resolveFieldBlockProps.length > 0,
836
+ };
837
+ }
838
+ function generateResolver({ generatorOptions, metadata }) {
839
+ const { classNameSingular, fileNameSingular, instanceNameSingular, classNamePlural, fileNamePlural, instanceNamePlural } = (0, build_name_variants_1.buildNameVariants)(metadata);
840
+ const { scopeProp, skipScopeCheck, argsClassName, argsFileName, hasSlugProp, hasSearchArg, hasSortArg, hasFilterArg, hasPositionProp, positionGroupProps, statusProp, hasStatusFilter, dedicatedResolverArgProps, } = buildOptions(metadata, generatorOptions);
841
+ const relationManyToOneProps = metadata.props.filter((prop) => prop.kind === "m:1");
842
+ const relationOneToManyProps = metadata.props.filter((prop) => prop.kind === "1:m");
843
+ const relationManyToManyProps = metadata.props.filter((prop) => prop.kind === "m:n");
844
+ const relationOneToOneProps = metadata.props.filter((prop) => prop.kind === "1:1");
845
+ const outputRelationManyToOneProps = relationManyToOneProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
846
+ const outputRelationOneToManyProps = relationOneToManyProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
847
+ const outputRelationManyToManyProps = relationManyToManyProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
848
+ const outputRelationOneToOneProps = relationOneToOneProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
849
+ const imports = [];
850
+ const injectRepositories = new Array();
851
+ const { code: createInputHandlingCode, injectRepositories: createInputHandlingInjectRepositories } = generateInputHandling({ mode: "create", inputName: "input", assignEntityCode: `const ${instanceNameSingular} = this.repository.create({` }, metadata, generatorOptions);
852
+ injectRepositories.push(...createInputHandlingInjectRepositories);
853
+ const { code: updateInputHandlingCode, injectRepositories: updateInputHandlingInjectRepositories } = generateInputHandling({ mode: "update", inputName: "input", assignEntityCode: `${instanceNameSingular}.assign({` }, metadata, generatorOptions);
854
+ injectRepositories.push(...updateInputHandlingInjectRepositories);
855
+ injectRepositories.push(...dedicatedResolverArgProps.map((prop) => {
856
+ if (!prop.targetMeta)
857
+ throw new Error("targetMeta is not set for relation");
858
+ return prop.targetMeta;
859
+ }));
860
+ const { imports: relationsFieldResolverImports, code: relationsFieldResolverCode, hasOutputRelations, needsBlocksTransformer, } = generateRelationsFieldResolver({
861
+ generatorOptions,
862
+ metadata,
863
+ });
864
+ imports.push(...relationsFieldResolverImports);
865
+ imports.push(generateEntityImport(metadata, generatorOptions.targetDirectory));
866
+ if (scopeProp && scopeProp.targetMeta) {
867
+ imports.push(generateEntityImport(scopeProp.targetMeta, generatorOptions.targetDirectory));
868
+ }
869
+ imports.push(...injectRepositories.map((meta) => generateEntityImport(meta, generatorOptions.targetDirectory)));
870
+ if (statusProp) {
871
+ const enumName = (0, ts_morph_helper_1.findEnumName)(statusProp.name, metadata);
872
+ const importPath = (0, ts_morph_helper_1.findEnumImportPath)(enumName, generatorOptions.targetDirectory, metadata);
873
+ imports.push({
874
+ name: enumName,
875
+ importPath,
876
+ });
877
+ }
878
+ function generateIdArg(name, metadata) {
879
+ if (constants_1.integerTypes.includes(metadata.properties[name].type)) {
880
+ return `@Args("${name}", { type: () => ID }, { transform: (value) => parseInt(value) }) ${name}: number`;
881
+ }
882
+ else {
883
+ return `@Args("${name}", { type: () => ID }) ${name}: string`;
884
+ }
885
+ }
886
+ imports.push({ name: "extractGraphqlFields", importPath: "@comet/cms-api" });
887
+ imports.push({ name: "SortDirection", importPath: "@comet/cms-api" });
888
+ imports.push({ name: "RequiredPermission", importPath: "@comet/cms-api" });
889
+ imports.push({ name: "AffectedEntity", importPath: "@comet/cms-api" });
890
+ imports.push({ name: "validateNotModified", importPath: "@comet/cms-api" });
891
+ imports.push({ name: "RootBlockDataScalar", importPath: "@comet/cms-api" });
892
+ imports.push({ name: "BlocksTransformerService", importPath: "@comet/cms-api" });
893
+ imports.push({ name: "gqlArgsToMikroOrmQuery", importPath: "@comet/cms-api" });
894
+ const resolverOut = `import { InjectRepository } from "@mikro-orm/nestjs";
895
+ import { EntityRepository, EntityManager } from "@mikro-orm/postgresql";
896
+ import { FindOptions, ObjectQuery, Reference } from "@mikro-orm/postgresql";
897
+ import { Args, ID, Info, Mutation, Query, Resolver, ResolveField, Parent } from "@nestjs/graphql";
898
+ import { GraphQLResolveInfo } from "graphql";
899
+
900
+ ${hasPositionProp ? `import { ${classNamePlural}Service } from "./${fileNamePlural}.service";` : ``}
901
+ import { ${classNameSingular}Input, ${classNameSingular}UpdateInput } from "./dto/${fileNameSingular}.input";
902
+ import { Paginated${classNamePlural} } from "./dto/paginated-${fileNamePlural}";
903
+ import { ${argsClassName} } from "./dto/${argsFileName}";
904
+ ${(0, generate_imports_code_1.generateImportsCode)(imports)}
905
+
906
+ @Resolver(() => ${metadata.className})
907
+ @RequiredPermission(${JSON.stringify(generatorOptions.requiredPermission)}${skipScopeCheck ? `, { skipScopeCheck: true }` : ""})
908
+ export class ${classNameSingular}Resolver {
909
+ constructor(
910
+ private readonly entityManager: EntityManager,${hasPositionProp ? `private readonly ${instanceNamePlural}Service: ${classNamePlural}Service,` : ``}
911
+ @InjectRepository(${metadata.className}) private readonly repository: EntityRepository<${metadata.className}>,
912
+ ${[...new Set(injectRepositories.map((meta) => meta.className))]
913
+ .map((type) => `@InjectRepository(${type}) private readonly ${(0, build_name_variants_1.classNameToInstanceName)(type)}Repository: EntityRepository<${type}>,`)
914
+ .join("")}${needsBlocksTransformer ? `private readonly blocksTransformer: BlocksTransformerService,` : ""}
915
+ ) {}
916
+
917
+ @Query(() => ${metadata.className})
918
+ @AffectedEntity(${metadata.className})
919
+ async ${instanceNameSingular}(${generateIdArg("id", metadata)}): Promise<${metadata.className}> {
920
+ const ${instanceNameSingular} = await this.repository.findOneOrFail(id);
921
+ return ${instanceNameSingular};
922
+ }
923
+
924
+ ${hasSlugProp
925
+ ? `
926
+ @Query(() => ${metadata.className}, { nullable: true })
927
+ async ${instanceNameSingular}BySlug(
928
+ @Args("slug") slug: string
929
+ ${scopeProp ? `, @Args("scope", { type: () => ${scopeProp.type} }) scope: ${scopeProp.type}` : ""}
930
+ ): Promise<${metadata.className} | null> {
931
+ const ${instanceNameSingular} = await this.repository.findOne({ slug${scopeProp ? `, scope` : ""}});
932
+
933
+ return ${instanceNameSingular} ?? null;
934
+ }
935
+ `
936
+ : ""}
937
+
938
+ ${generatorOptions.list
939
+ ? `
940
+ @Query(() => Paginated${classNamePlural})
941
+ ${dedicatedResolverArgProps
942
+ .map((dedicatedResolverArgProp) => {
943
+ var _a;
944
+ return `@AffectedEntity(${(_a = dedicatedResolverArgProp.targetMeta) === null || _a === void 0 ? void 0 : _a.className}, { idArg: "${dedicatedResolverArgProp.name}" })`;
945
+ })
946
+ .join("")}
947
+ async ${instanceNameSingular != instanceNamePlural ? instanceNamePlural : `${instanceNamePlural}List`}(
948
+ @Args() {${Object.entries(Object.assign(Object.assign({ scope: !!scopeProp }, dedicatedResolverArgProps.reduce((acc, dedicatedResolverArgProp) => {
949
+ acc[dedicatedResolverArgProp.name] = true;
950
+ return acc;
951
+ }, {})), { status: !!hasStatusFilter, search: !!hasSearchArg, filter: !!hasFilterArg, sort: !!hasSortArg, offset: true, limit: true }))
952
+ .filter(([key, use]) => use)
953
+ .map(([key]) => key)
954
+ .join(", ")}}: ${argsClassName}
955
+ ${hasOutputRelations ? `, @Info() info: GraphQLResolveInfo` : ""}
956
+ ): Promise<Paginated${classNamePlural}> {
957
+ const where${hasSearchArg || hasFilterArg
958
+ ? ` = gqlArgsToMikroOrmQuery({ ${hasSearchArg ? `search, ` : ""}${hasFilterArg ? `filter, ` : ""} }, this.repository);`
959
+ : `: ObjectQuery<${metadata.className}> = {}`}
960
+ ${hasStatusFilter ? `where.status = { $in: status };` : ""}
961
+ ${scopeProp ? `where.scope = scope;` : ""}
962
+ ${dedicatedResolverArgProps
963
+ .map((dedicatedResolverArgProp) => {
964
+ return `where.${dedicatedResolverArgProp.name} = ${dedicatedResolverArgProp.name};`;
965
+ })
966
+ .join("\n")}
967
+
968
+ ${hasOutputRelations
969
+ ? `const fields = extractGraphqlFields(info, { root: "nodes" });
970
+ const populate: string[] = [];`
971
+ : ""}
972
+ ${[...outputRelationManyToOneProps, ...outputRelationOneToManyProps, ...outputRelationManyToManyProps, ...outputRelationOneToOneProps]
973
+ .map((r) => `if (fields.includes("${r.name}")) {
974
+ populate.push("${r.name}");
975
+ }`)
976
+ .join("\n")}
977
+
978
+ ${hasOutputRelations ? `// eslint-disable-next-line @typescript-eslint/no-explicit-any` : ""}
979
+ const options: FindOptions<${metadata.className}${hasOutputRelations ? `, any` : ""}> = { offset, limit${hasOutputRelations ? `, populate` : ""}};
980
+
981
+ ${hasSortArg
982
+ ? `if (sort) {
983
+ options.orderBy = sort.map((sortItem) => {
984
+ return {
985
+ [sortItem.field]: sortItem.direction,
986
+ };
987
+ });
988
+ }`
989
+ : ""}
990
+
991
+ const [entities, totalCount] = await this.repository.findAndCount(where, options);
992
+ return new Paginated${classNamePlural}(entities, totalCount);
993
+ }
994
+ `
995
+ : ""}
996
+
997
+ ${generatorOptions.create
998
+ ? `
999
+
1000
+ @Mutation(() => ${metadata.className})
1001
+ ${dedicatedResolverArgProps
1002
+ .map((dedicatedResolverArgProp) => {
1003
+ var _a;
1004
+ return `@AffectedEntity(${(_a = dedicatedResolverArgProp.targetMeta) === null || _a === void 0 ? void 0 : _a.className}, { idArg: "${dedicatedResolverArgProp.name}" })`;
1005
+ })
1006
+ .join("")}
1007
+ async create${classNameSingular}(
1008
+ ${scopeProp ? `@Args("scope", { type: () => ${scopeProp.type} }) scope: ${scopeProp.type},` : ""}${dedicatedResolverArgProps
1009
+ .map((dedicatedResolverArgProp) => {
1010
+ return `${generateIdArg(dedicatedResolverArgProp.name, metadata)}, `;
1011
+ })
1012
+ .join("")}@Args("input", { type: () => ${classNameSingular}Input }) input: ${classNameSingular}Input
1013
+ ): Promise<${metadata.className}> {
1014
+ ${
1015
+ // use local position-var because typescript does not narrow down input.position, keeping "| undefined" typing resulting in typescript error in create-function
1016
+ hasPositionProp
1017
+ ? `
1018
+ const lastPosition = await this.${instanceNamePlural}Service.getLastPosition(${positionGroupProps.length
1019
+ ? `{ ${positionGroupProps
1020
+ .map((prop) => prop.name === "scope"
1021
+ ? `scope`
1022
+ : dedicatedResolverArgProps.find((dedicatedResolverArgProp) => dedicatedResolverArgProp.name === prop.name) !==
1023
+ undefined
1024
+ ? prop.name
1025
+ : `${prop.name}: input.${prop.name}`)
1026
+ .join(",")} }`
1027
+ : ``});
1028
+ let position = input.position;
1029
+ if (position !== undefined && position < lastPosition + 1) {
1030
+ await this.${instanceNamePlural}Service.incrementPositions(${positionGroupProps.length
1031
+ ? `{ ${positionGroupProps
1032
+ .map((prop) => prop.name === "scope"
1033
+ ? `scope`
1034
+ : dedicatedResolverArgProps.find((dedicatedResolverArgProp) => dedicatedResolverArgProp.name === prop.name) !==
1035
+ undefined
1036
+ ? prop.name
1037
+ : `${prop.name}: input.${prop.name}`)
1038
+ .join(",")} }, `
1039
+ : ``}position);
1040
+ } else {
1041
+ position = lastPosition + 1;
1042
+ }`
1043
+ : ""}
1044
+
1045
+ ${createInputHandlingCode}
1046
+
1047
+ await this.entityManager.flush();
1048
+
1049
+ return ${instanceNameSingular};
1050
+ }
1051
+ `
1052
+ : ""}
1053
+
1054
+ ${generatorOptions.update
1055
+ ? `
1056
+ @Mutation(() => ${metadata.className})
1057
+ @AffectedEntity(${metadata.className})
1058
+ async update${classNameSingular}(
1059
+ ${generateIdArg("id", metadata)},
1060
+ @Args("input", { type: () => ${classNameSingular}UpdateInput }) input: ${classNameSingular}UpdateInput
1061
+ ): Promise<${metadata.className}> {
1062
+ const ${instanceNameSingular} = await this.repository.findOneOrFail(id);
1063
+
1064
+ ${hasPositionProp
1065
+ ? `
1066
+ if (input.position !== undefined) {
1067
+ const lastPosition = await this.${instanceNamePlural}Service.getLastPosition(${positionGroupProps.length
1068
+ ? `{ ${positionGroupProps
1069
+ .map((prop) => `${prop.name}: ${instanceNameSingular}.${prop.name}${[postgresql_1.ReferenceKind.MANY_TO_ONE, postgresql_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)
1070
+ ? `${prop.nullable ? `?` : ``}.id`
1071
+ : ``}`)
1072
+ .join(",")} }`
1073
+ : ``});
1074
+ if (input.position > lastPosition + 1) {
1075
+ input.position = lastPosition + 1;
1076
+ }
1077
+ if (${instanceNameSingular}.position < input.position) {
1078
+ await this.${instanceNamePlural}Service.decrementPositions(${positionGroupProps.length
1079
+ ? `{ ${positionGroupProps
1080
+ .map((prop) => `${prop.name}: ${instanceNameSingular}.${prop.name}${[postgresql_1.ReferenceKind.MANY_TO_ONE, postgresql_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)
1081
+ ? `${prop.nullable ? `?` : ``}.id`
1082
+ : ``}`)
1083
+ .join(",")} },`
1084
+ : ``}${instanceNameSingular}.position, input.position);
1085
+ } else if (${instanceNameSingular}.position > input.position) {
1086
+ await this.${instanceNamePlural}Service.incrementPositions(${positionGroupProps.length
1087
+ ? `{ ${positionGroupProps
1088
+ .map((prop) => `${prop.name}: ${instanceNameSingular}.${prop.name}${[postgresql_1.ReferenceKind.MANY_TO_ONE, postgresql_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)
1089
+ ? `${prop.nullable ? `?` : ``}.id`
1090
+ : ``}`)
1091
+ .join(",")} },`
1092
+ : ``}input.position, ${instanceNameSingular}.position);
1093
+ }
1094
+ }`
1095
+ : ""}
1096
+
1097
+ ${updateInputHandlingCode}
1098
+
1099
+ await this.entityManager.flush();
1100
+
1101
+ return ${instanceNameSingular};
1102
+ }
1103
+ `
1104
+ : ""}
1105
+
1106
+ ${generatorOptions.delete
1107
+ ? `
1108
+ @Mutation(() => Boolean)
1109
+ @AffectedEntity(${metadata.className})
1110
+ async delete${metadata.className}(${generateIdArg("id", metadata)}): Promise<boolean> {
1111
+ const ${instanceNameSingular} = await this.repository.findOneOrFail(id);
1112
+ this.entityManager.remove(${instanceNameSingular});${hasPositionProp
1113
+ ? `await this.${instanceNamePlural}Service.decrementPositions(${positionGroupProps.length
1114
+ ? `{ ${positionGroupProps
1115
+ .map((prop) => `${prop.name}: ${instanceNameSingular}.${prop.name}${[postgresql_1.ReferenceKind.MANY_TO_ONE, postgresql_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)
1116
+ ? `${prop.nullable ? `?` : ``}.id`
1117
+ : ``}`)
1118
+ .join(",")} },`
1119
+ : ``}${instanceNameSingular}.position);`
1120
+ : ""}
1121
+ await this.entityManager.flush();
1122
+ return true;
1123
+ }
1124
+ `
1125
+ : ""}
1126
+
1127
+ ${relationsFieldResolverCode}
1128
+ }
1129
+ `;
1130
+ return resolverOut;
1131
+ }
1132
+ function generateCrud(generatorOptionsParam, metadata) {
1133
+ return __awaiter(this, void 0, void 0, function* () {
1134
+ var _a, _b, _c, _d;
1135
+ const generatorOptions = Object.assign(Object.assign({}, generatorOptionsParam), { create: (_a = generatorOptionsParam.create) !== null && _a !== void 0 ? _a : true, update: (_b = generatorOptionsParam.update) !== null && _b !== void 0 ? _b : true, delete: (_c = generatorOptionsParam.delete) !== null && _c !== void 0 ? _c : true, list: (_d = generatorOptionsParam.list) !== null && _d !== void 0 ? _d : true });
1136
+ const generatedFiles = [];
1137
+ const { fileNameSingular, fileNamePlural, instanceNamePlural } = (0, build_name_variants_1.buildNameVariants)(metadata);
1138
+ const { hasFilterArg, hasSortArg, argsFileName, hasPositionProp } = buildOptions(metadata, generatorOptions);
1139
+ if (!generatorOptions.requiredPermission)
1140
+ generatorOptions.requiredPermission = [instanceNamePlural];
1141
+ function generateCrudResolver() {
1142
+ return __awaiter(this, void 0, void 0, function* () {
1143
+ if (hasFilterArg) {
1144
+ generatedFiles.push({
1145
+ name: `dto/${fileNameSingular}.filter.ts`,
1146
+ content: generateFilterDto({ generatorOptions, metadata }),
1147
+ type: "filter",
1148
+ });
1149
+ }
1150
+ if (hasSortArg) {
1151
+ generatedFiles.push({
1152
+ name: `dto/${fileNameSingular}.sort.ts`,
1153
+ content: generateSortDto({ generatorOptions, metadata }),
1154
+ type: "sort",
1155
+ });
1156
+ }
1157
+ generatedFiles.push({
1158
+ name: `dto/paginated-${fileNamePlural}.ts`,
1159
+ content: generatePaginatedDto({ generatorOptions, metadata }),
1160
+ type: "sort",
1161
+ });
1162
+ generatedFiles.push({
1163
+ name: `dto/${argsFileName}.ts`,
1164
+ content: generateArgsDto({ generatorOptions, metadata }),
1165
+ type: "args",
1166
+ });
1167
+ if (hasPositionProp) {
1168
+ generatedFiles.push({
1169
+ name: `${fileNamePlural}.service.ts`,
1170
+ content: generateService({ generatorOptions, metadata }),
1171
+ type: "service",
1172
+ });
1173
+ }
1174
+ generatedFiles.push({
1175
+ name: `${fileNameSingular}.resolver.ts`,
1176
+ content: generateResolver({ generatorOptions, metadata }),
1177
+ type: "resolver",
1178
+ });
1179
+ metadata.props
1180
+ .filter((prop) => {
1181
+ if (prop.kind === "1:m" && prop.orphanRemoval) {
1182
+ if (!prop.targetMeta)
1183
+ throw new Error(`Target metadata not set`);
1184
+ const hasOwnCrudGenerator = Reflect.getMetadata(`data:crudGeneratorOptions`, prop.targetMeta.class);
1185
+ if (!hasOwnCrudGenerator) {
1186
+ //generate nested resolver only if target entity has no own crud generator
1187
+ return true;
1188
+ }
1189
+ }
1190
+ })
1191
+ .forEach((prop) => {
1192
+ if (!prop.targetMeta)
1193
+ throw new Error(`Target metadata not set`);
1194
+ const { fileNameSingular } = (0, build_name_variants_1.buildNameVariants)(prop.targetMeta);
1195
+ const content = generateNestedEntityResolver({ generatorOptions, metadata: prop.targetMeta });
1196
+ //can be null if no relations exist
1197
+ if (content) {
1198
+ generatedFiles.push({
1199
+ name: `${fileNameSingular}.resolver.ts`,
1200
+ content,
1201
+ type: "resolver",
1202
+ });
1203
+ }
1204
+ });
1205
+ return generatedFiles;
1206
+ });
1207
+ }
1208
+ const crudInput = yield (0, generate_crud_input_1.generateCrudInput)(generatorOptions, metadata);
1209
+ const crudResolver = yield generateCrudResolver();
1210
+ return generatorOptions.create || generatorOptions.update ? [...crudInput, ...crudResolver] : [...crudResolver];
1211
+ });
1212
+ }