Sfoglia il codice sorgente

:sparkles: sse push updates!

tags/0.0.1
J 4 anni fa
parent
commit
d142531acb

+ 5
- 0
backend/lib/index.js Vedi File

2
 const MembershipPlugin = require('./plugins/membership')
2
 const MembershipPlugin = require('./plugins/membership')
3
 const SurveyPlugin = require('./plugins/survey')
3
 const SurveyPlugin = require('./plugins/survey')
4
 const ProfilePlugin = require('./plugins/profile')
4
 const ProfilePlugin = require('./plugins/profile')
5
+const NotificationPlugin = require('./plugins/notification')
5
 
6
 
6
 /**
7
 /**
7
  * A Hapi server instance
8
  * A Hapi server instance
40
         await server.register(ProfilePlugin, {
41
         await server.register(ProfilePlugin, {
41
             routes: { prefix: '/profile' },
42
             routes: { prefix: '/profile' },
42
         })
43
         })
44
+        
45
+        await server.register(NotificationPlugin, {
46
+            routes: { prefix: '/notification' },
47
+        })
43
     },
48
     },
44
 }
49
 }

+ 75
- 0
backend/lib/plugins/notification.js Vedi File

1
+const Stream = require('stream')
2
+const PassThrough = Stream.PassThrough
3
+const Transform = Stream.Transform
4
+const NotificationRoute = require('../routes/notification')
5
+
6
+const stringifyEvent = function (event) {
7
+    let str = ''
8
+    const endl = '\r\n'
9
+    for (const i in event) {
10
+        let val = event[i]
11
+        if (val instanceof Buffer) { val = val.toString() }
12
+        if (typeof val === 'object') { val = JSON.stringify(val) }
13
+        str += i + ': ' + val + endl
14
+    }
15
+    str += endl
16
+    return str
17
+}
18
+
19
+class Transformer extends Transform {
20
+    constructor(options, objectMode) {
21
+        super({ objectMode })
22
+        options = options || {}
23
+        this.counter = 1
24
+        this.event = options.event || null
25
+        this.generateId = options.generateId ? options.generateId : () => {
26
+            return this.counter++
27
+        }
28
+    }
29
+    _transform (chunk, encoding, callback) {
30
+        const event = {
31
+            id: this.generateId(chunk),
32
+            data: chunk
33
+        }
34
+        if (this.event) { event.event = this.event }
35
+        this.push(stringifyEvent(event))
36
+        callback()
37
+    }
38
+    _flush(callback) {
39
+        this.push(stringifyEvent({ event: 'end', data: '' }))
40
+        callback()
41
+    }
42
+} 
43
+
44
+const writeEvent = function (event, stream) {
45
+    if (event) {
46
+        stream.write(stringifyEvent(event))
47
+    } else {
48
+        // closing time
49
+        stream.write(stringifyEvent({ event: 'end', data: '' }))
50
+        stream.end()
51
+    }
52
+}
53
+
54
+const onEvent = (event, h, streamOptions) => {
55
+    // We only support ObjectMode streams
56
+    if (!event._readableState.objectMode) return
57
+
58
+    const through = new Transformer(streamOptions, true)
59
+    const active = new PassThrough()
60
+    through.pipe(active)
61
+    event.pipe(through)
62
+    console.log(event)
63
+    return h.response(active)
64
+        .header('content-type', 'text/event-stream')
65
+        .header('content-encoding', 'identity')
66
+}
67
+
68
+module.exports = {
69
+    name: 'notification-plugin',
70
+    version: '1.0.0',
71
+    register: async (server, options) => {
72
+        await server.route(NotificationRoute)
73
+        server.decorate('toolkit', 'event', onEvent)
74
+    }
75
+}

+ 1
- 1
backend/lib/routes/membership/join.js Vedi File

71
                     groupingToWrite,
71
                     groupingToWrite,
72
                     role,
72
                     role,
73
                 )
73
                 )
74
-                console.log(memberships)
74
+                // console.log(memberships)
75
                 return h
75
                 return h
76
                     .response({
76
                     .response({
77
                         ok: true,
77
                         ok: true,

+ 53
- 0
backend/lib/routes/notification/index.js Vedi File

1
+const Joi = require('joi')
2
+const apiSchema = require('../../schemas/api')
3
+const errorSchema = require('../../schemas/errors')
4
+const Stream = require('stream')
5
+const PassThrough = require('stream').PassThrough
6
+
7
+const pluginConfig = {
8
+    handlerType: 'notifictaion',
9
+    docs: {
10
+        description: 'subscribe',
11
+        notes: 'Subscribe to notifications based on profile_id',
12
+    },
13
+}
14
+
15
+const validators = {
16
+    params: Joi.object({
17
+        profile_id: Joi.number(),
18
+    })
19
+}
20
+
21
+module.exports = {
22
+    method: 'GET',
23
+    path: '/{profile_id}/subscribe',
24
+    options: {
25
+        ...pluginConfig.docs,
26
+        tags: ['api'],
27
+        auth: false,
28
+        cors: true,
29
+        handler: async (request, h) => {
30
+            const input = new PassThrough({ objectMode: true })
31
+            const eventType = 'stonk'
32
+
33
+            const msg = { name: 'BDGRS', price: (500 + Math.floor(Math.random() * 100)).toString(), order: null }
34
+
35
+            // Write to the input stream
36
+            setInterval(() => {
37
+                msg.order = Math.floor(Math.random() * 2) === 1 ? 'BUY' : 'SELL'
38
+                input.write(msg)
39
+            }, 5000)
40
+
41
+            // h.event() Added at plugin registration
42
+            // h is the toolkit
43
+            return h.event(input, h, { event: eventType })
44
+        },
45
+
46
+        /** Validate based on validators object */
47
+         validate: {
48
+            ...validators,
49
+            failAction: 'log'
50
+        },
51
+
52
+    },
53
+}

+ 5
- 5
backend/lib/routes/profile/queue.js Vedi File

52
 
52
 
53
             const queue = await matchQueueService.getQueue(profile_id)
53
             const queue = await matchQueueService.getQueue(profile_id)
54
             const queueIds = queue.map(entry => entry.target_id)
54
             const queueIds = queue.map(entry => entry.target_id)
55
-            console.log('queueIds', queueIds)
55
+            // console.log('queueIds', queueIds)
56
             const res =  {
56
             const res =  {
57
                 ok:true,
57
                 ok:true,
58
                 handler: pluginConfig.handlerType,
58
                 handler: pluginConfig.handlerType,
63
             // queueIds spits out the queue profiles in the correct order
63
             // queueIds spits out the queue profiles in the correct order
64
             // ~However~ when it goes through getProfilesFor
64
             // ~However~ when it goes through getProfilesFor
65
             // it comes back in literal database order regardless of is_deleted status
65
             // it comes back in literal database order regardless of is_deleted status
66
-            console.log(
67
-                'include_profile results',
68
-                await profileService.getProfilesFor(queueIds),
69
-            )
66
+            // console.log(
67
+            //     'include_profile results',
68
+            //     await profileService.getProfilesFor(queueIds),
69
+            // )
70
             if(include_profile) {
70
             if(include_profile) {
71
                 res.data = await profileService.getProfilesFor(queueIds)
71
                 res.data = await profileService.getProfilesFor(queueIds)
72
             }
72
             }

+ 27
- 27
backend/package-lock.json Vedi File

1052
       "dev": true
1052
       "dev": true
1053
     },
1053
     },
1054
     "ansi-regex": {
1054
     "ansi-regex": {
1055
-      "version": "5.0.0",
1056
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
1057
-      "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
1055
+      "version": "5.0.1",
1056
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
1057
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
1058
       "dev": true
1058
       "dev": true
1059
     },
1059
     },
1060
     "ansi-styles": {
1060
     "ansi-styles": {
1240
           "dev": true
1240
           "dev": true
1241
         },
1241
         },
1242
         "boxen": {
1242
         "boxen": {
1243
-          "version": "5.0.1",
1244
-          "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.0.1.tgz",
1245
-          "integrity": "sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA==",
1243
+          "version": "5.1.2",
1244
+          "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
1245
+          "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==",
1246
           "dev": true,
1246
           "dev": true,
1247
           "requires": {
1247
           "requires": {
1248
             "ansi-align": "^3.0.0",
1248
             "ansi-align": "^3.0.0",
1249
             "camelcase": "^6.2.0",
1249
             "camelcase": "^6.2.0",
1250
             "chalk": "^4.1.0",
1250
             "chalk": "^4.1.0",
1251
             "cli-boxes": "^2.2.1",
1251
             "cli-boxes": "^2.2.1",
1252
-            "string-width": "^4.2.0",
1252
+            "string-width": "^4.2.2",
1253
             "type-fest": "^0.20.2",
1253
             "type-fest": "^0.20.2",
1254
             "widest-line": "^3.1.0",
1254
             "widest-line": "^3.1.0",
1255
             "wrap-ansi": "^7.0.0"
1255
             "wrap-ansi": "^7.0.0"
1256
           }
1256
           }
1257
         },
1257
         },
1258
         "camelcase": {
1258
         "camelcase": {
1259
-          "version": "6.2.0",
1260
-          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
1261
-          "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
1259
+          "version": "6.3.0",
1260
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
1261
+          "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
1262
           "dev": true
1262
           "dev": true
1263
         },
1263
         },
1264
         "chalk": {
1264
         "chalk": {
3063
       }
3063
       }
3064
     },
3064
     },
3065
     "hapi-swagger": {
3065
     "hapi-swagger": {
3066
-      "version": "14.1.3",
3067
-      "resolved": "https://registry.npmjs.org/hapi-swagger/-/hapi-swagger-14.1.3.tgz",
3068
-      "integrity": "sha512-Lb4LuJ4VCg/D4xeat/c9aMCpifuTKMgVC8taazB+Dn8evRRTyoymccGouk1H9VI35maV7F1dxzyZJ5ur+C2b/A==",
3066
+      "version": "14.2.5",
3067
+      "resolved": "https://registry.npmjs.org/hapi-swagger/-/hapi-swagger-14.2.5.tgz",
3068
+      "integrity": "sha512-rIxwCT9i+R9E9Z5m9BT15rwYI58IOKTKu7NEx9+pHO5aVeJK703qW3PWk72D7x9MSAnhmlJoEyUiFAU+6zQJ9A==",
3069
       "requires": {
3069
       "requires": {
3070
         "@hapi/boom": "^9.1.0",
3070
         "@hapi/boom": "^9.1.0",
3071
         "@hapi/hoek": "^9.0.2",
3071
         "@hapi/hoek": "^9.0.2",
3072
-        "handlebars": "^4.5.3",
3072
+        "handlebars": "^4.7.7",
3073
         "http-status": "^1.0.1",
3073
         "http-status": "^1.0.1",
3074
         "json-schema-ref-parser": "^6.1.0",
3074
         "json-schema-ref-parser": "^6.1.0",
3075
         "swagger-parser": "4.0.2",
3075
         "swagger-parser": "4.0.2",
3076
-        "swagger-ui-dist": "^3.47.1"
3076
+        "swagger-ui-dist": "^4.5.0"
3077
       }
3077
       }
3078
     },
3078
     },
3079
     "has": {
3079
     "has": {
4538
       }
4538
       }
4539
     },
4539
     },
4540
     "objection": {
4540
     "objection": {
4541
-      "version": "2.2.15",
4542
-      "resolved": "https://registry.npmjs.org/objection/-/objection-2.2.15.tgz",
4543
-      "integrity": "sha512-ZOLJDigE9Z2ppk3C//S2fcWL6ph2jUe6Cwl0CEpslTZegnnOeG6RPIV80nhPAaghWjl/8F2kox/w89VVBN4ccg==",
4541
+      "version": "2.2.18",
4542
+      "resolved": "https://registry.npmjs.org/objection/-/objection-2.2.18.tgz",
4543
+      "integrity": "sha512-la7dWwKzpw+Sh4ROWWrZycq2k2z0IyYv3kb8rTPJPIRKuyV59DlSSctALPs5Feoi8I3RNxo3vnCuVoNcKgxthw==",
4544
       "requires": {
4544
       "requires": {
4545
         "ajv": "^6.12.6",
4545
         "ajv": "^6.12.6",
4546
         "db-errors": "^0.2.3"
4546
         "db-errors": "^0.2.3"
5848
       "integrity": "sha1-cAcEaNbSl3ylI3suUZyn0Gouo/0="
5848
       "integrity": "sha1-cAcEaNbSl3ylI3suUZyn0Gouo/0="
5849
     },
5849
     },
5850
     "swagger-ui-dist": {
5850
     "swagger-ui-dist": {
5851
-      "version": "3.49.0",
5852
-      "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.49.0.tgz",
5853
-      "integrity": "sha512-R1+eT16XNP1bBLfacISifZAkFJlpwvWsS2vVurF5pbIFZnmCasD/hj+9r/q7urYdQyb0B6v11mDnuYU7rUpfQg=="
5851
+      "version": "4.5.2",
5852
+      "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.5.2.tgz",
5853
+      "integrity": "sha512-wV4w54eW9z+VKbYJBJfULfqO05otCbM9jwgRIkwRl9CrfTVKelDzyhhEvdUQkGUzro+Ir8TOZPiZgKIdIdolWQ=="
5854
     },
5854
     },
5855
     "table": {
5855
     "table": {
5856
       "version": "6.7.1",
5856
       "version": "6.7.1",
6002
       }
6002
       }
6003
     },
6003
     },
6004
     "trim-off-newlines": {
6004
     "trim-off-newlines": {
6005
-      "version": "1.0.1",
6006
-      "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz",
6007
-      "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=",
6005
+      "version": "1.0.3",
6006
+      "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.3.tgz",
6007
+      "integrity": "sha512-kh6Tu6GbeSNMGfrrZh6Bb/4ZEHV1QlB4xNDBeog8Y9/QwFlKTRyWvY3Fs9tRDAMZliVUwieMgEdIeL/FtqjkJg==",
6008
       "dev": true
6008
       "dev": true
6009
     },
6009
     },
6010
     "type-check": {
6010
     "type-check": {
6038
       }
6038
       }
6039
     },
6039
     },
6040
     "uglify-js": {
6040
     "uglify-js": {
6041
-      "version": "3.13.7",
6042
-      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.7.tgz",
6043
-      "integrity": "sha512-1Psi2MmnZJbnEsgJJIlfnd7tFlJfitusmR7zDI8lXlFI0ACD4/Rm/xdrU8bh6zF0i74aiVoBtkRiFulkrmh3AA==",
6041
+      "version": "3.15.1",
6042
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.1.tgz",
6043
+      "integrity": "sha512-FAGKF12fWdkpvNJZENacOH0e/83eG6JyVQyanIJaBXCN1J11TUQv1T1/z8S+Z0CG0ZPk1nPcreF/c7lrTd0TEQ==",
6044
       "optional": true
6044
       "optional": true
6045
     },
6045
     },
6046
     "unc-path-regex": {
6046
     "unc-path-regex": {

+ 2
- 2
backend/package.json Vedi File

26
     "compute-cosine-similarity": "^1.0.0",
26
     "compute-cosine-similarity": "^1.0.0",
27
     "dotenv": "^10.0.0",
27
     "dotenv": "^10.0.0",
28
     "exiting": "^6.0.1",
28
     "exiting": "^6.0.1",
29
-    "hapi-swagger": "^14.1.3",
29
+    "hapi-swagger": "^14.2.5",
30
     "haversine": "^1.1.1",
30
     "haversine": "^1.1.1",
31
     "joi": "^17.4.0",
31
     "joi": "^17.4.0",
32
     "knex": "^0.21.19",
32
     "knex": "^0.21.19",
33
     "mysql": "^2.18.1",
33
     "mysql": "^2.18.1",
34
-    "objection": "^2.2.15",
34
+    "objection": "^2.2.18",
35
     "secure-password": "^4.0.0"
35
     "secure-password": "^4.0.0"
36
   },
36
   },
37
   "devDependencies": {
37
   "devDependencies": {

+ 16
- 5135
frontend/package-lock.json
File diff soppresso perché troppo grande
Vedi File


+ 21
- 1
frontend/src/utils/db.js Vedi File

72
     // async removeAll() { }
72
     // async removeAll() { }
73
 }
73
 }
74
 const db = new Connector()
74
 const db = new Connector()
75
-export { Connector, db }
75
+
76
+class Toaster {
77
+    constructor({ profileId }) {
78
+        this.url = `${remote}/notification/${profileId}/subscribe`
79
+        
80
+    }
81
+    connect() {
82
+        this.source = new EventSource(`${remote}/notification/${profileId}/subscribe`)
83
+        this.source.onmessage = e => {
84
+            console.log("Data", + e.data)
85
+        }
86
+        this.source.onerror = e => {
87
+            console.error("EventSource failed.", e)
88
+        }
89
+        this.source.addEventListener('end', e => {
90
+            this.close()
91
+        })
92
+    }
93
+}
94
+
95
+export { Connector, db, Toaster }

+ 17
- 1
frontend/src/views/home.vue Vedi File

12
 import mainNav from '../components/MainNav.vue'
12
 import mainNav from '../components/MainNav.vue'
13
 import profileCardList from '../components/ProfileCardList.vue'
13
 import profileCardList from '../components/ProfileCardList.vue'
14
 import { fetchQueueByProfileId } from '../services'
14
 import { fetchQueueByProfileId } from '../services'
15
+import { Toaster } from '../utils/db'
15
 
16
 
16
 import batch_10 from '../../../backend/db/generated/_batch_10.js.ref'
17
 import batch_10 from '../../../backend/db/generated/_batch_10.js.ref'
17
 import batch_20 from '../../../backend/db/generated/_batch_20.js.ref'
18
 import batch_20 from '../../../backend/db/generated/_batch_20.js.ref'
25
         user: null,
26
         user: null,
26
         mypid: null,
27
         mypid: null,
27
     }),
28
     }),
29
+    mounted() {
30
+        this.setupToaster()
31
+    },
28
     async created() {
32
     async created() {
29
         // this.mypid = auth.currentUser?.mypid || "99999";
33
         // this.mypid = auth.currentUser?.mypid || "99999";
30
         this.mypid = 38
34
         this.mypid = 38
33
 
37
 
34
         // Uncomment below to use API
38
         // Uncomment below to use API
35
         const queueList = await fetchQueueByProfileId(this.mypid)
39
         const queueList = await fetchQueueByProfileId(this.mypid)
36
-        console.log('queueList', queueList)
40
+        // console.log('queueList', queueList)
37
         this.processQueue(queueList)
41
         this.processQueue(queueList)
38
     },
42
     },
39
     methods: {
43
     methods: {
44
+        setupToaster() {
45
+            const stocks = {}
46
+            const source = new EventSource(`http://localhost:3001/api/notification/${this.mypid}/subscribe`)
47
+            source.addEventListener('stonk', function (message) {
48
+                console.log('updated:', message)
49
+                const data = JSON.parse(message.data)
50
+                stocks[data.name] = data
51
+            })
52
+            source.addEventListener('end', function (message) {
53
+                this.close()
54
+            })
55
+        },
40
         processQueue(queueList) {
56
         processQueue(queueList) {
41
             const formattedList = []
57
             const formattedList = []
42
             queueList.forEach(profile => {
58
             queueList.forEach(profile => {

Loading…
Annulla
Salva