@simitgroup/simpleapp-generator 1.0.29 → 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -16
- package/dist/framework.js +8 -3
- package/dist/framework.js.map +1 -1
- package/dist/generate.js +14 -3
- package/dist/generate.js.map +1 -1
- package/dist/processors/jsonschemabuilder.js +109 -16
- package/dist/processors/jsonschemabuilder.js.map +1 -1
- package/dist/type.js.map +1 -1
- package/package.json +1 -1
- package/src/framework.ts +8 -3
- package/src/generate.ts +18 -5
- package/src/processors/jsonschemabuilder.ts +129 -20
- package/src/type.ts +45 -1
- package/templates/basic/controller.eta +34 -17
- package/templates/basic/model.eta +4 -1
- package/templates/basic/pageindex.vue.eta +60 -5
- package/templates/basic/service.eta +29 -3
- package/templates/basic/simpleappclient.eta +54 -1
- package/templates/nest/SimpleAppService.eta +136 -39
- package/templates/nest/TenantMiddleware.eta +8 -13
- package/templates/nest/UserProvider.eta +127 -0
- package/templates/nest/Workflow.eta +75 -0
- package/templates/nest/app.controller.eta +12 -0
- package/templates/nest/app.module.eta +6 -2
- package/templates/nest/app.service.eta +8 -0
- package/templates/nest/nest.env.eta +7 -0
- package/templates/nest/nest.main.eta +2 -2
- package/templates/nuxt/components.crudsimple.vue.eta +2 -2
- package/templates/nuxt/components.debugdocdata.vue.eta +12 -6
- package/templates/nuxt/components.menus.vue.eta +22 -7
- package/templates/nuxt/composables.getautocomplete.ts.eta +3 -5
- package/templates/nuxt/composables.getmenus.ts.eta +12 -2
- package/templates/nuxt/pages.[xorg].index.vue.eta +9 -8
- package/templates/nuxt/pages.index.vue.eta +61 -8
- package/templates/nuxt/plugins.simpleapp.ts.eta +7 -0
- package/templates/nuxt/server.api.ts.eta +2 -1
- package/templates/nuxt/tailwind.css.eta +22 -1
- package/tsconfig.json +4 -1
- package/templates/nest/User.eta +0 -115
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
* Author: Ks Tan
|
|
7
7
|
*/
|
|
8
8
|
import { SimpleAppClient } from "@simitgroup/simpleapp-vue-component/src/SimpleAppClient";
|
|
9
|
+
import {AxiosResponse} from 'axios'
|
|
9
10
|
|
|
10
11
|
// import { JSONSchema7 } from 'json-schema';
|
|
11
12
|
import { Configuration,
|
|
12
13
|
<%= it.doctype.toUpperCase()%>Api,
|
|
13
14
|
<%= it.typename%> ,
|
|
14
15
|
<%Object.keys(it.schema).forEach(function(key){%>
|
|
15
|
-
<% if(typeof it.schema[key]=='string')
|
|
16
|
+
<% if(typeof it.schema[key]=='string' || (Array.isArray(it.schema[key]) && typeof it.schema[key][0]=='string')){%>
|
|
17
|
+
<%= it.schema[key] %>,
|
|
18
|
+
<%}%>
|
|
16
19
|
<%})%>
|
|
17
20
|
|
|
18
21
|
} from '../openapi';
|
|
@@ -24,6 +27,9 @@ export class <%= it.typename%>Doc extends SimpleAppClient<<%= it.typename%>,<%=
|
|
|
24
27
|
protected documentIdentityCode='<%~ it.autocompletecode %>'
|
|
25
28
|
protected documentIdentityName='<%~ it.autocompletename %>'
|
|
26
29
|
constructor(xorg:string,event:any,listen?:any) {
|
|
30
|
+
if(!xorg){
|
|
31
|
+
xorg='MC0wLTA' //0-0-0
|
|
32
|
+
}
|
|
27
33
|
const apiconfig = new Configuration({ basePath: `${useRuntimeConfig().public.APP_URL}/api/${xorg}` });
|
|
28
34
|
const apiobj = new <%= it.doctype.toUpperCase()%>Api(apiconfig)
|
|
29
35
|
super(apiobj,'<%= it.doctype %>','<%=it.name %>')
|
|
@@ -57,6 +63,53 @@ export class <%= it.typename%>Doc extends SimpleAppClient<<%= it.typename%>,<%=
|
|
|
57
63
|
this.setData(newdata)
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
<%Object.keys(it.jsonschema.properties).forEach(function(key) { %>
|
|
67
|
+
<% let obj=it.jsonschema.properties[key] %>
|
|
68
|
+
|
|
69
|
+
<% if(obj.type=='array' && obj.items.type=='object' && obj.items['properties']){ %>
|
|
70
|
+
<%let tablefields = Object.keys(obj.items.properties) %>
|
|
71
|
+
add<%=key%> = () => {
|
|
72
|
+
const tmp:<%=it.typename%><%=capitalizeFirstLetter(key)%> = {
|
|
73
|
+
<% for(let a=0;a<tablefields.length;a++){%>
|
|
74
|
+
<% const skey = tablefields[a] %>
|
|
75
|
+
<% const sobj = obj.items.properties[skey] %>
|
|
76
|
+
<%if(sobj.type=='object' && typeof sobj['x-foreignkey']!='undefined'){%>
|
|
77
|
+
<%=skey%>: <%~ sobj.default?JSON.stringify(sobj.default): '{_id:"",label:""}'%>,
|
|
78
|
+
<%}else if(sobj.type=='boolean'){%>
|
|
79
|
+
<%=skey%>: <%=sobj.default??false%>,
|
|
80
|
+
<%}else if(sobj.type=='number'){%>
|
|
81
|
+
<%=skey%>: <%=sobj.default??0.00%>,
|
|
82
|
+
<%}else if(sobj.type=='integer'){%>
|
|
83
|
+
<%=skey%>: <%=sobj.default??0%>,
|
|
84
|
+
<%}else if(sobj.type=='string'){%>
|
|
85
|
+
<%=skey%>: '<%=sobj.default??""%>',
|
|
86
|
+
<%}%>
|
|
87
|
+
|
|
88
|
+
<%}%>
|
|
89
|
+
}
|
|
90
|
+
this.getReactiveData().value.<%= key %>.push(tmp)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
<%}%>
|
|
95
|
+
<%})%>
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
<%for(let i=0;i<it.apiSettings.length;i++){%>
|
|
99
|
+
<% let api = it.apiSettings[i] %>
|
|
100
|
+
async exec<%=capitalizeFirstLetter(api.action)%>(){
|
|
101
|
+
const recordid: string = this.data.value._id ?? '';
|
|
102
|
+
return await this.docapi.exec<%=capitalizeFirstLetter(api.action)%>(recordid)
|
|
103
|
+
.then((res: AxiosResponse) => {
|
|
104
|
+
if(this.event){this.event('info:<%=capitalizeFirstLetter(api.action)%>',res.data)}
|
|
105
|
+
return res;
|
|
106
|
+
}).catch((res:any)=>{
|
|
107
|
+
if(this.event){this.event('error:<%=capitalizeFirstLetter(api.action)%>',res)}
|
|
108
|
+
return Promise.reject(res)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
<%}%>
|
|
112
|
+
|
|
60
113
|
/*****************************customized frontend + backend code*****************************************/
|
|
61
114
|
|
|
62
115
|
<%~ it.bothEndCode %>
|
|
@@ -1,41 +1,67 @@
|
|
|
1
|
-
import { Injectable } from '@nestjs/common';
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
2
|
import { InjectModel } from '@nestjs/mongoose';
|
|
3
3
|
import { Model } from 'mongoose';
|
|
4
4
|
import Ajv from 'ajv';
|
|
5
5
|
import addFormats from 'ajv-formats';
|
|
6
|
+
import addErrors from 'ajv-errors';
|
|
6
7
|
import foreignkeys from '../dicts/foreignkeys.json';
|
|
7
|
-
|
|
8
|
+
import { Workflow } from './Workflow';
|
|
8
9
|
import {
|
|
9
10
|
NotFoundException,
|
|
10
11
|
InternalServerErrorException,
|
|
11
12
|
} from '@nestjs/common/exceptions';
|
|
12
|
-
import {
|
|
13
|
+
import { UserProvider } from './UserProvider';
|
|
13
14
|
|
|
14
15
|
export enum IsolationType {
|
|
16
|
+
'none' = 'none',
|
|
15
17
|
'org' = 'org',
|
|
16
18
|
'tenant' = 'tenant',
|
|
17
19
|
'branch' = 'branch',
|
|
18
20
|
}
|
|
21
|
+
export enum HookType {
|
|
22
|
+
'init' = 'init',
|
|
23
|
+
'beforeSearch' = 'beforeSearch',
|
|
24
|
+
'afterSearch' = 'afterSearch',
|
|
25
|
+
'beforeValidation' = 'beforeValidation',
|
|
26
|
+
'afterValidation' = 'afterValidation',
|
|
27
|
+
'beforeCreate' = 'beforeCreate',
|
|
28
|
+
'afterCreate' = 'afterCreate',
|
|
29
|
+
'beforeUpdate' = 'beforeUpdate',
|
|
30
|
+
'afterUpdate' = 'afterUpdate',
|
|
31
|
+
'beforeDelete' = 'beforeDelete',
|
|
32
|
+
'afterDelete' = 'afterDelete',
|
|
33
|
+
'beforeFetchRecord' = 'beforeFetchRecord',
|
|
34
|
+
'afterFetchRecord' = 'afterFetchRecord',
|
|
35
|
+
}
|
|
36
|
+
export type MoreProjectionType = {
|
|
37
|
+
[key: string]: string;
|
|
38
|
+
};
|
|
19
39
|
@Injectable()
|
|
20
40
|
export class SimpleAppService<T extends { _id?: string }> {
|
|
41
|
+
protected logger = new Logger();
|
|
21
42
|
protected jsonschema = { type: 'object', properties: {}, required: [] };
|
|
22
43
|
protected documentIdentityCode = 'code';
|
|
23
44
|
protected documentIdentityName = 'label';
|
|
24
|
-
protected documentName = '
|
|
25
|
-
protected documentType = '
|
|
45
|
+
protected documentName = '-unknowndocname-';
|
|
46
|
+
protected documentType = '-unknowndoctype-';
|
|
26
47
|
protected LIMITPERPAGE = 20;
|
|
48
|
+
protected moreAutoCompleteField: MoreProjectionType = {};
|
|
27
49
|
protected isolationtype: IsolationType = IsolationType.org;
|
|
28
50
|
protected isolationFilter: any = {};
|
|
29
51
|
protected data: T = { _id: '' } as T;
|
|
30
52
|
private doc: Model<T>; //set private to prevent developer break data isolation control
|
|
31
53
|
protected errorlist = [];
|
|
32
54
|
constructor(
|
|
55
|
+
doctype: string,
|
|
56
|
+
docname: string,
|
|
33
57
|
newdoc: Model<T>,
|
|
34
58
|
isolationtype: IsolationType = IsolationType.org,
|
|
35
59
|
) {
|
|
60
|
+
this.documentType = doctype;
|
|
61
|
+
this.documentName = docname;
|
|
36
62
|
this.doc = newdoc;
|
|
37
63
|
this.isolationtype = isolationtype;
|
|
38
|
-
|
|
64
|
+
this.hook(HookType.init, this.documentName);
|
|
39
65
|
// this.tenantdoc = tenantdoc
|
|
40
66
|
}
|
|
41
67
|
getRecordId = (): string => this.data._id;
|
|
@@ -53,15 +79,18 @@ export class SimpleAppService<T extends { _id?: string }> {
|
|
|
53
79
|
getIsolationFilter = () => {
|
|
54
80
|
let isolationFilter = {};
|
|
55
81
|
switch (this.isolationtype) {
|
|
82
|
+
case 'none':
|
|
83
|
+
isolationFilter = {};
|
|
84
|
+
break;
|
|
56
85
|
case 'branch':
|
|
57
|
-
isolationFilter =
|
|
86
|
+
isolationFilter = UserProvider.getInstance().getBranchFilter();
|
|
58
87
|
break;
|
|
59
88
|
case 'tenant':
|
|
60
|
-
isolationFilter =
|
|
89
|
+
isolationFilter = UserProvider.getInstance().getTenantFilter();
|
|
61
90
|
break;
|
|
62
91
|
case 'org':
|
|
63
92
|
default:
|
|
64
|
-
isolationFilter =
|
|
93
|
+
isolationFilter = UserProvider.getInstance().getOrgFilter();
|
|
65
94
|
break;
|
|
66
95
|
}
|
|
67
96
|
return isolationFilter;
|
|
@@ -80,20 +109,32 @@ export class SimpleAppService<T extends { _id?: string }> {
|
|
|
80
109
|
throw new InternalServerErrorException(err.message);
|
|
81
110
|
}
|
|
82
111
|
}
|
|
112
|
+
addAutoCompleteField = (morefield: MoreProjectionType) => {
|
|
113
|
+
const props = Object.getOwnPropertyNames(morefield);
|
|
114
|
+
console.log('addAutoCompleteField', props);
|
|
115
|
+
for (let i = 0; i < props.length; i++) {
|
|
116
|
+
const key = props[i];
|
|
117
|
+
this.moreAutoCompleteField[key] = '$' + morefield[key];
|
|
118
|
+
}
|
|
119
|
+
console.log(this.moreAutoCompleteField);
|
|
120
|
+
};
|
|
83
121
|
async getAutoComplete(keyword: string) {
|
|
84
122
|
try {
|
|
85
123
|
const filter1 = {};
|
|
86
124
|
const filter2 = {};
|
|
87
|
-
filter1[this.documentIdentityCode] = { $regex: keyword };
|
|
88
|
-
filter2[this.documentIdentityName] = { $regex: keyword };
|
|
125
|
+
filter1[this.documentIdentityCode] = { $regex: keyword, $options: 'i' };
|
|
126
|
+
filter2[this.documentIdentityName] = { $regex: keyword, $options: 'i' };
|
|
89
127
|
const filterobj = { $or: [filter1, filter2] };
|
|
90
128
|
|
|
91
129
|
Object.assign(filterobj, this.getIsolationFilter());
|
|
92
|
-
|
|
130
|
+
let projections = {
|
|
93
131
|
id: `\$_id`,
|
|
94
132
|
label: `\$${this.documentIdentityCode}`,
|
|
95
133
|
name: `\$${this.documentIdentityName}`,
|
|
96
134
|
};
|
|
135
|
+
if (this.moreAutoCompleteField) {
|
|
136
|
+
Object.assign(projections, this.moreAutoCompleteField);
|
|
137
|
+
}
|
|
97
138
|
const products = await this.doc.find(filterobj, projections, {
|
|
98
139
|
limit: this.LIMITPERPAGE,
|
|
99
140
|
});
|
|
@@ -108,10 +149,12 @@ export class SimpleAppService<T extends { _id?: string }> {
|
|
|
108
149
|
async search(filters: Object) {
|
|
109
150
|
try {
|
|
110
151
|
Object.assign(filters, this.getIsolationFilter());
|
|
152
|
+
await this.hook(HookType.beforeSearch, filters);
|
|
111
153
|
const products = await this.doc.find(filters);
|
|
112
154
|
const productlist = products.map((p: T) => {
|
|
113
155
|
return p;
|
|
114
156
|
});
|
|
157
|
+
await this.hook(HookType.afterSearch, productlist);
|
|
115
158
|
// console.log(products);
|
|
116
159
|
return productlist;
|
|
117
160
|
} catch (err) {
|
|
@@ -120,7 +163,9 @@ export class SimpleAppService<T extends { _id?: string }> {
|
|
|
120
163
|
// return this;
|
|
121
164
|
}
|
|
122
165
|
async findById(id: string) {
|
|
166
|
+
await this.hook(HookType.beforeFetchRecord, id);
|
|
123
167
|
const data = await this.search({ _id: id });
|
|
168
|
+
await this.hook(HookType.afterFetchRecord, data);
|
|
124
169
|
if (data.length == 1) {
|
|
125
170
|
// console.log('data0', data[0]);
|
|
126
171
|
return data[0];
|
|
@@ -133,11 +178,13 @@ export class SimpleAppService<T extends { _id?: string }> {
|
|
|
133
178
|
let result;
|
|
134
179
|
|
|
135
180
|
try {
|
|
136
|
-
|
|
181
|
+
await this.hook(HookType.beforeCreate, this.data);
|
|
182
|
+
Object.assign(this.data, UserProvider.getInstance().getCreateFilter());
|
|
137
183
|
delete this.data._id;
|
|
138
|
-
this.validateData(this.data);
|
|
184
|
+
await this.validateData(this.data);
|
|
139
185
|
const newdoc = new this.doc(this.data);
|
|
140
186
|
result = await newdoc.save();
|
|
187
|
+
await this.hook(HookType.afterCreate, result);
|
|
141
188
|
} catch (err) {
|
|
142
189
|
throw new InternalServerErrorException(err.message);
|
|
143
190
|
}
|
|
@@ -145,8 +192,8 @@ export class SimpleAppService<T extends { _id?: string }> {
|
|
|
145
192
|
}
|
|
146
193
|
async update() {
|
|
147
194
|
const id: string = this.getRecordId();
|
|
148
|
-
Object.assign(this.data,
|
|
149
|
-
this.findIdThenUpdate(id, this.data);
|
|
195
|
+
Object.assign(this.data, UserProvider.getInstance().getUpdateFilter());
|
|
196
|
+
await this.findIdThenUpdate(id, this.data);
|
|
150
197
|
}
|
|
151
198
|
async delete() {
|
|
152
199
|
const id: string = this.getRecordId();
|
|
@@ -157,47 +204,65 @@ export class SimpleAppService<T extends { _id?: string }> {
|
|
|
157
204
|
return dependency;
|
|
158
205
|
}
|
|
159
206
|
}
|
|
160
|
-
hook(type: string, data
|
|
207
|
+
hook = async (type: string, data?: any) => {
|
|
208
|
+
// console.log('Default hook', data);
|
|
161
209
|
return true;
|
|
162
|
-
}
|
|
163
|
-
validateData(data: T) {
|
|
210
|
+
};
|
|
211
|
+
async validateData(data: T) {
|
|
164
212
|
const ajv = new Ajv({ allErrors: true });
|
|
165
213
|
addFormats(ajv);
|
|
214
|
+
addErrors(ajv);
|
|
215
|
+
|
|
166
216
|
ajv.addFormat('x-document-no', /.*$/);
|
|
167
|
-
ajv.addFormat('x-document-
|
|
217
|
+
ajv.addFormat('x-document-label', /.*$/);
|
|
218
|
+
ajv.addFormat('tel', /^$|^\d{7,15}$/gm);
|
|
168
219
|
ajv.addFormat('x-text', /.*$/);
|
|
169
220
|
ajv.addFormat('x-html', /.*$/);
|
|
170
|
-
ajv.addKeyword({
|
|
171
|
-
keyword: 'x-foreignkey',
|
|
172
|
-
type: 'string',
|
|
173
|
-
});
|
|
174
221
|
|
|
175
|
-
|
|
222
|
+
ajv.addKeyword({ keyword: 'x-document-status', type: 'array' });
|
|
223
|
+
ajv.addKeyword({ keyword: 'x-document-api', type: 'array' });
|
|
224
|
+
ajv.addKeyword({ keyword: 'x-ignore-autocomplete', type: 'boolean' });
|
|
225
|
+
ajv.addKeyword({ keyword: 'x-isolation-type', type: 'string' });
|
|
226
|
+
ajv.addKeyword({ keyword: 'x-document-type', type: 'string' });
|
|
227
|
+
ajv.addKeyword({ keyword: 'x-document-name', type: 'string' });
|
|
228
|
+
ajv.addKeyword({ keyword: 'x-collection-name', type: 'string' });
|
|
229
|
+
ajv.addKeyword({ keyword: 'x-autocomplete-field', type: 'boolean' });
|
|
230
|
+
ajv.addKeyword({ keyword: 'x-foreignkey', type: 'string' });
|
|
231
|
+
|
|
232
|
+
const issuccess = await this.hook(HookType.beforeValidation, data);
|
|
233
|
+
if (!issuccess) {
|
|
176
234
|
const erromsg: string[] = [];
|
|
177
235
|
for (let i = 0; i < this.errorlist.length; i++) {
|
|
178
236
|
erromsg.push(this.errorlist[i].message);
|
|
179
237
|
}
|
|
180
|
-
|
|
238
|
+
this.logger.log('run hook during validation');
|
|
181
239
|
throw new InternalServerErrorException(erromsg.join('\n'));
|
|
182
240
|
}
|
|
183
|
-
|
|
241
|
+
|
|
242
|
+
let validate;
|
|
243
|
+
try {
|
|
244
|
+
validate = ajv.compile(this.jsonschema);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
this.logger.error('compile error', err);
|
|
247
|
+
}
|
|
248
|
+
|
|
184
249
|
const valid = validate(data);
|
|
185
|
-
//console.log(validate.errors);
|
|
186
250
|
if (!valid) {
|
|
187
|
-
|
|
251
|
+
this.logger.error(validate.errors);
|
|
188
252
|
const erromsg: string[] = [];
|
|
189
|
-
for (let i = 0; i <
|
|
190
|
-
erromsg.push(
|
|
253
|
+
for (let i = 0; i < ajv.errors.length; i++) {
|
|
254
|
+
erromsg.push(ajv.errors[i].message);
|
|
191
255
|
}
|
|
192
|
-
|
|
256
|
+
this.logger.error('ajv erromsg: ', erromsg);
|
|
193
257
|
throw new InternalServerErrorException(erromsg.join('\n'));
|
|
194
258
|
}
|
|
195
|
-
this.hook(
|
|
259
|
+
await this.hook(HookType.afterValidation, data);
|
|
196
260
|
}
|
|
197
261
|
|
|
198
262
|
async findIdThenDelete(id: string): Promise<any> {
|
|
199
263
|
// const data = await this.findById(id);
|
|
200
264
|
try {
|
|
265
|
+
await this.hook(HookType.beforeDelete, id);
|
|
201
266
|
//console.log('deletedeletedeletedelete');
|
|
202
267
|
const dependency = await this.getRelatedRecords(id);
|
|
203
268
|
if (!dependency) {
|
|
@@ -205,7 +270,7 @@ export class SimpleAppService<T extends { _id?: string }> {
|
|
|
205
270
|
filter['_id'] = id;
|
|
206
271
|
const deleteresult = await this.doc.deleteOne(filter);
|
|
207
272
|
// this.doc.findByIdAndDelete(id)
|
|
208
|
-
|
|
273
|
+
await this.hook(HookType.afterDelete, deleteresult);
|
|
209
274
|
//this.doc.findByIdAndDelete(id);
|
|
210
275
|
return deleteresult;
|
|
211
276
|
} else {
|
|
@@ -222,15 +287,15 @@ export class SimpleAppService<T extends { _id?: string }> {
|
|
|
222
287
|
|
|
223
288
|
findIdThenUpdate = async (id: string, data: T) => {
|
|
224
289
|
const existingdata = await this.findById(id);
|
|
225
|
-
|
|
290
|
+
await this.hook(HookType.beforeUpdate, data);
|
|
226
291
|
try {
|
|
227
|
-
Object.assign(data,
|
|
292
|
+
Object.assign(data, UserProvider.getInstance().getUpdateFilter());
|
|
228
293
|
Object.assign(existingdata, data);
|
|
229
|
-
this.validateData(data);
|
|
294
|
+
await this.validateData(data);
|
|
230
295
|
let filter = this.getIsolationFilter();
|
|
231
296
|
filter['_id'] = id;
|
|
232
297
|
const result = await this.doc.findOneAndUpdate(filter, data);
|
|
233
|
-
|
|
298
|
+
await this.hook(HookType.afterUpdate, data);
|
|
234
299
|
return result;
|
|
235
300
|
} catch (err) {
|
|
236
301
|
throw new InternalServerErrorException(err.message);
|
|
@@ -252,7 +317,7 @@ export class SimpleAppService<T extends { _id?: string }> {
|
|
|
252
317
|
const collection = this.doc.db.collection(collectionname);
|
|
253
318
|
//single schema may have multiple foreign key link here, loop all
|
|
254
319
|
for (let j = 0; j < fobjs.length; j++) {
|
|
255
|
-
const fkey = fobjs[j];
|
|
320
|
+
const fkey = fobjs[j] + '._id';
|
|
256
321
|
const filter = {};
|
|
257
322
|
filter[fkey] = id;
|
|
258
323
|
//console.log('getRelatedRecords get filter', collectionname, filter);
|
|
@@ -266,4 +331,36 @@ export class SimpleAppService<T extends { _id?: string }> {
|
|
|
266
331
|
}
|
|
267
332
|
return Promise.resolve(null);
|
|
268
333
|
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* change property documentStatus for specific document, no workflow execution
|
|
337
|
+
* @param id
|
|
338
|
+
* @param docstatus
|
|
339
|
+
* @returns Promise
|
|
340
|
+
*/
|
|
341
|
+
async setDocumentStatus(id: string, docstatus: string) {
|
|
342
|
+
const partialdata: T = {} as T;
|
|
343
|
+
partialdata['documentStatus'] = docstatus;
|
|
344
|
+
return this.findIdThenUpdate(id, partialdata);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* change property documentStatus for specific document, no workflow execution
|
|
349
|
+
* @param id
|
|
350
|
+
* @param docstatus
|
|
351
|
+
* @returns Promise
|
|
352
|
+
*/
|
|
353
|
+
async executeWorkFlow(id: string, bpmnname: string, docstatus: string) {
|
|
354
|
+
const data = await this.findById(id);
|
|
355
|
+
return Workflow.getInstance().executeWorkFlow(
|
|
356
|
+
id,
|
|
357
|
+
bpmnname,
|
|
358
|
+
docstatus,
|
|
359
|
+
data,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async getMyUserTask() {
|
|
364
|
+
return Workflow.getInstance().getMyUserTask();
|
|
365
|
+
}
|
|
269
366
|
}
|
|
@@ -2,38 +2,33 @@ import { Injectable, NestMiddleware, Scope } from '@nestjs/common';
|
|
|
2
2
|
import { Request, Response, NextFunction } from 'express';
|
|
3
3
|
// import * as jwt from 'nestjs-jwt'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
import { User } from './User';
|
|
5
|
+
import { UserProvider } from './UserProvider';
|
|
7
6
|
// import {KeycloakConfigService} from "../keycloak/keycloak.service"
|
|
8
7
|
@Injectable({
|
|
9
8
|
scope: Scope.REQUEST,
|
|
10
9
|
})
|
|
11
|
-
|
|
12
10
|
export class TenantMiddleware implements NestMiddleware {
|
|
13
11
|
use(req: Request, res: Response, next: NextFunction) {
|
|
14
12
|
if (req.baseUrl == '/oauth2-redirect.html') {
|
|
15
13
|
next();
|
|
16
|
-
return
|
|
14
|
+
return;
|
|
17
15
|
}
|
|
18
|
-
if(!req.headers['authorization']){
|
|
16
|
+
if (!req.headers['authorization']) {
|
|
19
17
|
return res.status(401).send('Undefine bearer token');
|
|
20
18
|
}
|
|
21
|
-
if (!req.headers['x-org']){
|
|
19
|
+
if (!req.headers['x-org']) {
|
|
22
20
|
return res.status(401).send('undefine header string x-org');
|
|
23
21
|
}
|
|
24
|
-
const u =
|
|
22
|
+
const u = UserProvider.getInstance();
|
|
25
23
|
|
|
26
24
|
try {
|
|
27
25
|
u.setXorg(req.headers['x-org'].toString());
|
|
28
|
-
let tokenstr:string = req.headers['authorization']
|
|
29
|
-
tokenstr = tokenstr.replace(
|
|
30
|
-
u.setUserToken(tokenstr);
|
|
26
|
+
let tokenstr: string = req.headers['authorization'];
|
|
27
|
+
tokenstr = tokenstr.replace('Bearer ', '');
|
|
28
|
+
u.setUserToken(tokenstr);
|
|
31
29
|
next();
|
|
32
30
|
} catch {
|
|
33
31
|
return res.status(401).send('Invalid x-org or user info');
|
|
34
32
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
33
|
}
|
|
39
34
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Injectable, Scope } from '@nestjs/common';
|
|
2
|
+
import Base64URL from '@darkwolf/base64url';
|
|
3
|
+
import * as jwt from 'jsonwebtoken';
|
|
4
|
+
|
|
5
|
+
@Injectable({
|
|
6
|
+
scope: Scope.REQUEST,
|
|
7
|
+
})
|
|
8
|
+
export class UserProvider {
|
|
9
|
+
private static instance: UserProvider;
|
|
10
|
+
protected uid: string = '';
|
|
11
|
+
protected uname: string = '';
|
|
12
|
+
protected email: string = '';
|
|
13
|
+
protected fullname: string = '';
|
|
14
|
+
protected xOrg: string = '';
|
|
15
|
+
protected tenantId: number = 0;
|
|
16
|
+
protected orgId: number = 0;
|
|
17
|
+
protected branchId: number = 0;
|
|
18
|
+
protected accessrights: any = {};
|
|
19
|
+
protected token: string = '';
|
|
20
|
+
protected refreshtoken: string = '';
|
|
21
|
+
protected groups: string[] = [];
|
|
22
|
+
constructor() {}
|
|
23
|
+
public static getInstance(): UserProvider {
|
|
24
|
+
if (!UserProvider.instance) {
|
|
25
|
+
UserProvider.instance = new UserProvider();
|
|
26
|
+
}
|
|
27
|
+
return UserProvider.instance;
|
|
28
|
+
}
|
|
29
|
+
getUid = () => this.uid;
|
|
30
|
+
getUname = () => this.uname;
|
|
31
|
+
getFullname = () => this.fullname;
|
|
32
|
+
getTenantId = () => this.tenantId;
|
|
33
|
+
getOrgId = () => this.orgId;
|
|
34
|
+
getBranchId = () => this.branchId;
|
|
35
|
+
getEmail = () => this.email;
|
|
36
|
+
getGroups = () => this.groups;
|
|
37
|
+
setUserToken = (tokenstr: string) => {
|
|
38
|
+
const tokeninfo = jwt.decode(tokenstr);
|
|
39
|
+
// realm_access: {
|
|
40
|
+
// roles: [
|
|
41
|
+
// 'default-roles-simitdeveloper',
|
|
42
|
+
// 'offline_access',
|
|
43
|
+
// 'uma_authorization'
|
|
44
|
+
// ]
|
|
45
|
+
// },
|
|
46
|
+
// resource_access: { account: { roles: [Array] } },
|
|
47
|
+
// scope: 'openid email profile',
|
|
48
|
+
// sid: '53192f53-d4af-413b-b8d7-1e186419fe53',
|
|
49
|
+
// email_verified: false,
|
|
50
|
+
// name: 'kstan kstan',
|
|
51
|
+
// preferred_username: 'kstan',
|
|
52
|
+
// given_name: 'kstan',
|
|
53
|
+
// family_name: 'kstan',
|
|
54
|
+
// email: 'kstan@simitgroup.com'
|
|
55
|
+
|
|
56
|
+
const u = UserProvider.getInstance();
|
|
57
|
+
u.token = tokenstr;
|
|
58
|
+
u.uid = tokeninfo.sub;
|
|
59
|
+
u.email = tokeninfo.email;
|
|
60
|
+
u.uname = tokeninfo.preferred_username;
|
|
61
|
+
u.fullname = tokeninfo.name;
|
|
62
|
+
u.groups = ['superuser', 'accountant', 'directors'];
|
|
63
|
+
u.accessrights = tokeninfo.resource_access;
|
|
64
|
+
};
|
|
65
|
+
getInfo = () => {
|
|
66
|
+
return UserProvider.getInstance();
|
|
67
|
+
};
|
|
68
|
+
getBranchFilter = () => {
|
|
69
|
+
return {
|
|
70
|
+
tenantId: UserProvider.getInstance().tenantId,
|
|
71
|
+
orgId: UserProvider.getInstance().orgId,
|
|
72
|
+
branchId: UserProvider.getInstance().branchId,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
getTenantFilter = () => {
|
|
76
|
+
return { tenantId: UserProvider.getInstance().tenantId };
|
|
77
|
+
};
|
|
78
|
+
getOrgFilter = () => {
|
|
79
|
+
return {
|
|
80
|
+
tenantId: UserProvider.getInstance().tenantId,
|
|
81
|
+
orgId: UserProvider.getInstance().orgId,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
getWorkflowTaskFilter() {
|
|
85
|
+
return {
|
|
86
|
+
'data.tenantId': UserProvider.getInstance().tenantId,
|
|
87
|
+
'assignments.assignee': UserProvider.getInstance().getUid(),
|
|
88
|
+
// 'assignments.assignee': User.getInstance().getUid(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
getCreateFilter = () => {
|
|
92
|
+
const u = UserProvider.getInstance();
|
|
93
|
+
return {
|
|
94
|
+
tenantId: u.tenantId,
|
|
95
|
+
orgId: u.orgId,
|
|
96
|
+
branchId: u.branchId,
|
|
97
|
+
createdby: u.uid,
|
|
98
|
+
updatedby: u.uid,
|
|
99
|
+
created: new Date().getTime().toString(),
|
|
100
|
+
updated: new Date().getTime().toString(),
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
getUpdateFilter = () => {
|
|
104
|
+
const u = UserProvider.getInstance();
|
|
105
|
+
return {
|
|
106
|
+
updatedby: u.uid,
|
|
107
|
+
updated: new Date().getTime().toString(),
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
setXorg = (xorg) => {
|
|
111
|
+
try {
|
|
112
|
+
const decodedText: string = Base64URL.decodeText(xorg);
|
|
113
|
+
const arrXorg = decodedText.split('-');
|
|
114
|
+
|
|
115
|
+
if (arrXorg.length == 3) {
|
|
116
|
+
const u = UserProvider.getInstance();
|
|
117
|
+
u.tenantId = Number(arrXorg[0]);
|
|
118
|
+
u.orgId = Number(arrXorg[1]);
|
|
119
|
+
u.branchId = Number(arrXorg[2]);
|
|
120
|
+
} else {
|
|
121
|
+
throw 'invalid x-org';
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { BPMNClient } from "bpmn-client";
|
|
2
|
+
import { UserProvider } from './UserProvider';
|
|
3
|
+
import { Injectable } from '@nestjs/common';
|
|
4
|
+
|
|
5
|
+
export type UserTaskActorOptions={
|
|
6
|
+
userId:string
|
|
7
|
+
actGroups?:string[]
|
|
8
|
+
actUsers?:string[]
|
|
9
|
+
}
|
|
10
|
+
@Injectable()
|
|
11
|
+
export class Workflow{
|
|
12
|
+
private static instance: Workflow;
|
|
13
|
+
server:BPMNClient
|
|
14
|
+
|
|
15
|
+
constructor(){
|
|
16
|
+
this.server= new BPMNClient(process.env.BPMN_HOST, process.env.BPMN_PORT, process.env.BPMN_API_KEY);
|
|
17
|
+
}
|
|
18
|
+
public static getInstance(): Workflow {
|
|
19
|
+
if (!Workflow.instance) {
|
|
20
|
+
Workflow.instance = new Workflow();
|
|
21
|
+
}
|
|
22
|
+
return Workflow.instance;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async executeWorkFlow(id: string, bpmnname: string, docstatus: string,data:any) {
|
|
26
|
+
const workflowactoroptions : UserTaskActorOptions ={
|
|
27
|
+
userId:UserProvider.getInstance().getUid(),
|
|
28
|
+
actGroups:[],
|
|
29
|
+
actUsers:[]
|
|
30
|
+
}
|
|
31
|
+
var instance = await this.server.engine.start(bpmnname, data,null,workflowactoroptions);
|
|
32
|
+
const res = {instanceId:instance.id,data:instance.data,name:instance.name,status:instance.status}
|
|
33
|
+
return Promise.resolve(res);
|
|
34
|
+
}
|
|
35
|
+
async getMyUserTask(){
|
|
36
|
+
const uid = UserProvider.getInstance().getUid()
|
|
37
|
+
const groups = UserProvider.getInstance().getGroups()
|
|
38
|
+
// 'assignments.candidateUsers': User.getInstance().getUid(),
|
|
39
|
+
// 'assignments.candidateGroups': User.getInstance().getGroups
|
|
40
|
+
|
|
41
|
+
let anyof:any = [
|
|
42
|
+
{'items.assignments.assignee':uid},
|
|
43
|
+
]
|
|
44
|
+
let usersfilter={}
|
|
45
|
+
usersfilter[uid]={$in: 'items.assignments.candidateUsers'}
|
|
46
|
+
anyof.push(usersfilter)
|
|
47
|
+
//{'data.tenantId': 1,'$or':[{
|
|
48
|
+
// 'items.assignments.assignee': 'b2a49a8f-a943-4814-8087-60b1ef2f304f'
|
|
49
|
+
// }]}
|
|
50
|
+
//any of the group
|
|
51
|
+
for(let i=0;i<groups.length;i++){
|
|
52
|
+
const gname = groups[i]
|
|
53
|
+
const tmp = {}
|
|
54
|
+
tmp[gname]={ $in: 'items.assignments.candidateGroups'}
|
|
55
|
+
anyof.push(tmp)
|
|
56
|
+
}
|
|
57
|
+
const filters = {
|
|
58
|
+
'data.tenantId': UserProvider.getInstance().getTenantId(),
|
|
59
|
+
'$or':anyof
|
|
60
|
+
}
|
|
61
|
+
console.dir(filters)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
// 'assignments.assignee': User.getInstance().getUid(),
|
|
65
|
+
const client = new BPMNClient(process.env.BPMN_HOST, process.env.BPMN_PORT, process.env.BPMN_API_KEY);
|
|
66
|
+
const items = await client.datastore.findItems(filters)
|
|
67
|
+
let data=[]
|
|
68
|
+
for(let i=0;i<items.length;i++){
|
|
69
|
+
if(items[i].status=='wait'){
|
|
70
|
+
data.push(items[i])
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return Promise.resolve(data);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Controller, Get } from '@nestjs/common';
|
|
2
|
+
import { AppService } from './app.service';
|
|
3
|
+
|
|
4
|
+
@Controller()
|
|
5
|
+
export class AppController {
|
|
6
|
+
constructor(private readonly appService: AppService) {}
|
|
7
|
+
|
|
8
|
+
@Get('myworkflowtask')
|
|
9
|
+
async getHello() {
|
|
10
|
+
return await this.appService.getMyTask();
|
|
11
|
+
}
|
|
12
|
+
}
|