@comet/api-generator 8.0.0-canary-20250227115813
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -0
- package/bin/api-generator.js +5 -0
- package/lib/apiGenerator.d.ts +1 -0
- package/lib/apiGenerator.js +7 -0
- package/lib/commands/generate/generate-command.d.ts +2 -0
- package/lib/commands/generate/generate-command.js +45 -0
- package/lib/commands/generate/generateCrud/generate-crud.d.ts +24 -0
- package/lib/commands/generate/generateCrud/generate-crud.js +1212 -0
- package/lib/commands/generate/generateCrudInput/generate-crud-input.d.ts +11 -0
- package/lib/commands/generate/generateCrudInput/generate-crud-input.js +463 -0
- package/lib/commands/generate/generateCrudSingle/generate-crud-single.d.ts +4 -0
- package/lib/commands/generate/generateCrudSingle/generate-crud-single.js +153 -0
- package/lib/commands/generate/utils/build-name-variants.d.ts +10 -0
- package/lib/commands/generate/utils/build-name-variants.js +25 -0
- package/lib/commands/generate/utils/constants.d.ts +1 -0
- package/lib/commands/generate/utils/constants.js +4 -0
- package/lib/commands/generate/utils/generate-imports-code.d.ts +5 -0
- package/lib/commands/generate/utils/generate-imports-code.js +32 -0
- package/lib/commands/generate/utils/test-helper.d.ts +5 -0
- package/lib/commands/generate/utils/test-helper.js +72 -0
- package/lib/commands/generate/utils/ts-morph-helper.d.ts +10 -0
- package/lib/commands/generate/utils/ts-morph-helper.js +191 -0
- package/lib/commands/generate/utils/write-generated-file.d.ts +1 -0
- package/lib/commands/generate/utils/write-generated-file.js +69 -0
- package/lib/commands/generate/utils/write-generated-files.d.ts +8 -0
- package/lib/commands/generate/utils/write-generated-files.js +20 -0
- package/package.json +66 -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
|
+
}
|