@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.
Files changed (22) hide show
  1. package/ReleaseNote.md +11 -0
  2. package/package.json +1 -1
  3. package/templates/basic/nest/controller.ts.eta +2 -2
  4. package/templates/basic/nuxt/resource-bridge.service.ts.eta +14 -6
  5. package/templates/nest/src/app.controller.ts.eta +10 -10
  6. package/templates/nest/src/app.module.ts._eta +1 -0
  7. package/templates/nest/src/simple-app/_core/features/auth/role-guard/roles.guard.ts.eta +5 -18
  8. package/templates/nest/src/simple-app/_core/features/document-no-format/document-no-format.service.ts.eta +14 -7
  9. package/templates/nest/src/simple-app/_core/features/log/log.service.ts.eta +10 -5
  10. package/templates/nest/src/simple-app/_core/features/mini-app/mini-app-scope/mini-app-scope.guard.ts.eta +4 -0
  11. package/templates/nest/src/simple-app/_core/features/profile/profile.service.ts.eta +15 -11
  12. package/templates/nest/src/simple-app/_core/features/queue/queue-base/queue-base.consumer.ts.eta +14 -13
  13. package/templates/nest/src/simple-app/_core/features/user-context/user.context.ts.eta +54 -3
  14. package/templates/nest/src/simple-app/_core/framework/base/simple-app.controller.ts.eta +16 -3
  15. package/templates/nest/src/simple-app/_core/framework/base/simple-app.service.ts.eta +401 -198
  16. package/templates/nest/src/simple-app/_core/framework/framework.module.ts.eta +3 -2
  17. package/templates/nest/src/simple-app/_core/framework/schemas/simple-app.schema.ts.eta +6 -0
  18. package/templates/nest/src/simple-app/_core/framework/simple-app-db-revert.service.ts.eta +101 -0
  19. package/templates/nest/src/simple-app/_core/framework/simple-app.interceptor.ts.eta +33 -12
  20. package/templates/nuxt/app.vue.eta +2 -2
  21. package/templates/nuxt/plugins/18.simpleapp-custom-field-store.ts.eta +2 -2
  22. package/templates/nuxt/types/others.ts.eta +4 -0
package/ReleaseNote.md CHANGED
@@ -1,3 +1,14 @@
1
+ [2.0.2d-alpha]
2
+
3
+ 1. fix pagination issue
4
+
5
+
6
+ [2.0.2c-alpha]
7
+
8
+ 1. support environment variable off mongodb transaction
9
+ 2. add /health check endpoint which no need credentials
10
+ 3. add still buggy pagination setting
11
+
1
12
  [2.0.2b-alpha]
2
13
 
3
14
  1. fix generate no bugs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simitgroup/simpleapp-generator",
3
- "version": "2.0.2b-alpha",
3
+ "version": "2.0.2d-alpha",
4
4
  "description": "frontend nuxtjs and backend nests code generator using jsonschema",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -99,8 +99,8 @@ export class <%= it.typename %>Controller extends SimpleAppController<
99
99
  @ApiBody({ description: 'Data', type: schemas.SearchBody })
100
100
  @ApiOperation({ operationId: 'runSearch' })
101
101
  <%~ drawMiniAppScope('list') %>
102
- async search(@AppUser() appuser: UserContext,@Body() data: schemas.SearchBody) {
103
- return await this._search(appuser,data)
102
+ async search(@AppUser() appuser: UserContext,@Body() data: schemas.SearchBody, @Res({ passthrough: true }) res: Response) {
103
+ return await this._search(appuser,data, res)
104
104
  }
105
105
 
106
106
  <% if(simpleappconfig.search !==undefined){%>
@@ -79,12 +79,20 @@ export class MiniApp<%= pascalName %>BridgeService {
79
79
  <% if (value !== true && typeof value !== 'object') { return; } %>
80
80
 
81
81
  <% if(action === 'list') { %>
82
- protected async handleList(message: MiniAppBridgeMessageApi<<%= typeActionName %>>) {
83
- return (await this.api!.runSearch({
84
- fields: message.params.body?.fields,
85
- sorts: message.params.body?.sorts,
86
- filter: message.params.body?.filters,
87
- })).data;
82
+ protected async handleList(message: MiniAppBridgeMessageApi<MiniAppStudentActions>) {
83
+ const rawBody: any = message.params.body ?? {};
84
+ const pageSize = rawBody.pagination?.pageSize ?? rawBody.pageSize ?? 200;
85
+ const pageNo = rawBody.pagination?.pageNo ?? rawBody.pageNo ?? 0;
86
+ const res = await this.api!.runSearch({
87
+ filter: rawBody.filters,
88
+ fields: rawBody.fields,
89
+ sorts: rawBody.sorts,
90
+ pagination: {
91
+ pageSize,
92
+ pageNo,
93
+ },
94
+ } as any);
95
+ return res.data;
88
96
  }
89
97
  <% } else if(action === 'detail') { %>
90
98
  protected async handleDetail(message: MiniAppBridgeMessageApi<<%= typeActionName %>>) {
@@ -4,22 +4,22 @@
4
4
  * last change 2023-10-28
5
5
  * Author: Ks Tan
6
6
  */
7
- import { Controller, Get,Logger } from '@nestjs/common';
7
+ import { Controller, Get, Logger } from '@nestjs/common';
8
8
  import { AppService } from './app.service';
9
-
9
+ import { Roles } from 'src/simple-app/_core/features/auth/role-guard/roles.decorator';
10
+ import { Role } from 'src/simple-app/_core/features/auth/role-guard/roles.enum';
10
11
  @Controller()
11
12
  export class AppController {
12
- private logger = new Logger()
13
+ private logger = new Logger();
13
14
  constructor(private readonly appService: AppService) {
14
- if(process.env.DRYRUN=='true'){
15
- this.logger.warn("DRY run Mode, transaction will roll back")
15
+ if (process.env.DRYRUN == 'true') {
16
+ this.logger.warn('DRY run Mode, transaction will roll back');
16
17
  }
17
-
18
-
19
18
  }
20
19
 
21
- @Get()
22
- async getHello() {
23
- return 'hello'
20
+ @Get('/health')
21
+ @Roles(Role.Everyone)
22
+ async getHealth() {
23
+ return Promise.resolve('ok');
24
24
  }
25
25
  }
@@ -100,6 +100,7 @@ export class AppModule implements NestModule {
100
100
  consumer
101
101
  .apply(SimpleAppMiddleware)
102
102
  // .exclude('/graphql')
103
+ .exclude('/health')
103
104
  .exclude('/api-yaml')
104
105
  .exclude('/api-json')
105
106
  .exclude('/api')
@@ -4,13 +4,7 @@
4
4
  * last change 2024-03-17
5
5
  * Author: Ks Tan
6
6
  */
7
- import {
8
- Injectable,
9
- Inject,
10
- CanActivate,
11
- ExecutionContext,
12
- Scope,
13
- } from '@nestjs/common';
7
+ import { Injectable, Inject, CanActivate, ExecutionContext, Scope } from '@nestjs/common';
14
8
  import { Reflector } from '@nestjs/core';
15
9
  import { Role } from './roles.enum';
16
10
  import { ROLES_KEY } from './roles.decorator';
@@ -21,18 +15,13 @@ export class RolesGuard implements CanActivate {
21
15
  constructor(private reflector: Reflector) {}
22
16
 
23
17
  canActivate(context: ExecutionContext): boolean {
24
- const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
25
- context.getHandler(),
26
- context.getClass(),
27
- ]) || [];
18
+ const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [context.getHandler(), context.getClass()]) || [];
28
19
  const criticalRoles = [Role.SuperAdmin, Role.SuperUser, Role.TenantOwner];
29
-
30
20
  criticalRoles.forEach((role) => {
31
21
  if (!requiredRoles.includes(role)) {
32
22
  requiredRoles.push(role);
33
23
  }
34
24
  });
35
-
36
25
  let sessionuser: UserContext;
37
26
  if (context.getType() == 'http') {
38
27
  const request = context.switchToHttp().getRequest();
@@ -41,17 +30,15 @@ export class RolesGuard implements CanActivate {
41
30
  const req = context.getArgs()[2].req;
42
31
  sessionuser = req['sessionuser'];
43
32
  }
44
-
33
+
45
34
  if (!requiredRoles) {
46
35
  return true;
47
36
  }
48
-
49
- const roles = sessionuser.getRoles();
37
+ const roles = sessionuser ? sessionuser.getRoles() : [Role.Everyone];
50
38
  if (!roles) {
51
39
  return false;
52
40
  }
53
- const result = requiredRoles.some((role) => roles.includes(role));
54
-
41
+ const result = requiredRoles.some((role) => roles.includes(role));
55
42
  return result;
56
43
  // return true
57
44
  }
@@ -65,8 +65,8 @@ export class SimpleAppDocumentNoFormatService {
65
65
  }
66
66
  //
67
67
  Object.assign(filter, appUser.getBranchFilter());
68
- const session = appUser.getDBSession()
69
- const result = await this.docformat.find(filter)//.session(session);
68
+ const session = appUser.getDBSession();
69
+ const result = await this.docformat.find(filter).session(session);
70
70
  if (result && result.length > 0) {
71
71
  const d: DocumentNoFormat = result[0];
72
72
  const recordId = d._id;
@@ -74,8 +74,9 @@ export class SimpleAppDocumentNoFormatService {
74
74
  const newnextnumber = d.nextNumber + 1;
75
75
  const updatedata = { nextNumber: newnextnumber } as DocumentNoFormat;
76
76
 
77
- const options = process.env.BlockGenerateDocNumberWithSession ? {} : {session:session}
78
- const updateresult = await this.docformat.findByIdAndUpdate(recordId, updatedata,options)
77
+ const options = process.env.BlockGenerateDocNumberWithSession ? {} : { session: session };
78
+ const updateresult = await this.docformat.findByIdAndUpdate(recordId, updatedata, options);
79
+ appUser.addTransactionStep('update','documentnoformat',[recordId],[result[0]])
79
80
  if (updateresult) {
80
81
  const result: DocNumberFormatResult = {
81
82
  formatId: d._id,
@@ -111,7 +112,9 @@ export class SimpleAppDocumentNoFormatService {
111
112
  const docformats = alldocuments.filter((item) => item.docNumber);
112
113
  const allFormats: DocumentNoFormat[] = [];
113
114
  for (let i = 0; i < docformats.length; i++) {
115
+
114
116
  const doc = docformats[i];
117
+ if(['tenantinvoice'].includes(doc.docType)) continue;
115
118
  const pattern = doc.docNoPattern.replace('@BranchCode', branchCode);
116
119
  const formatdata: DocumentNoFormat = {
117
120
  _id: crypto.randomUUID(),
@@ -137,6 +140,10 @@ export class SimpleAppDocumentNoFormatService {
137
140
  try {
138
141
  //rollback when not able to create branch
139
142
  const result = await this.docformat.insertMany(allFormats, { session: appUser.getDBSession() });
143
+ const allRecordIds = result.map(item=>item._id)
144
+ const allLogData = result.map(item=>null)
145
+
146
+ appUser.addTransactionStep('create','documentnoformat',allRecordIds,allLogData)
140
147
  if (!result) {
141
148
  throw new InternalServerErrorException(`Generate ${allFormats.length} document formats for "${branchCode}" failed.`, 'generateDefaultDocNumbers');
142
149
  }
@@ -215,9 +222,9 @@ export class SimpleAppDocumentNoFormatService {
215
222
  }
216
223
  const updatedata = { nextNumber: d.nextNumber + 1 } as DocumentNoFormat;
217
224
 
218
- const options = process.env.BlockGenerateDocNumberWithSession ? {} : {session:appUser.getDBSession()}
219
- const updateresult = await this.docformat.findByIdAndUpdate(recordId, updatedata,options)
220
-
225
+ const options = process.env.BlockGenerateDocNumberWithSession ? {} : { session: appUser.getDBSession(), };
226
+ const updateresult = await this.docformat.findByIdAndUpdate(recordId, updatedata, options);
227
+ appUser.addTransactionStep('update','documentnoformat',[recordId],[result[0]])
221
228
  return documentNumbers;
222
229
  } else {
223
230
  throw new BadRequestException(`No active document number found for ${doctype}. Please update in Settings > Document Numbering Format`);
@@ -48,6 +48,8 @@ export class SimpleAppLogService {
48
48
  return data;
49
49
  }
50
50
  async addEvent(appUser: UserContext, documentName: string, id: string, eventType: string, data: any) {
51
+ if(process.env.DISABLE_DOCUMENT_EVENT==='true') return false
52
+
51
53
  const eventdata: DocumentEvent = {
52
54
  _id: crypto.randomUUID(),
53
55
  documentName: documentName,
@@ -68,6 +70,7 @@ export class SimpleAppLogService {
68
70
  const newdoc = new this.doc(eventdata);
69
71
  const dbsession = appUser.getDBSession();
70
72
  const result = await newdoc.save({ session: dbsession });
73
+ appUser.addTransactionStep('create','documentevent',[eventdata._id],[null])
71
74
  }
72
75
 
73
76
  async addManyEvents(appUser: UserContext, documentName: string, eventType: string, datas: any[]) {
@@ -93,11 +96,13 @@ export class SimpleAppLogService {
93
96
  alldata.push(eventdata);
94
97
  }
95
98
  const dbsession = appUser.getDBSession();
96
- try{
97
- await this.doc.insertMany(alldata, { session: dbsession });
98
- }catch(e){
99
- throw e
99
+ try {
100
+ await this.doc.insertMany(alldata, { session: dbsession });
101
+ const allIds = alldata.map(item=>item._id)
102
+ const allLogData = alldata.map(item=>null)
103
+ appUser.addTransactionStep('create','documentevent',allIds,allLogData)
104
+ } catch (e) {
105
+ throw e;
100
106
  }
101
-
102
107
  }
103
108
  }
@@ -20,6 +20,10 @@ export class MiniAppScopeGuard implements CanActivate {
20
20
  }
21
21
 
22
22
  const req = context.switchToHttp().getRequest();
23
+
24
+ if(req.url==='/health'){
25
+ return true
26
+ }
23
27
  const appUser: UserContext = req?.sessionuser;
24
28
  if (!appUser) {
25
29
  throw new BadRequestException('User Context Not Found');
@@ -87,14 +87,16 @@ export class ProfileService {
87
87
  return userinfo;
88
88
  }
89
89
 
90
- async createTenant(appuser: UserContext, tenantName: string, timeZone: string, utcOffset: number, businessType: string,mobileNo:string) {
90
+ async createTenant(appuser: UserContext, tenantName: string, timeZone: string, utcOffset: number, businessType: string, mobileNo: string) {
91
91
  // try{
92
+
93
+
92
94
  const timezonedata = countrytimezone.getCountriesForTimezone(timeZone)[0];
93
95
  const countryCode = timezonedata['id'];
94
96
  const countryName = timezonedata['name'];
95
97
  const currencyCode = countryToCurrency[countryCode];
96
98
 
97
- appuser.getDBSession().startTransaction({readConcern:{level:"snapshot"},writeConcern:{w:"majority"}, readPreference: 'primary' });
99
+ appuser.startTransaction()
98
100
  const tenantdata: Tenant = {
99
101
  tenantId: 1,
100
102
  tenantName: tenantName,
@@ -111,12 +113,12 @@ export class ProfileService {
111
113
  uid: appuser.getUid(),
112
114
  },
113
115
  };
114
- this.logger.log(tenantdata, 'createTenant data');
116
+ this.logger.debug(tenantdata, 'createTenant data');
115
117
  const tenantResult = await this.tenantService.create(appuser, tenantdata);
116
118
  if (!tenantResult) {
117
119
  throw new BadRequestException('Create tenant failed');
118
120
  }
119
- this.logger.log(tenantResult, 'createTenant result');
121
+ this.logger.debug(tenantResult, 'createTenant result');
120
122
  const tenantId = tenantResult.tenantId;
121
123
 
122
124
  // return tenantResult
@@ -133,7 +135,7 @@ export class ProfileService {
133
135
  orgId: 1,
134
136
  };
135
137
 
136
- this.logger.log(orgdata, 'createOrg data');
138
+ this.logger.debug(orgdata, 'createOrg data');
137
139
  const orgResult = await this.orgService.create(appuser, orgdata);
138
140
  if (!orgResult) {
139
141
  throw new BadRequestException('Create Org failed');
@@ -168,14 +170,14 @@ export class ProfileService {
168
170
  tenantId: tenantResult.tenantId,
169
171
  organization: { _id: orgRecordId, label: tenantName },
170
172
  };
171
- this.logger.log(branchdata, 'createbranch data');
173
+ this.logger.debug(branchdata, 'createbranch data');
172
174
 
173
175
  const branchResult = await this.branchService.create(appuser, branchdata);
174
176
  if (!branchResult) {
175
177
  throw new BadRequestException('Create Branch failed');
176
178
  }
177
179
  const branchRecordId = branchResult._id.toString();
178
- this.logger.log(branchResult, 'createbranch result');
180
+ this.logger.debug(branchResult, 'createbranch result');
179
181
 
180
182
  const userdata: User = {
181
183
  tenantId: tenantResult.tenantId,
@@ -186,14 +188,14 @@ export class ProfileService {
186
188
  email: appuser.getEmail(),
187
189
  active: true,
188
190
  };
189
- this.logger.log(userdata, 'createtenant user data');
191
+ this.logger.debug(userdata, 'createtenant user data');
190
192
  const userResult = await this.userService.create(appuser, userdata);
191
193
  // if(true ){
192
194
  if (!userResult) {
193
195
  throw new BadRequestException('Create User failed');
194
196
  }
195
197
 
196
- this.logger.log(userResult, 'createtenant user result');
198
+ this.logger.debug(userResult, 'createtenant user result');
197
199
  const userRecordId = userResult._id.toString();
198
200
 
199
201
  const permdata: Permission = {
@@ -204,13 +206,13 @@ export class ProfileService {
204
206
  userId: userRecordId,
205
207
  groups: ['admin'],
206
208
  };
207
- this.logger.log(permdata, 'create Permission data');
209
+ this.logger.debug(permdata, 'create Permission data');
208
210
  const permResult = await this.permService.create(appuser, permdata);
209
211
  if (!permResult) {
210
212
  throw new BadRequestException('Create permResult failed');
211
213
  }
212
214
 
213
- this.logger.log(permResult, 'create Permission result');
215
+ this.logger.debug(permResult, 'create Permission result');
214
216
 
215
217
  //tenant owner shall map to userId for that tenant
216
218
 
@@ -228,6 +230,8 @@ export class ProfileService {
228
230
  orgId: orgResult.orgId,
229
231
  branchId: branchResult.branchId,
230
232
  };
233
+
234
+ // console.log("finalresult, is transaction",finalresult,appuser.getDBSession().inTransaction())
231
235
  return finalresult;
232
236
  // }catch(e){
233
237
  // this.logger.error("Couldn't generate tenant or subsequence records")
@@ -7,19 +7,21 @@ import { UserContext } from '../../user-context/user.context';
7
7
  import { QueueUserContext } from '../queue-user-context/queue-user-context.service';
8
8
  import { Queuejob } from '../queue.type';
9
9
  // import { QueueName } from '../queue.enum';
10
- import { forwardRef, Inject, InternalServerErrorException } from '@nestjs/common';
10
+ import { forwardRef, Inject, Logger, InternalServerErrorException } from '@nestjs/common';
11
11
  import { EventEmitter2 } from '@nestjs/event-emitter';
12
12
  import _ from 'lodash';
13
+ import { SimpleAppDbRevertService } from 'src/simple-app/_core/framework/simple-app-db-revert.service';
13
14
 
14
15
  export abstract class BaseQueueConsumer extends WorkerHost {
15
16
  // protected abstract readonly processor: QueueName;
16
17
  protected abstract readonly processor: string;
17
-
18
+ protected logger = new Logger()
18
19
  constructor(
19
20
  protected readonly queueUserContext: QueueUserContext,
20
21
  @InjectModel('Queuejob') protected queueJobModel: Model<Queuejob>,
21
22
  @InjectConnection() private readonly connection: Connection,
22
23
  protected eventEmitter: EventEmitter2,
24
+ protected simpleAppDbRevertService: SimpleAppDbRevertService
23
25
  ) {
24
26
  super();
25
27
  }
@@ -36,6 +38,7 @@ export abstract class BaseQueueConsumer extends WorkerHost {
36
38
  async process(job: Job) {
37
39
  // console.log("process job", job.data)
38
40
  const { userContext, payload } = job.data;
41
+ this.logger.debug(payload,`run queue job ${job.name}`)
39
42
 
40
43
  const appUser = await this.createUserContext(userContext.user);
41
44
  await this.startSession(appUser);
@@ -45,19 +48,19 @@ export abstract class BaseQueueConsumer extends WorkerHost {
45
48
  // this.queueUserContext.assign(userContext);
46
49
 
47
50
  await this.dispatch(appUser, job, payload);
48
- if (appUser.getDBSession().inTransaction()) {
49
- await appUser.getDBSession().commitTransaction();
50
- }
51
+ // if (appUser.getDBSession().inTransaction()) {
52
+ await appUser.commitTransaction() //.getDBSession().commitTransaction();
53
+ // }
51
54
  return {
52
55
  success: true,
53
56
  };
54
57
  } catch (e) {
55
- if (appUser.getDBSession().inTransaction()) {
56
- await appUser.getDBSession().abortTransaction();
57
- }
58
+ // if (appUser.getDBSession().inTransaction()) {
59
+ await appUser.rollBackTransaction(this.simpleAppDbRevertService) //.getDBSession().abortTransaction();
60
+ // }
58
61
  throw e;
59
62
  } finally {
60
- appUser.getDBSession().endSession();
63
+ await appUser.endSession()//getDBSession().endSession();
61
64
  }
62
65
  }
63
66
 
@@ -74,8 +77,7 @@ export abstract class BaseQueueConsumer extends WorkerHost {
74
77
  await handler.call(this, appUser, job, payload);
75
78
  }
76
79
 
77
- protected async updateQueueJobRecord(job: Job, status: 'completed' | 'failed', result: any) {
78
-
80
+ protected async updateQueueJobRecord(job: Job, status: 'completed' | 'failed', result: any) {
79
81
  await this.queueJobModel.updateOne(
80
82
  {
81
83
  'job.id': job.id,
@@ -83,8 +85,7 @@ export abstract class BaseQueueConsumer extends WorkerHost {
83
85
  'job.processor': this.processor,
84
86
  },
85
87
  {
86
-
87
- 'job.status': status,
88
+ 'job.status': status,
88
89
  initTime: new Date(job.timestamp).toISOString(),
89
90
  startTime: new Date(job.processedOn).toISOString(),
90
91
  endTime: new Date(job.finishedOn).toISOString(),
@@ -30,12 +30,15 @@ import { Role } from '../auth/role-guard/roles.enum';
30
30
  import { Environment } from '@core-features/maintenance/schemas';
31
31
  import * as rolegroups from '../auth/role-guard/roles.group';
32
32
  import { TenantLicenseEnum } from '@resources/tenant/tenant.enum';
33
+ import { SimpleAppDbRevertService } from '../../framework/simple-app-db-revert.service';
34
+ import { StepData } from 'src/simple-app/_core/resources/permission/permission.schema';
33
35
 
34
36
  // import systemWebHooks from '../../webhooks';
35
37
  @Injectable({ scope: Scope.REQUEST })
36
38
  export class UserContext extends UserContextInfo {
37
39
  sessionId: string = crypto.randomUUID();
38
-
40
+ protected isTransaction = false
41
+ protected transSteps:StepData[] = []
39
42
  protected logger = new Logger(this.constructor.name);
40
43
 
41
44
  // protected uid: string = '';
@@ -148,7 +151,7 @@ export class UserContext extends UserContextInfo {
148
151
  };
149
152
 
150
153
  getDBSession = (): ClientSession => this.dbsession;
151
-
154
+ getTransStep = () => this.transSteps
152
155
  getId = () => this._id;
153
156
 
154
157
  getUid = () => this.uid;
@@ -448,7 +451,7 @@ export class UserContext extends UserContextInfo {
448
451
  branchName: '$b.branchName',
449
452
  branchId: '$b.branchId',
450
453
  imageUrl: '$b.imageUrl',
451
- active: '$b.active'
454
+ active: '$b.active',
452
455
  },
453
456
 
454
457
  groups: 1,
@@ -1216,8 +1219,56 @@ export class UserContext extends UserContextInfo {
1216
1219
  const isodate = new Date(timestamp + -offsets).toISOString().split('.')[0] + 'Z';
1217
1220
  return isodate;
1218
1221
  }
1222
+
1223
+
1224
+ addTransactionStep (action:string,collection:string,id:string[], data:any[]) {
1225
+ this.transSteps.push({action:action,collection:collection,id:id,data:data})
1226
+ }
1227
+
1228
+
1229
+ inTransaction(){
1230
+ if(process.env.MONGO_TRANS==='false')
1231
+ return this.isTransaction
1232
+ else
1233
+ return this.dbsession && this.dbsession.inTransaction() ? true : false
1234
+ }
1235
+ startTransaction(){
1236
+ if(process.env.MONGO_TRANS==='false'){
1237
+ this.isTransaction=true
1238
+ this.transSteps=[]
1239
+
1240
+
1241
+ }else{
1242
+ this.dbsession.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, readPreference: 'primary' });
1243
+ }
1244
+ }
1245
+ async commitTransaction(){
1246
+ if(process.env.MONGO_TRANS==='false'){
1247
+ this.isTransaction=false
1248
+ this.transSteps=[]
1249
+ }else{
1250
+ if(this.dbsession.inTransaction())
1251
+ await this.dbsession.commitTransaction()
1252
+ }
1253
+ }
1254
+ async rollBackTransaction(dbService:SimpleAppDbRevertService){
1255
+ if(process.env.MONGO_TRANS==='false'){
1256
+ await dbService.revertSteps(this.transSteps)
1257
+ this.isTransaction=false
1258
+ this.transSteps=[]
1259
+
1260
+ }else{
1261
+ if(this.dbsession.inTransaction())
1262
+ await this.dbsession.abortTransaction()
1263
+ }
1264
+ }
1265
+ async endSession(){
1266
+ await this.dbsession.endSession()
1267
+ }
1219
1268
  }
1220
1269
 
1270
+
1271
+ // type StepData = {action:string,collection:string,id:string[],data:any[]}
1221
1272
  /**
1222
1273
  * Define a type for userinfo
1223
1274
  */
@@ -4,7 +4,8 @@
4
4
  * last change 2025-09-01
5
5
  * Author: Ks Tan
6
6
  */
7
- import { Controller, Get, Put, Post, Delete, Body, Param, Type } from '@nestjs/common';
7
+ import { Controller, Get, Put, Post, Delete, Body, Param, Type, Res } from '@nestjs/common';
8
+ import { Response } from 'express';
8
9
  // import { ApiTags, ApiBody, ApiResponse, ApiOperation } from '@nestjs/swagger';
9
10
  import { UserContext } from '../../features/user-context/user.context';
10
11
  import { SearchBody, TextSearchBody, PatchManyRequest } from '../schemas';
@@ -15,6 +16,9 @@ const doctype = 'person'.toUpperCase();
15
16
  type ServiceType = {
16
17
  list: Function;
17
18
  search: Function;
19
+ searchWithPageInfo: Function;
20
+ setPaginationHeaders: Function;
21
+ getTotalCount: Function;
18
22
  create: Function;
19
23
  //update: Function;
20
24
  //delete: Function;
@@ -47,8 +51,17 @@ export class SimpleAppController<TService extends ServiceType, TApiSchema> {
47
51
  return this.service.fullTextSearch(appuser, body);
48
52
  }
49
53
 
50
- async _search(appuser: UserContext, searchObject: SearchBody) {
51
- return this.service.search(appuser, searchObject['filter'], searchObject['fields'], searchObject['sorts'], searchObject['lookup']);
54
+ async _search(appuser: UserContext, searchObject: SearchBody, res?: Response) {
55
+ const items = await this.service.searchWithPageInfo(
56
+ appuser,
57
+ searchObject['filter'],
58
+ searchObject['fields'],
59
+ searchObject['sorts'],
60
+ searchObject['lookup'],
61
+ searchObject['pagination'],
62
+ );
63
+
64
+ return items;
52
65
  }
53
66
 
54
67
  async _autocomplete(appuser: UserContext, keyword: string, data?: TApiSchema) {