@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.
Files changed (130) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +2 -0
  3. package/jsconfig.json +10 -0
  4. package/libs/fgta5js-dist/fgta5js-v1.8.3.min.css +2 -0
  5. package/libs/fgta5js-dist/fgta5js-v1.8.3.min.js +11 -0
  6. package/libs/fgta5js-dist/fgta5js-v1.8.3.min.js.map +1 -0
  7. package/libs/fgta5js-dist/fonts/karla-italic-latin-ext.woff2 +0 -0
  8. package/libs/fgta5js-dist/fonts/karla-italic-latin.woff2 +0 -0
  9. package/libs/fgta5js-dist/fonts/karla-normal-latin-ext.woff2 +0 -0
  10. package/libs/fgta5js-dist/fonts/karla-normal-latin.woff2 +0 -0
  11. package/libs/fgta5js-dist/fonts/karla.css +142 -0
  12. package/libs/webmodule/module-edit.css +163 -0
  13. package/libs/webmodule/module-footer.css +22 -0
  14. package/libs/webmodule/module-list.css +25 -0
  15. package/libs/webmodule/module.css +52 -0
  16. package/libs/webmodule/module.js +195 -0
  17. package/libs/webmodule/pagehelper.mjs +45 -0
  18. package/modules/generator/appgen-components.mjs +142 -0
  19. package/modules/generator/appgen-icons.mjs +6 -0
  20. package/modules/generator/appgen-io.mjs +784 -0
  21. package/modules/generator/appgen-ui-search.mjs +173 -0
  22. package/modules/generator/appgen-ui-unique.mjs +153 -0
  23. package/modules/generator/appgen-ui.mjs +1181 -0
  24. package/modules/generator/generator-context.mjs +18 -0
  25. package/modules/generator/generator-designtemplate.html +1508 -0
  26. package/modules/generator/generator-ext.html +0 -0
  27. package/modules/generator/generator-ext.mjs +3 -0
  28. package/modules/generator/generator.css +642 -0
  29. package/modules/generator/generator.mjs +195 -0
  30. package/modules/generator/generator.png +0 -0
  31. package/modules/generator/generatorEdit.html +185 -0
  32. package/modules/generator/generatorEdit.mjs +238 -0
  33. package/modules/generator/generatorList.html +32 -0
  34. package/modules/generator/generatorList.mjs +243 -0
  35. package/modules/login/login.css +11 -0
  36. package/modules/login/login.html +12 -0
  37. package/modules/login/login.mjs +111 -0
  38. package/package.json +46 -0
  39. package/percobaan/simmpan-ke-minio.js +24 -0
  40. package/src/api.js +80 -0
  41. package/src/apis/generator.api.js +226 -0
  42. package/src/apis/login.api.js +109 -0
  43. package/src/bucket.js +24 -0
  44. package/src/context.js +26 -0
  45. package/src/datalog.sql +22 -0
  46. package/src/datarecords.js +0 -0
  47. package/src/db.js +61 -0
  48. package/src/generator/createApiExtenderModule.js +54 -0
  49. package/src/generator/createApiModule.js +218 -0
  50. package/src/generator/createIcon.js +62 -0
  51. package/src/generator/createInfoAboutExtender.js +42 -0
  52. package/src/generator/createInfoLogs.js +41 -0
  53. package/src/generator/createInfoRecordExtender.js +41 -0
  54. package/src/generator/createModuleContext.js +48 -0
  55. package/src/generator/createModuleDetilEditHtml.js +110 -0
  56. package/src/generator/createModuleDetilEditMjs.js +172 -0
  57. package/src/generator/createModuleDetilListHtml.js +146 -0
  58. package/src/generator/createModuleDetilListMjs.js +73 -0
  59. package/src/generator/createModuleEjs.js +51 -0
  60. package/src/generator/createModuleExtenderHtml.js +43 -0
  61. package/src/generator/createModuleExtenderMjs.js +43 -0
  62. package/src/generator/createModuleHeaderEditHtml.js +148 -0
  63. package/src/generator/createModuleHeaderEditMjs.js +197 -0
  64. package/src/generator/createModuleHeaderListHtml.js +144 -0
  65. package/src/generator/createModuleHeaderListMjs.js +67 -0
  66. package/src/generator/createModuleMjs.js +67 -0
  67. package/src/generator/createModuleRollup.js +42 -0
  68. package/src/generator/createProgramData.js +96 -0
  69. package/src/generator/createTable.js +156 -0
  70. package/src/generator/ddl.js +475 -0
  71. package/src/generator/helper.js +149 -0
  72. package/src/generator/templates/__rollup-module.ejs +90 -0
  73. package/src/generator/templates/api-extender-module.js.ejs +0 -0
  74. package/src/generator/templates/api-module.js.ejs +818 -0
  75. package/src/generator/templates/module-context.ejs +16 -0
  76. package/src/generator/templates/module-ext-about.ejs +1 -0
  77. package/src/generator/templates/module-ext-record.ejs +1 -0
  78. package/src/generator/templates/module-ext.html.ejs +3 -0
  79. package/src/generator/templates/module-ext.mjs.ejs +21 -0
  80. package/src/generator/templates/module-logs.ejs +14 -0
  81. package/src/generator/templates/module.ejs.ejs +48 -0
  82. package/src/generator/templates/module.mjs.ejs +256 -0
  83. package/src/generator/templates/moduleDetilEdit.html.ejs +34 -0
  84. package/src/generator/templates/moduleDetilEdit.mjs.ejs +792 -0
  85. package/src/generator/templates/moduleDetilList.html.ejs +26 -0
  86. package/src/generator/templates/moduleDetilList.mjs.ejs +319 -0
  87. package/src/generator/templates/moduleHeaderEdit.html.ejs +53 -0
  88. package/src/generator/templates/moduleHeaderEdit.mjs.ejs +807 -0
  89. package/src/generator/templates/moduleHeaderList.html.ejs +24 -0
  90. package/src/generator/templates/moduleHeaderList.mjs.ejs +308 -0
  91. package/src/generator/templates/sqlAddField.ejs +3 -0
  92. package/src/generator/templates/sqlAddForeignKey.ejs +12 -0
  93. package/src/generator/templates/sqlAddUniqueIndex.ejs +4 -0
  94. package/src/generator/templates/sqlCreateTable.ejs +9 -0
  95. package/src/generator/templates/sqlDropForeignKey.ejs +3 -0
  96. package/src/generator/templates/sqlDropUniqueIndex.ejs +4 -0
  97. package/src/generator/templates/sqlModifyField.ejs +6 -0
  98. package/src/generator/trygenerate.js +83 -0
  99. package/src/generator/worker.js +389 -0
  100. package/src/helper.js +82 -0
  101. package/src/logger.js +39 -0
  102. package/src/router.js +84 -0
  103. package/src/routers/defaultLoginApi.js +29 -0
  104. package/src/routers/defaultLoginAsset.js +18 -0
  105. package/src/routers/defaultLoginPage.js +36 -0
  106. package/src/routers/defaultRootIndex.js +16 -0
  107. package/src/routers/downloadHandler.js +51 -0
  108. package/src/routers/fileUploadApi.js +15 -0
  109. package/src/routers/generatorApi.js +30 -0
  110. package/src/routers/generatorAsset.js +18 -0
  111. package/src/routers/generatorPage.js +37 -0
  112. package/src/routers/handleError.js +43 -0
  113. package/src/routers/handleModuleNotfound.js +12 -0
  114. package/src/routers/moduleApi.js +34 -0
  115. package/src/routers/modulePage.js +102 -0
  116. package/src/sequencerdoc.js +311 -0
  117. package/src/sequencerline.js +214 -0
  118. package/src/session.js +57 -0
  119. package/src/startup.js +59 -0
  120. package/src/webapps.js +239 -0
  121. package/src/workermanager.js +83 -0
  122. package/templates/_lib_debug.ejs +11 -0
  123. package/templates/_lib_production.ejs +5 -0
  124. package/templates/application.page.ejs +143 -0
  125. package/templates/generator.page.ejs +131 -0
  126. package/templates/index.page.ejs +24 -0
  127. package/templates/login.page.ejs +102 -0
  128. package/templates/moduleError.ejs +16 -0
  129. package/templates/moduleNotfound.ejs +14 -0
  130. package/webapps.code-workspace +11 -0
@@ -0,0 +1,214 @@
1
+ import sqlUtil from '@agung_dhewe/pgsqlc'
2
+
3
+ const MAX_LENGTH = 12
4
+ const tablename = "core.sequencer_line"
5
+
6
+
7
+ export function createSequencerLine(db, options) {
8
+ return new Sequencer(db, options)
9
+ }
10
+
11
+
12
+ class Sequencer {
13
+
14
+
15
+ #defaultOptions = {}
16
+
17
+
18
+ db
19
+ options
20
+
21
+
22
+ constructor(db, opt) {
23
+ this.db = db
24
+ this.options = {
25
+ ...this.#defaultOptions,
26
+ ...opt
27
+ }
28
+ }
29
+
30
+
31
+
32
+
33
+
34
+
35
+ async increment(doc_id) {
36
+ const self = this
37
+ const db = self.db
38
+ try {
39
+ const { year, month } = await getDbCurrentDate(db)
40
+ return await generateId(self, year, month, doc_id)
41
+ } catch (err) {
42
+ throw err
43
+ }
44
+ }
45
+
46
+ // 9 digit, untuk tipe integer
47
+ async yearlyshort(doc_id) {
48
+ const self = this
49
+ const db = self.db
50
+ try {
51
+ const month = 0
52
+ const { year } = await getDbCurrentDate(db)
53
+ return await generateId(self, year, month, doc_id, true)
54
+ } catch (err) {
55
+ throw err
56
+ }
57
+ }
58
+
59
+ }
60
+
61
+
62
+ async function generateId(self, year, month, doc_id, short=false) {
63
+ const options = self.options
64
+ const db = self.db
65
+ const searchMap = {
66
+ seqnum: `sequencer_seqnum=\${seqnum}`,
67
+ year: `sequencer_year=\${year}`,
68
+ month: `sequencer_month=\${month}`
69
+ };
70
+
71
+ sqlUtil.connect(db)
72
+
73
+ try {
74
+
75
+ // ambil code doc
76
+ const docparam = { doc_id }
77
+ const sqldoc = `select doc_seqnum from core.doc where doc_id=\${doc_id}`
78
+ const row = await db.oneOrNone(sqldoc, docparam)
79
+ const seqnum = row!=null ? row.doc_seqnum : 0
80
+
81
+
82
+ if (seqnum<0 || seqnum>99) {
83
+ throw new Error(`doc_id: '${doc_id}' has doc seqnum '${seqnum}' that is invalid. doc seqnum have to in range 1-99 `)
84
+ }
85
+
86
+
87
+
88
+ // hitung total panjang bigint yang akan dihailkan
89
+ let nlength = 0
90
+ if (month==0) {
91
+ // yearly
92
+ nlength += 2
93
+ } else {
94
+ // montly
95
+ nlength += 4
96
+ }
97
+
98
+ if (seqnum>0) {
99
+ nlength += 2
100
+ }
101
+
102
+
103
+
104
+ // 25 09 01 000402 0000009
105
+
106
+ const ln = nlength + self.options.numberLength
107
+
108
+ if (short) {
109
+ if (ln > 9) {
110
+ throw new Error(`Total length of sequencer (${ln}) is mre than max length allowed(9)`)
111
+ }
112
+ } else {
113
+ if (ln > MAX_LENGTH) {
114
+ throw new Error(`Total length of sequencer (${ln}) is mre than max length allowed(${MAX_LENGTH})`)
115
+ }
116
+ }
117
+
118
+
119
+ // ambil data sequencer
120
+ {
121
+ const criteria = { year, month, seqnum }
122
+ const {whereClause, queryParams} = sqlUtil.createWhereClause(criteria, searchMap)
123
+
124
+ const columns = [
125
+ 'sequencer_id',
126
+ 'sequencer_year',
127
+ 'sequencer_month',
128
+ 'sequencer_seqnum',
129
+ 'sequencer_number',
130
+ 'EXTRACT(YEAR FROM sequencer_lastdate) AS lastyear',
131
+ 'EXTRACT(MONTH FROM sequencer_lastdate) AS lastmonth'
132
+ ]
133
+
134
+ const sql = sqlUtil.createSqlSelect({tablename, columns, whereClause, sort:{}, limit:0, offset:0, queryParams}) + ' for update'
135
+ const row = await db.oneOrNone(sql, queryParams)
136
+
137
+ const obj = {}
138
+
139
+ if (row!=null) {
140
+ // console.log(row)
141
+ obj.sequencer_id = row.sequencer_id
142
+ obj._modifyby = 0
143
+ obj._modifydate = (new Date()).toISOString()
144
+ obj.sequencer_number = row.sequencer_number+1
145
+ const cmd = sqlUtil.createUpdateCommand(tablename, obj, ['sequencer_id'])
146
+ await cmd.execute(obj)
147
+ } else {
148
+ obj._createby = 0
149
+ obj._createdate = (new Date()).toISOString()
150
+ obj.sequencer_year = year,
151
+ obj.sequencer_month = month
152
+ obj.sequencer_seqnum = seqnum
153
+ obj.sequencer_number = 1
154
+ obj.sequencer_lastdate = (new Date()).toISOString()
155
+ obj.sequencer_remark = doc_id
156
+ const cmd = sqlUtil.createInsertCommand(tablename, obj, ['sequencer_id'])
157
+ await cmd.execute(obj)
158
+ }
159
+
160
+ // compose generated id
161
+ const YY = String(year-2000).padStart(2, '0')
162
+ const MM = String(month).padStart(2, '0')
163
+
164
+
165
+ const tokennum = []
166
+ if (short) {
167
+ // hanya tahun
168
+ tokennum.push(YY)
169
+ } else {
170
+ // tahun dan bulan
171
+ tokennum.push(YY)
172
+ tokennum.push(MM)
173
+ }
174
+
175
+
176
+
177
+ if (seqnum>0) {
178
+ tokennum.push(String(seqnum).padStart(2, '0'))
179
+ }
180
+
181
+
182
+ // 250901999999
183
+ // 2509019999999
184
+ const maxlen = short ? 9 : MAX_LENGTH
185
+ const idpref = tokennum.join('')
186
+ const numlen = maxlen - idpref.length
187
+ tokennum.push(String(obj.sequencer_number).padStart(numlen, '0'))
188
+
189
+ const s = tokennum.join('')
190
+ return s
191
+ }
192
+ } catch (err) {
193
+ throw err
194
+ }
195
+ }
196
+
197
+ async function getDbCurrentDate(db) {
198
+ try {
199
+ const sql = "SELECT EXTRACT('year' FROM CURRENT_DATE) AS year, EXTRACT('month' FROM CURRENT_DATE) AS month"
200
+ const row = await db.one(sql)
201
+
202
+ const ret = {
203
+ year: row.year,
204
+ month: row.month
205
+ }
206
+
207
+ return ret
208
+ } catch (err) {
209
+ throw err
210
+ }
211
+ }
212
+
213
+
214
+
package/src/session.js ADDED
@@ -0,0 +1,57 @@
1
+ import session from 'express-session'; // session
2
+ import { createClient } from 'redis'; // session
3
+ import * as connectRedis from 'connect-redis'; // session
4
+
5
+
6
+ export async function createSession(options) {
7
+
8
+ const redisUrl = options.redisUrl || 'redis://localhost:6379'
9
+ const sessionName = options.sessionName || 'sid'
10
+ const sessionSecret = options.sessionSecret || 'rahasia'
11
+ const sessionMaxAge = options.sessionMaxAge || 15 * 50 * 1000 // default 15 menit
12
+ const sessionDomain = options.sessionDomain || 'localhost'
13
+ const sessionSecure = options.sessionSecure ?? false
14
+ const sessionHttpOnly = options.sessionHttpOnly ?? true
15
+
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
44
+ }
45
+ }
46
+
47
+ // TODO: tambahkan untuk keperluan ini
48
+ // domain: '.example.com', // ✅ Cookie tersedia untuk semua subdomain
49
+ // path: '/', // ✅ Berlaku untuk semua path
50
+ // secure: true, // ✅ Hanya dikirim lewat HTTPS
51
+ // httpOnly: true, // ✅ Tidak bisa diakses dari JavaScript
52
+ // sameSite: 'none', // ✅ Bisa lintas domain (harus paired dengan secure)
53
+ // maxAge: 15 * MINUTE,
54
+
55
+ console.log('starting session manager.')
56
+ return session(sessionConfig)
57
+ }
package/src/startup.js ADDED
@@ -0,0 +1,59 @@
1
+
2
+ export async function authorizeRequest(db, req) {
3
+ const moduleName = req.params.modulename;
4
+ const program_id = req.query.prog;
5
+
6
+ try {
7
+
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`)
11
+ err.status = 401
12
+ err.code = 401
13
+ throw err
14
+ }
15
+
16
+ const user_id = req.session.user.userId
17
+ const user_fullname = req.session.user.userFullname
18
+ const developerAccess = req.session.user.developerAccess
19
+
20
+
21
+ // jika punya akses developer boleh buka semuanya
22
+ 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) {
25
+ return true // user adalah developer
26
+ }
27
+
28
+ // jika tidak punya akses developer, cek apakah boleh buka program
29
+ 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) {
32
+ return true // user punya akses program
33
+ }
34
+
35
+ const err = new Error(`user '${user_fullname}' tidak diperbolehkan mengakses program '${moduleName}'`)
36
+ err.status = 401
37
+ err.code = 401
38
+ throw err
39
+
40
+ } catch(err) {
41
+ throw err
42
+ }
43
+ }
44
+
45
+ export async function getApplicationSetting(db) {
46
+ const setting = {}
47
+ try {
48
+ const sql = 'select setting_id, setting_value from core.setting'
49
+ const rows = await db.any(sql);
50
+ for (var row of rows) {
51
+ const setting_id = row.setting_id
52
+ const setting_value = row.setting_value
53
+ setting[setting_id] = setting_value
54
+ }
55
+ return setting
56
+ } catch (err) {
57
+ throw err
58
+ }
59
+ }
package/src/webapps.js ADDED
@@ -0,0 +1,239 @@
1
+ import ExpressServer from 'express';
2
+ import { createSession } from './session.js'
3
+ import context from './context.js'
4
+ import { createBasicRouter, uploader } from './router.js'
5
+ import { handleModuleNotfound } from './routers/handleModuleNotfound.js'
6
+ import { fileURLToPath } from 'node:url';
7
+ import cors from 'cors';
8
+ import favicon from 'serve-favicon';
9
+ import * as path from 'node:path';
10
+ import * as helper from './helper.js'
11
+
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+
17
+
18
+
19
+ const defaultPort = 3000
20
+ const defaultRedisUrl = 'redis://localhost:6379'
21
+ const defaultSessionName = 'sid'
22
+ const defaultSessionSecret = 'rahasia'
23
+ const defaultSessionMaxAge = 15
24
+ const defaultSessionDomain = 'localhost'
25
+ const defaultSessionSecure = false
26
+ const defaultSessionHttpOnly = true
27
+
28
+
29
+ const defaultNotifierSocket = 'ws://localhost:8080'
30
+ const defaultNotifierServer = 'http://localhost:8080'
31
+
32
+
33
+ export function createWebApplication() {
34
+ const webapp = new WebApplication()
35
+ return webapp
36
+ }
37
+
38
+
39
+ export default class WebApplication {
40
+ #startedOnce
41
+ #express = ExpressServer();
42
+
43
+ get defaultPort() { return defaultPort }
44
+ get defaultRedisUrl() { return defaultRedisUrl }
45
+ get defaultNotifierSocket() { return defaultNotifierSocket }
46
+ get defaultNotifierServer() { return defaultNotifierServer }
47
+ get defaultSessionName() { return defaultSessionName }
48
+ get defaultSessionSecret() { return defaultSessionSecret }
49
+ get defaultSessionMaxAge() { return defaultSessionMaxAge }
50
+ get defaultSessionDomain() { return defaultSessionDomain }
51
+ get defaultSessionSecure() { return defaultSessionSecure }
52
+ get defaultSessionHttpOnly() { return defaultSessionHttpOnly }
53
+
54
+ get express() { return this.#express}
55
+
56
+ #__rootDirectory
57
+ get __rootDirectory() { return this.#__rootDirectory }
58
+ setRootDirectory(v) {
59
+ this.#__rootDirectory = v
60
+ }
61
+
62
+
63
+ constructor() {
64
+ context.setWebappsDirectory(path.join(__dirname, '..'))
65
+ }
66
+
67
+ start(options) {
68
+ if (this.__rootDirectory == undefined) {
69
+ throw new Error('__rootDirectory belum didefinisikan')
70
+ }
71
+
72
+ context.setRootDirectory(this.__rootDirectory)
73
+ context.setFnParseModuleRequest(options.fnParseModuleRequest)
74
+
75
+ if (this.#startedOnce) {
76
+ throw new Error('start already called!')
77
+ }
78
+ this.#startedOnce = true
79
+
80
+ // malai rutin utama untuk menjalankan server
81
+ main(this, options)
82
+ }
83
+
84
+
85
+ }
86
+
87
+
88
+ export function createDefaultAppConfig() {
89
+ const sessionName = defaultSessionName
90
+ const sessionSecret = defaultSessionSecret
91
+ const sessionMaxAge = defaultSessionMaxAge
92
+ const sessionDomain = defaultSessionDomain
93
+ const sessionSecure = defaultSessionDomain
94
+ const sessionHttpOnly = defaultSessionHttpOnly
95
+
96
+ const notifierSocket = defaultNotifierSocket
97
+ const notifierServer = defaultNotifierServer
98
+
99
+ const redisUrl = defaultRedisUrl
100
+
101
+ return {
102
+ sessionName,
103
+ sessionSecret,
104
+ sessionMaxAge,
105
+ sessionDomain,
106
+ sessionSecure,
107
+ sessionHttpOnly,
108
+
109
+ notifierSocket,
110
+ notifierServer,
111
+ redisUrl
112
+ }
113
+ }
114
+
115
+
116
+ async function main(self, options) {
117
+ const __rootDirectory = self.__rootDirectory
118
+
119
+ const app = self.express
120
+ const port = options.port ?? self.defaultPort
121
+ const startingMessage = options.startingMessage ?? `Starting webserver on port \x1b[1;33m${port}\x1b[0m`
122
+ const appConfig = options.appConfig || createDefaultAppConfig()
123
+
124
+ const basicRouter = createBasicRouter()
125
+ const extendedRouter = options.router || ExpressServer.Router({ mergeParams: true });
126
+ const router = ExpressServer.Router({ mergeParams: true });
127
+
128
+
129
+ const redisUrl = appConfig.redisUrl
130
+ const notifierSocket = appConfig.notifierSocket
131
+ const notifierServer = appConfig.notifierServer
132
+
133
+ const sessionName = appConfig.sessionName
134
+ const sessionSecret = appConfig.sessionSecret
135
+ const sessionMaxAge = appConfig.sessionMaxAge
136
+ const sessionDomain = appConfig.sessionDomain
137
+ const sessionSecure = appConfig.sessionSecure
138
+ const sessionHttpOnly = appConfig.sessionHttpOnly
139
+
140
+ const session = await createSession({ redisUrl, sessionName, sessionSecret, sessionMaxAge, sessionDomain, sessionSecure, sessionHttpOnly })
141
+
142
+
143
+ router.use(uploader)
144
+
145
+ router.use(extendedRouter) // extended akan dipanggil dahulu, sehingga akan meng-override basicRouter
146
+ router.use(basicRouter)
147
+
148
+
149
+ // setup variabel konfigurasi local, nanti bisa diakses dari router/api
150
+ app.locals.appConfig = appConfig
151
+
152
+
153
+
154
+ // konfigurasi applikasi
155
+ app.set('trust proxy', true); // ini nanti diisi daftar host yang dipercaya sebagai proxy, baca dari env
156
+ app.set("view engine", "ejs");
157
+ app.set("views", [
158
+ path.join(__rootDirectory, "views"),
159
+ path.join(__rootDirectory, 'public', 'modules')
160
+ ]);
161
+
162
+ // setup cors
163
+ if (options.allowedOrigins!=null) {
164
+ const allowedOrigins = options.allowedOrigins
165
+ app.use(cors({
166
+ origin: function (origin, callback) {
167
+ if (!origin) return callback(null, true); // untuk server-side atau curl
168
+ const isAllowed = allowedOrigins.some(o => {
169
+ if (typeof o === 'string') return o === origin;
170
+ if (o instanceof RegExp) return o.test(origin);
171
+ return false;
172
+ });
173
+
174
+ if (isAllowed) {
175
+ callback(null, true);
176
+ } else {
177
+ callback(new Error('Not allowed by CORS'));
178
+ }
179
+ },
180
+ // methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
181
+ // allowedHeaders: ['Content-Type', 'Authorization'],
182
+ // credentials: true
183
+ }));
184
+ } else {
185
+ app.use(cors());
186
+ }
187
+
188
+
189
+
190
+ // setup middleware
191
+ app.use(favicon(path.join(__rootDirectory, 'public', 'favicon.ico')));
192
+ app.use(ExpressServer.json());
193
+ app.use(ExpressServer.urlencoded({ extended: true }));
194
+ app.use(session);
195
+
196
+
197
+ // framework ini menggunakan library fgta5 untuk ui di client
198
+ if (appConfig.fgta5jsDebugMode) {
199
+ app.use('/public/libs/fgta5js', ExpressServer.static(path.join(__dirname, '..', 'libs', 'fgta5js')));
200
+ } else {
201
+ app.use('/public/libs/fgta5js', ExpressServer.static(path.join(__dirname, '..', 'libs', 'fgta5js-dist')));
202
+ }
203
+
204
+ app.use('/public/libs/webmodule', ExpressServer.static(path.join(__dirname, '..', 'libs', 'webmodule')));
205
+
206
+ // Routing /public untuk serve halaman-halaman static
207
+ app.use('/public', rejectEjsFiles);
208
+ app.use('/public', ExpressServer.static(path.join(__rootDirectory, 'public')));
209
+ app.use('/', router)
210
+ app.use(handleModuleNotfound)
211
+
212
+
213
+ const server = app.listen(port, ()=>{
214
+ console.log('\n\n' + startingMessage);
215
+ });
216
+
217
+ // Tangani event 'error' pada objek server
218
+ server.on('error', (err) => {
219
+ if (err.code === 'EADDRINUSE') {
220
+ console.error(`\n\x1b[31mError!\x1b[0m\nPort ${port} sudah digunakan. Silakan coba port lain.\n`);
221
+ } else {
222
+ console.error('\n\x1b[31mError!\x1b[0m\nTerjadi kesalahan saat memulai server:', err, "\n");
223
+ }
224
+ // Anda bisa memutuskan untuk keluar dari aplikasi atau mencoba port lain
225
+ process.exit(1); // Keluar dengan kode error
226
+ });
227
+ }
228
+
229
+
230
+ function rejectEjsFiles(req, res, next) {
231
+ const excludedExtensions = ['.ejs'];
232
+ const ext = path.extname(req.url);
233
+
234
+ if (excludedExtensions.includes(ext)) {
235
+ return res.status(403).send('Akses ke file ini tidak diperbolehkan');
236
+ }
237
+
238
+ next();
239
+ }
@@ -0,0 +1,83 @@
1
+ import { Worker } from 'worker_threads';
2
+
3
+
4
+ export const runDetachedWorker = (workerPath, notifierServer, clientId, options) => {
5
+ const timeout = options.timeout || 10000
6
+
7
+ // cek dulu apakah valid
8
+ try {
9
+ if (clientId==null || notifierServer==null) {
10
+ throw new Error('clientId / notifierServer belum didefinisikan di parameter runDetachedWorker')
11
+ }
12
+ } catch (err) {
13
+ throw err
14
+ }
15
+
16
+
17
+ console.log('Starting worker')
18
+ const worker = new Worker(workerPath, {
19
+ workerData: options
20
+ });
21
+
22
+ const timeoutId = setTimeout(() => {
23
+ console.warn('Worker timeout, terminating...');
24
+ notifyClient(notifierServer, clientId, 'timeout') // nofify ke clent, kalau timeout
25
+ worker.terminate();
26
+ }, timeout);
27
+
28
+ worker.on('message', (info) => {
29
+ clearTimeout(timeoutId);
30
+ console.log('Worker message:', 'message', info);
31
+ if (info.done===true) {
32
+ notifyClient(notifierServer, clientId, 'done', info) // nofify message ke clent
33
+ // worker.terminate(); // Optional: let it exit naturally if preferred
34
+ } else {
35
+ notifyClient(notifierServer, clientId, 'message', info) // nofify message ke clent
36
+ }
37
+ });
38
+
39
+ worker.on('error', (err) => {
40
+ clearTimeout(timeoutId);
41
+ console.error('Worker error:', err);
42
+ notifyClient(notifierServer, clientId, 'error', {message: err.message}) // nofify error ke clent
43
+ worker.terminate();
44
+ });
45
+
46
+ worker.on('exit', (code) => {
47
+ clearTimeout(timeoutId);
48
+ notifyClient(notifierServer, clientId, 'done', {})
49
+ console.log(`🚪 Worker exited with code ${code}`);
50
+ });
51
+ };
52
+
53
+
54
+
55
+ async function notifyClient(notifierServer, clientId, status, info) {
56
+ try {
57
+ console.log(status)
58
+ console.log('notify to:' , notifierServer)
59
+ console.log(clientId)
60
+
61
+ const data = {clientId, status, info}
62
+ const url = `${notifierServer}/notify`
63
+ const response = await fetch(url, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ },
68
+ body: JSON.stringify(data)
69
+ });
70
+
71
+ if (!response.ok) {
72
+ const status = response.status
73
+ const statustext = response.statusText
74
+ const err = new Error(`${status} ${statustext}: ${options.method} ${url}`)
75
+ err.code = status
76
+ throw err
77
+ }
78
+
79
+ return response
80
+ } catch (err) {
81
+ throw err
82
+ }
83
+ }
@@ -0,0 +1,11 @@
1
+
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" />
11
+
@@ -0,0 +1,5 @@
1
+
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" />
5
+