@simitgroup/simpleapp-generator 2.0.3-m-alpha → 2.0.3-o-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 CHANGED
@@ -1,3 +1,11 @@
1
+ [2.0.3o-alpha]
2
+ 1. Fix mini app current action type
3
+
4
+ [2.0.3n-alpha]
5
+ 1. Add hasRole checking in hasAccessByPageMeta
6
+ 2. Add type for Simple App Service
7
+ 3. Only show known features at mini app detail
8
+
1
9
  [2.0.3m-alpha]
2
10
  1. Extract BridgeResourceAccessorBase for extensibility
3
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simitgroup/simpleapp-generator",
3
- "version": "2.0.3m-alpha",
3
+ "version": "2.0.3o-alpha",
4
4
  "description": "frontend nuxtjs and backend nests code generator using jsonschema.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -38,6 +38,9 @@
38
38
  import { MiniAppApiListParam } from "../../types/bridge.type";
39
39
  import { MiniAppBridgeService } from "../bridge.service";
40
40
  import * as Schema from "../../openapi/backend-api";
41
+ <% if (miniAppWhitelistApis.current) { %>
42
+ import * as CustomSchema from "../../types/resources";
43
+ <% } %>
41
44
  import { CreateResource, PatchResource, UpdateResource } from "../../types/common";
42
45
 
43
46
  export class MiniApp<%= pascalName %>BridgeService {
@@ -89,7 +92,7 @@ export class MiniApp<%= pascalName %>BridgeService {
89
92
  }
90
93
  <% } else if(action === 'current') { %>
91
94
  async <%= action %>() {
92
- return this.bridge.callApi(this.resourceName, "<%= action %>");
95
+ return this.bridge.callApi<CustomSchema.Current<%= pascalName %>>(this.resourceName, "<%= action %>");
93
96
  }
94
97
  <% } else { %>
95
98
  <% const apiSetting = it.apiSettings.find(item => item.action === action); %>
@@ -24,7 +24,7 @@ export class MiniApp<%= pascalName %>BridgeEditableService extends MiniApp<%= pa
24
24
  <% if (value !== true && typeof value !== 'object') { return; } %>
25
25
 
26
26
  <% if(action === 'current') { %>
27
- protected override async handleCurrent(message: MiniAppBridgeMessageApi<<%= typeActionName %>>): Promise<any> {
27
+ protected override async handleCurrent(message: MiniAppBridgeMessageApi<<%= typeActionName %>>) {
28
28
  return {};
29
29
  }
30
30
  <% } %>
@@ -151,8 +151,8 @@ export class MiniApp<%= pascalName %>BridgeService {
151
151
  return (await this.api!.autoComplete(message.params.query ?? "", {})).data;
152
152
  }
153
153
  <% } else if(action === 'current') { %>
154
- protected async handleCurrent(message: MiniAppBridgeMessageApi<<%= typeActionName %>>) {
155
- return {};
154
+ protected async handleCurrent(message: MiniAppBridgeMessageApi<<%= typeActionName %>>): Promise<unknown> {
155
+ return null;
156
156
  }
157
157
  <% } else { %>
158
158
  protected async handle<%= upperFirstCase(action) %>(message: MiniAppBridgeMessageApi<<%= typeActionName %>>) {
@@ -13,7 +13,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
13
13
  import Ajv from 'ajv';
14
14
  import addErrors from 'ajv-errors';
15
15
  import addFormats from 'ajv-formats';
16
- import { AnyKeys, AnyObject, FilterQuery, Model, PipelineStage, mongo } from 'mongoose';
16
+ import { AnyKeys, AnyObject, FilterQuery, Model, PipelineStage, SortOrder, mongo } from 'mongoose';
17
17
  // import { CloudApiService } from 'src/cloudapi/cloudapi.service';
18
18
  import { foreignkeys } from '../../features/foreign-key/foreignkeys.dict';
19
19
 
@@ -26,6 +26,31 @@ import { RunWebhookService } from '../../features/webhook/run-webhook.service';
26
26
  import { DeleteResultType, IsolationType, MoreProjectionType, PatchManyRequest, SchemaFields, TextSearchBody, UniqueKeyExistResponse } from '../schemas';
27
27
  import { SearchWithRelation } from './dto/simple-app-search-with-relation.dto';
28
28
 
29
+ type DynamicRecord = Record<string, unknown>;
30
+ type LooseRecord = ReturnType<typeof JSON.parse>;
31
+ type SimpleAppJsonSchema = DynamicRecord & {
32
+ properties?: Record<string, DynamicRecord>;
33
+ 'x-simpleapp-config': {
34
+ resourceName?: string;
35
+ } & DynamicRecord;
36
+ };
37
+ type ErrorWithMessage = { message: string };
38
+ type EventResult = { name?: string };
39
+ type SortOption = string | Record<string, SortOrder> | [string, SortOrder][];
40
+ type AggregateTotalResult = { total?: number };
41
+ type RelatedRecord = { _id?: string } & DynamicRecord;
42
+ type ForeignKeySettings = Record<string, string[]>;
43
+ type ResourceForeignKeyDictionary = Record<string, string[]>;
44
+ type GlobalForeignKeyDictionary = Record<string, ForeignKeySettings>;
45
+ type ForeignKeyAggregateRow = { _id: string; collection: string };
46
+
47
+ const getErrorMessage = (err: unknown): string => {
48
+ if (err instanceof Error) return err.message;
49
+ if (typeof err === 'string') return err;
50
+ return JSON.stringify(err);
51
+ };
52
+ const isEventResult = (value: unknown): value is EventResult => typeof value === 'object' && value !== null && 'name' in value;
53
+
29
54
  @Injectable()
30
55
  export class SimpleAppService<T extends SchemaFields> {
31
56
  // @Inject(EventEmitter2)
@@ -42,7 +67,7 @@ export class SimpleAppService<T extends SchemaFields> {
42
67
  protected docnogenerator: SimpleAppDocumentNoFormatService;
43
68
  protected logger = new Logger();
44
69
  protected strictIsolation = true;
45
- protected jsonschema: any = {
70
+ protected jsonschema: SimpleAppJsonSchema = {
46
71
  type: 'object',
47
72
  'x-simpleapp-config': {},
48
73
  properties: {},
@@ -57,12 +82,12 @@ export class SimpleAppService<T extends SchemaFields> {
57
82
  protected MAX_PAGE_SIZE = 100000;
58
83
  protected moreAutoCompleteField: MoreProjectionType = {};
59
84
  protected isolationtype: IsolationType = IsolationType.org;
60
- protected isolationFilter: any = {};
85
+ protected isolationFilter: FilterQuery<T> = {};
61
86
  protected data: T = { _id: '' } as T;
62
87
  protected doc: Model<T>; //set private to prevent developer break data isolation control
63
- protected errorlist = [];
88
+ protected errorlist: ErrorWithMessage[] = [];
64
89
  protected withDocNumberFormat = false;
65
- protected foreignkeys = {};
90
+ protected foreignkeys: ResourceForeignKeyDictionary = {};
66
91
  private eventEmitter: EventEmitter2;
67
92
  private logService: SimpleAppLogService;
68
93
  // protected userprovider = new UserContext() ;
@@ -91,7 +116,7 @@ export class SimpleAppService<T extends SchemaFields> {
91
116
  getDocumentType = () => this.documentType;
92
117
  getDocumentName = (capFirst: boolean = false) => (capFirst ? _.upperFirst(this.documentName) : this.documentName);
93
118
  getRecordId = (): string => this.data?._id ?? '';
94
- setSchema = (newschema) => (this.jsonschema = newschema);
119
+ setSchema = (newschema: SimpleAppJsonSchema) => (this.jsonschema = newschema);
95
120
  getSchema = () => this.doc.schema.obj;
96
121
  getJsonSchema = () => this.jsonschema;
97
122
 
@@ -110,19 +135,19 @@ export class SimpleAppService<T extends SchemaFields> {
110
135
  return false;
111
136
  }
112
137
  reCalculateValue(data: T) {}
113
- getIsolationFilter = (appUser: UserContext) => {
114
- let isolationFilter = {};
138
+ getIsolationFilter = (appUser: UserContext): FilterQuery<T> => {
139
+ let isolationFilter: FilterQuery<T> = {};
115
140
  switch (this.isolationtype) {
116
- case 'none':
141
+ case IsolationType.none:
117
142
  isolationFilter = {};
118
143
  break;
119
- case 'branch':
144
+ case IsolationType.branch:
120
145
  isolationFilter = appUser.getBranchFilter();
121
146
  break;
122
- case 'tenant':
147
+ case IsolationType.tenant:
123
148
  isolationFilter = appUser.getTenantFilter();
124
149
  break;
125
- case 'org':
150
+ case IsolationType.org:
126
151
  default:
127
152
  isolationFilter = appUser.getOrgFilter();
128
153
  break;
@@ -140,7 +165,7 @@ export class SimpleAppService<T extends SchemaFields> {
140
165
  // console.log(products);
141
166
  return productlist;
142
167
  } catch (err) {
143
- throw new InternalServerErrorException(err.message);
168
+ throw new InternalServerErrorException(getErrorMessage(err));
144
169
  }
145
170
  }
146
171
  addAutoCompleteField = (morefield: MoreProjectionType) => {
@@ -154,22 +179,22 @@ export class SimpleAppService<T extends SchemaFields> {
154
179
  };
155
180
  async getAutoComplete(appUser: UserContext, keyword: string, data?: T) {
156
181
  try {
157
- const filter1: any = {};
158
- const filter2: any = {};
159
- const filters: any[] = [];
160
- if (this.jsonschema.properties[this.documentIdentityCode]['type'] == 'string') {
182
+ const filter1: DynamicRecord = {};
183
+ const filter2: DynamicRecord = {};
184
+ const filters: DynamicRecord[] = [];
185
+ if (this.jsonschema.properties?.[this.documentIdentityCode]?.type == 'string') {
161
186
  filter1[this.documentIdentityCode] = { $regex: keyword, $options: 'i' };
162
187
  filters.push(filter1);
163
188
  }
164
189
  filter2[this.documentIdentityLabel] = { $regex: keyword, $options: 'i' };
165
190
  filters.push(filter2);
166
191
 
167
- const filterobj = { $or: filters };
192
+ const filterobj = { $or: filters } as FilterQuery<T>;
168
193
  Object.assign(filterobj, data);
169
194
  Object.assign(filterobj, this.getIsolationFilter(appUser));
170
195
  const projections = {
171
- label: `\$${this.documentIdentityLabel}`,
172
- code: `\$${this.documentIdentityCode}`,
196
+ label: `$${this.documentIdentityLabel}`,
197
+ code: `$${this.documentIdentityCode}`,
173
198
  };
174
199
  if (this.moreAutoCompleteField) {
175
200
  Object.assign(projections, this.moreAutoCompleteField);
@@ -183,7 +208,7 @@ export class SimpleAppService<T extends SchemaFields> {
183
208
  });
184
209
  return productlist;
185
210
  } catch (err) {
186
- throw new InternalServerErrorException(err.message);
211
+ throw new InternalServerErrorException(getErrorMessage(err));
187
212
  }
188
213
  }
189
214
 
@@ -205,7 +230,7 @@ export class SimpleAppService<T extends SchemaFields> {
205
230
  // console.log(products);
206
231
  return productlist;
207
232
  } catch (err) {
208
- throw new InternalServerErrorException(err.message);
233
+ throw new InternalServerErrorException(getErrorMessage(err));
209
234
  }
210
235
  // return this;
211
236
  }
@@ -224,7 +249,7 @@ export class SimpleAppService<T extends SchemaFields> {
224
249
  throw new InternalServerErrorException('first aggregate pipelinestage shall use $match');
225
250
  }
226
251
  }
227
- async aggregate<T = any>(appUser: UserContext, pipeline: PipelineStage[]) {
252
+ async aggregate<TAggregateResult = LooseRecord>(appUser: UserContext, pipeline: PipelineStage[]) {
228
253
  if (pipeline[0] && pipeline[0]['$match']) {
229
254
  try {
230
255
  const isolationFilter = { ...this.getIsolationFilter(appUser) };
@@ -236,7 +261,7 @@ export class SimpleAppService<T extends SchemaFields> {
236
261
  // if(appUser.getId() == ''){
237
262
  // console.log(JSON.stringify(pipeline))
238
263
  // }
239
- const res = await this.doc.aggregate<T>(pipeline, {
264
+ const res = await this.doc.aggregate<TAggregateResult>(pipeline, {
240
265
  session: appUser.getDBSession(),
241
266
  });
242
267
 
@@ -255,7 +280,7 @@ export class SimpleAppService<T extends SchemaFields> {
255
280
  appUser: UserContext,
256
281
  filters: FilterQuery<T>,
257
282
  projection: string[] = undefined,
258
- sort: any = undefined,
283
+ sort: SortOption = undefined,
259
284
  lookup: { [key: string]: string } = undefined,
260
285
  pagination?: { pageSize?: number; pageNo?: number },
261
286
  ) {
@@ -281,7 +306,7 @@ export class SimpleAppService<T extends SchemaFields> {
281
306
 
282
307
  searchResults = await query.exec();
283
308
  } else {
284
- const pipelines = this.searchToAggregate(appUser, newfilters, projection, sort, lookup, pagination);
309
+ const pipelines = this.searchToAggregate(appUser, newfilters, projection, this.toAggregateSort(sort), lookup, pagination);
285
310
  this.logger.debug('after aggregate', pipelines);
286
311
  searchResults = await this.aggregate(appUser, pipelines);
287
312
  }
@@ -295,7 +320,7 @@ export class SimpleAppService<T extends SchemaFields> {
295
320
  // console.log(products);
296
321
  return list;
297
322
  } catch (err) {
298
- throw new BadRequestException(err.message);
323
+ throw new BadRequestException(getErrorMessage(err));
299
324
  }
300
325
  // return this;
301
326
  }
@@ -308,7 +333,7 @@ export class SimpleAppService<T extends SchemaFields> {
308
333
  appUser: UserContext,
309
334
  filters: FilterQuery<T> = {},
310
335
  projection: string[] = undefined,
311
- sort: any = undefined,
336
+ sort: SortOption = undefined,
312
337
  lookup: { [key: string]: string } = undefined,
313
338
  pagination?: { pageSize?: number; pageNo?: number },
314
339
  ) {
@@ -345,7 +370,7 @@ export class SimpleAppService<T extends SchemaFields> {
345
370
  items = rows;
346
371
  total = count;
347
372
  } else {
348
- const pipelines = this.searchToAggregate(appUser, newfilters, projection as any, sort, lookup as any, { pageSize, pageNo });
373
+ const pipelines = this.searchToAggregate(appUser, newfilters, projection, this.toAggregateSort(sort), lookup, { pageSize, pageNo });
349
374
 
350
375
  const countPipeline: PipelineStage[] = [{ $match: newfilters }];
351
376
  const collections = Object.keys(lookup);
@@ -368,15 +393,15 @@ export class SimpleAppService<T extends SchemaFields> {
368
393
 
369
394
  this.logger.debug('after aggregate searchWithPageInfo', pipelines);
370
395
 
371
- const [rows, countResult] = await Promise.all([this.aggregate(appUser, pipelines), this.aggregate(appUser, countPipeline)]);
396
+ const [rows, countResult] = await Promise.all([this.aggregate<T>(appUser, pipelines), this.aggregate<AggregateTotalResult>(appUser, countPipeline)]);
372
397
 
373
- items = rows as T[];
374
- total = countResult && countResult.length > 0 ? countResult[0]['total'] || 0 : 0;
398
+ items = rows;
399
+ total = countResult && countResult.length > 0 ? countResult[0].total || 0 : 0;
375
400
  }
376
401
 
377
402
  return items;
378
403
  } catch (err) {
379
- throw new BadRequestException(err.message);
404
+ throw new BadRequestException(getErrorMessage(err));
380
405
  }
381
406
  }
382
407
 
@@ -441,7 +466,7 @@ export class SimpleAppService<T extends SchemaFields> {
441
466
  const keyword = body.keyword;
442
467
  // console.log("bodybody",body)
443
468
  // Prepare regex filter
444
- const orConditions: any[] = [];
469
+ const orConditions: DynamicRecord[] = [];
445
470
  const regex = new RegExp(keyword, 'i');
446
471
 
447
472
  if (body.filters && body.filters.length > 0) {
@@ -477,7 +502,7 @@ export class SimpleAppService<T extends SchemaFields> {
477
502
  async findById(appUser: UserContext, id: string) {
478
503
  // if (this.hooks.beforeFetchRecord) await this.hooks.beforeFetchRecord(appUser, id);
479
504
 
480
- const data = await this.search(appUser, { _id: id as any });
505
+ const data = await this.search(appUser, { _id: id });
481
506
  // if (this.hooks.afterFetchRecord) await this.hooks.afterFetchRecord(appUser, data[0]);
482
507
 
483
508
  if (data.length == 1) {
@@ -491,7 +516,7 @@ export class SimpleAppService<T extends SchemaFields> {
491
516
  async findByIdNoIsolation(appUser: UserContext, id: string) {
492
517
  // if (this.hooks.beforeFetchRecord) await this.hooks.beforeFetchRecord(appUser, id);
493
518
 
494
- const data = await this.searchNoIsolation(appUser, { _id: id as any });
519
+ const data = await this.searchNoIsolation(appUser, { _id: id });
495
520
  // if (this.hooks.afterFetchRecord) await this.hooks.afterFetchRecord(appUser, data[0]);
496
521
 
497
522
  if (data.length == 1) {
@@ -511,7 +536,7 @@ export class SimpleAppService<T extends SchemaFields> {
511
536
  if (Array.isArray(datas)) {
512
537
  for (let i = 0; i < datas.length; i++) {
513
538
  const data = datas[i];
514
- let isolationFilter: any = { ...appUser.getCreateFilter() };
539
+ let isolationFilter: FilterQuery<T> = { ...appUser.getCreateFilter() };
515
540
  isolationFilter = this.polishIsolationFilter(isolationFilter, data);
516
541
 
517
542
  Object.assign(data, isolationFilter);
@@ -524,28 +549,25 @@ export class SimpleAppService<T extends SchemaFields> {
524
549
  appUser.startTransaction();
525
550
  }
526
551
 
527
- try {
528
- const result = await this.doc.insertMany(datas, { session: dbsession });
529
- const allIds = datas.map((item) => item._id);
530
- const allLogData = datas.map((item) => null);
531
- appUser.addTransactionStep('createMany', this.documentName, allIds, allLogData);
532
-
533
- await this.addManyAuditEvents(appUser, this.documentName, 'createMany', datas);
534
- for (const data of datas) {
535
- appUser.addInsertedRecordId(this.documentName, data._id);
536
- }
552
+ const result = await this.doc.insertMany(datas, { session: dbsession });
553
+ const allIds = datas.map((item) => item._id);
554
+ const allLogData = datas.map((item) => null);
555
+ appUser.addTransactionStep('createMany', this.documentName, allIds, allLogData);
537
556
 
538
- return result;
539
- } catch (e) {
540
- throw e;
557
+ await this.addManyAuditEvents(appUser, this.documentName, 'createMany', datas);
558
+ for (const data of datas) {
559
+ appUser.addInsertedRecordId(this.documentName, data._id);
541
560
  }
561
+
562
+ return result;
563
+
542
564
  } else {
543
565
  throw new BadRequestException(this.getDocumentType() + ': create many only support array');
544
566
  }
545
567
  }
546
568
 
547
569
  async create(appUser: UserContext, data: T, noStartTransaction: boolean = false) {
548
- let result;
570
+ let result: T;
549
571
 
550
572
  if (!data._id) {
551
573
  data._id = crypto.randomUUID();
@@ -560,7 +582,7 @@ export class SimpleAppService<T extends SchemaFields> {
560
582
  await this.genNewDocNo(appUser, data);
561
583
  }
562
584
 
563
- let isolationFilter: any = { ...appUser.getCreateFilter() };
585
+ let isolationFilter: FilterQuery<T> = { ...appUser.getCreateFilter() };
564
586
  isolationFilter = this.polishIsolationFilter(isolationFilter, data);
565
587
 
566
588
  this.logger.debug('isolationFilter', 'SimpleAppService');
@@ -580,36 +602,41 @@ export class SimpleAppService<T extends SchemaFields> {
580
602
  const newdoc = new this.doc(data);
581
603
  await this.identifyForeignKeys(appUser, data);
582
604
  try {
583
- result = await newdoc.save({ session: dbsession });
605
+ result = (await newdoc.save({ session: dbsession })) as T;
606
+ const resultId = result._id ?? data._id ?? '';
584
607
  appUser.addTransactionStep('create', this.documentName, [data._id], [null]);
585
- await this.addAuditEvent(appUser, this.documentName, result._id, 'create', data);
586
- appUser.addInsertedRecordId(this.documentName, result._id);
608
+ await this.addAuditEvent(appUser, this.documentName, resultId, 'create', data);
609
+ appUser.addInsertedRecordId(this.documentName, resultId);
587
610
  await this.runEvent(appUser, 'afterCreate', { data: result }, false);
588
611
  await this.callWebhook(appUser, 'create', result);
589
- return result as T;
612
+ return result;
590
613
  } catch (err) {
591
614
  await this.runErrorEvent(appUser, 'errorCreate', data, err);
592
615
  throw new InternalServerErrorException(`createHook ${this.documentType} error: ${JSON.stringify(err)}`, `${this.documentType} createHook error`);
593
616
  }
594
617
  }
595
618
 
596
- applyNestedDateTime = (appUser: UserContext, data: any, transtype: string) => {
597
- const props = Object.getOwnPropertyNames(data);
619
+ applyNestedDateTime = (appUser: UserContext, data: object, transtype: string) => {
620
+ const record = data as DynamicRecord;
621
+ const props = Object.getOwnPropertyNames(record);
598
622
  for (let i = 0; i < props.length; i++) {
599
623
  const key = props[i];
600
624
  //need to apply nested
601
- if (Array.isArray(data[key]) && data[key].length > 0 && typeof data[key][0] == 'object') {
602
- for (let j = 0; j < data[key].length; j++) {
603
- this.applyNestedDateTime(appUser, data[key][j], transtype);
625
+ const value = record[key];
626
+ if (Array.isArray(value) && value.length > 0 && typeof value[0] == 'object' && value[0] !== null) {
627
+ for (let j = 0; j < value.length; j++) {
628
+ if (typeof value[j] == 'object' && value[j] !== null) {
629
+ this.applyNestedDateTime(appUser, value[j] as DynamicRecord, transtype);
630
+ }
604
631
  }
605
632
  } else if (key == 'created') {
606
- data['created'] = transtype == 'create' || !data['created'] ? new Date().toISOString() : data['created'];
633
+ record['created'] = transtype == 'create' || !record['created'] ? new Date().toISOString() : record['created'];
607
634
  } else if (key == 'createdBy') {
608
- data['createdBy'] = transtype == 'create' || !data['createdBy'] ? appUser.getUid() : data['createdBy'];
635
+ record['createdBy'] = transtype == 'create' || !record['createdBy'] ? appUser.getUid() : record['createdBy'];
609
636
  } else if (key == 'updated') {
610
- data['updated'] = new Date().toISOString();
637
+ record['updated'] = new Date().toISOString();
611
638
  } else if (key == 'updatedBy') {
612
- data['updatedBy'] = appUser.getUid();
639
+ record['updatedBy'] = appUser.getUid();
613
640
  }
614
641
  }
615
642
  };
@@ -631,26 +658,14 @@ export class SimpleAppService<T extends SchemaFields> {
631
658
  ajv.addKeyword({ keyword: 'x-remote', schemaType: 'object' });
632
659
  ajv.addKeyword({ keyword: 'x-readonly', schemaType: 'boolean' });
633
660
  this.logger.debug('run hook during validation');
634
- const issuccess = true;
635
- // if (this.hooks.beforeValidation) {
636
- // issuccess = await this.hooks.beforeValidation(appUser, data, _id);
637
- // }
638
- // const issuccess = await this.hook(appUser, HookType.beforeValidation, data);
639
- if (!issuccess) {
640
- const errormsg: string[] = [];
641
- for (let i = 0; i < this.errorlist.length; i++) {
642
- errormsg.push(this.errorlist[i].message);
643
- }
644
-
645
- throw new BadRequestException(errormsg as HttpExceptionOptions, 'Before validation hook failed');
646
- }
661
+ await this.runEvent(appUser, 'beforeValidation', { data, id: _id }, false);
647
662
 
648
- let validate;
663
+ let validate: ReturnType<Ajv['compile']>;
649
664
  try {
650
665
  validate = ajv.compile(this.jsonschema);
651
666
  } catch (err) {
652
667
  this.logger.error('compile error', err);
653
- throw new ForbiddenException(err.message);
668
+ throw new ForbiddenException(getErrorMessage(err));
654
669
  }
655
670
  const valid = validate(data);
656
671
  if (!valid) {
@@ -660,13 +675,13 @@ export class SimpleAppService<T extends SchemaFields> {
660
675
  }
661
676
  }
662
677
 
663
- polishIsolationFilter = (filterIsolation: any, data: any = {}) => {
664
- if (this.isolationtype == 'none') {
678
+ polishIsolationFilter = <TFilter extends FilterQuery<T>>(filterIsolation: TFilter, data: Partial<T> = {}): TFilter => {
679
+ if (this.isolationtype == IsolationType.none) {
665
680
  delete filterIsolation['branchId'];
666
681
  delete filterIsolation['orgId'];
667
682
  delete filterIsolation['tenantId'];
668
683
  }
669
- if (this.isolationtype == 'tenant' && !this.strictIsolation) {
684
+ if (this.isolationtype == IsolationType.tenant && !this.strictIsolation) {
670
685
  // delete filterIsolation['tenantId']
671
686
  if (data['tenantId']) {
672
687
  filterIsolation['tenantId'] = data['tenantId'];
@@ -674,7 +689,7 @@ export class SimpleAppService<T extends SchemaFields> {
674
689
  delete filterIsolation['branchId'];
675
690
  delete filterIsolation['orgId'];
676
691
  }
677
- if (this.isolationtype == 'org' && !this.strictIsolation) {
692
+ if (this.isolationtype == IsolationType.org && !this.strictIsolation) {
678
693
  // delete filterIsolation['tenantId']
679
694
  if (data['tenantId']) {
680
695
  filterIsolation['tenantId'] = data['tenantId'];
@@ -687,7 +702,7 @@ export class SimpleAppService<T extends SchemaFields> {
687
702
  }
688
703
  return filterIsolation;
689
704
  };
690
- async findIdThenDelete(appUser: UserContext, id: string): Promise<any> {
705
+ async findIdThenDelete(appUser: UserContext, id: string): Promise<LooseRecord> {
691
706
  const deletedata = await this.findById(appUser, id);
692
707
  if (!deletedata) {
693
708
  throw new NotFoundException(`${id} not found`, 'not found');
@@ -794,12 +809,12 @@ export class SimpleAppService<T extends SchemaFields> {
794
809
  // Add audit event
795
810
  await this.addAuditEvent(appUser, this.documentName, id, 'update', data);
796
811
 
797
- appUser.addUpdatedRecordId(this.documentName, data._id);
812
+ appUser.addUpdatedRecordId(this.documentName, id);
798
813
 
799
814
  return result;
800
815
  } catch (err) {
801
816
  this.logger.error(err);
802
- throw new InternalServerErrorException(err.message);
817
+ throw new InternalServerErrorException(getErrorMessage(err));
803
818
  }
804
819
  };
805
820
 
@@ -862,7 +877,7 @@ export class SimpleAppService<T extends SchemaFields> {
862
877
  } catch (err) {
863
878
  this.logger.error(err);
864
879
  await this.runErrorEvent(appUser, 'errorUpdate', data, err);
865
- throw new InternalServerErrorException(err.message);
880
+ throw new InternalServerErrorException(getErrorMessage(err));
866
881
  }
867
882
  };
868
883
 
@@ -903,7 +918,7 @@ export class SimpleAppService<T extends SchemaFields> {
903
918
  // existingdata['_id']=''
904
919
  await this.validateData(appUser, data, id);
905
920
 
906
- const isolationFilter = {};
921
+ const isolationFilter: FilterQuery<T> = {};
907
922
  // this.polishIsolationFilter(isolationFilter);
908
923
  isolationFilter['_id'] = id;
909
924
  this.applyNestedDateTime(appUser, data, 'update');
@@ -923,7 +938,7 @@ export class SimpleAppService<T extends SchemaFields> {
923
938
  return result; // await this.findById(appUser, id);
924
939
  } catch (err) {
925
940
  this.logger.error(err);
926
- throw new InternalServerErrorException(err.message);
941
+ throw new InternalServerErrorException(getErrorMessage(err));
927
942
  }
928
943
  };
929
944
 
@@ -961,7 +976,7 @@ export class SimpleAppService<T extends SchemaFields> {
961
976
  // existingdata['_id']=''
962
977
  // console.log("newdata",data)
963
978
  //path record no validation
964
- // await this.validateData(appUser, data);
979
+ // this.validateData(appUser, data);
965
980
 
966
981
  const isolationFilter = { ...this.getIsolationFilter(appUser) };
967
982
  this.polishIsolationFilter(isolationFilter);
@@ -983,9 +998,9 @@ export class SimpleAppService<T extends SchemaFields> {
983
998
  await this.runEvent(appUser, 'afterPatch', { id: id, patchData: data, prevData: existingdata }, false);
984
999
  return result; //await this.findById(appUser, id);
985
1000
  } catch (err) {
986
- this.logger.error(err.message, 'findIdThenPath error');
1001
+ this.logger.error(getErrorMessage(err), 'findIdThenPath error');
987
1002
  console.error(err);
988
- throw new InternalServerErrorException(err.message);
1003
+ throw new InternalServerErrorException(getErrorMessage(err));
989
1004
  }
990
1005
  };
991
1006
  async deleteMany(appUser: UserContext, filter: FilterQuery<T>) {
@@ -1016,9 +1031,9 @@ export class SimpleAppService<T extends SchemaFields> {
1016
1031
  // };
1017
1032
 
1018
1033
  const filter = data.filter;
1019
- const patch = data.data as object;
1034
+ const patch = data.data as DynamicRecord;
1020
1035
 
1021
- const isolationFilter = { ...this.getIsolationFilter(appUser), ...(filter || {}) };
1036
+ const isolationFilter = { ...this.getIsolationFilter(appUser), ...(filter || {}) } as FilterQuery<T>;
1022
1037
  this.polishIsolationFilter(isolationFilter);
1023
1038
  this.applyNestedDateTime(appUser, patch, 'update');
1024
1039
 
@@ -1047,7 +1062,7 @@ export class SimpleAppService<T extends SchemaFields> {
1047
1062
  throw new InternalServerErrorException('foreignkeys object undetected');
1048
1063
  }
1049
1064
 
1050
- const foreignkeysettings = foreignkeys[this.documentName];
1065
+ const foreignkeysettings = (foreignkeys as GlobalForeignKeyDictionary)[this.documentName];
1051
1066
  if (!foreignkeysettings) {
1052
1067
  return null;
1053
1068
  }
@@ -1064,14 +1079,14 @@ export class SimpleAppService<T extends SchemaFields> {
1064
1079
  for (let j = 0; j < fobjs.length; j++) {
1065
1080
  const fkey = fobjs[j];
1066
1081
  //not deleted in current session, check from database
1067
- const filter = {};
1082
+ const filter: Record<string, string> = {};
1068
1083
  filter[fkey] = id;
1069
- const result: any = await collection.findOne(filter, {
1084
+ const result = (await collection.findOne(filter, {
1070
1085
  session: appUser.getDBSession(),
1071
- });
1086
+ })) as unknown as RelatedRecord | null;
1072
1087
  if (result) {
1073
1088
  //record deleted but not yet commit, safely ignore
1074
- if (appUser.searchDeletedRecordId(collectionname, result._id)) continue;
1089
+ if (appUser.searchDeletedRecordId(collectionname, String(result._id ?? ''))) continue;
1075
1090
 
1076
1091
  this.logger.error(result, `related result found in ${collectionname} ${fkey} = ${id}`);
1077
1092
  return result;
@@ -1085,7 +1100,7 @@ export class SimpleAppService<T extends SchemaFields> {
1085
1100
  /**
1086
1101
  * dummy ping
1087
1102
  */
1088
- ping(...data) {
1103
+ ping(...data: unknown[]) {
1089
1104
  return `hello ${JSON.stringify(data)}`;
1090
1105
  }
1091
1106
 
@@ -1133,6 +1148,7 @@ export class SimpleAppService<T extends SchemaFields> {
1133
1148
  }
1134
1149
  } catch (e) {
1135
1150
  await this.runErrorEvent(appUser, 'errorSetStatus', data, e);
1151
+ throw e;
1136
1152
  }
1137
1153
  }
1138
1154
 
@@ -1145,7 +1161,7 @@ export class SimpleAppService<T extends SchemaFields> {
1145
1161
  * @param {string} eventName The event name
1146
1162
  * @param {any} data The data
1147
1163
  */
1148
- runBackgroundWorker(appUser: UserContext, eventName: string, payloads: any) {
1164
+ runBackgroundWorker(appUser: UserContext, eventName: string, payloads: unknown) {
1149
1165
  this.eventEmitter.emit(eventName, appUser, payloads);
1150
1166
  }
1151
1167
 
@@ -1165,7 +1181,7 @@ export class SimpleAppService<T extends SchemaFields> {
1165
1181
  * @param {any} data The data
1166
1182
  * @return {Promise} { description_of_the_return_value }
1167
1183
  */
1168
- async runEvent(appUser: UserContext, shortEventName: string, payloads: any, enforce: boolean = true) {
1184
+ async runEvent(appUser: UserContext, shortEventName: string, payloads: unknown, enforce: boolean = true): Promise<unknown> {
1169
1185
  const eventName = this.setHookName(shortEventName);
1170
1186
  try {
1171
1187
  if (enforce && !this.eventEmitter.hasListeners(eventName)) {
@@ -1178,8 +1194,9 @@ export class SimpleAppService<T extends SchemaFields> {
1178
1194
  throw new InternalServerErrorException(`${eventName} is invalid worker`);
1179
1195
  }
1180
1196
 
1181
- const result = res[0];
1182
- if (result?.name && result?.name.includes('Exception')) throw result;
1197
+ const result: unknown = res[0];
1198
+ if (result instanceof Error) throw result;
1199
+ if (isEventResult(result) && result.name?.includes('Exception')) throw new InternalServerErrorException(result.name);
1183
1200
  return result;
1184
1201
  }
1185
1202
  } catch (e) {
@@ -1188,12 +1205,12 @@ export class SimpleAppService<T extends SchemaFields> {
1188
1205
  }
1189
1206
  }
1190
1207
 
1191
- async runErrorEvent(appUser: UserContext, shortEventName: string, payloads: any, error: any) {
1208
+ async runErrorEvent(appUser: UserContext, shortEventName: string, payloads: unknown, error: unknown) {
1192
1209
  const eventName = this.setHookName(shortEventName);
1193
1210
  try {
1194
1211
  if (this.eventEmitter.hasListeners(eventName)) {
1195
1212
  console.log('run eventName', eventName);
1196
- const res = await this.eventEmitter.emitAsync(eventName, appUser, payloads, error);
1213
+ await this.eventEmitter.emitAsync(eventName, appUser, payloads, error);
1197
1214
  }
1198
1215
  } catch (e) {
1199
1216
  //runErrorEvent wont handle more error
@@ -1207,10 +1224,10 @@ export class SimpleAppService<T extends SchemaFields> {
1207
1224
  this.logger.debug(result, 'genNewDocNo');
1208
1225
 
1209
1226
  // been for to convert become object
1210
- (data as any)[this.documentIdentityCode] = result;
1227
+ (data as DynamicRecord)[this.documentIdentityCode] = result;
1211
1228
  }
1212
- async runDefault(appUser: UserContext): Promise<unknown> {
1213
- return 'Hello this is ' + this.getDocumentType() + ': ' + this.getDocumentName();
1229
+ async runDefault(appUser: UserContext): Promise<string> {
1230
+ return await Promise.resolve('Hello this is ' + this.getDocumentType() + ': ' + this.getDocumentName());
1214
1231
  }
1215
1232
  async identifyForeignKeys(appUser: UserContext, data: Partial<T>) {
1216
1233
  /**
@@ -1218,21 +1235,19 @@ export class SimpleAppService<T extends SchemaFields> {
1218
1235
  * 2. loop through record obtain all foreign key value
1219
1236
  * 3. get all unique key value in array {product:['xxxx','yyyy'],customer:['aaa']}
1220
1237
  */
1221
- const schema = this.jsonschema;
1222
-
1223
1238
  //get all foreign keys catalogue
1224
1239
  const collections = Object.getOwnPropertyNames(this.foreignkeys);
1225
1240
 
1226
1241
  //obtain exists data in according foreign key
1227
1242
  const pipelines: PipelineStage[] = [{ $match: { _id: false } }]; //exclude data from current collection
1228
1243
  const vdata = data; //['_doc']
1229
- const keystore = {};
1244
+ const keystore: Record<string, string[]> = {};
1230
1245
  collections.forEach((collectionname) => {
1231
1246
  const fks: string[] = this.foreignkeys[collectionname];
1232
1247
  let results: string[] = [];
1233
1248
  fks.forEach((fieldpath) => {
1234
1249
  //console.log("fieldpath:",fieldpath,"vdata",data,vdata)
1235
- const tmp = jsonpath.query(vdata, fieldpath).filter((item: string) => item != '');
1250
+ const tmp = (jsonpath.query(vdata, fieldpath) as unknown[]).filter((item): item is string => typeof item === 'string' && item != '');
1236
1251
  // console.log("tmp",tmp)
1237
1252
 
1238
1253
  results = results.concat(tmp);
@@ -1262,10 +1277,10 @@ export class SimpleAppService<T extends SchemaFields> {
1262
1277
 
1263
1278
  if (!unionresult) {
1264
1279
  this.logger.error('foreign key control failed ', 'identifyForeignKeys');
1265
- throw new InternalServerErrorException(pipelines as HttpExceptionOptions, 'Foreignkey check execution error');
1280
+ throw new InternalServerErrorException(pipelines, 'Foreignkey check execution error');
1266
1281
  } else {
1267
- const searchresult: any = {};
1268
- unionresult.forEach((item) => {
1282
+ const searchresult: Record<string, string[]> = {};
1283
+ (unionresult as ForeignKeyAggregateRow[]).forEach((item) => {
1269
1284
  if (searchresult[item.collection]) {
1270
1285
  searchresult[item.collection].push(item._id);
1271
1286
  } else {
@@ -1308,7 +1323,7 @@ export class SimpleAppService<T extends SchemaFields> {
1308
1323
  async checkUniqueKeyExist(appUser: UserContext, data: string[]): Promise<UniqueKeyExistResponse[]> {
1309
1324
  const response: UniqueKeyExistResponse[] = [];
1310
1325
  const unionKey = this.getDocumentIdentityCode();
1311
- const searchQuery: any = { [unionKey]: { $in: data } };
1326
+ const searchQuery = { [unionKey]: { $in: data } } as FilterQuery<T>;
1312
1327
  // search for multiple union exist
1313
1328
  const result = await this.search(appUser, searchQuery);
1314
1329
  for (const item of data) {
@@ -1321,7 +1336,7 @@ export class SimpleAppService<T extends SchemaFields> {
1321
1336
 
1322
1337
  searchToAggregate(appUser: UserContext, filter: FilterQuery<T>, columns: string[], sort: string[][], lookup: { [key: string]: string }, pagination?: { pageSize?: number; pageNo?: number }) {
1323
1338
  const pipelines: PipelineStage[] = [];
1324
- const projection = {};
1339
+ const projection: Record<string, 1> = {};
1325
1340
  // console.log('sortsort', sort);
1326
1341
 
1327
1342
  pipelines.push({ $match: filter });
@@ -1356,7 +1371,7 @@ export class SimpleAppService<T extends SchemaFields> {
1356
1371
  if (Object.keys(projection).length > 0) pipelines.push({ $project: projection });
1357
1372
 
1358
1373
  if (Array.isArray(sort) && sort.length > 0) {
1359
- const sortobj = {};
1374
+ const sortobj: Record<string, 1 | -1> = {};
1360
1375
  sort.forEach((item) => {
1361
1376
  sortobj[item[0]] = item[1].toLowerCase() == 'asc' ? 1 : -1;
1362
1377
  });
@@ -1374,12 +1389,19 @@ export class SimpleAppService<T extends SchemaFields> {
1374
1389
  return pipelines;
1375
1390
  }
1376
1391
 
1377
- setHookName(hookName) {
1378
- const resourceName = this.jsonschema['x-simpleapp-config']['resourceName'];
1392
+ private toAggregateSort(sort: SortOption): string[][] {
1393
+ if (Array.isArray(sort)) return sort.map(([field, direction]) => [field, String(direction)]);
1394
+ if (typeof sort === 'string') return [];
1395
+ if (sort && typeof sort === 'object') return Object.entries(sort).map(([field, direction]) => [field, String(direction)]);
1396
+ return [];
1397
+ }
1398
+
1399
+ setHookName(hookName: string) {
1400
+ const resourceName = this.jsonschema['x-simpleapp-config'].resourceName ?? '';
1379
1401
  return camelToKebab(resourceName) + '.' + camelToKebab(hookName);
1380
1402
  }
1381
1403
  //only realtime webhook supported at this moment
1382
- async callWebhook(appUser: UserContext, actionName: string, data: any) {
1404
+ async callWebhook(appUser: UserContext, actionName: string, data: unknown) {
1383
1405
  try {
1384
1406
  await this.runWebHook.run(appUser, this.documentName, actionName, data);
1385
1407
  } catch (e) {
@@ -1387,10 +1409,10 @@ export class SimpleAppService<T extends SchemaFields> {
1387
1409
  }
1388
1410
  }
1389
1411
 
1390
- async addAuditEvent(appUser: UserContext, documentName: string, id: string, eventType: string, data: any) {
1412
+ async addAuditEvent(appUser: UserContext, documentName: string, id: string, eventType: string, data: unknown) {
1391
1413
  await this.logService.addEvent(appUser, documentName, id, eventType, data);
1392
1414
  }
1393
- async addManyAuditEvents(appUser: UserContext, documentName: string, eventType: string, datas: any) {
1415
+ async addManyAuditEvents(appUser: UserContext, documentName: string, eventType: string, datas: unknown[]) {
1394
1416
  await this.logService.addManyEvents(appUser, documentName, eventType, datas);
1395
1417
  }
1396
1418
 
@@ -1462,6 +1484,6 @@ export class SimpleAppService<T extends SchemaFields> {
1462
1484
  });
1463
1485
  }
1464
1486
 
1465
- return await this.aggregate(appUser, pipeline);
1487
+ return await this.aggregate<T>(appUser, pipeline);
1466
1488
  }
1467
1489
  }
@@ -4,153 +4,158 @@
4
4
  * last change 2023-09-10
5
5
  * author: Ks Tan
6
6
  */
7
- import _ from 'lodash'
8
- import {MenuData} from '~/types'
9
- import {getAllDocuments} from '../simpleapp/generate/commons/documents'
10
- export const getDocTypes = ()=>{
11
- return getAllDocuments().filter(item=>item.page!='')
12
- }
13
- export const getAllDocFormats = ()=>{
14
- return getAllDocuments().filter(item=>item.docNumber)
15
- }
16
-
17
- export const getPublicResource = (xorg:string) : MenuData[] => {
7
+ import { MenuData } from "~/types";
8
+ import { getAllDocuments } from "../simpleapp/generate/commons/documents";
9
+ export const getDocTypes = () => {
10
+ return getAllDocuments().filter((item) => item.page != "");
11
+ };
12
+ export const getAllDocFormats = () => {
13
+ return getAllDocuments().filter((item) => item.docNumber);
14
+ };
15
+
16
+ export const getPublicResource = (xorg: string): MenuData[] => {
18
17
  // const xorg = getUserStore().getCurrentXorg()
19
- const menus:MenuData[] = [
20
- {label: 'Home',icon: 'pi pi-fw pi-home', url:`/${xorg}`},
21
- {label: "Profile", url:`/profile`, isolationType:'none', icon:''},
22
- {label: "Profile", url:`/${xorg}/profile`, isolationType:'none', icon:''},
23
- {label: 'Signout',icon: 'pi pi-fw pi-home', command: () => logout()},
24
- {label: 'Signin',icon: 'pi pi-fw pi-home', 'url':'/login'},
25
- ]
26
- return menus
27
- }
28
- export const getMenus =() :MenuData[]=>{
29
- // const logout = async () => {
30
- // const { signOut } = useAuth();
31
- // const { data } = await <any>useFetch('/api/auth/logout');
32
- // const addPath = encodeURIComponent("/login");
33
- // signOut({ redirect: false });
34
- // window.location.href = data?.value?.path + addPath;
35
- // };
36
-
37
- // const route = useRoute();
38
- const xorg = getUserStore().getCurrentXorg()
39
- // let data:MenuData[] =[];
40
- // //sss
41
- let allmenus = getAllDocuments().filter(item=>item.page!='')
42
-
43
- const roles = getUserProfile().roles
44
- let allowmenus=[]
45
- for(let i=0; i< allmenus.length;i++){
46
- const keyword = allmenus[i].docName
18
+ const menus: MenuData[] = [
19
+ { label: "Home", icon: "pi pi-fw pi-home", url: `/${xorg}` },
20
+ { label: "Profile", url: `/profile`, isolationType: "none", icon: "" },
21
+ { label: "Profile", url: `/${xorg}/profile`, isolationType: "none", icon: "" },
22
+ { label: "Signout", icon: "pi pi-fw pi-home", command: () => logout() },
23
+ { label: "Signin", icon: "pi pi-fw pi-home", url: "/login" },
24
+ ];
25
+ return menus;
26
+ };
27
+ export const getMenus = (): MenuData[] => {
28
+ // const logout = async () => {
29
+ // const { signOut } = useAuth();
30
+ // const { data } = await <any>useFetch('/api/auth/logout');
31
+ // const addPath = encodeURIComponent("/login");
32
+ // signOut({ redirect: false });
33
+ // window.location.href = data?.value?.path + addPath;
34
+ // };
35
+
36
+ // const route = useRoute();
37
+ const xorg = getUserStore().getCurrentXorg();
38
+ // let data:MenuData[] =[];
39
+ // //sss
40
+ let allmenus = getAllDocuments().filter((item) => item.page != "");
41
+
42
+ const roles = getUserProfile().roles;
43
+ let allowmenus = [];
44
+ for (let i = 0; i < allmenus.length; i++) {
45
+ const keyword = allmenus[i].docName;
47
46
  // if(m.label)
48
-
49
- if(getUserStore().haveAccess(keyword)){
50
- const m:MenuData = {label: t(keyword), url:`/${xorg}/${keyword}`, isolationType:allmenus[i].isolationType}
51
- allowmenus.push(m)
52
- }
47
+
48
+ if (getUserStore().haveAccess(keyword)) {
49
+ const m: MenuData = {
50
+ label: t(keyword),
51
+ url: `/${xorg}/${keyword}`,
52
+ isolationType: allmenus[i].isolationType,
53
+ };
54
+ allowmenus.push(m);
55
+ }
53
56
  }
54
- // data = publicMenus()
55
- // if(xorg){
56
- // data.push({label: 'Cruds',icon: 'pi pi-fw pi-pencil',items:allowmenus})
57
- // }
58
- // console.log('data',data)
59
- return allowmenus
60
- }
61
-
62
-
63
- export const getMenustFromPageMeta =() =>{
64
- const allmenus = useRouter().getRoutes()
65
- .filter((item)=>(item.meta && item.meta.menuPath))
66
- .map(item=>item.meta.menuPath) as string[]
67
-
68
- return allmenus.sort((one:string, two:string) => (one > two ? -1 : 1))
69
-
70
- }
57
+ // data = publicMenus()
58
+ // if(xorg){
59
+ // data.push({label: 'Cruds',icon: 'pi pi-fw pi-pencil',items:allowmenus})
60
+ // }
61
+ // console.log('data',data)
62
+ return allowmenus;
63
+ };
64
+
65
+ export const getMenustFromPageMeta = () => {
66
+ const allmenus = useRouter()
67
+ .getRoutes()
68
+ .filter((item) => item.meta && item.meta.menuPath)
69
+ .map((item) => item.meta.menuPath) as string[];
70
+
71
+ return allmenus.sort((one: string, two: string) => (one > two ? -1 : 1));
72
+ };
71
73
  export const hasAccessByPageMeta = (pageName: string): boolean => {
72
- const userGroups = getUserStore().groups || []
73
- const userRoles = getUserStore().roles || []
74
-
74
+ const userGroups = getUserStore().groups || [];
75
+ const userRoles = getUserStore().roles || [];
76
+
75
77
  if (
76
- userRoles.includes('superadmin') ||
77
- userRoles.includes('tenantowner') ||
78
- userRoles.includes('superuser')
78
+ userRoles.includes("superadmin") ||
79
+ userRoles.includes("tenantowner") ||
80
+ userRoles.includes("superuser")
79
81
  ) {
80
- return true
82
+ return true;
81
83
  }
82
-
83
- const routes = useRouter().getRoutes()
84
+
85
+ const routes = useRouter().getRoutes();
84
86
  const route = routes.find((r) => {
85
- const menuPath = r.meta?.menuPath as string | undefined
86
- return menuPath && menuPath.endsWith(`/${pageName}`)
87
- })
88
-
87
+ const menuPath = r.meta?.menuPath as string | undefined;
88
+ return menuPath && menuPath.endsWith(`/${pageName}`);
89
+ });
90
+
89
91
  if (!route || !route.meta) {
90
- return false
92
+ return false;
91
93
  }
92
-
93
- const requiredGroups = route.meta.requiredGroups as string[] | undefined
94
-
94
+
95
+ const requiredGroups = route.meta.requiredGroups as string[] | undefined;
96
+
95
97
  if (!requiredGroups || requiredGroups.length === 0) {
96
- return false
98
+ return false;
97
99
  }
98
-
99
- return requiredGroups.some((group) => userGroups.includes(group))
100
- }
100
+
101
+ const hasRole = requiredGroups.some((group) => userRoles.includes(group));
102
+ if (hasRole) return hasRole;
103
+
104
+ return requiredGroups.some((group) => userGroups.includes(group));
105
+ };
101
106
 
102
107
  export const getMenusWithPageMetaAccess = (xorg: string): MenuData[] => {
103
- const routes = useRouter().getRoutes()
104
- const allowedMenus: MenuData[] = []
105
- const { getDescription, getIcon, getIconColor } = useSettingsMenu()
106
-
107
- const menuRoutes = routes.filter((route) => route.meta && route.meta.menuPath)
108
-
108
+ const routes = useRouter().getRoutes();
109
+ const allowedMenus: MenuData[] = [];
110
+ const { getDescription, getIcon, getIconColor } = useSettingsMenu();
111
+
112
+ const menuRoutes = routes.filter((route) => route.meta && route.meta.menuPath);
113
+
109
114
  for (const route of menuRoutes) {
110
- const menuPath = route.meta.menuPath as string
111
- const requiredGroups = route.meta.requiredGroups as string[] | undefined
112
-
113
- const pathParts = menuPath.split('/')
114
- const pageName = pathParts[pathParts.length - 1]
115
-
115
+ const menuPath = route.meta.menuPath as string;
116
+ const requiredGroups = route.meta.requiredGroups as string[] | undefined;
117
+
118
+ const pathParts = menuPath.split("/");
119
+ const pageName = pathParts[pathParts.length - 1];
120
+
116
121
  if (hasAccessByPageMeta(pageName)) {
117
- const icon = getIcon(pageName)
118
- const iconClass = getIconColor(pageName)
119
-
122
+ const icon = getIcon(pageName);
123
+ const iconClass = getIconColor(pageName);
124
+
120
125
  const menuItem: MenuData = {
121
126
  label: t(pageName),
122
127
  url: `/${xorg}/${pageName}`,
123
- isolationType: 'none',
124
- icon: typeof icon === 'string' ? icon : '',
125
- }
126
-
127
- allowedMenus.push(menuItem)
128
+ isolationType: "none",
129
+ icon: typeof icon === "string" ? icon : "",
130
+ };
131
+
132
+ allowedMenus.push(menuItem);
128
133
  }
129
134
  }
130
-
131
- return allowedMenus
132
- }
135
+
136
+ return allowedMenus;
137
+ };
133
138
 
134
139
  export const getMenuMetadata = (pageName: string) => {
135
- const routes = useRouter().getRoutes()
140
+ const routes = useRouter().getRoutes();
136
141
  const route = routes.find((r) => {
137
- const menuPath = r.meta?.menuPath as string | undefined
138
- return menuPath && menuPath.endsWith(`/${pageName}`)
139
- })
140
-
142
+ const menuPath = r.meta?.menuPath as string | undefined;
143
+ return menuPath && menuPath.endsWith(`/${pageName}`);
144
+ });
145
+
141
146
  if (route?.meta) {
142
147
  return {
143
148
  description: route.meta.description ? t(route.meta.description as string) : t(pageName),
144
- icon: route.meta.icon || '',
145
- colorClass: route.meta.colorClass || '',
146
- }
149
+ icon: route.meta.icon || "",
150
+ colorClass: route.meta.colorClass || "",
151
+ };
147
152
  }
148
-
149
- const { getDescription, getIcon, getIconColor } = useSettingsMenu()
150
-
153
+
154
+ const { getDescription, getIcon, getIconColor } = useSettingsMenu();
155
+
151
156
  return {
152
157
  description: t(getDescription(pageName)),
153
158
  icon: getIcon(pageName),
154
159
  colorClass: getIconColor(pageName),
155
- }
156
- }
160
+ };
161
+ };
@@ -38,7 +38,10 @@
38
38
  {{ permission }}
39
39
  </span>
40
40
  </div>
41
- <div v-else class="flex items-center gap-2">
41
+ <div
42
+ v-else
43
+ class="flex items-center gap-2"
44
+ >
42
45
  <span>|</span>
43
46
  <span
44
47
  v-for="permission in ['lite', 'pro', 'enterprise']"
@@ -51,13 +54,9 @@
51
54
  <p class="text-gray-600">{{ miniApp.intro.description }}</p>
52
55
 
53
56
  <div class="flex items-center gap-1">
54
- <template
55
- v-for="[feature, enabled] in Object.entries(
56
- miniApp.integration.enabled,
57
- )"
58
- >
57
+ <template v-for="[feature, enabled] in Object.entries(miniApp.integration.enabled)">
59
58
  <span
60
- v-if="enabled"
59
+ v-if="enabled && $te(`miniAppLang.integrationFeature.${feature}`)"
61
60
  class="px-3 py-0.5 bg-primary-50 text-primary-500 font-semibold text-xs rounded uppercase cursor-pointer"
62
61
  @click="() => handleOpenFeatureDialog(feature, enabled)"
63
62
  >
@@ -238,14 +237,14 @@
238
237
  </template>
239
238
 
240
239
  <script setup lang="ts">
241
- import _, { min } from "lodash";
242
- import { MiniAppDetail } from "~/simpleapp/generate/openapi";
243
- import MiniAppPermissionWrapper from "../MiniAppPermissionWrapper.vue";
240
+ import _ from "lodash";
244
241
  import UndrawNodata from "~/components/icon/UndrawNodata.vue";
245
- import MiniAppIcon from "../MiniAppIcon.vue";
246
- import MiniAppFeatureCustomPage from "../MiniAppFeatureCustomPage.vue";
242
+ import { MiniAppDetail } from "~/simpleapp/generate/openapi";
247
243
  import MiniAppFeatureCustomField from "../MiniAppFeatureCustomField.vue";
244
+ import MiniAppFeatureCustomPage from "../MiniAppFeatureCustomPage.vue";
248
245
  import MiniAppFeatureScope from "../MiniAppFeatureScope.vue";
246
+ import MiniAppIcon from "../MiniAppIcon.vue";
247
+ import MiniAppPermissionWrapper from "../MiniAppPermissionWrapper.vue";
249
248
 
250
249
  const props = defineProps<{
251
250
  miniApp?: MiniAppDetail;