@simitgroup/simpleapp-generator 2.0.2-b-alpha → 2.0.2-c-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.
@@ -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 = (appuser: UserContext) => {
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 = appuser.getBranchFilter();
119
+ isolationFilter = appUser.getBranchFilter();
119
120
  break;
120
121
  case 'tenant':
121
- isolationFilter = appuser.getTenantFilter();
122
+ isolationFilter = appUser.getTenantFilter();
122
123
  break;
123
124
  case 'org':
124
125
  default:
125
- isolationFilter = appuser.getOrgFilter();
126
+ isolationFilter = appUser.getOrgFilter();
126
127
  break;
127
128
  }
128
129
  return isolationFilter;
129
130
  };
130
- async list(appuser: UserContext) {
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(appuser));
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(appuser: UserContext, keyword: string, data?: T) {
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(appuser));
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 appuser
191
+ * @param appUser
191
192
  * @param filters
192
193
  * @returns
193
194
  */
194
- async searchNoIsolation(appuser: UserContext, filters: FilterQuery<T>) {
195
+ async searchNoIsolation(appUser: UserContext, filters: FilterQuery<T>) {
195
196
  try {
196
- //if (this.hooks.beforeSearch) await this.hooks.beforeSearch(appuser, filters);
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(appuser, productlist);
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(appuser: UserContext, pipeline: PipelineStage[]) {
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: appuser.getDBSession(),
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>(appuser: UserContext, pipeline: PipelineStage[]) {
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(appuser) };
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(appuser.getId() == ''){
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: appuser.getDBSession(),
239
+ session: appUser.getDBSession(),
239
240
  });
240
241
 
241
- // if(appuser.getId() == ''){
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(appuser: UserContext, filters: FilterQuery<T>, projection: string[] = undefined, sort: any = undefined, lookup: { [key: string]: string } = undefined) {
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
- const isolationFilter = { ...this.getIsolationFilter(appuser) };
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(appuser, newfilters);
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
- searchResults = await this.doc.find(newfilters, projection, { session: appuser.getDBSession() }).sort(sort);
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(appuser, newfilters, projection, sort, lookup);
284
+ const pipelines = this.searchToAggregate(appUser, newfilters, projection, sort, lookup, pagination);
269
285
  this.logger.debug('after aggregate', pipelines);
270
- searchResults = await this.aggregate(appuser, pipelines);
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(appuser, list);
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
- async fullTextSearch(appuser: UserContext, body: TextSearchBody) {
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.toString());
417
+ res.setHeader('x-page-size', pageSize.toString());
418
+ res.setHeader('x-page-no', pageNo.toString());
419
+ res.setHeader('x-total-count', total.toString());
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(appuser) };
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
- orConditions.push({
301
- [field]: { $regex: regex },
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(appuser: UserContext, id: string) {
330
- // if (this.hooks.beforeFetchRecord) await this.hooks.beforeFetchRecord(appuser, id);
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(appuser, { _id: id as any });
333
- // if (this.hooks.afterFetchRecord) await this.hooks.afterFetchRecord(appuser, data[0]);
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(appuser: UserContext, id: string) {
344
- // if (this.hooks.beforeFetchRecord) await this.hooks.beforeFetchRecord(appuser, id);
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(appuser, { _id: id as any });
347
- // if (this.hooks.afterFetchRecord) await this.hooks.afterFetchRecord(appuser, data[0]);
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 appuser
525
+ * @param appUser
360
526
  * @param datas
361
527
  */
362
- async createMany(appuser: UserContext, datas: T[]) {
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 = { ...appuser.getCreateFilter() };
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(appuser, data);
372
- this.applyNestedDateTime(appuser, data, 'create');
537
+ await this.validateData(appUser, data);
538
+ this.applyNestedDateTime(appUser, data, 'create');
373
539
  }
374
- const dbsession = appuser.getDBSession();
375
- if (dbsession && !dbsession.inTransaction()) {
376
- dbsession.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, readPreference: 'primary' });
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
- await this.addManyAuditEvents(appuser, this.documentName, 'createMany', datas);
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
- appuser.addInsertedRecordId(this.documentName, data._id);
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(appuser: UserContext, data: T, noStartTransaction: boolean = false) {
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 = appuser.getDBSession();
402
- if (dbsession && !dbsession.inTransaction() && !noStartTransaction) {
403
- dbsession.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, readPreference: 'primary' });
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(appuser, data);
578
+ await this.genNewDocNo(appUser, data);
409
579
  }
410
580
 
411
- let isolationFilter: any = { ...appuser.getCreateFilter() };
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(appuser, data);
590
+ await this.validateData(appUser, data);
421
591
  this.logger.debug(data, `after create validation`);
422
- this.applyNestedDateTime(appuser, data, 'create');
592
+ this.applyNestedDateTime(appUser, data, 'create');
423
593
 
424
594
  //new way of hook
425
- await this.runEvent(appuser, this.setHookName('beforeCreate'), { data: data }, false);
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(appuser, data);
599
+ await this.identifyForeignKeys(appUser, data);
430
600
  try {
431
601
  result = await newdoc.save({ session: dbsession });
432
- await this.addAuditEvent(appuser, this.documentName, result._id, 'create', data);
433
- appuser.addInsertedRecordId(this.documentName, result._id);
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(appuser, this.setHookName('afterCreate'), { data: result }, false);
612
+ await this.runEvent(appUser, this.setHookName('afterCreate'), { data: result }, false);
442
613
 
443
- await this.callWebhook(appuser, 'create', result);
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 = (appuser: UserContext, data: any, transtype: string) => {
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(appuser, data[key][j], transtype);
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'] ? appuser.getUid() : 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'] = appuser.getUid();
637
+ data['updatedBy'] = appUser.getUid();
467
638
  }
468
639
  }
469
640
  };
470
- async validateData(appuser: UserContext, data: T, _id?: string) {
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(appuser, data, _id);
661
+ // issuccess = await this.hooks.beforeValidation(appUser, data, _id);
491
662
  // }
492
- // const issuccess = await this.hook(appuser, HookType.beforeValidation, data);
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(appuser: UserContext, id: string): Promise<any> {
545
- const deletedata = await this.findById(appuser, id);
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 = appuser.getDBSession();
550
- if (dbsession && !dbsession.inTransaction()) {
551
- dbsession.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, readPreference: 'primary' });
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(appuser, this.setHookName('beforeDelete'), { id: id, deleteData: deletedata }, false);
728
+ await this.runEvent(appUser, this.setHookName('beforeDelete'), { id: id, deleteData: deletedata }, false);
558
729
 
559
- // if (this.hooks.beforeDelete) await this.hooks.beforeDelete(appuser, id, deletedata);
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(appuser, id);
732
+ dependency = await this.getRelatedRecords(appUser, id);
562
733
  if (!dependency) {
563
- const filterIsolation = this.getIsolationFilter(appuser);
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(appuser, this.documentName, id, 'delete', deletedata);
743
+ await this.addAuditEvent(appUser, this.documentName, id, 'delete', deletedata);
571
744
 
572
- appuser.addDeletedRecordId(this.documentName, id);
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(appuser, this.setHookName('afterDelete'), { id: id, deleteData: deletedata }, false);
754
+ await this.runEvent(appUser, this.setHookName('afterDelete'), { id: id, deleteData: deletedata }, false);
582
755
 
583
- await this.callWebhook(appuser, 'delete', deletedata);
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 (appuser:UserContext,data: T) => {
772
+ // updateOne = async (appUser:UserContext,data: T) => {
600
773
  // this.doc.updateOne(data);
601
774
  // };
602
775
 
603
- findIdThenUnsetField = async (appuser: UserContext, id: string, data: AnyKeys<T> & AnyObject, noStartTransaction: boolean = false) => {
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(appuser, id);
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
- ...appuser.getUpdateFilter(),
789
+ ...appUser.getUpdateFilter(),
617
790
  __v: existingdata.__v + 1,
618
791
  };
619
792
 
620
793
  // Start transaction
621
- const dbsession = appuser.getDBSession();
622
- if (dbsession && !dbsession.inTransaction() && !noStartTransaction) {
623
- dbsession.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, readPreference: 'primary' });
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(appuser) };
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(appuser, this.documentName, id, 'update', data);
820
+ await this.addAuditEvent(appUser, this.documentName, id, 'update', data);
648
821
 
649
- appuser.addUpdatedRecordId(this.documentName, data._id);
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 (appuser: UserContext, id: string, data: T, noStartTransaction: boolean = false) => {
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(appuser, id);
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(appuser, data);
848
+ await this.identifyForeignKeys(appUser, data);
676
849
 
677
850
  //new way of hook
678
- await this.runEvent(appuser, this.setHookName('beforeUpdate'), { id: id, prevData: existingdata, newData: data }, false);
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(appuser, id, existingdata, data);
853
+ // if (this.hooks.beforeUpdate) await this.hooks.beforeUpdate(appUser, id, existingdata, data);
681
854
 
682
- const dbsession = appuser.getDBSession();
683
- if (dbsession && !dbsession.inTransaction() && !noStartTransaction) {
684
- dbsession.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, readPreference: 'primary' });
855
+ const dbsession = appUser.getDBSession();
856
+ if (!appUser.inTransaction() && !noStartTransaction) {
857
+ appUser.startTransaction();
685
858
  }
686
859
  // try {
687
- Object.assign(data, appuser.getUpdateFilter());
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(appuser, data, id);
867
+ await this.validateData(appUser, data, id);
695
868
 
696
- const isolationFilter = { ...this.getIsolationFilter(appuser) };
869
+ const isolationFilter = { ...this.getIsolationFilter(appUser) };
697
870
  this.polishIsolationFilter(isolationFilter);
698
871
  isolationFilter['_id'] = id;
699
- this.applyNestedDateTime(appuser, data, 'update');
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
- appuser.addUpdatedRecordId(this.documentName, data._id);
708
- await this.runEvent(appuser, this.setHookName('afterUpdate'), { id: id, prevData: existingdata, newData: data }, false);
879
+ appUser.addTransactionStep('update',this.documentName,[id],[existingdata]);
880
+ await this.addAuditEvent(appUser, this.documentName, id, 'update', data);
709
881
 
710
- await this.callWebhook(appuser, 'update', result);
711
- return result; // await this.findById(appuser, id);
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 (appuser: UserContext, id: string, data: T) => {
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(appuser, id);
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(appuser, data);
910
+ await this.identifyForeignKeys(appUser, data);
736
911
 
737
- await this.runEvent(appuser, this.setHookName('beforeUpdate'), { data: data }, false);
912
+ await this.runEvent(appUser, this.setHookName('beforeUpdate'), { data: data }, false);
738
913
 
739
- // if (this.hooks.beforeUpdate) await this.hooks.beforeUpdate(appuser, id, existingdata, data);
914
+ // if (this.hooks.beforeUpdate) await this.hooks.beforeUpdate(appUser, id, existingdata, data);
740
915
 
741
- const dbsession = appuser.getDBSession();
742
- if (dbsession && !dbsession.inTransaction()) {
743
- dbsession.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, readPreference: 'primary' });
916
+ const dbsession = appUser.getDBSession();
917
+ if (!appUser.inTransaction()) {
918
+ appUser.startTransaction();
744
919
  }
745
920
  // try {
746
- // Object.assign(data, appuser.getUpdateFilter());
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(appuser, data, id);
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(appuser, data, 'update');
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
- await this.runEvent(appuser, this.setHookName('afterUpdate'), { id: id, prevData: existingdata, newData: result }, false);
767
- appuser.addUpdatedRecordId(this.documentName, data._id);
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(appuser, 'update', result);
770
- return result; // await this.findById(appuser, id);
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 (appuser: UserContext, id: string, data: Partial<T>, session: mongo.ClientSession = undefined, skipLog: boolean = false) => {
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(appuser, id);
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(appuser, data);
969
+ await this.identifyForeignKeys(appUser, data);
793
970
 
794
971
  //patch not suitable trigger afterupdate
795
- // if (this.hooks.beforeUpdate) await this.hooks.beforeUpdate(appuser, id, existingdata, data);
796
- await this.runEvent(appuser, this.setHookName('beforePatch'), { id: id, patchData: data, prevData: existingdata }, false);
797
- const dbsession = appuser.getDBSession();
798
- if (dbsession && !dbsession.inTransaction()) {
799
- dbsession.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, readPreference: 'primary' });
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, appuser.getUpdateFilter());
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(appuser, data);
988
+ // await this.validateData(appUser, data);
812
989
 
813
- const isolationFilter = { ...this.getIsolationFilter(appuser) };
990
+ const isolationFilter = { ...this.getIsolationFilter(appUser) };
814
991
  this.polishIsolationFilter(isolationFilter);
815
992
 
816
993
  isolationFilter['_id'] = id;
817
- this.applyNestedDateTime(appuser, data, 'update');
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(appuser, this.documentName, id, 'patch', data);
1004
+ await this.addAuditEvent(appUser, this.documentName, id, 'patch', data);
827
1005
  }
828
- appuser.addUpdatedRecordId(this.documentName, data._id);
829
- await this.runEvent(appuser, this.setHookName('afterPatch'), { id: id, patchData: data, prevData: existingdata }, false);
830
- return result; //await this.findById(appuser, id);
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 (dbsession && !dbsession.inTransaction()) {
840
- dbsession.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, readPreference: 'primary' });
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>(appuser: UserContext, data: PatchManyRequest<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(appuser), ...(filter || {}) };
1046
+ const isolationFilter = { ...this.getIsolationFilter(appUser), ...(filter || {}) };
862
1047
  this.polishIsolationFilter(isolationFilter);
863
- this.applyNestedDateTime(appuser, patch, 'update');
1048
+ this.applyNestedDateTime(appUser, patch, 'update');
864
1049
 
865
1050
  // Get DB Session
866
- const dbsession = appuser.getDBSession();
867
- if (dbsession && !dbsession.inTransaction()) {
868
- dbsession.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, readPreference: 'primary' });
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
- await this.addManyAuditEvents(appuser, this.documentName, 'patchMany', [patch]);
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(appuser: UserContext, id: string) {
1067
+ async getRelatedRecords(appUser: UserContext, id: string) {
879
1068
  this.logger.debug('get foreignkey for delete:', id);
880
- // console.log('session modifeds', appuser.getModifieds());
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: appuser.getDBSession(),
1095
+ session: appUser.getDBSession(),
907
1096
  });
908
1097
  if (result) {
909
1098
  //record deleted but not yet commit, safely ignore
910
- if (appuser.searchDeletedRecordId(collectionname, result._id)) continue;
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(appuser: UserContext, id: string, data: T, docstatus: string) {
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(appuser, id);
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(appuser, HookType.beforeSetStatus, data);
950
- await this.runEvent(appuser, this.setHookName('beforeSetStatus'), { docStatus: docstatus, prevData: existdata, newData: data }, false);
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(appuser, docstatus, data, existdata);
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(appuser, data);
956
- await this.runEvent(appuser, this.setHookName('afterSetStatus'), { docStatus: docstatus, data: createresult }, false);
957
- // if (this.hooks.afterSetStatus) await this.hooks.afterSetStatus(appuser, docstatus, createresult);
958
- await this.addAuditEvent(appuser, this.documentName, id, docstatus, data);
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(appuser, id, data);
963
- const finaldata = await this.findById(appuser, id);
964
- await this.runEvent(appuser, this.setHookName('afterSetStatus'), { docStatus: docstatus, data: finaldata }, false);
965
- await this.addAuditEvent(appuser, this.documentName, id, docstatus, data);
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(appuser, docstatus, finaldata);
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 appuser db's transaction
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} appuser The appuser
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(appuser: UserContext, eventName: string, payloads: any) {
982
- this.eventEmitter.emit(eventName, appuser, payloads);
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 appuser dbtransaction remain usable. however, the execution may delay response
1182
+ * the appUser dbtransaction remain usable. however, the execution may delay response
994
1183
  *
995
1184
  *
996
- * @param {UserContext} appuser The appuser
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(appuser: UserContext, eventName: string, payloads: any, enforce: boolean = true) {
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, appuser, payloads);
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(appuser: UserContext, processName: WorkflowName, workflowData: any) {
1023
- // return this.eventEmitter.emit('workflow.start', appuser, processName, workflowData);
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(appuser: UserContext, data: T) {
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(appuser, this.documentType, data);
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(appuser: UserContext): Promise<unknown> {
1225
+ async runDefault(appUser: UserContext): Promise<unknown> {
1037
1226
  return 'Hello this is ' + this.getDocumentType() + ': ' + this.getDocumentName();
1038
1227
  }
1039
- async identifyForeignKeys(appuser: UserContext, data: Partial<T>) {
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 (appuser.searchInsertedRecordId(collectionname, key)) {
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(appuser.getModifieds, 'appuser.getModifieds');
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(appuser: UserContext, id: string, formatId: string) {
1126
- // // const pdfresult = await this.printapi.getBase64Pdf(appuser, formatid, id);
1127
- // const pdfresult = await this.runEvent(appuser, 'print.getbase64pdf', { id: id, formatId: formatId }, true);
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(appuser: UserContext, data: string[]): Promise<UniqueKeyExistResponse[]> {
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(appuser, searchQuery);
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(appuser: UserContext, filter: FilterQuery<T>, columns: string[], sort: string[][], lookup: { [key: string]: string }) {
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: appuser.getTenantId() } }],
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(appuser: UserContext, actionName: string, data: any) {
1402
+ async callWebhook(appUser: UserContext, actionName: string, data: any) {
1200
1403
  try {
1201
- await this.runWebHook.run(appuser, this.documentName, actionName, data);
1404
+ await this.runWebHook.run(appUser, this.documentName, actionName, data);
1202
1405
  } catch (e) {
1203
1406
  throw new InternalServerErrorException(e);
1204
1407
  }