@everneed/worker 1.2.0 → 2.1.0

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/index.js CHANGED
@@ -89,6 +89,6 @@ import pseudoEnv from "./pseudoenv.js"
89
89
 
90
90
  export { rabbitmq } // preserve seperate-dir for example
91
91
  export { postgresInstance as postgres }
92
- // export { mongooseInstance as mongoose }
92
+ export { mongooseInstance as mongoose }
93
93
  export { redisInstance as redis }
94
94
  export { socketioInstance as socketio }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everneed/worker",
3
- "version": "1.2.0",
3
+ "version": "2.1.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -10,6 +10,178 @@ import { redis } from "@everneed/worker"
10
10
  import { ResponseCode } from "@everneed/responsecode"
11
11
  import { pipe, switchPourStr, randomStr } from "@everneed/helper"
12
12
 
13
+ class VersionManager{
14
+ #currentVer
15
+ #deprecated
16
+ #configPath = null
17
+ #endpointMap
18
+
19
+ // 🗑️
20
+ #existingBucket = []
21
+ #loadedBucket = []
22
+
23
+ constructor(){
24
+ this.init()
25
+ this.#load()
26
+ }
27
+ init(){
28
+ this.#load()
29
+ }
30
+ config(configPath){
31
+ this.#configPath = path.join("../../..", configPath)
32
+
33
+ /* Re-init */
34
+ this.init()
35
+ }
36
+ #clean(){
37
+ this.#currentVer = null
38
+ this.#configPath = null
39
+ this.#endpointMap = {}
40
+ this.#existingBucket = []
41
+ this.#loadedBucket = []
42
+ }
43
+ #load(){
44
+ // json into endpointMaps
45
+ let json
46
+ if(!this.#configPath){
47
+ try{
48
+ // json = require("../../config/versionmap.json")
49
+ json = fs.readFileSync(path.join(process.cwd(), "/config/versionmap.json"))
50
+ }
51
+ catch(err){
52
+ json = `{"/endpoint/one":{"latest": 1, "map":{"1":1}}}`
53
+ }
54
+ }
55
+ else{
56
+ json = fs.readFileSync(path.resolve(this.#configPath))
57
+ const fs = require("fs")
58
+ }
59
+
60
+ const config = pipe(json)
61
+ .then(json=> JSON.parse(json))
62
+ .result
63
+ this.#endpointMap = config.endpoint
64
+ this.#currentVer = config.apiVer
65
+ this.#deprecated = config.deprecatedVer
66
+ }
67
+ async update(){
68
+ // Define new version
69
+ const newVer = this.#currentVer+1
70
+ // Load all existing running API and stage them
71
+ // in existingBucket
72
+ const files = fs.readdirSync(`./src/mq-gateway`)
73
+ for(const file of files){
74
+ const module = file.replace(/.js|.ts/g, ``).toLowerCase()
75
+ const functs = await import(`file://${path.join(process.cwd(), `/src/mq-gateway/${file}`)}`)
76
+
77
+ for(const funct in functs.default){
78
+ for(const ver in functs.default[funct].function){
79
+ this.#existingBucket.push(`/${pseudoEnv.process.env.SVC_NAME}/${module}/${funct}/${ver}`)
80
+ }
81
+ }
82
+ }
83
+
84
+ // Prep irregular changes and unchanges 🔪🌽🍍🥕
85
+ // into more consumable iterable form
86
+ // will output the elements from existing bucket that are not in the loaded bucket
87
+ const addition = this.#existingBucket.filter(x => !this.#loadedBucket.includes(x))
88
+ const updation = pipe(addition)
89
+ .then(addition=>{
90
+ return {
91
+ addition: addition.map(fullEndpoint=> fullEndpoint.split(/\/v([0-9]+)$/, 2)[0]),
92
+ all: this.#existingBucket.map(fullEndpoint=> fullEndpoint.split(/\/v([0-9]+)$/, 2)[0])
93
+ }
94
+ })
95
+ .then(({addition, all})=> new Set(all).difference(new Set(addition)))
96
+ .then(set=> [...set])
97
+ .result
98
+
99
+ // Process to then stash result to head variable 🥣
100
+ for(const endpoint of addition){
101
+ // "/endpoint/one/v1" -> [/endpoint/one, 1]
102
+ const [pureEndpoint, versionInt] = endpoint.split(/\/v([0-9]+)$/, 2)
103
+
104
+ // new endpoint
105
+ if(!this.#endpointMap[pureEndpoint]){
106
+ this.#endpointMap[pureEndpoint] = {
107
+ latest: 1,
108
+ map: {}
109
+ }
110
+
111
+ this.#endpointMap[pureEndpoint]["map"][`v${newVer}`] = 1
112
+ }
113
+
114
+ this.#endpointMap[pureEndpoint].latest = Number(versionInt)
115
+ this.#endpointMap[pureEndpoint].map[`v${newVer}`] = Number(versionInt)
116
+ // /pure/endpoint:{
117
+ // latest: (2), <--- updated
118
+ // map:{
119
+ // v1: 1,
120
+ // v2: 1,
121
+ // v3: 2 <-- addition
122
+ // }
123
+ // }
124
+ }
125
+ for(const endpoint in updation){
126
+ this.#endpointMap[endpoint].map[`v${newVer}`] = this.#endpointMap[endpoint].latest
127
+ // /pure/endpoint:{
128
+ // latest: 1,
129
+ // map:{
130
+ // v1: 1,
131
+ // v2: 1,
132
+ // v3: 1 <-- updated
133
+ // }
134
+ // }
135
+ }
136
+
137
+ // Save head variable into config file (.json)
138
+ const json = JSON.stringify({
139
+ apiVer: newVer,
140
+ lastUpdate: moment().utc().format(),
141
+ endpoint: this.#endpointMap
142
+ }, null, 4)
143
+ if(!this.#configPath){
144
+ try{
145
+ fs.writeFileSync(path.join(process.cwd(), "/config/versionmap.json"), json)
146
+ }
147
+ catch(err){
148
+ console.log(err)
149
+ }
150
+ }
151
+ else{
152
+ fs.writeFileSync(path.resolve(__dirname, this.#configPath), json)
153
+ }
154
+
155
+ // re-load
156
+ this.#clean()
157
+ this.#load()
158
+ }
159
+ allVersion(endpoint){
160
+ // returns array of available version map
161
+ // in respecting order (index 0 should be null)
162
+ // Array due to be iterated nature
163
+
164
+ const result = [null]
165
+
166
+ for(const majorVer in this.#endpointMap[endpoint].map){
167
+ result[majorVer] = this.#endpointMap[endpoint].map[majorVer] // major = individual
168
+ }
169
+
170
+ return result // [null, individual ver, individual ver]
171
+ }
172
+ trueVersion({endpoint, version}){
173
+ return this.#endpointMap[endpoint].map[`v${version}`]
174
+ }
175
+ latestVersion(){
176
+ return this.#currentVer
177
+ }
178
+ deprecatedVersion(){
179
+ return this.#deprecated
180
+ }
181
+ }
182
+ const version = new VersionManager()
183
+ version.init()
184
+
13
185
  export default {
14
186
  config: function(object){
15
187
  // config({
@@ -55,73 +227,72 @@ export default {
55
227
  const main = await import(`file://${path.join(process.cwd(), `/src/mq-gateway/${moduleSpace}`)}.js`)
56
228
  // const main = await import(`#@/mq-gateway/${moduleSpace}.js`)
57
229
 
58
- for(const functName in main.default){
230
+ for(const endpoint in main.default){
59
231
  /* Generate consumer */
60
- channel.assertQueue(`req/${pseudoEnv.process.env.SVC_NAME}/${moduleSpace}/${functName}`, {
232
+ channel.assertQueue(`req/${pseudoEnv.process.env.SVC_NAME}/${moduleSpace}/${endpoint}`, {
61
233
  durable: true
62
234
  })
63
- channel.consume(`req/${pseudoEnv.process.env.SVC_NAME}/${moduleSpace}/${functName}`, async (message)=>{
235
+ channel.consume(`req/${pseudoEnv.process.env.SVC_NAME}/${moduleSpace}/${endpoint}`, async (message)=>{
64
236
  const mqHeader = message.properties.headers
65
237
  const mqReq = await this.decryptMessage({
66
238
  message: message.content.toString(),
67
239
  ...(mqHeader.auth && { token: mqHeader.auth.token }),
68
240
  ...(mqHeader.auth && { userAgent: mqHeader.auth.userAgent })
69
241
  })
242
+ const trueVersion = mqHeader.version ?
243
+ version.trueVersion({
244
+ endpoint: `/${pseudoEnv.process.env.SVC_NAME}/${moduleSpace}/${endpoint}`,
245
+ version: mqHeader.version
246
+ })
247
+ :
248
+ version.latestVersion()
249
+
70
250
  const prep = {
71
251
  message: null
72
252
  }
73
-
74
- /* With Auth */
75
- if(main.default[functName].authentication){
76
- if(!mqHeader.auth){
77
- const response = new ResponseCode()
78
- response.pushCode("NOT_AUTHENTICATED")
79
- this.encryptMessage({
80
- message: response
81
- })
82
- return
83
- }
84
-
85
- const { token, userAgent } = mqHeader.auth
86
- const auth = await this.verifyToken({
87
- token: token,
88
- userAgent: userAgent
89
- })
90
253
 
91
- if(auth.checkError()){ // NOT_AUTHENTICATED || SESSION_EXPIRED
254
+ try{
255
+ /* Active Endpoint Filter */
256
+ if(!main.default[endpoint].active){
257
+ const response = new ResponseCode()
258
+ response.pushCode("INVALID_ENDPOINT")
92
259
  prep["message"] = await this.encryptMessage({
93
- message: auth.result,
94
- token: token,
95
- userAgent: userAgent
260
+ message: response.result
96
261
  })
262
+ throw "invalid_endpoint"
97
263
  }
98
- else{ // AUTHENTICATED || REFRESH_SESSION
99
- const result = await main.default[functName](mqHeader.auth, mqReq.payload)
100
- prep["message"] = await this.encryptMessage({
101
- message: {
102
- ...result,
103
- ...(auth.hasCode("REFRESH_SESSION") && auth.result.data)
104
- },
105
- token: token,
106
- userAgent: userAgent
107
- })
264
+ /* API enforce Auth */
265
+ if(main.default[endpoint].authentication){
266
+ if(!mqHeader.auth){
267
+ const response = new ResponseCode()
268
+ response.pushCode("NOT_AUTHENTICATED")
269
+ prep["message"] = await this.encryptMessage({
270
+ message: response.result
271
+ })
272
+ throw "not_authenticated"
273
+ }
108
274
  }
109
- }
110
-
111
- /* Without Auth */
112
- else{
113
- const result = await main.default[functName](false, mqReq.payload)
275
+ /* Main Process */
276
+ const result = await main.default[endpoint].function[`v${trueVersion}`](mqHeader.auth || false, mqReq.payload)
114
277
  prep["message"] = await this.encryptMessage({
115
278
  message: result,
116
279
  ...(mqHeader.auth && { token: mqHeader.auth.token }),
117
280
  ...(mqHeader.auth && { userAgent: mqHeader.auth.userAgent })
118
281
  })
119
- }
120
- if(mqReq.trip == "returning"){
121
- channel.sendToQueue(message.properties.replyTo, Buffer.from(prep.message), {
122
- correlationId: message.properties.correlationId
123
- })
124
- channel.ack(message)
282
+
283
+ throw "finish"
284
+ }
285
+ catch(something){
286
+ if(mqReq.trip == "returning"){
287
+ channel.sendToQueue(message.properties.replyTo, Buffer.from(prep.message), {
288
+ correlationId: message.properties.correlationId,
289
+ headers:{
290
+ ...(mqHeader.version && { version: mqHeader.version }),
291
+ ...(mqHeader.auth && { auth: mqHeader.auth })
292
+ },
293
+ })
294
+ channel.ack(message)
295
+ }
125
296
  }
126
297
  },{
127
298
  noAck: false
@@ -132,7 +303,7 @@ export default {
132
303
  // connection.close()
133
304
  })
134
305
  },
135
- publish: function({topic, auth=null, trip="passby", data}){
306
+ publish: function({topic, version, auth=null, trip="passby", data}){
136
307
  /* Usage */
137
308
  // publish({
138
309
  // topic: <topic start at svc :String>,
@@ -148,72 +319,107 @@ export default {
148
319
  // ex: req/cd6f01UjkB7E/rolling-tempat-duduk/class/getAll
149
320
  // ex: res/cd6f01UjkB7E/rolling-tempat-duduk/class/getAll
150
321
 
322
+ const response = new ResponseCode()
151
323
  return new Promise(async (resolve, reject)=>{
152
324
  /* RabbitMQ Engine */
153
325
  amqp.connect(pseudoEnv.process.env.RABBIT_HOST, async (error0, connection)=>{
154
326
  if(error0){ throw error0 }
155
327
 
156
- /* Creates connection & channel */
157
- const channel = connection.createChannel((error1, channel)=>{
158
- if (error1){ throw error1 }
159
- return channel
160
- })
161
-
162
- /* Build payload structure */
163
- // like topic, address, etc.
164
- const userName = auth ? this.getUserInfo({tokenBody: auth.token.split(".")[0]}).userName : `guest${randomStr({length: 12})}`
165
- const prep = {
166
- topic: `req/${topic}`,
167
- message:{
168
- trip: trip,
169
- senderAddress: `${createHmac("sha256", userName).digest("hex").slice(0, 12)}${moment().utc().format("HHmmssSSS")}`,
170
- payload: data
328
+ try{
329
+ /* Creates connection & channel */
330
+ const channel = connection.createChannel((error1, channel)=>{
331
+ if (error1){ throw error1 }
332
+ return channel
333
+ })
334
+
335
+ /* Build payload structure */
336
+ // like topic, address, etc.
337
+ const userName = auth ? this.getUserInfo({tokenBody: auth.token.split(".")[0]}).userName : `guest${randomStr({length: 12})}`
338
+ const prep = {
339
+ topic: `req/${topic}`,
340
+ message:{
341
+ trip: trip,
342
+ senderAddress: `${createHmac("sha256", userName).digest("hex").slice(0, 12)}${moment().utc().format("HHmmssSSS")}`,
343
+ payload: data
344
+ }
171
345
  }
172
- }
173
- /* Main publisher */
174
- const mqReq = await this.encryptMessage({
175
- message: prep.message,
176
- ...(auth && { token: auth.token }),
177
- ...(auth && { userAgent: auth.userAgent })
178
- })
179
-
180
- channel.assertQueue(prep.topic, { durable: true })
181
- channel.sendToQueue(prep.topic, Buffer.from(mqReq), {
182
- ...(trip == "returning" && { correlationId: prep.message.senderAddress }),
183
- ...(trip == "returning" && { replyTo: `res/${topic}` }),
184
- ...(auth && { headers:{auth: auth} })
185
- })
186
-
187
- /* Resolvation According to Trip */
188
- if(trip == "returning"){
189
- /* Creates consumer for returning messages */
190
- channel.assertQueue(`res/${topic}`, { durable: true })
191
- channel.consume(`res/${topic}`, async (message)=>{
192
- const mqRes = await this.decryptMessage({
193
- message: message.content.toString(),
194
- ...(auth && { token: auth.token }),
195
- ...(auth && { userAgent: auth.userAgent })
346
+ /* Validate Auth */
347
+ if(auth){
348
+ const validate = await this.publish({
349
+ topic: "authenthor/session/svVerifyToken",
350
+ trip: "returning",
351
+ data:{
352
+ token: auth.token,
353
+ userAgent: auth.userAgent,
354
+ noRefresh: true
355
+ }
196
356
  })
197
357
 
198
- if(message.properties.correlationId == prep.message.senderAddress){
199
- resolve(mqRes)
200
- channel.ack(message)
201
- setTimeout(()=>{
202
- connection.close()
203
- }, 500)
204
- }
205
- }, {
206
- noAck: false
358
+ response.createNew(validate)
359
+ if(response.checkError()) throw "abort"
360
+ else response.reset()
361
+ }
362
+ /* Main publisher */
363
+ const mqReq = await this.encryptMessage({
364
+ message: prep.message,
365
+ ...(auth && { token: auth.token }),
366
+ ...(auth && { userAgent: auth.userAgent })
207
367
  })
208
- }
209
- else if(trip == "passby"){
210
- resolve("sent")
211
-
212
- setTimeout(()=>{
213
- connection.close()
214
- }, 500)
215
- }
368
+
369
+ channel.assertQueue(prep.topic, { durable: true })
370
+ channel.sendToQueue(prep.topic, Buffer.from(mqReq), {
371
+ headers:{
372
+ ...(version && { version: version }),
373
+ ...(auth && { auth: auth })
374
+ },
375
+ ...(trip == "returning" && { correlationId: prep.message.senderAddress }),
376
+ ...(trip == "returning" && { replyTo: `res/${topic}` }),
377
+ })
378
+
379
+ /* Resolvation According to Trip */
380
+ if(trip == "returning"){
381
+ /* Creates consumer for returning messages */
382
+ channel.assertQueue(`res/${topic}`, { durable: true })
383
+ channel.consume(`res/${topic}`, async (message)=>{
384
+ const mqRes = await this.decryptMessage({
385
+ message: message.content.toString(),
386
+ ...(auth && { token: auth.token }),
387
+ ...(auth && { userAgent: auth.userAgent })
388
+ })
389
+
390
+ if(message.properties.correlationId == prep.message.senderAddress){
391
+ response.createNew(mqRes)
392
+ channel.ack(message)
393
+ setTimeout(()=>{
394
+ connection.close()
395
+ }, 500)
396
+
397
+ resolve(response)
398
+ }
399
+ }, {
400
+ noAck: false
401
+ })
402
+ }
403
+ else if(trip == "passby"){
404
+ response.pushCode("GENERAL_OK")
405
+
406
+ setTimeout(()=>{
407
+ connection.close()
408
+ }, 500)
216
409
 
410
+ resolve(response)
411
+ }
412
+ }
413
+ catch(something){
414
+ response.createNew(response.result)
415
+ if(something != "abort"){
416
+ // err: other error
417
+ response.pushCode("LOGIC_ERROR")
418
+ response.pushTrace({code: "LOGIC_ERROR", trace: `Unexpected error on queue publisher`})
419
+ }
420
+
421
+ resolve(response)
422
+ }
217
423
  // connection.close()
218
424
  })
219
425
  })
@@ -226,20 +432,19 @@ export default {
226
432
  const uaHash = crypto.hash("sha256", switchPourStr(userAgent, pseudoEnv.process.env.TOKEN_UA_SALT))
227
433
  await redis.connect().catch(error => {})
228
434
  const authKey = await redis.get(`auth:session:long:${userId}:${uaHash}`)
229
- await redis.quit()
230
- const hash = crypto.hash("sha256", switchPourStr(authKey, pseudoEnv.process.env.RABBIT_MESSAGE_SALT))
435
+ const hash = crypto.hash("sha256", switchPourStr(authKey || "kontol", pseudoEnv.process.env.RABBIT_MESSAGE_SALT))
231
436
  const key = hash.substring(0, 16)
232
437
  const iv = hash.substring(48)
233
438
 
234
439
  const cipher = crypto.createCipheriv("aes-128-cbc", Buffer.from(key), Buffer.from(iv))
235
- let encrypted = cipher.update(JSON.stringify(nullCleanser(message)), "utf-8", "base64")
440
+ let encrypted = cipher.update(JSON.stringify(message), "utf-8", "base64")
236
441
  encrypted += cipher.final("base64")
237
442
  return resolve(encrypted)
238
443
  }
239
444
  /* No Auth */
240
445
  else{
241
446
  const cipher = crypto.createCipheriv("aes-128-cbc", Buffer.from(pseudoEnv.process.env.RABBIT_MESSAGE_KEY), Buffer.from(pseudoEnv.process.env.RABBIT_MESSAGE_IV))
242
- let encrypted = cipher.update(JSON.stringify(nullCleanser(message)), "utf-8", "base64")
447
+ let encrypted = cipher.update(JSON.stringify(message), "utf-8", "base64")
243
448
  encrypted += cipher.final("base64")
244
449
  return resolve(encrypted)
245
450
  }
@@ -253,55 +458,22 @@ export default {
253
458
  const uaHash = crypto.hash("sha256", switchPourStr(userAgent, pseudoEnv.process.env.TOKEN_UA_SALT))
254
459
  await redis.connect().catch(error => {})
255
460
  const authKey = await redis.get(`auth:session:long:${userId}:${uaHash}`)
256
- await redis.quit()
257
- const hash = crypto.hash("sha256", switchPourStr(authKey, pseudoEnv.process.env.RABBIT_MESSAGE_SALT))
461
+ const hash = crypto.hash("sha256", switchPourStr(authKey || "kontol", pseudoEnv.process.env.RABBIT_MESSAGE_SALT))
258
462
  const key = hash.substring(0, 16)
259
463
  const iv = hash.substring(48)
260
464
 
261
465
  const decipher = crypto.createDecipheriv("aes-128-cbc", Buffer.from(key), Buffer.from(iv))
262
466
  let decrypted = decipher.update(message, "base64", "utf-8")
263
467
  decrypted += decipher.final("utf-8")
264
- return resolve(nullCleanser(JSON.parse(decrypted)))
468
+ return resolve(JSON.parse(decrypted))
265
469
  }
266
470
  /* No Auth */
267
471
  else{
268
472
  const decipher = crypto.createDecipheriv("aes-128-cbc", Buffer.from(pseudoEnv.process.env.RABBIT_MESSAGE_KEY), Buffer.from(pseudoEnv.process.env.RABBIT_MESSAGE_IV))
269
473
  let decrypted = decipher.update(message, "base64", "utf-8")
270
474
  decrypted += decipher.final("utf-8")
271
- return resolve(nullCleanser(JSON.parse(decrypted)))
272
- }
273
- })
274
- },
275
- verifyToken: function({token, userAgent}){
276
- const response = new ResponseCode()
277
- return new Promise(async (resolve, reject)=>{
278
- try{
279
- await this.publish({
280
- topic: "authentor/session/svVerifyToken",
281
- trip: "returning",
282
- auth:{
283
- token: token,
284
- userAgent: userAgent,
285
- },
286
- data:{
287
- token: token,
288
- userAgent: userAgent
475
+ return resolve(JSON.parse(decrypted))
289
476
  }
290
- })
291
- .then(apiRes=>{
292
- response.createNew(apiRes)
293
- throw "abort"
294
- })
295
- }
296
- catch(something){
297
- if(something != "abort"){
298
- // err: other error
299
- response.pushCode("LOGIC_ERROR")
300
- response.pushTrace({code: "LOGIC_ERROR", trace: `Unexpected error on ${pseudoEnv.process.env.SVC_DISPLAY} svCreateToken() function`})
301
- }
302
-
303
- resolve(response)
304
- }
305
477
  })
306
478
  },
307
479
  getUserInfo: function({tokenBody}){
@@ -315,4 +487,13 @@ export default {
315
487
  .then(circular=> circular.user)
316
488
  .result // { userId, userName, displayName }
317
489
  },
490
+ update: function(){
491
+ version.update()
492
+ },
493
+ apiVer: function(){
494
+ return version.deprecatedVersion()+1
495
+ },
496
+ latestVer: function(){
497
+ return version.latestVersion()
498
+ }
318
499
  }