Procházet zdrojové kódy

:recycle: probably too big of an update | pubnub chat | sse notifications | planetscale connection script | added some env vars

tags/0.0.1
J před 4 roky
rodič
revize
491cf88307

+ 7
- 1
backend/.env.sample Zobrazit soubor

@@ -7,8 +7,14 @@ API_HOST=localhost
7 7
 API_PORT=3001
8 8
 APP_SECRET=mysecret
9 9
 
10
+USE_LOCAL_DB=true
11
+
10 12
 DB_ROOT_PASSWORD=root
11 13
 DB_USER=root
12 14
 DB_NAME=test
13 15
 DB_HOST=localhost
14
-DB_TYPE=mysql
16
+DB_TYPE=mysql
17
+DB_PORT=3306
18
+
19
+PSCALE_DB_BRANCH=main
20
+PSCALE_DB_NAME=planet-scale-db

+ 18
- 7
backend/knexfile.js Zobrazit soubor

@@ -1,15 +1,26 @@
1 1
 require('dotenv').config()
2 2
 
3
+const local = {
4
+    host: process.env.DB_HOST,
5
+    user: process.env.DB_USER,
6
+    password: process.env.DB_ROOT_PASSWORD,
7
+    database: process.env.DB_NAME,
8
+    port: process.env.DB_PORT
9
+}
10
+const pscale = {
11
+    host: process.env.PSCALE_DB_HOST ? process.env.PSCALE_DB_HOST : '127.0.0.1',
12
+    user: process.env.PSCALE_DB_USER ? process.env.PSCALE_DB_USER : 'root',
13
+    password: process.env.PSCALE_DB_PASSWORD ? process.env.PSCALE_DB_PASSWORD : '', 
14
+    database: process.env.PSCALE_DB_NAME,
15
+    port: process.env.PSCALE_DB_PORT ? process.env.PSCALE_DB_PORT : 3306
16
+}
17
+
18
+const connectionSettings = process.env.USE_LOCAL_DB == true ? local : pscale
19
+
3 20
 module.exports = {
4 21
     development: {
5 22
         client: process.env.DB_TYPE,
6
-        connection: {
7
-            host: process.env.DB_HOST,
8
-            user: process.env.DB_USER,
9
-            password: process.env.DB_ROOT_PASSWORD,
10
-            database: process.env.DB_NAME,
11
-            port: 3307
12
-        },
23
+        connection: connectionSettings,
13 24
         pool: {
14 25
             min: 2,
15 26
             max: 10

+ 1
- 65
backend/lib/plugins/notification.js Zobrazit soubor

@@ -1,69 +1,5 @@
1
-const Stream = require('stream')
2
-const PassThrough = Stream.PassThrough
3
-const Transform = Stream.Transform
4 1
 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
-}
2
+const { onEvent } = require('../services/notification')
67 3
 
68 4
 module.exports = {
69 5
     name: 'notification-plugin',

+ 118
- 0
backend/lib/services/notification.js Zobrazit soubor

@@ -0,0 +1,118 @@
1
+/** Heavily lifted from: https://github.com/mtharrison/susie/blob/master/lib/index.js */
2
+
3
+const Stream = require('stream')
4
+const PassThrough = Stream.PassThrough
5
+const Transform = Stream.Transform
6
+
7
+const ENDER = { event: 'end', data: '' }
8
+
9
+/**
10
+ * Stringify a stream
11
+ * ?: I don't really get what this is doing
12
+ * @param {Stream} event
13
+ * @returns {string}
14
+ */
15
+const stringifyEvent = function (event) {
16
+    let str = ''
17
+    const endl = '\r\n'
18
+    for (const i in event) {
19
+        let val = event[i]
20
+        if (val instanceof Buffer) { val = val.toString() }
21
+        if (typeof val === 'object') { val = JSON.stringify(val) }
22
+        str += i + ': ' + val + endl
23
+    }
24
+    str += endl
25
+    return str
26
+}
27
+
28
+/**
29
+ * Transform extension
30
+ * ?: I don't really get what this is doing
31
+ * @param {object} options
32
+ * @param {object} objectMode 
33
+ */
34
+class Transformer extends Transform {
35
+    constructor(options, objectMode) {
36
+        super({ objectMode })
37
+        options = options || {}
38
+        this.counter = 1
39
+        this.event = options.event || null
40
+        this.generateId = options.generateId ? options.generateId : () => {
41
+            return this.counter++
42
+        }
43
+    }
44
+    _transform (chunk, encoding, callback) {
45
+        const event = {
46
+            id: this.generateId(chunk),
47
+            data: chunk
48
+        }
49
+        if (this.event) { event.event = this.event }
50
+        this.push(stringifyEvent(event))
51
+        callback()
52
+    }
53
+    _flush(callback) {
54
+        this.push(stringifyEvent(ENDER))
55
+        callback()
56
+    }
57
+} 
58
+
59
+/**
60
+ * Take an event stream and write content to another stream
61
+ * ?: Save this for future extension
62
+ * @param {Stream} event stream input
63
+ * @param {Stream} stream to write to
64
+ */
65
+const writeEvent = function (event, stream) {
66
+    if (event) {
67
+        stream.write(stringifyEvent(event))
68
+    } else {
69
+        // closing time
70
+        stream.write(stringifyEvent(ENDER))
71
+        stream.end()
72
+    }
73
+}
74
+
75
+/**
76
+ * Callback to decorate server toolkit (h)
77
+ * !: Currently we only support ObjectMode streams
78
+ * ?: I don't really get what this is doing
79
+ * @param {Stream} event stream input
80
+ * @param {Toolkit} h hapi common response toolkit
81
+ * @param {object} streamOptions
82
+ */
83
+const onEvent = (event, h, streamOptions) => {
84
+    // const state = h.request.plugins.notifications = h.request.plugins.notifications || {}
85
+    let active
86
+    if (event instanceof Stream.Readable) {
87
+        if (event._readableState.objectMode) {
88
+            const through = new Transformer(streamOptions, true)
89
+            active = new PassThrough()
90
+            through.pipe(active)
91
+            event.pipe(through)
92
+        }
93
+        // else {
94
+        //     stream = new Transformer(streamOptions, false)
95
+        //     event.pipe(stream)
96
+        // }
97
+        return h.response(active)
98
+            .header('content-type', 'text/event-stream')
99
+            .header('content-encoding', 'identity')
100
+    }
101
+    // Uncomment to do stream state stuff
102
+    // handle a first object arg
103
+    // if (!state.stream) {
104
+    //     active = new PassThrough()
105
+    //     state.stream = active
106
+    //     state.mode = 'object'
107
+    //     const response = h.response(active)
108
+    //         .header('content-type', 'text/event-stream')
109
+    //         .header('content-encoding', 'identity')
110
+    //     writeEvent(event, active)
111
+    //     return response
112
+    // }
113
+    // already have an object stream flowing, just write next event
114
+    // active = state.stream
115
+    // internals.writeEvent(event, active)
116
+}
117
+
118
+module.exports = { onEvent }

+ 2
- 1
backend/package.json Zobrazit soubor

@@ -5,7 +5,8 @@
5 5
   "main": "index.js",
6 6
   "scripts": {
7 7
     "start": "nodemon server",
8
-    "migrate": "knex migjrate:latest",
8
+    "connect": "USE_LOCAL_DB=false pscale connect $(grep '^PSCALE_DB_NAME' .env | cut -d '=' -f2) $(grep '^PSCALE_DB_BRANCH' .env | cut -d '=' -f2)",
9
+    "migrate": "knex migrate:latest",
9 10
     "unmigrate": "knex migrate:down",
10 11
     "reseed": "knex migrate:rollback --all && knex migrate:latest && knex seed:run",
11 12
     "generate": "node ./db/data-generator/index.js",

+ 3
- 0
frontend/.env.sample Zobrazit soubor

@@ -0,0 +1,3 @@
1
+VITE_PUBNUB_PUBLISH_KEY=pub-a-123467890-123467890
2
+VITE_PUBNUB_SUBSCRIBE_KEY=sub-a-123467890-123467890
3
+VITE_UUID_SECRET=00000000-0000-1111-0000-000000000000

+ 607
- 32
frontend/package-lock.json
Diff nebyl zobrazen, protože je příliš veliký
Zobrazit soubor


+ 1
- 1
frontend/src/App.vue Zobrazit soubor

@@ -6,7 +6,7 @@ router-view
6 6
 import * as sss from '@/sss/import.css'
7 7
 
8 8
 export default {
9
-    name: 'app',
9
+    name: 'app'
10 10
 }
11 11
 </script>
12 12
 

+ 93
- 0
frontend/src/services/chat.service.js Zobrazit soubor

@@ -0,0 +1,93 @@
1
+import PubNub from 'pubnub'
2
+
3
+/** 
4
+ * Provider method holder
5
+ * We always reference this object so
6
+ * we don't have to hardcode provider specific
7
+ * methods for doing chat things.
8
+ * 
9
+ * This gets overloaded later in the program
10
+ */
11
+const providerMethods = {
12
+    publish: () => console.error('no provider publish method set'),
13
+    subscribe: () => console.error('no provider subscribe method set'),
14
+    listen: () => console.error('no provider listen method set')
15
+}
16
+
17
+/** 
18
+ * Breaking out as much pubnub specific flavor
19
+ */
20
+const setupPubnub = async uuid => {
21
+    if(!uuid) return console.error('no pubnub uuid set')
22
+    
23
+    const pubnubClient = await new PubNub({
24
+        publishKey: import.meta.env.VITE_PUBNUB_PUBLISH_KEY,
25
+        subscribeKey: import.meta.env.VITE_PUBNUB_SUBSCRIBE_KEY,
26
+        uuid
27
+    })
28
+    // Pass pubnub specific methods to our placeholder obj
29
+    providerMethods['publish'] = pubnubClient.publish
30
+    providerMethods['subscribe'] = pubnubClient.subscribe
31
+    providerMethods['listen'] = pubnubClient.addListener
32
+
33
+    return pubnubClient
34
+}
35
+
36
+
37
+class ChatMessage {
38
+    constructor({ title, description }) {
39
+        this.title = title,
40
+        this.description = description
41
+    }
42
+}
43
+
44
+const testMessage = new ChatMessage({
45
+    title: "testing",
46
+    description: "hello world!",
47
+})
48
+const MAIN_CHANNEL = 'hello_world'
49
+
50
+class Chatter {
51
+    constructor() {
52
+        this.groupings = {}
53
+        this.provider = null
54
+        this.uuid = null
55
+
56
+        // Setup the main channel
57
+        this.subscriptions = [MAIN_CHANNEL]
58
+        
59
+        this.listeners = {
60
+            status: async e => {
61
+                if (e.category !== "PNConnectedCategory") return
62
+                await this._publish(this.subscriptions[0], testMessage)
63
+            },
64
+            message: this._onMessage,
65
+            presence: this._onPresence
66
+        }
67
+    }
68
+    async _onMessage(e) {
69
+        console.log(e.message.title)
70
+        console.log(e.message.description)
71
+    }
72
+    async _onPresence(e) {
73
+        return
74
+    }
75
+    async setup(uuid) {
76
+        this.uuid = uuid
77
+        this.provider = await setupPubnub(this.uuid)
78
+
79
+        this._listenFor({ listeners: this.listeners })
80
+        this._subscribe(this.subscriptions)
81
+    }
82
+    async _publish(channel, message) {
83
+        return await providerMethods['publish']({ channel, message })
84
+    }
85
+    _subscribe(channels) {
86
+        providerMethods['subscribe']({ channels })
87
+    }
88
+    _listenFor({ listeners }) {
89
+        providerMethods['listen'](listeners)
90
+    }
91
+}
92
+
93
+export { Chatter }

+ 28
- 0
frontend/src/services/notification.service.js Zobrazit soubor

@@ -0,0 +1,28 @@
1
+import { remote } from '../utils/db'
2
+
3
+class Toaster {
4
+    constructor(profileId) {
5
+        this.url = `${remote}/notification/${profileId}/subscribe`
6
+        this.source = null
7
+        this.source = new EventSource(this.url)
8
+        this.listenFor('end', message => this.source.close)
9
+    }
10
+    listenFor(event, callback) {
11
+        this.source.addEventListener(event, callback)
12
+    }
13
+}
14
+
15
+class StonkAlert extends Toaster {
16
+    constructor(profileId) {
17
+        super(profileId)
18
+
19
+        this.stonks = {}
20
+        this.listenFor('stonk', message => {
21
+            const parsed = JSON.parse(message.data)
22
+            this.stonks[parsed.name] = parsed
23
+            console.log('updated:', this.stonks)
24
+        })
25
+    }
26
+}
27
+
28
+export { Toaster, StonkAlert }

+ 7
- 2
frontend/src/services/queue.service.js Zobrazit soubor

@@ -6,9 +6,14 @@ const fetchQueueByProfileId = async profileId => {
6 6
     const queue = await db.get(
7 7
         `/profile/${profileId}/queue?include_profile=true`,
8 8
     )
9
-    return queue.map(profileData => {
9
+    
10
+    if(!queue?.length) {
11
+        console.error('could not retrieve match queue. Please take the survey and rescore.')
12
+    }
13
+
14
+    return queue ? queue.map(profileData => {
10 15
         return new Profile({ email: null, ...profileData })
11
-    })
16
+    }) : []
12 17
 }
13 18
 
14 19
 const updateQueueByProfileId = async (profileId, targetId, reinsert) => {

+ 2
- 21
frontend/src/utils/db.js Zobrazit soubor

@@ -26,7 +26,7 @@ class Connector {
26 26
         const header = { ...headerTemplate }
27 27
         header.method = 'GET'
28 28
         try {
29
-            console.log(`${remote}${endpoint}`)
29
+            // console.log(`${remote}${endpoint}`)
30 30
             let res = await fetch(`${remote}${endpoint}`, header)
31 31
             if (!res.ok) {
32 32
                 throw Error(res.statusText)
@@ -73,23 +73,4 @@ class Connector {
73 73
 }
74 74
 const db = new Connector()
75 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 }
76
+export { Connector, db, remote }

+ 11
- 11
frontend/src/views/home.vue Zobrazit soubor

@@ -13,7 +13,8 @@ import sidebar from '../components/Sidebar.vue'
13 13
 import mainNav from '../components/MainNav.vue'
14 14
 import profileCardList from '../components/ProfileCardList.vue'
15 15
 import { fetchQueueByProfileId } from '../services'
16
-import { Toaster } from '../utils/db'
16
+import { StonkAlert } from '../services/notification.service'
17
+import { Chatter } from '../services/chat.service'
17 18
 
18 19
 import batch_10 from '../../../backend/db/generated/_batch_10.js.ref'
19 20
 import batch_20 from '../../../backend/db/generated/_batch_20.js.ref'
@@ -29,6 +30,7 @@ export default {
29 30
         loading: true
30 31
     }),
31 32
     mounted() {
33
+        this.setupChatter()
32 34
         this.setupToaster()
33 35
     },
34 36
     async created() {
@@ -54,16 +56,14 @@ export default {
54 56
             this.loading = false
55 57
         },
56 58
         setupToaster() {
57
-            const stocks = {}
58
-            const source = new EventSource(`http://localhost:3001/api/notification/${this.mypid}/subscribe`)
59
-            source.addEventListener('stonk', function (message) {
60
-                console.log('updated:', message)
61
-                const data = JSON.parse(message.data)
62
-                stocks[data.name] = data
63
-            })
64
-            source.addEventListener('end', function (message) {
65
-                this.close()
66
-            })
59
+            const t = new StonkAlert(this.mypid)
60
+        },
61
+        setupChatter() {
62
+            const c = new Chatter()
63
+            const testAccountUUID = import.meta.env.VITE_TEST_ACCOUNT_UUID
64
+            c.setup(testAccountUUID)
65
+            console.log('---')
66
+            console.log(c)
67 67
         },
68 68
         processQueue(queueList) {
69 69
             const formattedList = []

Načítá se…
Zrušit
Uložit