@simitgroup/simpleapp-generator 1.0.16 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +28 -18
  2. package/backend1/.eslintrc.js +25 -0
  3. package/backend1/.prettierrc +4 -0
  4. package/backend1/README.md +73 -0
  5. package/backend1/nest-cli.json +8 -0
  6. package/backend1/package.json +69 -0
  7. package/backend1/pnpm-lock.yaml +5208 -0
  8. package/backend1/src/app.controller.js +71 -0
  9. package/backend1/src/app.controller.js.map +1 -0
  10. package/backend1/src/app.controller.spec.js +21 -0
  11. package/backend1/src/app.controller.spec.js.map +1 -0
  12. package/backend1/src/app.controller.spec.ts +22 -0
  13. package/backend1/src/app.controller.ts +12 -0
  14. package/backend1/src/app.module.js +67 -0
  15. package/backend1/src/app.module.js.map +1 -0
  16. package/backend1/src/app.module.ts +10 -0
  17. package/backend1/src/app.service.js +64 -0
  18. package/backend1/src/app.service.js.map +1 -0
  19. package/backend1/src/app.service.ts +8 -0
  20. package/backend1/src/main.js +10 -0
  21. package/backend1/src/main.js.map +1 -0
  22. package/backend1/src/main.ts +8 -0
  23. package/backend1/test/app.e2e-spec.js +45 -0
  24. package/backend1/test/app.e2e-spec.js.map +1 -0
  25. package/backend1/test/app.e2e-spec.ts +24 -0
  26. package/backend1/test/jest-e2e.json +9 -0
  27. package/backend1/tsconfig.build.json +4 -0
  28. package/backend1/tsconfig.json +21 -0
  29. package/definations/category.cat.jsonschema.json +1 -0
  30. package/definations/product.prd.jsonschema.json +40 -0
  31. package/dist/createproject.js +13 -3
  32. package/dist/createproject.js.map +1 -1
  33. package/dist/generate.js +3 -3
  34. package/dist/generate.js.map +1 -1
  35. package/dist/index.js +36 -51
  36. package/dist/index.js.map +1 -1
  37. package/dist/index2.js +118 -0
  38. package/dist/index2.js.map +1 -0
  39. package/dist/installdependency.js +20 -0
  40. package/dist/installdependency.js.map +1 -0
  41. package/dist/installnest.js +2 -0
  42. package/dist/installnest.js.map +1 -0
  43. package/dist/installnuxt.js +2 -0
  44. package/dist/installnuxt.js.map +1 -0
  45. package/dist/processors/jsonschemabuilder.js +1 -1
  46. package/dist/processors/jsonschemabuilder.js.map +1 -1
  47. package/dist/validation.js +2 -0
  48. package/dist/validation.js.map +1 -0
  49. package/openapitools.json +0 -0
  50. package/package.json +5 -1
  51. package/sampleconfig.json +2 -2
  52. package/src/createproject.ts +17 -4
  53. package/src/generate.ts +3 -3
  54. package/src/index.ts +56 -60
  55. package/src/index2.ts +132 -0
  56. package/src/installdependency.sh +5 -0
  57. package/src/installdependency.ts +4 -0
  58. package/src/installnest.ts +0 -0
  59. package/src/installnuxt.ts +0 -0
  60. package/src/processors/jsonschemabuilder.ts +1 -1
  61. package/src/validation.ts +0 -0
  62. package/templates/SimpleAppService.eta +3 -0
  63. package/templates/basic/pageindex.vue.eta +13 -9
  64. package/templates/nuxt/components.crudsimple.vue.eta +24 -7
  65. package/templates/nuxt/nuxt.config.ts.eta +6 -0
  66. package/templates/nuxt/plugins.simpleapp.ts.eta +8 -3
  67. package/tsconfig.json +1 -1
  68. package/dist/app.module.ts +0 -14
package/src/index2.ts ADDED
@@ -0,0 +1,132 @@
1
+ #! /usr/bin/env node
2
+
3
+ import { error } from "console"
4
+
5
+ const program = require("commander") // add this line
6
+ const Fieldtypes= require( './type')
7
+ const generator= require( './generate')
8
+ const fs = require( 'fs')
9
+ const createproject =require( './createproject')
10
+ const {exec} = require( "child_process")
11
+
12
+ const capitalizeFirstLetter= require( './libs')
13
+ const {Logger, ILogObj} = require( "tslog");
14
+
15
+ const log :typeof Logger= new Logger();
16
+
17
+ const figlet = require("figlet");
18
+ // const program = new Command();
19
+ const pj = require('../package.json')
20
+
21
+ let version=pj.version
22
+ program
23
+ .version(version)
24
+ .description("An simpleapp CLI tool for generate frontend (vuejs) and backend(nestjs) codes")
25
+ .option("-c, --config-file <value>", 'configuration file content such as:{"definationsFolder":"./definations", "backendFolder":"./nestproject/src/docs", "frontendFolder":"./nuxt/server"}')
26
+ // .option("-s, --definations-folder <value>", "load defination files from which folder")
27
+ // .option("-b, --backend-folder <value>", "Create backend code at which folder")
28
+ // .option("-f, --frontend-folder <value>", "Create frontend code at which folder")
29
+ // .option("-i, --openapi3-yaml <value>", 'openapi3.yaml generated by backend server')
30
+ .parse(process.argv);
31
+
32
+
33
+ const options = program.opts();
34
+ console.log(figlet.textSync(`SimpleApp Generator ${version}`));
35
+ // console.log(options)
36
+ let path=''
37
+ if(!options.configFile){
38
+ log.error("Config file parameter is required. Example: simpleapp-generator -c ./config.json")
39
+ throw "Undefine configuration file"
40
+ }
41
+ else if(options.configFile && options.configFile[0]=='/'){
42
+ path=options.configFile
43
+ }
44
+ else if(options.configFile){
45
+ path=process.cwd()+'/'+options.configFile
46
+ }else{
47
+ log.error("undefine configuration file, use command simpleapp-generator -c <configfilename.json>")
48
+ throw error
49
+ }
50
+ const configs = require(path)
51
+ console.log("configurations: ",configs)
52
+ const definationsFolder = configs.definationsFolder ?? options.definationsFolder
53
+ const backendFolder = configs.backendFolder ?? options.backendFolder
54
+ const frontendFolder = configs.frontendFolder ?? options.frontendFolder
55
+ const openapi3Yaml:string = configs.openapi3Yaml ?? options.openapi3Yaml
56
+
57
+ const runGenNext = async (callback)=>{
58
+ let isnestready=false
59
+ if(!fs.existsSync(backendFolder)){
60
+ console.log("Nest does not exists, begin install nodejs environment tools: [pnpm, @nestjs/cli, @openapitools/openapi-generator-cli, nuxi]")
61
+ isnestready = await exec("npm install -g pnpm @nestjs/cli @openapitools/openapi-generator-cli nuxi",async (a,b,c)=>{
62
+ console.log("result of environment tools:",a,b,c)
63
+ console.log(`Install new nest backend: 'nest new -p pnpm ${backendFolder}'`)
64
+ return await exec(`nest new -p pnpm ${backendFolder}`,(a,b,c)=>{
65
+ console.log("new nest result",a,b,c)
66
+ return true
67
+ })
68
+ })
69
+ }else{
70
+ isnestready=true
71
+ }
72
+
73
+
74
+
75
+ if(isnestready){
76
+ if(!fs.existsSync(`${backendFolder}/.env`)){
77
+ log.info(`initial nest configuratoin for simpleapp generator`)
78
+ await createNest(backendFolder,callback)
79
+ }else{
80
+ log.warn(`.env file exists, skip nest initialization`)
81
+ callback()
82
+ }
83
+ }else{
84
+ log.error('nest not ready, which shouldnot continue')
85
+ throw error
86
+
87
+ }
88
+
89
+ }
90
+ const runGenNuxt = async (callback)=>{
91
+
92
+ if(!fs.existsSync(frontendFolder)){
93
+ log.error(`${frontendFolder} does not exists, please run "npx nuxi@latest init ${frontendFolder}"`)
94
+ }else if(!fs.existsSync(`${frontendFolder}/.env`)){
95
+ log.info(`initial nuxt configuratoin for simpleapp generator`)
96
+ createNuxt(frontendFolder,callback)
97
+ }else{
98
+ log.warn(`.env file exists, skip nuxt initialization`)
99
+ callback()
100
+ }
101
+ }
102
+
103
+
104
+ const generateCode = async () =>{
105
+
106
+ await runGenNext(async ()=>{
107
+ log.info("runGenNext (backen) done")
108
+ await runGenNuxt( async ()=>{
109
+ log.info("runGenNuxt (frontend) done")
110
+ generator.initialize(definationsFolder,backendFolder,frontendFolder)
111
+ exec(`cd ${frontendFolder};npx prettier --write "./pages/**/*.vue" "./simpleapp/**/*" `)
112
+ exec(`cd ${backendFolder};npx run format `)
113
+ if(openapi3Yaml !=''){
114
+
115
+ exec(`openapi-generator-cli generate -i ${openapi3Yaml} -o ${frontendFolder}/simpleapp/openapi -g typescript-axios --skip-validate-spec`,(error, stdout, stderr)=>{
116
+ if(error){
117
+ log.error(stderr);
118
+ }
119
+ });
120
+ }
121
+ })
122
+ })
123
+
124
+ }
125
+
126
+ log.info("generate code: ")
127
+ generateCode()
128
+
129
+
130
+
131
+ // pnpm exec prettier ./src/docs --write
132
+ // pnpm exec prettier ./apiclients --write
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ # npm install -g pnpm @nestjs/cli @openapitools/openapi-generator-cli nuxi
3
+ # nest new -p pnpm $backendFolder
4
+ npx nuxi@latest init ../../aaa
5
+ # ${frontendFolder}"
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+ npm install -g pnpm @nestjs/cli @openapitools/openapi-generator-cli nuxi
3
+ nest new -p pnpm $backendFolder
4
+ npx nuxi@latest init ${frontendFolder}"
File without changes
File without changes
@@ -129,7 +129,7 @@ const genSchema = (
129
129
  if (obj.type == 'object') {
130
130
 
131
131
  if(obj[FOREIGNKEY_PROPERTY]){
132
- console.warn("FOREIGNKEY_PROPERTY exists",FOREIGNKEY_PROPERTY,obj[FOREIGNKEY_PROPERTY])
132
+ // console.warn("FOREIGNKEY_PROPERTY exists",FOREIGNKEY_PROPERTY,obj[FOREIGNKEY_PROPERTY])
133
133
  const masterdatacollection = obj[FOREIGNKEY_PROPERTY]
134
134
  const clientdatacollection = docname.toLowerCase()
135
135
  const foreignkeyidentity= key+'._id'
File without changes
@@ -123,10 +123,13 @@ export class SimpleAppService<T extends { _id?: string }> {
123
123
  addFormats(ajv);
124
124
  ajv.addFormat('field-autocomplete-code', /.*$/);
125
125
  ajv.addFormat('field-autocomplete-name', /.*$/);
126
+ ajv.addFormat('x-text',/.*$/)
127
+ ajv.addFormat('x-html',/.*$/)
126
128
  ajv.addKeyword({
127
129
  keyword: 'x-foreignkey',
128
130
  type: 'string',
129
131
  });
132
+
130
133
  if (!this.hook('pre-validation', data)) {
131
134
  const erromsg: string[] = [];
132
135
  for (let i = 0; i < this.errorlist.length; i++) {
@@ -8,26 +8,30 @@
8
8
  <% let obj=it.jsonschema.properties[key] %>
9
9
  <% if(skipcolumns.indexOf(key)>=0){ %>
10
10
  <% } else if(obj.type=='boolean'){ %>
11
- <SimpleAppCheckbox :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
11
+ <SimpleAppCheckbox autofocus :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
12
12
  <% } else if(obj.type=='number' || obj.type=='integer'){ %>
13
- <SimpleAppNumber :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
13
+ <SimpleAppNumber autofocus :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
14
14
  <% } else if(obj.type=='array' && obj.items && obj.items.type =='string' ){ %>
15
- <SimpleAppChip :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
15
+ <SimpleAppChip autofocus :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
16
16
  <% } else if(obj.type=='object' && typeof obj['x-foreignkey']!='undefined'){ %>
17
- <SimpleAppAutocomplete :setting="o.getField('#/properties/<%= key %>')"
17
+ <SimpleAppAutocomplete autofocus :setting="o.getField('#/properties/<%= key %>')"
18
18
  v-model="data.<%= key %>" optionLabel="label" :remote-src="getAutocomplete('<%=obj['x-foreignkey']%>')"/>
19
19
  <% } else if(obj.type=='string'){ %>
20
20
  <% if(obj.format=='date'){ %>
21
- <SimpleAppText type="<%=formattype%>" :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
21
+ <SimpleAppText type="<%=obj.format%>" autofocus :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
22
+ <% } else if(obj.format=='x-text'){ %>
23
+ <SimpleAppTextarea :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
24
+ <% } else if(obj.format=='x-html'){ %>
25
+ <SimpleAppEditor editorStyle="height: 320px" :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
22
26
  <% } else if(obj.format=='email'){ %>
23
- <SimpleAppText type="<%=formattype%>" :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
27
+ <SimpleAppText autofocus type="<%=obj.type%>" :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
24
28
  <% } else if(obj.enum){ %>
25
- <SimpleAppRadio :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
29
+ <SimpleAppRadio autofocus :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
26
30
  <% } else {%>
27
- <SimpleAppText :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
31
+ <SimpleAppText autofocus :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
28
32
  <% }%>
29
33
  <% } else { %>
30
- <SimpleAppText :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
34
+ <SimpleAppText autofocus :setting="o.getField('#/properties/<%= key %>')" v-model="data.<%= key %>"/>
31
35
  <% }%>
32
36
  <%})%>
33
37
 
@@ -16,9 +16,10 @@
16
16
  <Dialog v-model:visible="visible" modal header="Header" class="crudsimple-dialog" :autoZIndex="false" :style="{zIndex:100, width: '80vw' }">
17
17
  <SimpleAppForm :document="obj" :title="title" #default="o">
18
18
  <div class="simpleapp-tool-bar" >
19
- <button class="bg-default" @click="newData" type="reset">New</button>
20
- <button class="bg-primary" @click="saveData" type="submit">Save</button>
21
- <button class="bg-danger" @click="deleteData($event)">Delete</button>
19
+ <button class="bg-default" :disabled="disabled" @click="newData" type="reset">New</button>
20
+ <button class="bg-primary" :disabled="disabled" @click="saveData" type="submit">Save</button>
21
+ <button class="bg-danger" :disabled="disabled" @click="deleteData($event)">Delete</button>
22
+ <ProgressSpinner v-if="disabled==true" style="width: 2rem; height: 2rem" ></ProgressSpinner>
22
23
  <ConfirmPopup></ConfirmPopup>
23
24
  </div>
24
25
  <slot :data="o.data" :getField="o.getField" name="default"></slot>
@@ -31,6 +32,9 @@ import { SimpleAppClient } from '@simitgroup//simpleapp-vue-component/src/Simple
31
32
  import SimpleAppForm from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppForm.vue';
32
33
  import SimpleAppDatatable from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppDatatable.vue';
33
34
  import Dialog from 'primevue/dialog';
35
+ import axios from 'axios'
36
+ import ProgressSpinner from 'primevue/progressspinner';
37
+
34
38
  import ConfirmPopup from 'primevue/confirmpopup';
35
39
  import { useConfirm } from "primevue/useconfirm";
36
40
 
@@ -43,44 +47,57 @@ const props = defineProps<{
43
47
  const visible = ref(false)
44
48
  const obj = props.document
45
49
  const data = obj.getReactiveData()
50
+ const disabled=ref(false)
46
51
  const recordlist = ref();
47
-
52
+ const setCsrf=()=>{
53
+ // const { csrf } = useCsrf()
54
+ // console.log('csrf',csrf)
55
+ // axios.defaults.headers.common = {
56
+ // "X-CSRF-TOKEN": csrf
57
+ // };
58
+ }
48
59
 
49
60
  const refresh = () => {
50
61
  obj.list().then((res:any) => {
51
62
  recordlist.value = res;
63
+ disabled.value=false
52
64
  });
53
65
  };
54
66
  const newData = () => {
55
67
  obj.setNew()
68
+ setCsrf()
56
69
  visible.value=true;
57
70
  };
58
71
 
59
72
  const editRecord = (event: any) => {
60
73
  obj.getById(event.data._id);
74
+ setCsrf()
61
75
  visible.value=true
62
76
  };
63
77
 
64
78
  const saveData = () => {
79
+ disabled.value=true
65
80
  if (data.value._id == "") {
66
- obj.create().then(()=>visible.value=false).finally(() => refresh());
81
+ obj.create().then(()=>{visible.value=false}).catch(()=>setCsrf()).finally(() => refresh());
67
82
  } else {
68
- obj.update().then(()=>visible.value=false).finally(() => refresh());
83
+ obj.update().then(()=>visible.value=false).catch(()=>setCsrf()).finally(() => refresh());
69
84
  }
70
85
  };
71
86
  const deleteData = (event:Event) => {
72
- console.log("deleteData")
87
+
73
88
  confirm.require({
74
89
  target: event.currentTarget as HTMLElement,
75
90
  message:'Delete?',
76
91
  icon: 'pi pi-exclamation-triangle',
77
92
  acceptClass: 'p-button-danger',
78
93
  accept: ()=>{
94
+ disabled.value=true
79
95
  obj.delete(data.value._id ?? "").then(()=>visible.value=false).finally(() => {
80
96
  refresh();
81
97
  });
82
98
  },
83
99
  reject: () => {
100
+ setCsrf()
84
101
  console.log("Cancel delete")
85
102
  }
86
103
  })
@@ -18,11 +18,17 @@ tailwindcss: {
18
18
  // Options
19
19
  },
20
20
  modules: [
21
+ // '@sidebase/nuxt-auth',
22
+ "nuxt-security",
21
23
  '@nuxtjs/tailwindcss',
24
+ '@vueuse/nuxt',
22
25
  // '@nuxtjs/color-mode'
23
26
 
24
27
  ],
25
28
  ssr: true,
29
+ security: {
30
+ csrf: true,
31
+ },
26
32
  css: [
27
33
  "primevue/resources/themes/lara-light-blue/theme.css",
28
34
  'primeicons/primeicons.css'
@@ -1,4 +1,5 @@
1
1
  import { defineNuxtPlugin } from "#app";
2
+ import axios from 'axios'
2
3
  import PrimeVue from "primevue/config";
3
4
  import SimpleAppAutocomplete from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppAutocomplete.vue'
4
5
  import SimpleAppAutocompletemulti from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppAutocompletemulti.vue'
@@ -6,7 +7,7 @@ import SimpleAppCalendar from '@simitgroup/simpleapp-vue-component/src/component
6
7
  import SimpleAppCheckbox from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppCheckbox.vue'
7
8
  import SimpleAppChip from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppChip.vue'
8
9
  import SimpleAppColor from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppColor.vue'
9
- import SimpleAppEditor from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppEditor.vue'
10
+ // import SimpleAppEditor from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppEditor.vue'
10
11
  import SimpleAppForm from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppForm.vue'
11
12
  import SimpleAppList from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppList.vue'
12
13
  import SimpleAppListmulti from '@simitgroup/simpleapp-vue-component/src/components/SimpleAppListmulti.vue'
@@ -26,12 +27,15 @@ import SimpleAppDatatable from '@simitgroup/simpleapp-vue-component/src/componen
26
27
  import mitt from 'mitt'
27
28
  import ToastService from 'primevue/toastservice';
28
29
  import ConfirmationService from 'primevue/confirmationservice';
29
-
30
+ //import Quill from 'quill'
30
31
  const emitter = mitt()
31
32
 
32
33
 
33
34
 
34
35
  export default defineNuxtPlugin((nuxtApp) => {
36
+ const { csrf } = useCsrf()
37
+ axios.defaults.headers.common = {"CSRF-TOKEN": csrf};
38
+
35
39
  nuxtApp.vueApp.use(PrimeVue, { ripple: true });
36
40
  nuxtApp.vueApp
37
41
  .component("SimpleAppAutocomplete",SimpleAppAutocomplete)
@@ -40,7 +44,7 @@ export default defineNuxtPlugin((nuxtApp) => {
40
44
  .component("SimpleAppCheckbox",SimpleAppCheckbox)
41
45
  .component("SimpleAppChip",SimpleAppChip)
42
46
  .component("SimpleAppColor",SimpleAppColor)
43
- .component("SimpleAppEditor",SimpleAppEditor)
47
+ // .component("SimpleAppEditor",SimpleAppEditor) // not suitable, will cause problem in ssr mode
44
48
  .component("SimpleAppForm",SimpleAppForm)
45
49
  .component("SimpleAppList",SimpleAppList)
46
50
  .component("SimpleAppListmulti",SimpleAppListmulti)
@@ -56,6 +60,7 @@ export default defineNuxtPlugin((nuxtApp) => {
56
60
  .component("SimpleAppTextarea",SimpleAppTextarea)
57
61
  .component("SimpleAppValue",SimpleAppValue)
58
62
  .component("SimpleFieldContainer",SimpleFieldContainer)
63
+ //.component("Quill",Quill) // cause problem in ssr mode
59
64
  .use(ToastService).use(ConfirmationService)
60
65
  ;
61
66
  return {
package/tsconfig.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "sourceMap": true,
9
9
  "esModuleInterop": true,
10
10
  "moduleResolution": "node",
11
-
11
+ "resolveJsonModule":true,
12
12
  "strictNullChecks": false,
13
13
  "noImplicitAny": false,
14
14
  "strictBindCallApply": false,
@@ -1,14 +0,0 @@
1
- import { Module } from '@nestjs/common';
2
- import { MongooseModule } from '@nestjs/mongoose';
3
- import { ConfigModule } from '@nestjs/config';
4
-
5
- import {PersonModule} from './docs/pes/pes.module'
6
- import {SalesinvoiceModule} from './docs/si/si.module'
7
-
8
- @Module({
9
- //define environment variables: MONGODB_URL='mongodb://<user>:<pass>@<host>:<port>/<db>?authMechanism=DEFAULT'
10
- imports: [ConfigModule.forRoot(),MongooseModule.forRoot(process.env.MONGODB_URL),PersonModule,SalesinvoiceModule,],
11
- controllers: [],
12
- providers: [],
13
- })
14
- export class AppModule {}