nicot 1.1.22 → 1.1.24
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/README.md +2 -0
- package/build.js +167 -0
- package/dist/index.cjs +2210 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.mjs +2184 -0
- package/dist/index.mjs.map +7 -0
- package/index.ts +0 -2
- package/package.json +20 -7
- package/dist/index.js +0 -25
- package/dist/index.js.map +0 -1
- package/dist/src/bases/base-restful-controller.js +0 -53
- package/dist/src/bases/base-restful-controller.js.map +0 -1
- package/dist/src/bases/id-base.js +0 -58
- package/dist/src/bases/id-base.js.map +0 -1
- package/dist/src/bases/index.js +0 -20
- package/dist/src/bases/index.js.map +0 -1
- package/dist/src/bases/page-settings.js +0 -64
- package/dist/src/bases/page-settings.js.map +0 -1
- package/dist/src/bases/time-base.js +0 -53
- package/dist/src/bases/time-base.js.map +0 -1
- package/dist/src/crud-base.js +0 -480
- package/dist/src/crud-base.js.map +0 -1
- package/dist/src/decorators/access.js +0 -24
- package/dist/src/decorators/access.js.map +0 -1
- package/dist/src/decorators/index.js +0 -21
- package/dist/src/decorators/index.js.map +0 -1
- package/dist/src/decorators/pipes.js +0 -26
- package/dist/src/decorators/pipes.js.map +0 -1
- package/dist/src/decorators/property.js +0 -191
- package/dist/src/decorators/property.js.map +0 -1
- package/dist/src/decorators/query.js +0 -67
- package/dist/src/decorators/query.js.map +0 -1
- package/dist/src/dto/cursor-pagination.js +0 -89
- package/dist/src/dto/cursor-pagination.js.map +0 -1
- package/dist/src/dto/import-entry.js +0 -45
- package/dist/src/dto/import-entry.js.map +0 -1
- package/dist/src/dto/index.js +0 -19
- package/dist/src/dto/index.js.map +0 -1
- package/dist/src/restful.js +0 -402
- package/dist/src/restful.js.map +0 -1
- package/dist/src/utility/bigint.js +0 -16
- package/dist/src/utility/bigint.js.map +0 -1
- package/dist/src/utility/cursor-pagination-utils.js +0 -252
- package/dist/src/utility/cursor-pagination-utils.js.map +0 -1
- package/dist/src/utility/filter-relations.js +0 -91
- package/dist/src/utility/filter-relations.js.map +0 -1
- package/dist/src/utility/get-typeorm-relations.js +0 -50
- package/dist/src/utility/get-typeorm-relations.js.map +0 -1
- package/dist/src/utility/index.js +0 -18
- package/dist/src/utility/index.js.map +0 -1
- package/dist/src/utility/metadata.js +0 -21
- package/dist/src/utility/metadata.js.map +0 -1
- package/dist/src/utility/omit-type-exclude.js +0 -14
- package/dist/src/utility/omit-type-exclude.js.map +0 -1
- package/dist/src/utility/query-full-text-column-options.interface.js +0 -3
- package/dist/src/utility/query-full-text-column-options.interface.js.map +0 -1
- package/dist/src/utility/query.js +0 -49
- package/dist/src/utility/query.js.map +0 -1
- package/dist/src/utility/recursive-key-of.js +0 -4
- package/dist/src/utility/recursive-key-of.js.map +0 -1
- package/dist/src/utility/relation-def.js +0 -3
- package/dist/src/utility/relation-def.js.map +0 -1
- package/dist/src/utility/rename-class.js +0 -8
- package/dist/src/utility/rename-class.js.map +0 -1
- package/dist/src/utility/subject-registry.js +0 -19
- package/dist/src/utility/subject-registry.js.map +0 -1
- package/dist/src/utility/type-transformer.js +0 -22
- package/dist/src/utility/type-transformer.js.map +0 -1
- package/dist/src/utility/unshift-order-by.js +0 -18
- package/dist/src/utility/unshift-order-by.js.map +0 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2184 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// index.ts
|
|
13
|
+
export * from "nesties";
|
|
14
|
+
|
|
15
|
+
// src/dto/import-entry.ts
|
|
16
|
+
import { ApiProperty } from "@nestjs/swagger";
|
|
17
|
+
import { Type } from "class-transformer";
|
|
18
|
+
import { ValidateNested } from "class-validator";
|
|
19
|
+
import {
|
|
20
|
+
getClassFromClassOrArray,
|
|
21
|
+
InsertField
|
|
22
|
+
} from "nesties";
|
|
23
|
+
var ImportEntryBaseDto = class {
|
|
24
|
+
};
|
|
25
|
+
__decorateClass([
|
|
26
|
+
ApiProperty({ description: "Import result", type: String })
|
|
27
|
+
], ImportEntryBaseDto.prototype, "result", 2);
|
|
28
|
+
function ImportEntryDto(type) {
|
|
29
|
+
return InsertField(
|
|
30
|
+
ImportEntryBaseDto,
|
|
31
|
+
{
|
|
32
|
+
entry: { type, options: { description: "Import entry" } }
|
|
33
|
+
},
|
|
34
|
+
`${getClassFromClassOrArray(type).name}ImportEntry`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
var ImportDataBaseDto = class {
|
|
38
|
+
};
|
|
39
|
+
__decorateClass([
|
|
40
|
+
ValidateNested()
|
|
41
|
+
], ImportDataBaseDto.prototype, "data", 2);
|
|
42
|
+
function ImportDataDto(type) {
|
|
43
|
+
const dtoClass = InsertField(
|
|
44
|
+
ImportDataBaseDto,
|
|
45
|
+
{
|
|
46
|
+
data: { type: [type], options: { description: "Import data" } }
|
|
47
|
+
},
|
|
48
|
+
`${getClassFromClassOrArray(type).name}ImportData`
|
|
49
|
+
);
|
|
50
|
+
Type(() => type)(dtoClass.prototype, "data");
|
|
51
|
+
return dtoClass;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/dto/cursor-pagination.ts
|
|
55
|
+
import {
|
|
56
|
+
IsInt as IsInt2,
|
|
57
|
+
IsNotEmpty,
|
|
58
|
+
IsOptional as IsOptional3,
|
|
59
|
+
IsPositive,
|
|
60
|
+
IsString as IsString2
|
|
61
|
+
} from "class-validator";
|
|
62
|
+
import { ApiProperty as ApiProperty3 } from "@nestjs/swagger";
|
|
63
|
+
import {
|
|
64
|
+
BlankReturnMessageDto,
|
|
65
|
+
getClassFromClassOrArray as getClassFromClassOrArray3,
|
|
66
|
+
InsertField as InsertField2
|
|
67
|
+
} from "nesties";
|
|
68
|
+
|
|
69
|
+
// src/decorators/access.ts
|
|
70
|
+
import { Expose } from "class-transformer";
|
|
71
|
+
import { IsOptional } from "class-validator";
|
|
72
|
+
|
|
73
|
+
// src/utility/metadata.ts
|
|
74
|
+
import { MetadataSetter, Reflector } from "typed-reflector";
|
|
75
|
+
var Metadata = new MetadataSetter();
|
|
76
|
+
var reflector = new Reflector();
|
|
77
|
+
function getSpecificFields(obj, type, filter = () => true) {
|
|
78
|
+
return reflector.getArray(`${type}Fields`, obj).filter((field) => {
|
|
79
|
+
const value = reflector.get(type, obj, field);
|
|
80
|
+
if (value == null) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return filter(value, obj);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function getNotInResultFields(obj, keepEntityVersioningDates = false) {
|
|
87
|
+
return getSpecificFields(
|
|
88
|
+
obj,
|
|
89
|
+
"notInResult",
|
|
90
|
+
(meta) => !keepEntityVersioningDates || !meta.entityVersioningDate
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/decorators/access.ts
|
|
95
|
+
import { MergePropertyDecorators } from "nesties";
|
|
96
|
+
var NotWritable = () => MergePropertyDecorators([
|
|
97
|
+
Expose({ groups: ["r"] }),
|
|
98
|
+
IsOptional(),
|
|
99
|
+
Metadata.set("notWritable", true, "notWritableFields"),
|
|
100
|
+
Metadata.set("notChangeable", true, "notChangeableFields")
|
|
101
|
+
]);
|
|
102
|
+
var NotChangeable = () => MergePropertyDecorators([
|
|
103
|
+
Expose({ groups: ["r", "c"] }),
|
|
104
|
+
Metadata.set("notChangeable", true, "notChangeableFields")
|
|
105
|
+
]);
|
|
106
|
+
var NotQueryable = () => Metadata.set("notQueryable", true, "notQueryableFields");
|
|
107
|
+
var NotInResult = (options = {}) => Metadata.set("notInResult", options, "notInResultFields");
|
|
108
|
+
|
|
109
|
+
// src/decorators/property.ts
|
|
110
|
+
import { ApiProperty as ApiProperty2 } from "@nestjs/swagger";
|
|
111
|
+
import { MergePropertyDecorators as MergePropertyDecorators2 } from "nesties";
|
|
112
|
+
import { Column, Index } from "typeorm";
|
|
113
|
+
import {
|
|
114
|
+
IsDate,
|
|
115
|
+
IsEnum,
|
|
116
|
+
IsInt,
|
|
117
|
+
IsNumber,
|
|
118
|
+
IsOptional as IsOptional2,
|
|
119
|
+
IsString,
|
|
120
|
+
MaxLength,
|
|
121
|
+
Min,
|
|
122
|
+
ValidateNested as ValidateNested2
|
|
123
|
+
} from "class-validator";
|
|
124
|
+
import { Exclude, Transform, Type as Type2 } from "class-transformer";
|
|
125
|
+
|
|
126
|
+
// src/utility/bigint.ts
|
|
127
|
+
var BigintTransformer = class {
|
|
128
|
+
from(dbValue) {
|
|
129
|
+
if (dbValue == null) {
|
|
130
|
+
return dbValue;
|
|
131
|
+
}
|
|
132
|
+
return parseInt(dbValue);
|
|
133
|
+
}
|
|
134
|
+
to(entValue) {
|
|
135
|
+
return entValue;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/decorators/property.ts
|
|
140
|
+
import { getClassFromClassOrArray as getClassFromClassOrArray2 } from "nesties";
|
|
141
|
+
|
|
142
|
+
// src/utility/type-transformer.ts
|
|
143
|
+
var TypeTransformer = class {
|
|
144
|
+
constructor(definition) {
|
|
145
|
+
this.definition = definition;
|
|
146
|
+
}
|
|
147
|
+
from(dbValue) {
|
|
148
|
+
if (!dbValue) {
|
|
149
|
+
return dbValue;
|
|
150
|
+
}
|
|
151
|
+
if (Array.isArray(this.definition)) {
|
|
152
|
+
return dbValue.map(
|
|
153
|
+
(value) => Object.assign(new this.definition[0](), value)
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return Object.assign(new this.definition(), dbValue);
|
|
157
|
+
}
|
|
158
|
+
to(entValue) {
|
|
159
|
+
return entValue;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/decorators/property.ts
|
|
164
|
+
function swaggerDecorator(options, injected = {}) {
|
|
165
|
+
return ApiProperty2({
|
|
166
|
+
default: options.default,
|
|
167
|
+
required: !!(options.required && options.default == null),
|
|
168
|
+
example: options.default,
|
|
169
|
+
description: options.description,
|
|
170
|
+
...injected,
|
|
171
|
+
...options.propertyExtras || {}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function validatorDecorator(options) {
|
|
175
|
+
const decs = [];
|
|
176
|
+
if (!options.required) {
|
|
177
|
+
decs.push(IsOptional2());
|
|
178
|
+
}
|
|
179
|
+
return MergePropertyDecorators2(decs);
|
|
180
|
+
}
|
|
181
|
+
function columnDecoratorOptions(options) {
|
|
182
|
+
return {
|
|
183
|
+
default: options.default,
|
|
184
|
+
nullable: !options.required && options.default == null,
|
|
185
|
+
comment: options.description,
|
|
186
|
+
...options.columnExtras
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
var StringColumn = (length, options = {}) => {
|
|
190
|
+
return MergePropertyDecorators2([
|
|
191
|
+
Column("varchar", { length, ...columnDecoratorOptions(options) }),
|
|
192
|
+
IsString(),
|
|
193
|
+
MaxLength(length),
|
|
194
|
+
validatorDecorator(options),
|
|
195
|
+
swaggerDecorator(options, { type: String, maxLength: length })
|
|
196
|
+
]);
|
|
197
|
+
};
|
|
198
|
+
var IntColumn = (type, options = {}) => {
|
|
199
|
+
const decs = [
|
|
200
|
+
Column(type, {
|
|
201
|
+
default: options.default,
|
|
202
|
+
unsigned: options.unsigned,
|
|
203
|
+
...type === "bigint" ? { transformer: new BigintTransformer() } : {},
|
|
204
|
+
...columnDecoratorOptions(options)
|
|
205
|
+
}),
|
|
206
|
+
IsInt(),
|
|
207
|
+
validatorDecorator(options),
|
|
208
|
+
swaggerDecorator(options, {
|
|
209
|
+
type: Number,
|
|
210
|
+
minimum: options.unsigned ? 0 : void 0
|
|
211
|
+
})
|
|
212
|
+
];
|
|
213
|
+
if (options.unsigned) {
|
|
214
|
+
decs.push(Min(0));
|
|
215
|
+
}
|
|
216
|
+
return MergePropertyDecorators2(decs);
|
|
217
|
+
};
|
|
218
|
+
var FloatColumn = (type, options = {}) => {
|
|
219
|
+
const decs = [
|
|
220
|
+
Column(type, {
|
|
221
|
+
default: options.default,
|
|
222
|
+
unsigned: options.unsigned,
|
|
223
|
+
...columnDecoratorOptions(options)
|
|
224
|
+
}),
|
|
225
|
+
IsNumber(),
|
|
226
|
+
validatorDecorator(options),
|
|
227
|
+
swaggerDecorator(options, {
|
|
228
|
+
type: Number,
|
|
229
|
+
minimum: options.unsigned ? 0 : void 0
|
|
230
|
+
})
|
|
231
|
+
];
|
|
232
|
+
if (options.unsigned) {
|
|
233
|
+
decs.push(Min(0));
|
|
234
|
+
}
|
|
235
|
+
return MergePropertyDecorators2(decs);
|
|
236
|
+
};
|
|
237
|
+
var DateColumn = (options = {}) => {
|
|
238
|
+
return MergePropertyDecorators2([
|
|
239
|
+
Column("timestamp", columnDecoratorOptions(options)),
|
|
240
|
+
IsDate(),
|
|
241
|
+
Transform(
|
|
242
|
+
(v) => {
|
|
243
|
+
const value = v.value;
|
|
244
|
+
if (value == null || value instanceof Date) return value;
|
|
245
|
+
const timestampToDate = (t, isSeconds) => new Date(isSeconds ? t * 1e3 : t);
|
|
246
|
+
if (typeof value === "number") {
|
|
247
|
+
const isSeconds = !Number.isInteger(value) || value < 1e12;
|
|
248
|
+
return timestampToDate(value, isSeconds);
|
|
249
|
+
}
|
|
250
|
+
if (typeof value === "string" && /^\d+(\.\d+)?$/.test(value)) {
|
|
251
|
+
const isSeconds = value.includes(".") || parseFloat(value) < 1e12;
|
|
252
|
+
return timestampToDate(parseFloat(value), isSeconds);
|
|
253
|
+
}
|
|
254
|
+
return new Date(value);
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
toClassOnly: true
|
|
258
|
+
}
|
|
259
|
+
),
|
|
260
|
+
validatorDecorator(options),
|
|
261
|
+
swaggerDecorator(options, { type: Date })
|
|
262
|
+
]);
|
|
263
|
+
};
|
|
264
|
+
var EnumColumn = (targetEnum, options = {}) => {
|
|
265
|
+
return MergePropertyDecorators2([
|
|
266
|
+
Index(),
|
|
267
|
+
Column("enum", {
|
|
268
|
+
enum: targetEnum,
|
|
269
|
+
...columnDecoratorOptions(options)
|
|
270
|
+
}),
|
|
271
|
+
IsEnum(targetEnum),
|
|
272
|
+
validatorDecorator(options),
|
|
273
|
+
swaggerDecorator(options, { enum: targetEnum })
|
|
274
|
+
]);
|
|
275
|
+
};
|
|
276
|
+
var BoolColumn = (options = {}) => MergePropertyDecorators2([
|
|
277
|
+
Index(),
|
|
278
|
+
Transform((v) => {
|
|
279
|
+
const trueValues = ["true", "1", "yes", "on", true, 1];
|
|
280
|
+
const falseValues = ["false", "0", "no", "off", false, 0];
|
|
281
|
+
if (trueValues.indexOf(v.value) !== -1) return true;
|
|
282
|
+
if (falseValues.indexOf(v.value) !== -1) return false;
|
|
283
|
+
return void 0;
|
|
284
|
+
}),
|
|
285
|
+
Column("boolean", columnDecoratorOptions(options)),
|
|
286
|
+
validatorDecorator(options),
|
|
287
|
+
swaggerDecorator(options, { type: Boolean })
|
|
288
|
+
]);
|
|
289
|
+
var JsonColumn = (definition, options = {}) => {
|
|
290
|
+
const cl = getClassFromClassOrArray2(definition);
|
|
291
|
+
return MergePropertyDecorators2([
|
|
292
|
+
NotQueryable(),
|
|
293
|
+
Type2(() => cl),
|
|
294
|
+
ValidateNested2(),
|
|
295
|
+
Column("jsonb", {
|
|
296
|
+
...columnDecoratorOptions(options),
|
|
297
|
+
transformer: new TypeTransformer(definition)
|
|
298
|
+
}),
|
|
299
|
+
validatorDecorator(options),
|
|
300
|
+
swaggerDecorator(options, { type: definition })
|
|
301
|
+
]);
|
|
302
|
+
};
|
|
303
|
+
var NotColumn = (options = {}, specials = {}) => MergePropertyDecorators2([
|
|
304
|
+
Exclude(),
|
|
305
|
+
swaggerDecorator({
|
|
306
|
+
required: false,
|
|
307
|
+
...options
|
|
308
|
+
}),
|
|
309
|
+
Metadata.set("notColumn", specials, "notColumnFields")
|
|
310
|
+
]);
|
|
311
|
+
var QueryColumn = (options = {}) => MergePropertyDecorators2([
|
|
312
|
+
NotWritable(),
|
|
313
|
+
NotInResult(),
|
|
314
|
+
swaggerDecorator({
|
|
315
|
+
required: false,
|
|
316
|
+
...options
|
|
317
|
+
})
|
|
318
|
+
]);
|
|
319
|
+
var RelationComputed = (type) => (obj, propertyKey) => {
|
|
320
|
+
const fun = () => {
|
|
321
|
+
const designType = Reflect.getMetadata("design:type", obj, propertyKey);
|
|
322
|
+
const entityClass = type ? type() : designType;
|
|
323
|
+
return {
|
|
324
|
+
entityClass,
|
|
325
|
+
isArray: designType === Array
|
|
326
|
+
};
|
|
327
|
+
};
|
|
328
|
+
const dec = Metadata.set("relationComputed", fun, "relationComputedFields");
|
|
329
|
+
return dec(obj, propertyKey);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// src/decorators/pipes.ts
|
|
333
|
+
import { ValidationPipe } from "@nestjs/common";
|
|
334
|
+
var CreatePipe = () => new ValidationPipe({
|
|
335
|
+
transform: true,
|
|
336
|
+
transformOptions: { groups: ["c"], enableImplicitConversion: true }
|
|
337
|
+
});
|
|
338
|
+
var GetPipe = () => new ValidationPipe({
|
|
339
|
+
transform: true,
|
|
340
|
+
transformOptions: { groups: ["r"], enableImplicitConversion: true },
|
|
341
|
+
skipMissingProperties: true,
|
|
342
|
+
skipNullProperties: true,
|
|
343
|
+
skipUndefinedProperties: true
|
|
344
|
+
});
|
|
345
|
+
var UpdatePipe = () => new ValidationPipe({
|
|
346
|
+
transform: true,
|
|
347
|
+
transformOptions: { groups: ["u"], enableImplicitConversion: true },
|
|
348
|
+
skipMissingProperties: true,
|
|
349
|
+
skipNullProperties: true,
|
|
350
|
+
skipUndefinedProperties: true
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// src/utility/query.ts
|
|
354
|
+
function createQueryCondition(cond) {
|
|
355
|
+
return (obj, qb, entityName, ...fields) => {
|
|
356
|
+
for (const field of fields) {
|
|
357
|
+
if (obj[field] == null) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const ret = cond(obj, qb, entityName, field);
|
|
361
|
+
if (typeof ret === "string") {
|
|
362
|
+
qb.andWhere(ret);
|
|
363
|
+
} else if (typeof ret === "object" && typeof ret["query"] === "string") {
|
|
364
|
+
const _ret = ret;
|
|
365
|
+
qb.andWhere(_ret.query, _ret.params);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return qb;
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
var applyQueryProperty = createQueryCondition(
|
|
372
|
+
(obj, qb, entityName, field) => qb.andWhere(`${entityName}.${field} = :${field}`, { [field]: obj[field] })
|
|
373
|
+
);
|
|
374
|
+
var applyQueryPropertyLike = createQueryCondition(
|
|
375
|
+
(obj, qb, entityName, field) => qb.andWhere(`${entityName}.${field} like (:${field} || '%')`, {
|
|
376
|
+
[field]: obj[field]
|
|
377
|
+
})
|
|
378
|
+
);
|
|
379
|
+
var applyQueryPropertySearch = createQueryCondition(
|
|
380
|
+
(obj, qb, entityName, field) => qb.andWhere(`${entityName}.${field} like ('%' || :${field} || '%')`, {
|
|
381
|
+
[field]: obj[field]
|
|
382
|
+
})
|
|
383
|
+
);
|
|
384
|
+
var applyQueryPropertyZeroNullable = createQueryCondition(
|
|
385
|
+
(obj, qb, entityName, field) => {
|
|
386
|
+
if ([0, "0"].indexOf(obj[field]) !== -1) {
|
|
387
|
+
qb.andWhere(`${entityName}.${field} IS NULL`);
|
|
388
|
+
} else {
|
|
389
|
+
qb.andWhere(`${entityName}.${field} = :${field}`, {
|
|
390
|
+
[field]: obj[field]
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
var applyQueryMatchBoolean = createQueryCondition(
|
|
396
|
+
(obj, qb, entityName, field) => {
|
|
397
|
+
const value = obj[field];
|
|
398
|
+
if (value === true || value === "true" || value === 1 || value === "1") {
|
|
399
|
+
qb.andWhere(`${entityName}.${field} = TRUE`);
|
|
400
|
+
}
|
|
401
|
+
if (value === false || value === "false" || value === 0 || value === "0") {
|
|
402
|
+
qb.andWhere(`${entityName}.${field} = FALSE`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
// src/decorators/query.ts
|
|
408
|
+
import { MergePropertyDecorators as MergePropertyDecorators3 } from "nesties";
|
|
409
|
+
|
|
410
|
+
// src/utility/unshift-order-by.ts
|
|
411
|
+
var unshiftOrderBy = (qb, sort, order, nulls) => {
|
|
412
|
+
const currentOrderBys = Object.entries(qb.expressionMap.allOrderBys);
|
|
413
|
+
qb.orderBy(sort, order, nulls);
|
|
414
|
+
currentOrderBys.forEach(([key, value]) => {
|
|
415
|
+
if (typeof value === "string") {
|
|
416
|
+
qb.addOrderBy(key, value);
|
|
417
|
+
} else {
|
|
418
|
+
qb.addOrderBy(key, value.order, value.nulls);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
return qb;
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// src/utility/subject-registry.ts
|
|
425
|
+
var subjectRegistry = /* @__PURE__ */ new WeakMap();
|
|
426
|
+
var addSubject = (qb, select, alias) => {
|
|
427
|
+
let subjects = subjectRegistry.get(qb);
|
|
428
|
+
if (!subjects) {
|
|
429
|
+
subjects = {};
|
|
430
|
+
subjectRegistry.set(qb, subjects);
|
|
431
|
+
}
|
|
432
|
+
subjects[alias] = select;
|
|
433
|
+
return qb.addSelect(select, alias);
|
|
434
|
+
};
|
|
435
|
+
var getSubject = (qb, alias) => {
|
|
436
|
+
return subjectRegistry.get(qb)?.[alias];
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// src/decorators/query.ts
|
|
440
|
+
var QueryCondition = (cond) => Metadata.set(
|
|
441
|
+
"queryCondition",
|
|
442
|
+
cond,
|
|
443
|
+
"queryConditionFields"
|
|
444
|
+
);
|
|
445
|
+
var QueryEqual = () => QueryCondition(applyQueryProperty);
|
|
446
|
+
var QueryLike = () => QueryCondition(applyQueryPropertyLike);
|
|
447
|
+
var QuerySearch = () => QueryCondition(applyQueryPropertySearch);
|
|
448
|
+
var QueryEqualZeroNullable = () => QueryCondition(applyQueryPropertyZeroNullable);
|
|
449
|
+
var QueryMatchBoolean = () => QueryCondition(applyQueryMatchBoolean);
|
|
450
|
+
var QueryOperator = (operator, field) => QueryCondition((obj, qb, entityName, key) => {
|
|
451
|
+
if (obj[key] == null) return;
|
|
452
|
+
const fieldName = field || key;
|
|
453
|
+
const typeormField = `_query_operator_${entityName}_${fieldName}_${key}`;
|
|
454
|
+
qb.andWhere(`${entityName}.${fieldName} ${operator} :${typeormField}`, {
|
|
455
|
+
[typeormField]: obj[key]
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
var createQueryOperator = (operator) => (field) => QueryOperator(operator, field);
|
|
459
|
+
var QueryGreater = createQueryOperator(">");
|
|
460
|
+
var QueryGreaterEqual = createQueryOperator(">=");
|
|
461
|
+
var QueryLess = createQueryOperator("<");
|
|
462
|
+
var QueryLessEqual = createQueryOperator("<=");
|
|
463
|
+
var QueryNotEqual = createQueryOperator("!=");
|
|
464
|
+
var QueryFullText = (options = {}) => {
|
|
465
|
+
const configurationName = options.parser ? `nicot_parser_${options.parser}` : options.configuration || "english";
|
|
466
|
+
const tsQueryFunction = options.tsQueryFunction || "websearch_to_tsquery";
|
|
467
|
+
return MergePropertyDecorators3([
|
|
468
|
+
QueryCondition((obj, qb, entityName, key) => {
|
|
469
|
+
if (obj[key] == null) return;
|
|
470
|
+
const fieldName = key;
|
|
471
|
+
const typeormField = key;
|
|
472
|
+
const tsVectorStatement = `to_tsvector('${configurationName}', "${entityName}"."${fieldName}")`;
|
|
473
|
+
const tsQueryStatement = `${tsQueryFunction}('${configurationName}', :${typeormField})`;
|
|
474
|
+
qb.andWhere(`${tsVectorStatement} @@ ${tsQueryStatement}`, {
|
|
475
|
+
[typeormField]: obj[key]
|
|
476
|
+
});
|
|
477
|
+
if (options.orderBySimilarity) {
|
|
478
|
+
const rankVirtualField = `_fulltext_rank_${key}`;
|
|
479
|
+
addSubject(
|
|
480
|
+
qb,
|
|
481
|
+
`ts_rank(${tsVectorStatement}, ${tsQueryStatement})`,
|
|
482
|
+
rankVirtualField
|
|
483
|
+
);
|
|
484
|
+
unshiftOrderBy(qb, `"${rankVirtualField}"`, "DESC");
|
|
485
|
+
}
|
|
486
|
+
}),
|
|
487
|
+
Metadata.set(
|
|
488
|
+
"queryFullTextColumn",
|
|
489
|
+
{
|
|
490
|
+
...options,
|
|
491
|
+
configuration: configurationName
|
|
492
|
+
},
|
|
493
|
+
"queryFullTextColumnFields"
|
|
494
|
+
)
|
|
495
|
+
]);
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// src/dto/cursor-pagination.ts
|
|
499
|
+
var CursorPaginationDto = class {
|
|
500
|
+
};
|
|
501
|
+
__decorateClass([
|
|
502
|
+
NotWritable(),
|
|
503
|
+
IsNotEmpty(),
|
|
504
|
+
IsString2(),
|
|
505
|
+
IsOptional3(),
|
|
506
|
+
ApiProperty3({
|
|
507
|
+
description: "Pagination Cursor",
|
|
508
|
+
required: false,
|
|
509
|
+
type: String
|
|
510
|
+
}),
|
|
511
|
+
NotInResult()
|
|
512
|
+
], CursorPaginationDto.prototype, "paginationCursor", 2);
|
|
513
|
+
__decorateClass([
|
|
514
|
+
NotWritable(),
|
|
515
|
+
IsPositive(),
|
|
516
|
+
IsInt2(),
|
|
517
|
+
ApiProperty3({
|
|
518
|
+
description: "Records per page.",
|
|
519
|
+
required: false,
|
|
520
|
+
type: Number,
|
|
521
|
+
minimum: 1
|
|
522
|
+
}),
|
|
523
|
+
NotInResult()
|
|
524
|
+
], CursorPaginationDto.prototype, "recordsPerPage", 2);
|
|
525
|
+
var BlankCursorPaginationReturnMessageDto = class extends BlankReturnMessageDto {
|
|
526
|
+
constructor(statusCode, message, cursorResponse) {
|
|
527
|
+
super(statusCode, message);
|
|
528
|
+
this.nextCursor = cursorResponse.nextCursor;
|
|
529
|
+
this.previousCursor = cursorResponse.previousCursor;
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
__decorateClass([
|
|
533
|
+
ApiProperty3({
|
|
534
|
+
description: "Next Cursor",
|
|
535
|
+
required: false,
|
|
536
|
+
type: String
|
|
537
|
+
})
|
|
538
|
+
], BlankCursorPaginationReturnMessageDto.prototype, "nextCursor", 2);
|
|
539
|
+
__decorateClass([
|
|
540
|
+
ApiProperty3({
|
|
541
|
+
description: "Previous Cursor",
|
|
542
|
+
required: false,
|
|
543
|
+
type: String
|
|
544
|
+
})
|
|
545
|
+
], BlankCursorPaginationReturnMessageDto.prototype, "previousCursor", 2);
|
|
546
|
+
var GenericCursorPaginationReturnMessageDto = class extends BlankCursorPaginationReturnMessageDto {
|
|
547
|
+
constructor(statusCode, message, data, cursorResponse) {
|
|
548
|
+
super(statusCode, message, cursorResponse);
|
|
549
|
+
this.data = data;
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
function CursorPaginationReturnMessageDto(type) {
|
|
553
|
+
return InsertField2(
|
|
554
|
+
GenericCursorPaginationReturnMessageDto,
|
|
555
|
+
{
|
|
556
|
+
data: {
|
|
557
|
+
type: [type],
|
|
558
|
+
options: {
|
|
559
|
+
required: false,
|
|
560
|
+
description: "Return data."
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
`${getClassFromClassOrArray3(type).name}CursorPaginationReturnMessageDto`
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/crud-base.ts
|
|
569
|
+
import {
|
|
570
|
+
In
|
|
571
|
+
} from "typeorm";
|
|
572
|
+
|
|
573
|
+
// src/bases/time-base.ts
|
|
574
|
+
import {
|
|
575
|
+
CreateDateColumn,
|
|
576
|
+
DeleteDateColumn,
|
|
577
|
+
UpdateDateColumn
|
|
578
|
+
} from "typeorm";
|
|
579
|
+
|
|
580
|
+
// src/bases/page-settings.ts
|
|
581
|
+
import { IsInt as IsInt3, IsPositive as IsPositive2 } from "class-validator";
|
|
582
|
+
import { ApiProperty as ApiProperty4 } from "@nestjs/swagger";
|
|
583
|
+
var PageSettingsDto = class {
|
|
584
|
+
getActualPageSettings() {
|
|
585
|
+
return {
|
|
586
|
+
pageCount: this.getPageCount(),
|
|
587
|
+
recordsPerPage: this.getRecordsPerPage()
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
getPageCount() {
|
|
591
|
+
return parseInt(this.pageCount) || 1;
|
|
592
|
+
}
|
|
593
|
+
getRecordsPerPage() {
|
|
594
|
+
return parseInt(this.recordsPerPage) || 25;
|
|
595
|
+
}
|
|
596
|
+
getStartingFrom() {
|
|
597
|
+
return (this.getPageCount() - 1) * this.getRecordsPerPage();
|
|
598
|
+
}
|
|
599
|
+
applyPaginationQuery(qb) {
|
|
600
|
+
qb.take(this.getRecordsPerPage()).skip(this.getStartingFrom());
|
|
601
|
+
}
|
|
602
|
+
applyQuery(qb, entityName) {
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
__decorateClass([
|
|
606
|
+
NotWritable(),
|
|
607
|
+
IsPositive2(),
|
|
608
|
+
IsInt3(),
|
|
609
|
+
ApiProperty4({
|
|
610
|
+
description: "The nth page, starting with 1.",
|
|
611
|
+
required: false,
|
|
612
|
+
type: Number,
|
|
613
|
+
minimum: 1
|
|
614
|
+
}),
|
|
615
|
+
NotInResult()
|
|
616
|
+
], PageSettingsDto.prototype, "pageCount", 2);
|
|
617
|
+
__decorateClass([
|
|
618
|
+
NotWritable(),
|
|
619
|
+
IsPositive2(),
|
|
620
|
+
IsInt3(),
|
|
621
|
+
ApiProperty4({
|
|
622
|
+
description: "Records per page.",
|
|
623
|
+
required: false,
|
|
624
|
+
type: Number,
|
|
625
|
+
minimum: 1
|
|
626
|
+
}),
|
|
627
|
+
NotInResult()
|
|
628
|
+
], PageSettingsDto.prototype, "recordsPerPage", 2);
|
|
629
|
+
|
|
630
|
+
// src/bases/time-base.ts
|
|
631
|
+
var TimeBase = class extends PageSettingsDto {
|
|
632
|
+
isValidInCreate() {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
636
|
+
async beforeCreate() {
|
|
637
|
+
}
|
|
638
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
639
|
+
async afterCreate() {
|
|
640
|
+
}
|
|
641
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
642
|
+
async beforeGet() {
|
|
643
|
+
}
|
|
644
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
645
|
+
async afterGet() {
|
|
646
|
+
}
|
|
647
|
+
isValidInUpdate() {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
651
|
+
async beforeUpdate() {
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
__decorateClass([
|
|
655
|
+
CreateDateColumn({ select: false }),
|
|
656
|
+
NotColumn(),
|
|
657
|
+
NotInResult({ entityVersioningDate: true })
|
|
658
|
+
], TimeBase.prototype, "createTime", 2);
|
|
659
|
+
__decorateClass([
|
|
660
|
+
UpdateDateColumn({ select: false }),
|
|
661
|
+
NotColumn(),
|
|
662
|
+
NotInResult({ entityVersioningDate: true })
|
|
663
|
+
], TimeBase.prototype, "updateTime", 2);
|
|
664
|
+
__decorateClass([
|
|
665
|
+
DeleteDateColumn({ select: false }),
|
|
666
|
+
NotColumn(),
|
|
667
|
+
NotInResult({ entityVersioningDate: true })
|
|
668
|
+
], TimeBase.prototype, "deleteTime", 2);
|
|
669
|
+
|
|
670
|
+
// src/bases/id-base.ts
|
|
671
|
+
import { Generated } from "typeorm";
|
|
672
|
+
import { IsNotEmpty as IsNotEmpty2, IsString as IsString3 } from "class-validator";
|
|
673
|
+
import { MergePropertyDecorators as MergePropertyDecorators4 } from "nesties";
|
|
674
|
+
function IdBase(idOptions = {}) {
|
|
675
|
+
const cl = class IdBase extends TimeBase {
|
|
676
|
+
applyQuery(qb, entityName) {
|
|
677
|
+
super.applyQuery(qb, entityName);
|
|
678
|
+
if (!idOptions.noOrderById) {
|
|
679
|
+
qb.orderBy(`${entityName}.id`, "DESC");
|
|
680
|
+
}
|
|
681
|
+
applyQueryProperty(this, qb, entityName, "id");
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
const dec = MergePropertyDecorators4([
|
|
685
|
+
NotWritable(),
|
|
686
|
+
IntColumn("bigint", {
|
|
687
|
+
unsigned: true,
|
|
688
|
+
description: idOptions.description,
|
|
689
|
+
columnExtras: { nullable: false, primary: true }
|
|
690
|
+
}),
|
|
691
|
+
Reflect.metadata("design:type", Number),
|
|
692
|
+
Generated("increment")
|
|
693
|
+
]);
|
|
694
|
+
dec(cl.prototype, "id");
|
|
695
|
+
return cl;
|
|
696
|
+
}
|
|
697
|
+
function StringIdBase(idOptions) {
|
|
698
|
+
const cl = class StringIdBase extends TimeBase {
|
|
699
|
+
applyQuery(qb, entityName) {
|
|
700
|
+
super.applyQuery(qb, entityName);
|
|
701
|
+
console.log("idbase order by");
|
|
702
|
+
qb.orderBy(`${entityName}.id`, "ASC");
|
|
703
|
+
applyQueryProperty(this, qb, entityName, "id");
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
const decs = [
|
|
707
|
+
StringColumn(idOptions.length || (idOptions.uuid ? 36 : 255), {
|
|
708
|
+
required: !idOptions.uuid,
|
|
709
|
+
description: idOptions.description,
|
|
710
|
+
columnExtras: { primary: true, nullable: false }
|
|
711
|
+
}),
|
|
712
|
+
Reflect.metadata("design:type", String),
|
|
713
|
+
...idOptions.uuid ? [Generated("uuid"), NotWritable()] : [IsString3(), IsNotEmpty2(), NotChangeable()]
|
|
714
|
+
];
|
|
715
|
+
const dec = MergePropertyDecorators4(decs);
|
|
716
|
+
dec(cl.prototype, "id");
|
|
717
|
+
return cl;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/crud-base.ts
|
|
721
|
+
import { ConsoleLogger } from "@nestjs/common";
|
|
722
|
+
import { camelCase } from "typeorm/util/StringUtils";
|
|
723
|
+
import _3, { omit } from "lodash";
|
|
724
|
+
import {
|
|
725
|
+
BlankReturnMessageDto as BlankReturnMessageDto2,
|
|
726
|
+
PaginatedReturnMessageDto,
|
|
727
|
+
ReturnMessageDto
|
|
728
|
+
} from "nesties";
|
|
729
|
+
|
|
730
|
+
// src/utility/get-typeorm-relations.ts
|
|
731
|
+
import { getMetadataArgsStorage } from "typeorm";
|
|
732
|
+
import _ from "lodash";
|
|
733
|
+
function getTypeormRelations(cl) {
|
|
734
|
+
const relations = getMetadataArgsStorage().relations.filter(
|
|
735
|
+
(r) => r.target === cl
|
|
736
|
+
);
|
|
737
|
+
const typeormRelations = relations.map((relation) => {
|
|
738
|
+
const isArray = relation.relationType.endsWith("-many");
|
|
739
|
+
const relationClassFactory = relation.type;
|
|
740
|
+
let propertyClass;
|
|
741
|
+
if (typeof relationClassFactory === "function") {
|
|
742
|
+
const relationClass = relationClassFactory();
|
|
743
|
+
if (typeof relationClass === "function") {
|
|
744
|
+
propertyClass = relationClass;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (!propertyClass) {
|
|
748
|
+
propertyClass = Reflect.getMetadata(
|
|
749
|
+
"design:type",
|
|
750
|
+
cl.prototype,
|
|
751
|
+
relation.propertyName
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
isArray,
|
|
756
|
+
propertyClass,
|
|
757
|
+
propertyName: relation.propertyName,
|
|
758
|
+
computed: false
|
|
759
|
+
};
|
|
760
|
+
});
|
|
761
|
+
const computedRelations = getSpecificFields(cl, "relationComputed").map(
|
|
762
|
+
(field) => {
|
|
763
|
+
const meta = reflector.get("relationComputed", cl, field);
|
|
764
|
+
const res = meta();
|
|
765
|
+
return {
|
|
766
|
+
isArray: res.isArray,
|
|
767
|
+
propertyClass: res.entityClass,
|
|
768
|
+
propertyName: field,
|
|
769
|
+
computed: true
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
);
|
|
773
|
+
return _.uniqBy(
|
|
774
|
+
[...typeormRelations, ...computedRelations],
|
|
775
|
+
// Merge typeorm relations and computed relations
|
|
776
|
+
(r) => r.propertyName
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/utility/cursor-pagination-utils.ts
|
|
781
|
+
import { Brackets } from "typeorm";
|
|
782
|
+
import _2 from "lodash";
|
|
783
|
+
import SJSON from "superjson";
|
|
784
|
+
|
|
785
|
+
// src/utility/filter-relations.ts
|
|
786
|
+
var extractRelationName = (relation) => {
|
|
787
|
+
if (typeof relation === "string") {
|
|
788
|
+
return relation;
|
|
789
|
+
} else {
|
|
790
|
+
return relation.name;
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
var typeormRelationsCache = /* @__PURE__ */ new Map();
|
|
794
|
+
var fetchTypeormRelations = (cl) => {
|
|
795
|
+
if (typeormRelationsCache.has(cl)) {
|
|
796
|
+
return typeormRelationsCache.get(cl);
|
|
797
|
+
}
|
|
798
|
+
const relations = getTypeormRelations(cl);
|
|
799
|
+
const map = Object.fromEntries(
|
|
800
|
+
relations.map((r) => [r.propertyName, r])
|
|
801
|
+
);
|
|
802
|
+
typeormRelationsCache.set(cl, map);
|
|
803
|
+
return map;
|
|
804
|
+
};
|
|
805
|
+
var filterRelations = (cl, relations, cond = () => true) => {
|
|
806
|
+
return relations?.filter((r) => {
|
|
807
|
+
const relationName = extractRelationName(r);
|
|
808
|
+
const checkLevel = (entityClass, name) => {
|
|
809
|
+
const [currentLevel, ...nextLevel] = name.split(".");
|
|
810
|
+
const relation = fetchTypeormRelations(entityClass)?.[currentLevel];
|
|
811
|
+
if (!relation) {
|
|
812
|
+
throw new Error(
|
|
813
|
+
`Relation ${currentLevel} not found in ${entityClass.name} (Reading ${relationName} in ${cl.name})`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
if (relation.computed) return false;
|
|
817
|
+
if (!nextLevel.length) return true;
|
|
818
|
+
return checkLevel(relation.propertyClass, nextLevel.join("."));
|
|
819
|
+
};
|
|
820
|
+
return checkLevel(cl, relationName);
|
|
821
|
+
}) || [];
|
|
822
|
+
};
|
|
823
|
+
var queryColumnOptionsFromAlias = (qb, cl, rootAlias, alias) => {
|
|
824
|
+
const [field, key] = alias.split(".");
|
|
825
|
+
if (field === rootAlias) {
|
|
826
|
+
return {
|
|
827
|
+
relations: [],
|
|
828
|
+
column: qb.connection.getMetadata(cl)?.findColumnWithPropertyName(key)
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
const relationLevels = field.split("_");
|
|
832
|
+
let currentClass = cl;
|
|
833
|
+
const relations = [];
|
|
834
|
+
while (relationLevels.length) {
|
|
835
|
+
const f = relationLevels.shift();
|
|
836
|
+
const relation = fetchTypeormRelations(currentClass)?.[f];
|
|
837
|
+
if (!relation || relation.computed) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
relations.push(relation);
|
|
841
|
+
currentClass = relation.propertyClass;
|
|
842
|
+
}
|
|
843
|
+
return {
|
|
844
|
+
relations,
|
|
845
|
+
column: qb.connection.getMetadata(currentClass)?.findColumnWithPropertyName(key)
|
|
846
|
+
};
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
// src/utility/cursor-pagination-utils.ts
|
|
850
|
+
function getValueFromOrderBy(orderBy, reversed = false) {
|
|
851
|
+
if (reversed) {
|
|
852
|
+
const value = getValueFromOrderBy(orderBy, false);
|
|
853
|
+
return value === "ASC" ? "DESC" : "ASC";
|
|
854
|
+
}
|
|
855
|
+
return typeof orderBy === "string" ? orderBy : orderBy.order;
|
|
856
|
+
}
|
|
857
|
+
function getNullsFromOrderBy(orderBy, reversed = false) {
|
|
858
|
+
if (reversed) {
|
|
859
|
+
const value = getNullsFromOrderBy(orderBy, false);
|
|
860
|
+
return value === "NULLS FIRST" ? "NULLS LAST" : "NULLS FIRST";
|
|
861
|
+
}
|
|
862
|
+
const nulls = typeof orderBy === "string" ? void 0 : orderBy.nulls;
|
|
863
|
+
if (!nulls) {
|
|
864
|
+
const value = getValueFromOrderBy(orderBy);
|
|
865
|
+
return value === "ASC" ? "NULLS FIRST" : "NULLS LAST";
|
|
866
|
+
}
|
|
867
|
+
return nulls;
|
|
868
|
+
}
|
|
869
|
+
function getOperator(orderBy) {
|
|
870
|
+
const value = getValueFromOrderBy(orderBy);
|
|
871
|
+
return value === "ASC" ? ">" : "<";
|
|
872
|
+
}
|
|
873
|
+
function getReversedTypeormOrderBy(orderBy) {
|
|
874
|
+
if (typeof orderBy === "string") {
|
|
875
|
+
return {
|
|
876
|
+
order: orderBy === "ASC" ? "DESC" : "ASC",
|
|
877
|
+
nulls: void 0
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
return {
|
|
881
|
+
order: orderBy.order === "ASC" ? "DESC" : "ASC",
|
|
882
|
+
nulls: orderBy.nulls ? orderBy.nulls === "NULLS FIRST" ? "NULLS LAST" : "NULLS FIRST" : void 0
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function reverseQueryOrderBy(qb) {
|
|
886
|
+
const orderBys = getTypeormOrderBy(qb);
|
|
887
|
+
orderBys.forEach(({ key, direction }, i) => {
|
|
888
|
+
const reversed = getReversedTypeormOrderBy(direction);
|
|
889
|
+
if (i === 0) {
|
|
890
|
+
qb.orderBy(key, reversed.order, reversed.nulls);
|
|
891
|
+
} else {
|
|
892
|
+
qb.addOrderBy(key, reversed.order, reversed.nulls);
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
function getTypeormOrderBy(qb) {
|
|
897
|
+
const orderBy = qb.expressionMap.allOrderBys;
|
|
898
|
+
const orderByEntrys = Object.entries(orderBy);
|
|
899
|
+
return orderByEntrys.map(([key, value]) => ({
|
|
900
|
+
key,
|
|
901
|
+
direction: value
|
|
902
|
+
}));
|
|
903
|
+
}
|
|
904
|
+
function extractValueFromOrderByKey(obj, key, entityAliasName) {
|
|
905
|
+
const getField = (obj2, key2) => {
|
|
906
|
+
const value2 = obj2[key2];
|
|
907
|
+
if (value2 == null) return value2;
|
|
908
|
+
if (Array.isArray(value2)) {
|
|
909
|
+
return void 0;
|
|
910
|
+
}
|
|
911
|
+
return value2;
|
|
912
|
+
};
|
|
913
|
+
const [alias, field] = key.split(".");
|
|
914
|
+
if (alias === entityAliasName) {
|
|
915
|
+
return getField(obj, field);
|
|
916
|
+
}
|
|
917
|
+
const aliasParts = alias.split("_");
|
|
918
|
+
if (aliasParts.length === 1) {
|
|
919
|
+
const value2 = getField(obj, alias);
|
|
920
|
+
if (value2 == null) return value2;
|
|
921
|
+
return getField(value2, field);
|
|
922
|
+
}
|
|
923
|
+
const value = getField(obj, aliasParts[0]);
|
|
924
|
+
if (!value == null) return value;
|
|
925
|
+
return extractValueFromOrderByKey(
|
|
926
|
+
value,
|
|
927
|
+
`${aliasParts.slice(1).join("_")}.${field}`
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
function encodeBase64Url(str) {
|
|
931
|
+
return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
932
|
+
}
|
|
933
|
+
function decodeBase64Url(str) {
|
|
934
|
+
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
935
|
+
while (str.length % 4) str += "=";
|
|
936
|
+
return Buffer.from(str, "base64").toString();
|
|
937
|
+
}
|
|
938
|
+
async function getPaginatedResult(qb, entityClass, entityAliasName, take, cursor) {
|
|
939
|
+
const orderBys = getTypeormOrderBy(qb);
|
|
940
|
+
qb.take(take + 1);
|
|
941
|
+
let type = "next";
|
|
942
|
+
if (cursor) {
|
|
943
|
+
const data2 = SJSON.parse(decodeBase64Url(cursor));
|
|
944
|
+
type = data2.type;
|
|
945
|
+
const keys = Object.keys(data2.payload).filter(
|
|
946
|
+
(k) => qb.expressionMap.orderBys[k]
|
|
947
|
+
);
|
|
948
|
+
if (keys.length) {
|
|
949
|
+
const staircasedKeys = keys.map(
|
|
950
|
+
(key, i) => _2.range(i + 1).map((j) => keys[j])
|
|
951
|
+
);
|
|
952
|
+
const cursorKey = (key) => `_cursor_${key.replace(/\./g, "__").replace(/"/g, "")}`;
|
|
953
|
+
const expressionMatrix = staircasedKeys.map(
|
|
954
|
+
(keys2) => keys2.map((key, j) => {
|
|
955
|
+
const paramKey = cursorKey(key);
|
|
956
|
+
const cursorValue = data2.payload[key];
|
|
957
|
+
const orderBy = qb.expressionMap.orderBys[key];
|
|
958
|
+
const reversed = data2.type === "prev";
|
|
959
|
+
const order = getValueFromOrderBy(orderBy, reversed);
|
|
960
|
+
const nulls = getNullsFromOrderBy(orderBy, reversed);
|
|
961
|
+
const isLast = j === keys2.length - 1;
|
|
962
|
+
const subject = key.includes('"') ? getSubject(qb, key.replace(/"/g, "")) : key;
|
|
963
|
+
const mayBeNullAtEnd = () => {
|
|
964
|
+
const res = nulls === "NULLS LAST" && order === "ASC" || nulls === "NULLS FIRST" && order === "DESC";
|
|
965
|
+
if (reversed) {
|
|
966
|
+
return !res;
|
|
967
|
+
}
|
|
968
|
+
return res;
|
|
969
|
+
};
|
|
970
|
+
if (cursorValue == null) {
|
|
971
|
+
if (isLast) {
|
|
972
|
+
if (mayBeNullAtEnd()) {
|
|
973
|
+
return "__never__";
|
|
974
|
+
} else {
|
|
975
|
+
return `${subject} IS NOT NULL`;
|
|
976
|
+
}
|
|
977
|
+
} else {
|
|
978
|
+
return `${subject} IS NULL`;
|
|
979
|
+
}
|
|
980
|
+
} else {
|
|
981
|
+
if (isLast) {
|
|
982
|
+
const expr = `${subject} ${getOperator(order)} :${paramKey}`;
|
|
983
|
+
if (mayBeNullAtEnd() && queryColumnOptionsFromAlias(
|
|
984
|
+
qb,
|
|
985
|
+
entityClass,
|
|
986
|
+
entityAliasName,
|
|
987
|
+
key
|
|
988
|
+
)?.column?.isNullable) {
|
|
989
|
+
return `(${expr} OR ${subject} IS NULL)`;
|
|
990
|
+
}
|
|
991
|
+
return expr;
|
|
992
|
+
} else {
|
|
993
|
+
return `${subject} = :${paramKey}`;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
})
|
|
997
|
+
).filter((s) => !s.includes("__never__"));
|
|
998
|
+
if (expressionMatrix.length) {
|
|
999
|
+
qb.andWhere(
|
|
1000
|
+
new Brackets((sqb) => {
|
|
1001
|
+
const levelToBrackets = (level) => new Brackets((qb2) => {
|
|
1002
|
+
level.forEach((expr, i) => {
|
|
1003
|
+
if (i === 0) {
|
|
1004
|
+
qb2.where(expr);
|
|
1005
|
+
} else {
|
|
1006
|
+
qb2.andWhere(expr);
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
});
|
|
1010
|
+
const [first, ...rest] = expressionMatrix;
|
|
1011
|
+
sqb.where(levelToBrackets(first));
|
|
1012
|
+
rest.forEach((level) => sqb.orWhere(levelToBrackets(level)));
|
|
1013
|
+
})
|
|
1014
|
+
).setParameters(
|
|
1015
|
+
Object.fromEntries(
|
|
1016
|
+
Object.entries(data2.payload).filter(([k, v]) => qb.expressionMap.orderBys[k] && v != null).map(([k, v]) => [cursorKey(k), v])
|
|
1017
|
+
)
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (data2.type === "prev") {
|
|
1022
|
+
reverseQueryOrderBy(qb);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
const { raw, entities: data } = await qb.getRawAndEntities();
|
|
1026
|
+
const rawMapById = /* @__PURE__ */ new Map();
|
|
1027
|
+
const getRawFromEntity = (entity) => {
|
|
1028
|
+
const isNumberId = typeof entity.id === "number";
|
|
1029
|
+
const id = entity.id;
|
|
1030
|
+
if (rawMapById.has(id)) {
|
|
1031
|
+
return rawMapById.get(id);
|
|
1032
|
+
}
|
|
1033
|
+
return raw.find((r) => {
|
|
1034
|
+
let id2 = r[`${entityAliasName}_id`];
|
|
1035
|
+
if (isNumberId) {
|
|
1036
|
+
id2 = Number(id2);
|
|
1037
|
+
if (isNaN(id2)) return false;
|
|
1038
|
+
}
|
|
1039
|
+
if (id2 == null) return false;
|
|
1040
|
+
rawMapById[id2] = r;
|
|
1041
|
+
return id2 === entity.id;
|
|
1042
|
+
});
|
|
1043
|
+
};
|
|
1044
|
+
const enough = data.length > take;
|
|
1045
|
+
const hasNext = enough || type === "prev";
|
|
1046
|
+
const hasPrev = cursor && (enough || type === "next");
|
|
1047
|
+
if (enough) {
|
|
1048
|
+
data.pop();
|
|
1049
|
+
}
|
|
1050
|
+
if (type === "prev") {
|
|
1051
|
+
data.reverse();
|
|
1052
|
+
}
|
|
1053
|
+
const generateCursor = (type2, data2) => {
|
|
1054
|
+
const targetObject = type2 === "prev" ? data2[0] : data2[data2.length - 1];
|
|
1055
|
+
const payload = Object.fromEntries(
|
|
1056
|
+
orderBys.map(({ key }) => {
|
|
1057
|
+
const value = !key.includes(".") || key.includes('"') ? getRawFromEntity(targetObject)?.[key.replace(/"/g, "")] : extractValueFromOrderByKey(targetObject, key, entityAliasName);
|
|
1058
|
+
return [key, value];
|
|
1059
|
+
}).filter((s) => s[1] !== void 0)
|
|
1060
|
+
);
|
|
1061
|
+
return encodeBase64Url(SJSON.stringify({ type: type2, payload }));
|
|
1062
|
+
};
|
|
1063
|
+
return {
|
|
1064
|
+
data,
|
|
1065
|
+
paginatedResult: {
|
|
1066
|
+
nextCursor: hasNext ? generateCursor("next", data) : void 0,
|
|
1067
|
+
previousCursor: hasPrev ? generateCursor("prev", data) : void 0
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// src/crud-base.ts
|
|
1073
|
+
import PQueue from "p-queue";
|
|
1074
|
+
var Relation = (name, options = {}) => {
|
|
1075
|
+
return { name, inner: false, ...options };
|
|
1076
|
+
};
|
|
1077
|
+
var Inner = (name, options = {}) => {
|
|
1078
|
+
return Relation(name, { inner: true, ...options });
|
|
1079
|
+
};
|
|
1080
|
+
var loadedParsers = /* @__PURE__ */ new Set();
|
|
1081
|
+
var loadFullTextQueue = new PQueue({
|
|
1082
|
+
concurrency: 1
|
|
1083
|
+
});
|
|
1084
|
+
var CrudBase = class {
|
|
1085
|
+
constructor(entityClass, repo, crudOptions) {
|
|
1086
|
+
this.entityClass = entityClass;
|
|
1087
|
+
this.repo = repo;
|
|
1088
|
+
this.crudOptions = crudOptions;
|
|
1089
|
+
this.entityName = this.entityClass.name;
|
|
1090
|
+
this.entityReturnMessageDto = ReturnMessageDto(this.entityClass);
|
|
1091
|
+
this.importEntryDto = ImportEntryDto(this.entityClass);
|
|
1092
|
+
this.importReturnMessageDto = ReturnMessageDto([this.importEntryDto]);
|
|
1093
|
+
this.entityPaginatedReturnMessageDto = PaginatedReturnMessageDto(
|
|
1094
|
+
this.entityClass
|
|
1095
|
+
);
|
|
1096
|
+
this.entityCursorPaginatedReturnMessageDto = CursorPaginationReturnMessageDto(this.entityClass);
|
|
1097
|
+
this.entityRelations = filterRelations(
|
|
1098
|
+
this.entityClass,
|
|
1099
|
+
this.crudOptions.relations,
|
|
1100
|
+
(r) => !r.computed
|
|
1101
|
+
);
|
|
1102
|
+
this.extraGetQuery = this.crudOptions.extraGetQuery || ((qb) => {
|
|
1103
|
+
});
|
|
1104
|
+
this.log = new ConsoleLogger(`${this.entityClass.name}Service`);
|
|
1105
|
+
this._typeormRelations = getTypeormRelations(this.entityClass);
|
|
1106
|
+
}
|
|
1107
|
+
_cleanEntityNotInResultFields(ent) {
|
|
1108
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1109
|
+
const runSingleObject = (o, cl) => {
|
|
1110
|
+
if (visited.has(o)) {
|
|
1111
|
+
return o;
|
|
1112
|
+
}
|
|
1113
|
+
const fields = getNotInResultFields(
|
|
1114
|
+
cl,
|
|
1115
|
+
this.crudOptions.keepEntityVersioningDates
|
|
1116
|
+
);
|
|
1117
|
+
for (const field of fields) {
|
|
1118
|
+
delete o[field];
|
|
1119
|
+
}
|
|
1120
|
+
visited.add(o);
|
|
1121
|
+
for (const relation of this._typeormRelations) {
|
|
1122
|
+
const propertyName = relation.propertyName;
|
|
1123
|
+
if (o[propertyName]) {
|
|
1124
|
+
if (Array.isArray(o[propertyName])) {
|
|
1125
|
+
o[propertyName] = o[propertyName].map(
|
|
1126
|
+
(r) => runSingleObject(r, relation.propertyClass)
|
|
1127
|
+
);
|
|
1128
|
+
} else {
|
|
1129
|
+
o[propertyName] = runSingleObject(
|
|
1130
|
+
o[propertyName],
|
|
1131
|
+
relation.propertyClass
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return o;
|
|
1137
|
+
};
|
|
1138
|
+
return runSingleObject(ent, this.entityClass);
|
|
1139
|
+
}
|
|
1140
|
+
cleanEntityNotInResultFields(ents) {
|
|
1141
|
+
if (Array.isArray(ents)) {
|
|
1142
|
+
return ents.map((ent) => this._cleanEntityNotInResultFields(ent));
|
|
1143
|
+
} else {
|
|
1144
|
+
return this._cleanEntityNotInResultFields(ents);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
async _batchCreate(ents, beforeCreate, skipErrors = false) {
|
|
1148
|
+
const entsWithId = ents.filter((ent) => ent.id != null);
|
|
1149
|
+
const result = await this.repo.manager.transaction(async (mdb) => {
|
|
1150
|
+
let skipped = [];
|
|
1151
|
+
const repo = mdb.getRepository(this.entityClass);
|
|
1152
|
+
let entsToSave = ents;
|
|
1153
|
+
if (entsWithId.length) {
|
|
1154
|
+
const entIds = entsWithId.map((ent) => ent.id);
|
|
1155
|
+
const entIdChunks = _3.chunk(entIds, 65535);
|
|
1156
|
+
const existingEnts = (await Promise.all(
|
|
1157
|
+
entIdChunks.map(
|
|
1158
|
+
(chunk) => repo.find({
|
|
1159
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1160
|
+
// @ts-ignore
|
|
1161
|
+
where: {
|
|
1162
|
+
id: In(chunk)
|
|
1163
|
+
},
|
|
1164
|
+
select: this.crudOptions.createOrUpdate ? void 0 : ["id", "deleteTime"],
|
|
1165
|
+
withDeleted: true
|
|
1166
|
+
})
|
|
1167
|
+
)
|
|
1168
|
+
)).flat();
|
|
1169
|
+
if (existingEnts.length) {
|
|
1170
|
+
const existingEntsWithoutDeleteTime = existingEnts.filter(
|
|
1171
|
+
(ent) => ent.deleteTime == null
|
|
1172
|
+
);
|
|
1173
|
+
const existingEntsWithDeleteTime = existingEnts.filter(
|
|
1174
|
+
(ent) => ent.deleteTime != null
|
|
1175
|
+
);
|
|
1176
|
+
if (existingEntsWithoutDeleteTime.length) {
|
|
1177
|
+
if (this.crudOptions.createOrUpdate) {
|
|
1178
|
+
const existingIdMap = /* @__PURE__ */ new Map();
|
|
1179
|
+
existingEntsWithoutDeleteTime.forEach((ent) => {
|
|
1180
|
+
existingIdMap.set(ent.id, ent);
|
|
1181
|
+
});
|
|
1182
|
+
entsToSave = [];
|
|
1183
|
+
for (const ent of ents) {
|
|
1184
|
+
if (existingIdMap.has(ent.id)) {
|
|
1185
|
+
const existingEnt = existingIdMap.get(ent.id);
|
|
1186
|
+
Object.assign(existingEnt, ent);
|
|
1187
|
+
entsToSave.push(existingEnt);
|
|
1188
|
+
} else {
|
|
1189
|
+
entsToSave.push(ent);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
} else {
|
|
1193
|
+
if (!skipErrors) {
|
|
1194
|
+
throw new BlankReturnMessageDto2(
|
|
1195
|
+
404,
|
|
1196
|
+
`${this.entityName} ID ${existingEntsWithoutDeleteTime.join(
|
|
1197
|
+
","
|
|
1198
|
+
)} already exists`
|
|
1199
|
+
).toException();
|
|
1200
|
+
}
|
|
1201
|
+
const existingEntsWithoutDeleteTimeIdSet = new Set(
|
|
1202
|
+
existingEntsWithoutDeleteTime.map((e) => e.id)
|
|
1203
|
+
);
|
|
1204
|
+
const skippedEnts = ents.filter(
|
|
1205
|
+
(ent) => existingEntsWithoutDeleteTimeIdSet.has(ent.id)
|
|
1206
|
+
);
|
|
1207
|
+
skipped = skippedEnts.map((ent) => ({
|
|
1208
|
+
result: "Already exists",
|
|
1209
|
+
entry: ent
|
|
1210
|
+
}));
|
|
1211
|
+
const skippedEntsSet = new Set(skippedEnts);
|
|
1212
|
+
entsToSave = ents.filter((ent) => !skippedEntsSet.has(ent));
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
if (existingEntsWithDeleteTime.length) {
|
|
1216
|
+
await repo.delete(
|
|
1217
|
+
existingEntsWithDeleteTime.map((ent) => ent.id)
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
if (beforeCreate) {
|
|
1223
|
+
await beforeCreate(repo);
|
|
1224
|
+
}
|
|
1225
|
+
try {
|
|
1226
|
+
const entChunksToSave = _3.chunk(
|
|
1227
|
+
entsToSave,
|
|
1228
|
+
Math.floor(
|
|
1229
|
+
65535 / Math.max(1, Object.keys(entsToSave[0] || {}).length)
|
|
1230
|
+
)
|
|
1231
|
+
);
|
|
1232
|
+
let results = [];
|
|
1233
|
+
for (const entChunk of entChunksToSave) {
|
|
1234
|
+
const savedChunk = await repo.save(entChunk);
|
|
1235
|
+
results = results.concat(savedChunk);
|
|
1236
|
+
}
|
|
1237
|
+
return {
|
|
1238
|
+
results,
|
|
1239
|
+
skipped
|
|
1240
|
+
};
|
|
1241
|
+
} catch (e) {
|
|
1242
|
+
this.log.error(
|
|
1243
|
+
`Failed to create entity ${JSON.stringify(
|
|
1244
|
+
entsToSave
|
|
1245
|
+
)}: ${e.toString()}`
|
|
1246
|
+
);
|
|
1247
|
+
throw new BlankReturnMessageDto2(500, "Internal error").toException();
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
return result;
|
|
1251
|
+
}
|
|
1252
|
+
async create(_ent, beforeCreate) {
|
|
1253
|
+
if (!_ent) {
|
|
1254
|
+
throw new BlankReturnMessageDto2(400, "Invalid entity").toException();
|
|
1255
|
+
}
|
|
1256
|
+
let ent = new this.entityClass();
|
|
1257
|
+
Object.assign(
|
|
1258
|
+
ent,
|
|
1259
|
+
omit(_ent, ...this._typeormRelations.map((r) => r.propertyName))
|
|
1260
|
+
);
|
|
1261
|
+
const invalidReason = ent.isValidInCreate();
|
|
1262
|
+
if (invalidReason) {
|
|
1263
|
+
throw new BlankReturnMessageDto2(400, invalidReason).toException();
|
|
1264
|
+
}
|
|
1265
|
+
const savedEnt = await this.repo.manager.transaction(async (mdb) => {
|
|
1266
|
+
const repo = mdb.getRepository(this.entityClass);
|
|
1267
|
+
if (ent.id != null) {
|
|
1268
|
+
const existingEnt = await repo.findOne({
|
|
1269
|
+
where: { id: ent.id },
|
|
1270
|
+
select: this.crudOptions.createOrUpdate ? void 0 : ["id", "deleteTime"],
|
|
1271
|
+
withDeleted: true
|
|
1272
|
+
});
|
|
1273
|
+
if (existingEnt) {
|
|
1274
|
+
if (existingEnt.deleteTime) {
|
|
1275
|
+
await repo.delete(existingEnt.id);
|
|
1276
|
+
} else if (this.crudOptions.createOrUpdate) {
|
|
1277
|
+
Object.assign(existingEnt, ent);
|
|
1278
|
+
ent = existingEnt;
|
|
1279
|
+
} else {
|
|
1280
|
+
throw new BlankReturnMessageDto2(
|
|
1281
|
+
404,
|
|
1282
|
+
`${this.entityName} ID ${ent.id} already exists`
|
|
1283
|
+
).toException();
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
if (beforeCreate) {
|
|
1288
|
+
await beforeCreate(repo);
|
|
1289
|
+
}
|
|
1290
|
+
await ent.beforeCreate?.();
|
|
1291
|
+
try {
|
|
1292
|
+
const savedEnt2 = await repo.save(ent);
|
|
1293
|
+
await savedEnt2.afterCreate?.();
|
|
1294
|
+
return this.cleanEntityNotInResultFields(savedEnt2);
|
|
1295
|
+
} catch (e) {
|
|
1296
|
+
this.log.error(
|
|
1297
|
+
`Failed to create entity ${JSON.stringify(ent)}: ${e.toString()}`
|
|
1298
|
+
);
|
|
1299
|
+
throw new BlankReturnMessageDto2(500, "Internal error").toException();
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
return new this.entityReturnMessageDto(200, "success", savedEnt);
|
|
1303
|
+
}
|
|
1304
|
+
get entityAliasName() {
|
|
1305
|
+
return camelCase(this.entityName);
|
|
1306
|
+
}
|
|
1307
|
+
_applyQueryRelation(qb, relation) {
|
|
1308
|
+
const { name } = relation;
|
|
1309
|
+
const relationUnit = name.split(".");
|
|
1310
|
+
const base = relationUnit.length === 1 ? this.entityAliasName : relationUnit.slice(0, relationUnit.length - 1).join("_");
|
|
1311
|
+
const property = relationUnit[relationUnit.length - 1];
|
|
1312
|
+
const properyAlias = relationUnit.join("_");
|
|
1313
|
+
const methodName = relation.inner ? "innerJoin" : "leftJoin";
|
|
1314
|
+
if (!relation.noSelect) {
|
|
1315
|
+
qb.addSelect(properyAlias);
|
|
1316
|
+
}
|
|
1317
|
+
qb[methodName](
|
|
1318
|
+
`${base}.${property}`,
|
|
1319
|
+
properyAlias,
|
|
1320
|
+
relation.extraCondition || void 0,
|
|
1321
|
+
relation.extraConditionFields || void 0
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
_applyQueryRelations(qb) {
|
|
1325
|
+
for (const relation of this.entityRelations) {
|
|
1326
|
+
if (typeof relation === "string") {
|
|
1327
|
+
this._applyQueryRelation(qb, { name: relation });
|
|
1328
|
+
} else {
|
|
1329
|
+
this._applyQueryRelation(qb, relation);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
_applyQueryFilters(qb, ent) {
|
|
1334
|
+
const queryFields = reflector.getArray(
|
|
1335
|
+
"queryConditionFields",
|
|
1336
|
+
this.entityClass
|
|
1337
|
+
);
|
|
1338
|
+
for (const field of queryFields) {
|
|
1339
|
+
const condition = reflector.get(
|
|
1340
|
+
"queryCondition",
|
|
1341
|
+
this.entityClass,
|
|
1342
|
+
field
|
|
1343
|
+
);
|
|
1344
|
+
if (condition) {
|
|
1345
|
+
condition(ent, qb, this.entityAliasName, field);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
queryBuilder() {
|
|
1350
|
+
return this.repo.createQueryBuilder(this.entityAliasName);
|
|
1351
|
+
}
|
|
1352
|
+
async findOne(id, extraQuery = () => {
|
|
1353
|
+
}) {
|
|
1354
|
+
const query = this.queryBuilder().where(`${this.entityAliasName}.id = :id`, { id }).take(1);
|
|
1355
|
+
this._applyQueryRelations(query);
|
|
1356
|
+
this.extraGetQuery(query);
|
|
1357
|
+
extraQuery(query);
|
|
1358
|
+
query.take(1);
|
|
1359
|
+
let ent;
|
|
1360
|
+
try {
|
|
1361
|
+
ent = await query.getOne();
|
|
1362
|
+
} catch (e) {
|
|
1363
|
+
const [sql, params] = query.getQueryAndParameters();
|
|
1364
|
+
this.log.error(
|
|
1365
|
+
`Failed to read entity ID ${id} with SQL ${sql} param ${params.join(
|
|
1366
|
+
","
|
|
1367
|
+
)}: ${e.toString()}`
|
|
1368
|
+
);
|
|
1369
|
+
throw new BlankReturnMessageDto2(500, "Internal error").toException();
|
|
1370
|
+
}
|
|
1371
|
+
if (!ent) {
|
|
1372
|
+
throw new BlankReturnMessageDto2(
|
|
1373
|
+
404,
|
|
1374
|
+
`${this.entityName} ID ${id} not found.`
|
|
1375
|
+
).toException();
|
|
1376
|
+
}
|
|
1377
|
+
await ent.afterGet?.();
|
|
1378
|
+
return new this.entityReturnMessageDto(
|
|
1379
|
+
200,
|
|
1380
|
+
"success",
|
|
1381
|
+
this.cleanEntityNotInResultFields(ent)
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
async _preFindAll(ent, extraQuery = () => {
|
|
1385
|
+
}) {
|
|
1386
|
+
const query = this.queryBuilder().where("1 = 1");
|
|
1387
|
+
const newEnt = new this.entityClass();
|
|
1388
|
+
if (ent) {
|
|
1389
|
+
Object.assign(newEnt, ent);
|
|
1390
|
+
await newEnt?.beforeGet?.();
|
|
1391
|
+
newEnt.applyQuery(query, this.entityAliasName);
|
|
1392
|
+
}
|
|
1393
|
+
this._applyQueryRelations(query);
|
|
1394
|
+
this._applyQueryFilters(query, newEnt);
|
|
1395
|
+
const pageSettings = newEnt instanceof PageSettingsDto ? newEnt : Object.assign(new PageSettingsDto(), newEnt);
|
|
1396
|
+
this.extraGetQuery(query);
|
|
1397
|
+
extraQuery(query);
|
|
1398
|
+
return { query, newEnt, pageSettings };
|
|
1399
|
+
}
|
|
1400
|
+
async findAll(ent, extraQuery = () => {
|
|
1401
|
+
}) {
|
|
1402
|
+
const { query, pageSettings } = await this._preFindAll(ent, extraQuery);
|
|
1403
|
+
pageSettings.applyPaginationQuery(query);
|
|
1404
|
+
try {
|
|
1405
|
+
const [data, count] = await query.getManyAndCount();
|
|
1406
|
+
await Promise.all(data.map((ent2) => ent2.afterGet?.()));
|
|
1407
|
+
return new this.entityPaginatedReturnMessageDto(
|
|
1408
|
+
200,
|
|
1409
|
+
"success",
|
|
1410
|
+
this.cleanEntityNotInResultFields(data),
|
|
1411
|
+
count,
|
|
1412
|
+
pageSettings.getActualPageSettings()
|
|
1413
|
+
);
|
|
1414
|
+
} catch (e) {
|
|
1415
|
+
const [sql, params] = query.getQueryAndParameters();
|
|
1416
|
+
this.log.error(
|
|
1417
|
+
`Failed to read entity cond ${JSON.stringify(
|
|
1418
|
+
ent
|
|
1419
|
+
)} with SQL ${sql} param ${params.join(",")}: ${e.toString()}`
|
|
1420
|
+
);
|
|
1421
|
+
throw new BlankReturnMessageDto2(500, "Internal error").toException();
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
async findAllCursorPaginated(ent, extraQuery = () => {
|
|
1425
|
+
}) {
|
|
1426
|
+
const { query, pageSettings } = await this._preFindAll(ent, extraQuery);
|
|
1427
|
+
try {
|
|
1428
|
+
const { data, paginatedResult } = await getPaginatedResult(
|
|
1429
|
+
query,
|
|
1430
|
+
this.entityClass,
|
|
1431
|
+
this.entityAliasName,
|
|
1432
|
+
pageSettings.getRecordsPerPage(),
|
|
1433
|
+
ent.paginationCursor
|
|
1434
|
+
);
|
|
1435
|
+
await Promise.all(data.map((ent2) => ent2.afterGet?.()));
|
|
1436
|
+
return new this.entityCursorPaginatedReturnMessageDto(
|
|
1437
|
+
200,
|
|
1438
|
+
"success",
|
|
1439
|
+
this.cleanEntityNotInResultFields(data),
|
|
1440
|
+
paginatedResult
|
|
1441
|
+
);
|
|
1442
|
+
} catch (e) {
|
|
1443
|
+
const [sql, params] = query.getQueryAndParameters();
|
|
1444
|
+
this.log.error(
|
|
1445
|
+
`Failed to read entity cond ${JSON.stringify(
|
|
1446
|
+
ent
|
|
1447
|
+
)} with SQL ${sql} param ${params.join(",")}: ${e.toString()}`
|
|
1448
|
+
);
|
|
1449
|
+
throw new BlankReturnMessageDto2(500, "Internal error").toException();
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
async update(id, entPart, cond = {}) {
|
|
1453
|
+
let result;
|
|
1454
|
+
const ent = new this.entityClass();
|
|
1455
|
+
Object.assign(ent, entPart);
|
|
1456
|
+
const invalidReason = ent.isValidInUpdate();
|
|
1457
|
+
if (invalidReason) {
|
|
1458
|
+
throw new BlankReturnMessageDto2(400, invalidReason).toException();
|
|
1459
|
+
}
|
|
1460
|
+
await ent.beforeUpdate?.();
|
|
1461
|
+
try {
|
|
1462
|
+
result = await this.repo.update(
|
|
1463
|
+
{
|
|
1464
|
+
id,
|
|
1465
|
+
...cond
|
|
1466
|
+
},
|
|
1467
|
+
ent
|
|
1468
|
+
);
|
|
1469
|
+
} catch (e) {
|
|
1470
|
+
this.log.error(
|
|
1471
|
+
`Failed to update entity ID ${id} to ${JSON.stringify(
|
|
1472
|
+
entPart
|
|
1473
|
+
)}: ${e.toString()}`
|
|
1474
|
+
);
|
|
1475
|
+
throw new BlankReturnMessageDto2(500, "Internal error").toException();
|
|
1476
|
+
}
|
|
1477
|
+
if (!result.affected) {
|
|
1478
|
+
throw new BlankReturnMessageDto2(
|
|
1479
|
+
404,
|
|
1480
|
+
`${this.entityName} ID ${id} not found.`
|
|
1481
|
+
).toException();
|
|
1482
|
+
}
|
|
1483
|
+
return new BlankReturnMessageDto2(200, "success");
|
|
1484
|
+
}
|
|
1485
|
+
async delete(id, cond = {}) {
|
|
1486
|
+
let result;
|
|
1487
|
+
const searchCond = {
|
|
1488
|
+
id,
|
|
1489
|
+
...cond
|
|
1490
|
+
};
|
|
1491
|
+
try {
|
|
1492
|
+
result = await (this.crudOptions.hardDelete || !this.repo.manager.connection.getMetadata(this.entityClass).deleteDateColumn ? this.repo.delete(searchCond) : this.repo.softDelete(searchCond));
|
|
1493
|
+
} catch (e) {
|
|
1494
|
+
this.log.error(`Failed to delete entity ID ${id}: ${e.toString()}`);
|
|
1495
|
+
throw new BlankReturnMessageDto2(500, "Internal error").toException();
|
|
1496
|
+
}
|
|
1497
|
+
if (!result.affected) {
|
|
1498
|
+
throw new BlankReturnMessageDto2(
|
|
1499
|
+
404,
|
|
1500
|
+
`${this.entityName} ID ${id} not found.`
|
|
1501
|
+
).toException();
|
|
1502
|
+
}
|
|
1503
|
+
return new BlankReturnMessageDto2(200, "success");
|
|
1504
|
+
}
|
|
1505
|
+
async importEntities(_ents, extraChecking) {
|
|
1506
|
+
const ents = _ents.map((ent) => {
|
|
1507
|
+
const newEnt = new this.entityClass();
|
|
1508
|
+
Object.assign(
|
|
1509
|
+
newEnt,
|
|
1510
|
+
omit(ent, ...this._typeormRelations.map((r) => r.propertyName))
|
|
1511
|
+
);
|
|
1512
|
+
return newEnt;
|
|
1513
|
+
});
|
|
1514
|
+
const invalidResults = _3.compact(
|
|
1515
|
+
await Promise.all(
|
|
1516
|
+
ents.map(async (ent) => {
|
|
1517
|
+
const reason = ent.isValidInCreate();
|
|
1518
|
+
if (reason) {
|
|
1519
|
+
return { entry: ent, result: reason };
|
|
1520
|
+
}
|
|
1521
|
+
if (extraChecking) {
|
|
1522
|
+
const reason2 = await extraChecking(ent);
|
|
1523
|
+
if (reason2) {
|
|
1524
|
+
return { entry: ent, result: reason2 };
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
})
|
|
1528
|
+
)
|
|
1529
|
+
);
|
|
1530
|
+
const remainingEnts = ents.filter(
|
|
1531
|
+
(ent) => !invalidResults.find((result) => result.entry === ent)
|
|
1532
|
+
);
|
|
1533
|
+
await Promise.all(remainingEnts.map((ent) => ent.beforeCreate?.()));
|
|
1534
|
+
const data = await this._batchCreate(remainingEnts, void 0, true);
|
|
1535
|
+
await Promise.all(data.results.map((e) => e.afterCreate?.()));
|
|
1536
|
+
const results = [
|
|
1537
|
+
...invalidResults,
|
|
1538
|
+
...data.skipped,
|
|
1539
|
+
...data.results.map((e) => ({ entry: e, result: "OK" }))
|
|
1540
|
+
];
|
|
1541
|
+
return new this.importReturnMessageDto(
|
|
1542
|
+
200,
|
|
1543
|
+
"success",
|
|
1544
|
+
results.map((r) => {
|
|
1545
|
+
const entry = new this.importEntryDto();
|
|
1546
|
+
Object.assign(entry, r);
|
|
1547
|
+
entry.entry = this.cleanEntityNotInResultFields(r.entry);
|
|
1548
|
+
return entry;
|
|
1549
|
+
})
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
async exists(id) {
|
|
1553
|
+
const ent = await this.repo.findOne({ where: { id }, select: ["id"] });
|
|
1554
|
+
return !!ent;
|
|
1555
|
+
}
|
|
1556
|
+
async _loadFullTextIndex() {
|
|
1557
|
+
const fields = reflector.getArray(
|
|
1558
|
+
"queryFullTextColumnFields",
|
|
1559
|
+
this.entityClass
|
|
1560
|
+
);
|
|
1561
|
+
const metadata = this.repo.metadata;
|
|
1562
|
+
const tableName = metadata.tableName;
|
|
1563
|
+
const sqls = [];
|
|
1564
|
+
for (const field of fields) {
|
|
1565
|
+
const options = reflector.get(
|
|
1566
|
+
"queryFullTextColumn",
|
|
1567
|
+
this.entityClass,
|
|
1568
|
+
field
|
|
1569
|
+
);
|
|
1570
|
+
if (!options) continue;
|
|
1571
|
+
const configurationName = options.configuration;
|
|
1572
|
+
const parser = options.parser;
|
|
1573
|
+
if (parser && !loadedParsers.has(parser)) {
|
|
1574
|
+
loadedParsers.add(parser);
|
|
1575
|
+
sqls.push(
|
|
1576
|
+
`CREATE EXTENSION IF NOT EXISTS ${parser};`,
|
|
1577
|
+
`DROP TEXT SEARCH CONFIGURATION IF EXISTS ${configurationName};`,
|
|
1578
|
+
`CREATE TEXT SEARCH CONFIGURATION ${configurationName} (PARSER = ${parser});`,
|
|
1579
|
+
`ALTER TEXT SEARCH CONFIGURATION ${configurationName} ADD MAPPING FOR n, v, a, i, e, l WITH simple;`
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
const indexName = `idx_fulltext_${this.entityName}_${field}`;
|
|
1583
|
+
sqls.push(
|
|
1584
|
+
`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${tableName}" USING GIN (to_tsvector('${configurationName}', "${field}"));`
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
if (sqls.length) {
|
|
1588
|
+
await this.repo.manager.query(sqls.join("\n"));
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
async onModuleInit() {
|
|
1592
|
+
await loadFullTextQueue.add(() => this._loadFullTextIndex());
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
function CrudService(entityClass, crudOptions = {}) {
|
|
1596
|
+
return class CrudServiceImpl extends CrudBase {
|
|
1597
|
+
constructor(repo) {
|
|
1598
|
+
super(entityClass, repo, crudOptions);
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// src/restful.ts
|
|
1604
|
+
import {
|
|
1605
|
+
Body,
|
|
1606
|
+
Delete,
|
|
1607
|
+
Get,
|
|
1608
|
+
HttpCode,
|
|
1609
|
+
Param,
|
|
1610
|
+
ParseIntPipe,
|
|
1611
|
+
Patch,
|
|
1612
|
+
Post,
|
|
1613
|
+
Query
|
|
1614
|
+
} from "@nestjs/common";
|
|
1615
|
+
import {
|
|
1616
|
+
BlankReturnMessageDto as BlankReturnMessageDto3,
|
|
1617
|
+
MergeMethodDecorators,
|
|
1618
|
+
PaginatedReturnMessageDto as PaginatedReturnMessageDto2,
|
|
1619
|
+
ReturnMessageDto as ReturnMessageDto2
|
|
1620
|
+
} from "nesties";
|
|
1621
|
+
import {
|
|
1622
|
+
ApiBadRequestResponse,
|
|
1623
|
+
ApiBody,
|
|
1624
|
+
ApiInternalServerErrorResponse,
|
|
1625
|
+
ApiNotFoundResponse,
|
|
1626
|
+
ApiOkResponse,
|
|
1627
|
+
ApiOperation,
|
|
1628
|
+
ApiParam,
|
|
1629
|
+
ApiProperty as ApiProperty5,
|
|
1630
|
+
IntersectionType,
|
|
1631
|
+
OmitType as OmitType2,
|
|
1632
|
+
PartialType
|
|
1633
|
+
} from "@nestjs/swagger";
|
|
1634
|
+
import _4, { upperFirst } from "lodash";
|
|
1635
|
+
|
|
1636
|
+
// src/utility/rename-class.ts
|
|
1637
|
+
function RenameClass(cls, name) {
|
|
1638
|
+
Object.defineProperty(cls, "name", { value: name });
|
|
1639
|
+
return cls;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// src/restful.ts
|
|
1643
|
+
import { DECORATORS } from "@nestjs/swagger/dist/constants";
|
|
1644
|
+
|
|
1645
|
+
// src/bases/base-restful-controller.ts
|
|
1646
|
+
var RestfulMethods = [
|
|
1647
|
+
"findOne",
|
|
1648
|
+
"findAll",
|
|
1649
|
+
"create",
|
|
1650
|
+
"update",
|
|
1651
|
+
"delete",
|
|
1652
|
+
"import"
|
|
1653
|
+
];
|
|
1654
|
+
var BaseRestfulController = class {
|
|
1655
|
+
constructor(serviceOrRepo, _options = {}) {
|
|
1656
|
+
this._options = _options;
|
|
1657
|
+
if (serviceOrRepo instanceof CrudBase) {
|
|
1658
|
+
this.service = serviceOrRepo;
|
|
1659
|
+
} else {
|
|
1660
|
+
const crudServiceClass = CrudService(this._options.entityClass, {
|
|
1661
|
+
relations: this._options.relations
|
|
1662
|
+
});
|
|
1663
|
+
this.service = new crudServiceClass(serviceOrRepo);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
findOne(id) {
|
|
1667
|
+
return this.service.findOne(id);
|
|
1668
|
+
}
|
|
1669
|
+
findAll(dto) {
|
|
1670
|
+
if (this._options.paginateType === "cursor") {
|
|
1671
|
+
return this.service.findAllCursorPaginated(dto);
|
|
1672
|
+
}
|
|
1673
|
+
if (this._options.paginateType === "offset") {
|
|
1674
|
+
return this.service.findAll(dto);
|
|
1675
|
+
}
|
|
1676
|
+
dto["recordsPerPage"] ??= 99999;
|
|
1677
|
+
return this.service.findAll(dto);
|
|
1678
|
+
}
|
|
1679
|
+
create(dto) {
|
|
1680
|
+
return this.service.create(dto);
|
|
1681
|
+
}
|
|
1682
|
+
update(id, dto) {
|
|
1683
|
+
return this.service.update(id, dto);
|
|
1684
|
+
}
|
|
1685
|
+
delete(id) {
|
|
1686
|
+
return this.service.delete(id);
|
|
1687
|
+
}
|
|
1688
|
+
import(data) {
|
|
1689
|
+
return this.service.importEntities(data.data);
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
|
|
1693
|
+
// src/utility/omit-type-exclude.ts
|
|
1694
|
+
import { OmitType } from "@nestjs/swagger";
|
|
1695
|
+
import { Exclude as Exclude2 } from "class-transformer";
|
|
1696
|
+
var OmitTypeExclude = (cl, keys) => {
|
|
1697
|
+
const omitted = OmitType(cl, keys);
|
|
1698
|
+
for (const key of keys) {
|
|
1699
|
+
Exclude2()(omitted.prototype, key);
|
|
1700
|
+
}
|
|
1701
|
+
return omitted;
|
|
1702
|
+
};
|
|
1703
|
+
|
|
1704
|
+
// src/restful.ts
|
|
1705
|
+
var getCurrentLevelRelations = (relations) => relations.filter((r) => !r.includes("."));
|
|
1706
|
+
var getNextLevelRelations = (relations, enteringField) => relations.filter((r) => r.includes(".") && r.startsWith(`${enteringField}.`)).map((r) => r.split(".").slice(1).join("."));
|
|
1707
|
+
var RestfulFactory = class _RestfulFactory {
|
|
1708
|
+
constructor(entityClass, options = {}, __resolveVisited = /* @__PURE__ */ new Map()) {
|
|
1709
|
+
this.entityClass = entityClass;
|
|
1710
|
+
this.options = options;
|
|
1711
|
+
this.__resolveVisited = __resolveVisited;
|
|
1712
|
+
this.fieldsToOmit = _4.uniq([
|
|
1713
|
+
...getSpecificFields(this.entityClass, "notColumn"),
|
|
1714
|
+
...this.options.fieldsToOmit || [],
|
|
1715
|
+
...getTypeormRelations(this.entityClass).map(
|
|
1716
|
+
(r) => r.propertyName
|
|
1717
|
+
)
|
|
1718
|
+
]);
|
|
1719
|
+
this.basicInputDto = OmitTypeExclude(
|
|
1720
|
+
this.entityClass,
|
|
1721
|
+
this.fieldsToOmit
|
|
1722
|
+
);
|
|
1723
|
+
this.createDto = RenameClass(
|
|
1724
|
+
OmitTypeExclude(
|
|
1725
|
+
this.basicInputDto,
|
|
1726
|
+
getSpecificFields(this.entityClass, "notWritable")
|
|
1727
|
+
),
|
|
1728
|
+
`Create${this.entityClass.name}Dto`
|
|
1729
|
+
);
|
|
1730
|
+
this.importDto = ImportDataDto(this.createDto);
|
|
1731
|
+
this.findAllDto = RenameClass(
|
|
1732
|
+
PartialType(
|
|
1733
|
+
OmitTypeExclude(
|
|
1734
|
+
this.entityClass instanceof PageSettingsDto ? this.basicInputDto : IntersectionType(
|
|
1735
|
+
this.basicInputDto,
|
|
1736
|
+
PageSettingsDto
|
|
1737
|
+
),
|
|
1738
|
+
getSpecificFields(this.entityClass, "notQueryable")
|
|
1739
|
+
)
|
|
1740
|
+
),
|
|
1741
|
+
`Find${this.entityClass.name}Dto`
|
|
1742
|
+
);
|
|
1743
|
+
this.findAllCursorPaginatedDto = RenameClass(
|
|
1744
|
+
IntersectionType(
|
|
1745
|
+
OmitTypeExclude(this.findAllDto, ["pageCount"]),
|
|
1746
|
+
CursorPaginationDto
|
|
1747
|
+
),
|
|
1748
|
+
`Find${this.entityClass.name}CursorPaginatedDto`
|
|
1749
|
+
);
|
|
1750
|
+
this.updateDto = RenameClass(
|
|
1751
|
+
PartialType(
|
|
1752
|
+
OmitTypeExclude(
|
|
1753
|
+
this.createDto,
|
|
1754
|
+
getSpecificFields(this.entityClass, "notChangeable")
|
|
1755
|
+
)
|
|
1756
|
+
),
|
|
1757
|
+
`Update${this.entityClass.name}Dto`
|
|
1758
|
+
);
|
|
1759
|
+
this.entityResultDto = this.resolveEntityResultDto();
|
|
1760
|
+
this.entityCreateResultDto = RenameClass(
|
|
1761
|
+
OmitType2(this.entityResultDto, [
|
|
1762
|
+
...getTypeormRelations(this.entityClass).map(
|
|
1763
|
+
(r) => r.propertyName
|
|
1764
|
+
),
|
|
1765
|
+
...getSpecificFields(
|
|
1766
|
+
this.entityClass,
|
|
1767
|
+
"notColumn",
|
|
1768
|
+
(m) => !m.keepInCreate
|
|
1769
|
+
)
|
|
1770
|
+
]),
|
|
1771
|
+
`${this.getEntityClassName()}CreateResultDto`
|
|
1772
|
+
);
|
|
1773
|
+
this.entityReturnMessageDto = ReturnMessageDto2(this.entityResultDto);
|
|
1774
|
+
this.entityCreateReturnMessageDto = ReturnMessageDto2(
|
|
1775
|
+
this.entityCreateResultDto
|
|
1776
|
+
);
|
|
1777
|
+
this.entityArrayReturnMessageDto = PaginatedReturnMessageDto2(
|
|
1778
|
+
this.entityResultDto
|
|
1779
|
+
);
|
|
1780
|
+
this.entityCursorPaginationReturnMessageDto = CursorPaginationReturnMessageDto(this.entityResultDto);
|
|
1781
|
+
this.importReturnMessageDto = ReturnMessageDto2([
|
|
1782
|
+
ImportEntryDto(this.entityCreateResultDto)
|
|
1783
|
+
]);
|
|
1784
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
1785
|
+
this.idType = Reflect.getMetadata(
|
|
1786
|
+
"design:type",
|
|
1787
|
+
this.entityClass.prototype,
|
|
1788
|
+
"id"
|
|
1789
|
+
);
|
|
1790
|
+
if (options.relations) {
|
|
1791
|
+
filterRelations(entityClass, options.relations);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
getEntityClassName() {
|
|
1795
|
+
return this.options.entityClassName || this.entityClass.name;
|
|
1796
|
+
}
|
|
1797
|
+
resolveEntityResultDto() {
|
|
1798
|
+
const relations = getTypeormRelations(this.entityClass);
|
|
1799
|
+
const currentLevelRelations = this.options.relations && new Set(
|
|
1800
|
+
getCurrentLevelRelations(
|
|
1801
|
+
this.options.relations.map(extractRelationName)
|
|
1802
|
+
)
|
|
1803
|
+
);
|
|
1804
|
+
const outputFieldsToOmit = /* @__PURE__ */ new Set([
|
|
1805
|
+
...getNotInResultFields(
|
|
1806
|
+
this.entityClass,
|
|
1807
|
+
this.options.keepEntityVersioningDates
|
|
1808
|
+
),
|
|
1809
|
+
...this.options.outputFieldsToOmit || [],
|
|
1810
|
+
...this.options.relations ? relations.map((r) => r.propertyName).filter((r) => !currentLevelRelations.has(r)) : []
|
|
1811
|
+
]);
|
|
1812
|
+
const resultDto = OmitType2(this.entityClass, [...outputFieldsToOmit]);
|
|
1813
|
+
for (const relation of relations) {
|
|
1814
|
+
if (outputFieldsToOmit.has(relation.propertyName)) continue;
|
|
1815
|
+
const replace = (useClass) => {
|
|
1816
|
+
const oldApiProperty = Reflect.getMetadata(
|
|
1817
|
+
DECORATORS.API_MODEL_PROPERTIES,
|
|
1818
|
+
this.entityClass.prototype,
|
|
1819
|
+
relation.propertyName
|
|
1820
|
+
) || {};
|
|
1821
|
+
ApiProperty5({
|
|
1822
|
+
...oldApiProperty,
|
|
1823
|
+
required: false,
|
|
1824
|
+
type: () => relation.isArray ? [useClass[0]] : useClass[0]
|
|
1825
|
+
})(resultDto.prototype, relation.propertyName);
|
|
1826
|
+
};
|
|
1827
|
+
const existing = this.__resolveVisited.get(relation.propertyClass);
|
|
1828
|
+
if (existing) {
|
|
1829
|
+
replace(existing);
|
|
1830
|
+
} else {
|
|
1831
|
+
if (!this.__resolveVisited.has(this.entityClass) && !this.options.relations) {
|
|
1832
|
+
this.__resolveVisited.set(this.entityClass, [null]);
|
|
1833
|
+
}
|
|
1834
|
+
const relationFactory = new _RestfulFactory(
|
|
1835
|
+
relation.propertyClass,
|
|
1836
|
+
{
|
|
1837
|
+
entityClassName: `${this.getEntityClassName()}${this.options.relations ? upperFirst(relation.propertyName) : relation.propertyClass.name}`,
|
|
1838
|
+
relations: this.options.relations && getNextLevelRelations(
|
|
1839
|
+
this.options.relations.map(extractRelationName),
|
|
1840
|
+
relation.propertyName
|
|
1841
|
+
),
|
|
1842
|
+
keepEntityVersioningDates: this.options.keepEntityVersioningDates
|
|
1843
|
+
},
|
|
1844
|
+
this.__resolveVisited
|
|
1845
|
+
);
|
|
1846
|
+
const relationResultDto = relationFactory.entityResultDto;
|
|
1847
|
+
replace([relationResultDto]);
|
|
1848
|
+
if (!this.options.relations) {
|
|
1849
|
+
this.__resolveVisited.set(relation.propertyClass, [
|
|
1850
|
+
relationResultDto
|
|
1851
|
+
]);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
const res = RenameClass(
|
|
1856
|
+
resultDto,
|
|
1857
|
+
`${this.getEntityClassName()}ResultDto`
|
|
1858
|
+
);
|
|
1859
|
+
const currentContainer = this.__resolveVisited.get(this.entityClass);
|
|
1860
|
+
if (currentContainer) {
|
|
1861
|
+
currentContainer[0] = res;
|
|
1862
|
+
}
|
|
1863
|
+
return res;
|
|
1864
|
+
}
|
|
1865
|
+
usePrefix(methodDec, path) {
|
|
1866
|
+
if (path) {
|
|
1867
|
+
if (this.options.prefix) {
|
|
1868
|
+
return methodDec(`${this.options.prefix}/${path}`);
|
|
1869
|
+
} else {
|
|
1870
|
+
return methodDec(path);
|
|
1871
|
+
}
|
|
1872
|
+
} else {
|
|
1873
|
+
if (this.options.prefix) {
|
|
1874
|
+
return methodDec(this.options.prefix);
|
|
1875
|
+
} else {
|
|
1876
|
+
return methodDec();
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
create(extras = {}) {
|
|
1881
|
+
return MergeMethodDecorators([
|
|
1882
|
+
this.usePrefix(Post),
|
|
1883
|
+
HttpCode(200),
|
|
1884
|
+
ApiOperation({
|
|
1885
|
+
summary: `Create a new ${this.getEntityClassName()}`,
|
|
1886
|
+
...extras
|
|
1887
|
+
}),
|
|
1888
|
+
ApiBody({ type: this.createDto }),
|
|
1889
|
+
ApiOkResponse({ type: this.entityCreateReturnMessageDto }),
|
|
1890
|
+
ApiBadRequestResponse({
|
|
1891
|
+
type: BlankReturnMessageDto3,
|
|
1892
|
+
description: `The ${this.getEntityClassName()} is not valid`
|
|
1893
|
+
})
|
|
1894
|
+
]);
|
|
1895
|
+
}
|
|
1896
|
+
createParam() {
|
|
1897
|
+
return Body(CreatePipe());
|
|
1898
|
+
}
|
|
1899
|
+
findOne(extras = {}) {
|
|
1900
|
+
return MergeMethodDecorators([
|
|
1901
|
+
this.usePrefix(Get, ":id"),
|
|
1902
|
+
ApiOperation({
|
|
1903
|
+
summary: `Find a ${this.getEntityClassName()} by id`,
|
|
1904
|
+
...extras
|
|
1905
|
+
}),
|
|
1906
|
+
ApiParam({ name: "id", type: this.idType, required: true }),
|
|
1907
|
+
ApiOkResponse({ type: this.entityReturnMessageDto }),
|
|
1908
|
+
ApiNotFoundResponse({
|
|
1909
|
+
type: BlankReturnMessageDto3,
|
|
1910
|
+
description: `The ${this.getEntityClassName()} with the given id was not found`
|
|
1911
|
+
})
|
|
1912
|
+
]);
|
|
1913
|
+
}
|
|
1914
|
+
idParam() {
|
|
1915
|
+
if (this.idType === Number) {
|
|
1916
|
+
return Param("id", ParseIntPipe);
|
|
1917
|
+
} else {
|
|
1918
|
+
return Param("id");
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
findAll(extras = {}) {
|
|
1922
|
+
return MergeMethodDecorators([
|
|
1923
|
+
this.usePrefix(Get),
|
|
1924
|
+
ApiOperation({
|
|
1925
|
+
summary: `Find all ${this.getEntityClassName()}`,
|
|
1926
|
+
...extras
|
|
1927
|
+
}),
|
|
1928
|
+
ApiOkResponse({ type: this.entityArrayReturnMessageDto })
|
|
1929
|
+
]);
|
|
1930
|
+
}
|
|
1931
|
+
findAllCursorPaginated(extras = {}) {
|
|
1932
|
+
return MergeMethodDecorators([
|
|
1933
|
+
this.usePrefix(Get),
|
|
1934
|
+
ApiOperation({
|
|
1935
|
+
summary: `Find all ${this.getEntityClassName()}`,
|
|
1936
|
+
...extras
|
|
1937
|
+
}),
|
|
1938
|
+
ApiOkResponse({ type: this.entityCursorPaginationReturnMessageDto })
|
|
1939
|
+
]);
|
|
1940
|
+
}
|
|
1941
|
+
findAllParam() {
|
|
1942
|
+
return Query(GetPipe());
|
|
1943
|
+
}
|
|
1944
|
+
update(extras = {}) {
|
|
1945
|
+
return MergeMethodDecorators([
|
|
1946
|
+
this.usePrefix(Patch, ":id"),
|
|
1947
|
+
HttpCode(200),
|
|
1948
|
+
ApiOperation({
|
|
1949
|
+
summary: `Update a ${this.getEntityClassName()} by id`,
|
|
1950
|
+
...extras
|
|
1951
|
+
}),
|
|
1952
|
+
ApiParam({ name: "id", type: this.idType, required: true }),
|
|
1953
|
+
ApiBody({ type: this.updateDto }),
|
|
1954
|
+
ApiOkResponse({ type: BlankReturnMessageDto3 }),
|
|
1955
|
+
ApiNotFoundResponse({
|
|
1956
|
+
type: BlankReturnMessageDto3,
|
|
1957
|
+
description: `The ${this.getEntityClassName()} with the given id was not found`
|
|
1958
|
+
}),
|
|
1959
|
+
ApiBadRequestResponse({
|
|
1960
|
+
type: BlankReturnMessageDto3,
|
|
1961
|
+
description: `The ${this.getEntityClassName()} is not valid`
|
|
1962
|
+
}),
|
|
1963
|
+
ApiInternalServerErrorResponse({
|
|
1964
|
+
type: BlankReturnMessageDto3,
|
|
1965
|
+
description: "Internal error"
|
|
1966
|
+
})
|
|
1967
|
+
]);
|
|
1968
|
+
}
|
|
1969
|
+
updateParam() {
|
|
1970
|
+
return Body(UpdatePipe());
|
|
1971
|
+
}
|
|
1972
|
+
delete(extras = {}) {
|
|
1973
|
+
return MergeMethodDecorators([
|
|
1974
|
+
this.usePrefix(Delete, ":id"),
|
|
1975
|
+
HttpCode(200),
|
|
1976
|
+
ApiOperation({
|
|
1977
|
+
summary: `Delete a ${this.getEntityClassName()} by id`,
|
|
1978
|
+
...extras
|
|
1979
|
+
}),
|
|
1980
|
+
ApiParam({ name: "id", type: this.idType, required: true }),
|
|
1981
|
+
ApiOkResponse({ type: BlankReturnMessageDto3 }),
|
|
1982
|
+
ApiNotFoundResponse({
|
|
1983
|
+
type: BlankReturnMessageDto3,
|
|
1984
|
+
description: `The ${this.getEntityClassName()} with the given id was not found`
|
|
1985
|
+
}),
|
|
1986
|
+
ApiInternalServerErrorResponse({
|
|
1987
|
+
type: BlankReturnMessageDto3,
|
|
1988
|
+
description: "Internal error"
|
|
1989
|
+
})
|
|
1990
|
+
]);
|
|
1991
|
+
}
|
|
1992
|
+
import(extras = {}) {
|
|
1993
|
+
return MergeMethodDecorators([
|
|
1994
|
+
Post("import"),
|
|
1995
|
+
HttpCode(200),
|
|
1996
|
+
ApiOperation({
|
|
1997
|
+
summary: `Import ${this.getEntityClassName()}`,
|
|
1998
|
+
...extras
|
|
1999
|
+
}),
|
|
2000
|
+
ApiBody({ type: this.importDto }),
|
|
2001
|
+
ApiOkResponse({ type: this.importReturnMessageDto }),
|
|
2002
|
+
ApiInternalServerErrorResponse({
|
|
2003
|
+
type: BlankReturnMessageDto3,
|
|
2004
|
+
description: "Internal error"
|
|
2005
|
+
})
|
|
2006
|
+
]);
|
|
2007
|
+
}
|
|
2008
|
+
baseController(routeOptions = {}) {
|
|
2009
|
+
const _this = this;
|
|
2010
|
+
const cl = class SpecificRestfulController extends BaseRestfulController {
|
|
2011
|
+
constructor(service) {
|
|
2012
|
+
super(service, {
|
|
2013
|
+
paginateType: routeOptions.paginateType || "offset",
|
|
2014
|
+
relations: _this.options.relations,
|
|
2015
|
+
entityClass: _this.entityClass
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
const anyTrueWritten = RestfulMethods.some(
|
|
2020
|
+
(m) => routeOptions?.routes?.[m]?.enabled === true
|
|
2021
|
+
);
|
|
2022
|
+
const validMethods = RestfulMethods.filter((m) => {
|
|
2023
|
+
const value = routeOptions?.routes?.[m]?.enabled;
|
|
2024
|
+
if (value === false) return false;
|
|
2025
|
+
if (value === true) return true;
|
|
2026
|
+
return !anyTrueWritten || value === true;
|
|
2027
|
+
});
|
|
2028
|
+
const useDecorators = {
|
|
2029
|
+
findOne: {
|
|
2030
|
+
paramTypes: [this.idType],
|
|
2031
|
+
paramDecorators: () => [this.idParam()],
|
|
2032
|
+
methodDecorators: () => [this.findOne()]
|
|
2033
|
+
},
|
|
2034
|
+
findAll: {
|
|
2035
|
+
paramTypes: [
|
|
2036
|
+
routeOptions.paginateType === "cursor" ? this.findAllCursorPaginatedDto : routeOptions.paginateType === "none" ? OmitType2(this.findAllDto, [
|
|
2037
|
+
"pageCount",
|
|
2038
|
+
"recordsPerPage"
|
|
2039
|
+
]) : this.findAllDto
|
|
2040
|
+
],
|
|
2041
|
+
paramDecorators: () => [this.findAllParam()],
|
|
2042
|
+
methodDecorators: () => [
|
|
2043
|
+
routeOptions.paginateType === "cursor" ? this.findAllCursorPaginated() : this.findAll()
|
|
2044
|
+
]
|
|
2045
|
+
},
|
|
2046
|
+
create: {
|
|
2047
|
+
paramTypes: [this.createDto],
|
|
2048
|
+
paramDecorators: () => [this.createParam()],
|
|
2049
|
+
methodDecorators: () => [this.create()]
|
|
2050
|
+
},
|
|
2051
|
+
update: {
|
|
2052
|
+
paramTypes: [this.idType, this.updateDto],
|
|
2053
|
+
paramDecorators: () => [this.idParam(), this.updateParam()],
|
|
2054
|
+
methodDecorators: () => [this.update()]
|
|
2055
|
+
},
|
|
2056
|
+
delete: {
|
|
2057
|
+
paramTypes: [this.idType],
|
|
2058
|
+
paramDecorators: () => [this.idParam()],
|
|
2059
|
+
methodDecorators: () => [this.delete()]
|
|
2060
|
+
},
|
|
2061
|
+
import: {
|
|
2062
|
+
paramTypes: [this.importDto],
|
|
2063
|
+
paramDecorators: () => [this.createParam()],
|
|
2064
|
+
methodDecorators: () => [this.import()]
|
|
2065
|
+
}
|
|
2066
|
+
};
|
|
2067
|
+
for (const method of validMethods) {
|
|
2068
|
+
const methodImpl = function namedMethodImpl(...args) {
|
|
2069
|
+
return BaseRestfulController.prototype[method].apply(this, args);
|
|
2070
|
+
};
|
|
2071
|
+
Object.defineProperty(methodImpl, "name", {
|
|
2072
|
+
value: method,
|
|
2073
|
+
configurable: true
|
|
2074
|
+
});
|
|
2075
|
+
cl.prototype[method] = methodImpl;
|
|
2076
|
+
const paramDecorators = useDecorators[method].paramDecorators();
|
|
2077
|
+
const paramTypes = useDecorators[method].paramTypes;
|
|
2078
|
+
const methodDecorators = [
|
|
2079
|
+
...useDecorators[method].methodDecorators(),
|
|
2080
|
+
...routeOptions?.routes?.[method]?.methodDecorators || [],
|
|
2081
|
+
...routeOptions?.globalMethodDecorators || []
|
|
2082
|
+
];
|
|
2083
|
+
paramDecorators.forEach((paramDecorator, index) => {
|
|
2084
|
+
paramDecorator(cl.prototype, method, index);
|
|
2085
|
+
});
|
|
2086
|
+
Reflect.defineMetadata(
|
|
2087
|
+
"design:paramtypes",
|
|
2088
|
+
paramTypes,
|
|
2089
|
+
cl.prototype,
|
|
2090
|
+
method
|
|
2091
|
+
);
|
|
2092
|
+
const baseDescriptor = Object.getOwnPropertyDescriptor(
|
|
2093
|
+
BaseRestfulController.prototype,
|
|
2094
|
+
method
|
|
2095
|
+
);
|
|
2096
|
+
if (baseDescriptor) {
|
|
2097
|
+
Reflect.defineMetadata(
|
|
2098
|
+
"design:type",
|
|
2099
|
+
baseDescriptor.value,
|
|
2100
|
+
cl.prototype,
|
|
2101
|
+
method
|
|
2102
|
+
);
|
|
2103
|
+
Reflect.defineMetadata(
|
|
2104
|
+
"design:returntype",
|
|
2105
|
+
Promise,
|
|
2106
|
+
cl.prototype,
|
|
2107
|
+
method
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
2110
|
+
methodDecorators.forEach((methodDecorator) => {
|
|
2111
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
2112
|
+
cl.prototype,
|
|
2113
|
+
method
|
|
2114
|
+
);
|
|
2115
|
+
methodDecorator(cl.prototype, method, descriptor);
|
|
2116
|
+
Object.defineProperty(cl.prototype, method, descriptor);
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
return RenameClass(cl, `${this.getEntityClassName()}Controller`);
|
|
2120
|
+
}
|
|
2121
|
+
crudService(options = {}) {
|
|
2122
|
+
return CrudService(this.entityClass, {
|
|
2123
|
+
relations: this.options.relations,
|
|
2124
|
+
...options
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
};
|
|
2128
|
+
export {
|
|
2129
|
+
BlankCursorPaginationReturnMessageDto,
|
|
2130
|
+
BoolColumn,
|
|
2131
|
+
CreatePipe,
|
|
2132
|
+
CrudBase,
|
|
2133
|
+
CrudService,
|
|
2134
|
+
CursorPaginationDto,
|
|
2135
|
+
CursorPaginationReturnMessageDto,
|
|
2136
|
+
DateColumn,
|
|
2137
|
+
EnumColumn,
|
|
2138
|
+
FloatColumn,
|
|
2139
|
+
GenericCursorPaginationReturnMessageDto,
|
|
2140
|
+
GetPipe,
|
|
2141
|
+
IdBase,
|
|
2142
|
+
ImportDataBaseDto,
|
|
2143
|
+
ImportDataDto,
|
|
2144
|
+
ImportEntryBaseDto,
|
|
2145
|
+
ImportEntryDto,
|
|
2146
|
+
Inner,
|
|
2147
|
+
IntColumn,
|
|
2148
|
+
JsonColumn,
|
|
2149
|
+
NotChangeable,
|
|
2150
|
+
NotColumn,
|
|
2151
|
+
NotInResult,
|
|
2152
|
+
NotQueryable,
|
|
2153
|
+
NotWritable,
|
|
2154
|
+
PageSettingsDto,
|
|
2155
|
+
QueryColumn,
|
|
2156
|
+
QueryCondition,
|
|
2157
|
+
QueryEqual,
|
|
2158
|
+
QueryEqualZeroNullable,
|
|
2159
|
+
QueryFullText,
|
|
2160
|
+
QueryGreater,
|
|
2161
|
+
QueryGreaterEqual,
|
|
2162
|
+
QueryLess,
|
|
2163
|
+
QueryLessEqual,
|
|
2164
|
+
QueryLike,
|
|
2165
|
+
QueryMatchBoolean,
|
|
2166
|
+
QueryNotEqual,
|
|
2167
|
+
QueryOperator,
|
|
2168
|
+
QuerySearch,
|
|
2169
|
+
Relation,
|
|
2170
|
+
RelationComputed,
|
|
2171
|
+
RestfulFactory,
|
|
2172
|
+
StringColumn,
|
|
2173
|
+
StringIdBase,
|
|
2174
|
+
TimeBase,
|
|
2175
|
+
UpdatePipe,
|
|
2176
|
+
applyQueryMatchBoolean,
|
|
2177
|
+
applyQueryProperty,
|
|
2178
|
+
applyQueryPropertyLike,
|
|
2179
|
+
applyQueryPropertySearch,
|
|
2180
|
+
applyQueryPropertyZeroNullable,
|
|
2181
|
+
createQueryCondition,
|
|
2182
|
+
createQueryOperator
|
|
2183
|
+
};
|
|
2184
|
+
//# sourceMappingURL=index.mjs.map
|