@agung_dhewe/webapps 1.1.2
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/LICENSE +28 -0
- package/README.md +2 -0
- package/jsconfig.json +10 -0
- package/libs/fgta5js-dist/fgta5js-v1.8.3.min.css +2 -0
- package/libs/fgta5js-dist/fgta5js-v1.8.3.min.js +11 -0
- package/libs/fgta5js-dist/fgta5js-v1.8.3.min.js.map +1 -0
- package/libs/fgta5js-dist/fonts/karla-italic-latin-ext.woff2 +0 -0
- package/libs/fgta5js-dist/fonts/karla-italic-latin.woff2 +0 -0
- package/libs/fgta5js-dist/fonts/karla-normal-latin-ext.woff2 +0 -0
- package/libs/fgta5js-dist/fonts/karla-normal-latin.woff2 +0 -0
- package/libs/fgta5js-dist/fonts/karla.css +142 -0
- package/libs/webmodule/module-edit.css +163 -0
- package/libs/webmodule/module-footer.css +22 -0
- package/libs/webmodule/module-list.css +25 -0
- package/libs/webmodule/module.css +52 -0
- package/libs/webmodule/module.js +195 -0
- package/libs/webmodule/pagehelper.mjs +45 -0
- package/modules/generator/appgen-components.mjs +142 -0
- package/modules/generator/appgen-icons.mjs +6 -0
- package/modules/generator/appgen-io.mjs +784 -0
- package/modules/generator/appgen-ui-search.mjs +173 -0
- package/modules/generator/appgen-ui-unique.mjs +153 -0
- package/modules/generator/appgen-ui.mjs +1181 -0
- package/modules/generator/generator-context.mjs +18 -0
- package/modules/generator/generator-designtemplate.html +1508 -0
- package/modules/generator/generator-ext.html +0 -0
- package/modules/generator/generator-ext.mjs +3 -0
- package/modules/generator/generator.css +642 -0
- package/modules/generator/generator.mjs +195 -0
- package/modules/generator/generator.png +0 -0
- package/modules/generator/generatorEdit.html +185 -0
- package/modules/generator/generatorEdit.mjs +238 -0
- package/modules/generator/generatorList.html +32 -0
- package/modules/generator/generatorList.mjs +243 -0
- package/modules/login/login.css +11 -0
- package/modules/login/login.html +12 -0
- package/modules/login/login.mjs +111 -0
- package/package.json +46 -0
- package/percobaan/simmpan-ke-minio.js +24 -0
- package/src/api.js +80 -0
- package/src/apis/generator.api.js +226 -0
- package/src/apis/login.api.js +109 -0
- package/src/bucket.js +24 -0
- package/src/context.js +26 -0
- package/src/datalog.sql +22 -0
- package/src/datarecords.js +0 -0
- package/src/db.js +61 -0
- package/src/generator/createApiExtenderModule.js +54 -0
- package/src/generator/createApiModule.js +218 -0
- package/src/generator/createIcon.js +62 -0
- package/src/generator/createInfoAboutExtender.js +42 -0
- package/src/generator/createInfoLogs.js +41 -0
- package/src/generator/createInfoRecordExtender.js +41 -0
- package/src/generator/createModuleContext.js +48 -0
- package/src/generator/createModuleDetilEditHtml.js +110 -0
- package/src/generator/createModuleDetilEditMjs.js +172 -0
- package/src/generator/createModuleDetilListHtml.js +146 -0
- package/src/generator/createModuleDetilListMjs.js +73 -0
- package/src/generator/createModuleEjs.js +51 -0
- package/src/generator/createModuleExtenderHtml.js +43 -0
- package/src/generator/createModuleExtenderMjs.js +43 -0
- package/src/generator/createModuleHeaderEditHtml.js +148 -0
- package/src/generator/createModuleHeaderEditMjs.js +197 -0
- package/src/generator/createModuleHeaderListHtml.js +144 -0
- package/src/generator/createModuleHeaderListMjs.js +67 -0
- package/src/generator/createModuleMjs.js +67 -0
- package/src/generator/createModuleRollup.js +42 -0
- package/src/generator/createProgramData.js +96 -0
- package/src/generator/createTable.js +156 -0
- package/src/generator/ddl.js +475 -0
- package/src/generator/helper.js +149 -0
- package/src/generator/templates/__rollup-module.ejs +90 -0
- package/src/generator/templates/api-extender-module.js.ejs +0 -0
- package/src/generator/templates/api-module.js.ejs +818 -0
- package/src/generator/templates/module-context.ejs +16 -0
- package/src/generator/templates/module-ext-about.ejs +1 -0
- package/src/generator/templates/module-ext-record.ejs +1 -0
- package/src/generator/templates/module-ext.html.ejs +3 -0
- package/src/generator/templates/module-ext.mjs.ejs +21 -0
- package/src/generator/templates/module-logs.ejs +14 -0
- package/src/generator/templates/module.ejs.ejs +48 -0
- package/src/generator/templates/module.mjs.ejs +256 -0
- package/src/generator/templates/moduleDetilEdit.html.ejs +34 -0
- package/src/generator/templates/moduleDetilEdit.mjs.ejs +792 -0
- package/src/generator/templates/moduleDetilList.html.ejs +26 -0
- package/src/generator/templates/moduleDetilList.mjs.ejs +319 -0
- package/src/generator/templates/moduleHeaderEdit.html.ejs +53 -0
- package/src/generator/templates/moduleHeaderEdit.mjs.ejs +807 -0
- package/src/generator/templates/moduleHeaderList.html.ejs +24 -0
- package/src/generator/templates/moduleHeaderList.mjs.ejs +308 -0
- package/src/generator/templates/sqlAddField.ejs +3 -0
- package/src/generator/templates/sqlAddForeignKey.ejs +12 -0
- package/src/generator/templates/sqlAddUniqueIndex.ejs +4 -0
- package/src/generator/templates/sqlCreateTable.ejs +9 -0
- package/src/generator/templates/sqlDropForeignKey.ejs +3 -0
- package/src/generator/templates/sqlDropUniqueIndex.ejs +4 -0
- package/src/generator/templates/sqlModifyField.ejs +6 -0
- package/src/generator/trygenerate.js +83 -0
- package/src/generator/worker.js +389 -0
- package/src/helper.js +82 -0
- package/src/logger.js +39 -0
- package/src/router.js +84 -0
- package/src/routers/defaultLoginApi.js +29 -0
- package/src/routers/defaultLoginAsset.js +18 -0
- package/src/routers/defaultLoginPage.js +36 -0
- package/src/routers/defaultRootIndex.js +16 -0
- package/src/routers/downloadHandler.js +51 -0
- package/src/routers/fileUploadApi.js +15 -0
- package/src/routers/generatorApi.js +30 -0
- package/src/routers/generatorAsset.js +18 -0
- package/src/routers/generatorPage.js +37 -0
- package/src/routers/handleError.js +43 -0
- package/src/routers/handleModuleNotfound.js +12 -0
- package/src/routers/moduleApi.js +34 -0
- package/src/routers/modulePage.js +102 -0
- package/src/sequencerdoc.js +311 -0
- package/src/sequencerline.js +214 -0
- package/src/session.js +57 -0
- package/src/startup.js +59 -0
- package/src/webapps.js +239 -0
- package/src/workermanager.js +83 -0
- package/templates/_lib_debug.ejs +11 -0
- package/templates/_lib_production.ejs +5 -0
- package/templates/application.page.ejs +143 -0
- package/templates/generator.page.ejs +131 -0
- package/templates/index.page.ejs +24 -0
- package/templates/login.page.ejs +102 -0
- package/templates/moduleError.ejs +16 -0
- package/templates/moduleNotfound.ejs +14 -0
- package/webapps.code-workspace +11 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import pgp from 'pg-promise'
|
|
3
|
+
import db from '../db.js'
|
|
4
|
+
import { workerData, parentPort } from 'worker_threads';
|
|
5
|
+
import { access, mkdir } from 'fs/promises';
|
|
6
|
+
import { constants } from 'fs';
|
|
7
|
+
|
|
8
|
+
import { isFileExist } from './helper.js'
|
|
9
|
+
|
|
10
|
+
import { createModuleRollup } from './createModuleRollup.js'
|
|
11
|
+
import { createModuleContext } from './createModuleContext.js'
|
|
12
|
+
import { createModuleExtenderMjs } from './createModuleExtenderMjs.js'
|
|
13
|
+
import { createModuleExtenderHtml } from './createModuleExtenderHtml.js'
|
|
14
|
+
import { createModuleEjs } from './createModuleEjs.js'
|
|
15
|
+
import { createModuleMjs } from './createModuleMjs.js'
|
|
16
|
+
import { createModuleHeaderListHtml } from './createModuleHeaderListHtml.js'
|
|
17
|
+
import { createModuleHeaderListMjs } from './createModuleHeaderListMjs.js'
|
|
18
|
+
import { createModuleHeaderEditHtml } from './createModuleHeaderEditHtml.js'
|
|
19
|
+
import { createModuleHeaderEditMjs } from './createModuleHeaderEditMjs.js'
|
|
20
|
+
import { createModuleDetilListHtml } from './createModuleDetilListHtml.js'
|
|
21
|
+
import { createModuleDetilListMjs } from './createModuleDetilListMjs.js'
|
|
22
|
+
import { createModuleDetilEditHtml } from './createModuleDetilEditHtml.js'
|
|
23
|
+
import { createModuleDetilEditMjs } from './createModuleDetilEditMjs.js'
|
|
24
|
+
import { createApiModule } from './createApiModule.js'
|
|
25
|
+
import { createApiExtenderModule } from './createApiExtenderModule.js'
|
|
26
|
+
import { createTable } from './createTable.js';
|
|
27
|
+
import { createInfoAboutExtender } from './createInfoAboutExtender.js';
|
|
28
|
+
import { createInfoLogs } from './createInfoLogs.js';
|
|
29
|
+
import { createInfoRecordExtender } from './createInfoRecordExtender.js';
|
|
30
|
+
import { createIcon } from './createIcon.js'
|
|
31
|
+
import { createProgramData } from './createProgramData.js'
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
const { generator_id, user_id, user_name, ipaddress, jeda } = workerData;
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
main(generator_id)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async function main(id) {
|
|
42
|
+
try {
|
|
43
|
+
const queryParams = {generator_id: id}
|
|
44
|
+
const sql = 'select generator_data from core."generator" where generator_id = \${generator_id}'
|
|
45
|
+
const data = await db.one(sql, queryParams);
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
// cek dahulu apakah directory tujuan benar
|
|
49
|
+
// console.log(data)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
await generate(id, data.generator_data)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
} catch (err) {
|
|
56
|
+
err.message = `Generator Worker: ${err.message}`
|
|
57
|
+
throw err
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function sleep(s) {
|
|
62
|
+
if (s==0) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return new Promise(lanjut=>{
|
|
67
|
+
setTimeout(()=>{
|
|
68
|
+
lanjut()
|
|
69
|
+
}, s*1000)
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function generate(id, data) {
|
|
74
|
+
|
|
75
|
+
const now = new Date();
|
|
76
|
+
|
|
77
|
+
const options = {
|
|
78
|
+
day: 'numeric',
|
|
79
|
+
month: 'short',
|
|
80
|
+
year: 'numeric',
|
|
81
|
+
hour: '2-digit',
|
|
82
|
+
minute: '2-digit',
|
|
83
|
+
hour12: false,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const formattedTime = now.toLocaleString('en-GB', options).replace(',', '');
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
const context = {
|
|
90
|
+
id:id,
|
|
91
|
+
user_id: user_id,
|
|
92
|
+
user_name: user_name,
|
|
93
|
+
ipaddress: ipaddress,
|
|
94
|
+
title: data.title,
|
|
95
|
+
descr: data.description,
|
|
96
|
+
directory: data.directory,
|
|
97
|
+
appname: data.appname,
|
|
98
|
+
moduleName: data.name,
|
|
99
|
+
entities: data.entities,
|
|
100
|
+
icon: data.icon,
|
|
101
|
+
timeGenerated: formattedTime,
|
|
102
|
+
postMessage: (info) => {
|
|
103
|
+
parentPort.postMessage(info)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const jedaWaktu = jeda ?? 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
// tambahkan referensi ke entity detil
|
|
113
|
+
const entityHeader = context.entities['header']
|
|
114
|
+
const headerPkFieldName = entityHeader.pk
|
|
115
|
+
const headerItems = entityHeader.Items
|
|
116
|
+
for (var entityName in context.entities) {
|
|
117
|
+
// tambahkan jika bukan header
|
|
118
|
+
if (entityName!='header') {
|
|
119
|
+
const headerPK = structuredClone(headerItems[headerPkFieldName])
|
|
120
|
+
headerPK.Reference.pk = entityHeader.pk
|
|
121
|
+
headerPK.Reference.table = entityHeader.table
|
|
122
|
+
headerPK.Reference.bindingValue = entityHeader.pk
|
|
123
|
+
headerPK.input_disabled = true
|
|
124
|
+
context.entities[entityName].Items[entityHeader.pk] = headerPK
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
|
|
131
|
+
await checkEntitiy(context)
|
|
132
|
+
// process.exit(0)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
await prepareDirectory(context, {overwrite:true})
|
|
136
|
+
await sleep(jedaWaktu)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
const iconFileName = await createIcon(context, {overwrite:true})
|
|
141
|
+
|
|
142
|
+
await createProgramData(context, {iconFileName})
|
|
143
|
+
|
|
144
|
+
await createTable(context, {overwrite:true})
|
|
145
|
+
await sleep(jedaWaktu)
|
|
146
|
+
|
|
147
|
+
await createApiModule(context, {overwrite:true})
|
|
148
|
+
await sleep(jedaWaktu)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
await createModuleRollup(context, {overwrite:true})
|
|
153
|
+
await sleep(jedaWaktu)
|
|
154
|
+
|
|
155
|
+
await createModuleContext(context, {overwrite:true})
|
|
156
|
+
await sleep(jedaWaktu)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
await createModuleEjs(context, {overwrite:true})
|
|
161
|
+
await sleep(jedaWaktu)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
await createModuleMjs(context, {overwrite:true, iconFileName})
|
|
165
|
+
await sleep(jedaWaktu)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
// Header
|
|
170
|
+
await createModuleHeaderListHtml(context, {overwrite:true})
|
|
171
|
+
await sleep(jedaWaktu)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
await createModuleHeaderListMjs(context, {overwrite:true})
|
|
175
|
+
await sleep(jedaWaktu)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
await createModuleHeaderEditHtml(context, {overwrite:true})
|
|
179
|
+
await sleep(jedaWaktu)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
await createModuleHeaderEditMjs(context, {overwrite:true})
|
|
183
|
+
await sleep(jedaWaktu)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
// Detils
|
|
188
|
+
await createModuleDetilListHtml(context, {overwrite:true})
|
|
189
|
+
await sleep(jedaWaktu)
|
|
190
|
+
|
|
191
|
+
await createModuleDetilListMjs(context, {overwrite:true})
|
|
192
|
+
await sleep(jedaWaktu)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
await createModuleDetilEditHtml(context, {overwrite:true})
|
|
196
|
+
await sleep(jedaWaktu)
|
|
197
|
+
|
|
198
|
+
await createModuleDetilEditMjs(context, {overwrite:true})
|
|
199
|
+
await sleep(jedaWaktu)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
await createInfoLogs(context, {overwrite:true})
|
|
203
|
+
await sleep(jedaWaktu)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
// Extender
|
|
207
|
+
await createApiExtenderModule(context, {overwrite:false})
|
|
208
|
+
await sleep(jedaWaktu)
|
|
209
|
+
|
|
210
|
+
await createModuleExtenderHtml(context, {overwrite:false})
|
|
211
|
+
await sleep(jedaWaktu)
|
|
212
|
+
|
|
213
|
+
await createModuleExtenderMjs(context, {overwrite:false})
|
|
214
|
+
await sleep(jedaWaktu)
|
|
215
|
+
|
|
216
|
+
await createInfoAboutExtender(context, {overwrite:false})
|
|
217
|
+
await sleep(jedaWaktu)
|
|
218
|
+
|
|
219
|
+
await createInfoRecordExtender(context, {overwrite:false})
|
|
220
|
+
await sleep(jedaWaktu)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
// Selesai
|
|
226
|
+
context.postMessage({message: `finish`, done:true})
|
|
227
|
+
} catch (err) {
|
|
228
|
+
throw err
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function prepareDirectory(context) {
|
|
233
|
+
const appname = context.appname
|
|
234
|
+
const moduleName = context.moduleName
|
|
235
|
+
const directory = context.directory
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
|
|
240
|
+
context.postMessage({message: `preparing directory`})
|
|
241
|
+
// await sleep(1)
|
|
242
|
+
|
|
243
|
+
// cek jika directory project exists
|
|
244
|
+
const projectDirExists = await directoryExists(directory)
|
|
245
|
+
if (!projectDirExists) {
|
|
246
|
+
throw new Error(`directory tujuan '${directory}' tidak ditemukan`)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const moduleDir = path.join(directory, 'public', 'modules', moduleName)
|
|
250
|
+
const apiDir = path.join(directory, 'src', 'apis')
|
|
251
|
+
const apiExtenderDir = path.join(apiDir, 'extenders')
|
|
252
|
+
|
|
253
|
+
const moduleDirExists = await directoryExists(moduleDir)
|
|
254
|
+
if (!moduleDirExists) {
|
|
255
|
+
// direktori modul tidak ditemukan, buat dulu
|
|
256
|
+
context.postMessage({message: `creating new directory: '${moduleDir}`})
|
|
257
|
+
await mkdir(moduleDir, {});
|
|
258
|
+
// await sleep(1)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const apiDirExists = await directoryExists(apiDir)
|
|
262
|
+
if (!apiDirExists) {
|
|
263
|
+
throw new Error(`directory tujuan '${apiDir}' tidak ditemukan`)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const apiExtenderDirExists = await directoryExists(apiExtenderDir)
|
|
267
|
+
if (!apiExtenderDirExists) {
|
|
268
|
+
throw new Error(`directory tujuan '${apiExtenderDir}' tidak ditemukan`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
// cek apakah sudah di lock
|
|
273
|
+
const lockFile = path.join(moduleDir, `${moduleName}.lock`)
|
|
274
|
+
var fileExists = await isFileExist(lockFile)
|
|
275
|
+
if (fileExists) {
|
|
276
|
+
console.log("\n\n\x1b[1m\x1b[31mERROR\x1b[0m")
|
|
277
|
+
console.log('Module sudah di lock, tidak bisa digenerate ulang')
|
|
278
|
+
process.exit(1)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
context.moduleDir = moduleDir
|
|
283
|
+
context.apiDir = apiDir
|
|
284
|
+
context.apiExtenderDir = apiExtenderDir
|
|
285
|
+
} catch (err) {
|
|
286
|
+
throw err
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
async function directoryExists(path) {
|
|
293
|
+
try {
|
|
294
|
+
await access(path, constants.F_OK);
|
|
295
|
+
return true;
|
|
296
|
+
} catch {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function checkEntitiy(context) {
|
|
302
|
+
const apps = []
|
|
303
|
+
try {
|
|
304
|
+
const sql = "select * from core.apps"
|
|
305
|
+
const rows = await db.any(sql)
|
|
306
|
+
for (let row of rows) {
|
|
307
|
+
apps[row.apps_id] = row
|
|
308
|
+
}
|
|
309
|
+
} catch (err) {
|
|
310
|
+
throw err
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
// cek appname
|
|
315
|
+
if (apps[context.appname]==null) {
|
|
316
|
+
throw new Error(`App Name: '${context.appname}' tidak valid. Cek di data apps`)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
// process.exit(1)
|
|
321
|
+
|
|
322
|
+
for (let entityName in context.entities) {
|
|
323
|
+
const entity = context.entities[entityName]
|
|
324
|
+
const entity_table = entity.table
|
|
325
|
+
const entity_pk = entity.pk
|
|
326
|
+
const Items = entity.Items
|
|
327
|
+
|
|
328
|
+
let pkExists = false
|
|
329
|
+
|
|
330
|
+
for (let fieldName in Items) {
|
|
331
|
+
const item = Items[fieldName]
|
|
332
|
+
const component = item.component
|
|
333
|
+
|
|
334
|
+
// Cek Primary Key
|
|
335
|
+
if (fieldName==entity_pk) {
|
|
336
|
+
pkExists = true
|
|
337
|
+
if (!item.showInForm) {
|
|
338
|
+
throw new Error(`Primary key '${fieldName}' pada entity '${entityName}' harus di embed di form. Apabila ingin menyembunyakannya, gunakan 'hidden' di Container CSS `)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Cek combobox
|
|
343
|
+
if (component=='Combobox') {
|
|
344
|
+
// cek apakah konfig udah bener
|
|
345
|
+
const reference = item.Reference
|
|
346
|
+
const table = reference.table.trim()
|
|
347
|
+
const pk = reference.pk.trim()
|
|
348
|
+
const bindingValue = reference.bindingValue.trim()
|
|
349
|
+
const bindingText = reference.bindingText.trim()
|
|
350
|
+
const loaderApiModule = reference.loaderApiModule.trim()
|
|
351
|
+
const loaderApiPath = reference.loaderApiPath.trim()
|
|
352
|
+
|
|
353
|
+
if(table=='') {
|
|
354
|
+
throw new Error(`table reference Combobox '${fieldName}' di entity '${entityName}' tidak boleh kosong`)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if(pk=='') {
|
|
358
|
+
throw new Error(`PK untuk table reference Combobox '${fieldName}' di entity '${entityName}' tidak boleh kosong`)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if(bindingValue=='') {
|
|
362
|
+
throw new Error(`binding value untuk table reference Combobox '${fieldName}' di entity '${entityName}' tidak boleh kosong`)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if(bindingText=='') {
|
|
366
|
+
throw new Error(`binding value untuk table reference Combobox '${fieldName}' di entity '${entityName}' tidak boleh kosong`)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if(loaderApiModule=='') {
|
|
370
|
+
throw new Error(`loader API Name untuk table reference Combobox '${fieldName}' di entity '${entityName}' tidak boleh kosong`)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (apps[loaderApiModule]==null) {
|
|
374
|
+
throw new Error(`loader API Name: '${loaderApiModule}' untuk table reference Combobox '${fieldName}' di entity '${entityName}' tidak valid. Cek di data apps`)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if(loaderApiPath=='') {
|
|
378
|
+
throw new Error(`loader API path untuk table reference Combobox '${fieldName}' di entity '${entityName}' tidak boleh kosong`)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (!pkExists) {
|
|
384
|
+
throw new Error(`Primary key pada entity '${entityName}' belum didefinisikan`)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
}
|
|
389
|
+
}
|
package/src/helper.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import ejs from 'ejs'
|
|
4
|
+
import context from './context.js'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export function kebabToCamel(str) {
|
|
9
|
+
return str
|
|
10
|
+
.split('-')
|
|
11
|
+
.map((part, index) =>
|
|
12
|
+
index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)
|
|
13
|
+
)
|
|
14
|
+
.join('');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export async function importApiModule(modulename, options={}) {
|
|
19
|
+
// jika mode debug,
|
|
20
|
+
// load api akan selalu dilakukan saat request (tanpa caching)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const cached = options.cached===true ? true : false
|
|
24
|
+
const apiDir = options.apiDir || path.join('.', 'apis')
|
|
25
|
+
const apiPath = path.join(apiDir, `${modulename}.api.js`)
|
|
26
|
+
|
|
27
|
+
if (cached) {
|
|
28
|
+
return (await import(apiPath)).default;
|
|
29
|
+
} else {
|
|
30
|
+
const fullPath = new URL(apiPath, import.meta.url).pathname;
|
|
31
|
+
const mtime = (await fs.stat(fullPath)).mtimeMs;
|
|
32
|
+
const freshUrl = `${fullPath}?v=${mtime}`;
|
|
33
|
+
const module = await import(freshUrl);
|
|
34
|
+
|
|
35
|
+
if (module.default===undefined) {
|
|
36
|
+
throw new Error(`modul api '${modulename}' tidak mempunyai default class untuk import`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return module.default;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
export async function isFileExists(filepath) {
|
|
47
|
+
try {
|
|
48
|
+
await fs.access(filepath);
|
|
49
|
+
return true
|
|
50
|
+
} catch (err) {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function parseTemplate(tplFilePath, variables={}) {
|
|
56
|
+
const template = await fs.readFile(tplFilePath, 'utf-8');
|
|
57
|
+
const content = ejs.render(template, variables)
|
|
58
|
+
return content
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
export function createDefaultEjsVariable(req) {
|
|
63
|
+
const appName = req.app.locals.appConfig.appName || ''
|
|
64
|
+
const moduleName = req.params.modulename
|
|
65
|
+
const libDebug = path.join(context.getWebappsDirectory(), 'templates', '_lib_debug.ejs')
|
|
66
|
+
const libProduction = path.join(context.getWebappsDirectory(), 'templates', '_lib_production.ejs')
|
|
67
|
+
const fgta5jsDebugMode = req.app.locals.appConfig.fgta5jsDebugMode
|
|
68
|
+
const fgta5jsVersion = req.app.locals.appConfig.fgta5jsVersion
|
|
69
|
+
const appDebugMode = req.app.locals.appConfig.appDebugMode
|
|
70
|
+
|
|
71
|
+
const variables = {
|
|
72
|
+
appName,
|
|
73
|
+
moduleName,
|
|
74
|
+
libDebug,
|
|
75
|
+
libProduction,
|
|
76
|
+
fgta5jsDebugMode,
|
|
77
|
+
fgta5jsVersion,
|
|
78
|
+
appDebugMode
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return variables
|
|
82
|
+
}
|
package/src/logger.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { dblog } from './db.js'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export default new (class {
|
|
5
|
+
async access(user, modulename, url, errormessage) {
|
|
6
|
+
console.log('logger access', modulename, url, errormessage)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async log(logdata) {
|
|
11
|
+
const { id, user_id, user_name, moduleName, action, tablename, executionTimeMs, remark, metadata, ipaddress=''} = logdata
|
|
12
|
+
const sql = `insert into datalog
|
|
13
|
+
(log_time, log_user_id, log_user_name, log_action, log_ipaddress, log_module, log_table, log_doc_id, log_remark, log_executiontime, log_metadata)
|
|
14
|
+
values
|
|
15
|
+
(now(), \${log_user_id}, \${log_user_name}, \${log_action}, \${log_ipaddress}, \${log_module}, \${log_table}, \${log_doc_id}, \${log_remark}, \${log_executiontime}, \${log_metadata})
|
|
16
|
+
RETURNING log_time
|
|
17
|
+
`
|
|
18
|
+
const variable = {
|
|
19
|
+
log_user_id: user_id,
|
|
20
|
+
log_user_name: user_name,
|
|
21
|
+
log_action: action,
|
|
22
|
+
log_ipaddress: ipaddress,
|
|
23
|
+
log_module: moduleName,
|
|
24
|
+
log_table: tablename,
|
|
25
|
+
log_doc_id: id,
|
|
26
|
+
log_remark: remark,
|
|
27
|
+
log_executiontime: executionTimeMs,
|
|
28
|
+
log_metadata: metadata
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ret = await dblog.oneOrNone(sql, variable)
|
|
32
|
+
return ret
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
})()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
package/src/router.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import ExpressServer from 'express'
|
|
2
|
+
import multer from 'multer';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import * as path from 'node:path'
|
|
5
|
+
import * as http from 'node:http'
|
|
6
|
+
import * as helper from './helper.js'
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import context from './context.js'
|
|
9
|
+
|
|
10
|
+
import { defaultRootIndex } from './routers/defaultRootIndex.js'
|
|
11
|
+
import { handleModuleNotfound } from './routers/handleModuleNotfound.js'
|
|
12
|
+
import { handleError } from './routers/handleError.js'
|
|
13
|
+
import { fileUploadApi } from './routers/fileUploadApi.js'
|
|
14
|
+
import { publicDownloadHandler, privateDownloadHandler } from './routers/downloadHandler.js'
|
|
15
|
+
import { defaultLoginPage } from './routers/defaultLoginPage.js'
|
|
16
|
+
import { defaultLoginAsset } from './routers/defaultLoginAsset.js'
|
|
17
|
+
import { defaultLoginApi } from './routers/defaultLoginApi.js'
|
|
18
|
+
import { generatorPage } from './routers/generatorPage.js'
|
|
19
|
+
import { generatorAsset } from './routers/generatorAsset.js'
|
|
20
|
+
import { generatorApi } from './routers/generatorApi.js'
|
|
21
|
+
import { modulePage } from './routers/modulePage.js'
|
|
22
|
+
import { moduleApi } from './routers/moduleApi.js'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
createBasicRouter: () => { return createBasicRouter() }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export function uploader(req, res, next) {
|
|
31
|
+
if (req.is('multipart/form-data')) {
|
|
32
|
+
const upload = multer();
|
|
33
|
+
upload.any()(req, res, next);
|
|
34
|
+
} else {
|
|
35
|
+
next();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export function createBasicRouter() {
|
|
42
|
+
const router = ExpressServer.Router({ mergeParams: true });
|
|
43
|
+
|
|
44
|
+
// index
|
|
45
|
+
router.get('/', defaultRootIndex)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
// upload
|
|
49
|
+
router.post('/upload', fileUploadApi)
|
|
50
|
+
|
|
51
|
+
// download
|
|
52
|
+
router.get('/download', publicDownloadHandler)
|
|
53
|
+
router.post('/download', privateDownloadHandler)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// login
|
|
57
|
+
router.get('/login', defaultLoginPage)
|
|
58
|
+
router.get('/login/:requestedAsset', defaultLoginAsset)
|
|
59
|
+
router.post('/login/:method', defaultLoginApi)
|
|
60
|
+
|
|
61
|
+
// generator
|
|
62
|
+
router.get('/generator', generatorPage)
|
|
63
|
+
router.get('/generator/:requestedAsset', generatorAsset)
|
|
64
|
+
router.post('/generator/:method', generatorApi)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
// debug
|
|
68
|
+
// router.get('/debug/:modulename', modulePage)
|
|
69
|
+
// router.post('/debug/:modulename/:method', moduleApi)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
// modul
|
|
73
|
+
router.get('/:modulename', modulePage)
|
|
74
|
+
router.post('/:modulename/:method', moduleApi)
|
|
75
|
+
|
|
76
|
+
return router
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as helper from './../helper.js'
|
|
2
|
+
import { handleError } from './handleError.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export async function defaultLoginApi(req, res, next) {
|
|
6
|
+
const moduleName = 'login'
|
|
7
|
+
const methodName = req.params.method
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
|
|
11
|
+
const ModuleClass = await helper.importApiModule(moduleName)
|
|
12
|
+
const method = helper.kebabToCamel(methodName);
|
|
13
|
+
if (ModuleClass===undefined) {
|
|
14
|
+
throw new Error(`invalid module: '${moduleName}'`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const requestedBody = req.body
|
|
18
|
+
const module = new ModuleClass(req, res, next)
|
|
19
|
+
const result = await module.handleRequest(method, requestedBody)
|
|
20
|
+
const response = {
|
|
21
|
+
code: 0,
|
|
22
|
+
result: result
|
|
23
|
+
}
|
|
24
|
+
res.json(response)
|
|
25
|
+
} catch (err) {
|
|
26
|
+
handleError(err, req, res)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import context from './../context.js'
|
|
2
|
+
import * as helper from './../helper.js'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
import { handleError } from './handleError.js'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export async function defaultLoginAsset(req, res, next) {
|
|
8
|
+
const __dirname = context.getWebappsDirectory()
|
|
9
|
+
const requestedFile = req.params.requestedAsset;
|
|
10
|
+
const filePath = path.join(__dirname, 'modules', 'login', requestedFile)
|
|
11
|
+
|
|
12
|
+
const assetExists = await helper.isFileExists(filePath)
|
|
13
|
+
if (assetExists) {
|
|
14
|
+
res.sendFile(filePath)
|
|
15
|
+
} else {
|
|
16
|
+
res.status(404).send(`'${requestedFile}' is not found`)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import context from './../context.js'
|
|
2
|
+
import * as helper from './../helper.js'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
import { handleError } from './handleError.js'
|
|
5
|
+
|
|
6
|
+
export async function defaultLoginPage(req, res, next) {
|
|
7
|
+
const __dirname = context.getWebappsDirectory()
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
|
|
11
|
+
const loginPagePath = path.join(__dirname, 'modules', 'login', 'login.html')
|
|
12
|
+
|
|
13
|
+
const variables = {
|
|
14
|
+
...helper.createDefaultEjsVariable(req),
|
|
15
|
+
...{
|
|
16
|
+
loginPagePath
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const tplFilePath = path.join(__dirname, 'templates', 'login.page.ejs')
|
|
21
|
+
const content = await helper.parseTemplate(tplFilePath, variables)
|
|
22
|
+
|
|
23
|
+
res.status(200).send(content)
|
|
24
|
+
// res.status(200).send(`defaultLoginApi`)
|
|
25
|
+
|
|
26
|
+
} catch (err) {
|
|
27
|
+
// next(err)
|
|
28
|
+
handleError(err, req, res)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import context from './../context.js'
|
|
2
|
+
import * as helper from './../helper.js'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export async function defaultRootIndex(req, res, next) {
|
|
7
|
+
const variables = {
|
|
8
|
+
...helper.createDefaultEjsVariable(req),
|
|
9
|
+
...{
|
|
10
|
+
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const tplFilePath = path.join(context.getWebappsDirectory(), 'templates', 'index.page.ejs')
|
|
14
|
+
const content = await helper.parseTemplate(tplFilePath, variables)
|
|
15
|
+
res.status(200).send(content)
|
|
16
|
+
}
|