@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 CHANGED
@@ -1,3 +1,8 @@
1
+ [2.0.1v-alpha]
2
+
3
+ 1. Cron add debugger
4
+ 2. Add remote selection
5
+
1
6
  [2.0.1u-alpha]
2
7
 
3
8
  1. Allow array string custom field to set input type
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simitgroup/simpleapp-generator",
3
- "version": "2.0.1u-alpha",
3
+ "version": "2.0.1v-alpha",
4
4
  "description": "frontend nuxtjs and backend nests code generator using jsonschema",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -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) {
@@ -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 "../../user-context/robot-user.service";
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
- // eventObj.statusCode = err.status;
89
- // eventObj.errMsg = responseBody.message;
90
- // const endtime = new Date();
91
- // eventObj.updated = endtime.toISOString();
92
- // eventObj.data = req.body;
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
- 'text'='text',
9
- 'textarea'='textarea',
10
- 'html'='html',
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
- 'autocomplete'='autocomplete',
18
- 'autocompletemultiple'='autocompletemultiple',
12
+ "date" = "date",
13
+ "time" = "time",
14
+ "calendar" = "calendar",
15
+ "datetime" = "datetime",
19
16
 
20
- 'selectmultiple'='selectmultiple',
21
- 'listmultiple'='listmultiple',
17
+ "autocomplete" = "autocomplete",
18
+ "autocompletemultiple" = "autocompletemultiple",
22
19
 
23
- 'radio'='radio',
24
- 'select'='select',
25
- 'list'='list',
20
+ "selectmultiple" = "selectmultiple",
21
+ "listmultiple" = "listmultiple",
22
+ "remoteselect" = "remoteselect",
26
23
 
27
- 'chip'='chip',
24
+ "radio" = "radio",
25
+ "select" = "select",
26
+ "list" = "list",
28
27
 
29
- 'checkbox'='checkbox',
30
- 'switch'='switch',
31
-
32
-
33
- 'documentno'='documentno',
34
- 'password'='password',
28
+ "chip" = "chip",
35
29
 
36
- 'number'='number',
37
- 'money'='money',
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
- export type autocompletetype={
45
- _id:string
46
- label:string
47
- code:string
48
- [key:string]:any
36
+ "number" = "number",
37
+ "money" = "money",
38
+ "rating" = "rating",
39
+ "slider" = "slider",
40
+ "user" = "user",
49
41
  }
50
- export enum FormCrudEvent {
51
- 'mount'='mount',
52
- 'exit'='exit',
53
- 'create'='create',
54
- 'update'='update',
55
- 'delete'='delete',
56
- 'setDocStatus'='setDocStatus',
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
- [key:string]:string[]
60
- }
58
+ [key: string]: string[];
59
+ };
61
60
 
62
61
  export type FormMenu = {
63
- type?:string
64
- action?:string
65
- label?: string | ((...args: any) => string) | undefined
66
- command? : Function
67
- event?:FormCrudEvent
68
- [key:string] : any
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
+ };