@simitgroup/simpleapp-generator 2.0.2-b-alpha → 2.0.2-d-alpha
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/ReleaseNote.md +11 -0
- package/package.json +1 -1
- package/templates/basic/nest/controller.ts.eta +2 -2
- package/templates/basic/nuxt/resource-bridge.service.ts.eta +14 -6
- package/templates/nest/src/app.controller.ts.eta +10 -10
- package/templates/nest/src/app.module.ts._eta +1 -0
- package/templates/nest/src/simple-app/_core/features/auth/role-guard/roles.guard.ts.eta +5 -18
- package/templates/nest/src/simple-app/_core/features/document-no-format/document-no-format.service.ts.eta +14 -7
- package/templates/nest/src/simple-app/_core/features/log/log.service.ts.eta +10 -5
- package/templates/nest/src/simple-app/_core/features/mini-app/mini-app-scope/mini-app-scope.guard.ts.eta +4 -0
- package/templates/nest/src/simple-app/_core/features/profile/profile.service.ts.eta +15 -11
- package/templates/nest/src/simple-app/_core/features/queue/queue-base/queue-base.consumer.ts.eta +14 -13
- package/templates/nest/src/simple-app/_core/features/user-context/user.context.ts.eta +54 -3
- package/templates/nest/src/simple-app/_core/framework/base/simple-app.controller.ts.eta +16 -3
- package/templates/nest/src/simple-app/_core/framework/base/simple-app.service.ts.eta +401 -198
- package/templates/nest/src/simple-app/_core/framework/framework.module.ts.eta +3 -2
- package/templates/nest/src/simple-app/_core/framework/schemas/simple-app.schema.ts.eta +6 -0
- package/templates/nest/src/simple-app/_core/framework/simple-app-db-revert.service.ts.eta +101 -0
- package/templates/nest/src/simple-app/_core/framework/simple-app.interceptor.ts.eta +33 -12
- package/templates/nuxt/app.vue.eta +2 -2
- package/templates/nuxt/plugins/18.simpleapp-custom-field-store.ts.eta +2 -2
- package/templates/nuxt/types/others.ts.eta +4 -0
|
@@ -22,8 +22,9 @@ import { SimpleAppDocumentNoFormatService } from '../../features/document-no-for
|
|
|
22
22
|
import { SimpleAppLogService } from '../../features/log/log.service';
|
|
23
23
|
import { UserContext } from '../../features/user-context/user.context';
|
|
24
24
|
import { RunWebhookService } from '../../features/webhook/run-webhook.service';
|
|
25
|
-
import { DeleteResultType, IsolationType, MoreProjectionType, PatchManyRequest, SchemaFields, TextSearchBody, UniqueKeyExistResponse } from '../schemas';
|
|
25
|
+
import { DeleteResultType, IsolationType, MoreProjectionType, PatchManyRequest, SchemaFields, SearchBody, TextSearchBody, UniqueKeyExistResponse } from '../schemas';
|
|
26
26
|
import { SearchWithRelation } from './dto/simple-app-search-with-relation.dto';
|
|
27
|
+
import { Response } from 'express';
|
|
27
28
|
|
|
28
29
|
@Injectable()
|
|
29
30
|
export class SimpleAppService<T extends SchemaFields> {
|
|
@@ -108,29 +109,29 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
108
109
|
return false;
|
|
109
110
|
}
|
|
110
111
|
reCalculateValue(data: T) {}
|
|
111
|
-
getIsolationFilter = (
|
|
112
|
+
getIsolationFilter = (appUser: UserContext) => {
|
|
112
113
|
let isolationFilter = {};
|
|
113
114
|
switch (this.isolationtype) {
|
|
114
115
|
case 'none':
|
|
115
116
|
isolationFilter = {};
|
|
116
117
|
break;
|
|
117
118
|
case 'branch':
|
|
118
|
-
isolationFilter =
|
|
119
|
+
isolationFilter = appUser.getBranchFilter();
|
|
119
120
|
break;
|
|
120
121
|
case 'tenant':
|
|
121
|
-
isolationFilter =
|
|
122
|
+
isolationFilter = appUser.getTenantFilter();
|
|
122
123
|
break;
|
|
123
124
|
case 'org':
|
|
124
125
|
default:
|
|
125
|
-
isolationFilter =
|
|
126
|
+
isolationFilter = appUser.getOrgFilter();
|
|
126
127
|
break;
|
|
127
128
|
}
|
|
128
129
|
return isolationFilter;
|
|
129
130
|
};
|
|
130
|
-
async list(
|
|
131
|
+
async list(appUser: UserContext) {
|
|
131
132
|
try {
|
|
132
133
|
//console.log("this.isolationFilter",this.getIsolationFilter())
|
|
133
|
-
const products = await this.doc.find(this.getIsolationFilter(
|
|
134
|
+
const products = await this.doc.find(this.getIsolationFilter(appUser));
|
|
134
135
|
// console.log(products)
|
|
135
136
|
const productlist = products.map((p: T) => {
|
|
136
137
|
return p;
|
|
@@ -150,7 +151,7 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
150
151
|
}
|
|
151
152
|
// console.log(this.moreAutoCompleteField);
|
|
152
153
|
};
|
|
153
|
-
async getAutoComplete(
|
|
154
|
+
async getAutoComplete(appUser: UserContext, keyword: string, data?: T) {
|
|
154
155
|
try {
|
|
155
156
|
const filter1: any = {};
|
|
156
157
|
const filter2: any = {};
|
|
@@ -164,7 +165,7 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
164
165
|
|
|
165
166
|
const filterobj = { $or: filters };
|
|
166
167
|
Object.assign(filterobj, data);
|
|
167
|
-
Object.assign(filterobj, this.getIsolationFilter(
|
|
168
|
+
Object.assign(filterobj, this.getIsolationFilter(appUser));
|
|
168
169
|
const projections = {
|
|
169
170
|
label: `\$${this.documentIdentityLabel}`,
|
|
170
171
|
code: `\$${this.documentIdentityCode}`,
|
|
@@ -187,18 +188,18 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
187
188
|
|
|
188
189
|
/**
|
|
189
190
|
* Special search function which can by pass data isolation. reserved and try not use
|
|
190
|
-
* @param
|
|
191
|
+
* @param appUser
|
|
191
192
|
* @param filters
|
|
192
193
|
* @returns
|
|
193
194
|
*/
|
|
194
|
-
async searchNoIsolation(
|
|
195
|
+
async searchNoIsolation(appUser: UserContext, filters: FilterQuery<T>) {
|
|
195
196
|
try {
|
|
196
|
-
//if (this.hooks.beforeSearch) await this.hooks.beforeSearch(
|
|
197
|
+
//if (this.hooks.beforeSearch) await this.hooks.beforeSearch(appUser, filters);
|
|
197
198
|
const products = await this.doc.find(filters);
|
|
198
199
|
const productlist = products.map((p) => {
|
|
199
200
|
return p;
|
|
200
201
|
});
|
|
201
|
-
//if (this.hooks.afterSearch) await this.hooks.afterSearch(
|
|
202
|
+
//if (this.hooks.afterSearch) await this.hooks.afterSearch(appUser, productlist);
|
|
202
203
|
|
|
203
204
|
// console.log(products);
|
|
204
205
|
return productlist;
|
|
@@ -207,13 +208,13 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
207
208
|
}
|
|
208
209
|
// return this;
|
|
209
210
|
}
|
|
210
|
-
async aggregateNoIsolation(
|
|
211
|
+
async aggregateNoIsolation(appUser: UserContext, pipeline: PipelineStage[]) {
|
|
211
212
|
if (pipeline[0] && pipeline[0]['$match']) {
|
|
212
213
|
try {
|
|
213
214
|
this.logger.verbose(pipeline, `${this.documentName} aggregate:`);
|
|
214
215
|
|
|
215
216
|
return await this.doc.aggregate(pipeline, {
|
|
216
|
-
session:
|
|
217
|
+
session: appUser.getDBSession(),
|
|
217
218
|
});
|
|
218
219
|
} catch (err) {
|
|
219
220
|
throw new InternalServerErrorException(err);
|
|
@@ -222,23 +223,23 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
222
223
|
throw new InternalServerErrorException('first aggregate pipelinestage shall use $match');
|
|
223
224
|
}
|
|
224
225
|
}
|
|
225
|
-
async aggregate<T = any>(
|
|
226
|
+
async aggregate<T = any>(appUser: UserContext, pipeline: PipelineStage[]) {
|
|
226
227
|
if (pipeline[0] && pipeline[0]['$match']) {
|
|
227
228
|
try {
|
|
228
|
-
const isolationFilter = { ...this.getIsolationFilter(
|
|
229
|
+
const isolationFilter = { ...this.getIsolationFilter(appUser) };
|
|
229
230
|
this.polishIsolationFilter(isolationFilter);
|
|
230
231
|
|
|
231
232
|
Object.assign(pipeline[0]['$match'], isolationFilter);
|
|
232
233
|
this.logger.verbose(pipeline, `${this.documentName} aggregate:`);
|
|
233
234
|
|
|
234
|
-
// if(
|
|
235
|
+
// if(appUser.getId() == ''){
|
|
235
236
|
// console.log(JSON.stringify(pipeline))
|
|
236
237
|
// }
|
|
237
238
|
const res = await this.doc.aggregate<T>(pipeline, {
|
|
238
|
-
session:
|
|
239
|
+
session: appUser.getDBSession(),
|
|
239
240
|
});
|
|
240
241
|
|
|
241
|
-
// if(
|
|
242
|
+
// if(appUser.getId() == ''){
|
|
242
243
|
// console.log(res)
|
|
243
244
|
// }
|
|
244
245
|
return res;
|
|
@@ -249,32 +250,47 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
249
250
|
throw new InternalServerErrorException('first aggregate pipelinestage shall use $match');
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
|
-
async search(
|
|
253
|
+
async search(
|
|
254
|
+
appUser: UserContext,
|
|
255
|
+
filters: FilterQuery<T>,
|
|
256
|
+
projection: string[] = undefined,
|
|
257
|
+
sort: any = undefined,
|
|
258
|
+
lookup: { [key: string]: string } = undefined,
|
|
259
|
+
pagination?: { pageSize?: number; pageNo?: number },
|
|
260
|
+
) {
|
|
253
261
|
try {
|
|
254
|
-
|
|
262
|
+
|
|
263
|
+
const isolationFilter = { ...this.getIsolationFilter(appUser) };
|
|
255
264
|
this.polishIsolationFilter(isolationFilter);
|
|
256
265
|
|
|
257
266
|
// console.log("initial search",filters)
|
|
258
267
|
const newfilters: FilterQuery<T> = { ...filters, ...isolationFilter };
|
|
259
268
|
|
|
260
|
-
// if (this.hooks.beforeSearch) await this.hooks.beforeSearch(
|
|
269
|
+
// if (this.hooks.beforeSearch) await this.hooks.beforeSearch(appUser, newfilters);
|
|
261
270
|
// console.log("before _find",newfilters)
|
|
262
271
|
// console.log("this.doc",this.doc)
|
|
263
272
|
let searchResults: T[] = [];
|
|
264
273
|
if (lookup === undefined) {
|
|
265
274
|
this.logger.debug('after search', newfilters);
|
|
266
|
-
|
|
275
|
+
let query = this.doc.find(newfilters, projection, { session: appUser.getDBSession() }).sort(sort);
|
|
276
|
+
|
|
277
|
+
if (pagination) {
|
|
278
|
+
const { pageSize, pageNo, skip } = this.buildPagination(pagination);
|
|
279
|
+
query = query.skip(skip).limit(pageSize);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
searchResults = await query.exec();
|
|
267
283
|
} else {
|
|
268
|
-
const pipelines = this.searchToAggregate(
|
|
284
|
+
const pipelines = this.searchToAggregate(appUser, newfilters, projection, sort, lookup, pagination);
|
|
269
285
|
this.logger.debug('after aggregate', pipelines);
|
|
270
|
-
searchResults = await this.aggregate(
|
|
286
|
+
searchResults = await this.aggregate(appUser, pipelines);
|
|
271
287
|
}
|
|
272
288
|
|
|
273
289
|
const list: T[] = searchResults.map((p: T) => {
|
|
274
290
|
return p;
|
|
275
291
|
});
|
|
276
292
|
// console.log("after map",productlist)
|
|
277
|
-
// if (this.hooks.afterSearch) await this.hooks.afterSearch(
|
|
293
|
+
// if (this.hooks.afterSearch) await this.hooks.afterSearch(appUser, list);
|
|
278
294
|
|
|
279
295
|
// console.log(products);
|
|
280
296
|
return list;
|
|
@@ -284,26 +300,176 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
284
300
|
// return this;
|
|
285
301
|
}
|
|
286
302
|
|
|
287
|
-
|
|
303
|
+
/**
|
|
304
|
+
* Search with pagination that returns page info (pageCount, pageSize, pageNo, total) along with items.
|
|
305
|
+
* For aggregation queries (with lookup), uses $facet to get both metadata and data in a single query.
|
|
306
|
+
*/
|
|
307
|
+
async searchWithPageInfo(
|
|
308
|
+
appUser: UserContext,
|
|
309
|
+
filters: FilterQuery<T> = {},
|
|
310
|
+
projection: string[] = undefined,
|
|
311
|
+
sort: any = undefined,
|
|
312
|
+
lookup: { [key: string]: string } = undefined,
|
|
313
|
+
pagination?: { pageSize?: number; pageNo?: number },
|
|
314
|
+
) {
|
|
315
|
+
try {
|
|
316
|
+
const isolationFilter = { ...this.getIsolationFilter(appUser) };
|
|
317
|
+
this.polishIsolationFilter(isolationFilter);
|
|
318
|
+
|
|
319
|
+
const newfilters: FilterQuery<T> = { ...(filters as FilterQuery<T>), ...isolationFilter };
|
|
320
|
+
|
|
321
|
+
let pageSize: number;
|
|
322
|
+
let pageNo: number;
|
|
323
|
+
let skip: number;
|
|
324
|
+
|
|
325
|
+
if (pagination !== undefined) {
|
|
326
|
+
const paginationResult = this.buildPagination(pagination);
|
|
327
|
+
pageSize = paginationResult.pageSize;
|
|
328
|
+
pageNo = paginationResult.pageNo;
|
|
329
|
+
skip = paginationResult.skip;
|
|
330
|
+
} else {
|
|
331
|
+
pageSize = 10000;
|
|
332
|
+
pageNo = 0;
|
|
333
|
+
skip = 0;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
let items: T[] = [];
|
|
337
|
+
let total = 0;
|
|
338
|
+
|
|
339
|
+
if (lookup === undefined) {
|
|
340
|
+
this.logger.debug('after searchWithPageInfo', newfilters);
|
|
341
|
+
const baseQuery = this.doc.find(newfilters, projection, { session: appUser.getDBSession() }).sort(sort);
|
|
342
|
+
const query = baseQuery.skip(skip).limit(pageSize);
|
|
343
|
+
|
|
344
|
+
const [rows, count] = await Promise.all([
|
|
345
|
+
query.exec(),
|
|
346
|
+
this.doc.countDocuments(newfilters).session(appUser.getDBSession()),
|
|
347
|
+
]);
|
|
348
|
+
items = rows;
|
|
349
|
+
total = count;
|
|
350
|
+
} else {
|
|
351
|
+
const pipelines = this.searchToAggregate(
|
|
352
|
+
appUser,
|
|
353
|
+
newfilters,
|
|
354
|
+
projection as any,
|
|
355
|
+
sort as any,
|
|
356
|
+
lookup as any,
|
|
357
|
+
{ pageSize, pageNo },
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
const countPipeline: PipelineStage[] = [
|
|
361
|
+
{ $match: newfilters },
|
|
362
|
+
];
|
|
363
|
+
const collections = Object.keys(lookup);
|
|
364
|
+
collections.forEach((tokey: string) => {
|
|
365
|
+
const toarr = tokey.split('.');
|
|
366
|
+
const to = toarr[0];
|
|
367
|
+
toarr.splice(0, 1);
|
|
368
|
+
const foreignField = toarr.join('.') ?? '_id';
|
|
369
|
+
countPipeline.push({
|
|
370
|
+
$lookup: {
|
|
371
|
+
from: to,
|
|
372
|
+
as: '_' + to,
|
|
373
|
+
localField: lookup[tokey],
|
|
374
|
+
foreignField: foreignField,
|
|
375
|
+
pipeline: [{ $match: { tenantId: appUser.getTenantId() } }],
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
countPipeline.push({ $count: 'total' });
|
|
380
|
+
|
|
381
|
+
this.logger.debug('after aggregate searchWithPageInfo', pipelines);
|
|
382
|
+
|
|
383
|
+
const [rows, countResult] = await Promise.all([
|
|
384
|
+
this.aggregate(appUser, pipelines),
|
|
385
|
+
this.aggregate(appUser, countPipeline),
|
|
386
|
+
]);
|
|
387
|
+
|
|
388
|
+
items = rows as T[];
|
|
389
|
+
total = countResult && countResult.length > 0 ? (countResult[0]['total'] || 0) : 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return items;
|
|
393
|
+
} catch (err) {
|
|
394
|
+
throw new BadRequestException(err.message);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async getTotalCount(appUser: UserContext, filters: FilterQuery<T>) {
|
|
399
|
+
try {
|
|
400
|
+
const isolationFilter = { ...this.getIsolationFilter(appUser) };
|
|
401
|
+
this.polishIsolationFilter(isolationFilter);
|
|
402
|
+
const newfilters: FilterQuery<T> = { ...filters, ...isolationFilter };
|
|
403
|
+
return await this.doc.countDocuments(newfilters, { session: appUser.getDBSession() });
|
|
404
|
+
} catch (err) {
|
|
405
|
+
this.logger.error(err, 'getTotalCount error');
|
|
406
|
+
return 0;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
setPaginationHeaders(res: Response, total?: number, pagination?: { pageSize?: number; pageNo?: number }) {
|
|
411
|
+
if (!res || typeof res.setHeader !== 'function') return;
|
|
412
|
+
|
|
413
|
+
if (total !== undefined && total !== null && pagination) {
|
|
414
|
+
const { pageSize, pageNo } = this.buildPagination(pagination);
|
|
415
|
+
const pageCount = pageSize > 0 && total > 0 ? Math.ceil(total / pageSize) : (total > 0 ? 1 : 0);
|
|
416
|
+
res.setHeader('x-page-count', pageCount);
|
|
417
|
+
res.setHeader('x-page-size', pageSize);
|
|
418
|
+
res.setHeader('x-page-no', pageNo);
|
|
419
|
+
res.setHeader('x-total-count', total);
|
|
420
|
+
} else {
|
|
421
|
+
res.setHeader('x-page-count', 0);
|
|
422
|
+
res.setHeader('x-page-size', 0);
|
|
423
|
+
res.setHeader('x-page-no', 0);
|
|
424
|
+
res.setHeader('x-total-count', 0);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
buildPagination(pagination?: { pageSize?: number; pageNo?: number }) {
|
|
429
|
+
let finalPageSize: number;
|
|
430
|
+
if (pagination && typeof pagination.pageSize === 'number' && pagination.pageSize > 0) {
|
|
431
|
+
finalPageSize = pagination.pageSize;
|
|
432
|
+
} else if (pagination !== undefined) {
|
|
433
|
+
finalPageSize = 10000;
|
|
434
|
+
} else {
|
|
435
|
+
finalPageSize = this.LIMITPERPAGE;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const rawPageNo = Number.isFinite(pagination?.pageNo) ? pagination.pageNo : 0;
|
|
439
|
+
const pageNo = rawPageNo < 0 ? 0 : rawPageNo;
|
|
440
|
+
const skip = pageNo * finalPageSize;
|
|
441
|
+
|
|
442
|
+
this.logger.debug('buildPagination result:', {
|
|
443
|
+
input: pagination,
|
|
444
|
+
finalPageSize,
|
|
445
|
+
pageNo,
|
|
446
|
+
skip
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
return { pageSize: finalPageSize, skip, pageNo };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
async fullTextSearch(appUser: UserContext, body: TextSearchBody) {
|
|
288
455
|
try {
|
|
289
456
|
//{filters:string[],fields:string[]}
|
|
290
|
-
const isolationFilter = { ...this.getIsolationFilter(
|
|
457
|
+
const isolationFilter = { ...this.getIsolationFilter(appUser) };
|
|
291
458
|
this.polishIsolationFilter(isolationFilter);
|
|
292
459
|
const keyword = body.keyword;
|
|
293
460
|
// console.log("bodybody",body)
|
|
294
461
|
// Prepare regex filter
|
|
295
462
|
const orConditions: any[] = [];
|
|
296
|
-
const regex = new RegExp(keyword, 'i')
|
|
463
|
+
const regex = new RegExp(keyword, 'i');
|
|
297
464
|
|
|
298
465
|
if (body.filters && body.filters.length > 0) {
|
|
299
|
-
body.filters.forEach((field: string) => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
466
|
+
body.filters.forEach((field: string) => {
|
|
467
|
+
orConditions.push({
|
|
468
|
+
[field]: { $regex: regex },
|
|
469
|
+
});
|
|
303
470
|
});
|
|
304
471
|
}
|
|
305
472
|
|
|
306
|
-
|
|
307
473
|
const filter = {
|
|
308
474
|
$or: orConditions,
|
|
309
475
|
};
|
|
@@ -326,11 +492,11 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
326
492
|
throw new Error('Failed to perform search');
|
|
327
493
|
}
|
|
328
494
|
}
|
|
329
|
-
async findById(
|
|
330
|
-
// if (this.hooks.beforeFetchRecord) await this.hooks.beforeFetchRecord(
|
|
495
|
+
async findById(appUser: UserContext, id: string) {
|
|
496
|
+
// if (this.hooks.beforeFetchRecord) await this.hooks.beforeFetchRecord(appUser, id);
|
|
331
497
|
|
|
332
|
-
const data = await this.search(
|
|
333
|
-
// if (this.hooks.afterFetchRecord) await this.hooks.afterFetchRecord(
|
|
498
|
+
const data = await this.search(appUser, { _id: id as any });
|
|
499
|
+
// if (this.hooks.afterFetchRecord) await this.hooks.afterFetchRecord(appUser, data[0]);
|
|
334
500
|
|
|
335
501
|
if (data.length == 1) {
|
|
336
502
|
// console.log('data0', data[0]);
|
|
@@ -340,11 +506,11 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
340
506
|
}
|
|
341
507
|
}
|
|
342
508
|
|
|
343
|
-
async findByIdNoIsolation(
|
|
344
|
-
// if (this.hooks.beforeFetchRecord) await this.hooks.beforeFetchRecord(
|
|
509
|
+
async findByIdNoIsolation(appUser: UserContext, id: string) {
|
|
510
|
+
// if (this.hooks.beforeFetchRecord) await this.hooks.beforeFetchRecord(appUser, id);
|
|
345
511
|
|
|
346
|
-
const data = await this.searchNoIsolation(
|
|
347
|
-
// if (this.hooks.afterFetchRecord) await this.hooks.afterFetchRecord(
|
|
512
|
+
const data = await this.searchNoIsolation(appUser, { _id: id as any });
|
|
513
|
+
// if (this.hooks.afterFetchRecord) await this.hooks.afterFetchRecord(appUser, data[0]);
|
|
348
514
|
|
|
349
515
|
if (data.length == 1) {
|
|
350
516
|
// console.log('data0', data[0]);
|
|
@@ -356,31 +522,35 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
356
522
|
|
|
357
523
|
/**
|
|
358
524
|
* create many from array, for performance reason it submit all item in 1 go, so it won't implement hooks
|
|
359
|
-
* @param
|
|
525
|
+
* @param appUser
|
|
360
526
|
* @param datas
|
|
361
527
|
*/
|
|
362
|
-
async createMany(
|
|
528
|
+
async createMany(appUser: UserContext, datas: T[]) {
|
|
363
529
|
if (Array.isArray(datas)) {
|
|
364
530
|
for (let i = 0; i < datas.length; i++) {
|
|
365
531
|
const data = datas[i];
|
|
366
|
-
let isolationFilter: any = { ...
|
|
532
|
+
let isolationFilter: any = { ...appUser.getCreateFilter() };
|
|
367
533
|
isolationFilter = this.polishIsolationFilter(isolationFilter, data);
|
|
368
534
|
|
|
369
535
|
Object.assign(data, isolationFilter);
|
|
370
536
|
this.reCalculateValue(data);
|
|
371
|
-
await this.validateData(
|
|
372
|
-
this.applyNestedDateTime(
|
|
537
|
+
await this.validateData(appUser, data);
|
|
538
|
+
this.applyNestedDateTime(appUser, data, 'create');
|
|
373
539
|
}
|
|
374
|
-
const dbsession =
|
|
375
|
-
if (
|
|
376
|
-
|
|
540
|
+
const dbsession = appUser.getDBSession();
|
|
541
|
+
if (!appUser.inTransaction()) {
|
|
542
|
+
appUser.startTransaction();
|
|
377
543
|
}
|
|
378
544
|
|
|
379
545
|
try {
|
|
380
546
|
const result = await this.doc.insertMany(datas, { session: dbsession });
|
|
381
|
-
|
|
547
|
+
const allIds = datas.map(item=>item._id)
|
|
548
|
+
const allLogData = datas.map(item=>null)
|
|
549
|
+
appUser.addTransactionStep('createMany',this.documentName,allIds,allLogData);
|
|
550
|
+
|
|
551
|
+
await this.addManyAuditEvents(appUser, this.documentName, 'createMany', datas);
|
|
382
552
|
for (const data of datas) {
|
|
383
|
-
|
|
553
|
+
appUser.addInsertedRecordId(this.documentName, data._id);
|
|
384
554
|
}
|
|
385
555
|
|
|
386
556
|
return result;
|
|
@@ -392,23 +562,23 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
392
562
|
}
|
|
393
563
|
}
|
|
394
564
|
|
|
395
|
-
async create(
|
|
565
|
+
async create(appUser: UserContext, data: T, noStartTransaction: boolean = false) {
|
|
396
566
|
let result;
|
|
397
567
|
|
|
398
568
|
if (!data._id) {
|
|
399
569
|
data._id = crypto.randomUUID();
|
|
400
570
|
}
|
|
401
|
-
const dbsession =
|
|
402
|
-
if (
|
|
403
|
-
|
|
571
|
+
const dbsession = appUser.getDBSession();
|
|
572
|
+
if (!appUser.inTransaction() && !noStartTransaction) {
|
|
573
|
+
appUser.startTransaction();
|
|
404
574
|
}
|
|
405
575
|
|
|
406
576
|
this.logger.debug('this.withDocNumberFormat :' + this.withDocNumberFormat + ' && ' + '!data[this.documentIdentityCode] ==' + !data[this.documentIdentityCode]);
|
|
407
577
|
if (this.withDocNumberFormat && !data[this.documentIdentityCode]) {
|
|
408
|
-
await this.genNewDocNo(
|
|
578
|
+
await this.genNewDocNo(appUser, data);
|
|
409
579
|
}
|
|
410
580
|
|
|
411
|
-
let isolationFilter: any = { ...
|
|
581
|
+
let isolationFilter: any = { ...appUser.getCreateFilter() };
|
|
412
582
|
isolationFilter = this.polishIsolationFilter(isolationFilter, data);
|
|
413
583
|
|
|
414
584
|
this.logger.debug('isolationFilter', 'SimpleAppService');
|
|
@@ -417,20 +587,21 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
417
587
|
this.logger.debug(data, 'SimpleAppService');
|
|
418
588
|
Object.assign(data, isolationFilter);
|
|
419
589
|
this.reCalculateValue(data);
|
|
420
|
-
await this.validateData(
|
|
590
|
+
await this.validateData(appUser, data);
|
|
421
591
|
this.logger.debug(data, `after create validation`);
|
|
422
|
-
this.applyNestedDateTime(
|
|
592
|
+
this.applyNestedDateTime(appUser, data, 'create');
|
|
423
593
|
|
|
424
594
|
//new way of hook
|
|
425
|
-
await this.runEvent(
|
|
595
|
+
await this.runEvent(appUser, this.setHookName('beforeCreate'), { data: data }, false);
|
|
426
596
|
|
|
427
597
|
this.logger.debug(data, `Create Record ${this.documentName}`);
|
|
428
598
|
const newdoc = new this.doc(data);
|
|
429
|
-
await this.identifyForeignKeys(
|
|
599
|
+
await this.identifyForeignKeys(appUser, data);
|
|
430
600
|
try {
|
|
431
601
|
result = await newdoc.save({ session: dbsession });
|
|
432
|
-
|
|
433
|
-
|
|
602
|
+
appUser.addTransactionStep('create',this.documentName,[data._id],[null]);
|
|
603
|
+
await this.addAuditEvent(appUser, this.documentName, result._id, 'create', data);
|
|
604
|
+
appUser.addInsertedRecordId(this.documentName, result._id);
|
|
434
605
|
} catch (err) {
|
|
435
606
|
this.logger.error(err);
|
|
436
607
|
throw err;
|
|
@@ -438,36 +609,36 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
438
609
|
|
|
439
610
|
try {
|
|
440
611
|
//new way of hook
|
|
441
|
-
await this.runEvent(
|
|
612
|
+
await this.runEvent(appUser, this.setHookName('afterCreate'), { data: result }, false);
|
|
442
613
|
|
|
443
|
-
await this.callWebhook(
|
|
614
|
+
await this.callWebhook(appUser, 'create', result);
|
|
444
615
|
return result as T;
|
|
445
616
|
} catch (err) {
|
|
446
617
|
throw new InternalServerErrorException(`createHook ${this.documentType} error: ${JSON.stringify(err)}`, `${this.documentType} createHook error`);
|
|
447
618
|
}
|
|
448
619
|
}
|
|
449
620
|
|
|
450
|
-
applyNestedDateTime = (
|
|
621
|
+
applyNestedDateTime = (appUser: UserContext, data: any, transtype: string) => {
|
|
451
622
|
const props = Object.getOwnPropertyNames(data);
|
|
452
623
|
for (let i = 0; i < props.length; i++) {
|
|
453
624
|
const key = props[i];
|
|
454
625
|
//need to apply nested
|
|
455
626
|
if (Array.isArray(data[key]) && data[key].length > 0 && typeof data[key][0] == 'object') {
|
|
456
627
|
for (let j = 0; j < data[key].length; j++) {
|
|
457
|
-
this.applyNestedDateTime(
|
|
628
|
+
this.applyNestedDateTime(appUser, data[key][j], transtype);
|
|
458
629
|
}
|
|
459
630
|
} else if (key == 'created') {
|
|
460
631
|
data['created'] = transtype == 'create' || !data['created'] ? new Date().toISOString() : data['created'];
|
|
461
632
|
} else if (key == 'createdBy') {
|
|
462
|
-
data['createdBy'] = transtype == 'create' || !data['createdBy'] ?
|
|
633
|
+
data['createdBy'] = transtype == 'create' || !data['createdBy'] ? appUser.getUid() : data['createdBy'];
|
|
463
634
|
} else if (key == 'updated') {
|
|
464
635
|
data['updated'] = new Date().toISOString();
|
|
465
636
|
} else if (key == 'updatedBy') {
|
|
466
|
-
data['updatedBy'] =
|
|
637
|
+
data['updatedBy'] = appUser.getUid();
|
|
467
638
|
}
|
|
468
639
|
}
|
|
469
640
|
};
|
|
470
|
-
async validateData(
|
|
641
|
+
async validateData(appUser: UserContext, data: T, _id?: string) {
|
|
471
642
|
// console.log('validatedata');
|
|
472
643
|
const ajv = new Ajv({ allErrors: true, useDefaults: true });
|
|
473
644
|
addFormats(ajv);
|
|
@@ -487,9 +658,9 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
487
658
|
this.logger.debug('run hook during validation');
|
|
488
659
|
let issuccess = true;
|
|
489
660
|
// if (this.hooks.beforeValidation) {
|
|
490
|
-
// issuccess = await this.hooks.beforeValidation(
|
|
661
|
+
// issuccess = await this.hooks.beforeValidation(appUser, data, _id);
|
|
491
662
|
// }
|
|
492
|
-
// const issuccess = await this.hook(
|
|
663
|
+
// const issuccess = await this.hook(appUser, HookType.beforeValidation, data);
|
|
493
664
|
if (!issuccess) {
|
|
494
665
|
const errormsg: string[] = [];
|
|
495
666
|
for (let i = 0; i < this.errorlist.length; i++) {
|
|
@@ -541,35 +712,37 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
541
712
|
}
|
|
542
713
|
return filterIsolation;
|
|
543
714
|
};
|
|
544
|
-
async findIdThenDelete(
|
|
545
|
-
const deletedata = await this.findById(
|
|
715
|
+
async findIdThenDelete(appUser: UserContext, id: string): Promise<any> {
|
|
716
|
+
const deletedata = await this.findById(appUser, id);
|
|
546
717
|
if (!deletedata) {
|
|
547
718
|
throw new NotFoundException(`${id} not found`, 'not found');
|
|
548
719
|
}
|
|
549
|
-
const dbsession =
|
|
550
|
-
if (
|
|
551
|
-
|
|
720
|
+
const dbsession = appUser.getDBSession();
|
|
721
|
+
if (!appUser.inTransaction()) {
|
|
722
|
+
appUser.startTransaction();
|
|
552
723
|
}
|
|
553
724
|
|
|
554
725
|
let dependency;
|
|
555
726
|
try {
|
|
556
727
|
//new way of hook
|
|
557
|
-
await this.runEvent(
|
|
728
|
+
await this.runEvent(appUser, this.setHookName('beforeDelete'), { id: id, deleteData: deletedata }, false);
|
|
558
729
|
|
|
559
|
-
// if (this.hooks.beforeDelete) await this.hooks.beforeDelete(
|
|
730
|
+
// if (this.hooks.beforeDelete) await this.hooks.beforeDelete(appUser, id, deletedata);
|
|
560
731
|
this.logger.debug('delete record', this.documentName, id);
|
|
561
|
-
dependency = await this.getRelatedRecords(
|
|
732
|
+
dependency = await this.getRelatedRecords(appUser, id);
|
|
562
733
|
if (!dependency) {
|
|
563
|
-
const filterIsolation = this.getIsolationFilter(
|
|
734
|
+
const filterIsolation = this.getIsolationFilter(appUser);
|
|
564
735
|
this.polishIsolationFilter(filterIsolation);
|
|
565
736
|
|
|
566
737
|
filterIsolation['_id'] = id;
|
|
567
738
|
this.logger.debug('delete filter', filterIsolation);
|
|
568
739
|
const result = await this.doc.deleteOne(filterIsolation).session(dbsession);
|
|
740
|
+
appUser.addTransactionStep('delete',this.documentName,[id],[deletedata]);
|
|
741
|
+
|
|
569
742
|
|
|
570
|
-
await this.addAuditEvent(
|
|
743
|
+
await this.addAuditEvent(appUser, this.documentName, id, 'delete', deletedata);
|
|
571
744
|
|
|
572
|
-
|
|
745
|
+
appUser.addDeletedRecordId(this.documentName, id);
|
|
573
746
|
const deleteresult: DeleteResultType<T> = {
|
|
574
747
|
result: result,
|
|
575
748
|
data: deletedata,
|
|
@@ -578,9 +751,9 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
578
751
|
// this.doc.findByIdAndDelete(id)
|
|
579
752
|
|
|
580
753
|
//new way of hook
|
|
581
|
-
await this.runEvent(
|
|
754
|
+
await this.runEvent(appUser, this.setHookName('afterDelete'), { id: id, deleteData: deletedata }, false);
|
|
582
755
|
|
|
583
|
-
await this.callWebhook(
|
|
756
|
+
await this.callWebhook(appUser, 'delete', deletedata);
|
|
584
757
|
return deleteresult;
|
|
585
758
|
} else {
|
|
586
759
|
this.logger.debug('reject query', dependency);
|
|
@@ -596,14 +769,14 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
596
769
|
}
|
|
597
770
|
}
|
|
598
771
|
|
|
599
|
-
// updateOne = async (
|
|
772
|
+
// updateOne = async (appUser:UserContext,data: T) => {
|
|
600
773
|
// this.doc.updateOne(data);
|
|
601
774
|
// };
|
|
602
775
|
|
|
603
|
-
findIdThenUnsetField = async (
|
|
776
|
+
findIdThenUnsetField = async (appUser: UserContext, id: string, data: AnyKeys<T> & AnyObject, noStartTransaction: boolean = false) => {
|
|
604
777
|
try {
|
|
605
778
|
// Check is record exist
|
|
606
|
-
const existingdata = await this.findById(
|
|
779
|
+
const existingdata = await this.findById(appUser, id);
|
|
607
780
|
if (!existingdata) throw new NotFoundException(`${this.documentName} findIdThenUpdate: _id:${id} not found`, 'not found');
|
|
608
781
|
|
|
609
782
|
this.logger.debug('update id: ' + id, this.documentName + ' findIdThenRemoveField');
|
|
@@ -613,18 +786,18 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
613
786
|
|
|
614
787
|
// Prepare update audit data
|
|
615
788
|
const auditData = {
|
|
616
|
-
...
|
|
789
|
+
...appUser.getUpdateFilter(),
|
|
617
790
|
__v: existingdata.__v + 1,
|
|
618
791
|
};
|
|
619
792
|
|
|
620
793
|
// Start transaction
|
|
621
|
-
const dbsession =
|
|
622
|
-
if (
|
|
623
|
-
|
|
794
|
+
const dbsession = appUser.getDBSession();
|
|
795
|
+
if (!appUser.inTransaction() && !noStartTransaction) {
|
|
796
|
+
appUser.startTransaction();
|
|
624
797
|
}
|
|
625
798
|
|
|
626
799
|
// Isolation filter
|
|
627
|
-
const isolationFilter = { ...this.getIsolationFilter(
|
|
800
|
+
const isolationFilter = { ...this.getIsolationFilter(appUser) };
|
|
628
801
|
this.polishIsolationFilter(isolationFilter);
|
|
629
802
|
isolationFilter['_id'] = id;
|
|
630
803
|
|
|
@@ -644,9 +817,9 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
644
817
|
);
|
|
645
818
|
|
|
646
819
|
// Add audit event
|
|
647
|
-
await this.addAuditEvent(
|
|
820
|
+
await this.addAuditEvent(appUser, this.documentName, id, 'update', data);
|
|
648
821
|
|
|
649
|
-
|
|
822
|
+
appUser.addUpdatedRecordId(this.documentName, data._id);
|
|
650
823
|
|
|
651
824
|
return result;
|
|
652
825
|
} catch (err) {
|
|
@@ -655,10 +828,10 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
655
828
|
}
|
|
656
829
|
};
|
|
657
830
|
|
|
658
|
-
findIdThenUpdate = async (
|
|
831
|
+
findIdThenUpdate = async (appUser: UserContext, id: string, data: T, noStartTransaction: boolean = false) => {
|
|
659
832
|
try {
|
|
660
833
|
//version exists, need ensure different only 1
|
|
661
|
-
const existingdata = await this.findById(
|
|
834
|
+
const existingdata = await this.findById(appUser, id);
|
|
662
835
|
if (!existingdata) throw new NotFoundException(`${this.documentName} findIdThenUpdate: _id:${id} not found`, 'not found');
|
|
663
836
|
|
|
664
837
|
this.logger.debug('update id:' + id, this.documentName + ' findIdThenUpdate');
|
|
@@ -672,53 +845,55 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
672
845
|
// }
|
|
673
846
|
// this.logger.debug('warn2');
|
|
674
847
|
|
|
675
|
-
await this.identifyForeignKeys(
|
|
848
|
+
await this.identifyForeignKeys(appUser, data);
|
|
676
849
|
|
|
677
850
|
//new way of hook
|
|
678
|
-
await this.runEvent(
|
|
851
|
+
await this.runEvent(appUser, this.setHookName('beforeUpdate'), { id: id, prevData: existingdata, newData: data }, false);
|
|
679
852
|
|
|
680
|
-
// if (this.hooks.beforeUpdate) await this.hooks.beforeUpdate(
|
|
853
|
+
// if (this.hooks.beforeUpdate) await this.hooks.beforeUpdate(appUser, id, existingdata, data);
|
|
681
854
|
|
|
682
|
-
const dbsession =
|
|
683
|
-
if (
|
|
684
|
-
|
|
855
|
+
const dbsession = appUser.getDBSession();
|
|
856
|
+
if (!appUser.inTransaction() && !noStartTransaction) {
|
|
857
|
+
appUser.startTransaction();
|
|
685
858
|
}
|
|
686
859
|
// try {
|
|
687
|
-
Object.assign(data,
|
|
860
|
+
Object.assign(data, appUser.getUpdateFilter());
|
|
688
861
|
// Object.assign(existingdata, data);
|
|
689
862
|
|
|
690
863
|
delete data['_id'];
|
|
691
864
|
this.reCalculateValue(data);
|
|
692
865
|
|
|
693
866
|
// existingdata['_id']=''
|
|
694
|
-
await this.validateData(
|
|
867
|
+
await this.validateData(appUser, data, id);
|
|
695
868
|
|
|
696
|
-
const isolationFilter = { ...this.getIsolationFilter(
|
|
869
|
+
const isolationFilter = { ...this.getIsolationFilter(appUser) };
|
|
697
870
|
this.polishIsolationFilter(isolationFilter);
|
|
698
871
|
isolationFilter['_id'] = id;
|
|
699
|
-
this.applyNestedDateTime(
|
|
872
|
+
this.applyNestedDateTime(appUser, data, 'update');
|
|
700
873
|
|
|
701
874
|
const result = await this.doc.findOneAndReplace(isolationFilter, data, {
|
|
702
875
|
session: dbsession,
|
|
703
876
|
new: true,
|
|
704
877
|
});
|
|
705
|
-
await this.addAuditEvent(appuser, this.documentName, id, 'update', data);
|
|
706
878
|
|
|
707
|
-
|
|
708
|
-
await this.
|
|
879
|
+
appUser.addTransactionStep('update',this.documentName,[id],[existingdata]);
|
|
880
|
+
await this.addAuditEvent(appUser, this.documentName, id, 'update', data);
|
|
709
881
|
|
|
710
|
-
|
|
711
|
-
|
|
882
|
+
appUser.addUpdatedRecordId(this.documentName, data._id);
|
|
883
|
+
await this.runEvent(appUser, this.setHookName('afterUpdate'), { id: id, prevData: existingdata, newData: data }, false);
|
|
884
|
+
|
|
885
|
+
await this.callWebhook(appUser, 'update', result);
|
|
886
|
+
return result; // await this.findById(appUser, id);
|
|
712
887
|
} catch (err) {
|
|
713
888
|
this.logger.error(err);
|
|
714
889
|
throw new InternalServerErrorException(err.message);
|
|
715
890
|
}
|
|
716
891
|
};
|
|
717
892
|
|
|
718
|
-
findIdThenUpdateNoIsolation = async (
|
|
893
|
+
findIdThenUpdateNoIsolation = async (appUser: UserContext, id: string, data: T) => {
|
|
719
894
|
try {
|
|
720
895
|
//version exists, need ensure different only 1
|
|
721
|
-
const existingdata = await this.findByIdNoIsolation(
|
|
896
|
+
const existingdata = await this.findByIdNoIsolation(appUser, id);
|
|
722
897
|
if (!existingdata) throw new NotFoundException(`${this.documentName} findIdThenUpdate: _id:${id} not found`, 'not found');
|
|
723
898
|
|
|
724
899
|
this.logger.debug('update id:' + id, this.documentName + ' findIdThenUpdate');
|
|
@@ -732,51 +907,53 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
732
907
|
// }
|
|
733
908
|
// this.logger.debug('warn2');
|
|
734
909
|
|
|
735
|
-
await this.identifyForeignKeys(
|
|
910
|
+
await this.identifyForeignKeys(appUser, data);
|
|
736
911
|
|
|
737
|
-
await this.runEvent(
|
|
912
|
+
await this.runEvent(appUser, this.setHookName('beforeUpdate'), { data: data }, false);
|
|
738
913
|
|
|
739
|
-
// if (this.hooks.beforeUpdate) await this.hooks.beforeUpdate(
|
|
914
|
+
// if (this.hooks.beforeUpdate) await this.hooks.beforeUpdate(appUser, id, existingdata, data);
|
|
740
915
|
|
|
741
|
-
const dbsession =
|
|
742
|
-
if (
|
|
743
|
-
|
|
916
|
+
const dbsession = appUser.getDBSession();
|
|
917
|
+
if (!appUser.inTransaction()) {
|
|
918
|
+
appUser.startTransaction();
|
|
744
919
|
}
|
|
745
920
|
// try {
|
|
746
|
-
// Object.assign(data,
|
|
921
|
+
// Object.assign(data, appUser.getUpdateFilter());
|
|
747
922
|
// Object.assign(existingdata, data);
|
|
748
923
|
|
|
749
924
|
delete data['_id'];
|
|
750
925
|
this.reCalculateValue(data);
|
|
751
926
|
|
|
752
927
|
// existingdata['_id']=''
|
|
753
|
-
await this.validateData(
|
|
928
|
+
await this.validateData(appUser, data, id);
|
|
754
929
|
|
|
755
930
|
const isolationFilter = {};
|
|
756
931
|
// this.polishIsolationFilter(isolationFilter);
|
|
757
932
|
isolationFilter['_id'] = id;
|
|
758
|
-
this.applyNestedDateTime(
|
|
933
|
+
this.applyNestedDateTime(appUser, data, 'update');
|
|
759
934
|
|
|
760
935
|
const result = await this.doc.findOneAndReplace(isolationFilter, data, {
|
|
761
936
|
session: dbsession,
|
|
762
937
|
new: true,
|
|
763
938
|
});
|
|
764
|
-
await this.addAuditEvent(appuser, this.documentName, id, 'update', data);
|
|
765
939
|
|
|
766
|
-
|
|
767
|
-
|
|
940
|
+
appUser.addTransactionStep('update',this.documentName,[id],[existingdata]);
|
|
941
|
+
await this.addAuditEvent(appUser, this.documentName, id, 'update', data);
|
|
942
|
+
|
|
943
|
+
await this.runEvent(appUser, this.setHookName('afterUpdate'), { id: id, prevData: existingdata, newData: result }, false);
|
|
944
|
+
appUser.addUpdatedRecordId(this.documentName, data._id);
|
|
768
945
|
|
|
769
|
-
await this.callWebhook(
|
|
770
|
-
return result; // await this.findById(
|
|
946
|
+
await this.callWebhook(appUser, 'update', result);
|
|
947
|
+
return result; // await this.findById(appUser, id);
|
|
771
948
|
} catch (err) {
|
|
772
949
|
this.logger.error(err);
|
|
773
950
|
throw new InternalServerErrorException(err.message);
|
|
774
951
|
}
|
|
775
952
|
};
|
|
776
953
|
|
|
777
|
-
findIdThenPatch = async (
|
|
954
|
+
findIdThenPatch = async (appUser: UserContext, id: string, data: Partial<T>, session: mongo.ClientSession = undefined, skipLog: boolean = false) => {
|
|
778
955
|
try {
|
|
779
|
-
const existingdata = await this.findById(
|
|
956
|
+
const existingdata = await this.findById(appUser, id);
|
|
780
957
|
if (!existingdata) {
|
|
781
958
|
throw new NotFoundException(`${id} not found`, 'not found');
|
|
782
959
|
}
|
|
@@ -789,17 +966,17 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
789
966
|
}
|
|
790
967
|
data.__v = existingdata.__v + 1;
|
|
791
968
|
|
|
792
|
-
await this.identifyForeignKeys(
|
|
969
|
+
await this.identifyForeignKeys(appUser, data);
|
|
793
970
|
|
|
794
971
|
//patch not suitable trigger afterupdate
|
|
795
|
-
// if (this.hooks.beforeUpdate) await this.hooks.beforeUpdate(
|
|
796
|
-
await this.runEvent(
|
|
797
|
-
const dbsession =
|
|
798
|
-
if (
|
|
799
|
-
|
|
972
|
+
// if (this.hooks.beforeUpdate) await this.hooks.beforeUpdate(appUser, id, existingdata, data);
|
|
973
|
+
await this.runEvent(appUser, this.setHookName('beforePatch'), { id: id, patchData: data, prevData: existingdata }, false);
|
|
974
|
+
const dbsession = appUser.getDBSession();
|
|
975
|
+
if (!appUser.inTransaction()) {
|
|
976
|
+
appUser.startTransaction();
|
|
800
977
|
}
|
|
801
978
|
// try {
|
|
802
|
-
Object.assign(data,
|
|
979
|
+
Object.assign(data, appUser.getUpdateFilter());
|
|
803
980
|
delete data['_id'];
|
|
804
981
|
|
|
805
982
|
//patch not suitable trigger afterupdate
|
|
@@ -808,26 +985,27 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
808
985
|
// existingdata['_id']=''
|
|
809
986
|
// console.log("newdata",data)
|
|
810
987
|
//path record no validation
|
|
811
|
-
// await this.validateData(
|
|
988
|
+
// await this.validateData(appUser, data);
|
|
812
989
|
|
|
813
|
-
const isolationFilter = { ...this.getIsolationFilter(
|
|
990
|
+
const isolationFilter = { ...this.getIsolationFilter(appUser) };
|
|
814
991
|
this.polishIsolationFilter(isolationFilter);
|
|
815
992
|
|
|
816
993
|
isolationFilter['_id'] = id;
|
|
817
|
-
this.applyNestedDateTime(
|
|
994
|
+
this.applyNestedDateTime(appUser, data, 'update');
|
|
818
995
|
// console.log('findid patch ', data);
|
|
819
996
|
|
|
820
997
|
const result = await this.doc.findOneAndUpdate(isolationFilter, data, {
|
|
821
998
|
session: dbsession,
|
|
822
999
|
new: true,
|
|
823
1000
|
});
|
|
1001
|
+
appUser.addTransactionStep('update',this.documentName,[id],[existingdata]);
|
|
824
1002
|
//skip audit trail, useful when want to patch x-foreignkey code,label
|
|
825
1003
|
if (!skipLog) {
|
|
826
|
-
await this.addAuditEvent(
|
|
1004
|
+
await this.addAuditEvent(appUser, this.documentName, id, 'patch', data);
|
|
827
1005
|
}
|
|
828
|
-
|
|
829
|
-
await this.runEvent(
|
|
830
|
-
return result; //await this.findById(
|
|
1006
|
+
appUser.addUpdatedRecordId(this.documentName, data._id);
|
|
1007
|
+
await this.runEvent(appUser, this.setHookName('afterPatch'), { id: id, patchData: data, prevData: existingdata }, false);
|
|
1008
|
+
return result; //await this.findById(appUser, id);
|
|
831
1009
|
} catch (err) {
|
|
832
1010
|
this.logger.error(err.message, 'findIdThenPath error');
|
|
833
1011
|
console.error(err);
|
|
@@ -836,12 +1014,19 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
836
1014
|
};
|
|
837
1015
|
async deleteMany(appUser: UserContext, filter: FilterQuery<T>) {
|
|
838
1016
|
const dbsession = appUser.getDBSession();
|
|
839
|
-
if (
|
|
840
|
-
|
|
1017
|
+
if (!appUser.inTransaction()) {
|
|
1018
|
+
appUser.startTransaction();
|
|
841
1019
|
}
|
|
842
1020
|
const searchResult = await this.search(appUser, filter, []);
|
|
843
1021
|
const ids = searchResult.map((row) => row._id);
|
|
1022
|
+
|
|
1023
|
+
// const result = await this.doc.insertMany(datas, { session: dbsession });
|
|
1024
|
+
// const allIds = datas.map(item=>item._id)
|
|
1025
|
+
// const allLogData = datas.map(item=>null)
|
|
1026
|
+
|
|
1027
|
+
|
|
844
1028
|
const result = await this.doc.deleteMany({ _id: { $in: ids } }, { session: dbsession });
|
|
1029
|
+
appUser.addTransactionStep('deleteMany',this.documentName,ids,searchResult);
|
|
845
1030
|
// console.log(`Delete ${this.documentName} ids`, ids, result);
|
|
846
1031
|
for (const id of ids) {
|
|
847
1032
|
appUser.addDeletedRecordId(this.documentName, id);
|
|
@@ -849,7 +1034,7 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
849
1034
|
return result;
|
|
850
1035
|
//updateMany(isolationFilter, { $set: patch }, { session: dbsession });
|
|
851
1036
|
}
|
|
852
|
-
async patchMany<T>(
|
|
1037
|
+
async patchMany<T>(appUser: UserContext, data: PatchManyRequest<T>) {
|
|
853
1038
|
// filter = {
|
|
854
1039
|
// _id: '7eb2661a-6ea6-406e-b868-2e8b19c4658b',
|
|
855
1040
|
// 'tuitionClass._id': '5aa69cee-f651-45f4-bad8-0f52a3fb92b5',
|
|
@@ -858,26 +1043,30 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
858
1043
|
const filter = data.filter;
|
|
859
1044
|
const patch = data.data as Object;
|
|
860
1045
|
|
|
861
|
-
const isolationFilter = { ...this.getIsolationFilter(
|
|
1046
|
+
const isolationFilter = { ...this.getIsolationFilter(appUser), ...(filter || {}) };
|
|
862
1047
|
this.polishIsolationFilter(isolationFilter);
|
|
863
|
-
this.applyNestedDateTime(
|
|
1048
|
+
this.applyNestedDateTime(appUser, patch, 'update');
|
|
864
1049
|
|
|
865
1050
|
// Get DB Session
|
|
866
|
-
const dbsession =
|
|
867
|
-
if (
|
|
868
|
-
|
|
1051
|
+
const dbsession = appUser.getDBSession();
|
|
1052
|
+
if (!appUser.inTransaction()) {
|
|
1053
|
+
appUser.startTransaction();
|
|
869
1054
|
}
|
|
870
1055
|
|
|
1056
|
+
const searchResult = await this.search(appUser, filter, []);
|
|
1057
|
+
const ids = searchResult.map((row) => row._id);
|
|
1058
|
+
|
|
871
1059
|
const result = await this.doc.updateMany(isolationFilter, { $set: patch }, { session: dbsession });
|
|
872
|
-
|
|
1060
|
+
appUser.addTransactionStep('updateMany',this.documentName,ids,searchResult);
|
|
1061
|
+
await this.addManyAuditEvents(appUser, this.documentName, 'patchMany', [patch]);
|
|
873
1062
|
|
|
874
1063
|
return result;
|
|
875
1064
|
}
|
|
876
1065
|
|
|
877
1066
|
//find what foreign key constraint
|
|
878
|
-
async getRelatedRecords(
|
|
1067
|
+
async getRelatedRecords(appUser: UserContext, id: string) {
|
|
879
1068
|
this.logger.debug('get foreignkey for delete:', id);
|
|
880
|
-
// console.log('session modifeds',
|
|
1069
|
+
// console.log('session modifeds', appUser.getModifieds());
|
|
881
1070
|
if (foreignkeys === undefined) {
|
|
882
1071
|
this.logger.error('foreignkeys object undetected');
|
|
883
1072
|
throw new InternalServerErrorException('foreignkeys object undetected');
|
|
@@ -903,11 +1092,11 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
903
1092
|
const filter = {};
|
|
904
1093
|
filter[fkey] = id;
|
|
905
1094
|
const result: any = await collection.findOne(filter, {
|
|
906
|
-
session:
|
|
1095
|
+
session: appUser.getDBSession(),
|
|
907
1096
|
});
|
|
908
1097
|
if (result) {
|
|
909
1098
|
//record deleted but not yet commit, safely ignore
|
|
910
|
-
if (
|
|
1099
|
+
if (appUser.searchDeletedRecordId(collectionname, result._id)) continue;
|
|
911
1100
|
|
|
912
1101
|
this.logger.error(result, `related result found in ${collectionname} ${fkey} = ${id}`);
|
|
913
1102
|
return result;
|
|
@@ -932,7 +1121,7 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
932
1121
|
* @param docstatus
|
|
933
1122
|
* @returns Promise
|
|
934
1123
|
*/
|
|
935
|
-
async setDocumentStatus(
|
|
1124
|
+
async setDocumentStatus(appUser: UserContext, id: string, data: T, docstatus: string) {
|
|
936
1125
|
id = id.trim();
|
|
937
1126
|
|
|
938
1127
|
if (!id) {
|
|
@@ -941,45 +1130,45 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
941
1130
|
if (data['_id'] && data['_id'] != id) {
|
|
942
1131
|
throw new BadRequestException(`_id in data(${data['_id']} different with path param ${id})`, 'set documentstatus id not match with submited data');
|
|
943
1132
|
}
|
|
944
|
-
const existdata = await this.findById(
|
|
1133
|
+
const existdata = await this.findById(appUser, id);
|
|
945
1134
|
if (existdata && existdata['documentStatus'] == docstatus) {
|
|
946
1135
|
throw new BadRequestException(`Same document status "${docstatus}" is not allowed`);
|
|
947
1136
|
}
|
|
948
1137
|
data['documentStatus'] = docstatus;
|
|
949
|
-
// await this.hook(
|
|
950
|
-
await this.runEvent(
|
|
1138
|
+
// await this.hook(appUser, HookType.beforeSetStatus, data);
|
|
1139
|
+
await this.runEvent(appUser, this.setHookName('beforeSetStatus'), { docStatus: docstatus, prevData: existdata, newData: data }, false);
|
|
951
1140
|
|
|
952
|
-
// if (this.hooks.beforeSetStatus) await this.hooks.beforeSetStatus(
|
|
1141
|
+
// if (this.hooks.beforeSetStatus) await this.hooks.beforeSetStatus(appUser, docstatus, data, existdata);
|
|
953
1142
|
|
|
954
1143
|
if (data && !data['created']) {
|
|
955
|
-
const createresult = await this.create(
|
|
956
|
-
await this.runEvent(
|
|
957
|
-
// if (this.hooks.afterSetStatus) await this.hooks.afterSetStatus(
|
|
958
|
-
await this.addAuditEvent(
|
|
1144
|
+
const createresult = await this.create(appUser, data);
|
|
1145
|
+
await this.runEvent(appUser, this.setHookName('afterSetStatus'), { docStatus: docstatus, data: createresult }, false);
|
|
1146
|
+
// if (this.hooks.afterSetStatus) await this.hooks.afterSetStatus(appUser, docstatus, createresult);
|
|
1147
|
+
await this.addAuditEvent(appUser, this.documentName, id, docstatus, data);
|
|
959
1148
|
|
|
960
1149
|
return createresult;
|
|
961
1150
|
} else {
|
|
962
|
-
const updateresult = await this.findIdThenPatch(
|
|
963
|
-
const finaldata = await this.findById(
|
|
964
|
-
await this.runEvent(
|
|
965
|
-
await this.addAuditEvent(
|
|
1151
|
+
const updateresult = await this.findIdThenPatch(appUser, id, data);
|
|
1152
|
+
const finaldata = await this.findById(appUser, id);
|
|
1153
|
+
await this.runEvent(appUser, this.setHookName('afterSetStatus'), { docStatus: docstatus, data: finaldata }, false);
|
|
1154
|
+
await this.addAuditEvent(appUser, this.documentName, id, docstatus, data);
|
|
966
1155
|
|
|
967
|
-
await this.callWebhook(
|
|
1156
|
+
await this.callWebhook(appUser, docstatus, finaldata);
|
|
968
1157
|
return updateresult;
|
|
969
1158
|
}
|
|
970
1159
|
}
|
|
971
1160
|
|
|
972
1161
|
/**
|
|
973
1162
|
* similar like runEvent, but it is syncronizely add event into queue and return.
|
|
974
|
-
* No result will return from this method, it also lose
|
|
1163
|
+
* No result will return from this method, it also lose appUser db's transaction
|
|
975
1164
|
* due out of request flow
|
|
976
1165
|
*
|
|
977
|
-
* @param {UserContext}
|
|
1166
|
+
* @param {UserContext} appUser The appUser
|
|
978
1167
|
* @param {string} eventName The event name
|
|
979
1168
|
* @param {any} data The data
|
|
980
1169
|
*/
|
|
981
|
-
runBackgroundWorker(
|
|
982
|
-
this.eventEmitter.emit(eventName,
|
|
1170
|
+
runBackgroundWorker(appUser: UserContext, eventName: string, payloads: any) {
|
|
1171
|
+
this.eventEmitter.emit(eventName, appUser, payloads);
|
|
983
1172
|
}
|
|
984
1173
|
|
|
985
1174
|
/**
|
|
@@ -990,21 +1179,21 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
990
1179
|
* complex task service class may pass to worker class.
|
|
991
1180
|
* 2. it not cause circulate injection hell, which is useful in complex dependency
|
|
992
1181
|
* this run foreground event which can async/await to obtain execution result,
|
|
993
|
-
* the
|
|
1182
|
+
* the appUser dbtransaction remain usable. however, the execution may delay response
|
|
994
1183
|
*
|
|
995
1184
|
*
|
|
996
|
-
* @param {UserContext}
|
|
1185
|
+
* @param {UserContext} appUser The appUser
|
|
997
1186
|
* @param {string} eventName The event name
|
|
998
1187
|
* @param {any} data The data
|
|
999
1188
|
* @return {Promise} { description_of_the_return_value }
|
|
1000
1189
|
*/
|
|
1001
|
-
async runEvent(
|
|
1190
|
+
async runEvent(appUser: UserContext, eventName: string, payloads: any, enforce: boolean = true) {
|
|
1002
1191
|
try {
|
|
1003
1192
|
if (enforce && !this.eventEmitter.hasListeners(eventName)) {
|
|
1004
1193
|
throw new InternalServerErrorException(`${eventName} seems no listener`);
|
|
1005
1194
|
} else if (this.eventEmitter.hasListeners(eventName)) {
|
|
1006
1195
|
console.log('run eventName', eventName);
|
|
1007
|
-
const res = await this.eventEmitter.emitAsync(eventName,
|
|
1196
|
+
const res = await this.eventEmitter.emitAsync(eventName, appUser, payloads);
|
|
1008
1197
|
|
|
1009
1198
|
if (!res) {
|
|
1010
1199
|
throw new InternalServerErrorException(`${eventName} is invalid worker`);
|
|
@@ -1019,24 +1208,24 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
1019
1208
|
throw e;
|
|
1020
1209
|
}
|
|
1021
1210
|
}
|
|
1022
|
-
// startWorkflow(
|
|
1023
|
-
// return this.eventEmitter.emit('workflow.start',
|
|
1211
|
+
// startWorkflow(appUser: UserContext, processName: WorkflowName, workflowData: any) {
|
|
1212
|
+
// return this.eventEmitter.emit('workflow.start', appUser, processName, workflowData);
|
|
1024
1213
|
// }
|
|
1025
1214
|
|
|
1026
|
-
async genNewDocNo(
|
|
1215
|
+
async genNewDocNo(appUser: UserContext, data: T) {
|
|
1027
1216
|
this.logger.debug('genNewDocNo');
|
|
1028
1217
|
// console.log('before genNewDocNo');
|
|
1029
|
-
const result = await this.docnogenerator.generateNextNumberFromDocument(
|
|
1218
|
+
const result = await this.docnogenerator.generateNextNumberFromDocument(appUser, this.documentType, data);
|
|
1030
1219
|
// console.log('after genNewDocNo');
|
|
1031
1220
|
this.logger.debug(result, 'genNewDocNo');
|
|
1032
1221
|
|
|
1033
1222
|
// been for to convert become object
|
|
1034
1223
|
(data as any)[this.documentIdentityCode] = result;
|
|
1035
1224
|
}
|
|
1036
|
-
async runDefault(
|
|
1225
|
+
async runDefault(appUser: UserContext): Promise<unknown> {
|
|
1037
1226
|
return 'Hello this is ' + this.getDocumentType() + ': ' + this.getDocumentName();
|
|
1038
1227
|
}
|
|
1039
|
-
async identifyForeignKeys(
|
|
1228
|
+
async identifyForeignKeys(appUser: UserContext, data: Partial<T>) {
|
|
1040
1229
|
/**
|
|
1041
1230
|
* 1. looping schemas identify what foreign key exists
|
|
1042
1231
|
* 2. loop through record obtain all foreign key value
|
|
@@ -1109,11 +1298,11 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
1109
1298
|
const key = keys[k];
|
|
1110
1299
|
if (searchresult[collectionname] && searchresult[collectionname].includes(key)) {
|
|
1111
1300
|
this.logger.debug(`foreignkey ${collectionname}->${key} exists`);
|
|
1112
|
-
} else if (
|
|
1301
|
+
} else if (appUser.searchInsertedRecordId(collectionname, key)) {
|
|
1113
1302
|
this.logger.debug(`foreignkey ${collectionname} exists in user context which not yet commited`);
|
|
1114
1303
|
} else {
|
|
1115
1304
|
this.logger.warn(`${this.documentType}: Foreignkey ${key} at collection ${collectionname} does not exist`, 'identifyForeignKeys');
|
|
1116
|
-
this.logger.debug(
|
|
1305
|
+
this.logger.debug(appUser.getModifieds, 'appUser.getModifieds');
|
|
1117
1306
|
const errordata = { key: key, collection: collectionname };
|
|
1118
1307
|
throw new BadRequestException(`${this.documentType}: Foreignkey ${key} at collection ${collectionname} does not exist`, JSON.stringify(errordata));
|
|
1119
1308
|
}
|
|
@@ -1122,19 +1311,19 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
1122
1311
|
}
|
|
1123
1312
|
}
|
|
1124
1313
|
|
|
1125
|
-
// async print(
|
|
1126
|
-
// // const pdfresult = await this.printapi.getBase64Pdf(
|
|
1127
|
-
// const pdfresult = await this.runEvent(
|
|
1314
|
+
// async print(appUser: UserContext, id: string, formatId: string) {
|
|
1315
|
+
// // const pdfresult = await this.printapi.getBase64Pdf(appUser, formatid, id);
|
|
1316
|
+
// const pdfresult = await this.runEvent(appUser, 'print.getbase64pdf', { id: id, formatId: formatId }, true);
|
|
1128
1317
|
// // return pdfresult;
|
|
1129
1318
|
// return Promise.resolve('ok');
|
|
1130
1319
|
// }
|
|
1131
1320
|
|
|
1132
|
-
async checkUniqueKeyExist(
|
|
1321
|
+
async checkUniqueKeyExist(appUser: UserContext, data: string[]): Promise<UniqueKeyExistResponse[]> {
|
|
1133
1322
|
const response: UniqueKeyExistResponse[] = [];
|
|
1134
1323
|
const unionKey = this.getDocumentIdentityCode();
|
|
1135
1324
|
const searchQuery: any = { [unionKey]: { $in: data } };
|
|
1136
1325
|
// search for multiple union exist
|
|
1137
|
-
const result = await this.search(
|
|
1326
|
+
const result = await this.search(appUser, searchQuery);
|
|
1138
1327
|
for (const item of data) {
|
|
1139
1328
|
const found = result.find((x) => x[unionKey] === item);
|
|
1140
1329
|
response.push({ [item]: found ? 1 : 0 });
|
|
@@ -1143,7 +1332,14 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
1143
1332
|
return response;
|
|
1144
1333
|
}
|
|
1145
1334
|
|
|
1146
|
-
searchToAggregate(
|
|
1335
|
+
searchToAggregate(
|
|
1336
|
+
appUser: UserContext,
|
|
1337
|
+
filter: FilterQuery<T>,
|
|
1338
|
+
columns: string[],
|
|
1339
|
+
sort: string[][],
|
|
1340
|
+
lookup: { [key: string]: string },
|
|
1341
|
+
pagination?: { pageSize?: number; pageNo?: number },
|
|
1342
|
+
) {
|
|
1147
1343
|
const pipelines: PipelineStage[] = [];
|
|
1148
1344
|
const projection = {};
|
|
1149
1345
|
// console.log('sortsort', sort);
|
|
@@ -1167,7 +1363,7 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
1167
1363
|
as: '_' + to,
|
|
1168
1364
|
localField: lookup[tokey],
|
|
1169
1365
|
foreignField: foreignField,
|
|
1170
|
-
pipeline: [{ $match: { tenantId:
|
|
1366
|
+
pipeline: [{ $match: { tenantId: appUser.getTenantId() } }],
|
|
1171
1367
|
},
|
|
1172
1368
|
});
|
|
1173
1369
|
|
|
@@ -1186,6 +1382,13 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
1186
1382
|
});
|
|
1187
1383
|
pipelines.push({ $sort: sortobj });
|
|
1188
1384
|
}
|
|
1385
|
+
|
|
1386
|
+
if (pagination) {
|
|
1387
|
+
const { pageSize, skip } = this.buildPagination(pagination);
|
|
1388
|
+
pipelines.push({ $skip: skip });
|
|
1389
|
+
pipelines.push({ $limit: pageSize });
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1189
1392
|
//this.logger.warn( pipelines,'pipelinespipelinespipelines',);
|
|
1190
1393
|
|
|
1191
1394
|
return pipelines;
|
|
@@ -1196,9 +1399,9 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
1196
1399
|
return camelToKebab(resourceName) + '.' + camelToKebab(hookName);
|
|
1197
1400
|
}
|
|
1198
1401
|
//only realtime webhook supported at this moment
|
|
1199
|
-
async callWebhook(
|
|
1402
|
+
async callWebhook(appUser: UserContext, actionName: string, data: any) {
|
|
1200
1403
|
try {
|
|
1201
|
-
await this.runWebHook.run(
|
|
1404
|
+
await this.runWebHook.run(appUser, this.documentName, actionName, data);
|
|
1202
1405
|
} catch (e) {
|
|
1203
1406
|
throw new InternalServerErrorException(e);
|
|
1204
1407
|
}
|