@agung_dhewe/webapps 1.1.2 → 1.2.4

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 (94) hide show
  1. package/lib/fgta5js-dist/fgta5js-v1.8.5.min.js +11 -0
  2. package/lib/fgta5js-dist/fgta5js-v1.8.5.min.js.map +1 -0
  3. package/{libs → lib}/webmodule/module-edit.css +73 -18
  4. package/{libs → lib}/webmodule/module-list.css +13 -0
  5. package/lib/webmodule/module-print.css +28 -0
  6. package/{libs → lib}/webmodule/module.css +13 -6
  7. package/{libs → lib}/webmodule/module.js +14 -4
  8. package/lib/webmodule/pagehelper.mjs +129 -0
  9. package/modules/generator/appgen-io.mjs +153 -76
  10. package/modules/generator/appgen-ui.mjs +234 -167
  11. package/modules/generator/generator-designtemplate-def.html +38 -0
  12. package/modules/generator/generator-designtemplate.html +11 -1492
  13. package/modules/generator/generator.css +103 -65
  14. package/modules/generator/generator.mjs +1 -1
  15. package/modules/generator/generator.svg +98 -0
  16. package/modules/generator/generatorEdit.mjs +43 -35
  17. package/modules/generator/generatorList.mjs +27 -0
  18. package/modules/generator/tpl-designerinfo.html +100 -0
  19. package/modules/generator/tpl-field-checkbox.html +200 -0
  20. package/modules/generator/tpl-field-combobox.html +228 -0
  21. package/modules/generator/tpl-field-datepicker.html +192 -0
  22. package/modules/generator/tpl-field-filebox.html +189 -0
  23. package/modules/generator/tpl-field-numberbox.html +218 -0
  24. package/modules/generator/tpl-field-textbox.html +255 -0
  25. package/modules/generator/tpl-field-timepicker.html +192 -0
  26. package/modules/generator/tpl-searchdesign.html +32 -0
  27. package/modules/generator/tpl-uniquedesign.html +25 -0
  28. package/modules/login/login.css +10 -2
  29. package/package.json +5 -3
  30. package/percobaan/coba-sequencer.js +16 -0
  31. package/src/api.js +12 -9
  32. package/src/apis/generator.api.js +35 -23
  33. package/src/apis/login.api.js +1 -0
  34. package/src/context.js +12 -2
  35. package/src/db.js +58 -32
  36. package/src/generator/createApiModule.js +4 -1
  37. package/src/generator/createIcon.js +24 -2
  38. package/src/generator/createLayoutCss.js +107 -0
  39. package/src/generator/createModuleDetilEditHtml.js +12 -1
  40. package/src/generator/createModuleDetilEditMjs.js +32 -28
  41. package/src/generator/createModuleDetilListHtml.js +14 -7
  42. package/src/generator/createModuleDetilListMjs.js +13 -1
  43. package/src/generator/createModuleHeaderEditHtml.js +13 -1
  44. package/src/generator/createModuleHeaderEditMjs.js +23 -2
  45. package/src/generator/createProgramData.js +3 -2
  46. package/src/generator/createTable.js +42 -38
  47. package/src/generator/helper.js +45 -27
  48. package/src/generator/templates/__rollup-module copy.ejs +90 -0
  49. package/src/generator/templates/__rollup-module.ejs +102 -31
  50. package/src/generator/templates/api-module.js.ejs +171 -32
  51. package/src/generator/templates/layout.css.ejs +24 -0
  52. package/src/generator/templates/module-ext.html.ejs +1 -1
  53. package/src/generator/templates/module-ext.mjs.ejs +19 -1
  54. package/src/generator/templates/module.ejs.ejs +8 -0
  55. package/src/generator/templates/module.mjs.ejs +42 -5
  56. package/src/generator/templates/moduleDetilEdit.html.ejs +11 -0
  57. package/src/generator/templates/moduleDetilEdit.mjs.ejs +135 -30
  58. package/src/generator/templates/moduleDetilList.html.ejs +2 -1
  59. package/src/generator/templates/moduleDetilList.mjs.ejs +86 -11
  60. package/src/generator/templates/moduleHeaderEdit.html.ejs +8 -1
  61. package/src/generator/templates/moduleHeaderEdit.mjs.ejs +123 -36
  62. package/src/generator/templates/moduleHeaderList.html.ejs +5 -1
  63. package/src/generator/templates/moduleHeaderList.mjs.ejs +47 -15
  64. package/src/generator/trygenerate.js +18 -2
  65. package/src/generator/worker.js +83 -72
  66. package/src/helper.js +12 -13
  67. package/src/logger.js +12 -12
  68. package/src/notifier.js +29 -0
  69. package/src/routers/generatorApi.js +9 -3
  70. package/src/routers/generatorPage.js +3 -1
  71. package/src/routers/moduleApi.js +1 -1
  72. package/src/routers/modulePage.js +42 -7
  73. package/src/sequencerdoc.js +22 -46
  74. package/src/sequencerline.js +16 -4
  75. package/src/session.js +69 -33
  76. package/src/startup.js +47 -10
  77. package/src/webapps.js +62 -18
  78. package/templates/_lib_debug.ejs +8 -8
  79. package/templates/_lib_production.ejs +2 -2
  80. package/templates/application.page.ejs +43 -8
  81. package/templates/generator.page.ejs +4 -3
  82. package/templates/index.page.ejs +2 -2
  83. package/templates/login.page.ejs +3 -3
  84. package/libs/fgta5js-dist/fgta5js-v1.8.3.min.js +0 -11
  85. package/libs/fgta5js-dist/fgta5js-v1.8.3.min.js.map +0 -1
  86. package/libs/webmodule/pagehelper.mjs +0 -45
  87. package/modules/generator/generator.png +0 -0
  88. /package/{libs/fgta5js-dist/fgta5js-v1.8.3.min.css → lib/fgta5js-dist/fgta5js-v1.8.5.min.css} +0 -0
  89. /package/{libs → lib}/fgta5js-dist/fonts/karla-italic-latin-ext.woff2 +0 -0
  90. /package/{libs → lib}/fgta5js-dist/fonts/karla-italic-latin.woff2 +0 -0
  91. /package/{libs → lib}/fgta5js-dist/fonts/karla-normal-latin-ext.woff2 +0 -0
  92. /package/{libs → lib}/fgta5js-dist/fonts/karla-normal-latin.woff2 +0 -0
  93. /package/{libs → lib}/fgta5js-dist/fonts/karla.css +0 -0
  94. /package/{libs → lib}/webmodule/module-footer.css +0 -0
@@ -17,7 +17,6 @@ class Sequencer {
17
17
  #defaultOptions = {
18
18
  COMPANY_CODE: '00',
19
19
  blockLength: 3,
20
- clusterLength: 2,
21
20
  numberLength: 6
22
21
  }
23
22
 
@@ -44,24 +43,24 @@ class Sequencer {
44
43
  }
45
44
 
46
45
 
47
- async yearly(doc_id, block=0, cluster=0) {
46
+ async yearly(doc_id, block=0) {
48
47
  const self = this
49
48
  const db = self.db
50
49
  try {
51
50
  const { year } = await getDbCurrentDate(db)
52
51
  const month = 0
53
- return await generateId(self, year, month, doc_id, block, cluster)
52
+ return await generateId(self, year, month, doc_id, block)
54
53
  } catch (err) {
55
54
  throw err
56
55
  }
57
56
  }
58
57
 
59
- async monthly(doc_id, block=0, cluster=0) {
58
+ async monthly(doc_id, block=0) {
60
59
  const self = this
61
60
  const db = self.db
62
61
  try {
63
62
  const { year, month } = await getDbCurrentDate(db)
64
- return await generateId(self, year, month, doc_id, block, cluster)
63
+ return await generateId(self, year, month, doc_id, block)
65
64
  } catch (err) {
66
65
  throw err
67
66
  }
@@ -78,13 +77,13 @@ function isValidDigitMax(n, length) {
78
77
  }
79
78
 
80
79
 
81
- async function generateId(self, year, month, doc_id, block, cluster) {
80
+ async function generateId(self, year, month, doc_id, block) {
82
81
  const options = self.options
83
82
  const db = self.db
84
83
  const searchMap = {
85
84
  seqnum: `sequencer_seqnum=\${seqnum}`,
86
85
  block: `sequencer_block=\${block}`,
87
- cluster: 'sequencer_cluster=\${cluster}',
86
+ seqclust: 'sequencer_cluster=\${seqclust}',
88
87
  year: `sequencer_year=\${year}`,
89
88
  month: `sequencer_month=\${month}`
90
89
  };
@@ -96,30 +95,26 @@ async function generateId(self, year, month, doc_id, block, cluster) {
96
95
  throw new Error(`block value: '${block}' is not integer`)
97
96
  }
98
97
 
99
- if (!Number.isInteger(cluster)) {
100
- throw new Error(`cluster value: '${cluster}' is not integer`)
101
- }
102
-
103
98
  if (block<0 || !isValidDigitMax(block, options.blockLength)) {
104
99
  throw new Error(`block value: '${block}' is invalid. max length: ${options.blockLength} `)
105
100
  }
106
101
 
107
- if (cluster<0 || !isValidDigitMax(cluster, options.clusterLength)) {
108
- throw new Error(`cluster value: '${cluster}' is invalid. max length: ${options.clusterLength} `)
109
- }
110
-
111
-
112
-
113
102
 
114
103
  // ambil code doc
115
104
  const docparam = { doc_id }
116
- const sqldoc = `select doc_seqnum from core.doc where doc_id=\${doc_id}`
105
+ const sqldoc = `select doc_prefix, doc_seqclust, doc_seqnum from core.doc where doc_id=\${doc_id}`
117
106
  const row = await db.oneOrNone(sqldoc, docparam)
107
+ const prefix = row!=null ? row.doc_prefix : null
108
+ const seqclust = row!=null ? row.doc_seqclust : 0
118
109
  const seqnum = row!=null ? row.doc_seqnum : 0
119
110
 
120
111
 
112
+ if (seqclust<0 || seqclust>999) {
113
+ throw new Error(`doc_id: '${doc_id}' has doc seqclust '${seqclust}' that is invalid. doc seqnum have to in range 0-999 `)
114
+ }
115
+
121
116
  if (seqnum<0 || seqnum>99) {
122
- throw new Error(`doc_id: '${doc_id}' has doc seqnum '${seqnum}' that is invalid. doc seqnum have to in range 1-99 `)
117
+ throw new Error(`doc_id: '${doc_id}' has doc seqnum '${seqnum}' that is invalid. doc seqnum have to in range 0-99 `)
123
118
  }
124
119
 
125
120
 
@@ -141,10 +136,6 @@ async function generateId(self, year, month, doc_id, block, cluster) {
141
136
  nlength += self.options.blockLength
142
137
  }
143
138
 
144
- if (cluster>0) {
145
- nlength += self.options.clusterLength
146
- }
147
-
148
139
 
149
140
  // 250901000402 0000009
150
141
 
@@ -159,15 +150,11 @@ async function generateId(self, year, month, doc_id, block, cluster) {
159
150
 
160
151
  // ambil data sequencer
161
152
  {
162
- const criteria = { year, month, seqnum, block, cluster }
153
+ const criteria = { year, month, seqnum, block, seqclust }
163
154
  if (block==null) {
164
155
  criteria.block = 0
165
156
  }
166
157
 
167
- if (cluster==null) {
168
- criteria.cluster = 0
169
- }
170
-
171
158
 
172
159
  const {whereClause, queryParams} = sqlUtil.createWhereClause(criteria, searchMap)
173
160
  // console.log(year, month, whereClause, queryParams)
@@ -204,7 +191,7 @@ async function generateId(self, year, month, doc_id, block, cluster) {
204
191
  obj.sequencer_month = month
205
192
  obj.sequencer_seqnum = seqnum
206
193
  obj.sequencer_block = block
207
- obj.sequencer_cluster = cluster
194
+ obj.sequencer_cluster = seqclust
208
195
  obj.sequencer_number = 1
209
196
  obj.sequencer_lastdate = (new Date()).toISOString()
210
197
  obj.sequencer_remark = doc_id
@@ -221,15 +208,12 @@ async function generateId(self, year, month, doc_id, block, cluster) {
221
208
  const tokennum = []
222
209
 
223
210
 
224
- if (options.COMPANY_CODE!='00') {
225
- if (seqnum>0) {
226
- tokendoc.push(doc_id)
227
- tokendoc.push(options.COMPANY_CODE)
228
- }
229
- } else {
230
- if (seqnum>0) {
231
- tokendoc.push(doc_id)
232
- }
211
+ if (prefix!=null) {
212
+ tokendoc.push(prefix)
213
+ }
214
+
215
+ if (options.COMPANY_CODE!='00' && options.COMPANY_CODE!=null) {
216
+ tokendoc.push(options.COMPANY_CODE)
233
217
  }
234
218
 
235
219
 
@@ -263,14 +247,6 @@ async function generateId(self, year, month, doc_id, block, cluster) {
263
247
  tokennum.push(codeBlock)
264
248
  }
265
249
 
266
- if (cluster>0) {
267
- const codeCluster = String(cluster).padStart(self.options.clusterLength, '0')
268
- tokendoc.push('.')
269
- tokendoc.push(codeCluster)
270
-
271
- tokennum.push(codeCluster)
272
- }
273
-
274
250
 
275
251
  tokendoc.push('.')
276
252
  tokendoc.push(String(obj.sequencer_number).padStart(self.options.numberLength, '0'))
@@ -63,6 +63,7 @@ async function generateId(self, year, month, doc_id, short=false) {
63
63
  const options = self.options
64
64
  const db = self.db
65
65
  const searchMap = {
66
+ seqclust: 'sequencer_cluster=\${seqclust}',
66
67
  seqnum: `sequencer_seqnum=\${seqnum}`,
67
68
  year: `sequencer_year=\${year}`,
68
69
  month: `sequencer_month=\${month}`
@@ -74,10 +75,15 @@ async function generateId(self, year, month, doc_id, short=false) {
74
75
 
75
76
  // ambil code doc
76
77
  const docparam = { doc_id }
77
- const sqldoc = `select doc_seqnum from core.doc where doc_id=\${doc_id}`
78
+ const sqldoc = `select doc_prefix, doc_seqclust, doc_seqnum from core.doc where doc_id=\${doc_id}`
78
79
  const row = await db.oneOrNone(sqldoc, docparam)
80
+ const prefix = row!=null ? row.doc_prefix : ''
81
+ const seqclust = row!=null ? row.doc_seqclust : 0
79
82
  const seqnum = row!=null ? row.doc_seqnum : 0
80
83
 
84
+ if (seqclust<0 || seqclust>999) {
85
+ throw new Error(`doc_id: '${doc_id}' has doc seqclust '${seqclust}' that is invalid. doc seqnum have to in range 0-999 `)
86
+ }
81
87
 
82
88
  if (seqnum<0 || seqnum>99) {
83
89
  throw new Error(`doc_id: '${doc_id}' has doc seqnum '${seqnum}' that is invalid. doc seqnum have to in range 1-99 `)
@@ -110,6 +116,7 @@ async function generateId(self, year, month, doc_id, short=false) {
110
116
  throw new Error(`Total length of sequencer (${ln}) is mre than max length allowed(9)`)
111
117
  }
112
118
  } else {
119
+
113
120
  if (ln > MAX_LENGTH) {
114
121
  throw new Error(`Total length of sequencer (${ln}) is mre than max length allowed(${MAX_LENGTH})`)
115
122
  }
@@ -118,13 +125,14 @@ async function generateId(self, year, month, doc_id, short=false) {
118
125
 
119
126
  // ambil data sequencer
120
127
  {
121
- const criteria = { year, month, seqnum }
128
+ const criteria = { year, month, seqnum, seqclust }
122
129
  const {whereClause, queryParams} = sqlUtil.createWhereClause(criteria, searchMap)
123
130
 
124
131
  const columns = [
125
132
  'sequencer_id',
126
133
  'sequencer_year',
127
134
  'sequencer_month',
135
+ 'sequencer_cluster',
128
136
  'sequencer_seqnum',
129
137
  'sequencer_number',
130
138
  'EXTRACT(YEAR FROM sequencer_lastdate) AS lastyear',
@@ -150,6 +158,7 @@ async function generateId(self, year, month, doc_id, short=false) {
150
158
  obj.sequencer_year = year,
151
159
  obj.sequencer_month = month
152
160
  obj.sequencer_seqnum = seqnum
161
+ obj.sequencer_cluster = seqclust
153
162
  obj.sequencer_number = 1
154
163
  obj.sequencer_lastdate = (new Date()).toISOString()
155
164
  obj.sequencer_remark = doc_id
@@ -186,8 +195,11 @@ async function generateId(self, year, month, doc_id, short=false) {
186
195
  const numlen = maxlen - idpref.length
187
196
  tokennum.push(String(obj.sequencer_number).padStart(numlen, '0'))
188
197
 
189
- const s = tokennum.join('')
190
- return s
198
+ const ret = {
199
+ id: tokennum.join(''),
200
+ doc: `${prefix}${tokennum.join('')}`
201
+ }
202
+ return ret
191
203
  }
192
204
  } catch (err) {
193
205
  throw err
package/src/session.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import session from 'express-session'; // session
2
2
  import { createClient } from 'redis'; // session
3
3
  import * as connectRedis from 'connect-redis'; // session
4
-
4
+ import createFileStore from 'session-file-store'
5
+ import context from './context.js'
6
+ import * as path from 'node:path';
5
7
 
6
8
  export async function createSession(options) {
7
9
 
8
- const redisUrl = options.redisUrl || 'redis://localhost:6379'
10
+ const redisUrl = options.redisUrl //|| 'redis://localhost:6379'
9
11
  const sessionName = options.sessionName || 'sid'
10
12
  const sessionSecret = options.sessionSecret || 'rahasia'
11
13
  const sessionMaxAge = options.sessionMaxAge || 15 * 50 * 1000 // default 15 menit
@@ -13,35 +15,72 @@ export async function createSession(options) {
13
15
  const sessionSecure = options.sessionSecure ?? false
14
16
  const sessionHttpOnly = options.sessionHttpOnly ?? true
15
17
 
16
- const RedisStore = connectRedis.RedisStore;
17
-
18
-
19
- console.log(`connecting to redis ${redisUrl}`)
20
- const redisClient = createClient({
21
- url: redisUrl
22
- });
23
- await redisClient.connect();
24
- console.log('connected to redis server.')
25
-
26
- const redisStore = new RedisStore({
27
- client: redisClient,
28
- prefix: 'sess:',
29
- });
30
-
31
-
32
- const sessionConfig = {
33
- name: sessionName,
34
- store: redisStore,
35
- secret: sessionSecret,
36
- resave: false,
37
- saveUninitialized: false,
38
- rolling: true,
39
- cookie: {
40
- secure: sessionSecure,
41
- httpOnly: sessionHttpOnly,
42
- maxAge: sessionMaxAge,
43
- domain: sessionDomain
18
+
19
+ if (redisUrl!==undefined) {
20
+ const RedisStore = connectRedis.RedisStore;
21
+
22
+ console.log(`connecting to redis ${redisUrl}`)
23
+ const redisClient = createClient({
24
+ url: redisUrl
25
+ });
26
+ await redisClient.connect();
27
+ console.log('connected to redis server.')
28
+
29
+ const redisStore = new RedisStore({
30
+ client: redisClient,
31
+ prefix: 'sess:',
32
+ });
33
+
34
+
35
+ const sessionConfig = {
36
+ name: sessionName,
37
+ store: redisStore,
38
+ secret: sessionSecret,
39
+ resave: false,
40
+ saveUninitialized: false,
41
+ rolling: true,
42
+ cookie: {
43
+ secure: sessionSecure,
44
+ httpOnly: sessionHttpOnly,
45
+ maxAge: sessionMaxAge,
46
+ domain: sessionDomain
47
+ }
44
48
  }
49
+
50
+
51
+ console.log('starting redis session manager.')
52
+ return session(sessionConfig)
53
+
54
+ } else {
55
+
56
+ const __rootDirectory = context.getRootDirectory()
57
+ const SessionFileStore = createFileStore(session)
58
+
59
+ const fileStoreOptions = {
60
+ path: path.join(__rootDirectory, 'sessions'),
61
+ reapInterval: 3600, // dalam detik, bersihkan setiap 1 jam
62
+ logFn: () => {} // Bungkam semua log internal
63
+ }
64
+
65
+ const fileStore = new SessionFileStore(fileStoreOptions)
66
+
67
+ const sessionConfig = {
68
+ name: sessionName,
69
+ store: fileStore,
70
+ secret: sessionSecret,
71
+ resave: false,
72
+ saveUninitialized: false,
73
+ rolling: true,
74
+ cookie: {
75
+ secure: sessionSecure,
76
+ httpOnly: sessionHttpOnly,
77
+ maxAge: sessionMaxAge,
78
+ domain: sessionDomain
79
+ }
80
+ }
81
+
82
+ console.log('starting filebased session manager.')
83
+ return session(sessionConfig)
45
84
  }
46
85
 
47
86
  // TODO: tambahkan untuk keperluan ini
@@ -51,7 +90,4 @@ export async function createSession(options) {
51
90
  // httpOnly: true, // ✅ Tidak bisa diakses dari JavaScript
52
91
  // sameSite: 'none', // ✅ Bisa lintas domain (harus paired dengan secure)
53
92
  // maxAge: 15 * MINUTE,
54
-
55
- console.log('starting session manager.')
56
- return session(sessionConfig)
57
93
  }
package/src/startup.js CHANGED
@@ -1,13 +1,14 @@
1
1
 
2
2
  export async function authorizeRequest(db, req) {
3
3
  const moduleName = req.params.modulename;
4
- const program_id = req.query.prog;
4
+ const program_id = req.query.prog;
5
5
 
6
6
  try {
7
7
 
8
8
  // jika belum login
9
- if (req.session.user==null) {
10
- const err = new Error(`Belum login. Anda harus <a href="login">login</a> dulu untuk mengakses resource ini`)
9
+ if (req.session.user == null) {
10
+ const nextUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
11
+ const err = new Error(`Belum login. Anda harus <a href="login?nexturl=${nextUrl}">login</a> dulu untuk mengakses resource ini`)
11
12
  err.status = 401
12
13
  err.code = 401
13
14
  throw err
@@ -20,15 +21,15 @@ export async function authorizeRequest(db, req) {
20
21
 
21
22
  // jika punya akses developer boleh buka semuanya
22
23
  const sqlUser = 'select * from core.user where user_id=${user_id} and user_isdev=true'
23
- const rowUser = await db.oneOrNone(sqlUser, {user_id})
24
- if (rowUser!=null) {
24
+ const rowUser = await db.oneOrNone(sqlUser, { user_id })
25
+ if (rowUser != null) {
25
26
  return true // user adalah developer
26
27
  }
27
28
 
28
29
  // jika tidak punya akses developer, cek apakah boleh buka program
29
30
  const sql = 'select * from core.get_user_programs(${user_id}) where id=${program_id}'
30
- const row = await db.oneOrNone(sql, {user_id, program_id});
31
- if (row!=null) {
31
+ const row = await db.oneOrNone(sql, { user_id, program_id });
32
+ if (row != null) {
32
33
  return true // user punya akses program
33
34
  }
34
35
 
@@ -37,23 +38,59 @@ export async function authorizeRequest(db, req) {
37
38
  err.code = 401
38
39
  throw err
39
40
 
40
- } catch(err) {
41
+ } catch (err) {
41
42
  throw err
42
43
  }
43
44
  }
44
45
 
45
- export async function getApplicationSetting(db) {
46
+ export async function getApplicationSetting(db, tablename = 'core.setting') {
46
47
  const setting = {}
47
48
  try {
48
- const sql = 'select setting_id, setting_value from core.setting'
49
+ const sql = `select setting_id, setting_value from ${tablename}`
49
50
  const rows = await db.any(sql);
50
51
  for (var row of rows) {
51
52
  const setting_id = row.setting_id
52
53
  const setting_value = row.setting_value
53
54
  setting[setting_id] = setting_value
54
55
  }
56
+
57
+ setting.SETTING_TABLE_NAME = tablename
55
58
  return setting
56
59
  } catch (err) {
57
60
  throw err
58
61
  }
59
62
  }
63
+
64
+
65
+ export async function requireSetting(db, setting, setting_id, descr) {
66
+ if (setting[setting_id] !== undefined) {
67
+ // setting sudah ada
68
+ return true
69
+ } else {
70
+ const tablename = setting.SETTING_TABLE_NAME
71
+ const unset = '*** unset ***'
72
+
73
+ // cek di database
74
+ const sqlCek = `select setting_value from ${tablename} where setting_id=\${setting_id}`
75
+ const rowCek = await db.oneOrNone(sqlCek, { setting_id })
76
+ if (rowCek == null) {
77
+ throw new Error(`setting '${setting_id}' tidak ditemukan di data setting ${tablename}`)
78
+ }
79
+
80
+ const value = rowCek.setting_value
81
+ if (value == unset) {
82
+ throw new Error(`setting '${setting_id}' belum di set`)
83
+ }
84
+
85
+ // buat dulu di database
86
+ const sql = `
87
+ insert into ${tablename}
88
+ (setting_id, setting_value, setting_descr, _createby)
89
+ values
90
+ (\${setting_id}, \${defaultValue}, \${descr}, '240100000')`
91
+ await db.none(sql, { setting_id, defaultValue: unset, descr })
92
+
93
+ throw new Error(`setting ${setting_id} belum diisi di ${tablename}\n`)
94
+ }
95
+
96
+ }
package/src/webapps.js CHANGED
@@ -8,6 +8,10 @@ import cors from 'cors';
8
8
  import favicon from 'serve-favicon';
9
9
  import * as path from 'node:path';
10
10
  import * as helper from './helper.js'
11
+ import http from 'http'
12
+ import https from 'https'
13
+ import fs from 'fs'
14
+ import { execSync } from 'node:child_process';
11
15
 
12
16
 
13
17
  const __filename = fileURLToPath(import.meta.url);
@@ -51,11 +55,11 @@ export default class WebApplication {
51
55
  get defaultSessionSecure() { return defaultSessionSecure }
52
56
  get defaultSessionHttpOnly() { return defaultSessionHttpOnly }
53
57
 
54
- get express() { return this.#express}
58
+ get express() { return this.#express }
55
59
 
56
60
  #__rootDirectory
57
61
  get __rootDirectory() { return this.#__rootDirectory }
58
- setRootDirectory(v) {
62
+ setRootDirectory(v) {
59
63
  this.#__rootDirectory = v
60
64
  }
61
65
 
@@ -69,8 +73,9 @@ export default class WebApplication {
69
73
  throw new Error('__rootDirectory belum didefinisikan')
70
74
  }
71
75
 
72
- context.setRootDirectory(this.__rootDirectory)
76
+ context.setRootDirectory(this.__rootDirectory)
73
77
  context.setFnParseModuleRequest(options.fnParseModuleRequest)
78
+ context.alwaysLoadApi(!options.disableApiCache)
74
79
 
75
80
  if (this.#startedOnce) {
76
81
  throw new Error('start already called!')
@@ -81,7 +86,7 @@ export default class WebApplication {
81
86
  main(this, options)
82
87
  }
83
88
 
84
-
89
+
85
90
  }
86
91
 
87
92
 
@@ -120,7 +125,7 @@ async function main(self, options) {
120
125
  const port = options.port ?? self.defaultPort
121
126
  const startingMessage = options.startingMessage ?? `Starting webserver on port \x1b[1;33m${port}\x1b[0m`
122
127
  const appConfig = options.appConfig || createDefaultAppConfig()
123
-
128
+
124
129
  const basicRouter = createBasicRouter()
125
130
  const extendedRouter = options.router || ExpressServer.Router({ mergeParams: true });
126
131
  const router = ExpressServer.Router({ mergeParams: true });
@@ -160,9 +165,10 @@ async function main(self, options) {
160
165
  ]);
161
166
 
162
167
  // setup cors
163
- if (options.allowedOrigins!=null) {
168
+ if (options.allowedOrigins != null) {
164
169
  const allowedOrigins = options.allowedOrigins
165
170
  app.use(cors({
171
+ credentials: true,
166
172
  origin: function (origin, callback) {
167
173
  if (!origin) return callback(null, true); // untuk server-side atau curl
168
174
  const isAllowed = allowedOrigins.some(o => {
@@ -196,28 +202,33 @@ async function main(self, options) {
196
202
 
197
203
  // framework ini menggunakan library fgta5 untuk ui di client
198
204
  if (appConfig.fgta5jsDebugMode) {
199
- app.use('/public/libs/fgta5js', ExpressServer.static(path.join(__dirname, '..', 'libs', 'fgta5js')));
205
+ app.use('/public/lib/fgta5js', ExpressServer.static(path.join(__dirname, '..', 'lib', 'fgta5js')));
200
206
  } else {
201
- app.use('/public/libs/fgta5js', ExpressServer.static(path.join(__dirname, '..', 'libs', 'fgta5js-dist')));
207
+ app.use('/public/lib/fgta5js', ExpressServer.static(path.join(__dirname, '..', 'lib', 'fgta5js-dist')));
202
208
  }
203
-
204
- app.use('/public/libs/webmodule', ExpressServer.static(path.join(__dirname, '..', 'libs', 'webmodule')));
205
-
209
+
210
+ app.use('/public/lib/webmodule', ExpressServer.static(path.join(__dirname, '..', 'lib', 'webmodule')));
211
+
206
212
  // Routing /public untuk serve halaman-halaman static
207
213
  app.use('/public', rejectEjsFiles);
208
214
  app.use('/public', ExpressServer.static(path.join(__rootDirectory, 'public')));
209
215
  app.use('/', router)
210
216
  app.use(handleModuleNotfound)
211
-
212
217
 
213
- const server = app.listen(port, ()=>{
214
- console.log('\n\n' + startingMessage);
215
- });
218
+
219
+ // const server = app.listen(port, ()=>{
220
+ // console.log('\n\n' + startingMessage);
221
+ // });
222
+
223
+ // Buat server
224
+ const server = createApplicationServer(app, port, startingMessage, appConfig)
225
+
216
226
 
217
227
  // Tangani event 'error' pada objek server
218
228
  server.on('error', (err) => {
219
229
  if (err.code === 'EADDRINUSE') {
220
- console.error(`\n\x1b[31mError!\x1b[0m\nPort ${port} sudah digunakan. Silakan coba port lain.\n`);
230
+ const pid = getPidByPort(port)
231
+ console.error(`\n\x1b[31mError!\x1b[0m\nPort ${port} sudah digunakan pid ${pid}. Silakan coba port lain.\nCurrent pid: ${process.pid}\n`);
221
232
  } else {
222
233
  console.error('\n\x1b[31mError!\x1b[0m\nTerjadi kesalahan saat memulai server:', err, "\n");
223
234
  }
@@ -227,8 +238,29 @@ async function main(self, options) {
227
238
  }
228
239
 
229
240
 
241
+ function createApplicationServer(app, port, startingMessage, appConfig) {
242
+ const useSSL = appConfig.useSSL
243
+ const sslKey = appConfig.sslKey
244
+ const sslCertificate = appConfig.sslCertificate
245
+
246
+ if (useSSL) {
247
+ const sslOptions = {
248
+ key: fs.readFileSync(sslKey), // path ke private key
249
+ cert: fs.readFileSync(sslCertificate) // path ke sertifikat
250
+ };
251
+
252
+ return https.createServer(sslOptions, app).listen(port, () => {
253
+ console.log('\n\n' + startingMessage + ' (https)');
254
+ });
255
+ } else {
256
+ return http.createServer({}, app).listen(port, () => {
257
+ console.log('\n\n' + startingMessage);
258
+ });
259
+ }
260
+ }
261
+
230
262
  function rejectEjsFiles(req, res, next) {
231
- const excludedExtensions = ['.ejs'];
263
+ const excludedExtensions = ['.ejs'];
232
264
  const ext = path.extname(req.url);
233
265
 
234
266
  if (excludedExtensions.includes(ext)) {
@@ -236,4 +268,16 @@ function rejectEjsFiles(req, res, next) {
236
268
  }
237
269
 
238
270
  next();
239
- }
271
+ }
272
+
273
+
274
+ function getPidByPort(port) {
275
+ try {
276
+ // Menggunakan lsof untuk mendapatkan PID saja (-t)
277
+ const pid = execSync(`lsof -t -i:${port}`).toString().trim();
278
+ return pid ? parseInt(pid) : null;
279
+ } catch (error) {
280
+ // Jika tidak ada proses di port tersebut, lsof mengembalikan error code non-zero
281
+ return null;
282
+ }
283
+ };
@@ -1,11 +1,11 @@
1
1
 
2
2
  <!-- Component -->
3
- <script type="module" src="public/libs/fgta5js/src/main.mjs"></script>
4
- <link rel="stylesheet" href="public/libs/fgta5js/styles/fgta5js-root.css" />
5
- <link rel="stylesheet" href="public/libs/fgta5js/styles/fgta5js-messagebox.css" />
6
- <link rel="stylesheet" href="public/libs/fgta5js/styles/fgta5js-modal.css" />
7
- <link rel="stylesheet" href="public/libs/fgta5js/styles/fgta5js-entry.css" />
8
- <link rel="stylesheet" href="public/libs/fgta5js/styles/fgta5js-checkbox.css" />
9
- <link rel="stylesheet" href="public/libs/fgta5js/styles/fgta5js-gridview.css" />
10
- <link rel="stylesheet" href="public/libs/fgta5js/styles/fgta5js-appmanager.css" />
3
+ <script type="module" src="public/lib/fgta5js/src/main.mjs"></script>
4
+ <link rel="stylesheet" href="public/lib/fgta5js/styles/fgta5js-root.css" />
5
+ <link rel="stylesheet" href="public/lib/fgta5js/styles/fgta5js-messagebox.css" />
6
+ <link rel="stylesheet" href="public/lib/fgta5js/styles/fgta5js-modal.css" />
7
+ <link rel="stylesheet" href="public/lib/fgta5js/styles/fgta5js-entry.css" />
8
+ <link rel="stylesheet" href="public/lib/fgta5js/styles/fgta5js-checkbox.css" />
9
+ <link rel="stylesheet" href="public/lib/fgta5js/styles/fgta5js-gridview.css" />
10
+ <link rel="stylesheet" href="public/lib/fgta5js/styles/fgta5js-appmanager.css" />
11
11
 
@@ -1,5 +1,5 @@
1
1
 
2
2
  <!-- Component -->
3
- <script src="public/libs/fgta5js/fgta5js-<%=fgta5jsVersion%>.min.js"></script>
4
- <link rel="stylesheet" href="public/libs/fgta5js/fgta5js-<%=fgta5jsVersion%>.min.css" />
3
+ <script src="public/lib/fgta5js/fgta5js-<%=fgta5jsVersion%>.min.js"></script>
4
+ <link rel="stylesheet" href="public/lib/fgta5js/fgta5js-<%=fgta5jsVersion%>.min.css" />
5
5