@simitgroup/simpleapp-generator 2.0.1-u-alpha → 2.0.1-v-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 +5 -0
- package/package.json +1 -1
- package/templates/nest/src/simple-app/_core/features/cron/base/cron.base.ts.eta +3 -0
- package/templates/nest/src/simple-app/_core/features/cron/cron-system/cron-system.service.ts.eta +12 -10
- package/templates/nest/src/simple-app/_core/framework/base/simple-app.service.ts.eta +2 -0
- package/templates/nest/src/simple-app/_core/framework/simple-app.interceptor.ts.eta +7 -12
- package/templates/nuxt/components/renderer/RendererRemoteUrl.vue.eta +76 -0
- package/templates/nuxt/components/simpleApp/SimpleAppInput.vue.eta +19 -0
- package/templates/nuxt/components/simpleApp/SimpleAppRemoteSelect.vue.eta +110 -0
- package/templates/nuxt/types/simpleappinput.ts.eta +49 -50
package/ReleaseNote.md
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { BadRequestException, Injectable, Logger, NestMiddleware, ServiceUnavailableException } from '@nestjs/common';
|
|
1
2
|
import { UserContext } from '../../user-context/user.context';
|
|
2
3
|
import { SimpleAppRobotUserService } from '../../user-context/robot-user.service';
|
|
3
4
|
import { BaseUserContext } from '../../user-context/user-context.type';
|
|
4
5
|
export abstract class SimpleAppCronBaseClass {
|
|
6
|
+
protected logger:Logger;
|
|
5
7
|
private robotUser: SimpleAppRobotUserService;
|
|
6
8
|
|
|
7
9
|
constructor(robotUser: SimpleAppRobotUserService) {
|
|
10
|
+
this.logger = new Logger(this.constructor.name);
|
|
8
11
|
this.robotUser = robotUser;
|
|
9
12
|
}
|
|
10
13
|
async getAppUser(user: BaseUserContext) {
|
package/templates/nest/src/simple-app/_core/features/cron/cron-system/cron-system.service.ts.eta
CHANGED
|
@@ -3,21 +3,22 @@ import { InjectModel } from '@nestjs/mongoose';
|
|
|
3
3
|
import { Cron } from '@nestjs/schedule';
|
|
4
4
|
import { Model } from 'mongoose';
|
|
5
5
|
import { UserContext } from 'src/simple-app/_core/features/user-context/user.context';
|
|
6
|
-
import {SimpleAppCronBaseClass} from '../base/cron.base'
|
|
7
|
-
import {ApiEvent,QueueJob} from './schemas'
|
|
8
|
-
import { SimpleAppRobotUserService } from
|
|
9
|
-
export class CronSystemService extends SimpleAppCronBaseClass{
|
|
6
|
+
import { SimpleAppCronBaseClass } from '../base/cron.base';
|
|
7
|
+
import { ApiEvent, QueueJob } from './schemas';
|
|
8
|
+
import { SimpleAppRobotUserService } from '../../user-context/robot-user.service';
|
|
9
|
+
export class CronSystemService extends SimpleAppCronBaseClass {
|
|
10
10
|
constructor(
|
|
11
11
|
@InjectModel('ApiEvent') private apiEventModel: Model<ApiEvent>,
|
|
12
|
-
@InjectModel('QueueJob') private queueJobModel: Model<QueueJob>,
|
|
13
|
-
robotUserService:SimpleAppRobotUserService
|
|
12
|
+
@InjectModel('QueueJob') private queueJobModel: Model<QueueJob>,
|
|
13
|
+
robotUserService: SimpleAppRobotUserService,
|
|
14
14
|
) {
|
|
15
|
-
super(robotUserService)
|
|
15
|
+
super(robotUserService);
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
@Cron('0 1 * * *')
|
|
19
19
|
async deleteApiLog() {
|
|
20
|
-
if (process.env.ON_CRONTAB == 'true') {
|
|
20
|
+
if (process.env.ON_CRONTAB == 'true') {
|
|
21
|
+
this.logger.debug('run CronSystemService.deleteApiLog 0 1 * * *')
|
|
21
22
|
const today = new Date();
|
|
22
23
|
const newDate = new Date(today.getTime());
|
|
23
24
|
const cutoffdate = new Date(newDate.setDate(newDate.getDate() - 90)).toISOString();
|
|
@@ -28,10 +29,11 @@ export class CronSystemService extends SimpleAppCronBaseClass{
|
|
|
28
29
|
@Cron('30 0 * * *')
|
|
29
30
|
async deleteQueueJob() {
|
|
30
31
|
if (process.env.ON_CRONTAB == 'true') {
|
|
32
|
+
this.logger.debug('run deleteQueueJob.deleteApiLog 30 0 * * *')
|
|
31
33
|
const today = new Date();
|
|
32
34
|
const newDate = new Date(today.getTime());
|
|
33
35
|
const cutoffdate = new Date(newDate.setDate(newDate.getDate() - 90)).toISOString();
|
|
34
36
|
await this.queueJobModel.deleteMany({ created: { $lte: cutoffdate } });
|
|
35
37
|
}
|
|
36
|
-
}
|
|
38
|
+
}
|
|
37
39
|
}
|
|
@@ -485,6 +485,8 @@ export class SimpleAppService<T extends SchemaFields> {
|
|
|
485
485
|
|
|
486
486
|
ajv.addKeyword({ keyword: 'x-foreignkey', schemaType: 'string' });
|
|
487
487
|
ajv.addKeyword({ keyword: 'x-simpleapp-config', schemaType: 'object' });
|
|
488
|
+
ajv.addKeyword({ keyword: 'x-remote', schemaType: 'object' });
|
|
489
|
+
ajv.addKeyword({ keyword: 'x-readonly', schemaType: 'boolean' });
|
|
488
490
|
this.logger.debug('run hook during validation');
|
|
489
491
|
let issuccess = true;
|
|
490
492
|
// if (this.hooks.beforeValidation) {
|
|
@@ -85,17 +85,11 @@ export class SimpleAppInterceptor implements NestInterceptor {
|
|
|
85
85
|
error: err.options,
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// eventObj.errData = responseBody.error;
|
|
94
|
-
// eventObj.status = 'NG';
|
|
95
|
-
// eventObj.duration =
|
|
96
|
-
// endtime.getTime() - new Date(eventObj.created).getTime();
|
|
97
|
-
// eventObj.save();
|
|
98
|
-
|
|
88
|
+
eventObj.statusCode = err.status;
|
|
89
|
+
eventObj.errMsg = responseBody.message ;
|
|
90
|
+
eventObj.data = req.body;
|
|
91
|
+
eventObj.status = 'NG'
|
|
92
|
+
eventObj.errData = responseBody.error;
|
|
99
93
|
resp.status(err?.status ?? 500);
|
|
100
94
|
return responseBody;
|
|
101
95
|
}),
|
|
@@ -105,7 +99,8 @@ export class SimpleAppInterceptor implements NestInterceptor {
|
|
|
105
99
|
eventObj.isNew = false;
|
|
106
100
|
eventObj.statusCode = resp['statusCode'];
|
|
107
101
|
eventObj.updated = endtime.toISOString();
|
|
108
|
-
eventObj.status = 'OK';
|
|
102
|
+
eventObj.status = eventObj.status == 'D' ? 'OK' : eventObj.status;
|
|
103
|
+
|
|
109
104
|
eventObj.duration = endtime.getTime() - starttime.getTime();
|
|
110
105
|
await eventObj.save();
|
|
111
106
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span>
|
|
3
|
+
<slot> {{ (selectedOption as any)?.label ?? modelValue }}</slot>
|
|
4
|
+
</span>
|
|
5
|
+
</template>
|
|
6
|
+
<script setup lang="ts">
|
|
7
|
+
/**
|
|
8
|
+
* This file was automatically generated by simpleapp generator during initialization.
|
|
9
|
+
* DONT CHANGE THIS FILE CAUSE IT OVERRIDE
|
|
10
|
+
* last change 2024-04-14
|
|
11
|
+
* author: Ks Tan
|
|
12
|
+
*/
|
|
13
|
+
const modelValue = defineModel<string>();
|
|
14
|
+
|
|
15
|
+
const emits = defineEmits(["after"]);
|
|
16
|
+
const props = defineProps<{ value?: string; setting: any; data: any }>();
|
|
17
|
+
|
|
18
|
+
const options = ref([]);
|
|
19
|
+
|
|
20
|
+
const abortController = ref<AbortController | null>(null);
|
|
21
|
+
|
|
22
|
+
watchEffect(async (onCleanup) => {
|
|
23
|
+
if (!modelValue.value || modelValue.value === "") {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const remoteConfig = props.setting?.["x-remote"] ?? {};
|
|
28
|
+
if (!remoteConfig.url) {
|
|
29
|
+
options.value = [];
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Abort any previous fetch
|
|
34
|
+
if (abortController.value) {
|
|
35
|
+
abortController.value.abort();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
abortController.value = controller;
|
|
40
|
+
|
|
41
|
+
const { url, labelField, valueField } = remoteConfig;
|
|
42
|
+
|
|
43
|
+
const finalUrl = interpolateUrl(url, props.data);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const resp = await fetch(finalUrl, { signal: controller.signal });
|
|
47
|
+
const data = await resp.json();
|
|
48
|
+
|
|
49
|
+
options.value = data.map((item: any) => ({
|
|
50
|
+
...item,
|
|
51
|
+
label: item[labelField],
|
|
52
|
+
value: item[valueField],
|
|
53
|
+
}));
|
|
54
|
+
} catch (err: any) {
|
|
55
|
+
if (err.name === "AbortError") {
|
|
56
|
+
console.log("Fetch aborted");
|
|
57
|
+
} else {
|
|
58
|
+
console.error("Failed to load options:", err);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Cleanup when dependency changes or component unmounts
|
|
63
|
+
onCleanup(() => controller.abort());
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// also ensure cleanup on unmount
|
|
67
|
+
onBeforeUnmount(() => {
|
|
68
|
+
if (abortController.value) {
|
|
69
|
+
abortController.value.abort();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const selectedOption = computed(() => {
|
|
74
|
+
return options.value.find((option: any) => modelValue.value === option.value);
|
|
75
|
+
});
|
|
76
|
+
</script>
|
|
@@ -114,6 +114,20 @@
|
|
|
114
114
|
@change="onChange"
|
|
115
115
|
/>
|
|
116
116
|
|
|
117
|
+
<SimpleAppRemoteSelect
|
|
118
|
+
v-else-if="SimpleAppInputType.remoteselect == inputType"
|
|
119
|
+
v-model="modelValue"
|
|
120
|
+
:pt="pt"
|
|
121
|
+
:input-id="slotprops.uuid"
|
|
122
|
+
:path="setting.instancepath"
|
|
123
|
+
:readonly="isReadonly"
|
|
124
|
+
class="w w-full lg:w-full"
|
|
125
|
+
:placeholder="placeholder"
|
|
126
|
+
:setting="setting"
|
|
127
|
+
v-bind="componentProps"
|
|
128
|
+
@change="onChange"
|
|
129
|
+
/>
|
|
130
|
+
|
|
117
131
|
<MultiSelect
|
|
118
132
|
v-else-if="SimpleAppInputType.selectmultiple == inputType"
|
|
119
133
|
v-model="modelValue"
|
|
@@ -390,6 +404,7 @@ import type { TextareaProps } from "primevue/textarea";
|
|
|
390
404
|
import Textarea from "primevue/textarea";
|
|
391
405
|
import Editor from "primevue/editor";
|
|
392
406
|
import { SimpleAppInputType } from "~/types";
|
|
407
|
+
import SimpleAppRemoteSelect from "./SimpleAppRemoteSelect.vue";
|
|
393
408
|
const resetcount = ref(0);
|
|
394
409
|
const instancepath = ref("");
|
|
395
410
|
const modelValue = defineModel<any>({ required: true });
|
|
@@ -459,6 +474,10 @@ watch(props.setting.errors, (newvalue, oldvalue) => {
|
|
|
459
474
|
watchOnChange.value = true;
|
|
460
475
|
});
|
|
461
476
|
const isReadonly = computed(() => {
|
|
477
|
+
if (props.setting?.fieldsetting?.["x-readonly"]) {
|
|
478
|
+
return props.setting?.fieldsetting?.["x-readonly"];
|
|
479
|
+
}
|
|
480
|
+
|
|
462
481
|
if (props.readonly) {
|
|
463
482
|
return props.readonly;
|
|
464
483
|
} else if (props.setting.readonly) {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Select
|
|
3
|
+
v-model="modelValue"
|
|
4
|
+
:pt="pt"
|
|
5
|
+
:input-id="inputId"
|
|
6
|
+
:path="setting.instancepath"
|
|
7
|
+
:readonly="readonly"
|
|
8
|
+
class="w w-full lg:w-full"
|
|
9
|
+
:disabled="readonly ? true : false"
|
|
10
|
+
:options="options ?? []"
|
|
11
|
+
option-label="label"
|
|
12
|
+
option-value="value"
|
|
13
|
+
:placeholder="placeholder"
|
|
14
|
+
@change="handleChange"
|
|
15
|
+
/>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
setting: any;
|
|
21
|
+
readonly?: boolean;
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
hidelabel?: boolean;
|
|
24
|
+
inputId: string;
|
|
25
|
+
pt?: any;
|
|
26
|
+
}>();
|
|
27
|
+
|
|
28
|
+
const modelValue = defineModel();
|
|
29
|
+
|
|
30
|
+
const options = ref([]);
|
|
31
|
+
|
|
32
|
+
const abortController = ref<AbortController | null>(null);
|
|
33
|
+
|
|
34
|
+
function handleChange(newValue: any) {
|
|
35
|
+
const remoteConfig = props.setting.fieldsetting?.["x-remote"];
|
|
36
|
+
const actions = remoteConfig?.onChangeAction ?? [];
|
|
37
|
+
if (!Array.isArray(actions) || actions.length === 0) return;
|
|
38
|
+
|
|
39
|
+
const modelObject = props.setting.modelObject ?? {};
|
|
40
|
+
const selectedOption = options.value.find(
|
|
41
|
+
(opt: any) => opt.value === newValue,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
for (const action of actions) {
|
|
45
|
+
if (!action.updateField) continue;
|
|
46
|
+
|
|
47
|
+
let updateVal: any = action.updateValue;
|
|
48
|
+
|
|
49
|
+
if (
|
|
50
|
+
typeof updateVal === "string" &&
|
|
51
|
+
updateVal.startsWith("{") &&
|
|
52
|
+
updateVal.endsWith("}")
|
|
53
|
+
) {
|
|
54
|
+
// ✅ Replace {path.to.value} using selectedOption
|
|
55
|
+
const path = updateVal.slice(1, -1).trim();
|
|
56
|
+
const resolvedVal = resolvePath(selectedOption ?? {}, path);
|
|
57
|
+
updateVal = resolvedVal !== undefined ? resolvedVal : "";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setPath(modelObject, action.updateField, updateVal);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
watchEffect(async (onCleanup) => {
|
|
65
|
+
const remoteConfig = props.setting.fieldsetting?.["x-remote"] ?? {};
|
|
66
|
+
if (!remoteConfig.url) {
|
|
67
|
+
options.value = [];
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Abort any previous fetch
|
|
72
|
+
if (abortController.value) {
|
|
73
|
+
abortController.value.abort();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
abortController.value = controller;
|
|
78
|
+
|
|
79
|
+
const { url, labelField, valueField } = remoteConfig;
|
|
80
|
+
const modelObject = props.setting.modelObject ?? {};
|
|
81
|
+
const finalUrl = interpolateUrl(url, modelObject);
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const resp = await fetch(finalUrl, { signal: controller.signal });
|
|
85
|
+
const data = await resp.json();
|
|
86
|
+
|
|
87
|
+
options.value = data.map((item: any) => ({
|
|
88
|
+
...item,
|
|
89
|
+
label: item[labelField],
|
|
90
|
+
value: item[valueField],
|
|
91
|
+
}));
|
|
92
|
+
} catch (err: any) {
|
|
93
|
+
if (err.name === "AbortError") {
|
|
94
|
+
console.log("Fetch aborted");
|
|
95
|
+
} else {
|
|
96
|
+
console.error("Failed to load options:", err);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Cleanup when dependency changes or component unmounts
|
|
101
|
+
onCleanup(() => controller.abort());
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// also ensure cleanup on unmount
|
|
105
|
+
onBeforeUnmount(() => {
|
|
106
|
+
if (abortController.value) {
|
|
107
|
+
abortController.value.abort();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
</script>
|
|
@@ -4,66 +4,65 @@
|
|
|
4
4
|
* last change 2024-02-23
|
|
5
5
|
* Author: Ks Tan
|
|
6
6
|
*/
|
|
7
|
-
export enum SimpleAppInputType
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
'date'='date',
|
|
13
|
-
'time'='time',
|
|
14
|
-
'calendar'='calendar',
|
|
15
|
-
'datetime'='datetime',
|
|
7
|
+
export enum SimpleAppInputType {
|
|
8
|
+
"text" = "text",
|
|
9
|
+
"textarea" = "textarea",
|
|
10
|
+
"html" = "html",
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
"date" = "date",
|
|
13
|
+
"time" = "time",
|
|
14
|
+
"calendar" = "calendar",
|
|
15
|
+
"datetime" = "datetime",
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
"autocomplete" = "autocomplete",
|
|
18
|
+
"autocompletemultiple" = "autocompletemultiple",
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
"selectmultiple" = "selectmultiple",
|
|
21
|
+
"listmultiple" = "listmultiple",
|
|
22
|
+
"remoteselect" = "remoteselect",
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
"radio" = "radio",
|
|
25
|
+
"select" = "select",
|
|
26
|
+
"list" = "list",
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
'switch'='switch',
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
'documentno'='documentno',
|
|
34
|
-
'password'='password',
|
|
28
|
+
"chip" = "chip",
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'rating'='rating',
|
|
39
|
-
'slider'='slider',
|
|
40
|
-
'user'='user',
|
|
41
|
-
}
|
|
30
|
+
"checkbox" = "checkbox",
|
|
31
|
+
"switch" = "switch",
|
|
42
32
|
|
|
33
|
+
"documentno" = "documentno",
|
|
34
|
+
"password" = "password",
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
"number" = "number",
|
|
37
|
+
"money" = "money",
|
|
38
|
+
"rating" = "rating",
|
|
39
|
+
"slider" = "slider",
|
|
40
|
+
"user" = "user",
|
|
49
41
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
42
|
+
|
|
43
|
+
export type autocompletetype = {
|
|
44
|
+
_id: string;
|
|
45
|
+
label: string;
|
|
46
|
+
code: string;
|
|
47
|
+
[key: string]: any;
|
|
48
|
+
};
|
|
49
|
+
export enum FormCrudEvent {
|
|
50
|
+
"mount" = "mount",
|
|
51
|
+
"exit" = "exit",
|
|
52
|
+
"create" = "create",
|
|
53
|
+
"update" = "update",
|
|
54
|
+
"delete" = "delete",
|
|
55
|
+
"setDocStatus" = "setDocStatus",
|
|
57
56
|
}
|
|
58
57
|
export type FormActions = {
|
|
59
|
-
|
|
60
|
-
}
|
|
58
|
+
[key: string]: string[];
|
|
59
|
+
};
|
|
61
60
|
|
|
62
61
|
export type FormMenu = {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
62
|
+
type?: string;
|
|
63
|
+
action?: string;
|
|
64
|
+
label?: string | ((...args: any) => string) | undefined;
|
|
65
|
+
command?: Function;
|
|
66
|
+
event?: FormCrudEvent;
|
|
67
|
+
[key: string]: any;
|
|
68
|
+
};
|