Bläddra i källkod

Merge branch 'match-noti' of fyindr/siimee into dev

tags/0.0.1^2
maeda 3 år sedan
förälder
incheckning
d26721c13a

+ 69
- 15
backend/db/data-generator/mock.js Visa fil

@@ -100,13 +100,15 @@ module.exports = {
100 100
         {
101 101
             response_key_id: 2,
102 102
             response_key_category: 'openness',
103
-            response_key_prompt: 'are you open with your emotions with everyone',
103
+            response_key_prompt:
104
+                'are you open with your emotions with everyone',
104 105
             response_key_description: 'first round draft scoring question',
105 106
         },
106 107
         {
107 108
             response_key_id: 3,
108 109
             response_key_category: 'bravery',
109
-            response_key_prompt: 'do you speak-up when you feel something is wrong',
110
+            response_key_prompt:
111
+                'do you speak-up when you feel something is wrong',
110 112
             response_key_description: 'first round draft scoring question',
111 113
         },
112 114
         {
@@ -119,13 +121,15 @@ module.exports = {
119 121
         {
120 122
             response_key_id: 5,
121 123
             response_key_category: 'honesty',
122
-            response_key_prompt: 'when telling a story do you exaggerate for dramatic effect',
124
+            response_key_prompt:
125
+                'when telling a story do you exaggerate for dramatic effect',
123 126
             response_key_description: 'first round draft scoring question',
124 127
         },
125 128
         {
126 129
             response_key_id: 6,
127 130
             response_key_category: 'respect',
128
-            response_key_prompt: 'do you treat difficult people as well as you treat your close friends',
131
+            response_key_prompt:
132
+                'do you treat difficult people as well as you treat your close friends',
129 133
             response_key_description: 'first round draft scoring question',
130 134
         },
131 135
         {
@@ -144,19 +148,22 @@ module.exports = {
144 148
             response_key_id: 9,
145 149
             response_key_category: 'profile',
146 150
             response_key_prompt: 'language',
147
-            response_key_description: 'programming and spoken language preference',
151
+            response_key_description:
152
+                'programming and spoken language preference',
148 153
         },
149 154
         {
150 155
             response_key_id: 10,
151 156
             response_key_category: 'profile',
152 157
             response_key_prompt: 'duration',
153
-            response_key_description: 'duration preference for hours able to dedicate to work',
158
+            response_key_description:
159
+                'duration preference for hours able to dedicate to work',
154 160
         },
155 161
         {
156 162
             response_key_id: 11,
157 163
             response_key_category: 'profile',
158 164
             response_key_prompt: 'presence',
159
-            response_key_description: 'location preference for where work happens',
165
+            response_key_description:
166
+                'location preference for where work happens',
160 167
         },
161 168
         {
162 169
             response_key_id: 12,
@@ -186,18 +193,65 @@ module.exports = {
186 193
             response_key_id: 16,
187 194
             response_key_category: 'profile',
188 195
             response_key_prompt: 'distance',
189
-            response_key_description: 'preference for commuting distance cutoff',
196
+            response_key_description:
197
+                'preference for commuting distance cutoff',
190 198
         },
191 199
     ],
192 200
     responses: [],
193
-    memberships: [
194
-       
195
-    ],
196
-    groupings: [
197
-       
198
-    ],
201
+    memberships: [],
202
+    groupings: [],
199 203
     messages: [],
200 204
     match_queues: [
201
-        
205
+        { match_queue_id: 1, profile_id: 45, target_id: 62, is_deleted: false },
206
+        { match_queue_id: 2, profile_id: 62, target_id: 45, is_deleted: false },
207
+        { match_queue_id: 3, profile_id: 62, target_id: 46, is_deleted: false },
208
+        { match_queue_id: 4, profile_id: 46, target_id: 62, is_deleted: false },
209
+        { match_queue_id: 5, profile_id: 45, target_id: 46, is_deleted: false },
210
+        { match_queue_id: 6, profile_id: 46, target_id: 45, is_deleted: false },
211
+        { match_queue_id: 7, profile_id: 46, target_id: 44, is_deleted: false },
212
+        { match_queue_id: 8, profile_id: 46, target_id: 43, is_deleted: false },
213
+        { match_queue_id: 9, profile_id: 46, target_id: 42, is_deleted: false },
214
+        {
215
+            match_queue_id: 10,
216
+            profile_id: 46,
217
+            target_id: 41,
218
+            is_deleted: false,
219
+        },
220
+        {
221
+            match_queue_id: 11,
222
+            profile_id: 46,
223
+            target_id: 40,
224
+            is_deleted: false,
225
+        },
226
+        {
227
+            match_queue_id: 12,
228
+            profile_id: 40,
229
+            target_id: 46,
230
+            is_deleted: false,
231
+        },
232
+        {
233
+            match_queue_id: 13,
234
+            profile_id: 41,
235
+            target_id: 46,
236
+            is_deleted: false,
237
+        },
238
+        {
239
+            match_queue_id: 14,
240
+            profile_id: 42,
241
+            target_id: 46,
242
+            is_deleted: false,
243
+        },
244
+        {
245
+            match_queue_id: 15,
246
+            profile_id: 43,
247
+            target_id: 46,
248
+            is_deleted: false,
249
+        },
250
+        {
251
+            match_queue_id: 16,
252
+            profile_id: 44,
253
+            target_id: 46,
254
+            is_deleted: false,
255
+        },
202 256
     ],
203 257
 }

+ 111
- 3
backend/lib/plugins/notification.js Visa fil

@@ -1,11 +1,119 @@
1
+const _allStreams = {}
2
+
1 3
 const NotificationRoute = require('../routes/notification')
2
-const { onEvent } = require('../services/notification')
4
+
5
+/** Heavily lifted from: https://github.com/mtharrison/susie/blob/master/lib/index.js */
6
+
7
+const Stream = require('stream')
8
+const PassThrough = Stream.PassThrough
9
+const Transform = Stream.Transform
10
+
11
+const ENDER = { event: 'end', data: '' }
12
+
13
+/**
14
+ * Stringify a stream
15
+ * ?: I don't really get what this is doing
16
+ * @param {Stream} event
17
+ * @returns {string}
18
+ */
19
+const _stringifyEvent = function (event) {
20
+    let str = ''
21
+    const endl = '\r\n'
22
+    for (const i in event) {
23
+        let val = event[i]
24
+        if (val instanceof Buffer) {
25
+            val = val.toString()
26
+        }
27
+        if (typeof val === 'object') {
28
+            val = JSON.stringify(val)
29
+        }
30
+        str += i + ': ' + val + endl
31
+    }
32
+    str += endl
33
+    return str
34
+}
35
+
36
+/**
37
+ * Transform extension
38
+ * ?: I don't really get what this is doing
39
+ * @param {object} options
40
+ * @param {object} objectMode
41
+ */
42
+class Transformer extends Transform {
43
+    constructor(options, objectMode) {
44
+        super({ objectMode })
45
+        options = options || {}
46
+        this.counter = 1
47
+        this.event = options.event || null
48
+        this.generateId = options.generateId
49
+            ? options.generateId
50
+            : () => this.counter++
51
+    }
52
+    _transform(chunk, encoding, callback) {
53
+        const event = {
54
+            id: this.generateId(chunk),
55
+            data: chunk,
56
+        }
57
+        if (this.event) {
58
+            event.event = this.event
59
+        }
60
+        this.push(_stringifyEvent(event))
61
+        callback()
62
+    }
63
+    _flush(callback) {
64
+        this.push(_stringifyEvent(ENDER))
65
+        callback()
66
+    }
67
+}
68
+
69
+/**
70
+ * Callback to decorate server toolkit (h)
71
+ * !: Currently we only support ObjectMode streams
72
+ * ?: I don't really get what this is doing
73
+ * @param {Stream} event stream input
74
+ * @param {Toolkit} h hapi common response toolkit
75
+ * @param {object} streamOptions
76
+ */
77
+const _event = (event, h, streamOptions) => {
78
+    let active
79
+    if (event instanceof Stream.Readable) {
80
+        if (event._readableState.objectMode) {
81
+            active = new PassThrough()
82
+            const through = new Transformer(streamOptions, true)
83
+            through.pipe(active)
84
+            event.pipe(through)
85
+        }
86
+        return h
87
+            .response(active)
88
+            .header('content-type', 'text/event-stream')
89
+            .header('content-encoding', 'identity')
90
+    }
91
+}
92
+
93
+/**
94
+ * Takes an open HTTP stream and writes
95
+ * a msg to it, then fires notification plugin's
96
+ * _event callback
97
+ * @param {object} msg you want to send
98
+ * @param {string} name <profileId>.<eventType>
99
+ * @param {boolean} shouldInitialize
100
+ * @param {Toolkit} h hapi common response toolkit
101
+ */
102
+const onNotify = (name, msg, h, shouldInitialize = false) => {
103
+    if (shouldInitialize) {
104
+        _allStreams[name] = new PassThrough({ objectMode: true })
105
+    }
106
+    if (!_allStreams[name]) return
107
+    _allStreams[name].write(msg)
108
+
109
+    return _event(_allStreams[name], h, { event: name })
110
+}
3 111
 
4 112
 module.exports = {
5 113
     name: 'notification-plugin',
6 114
     version: '1.0.0',
7
-    register: async (server, options) => {
115
+    register: async server => {
8 116
         await server.route(NotificationRoute)
9
-        server.decorate('toolkit', 'event', onEvent)
117
+        server.method('notify', onNotify)
10 118
     },
11 119
 }

+ 1
- 1
backend/lib/routes/membership/active.js Visa fil

@@ -53,7 +53,7 @@ module.exports = {
53 53
                 profileId,
54 54
                 membershipType,
55 55
             )
56
-
56
+            console.log('groupings :>> ', groupings)
57 57
             /**
58 58
              * Heavily process the result by storing just a profile_id
59 59
              * and attach complete profiles

+ 31
- 9
backend/lib/routes/membership/join.js Visa fil

@@ -27,6 +27,7 @@ const responseSchemas = {
27 27
     response: Joi.object({
28 28
         memberships: Joi.array().items(),
29 29
         hasMatch: Joi.boolean(),
30
+        groupings: Joi.array().items(),
30 31
     }).label('grouping_membership_list'),
31 32
     error: errorSchema.single,
32 33
 }
@@ -48,6 +49,7 @@ module.exports = {
48 49
          */
49 50
         handler: async function (request, h) {
50 51
             try {
52
+                console.log('---')
51 53
                 const { membershipService } = request.server.services()
52 54
 
53 55
                 /** Grab payload info */
@@ -67,23 +69,43 @@ module.exports = {
67 69
                 // !: You should only be associated with a single company too
68 70
 
69 71
                 /** User membership service method to create membership */
70
-                const memberships = await membershipService.joinGrouping(
71
-                    profileId,
72
-                    res.target_id,
73
-                    groupingToWrite,
74
-                    role,
72
+                const { memberships, groupings } =
73
+                    await membershipService.joinGrouping(
74
+                        profileId,
75
+                        res.target_id,
76
+                        groupingToWrite,
77
+                        role,
78
+                    )
79
+                const hasMatch = memberships.every(
80
+                    membership => membership && membership.is_active == true,
75 81
                 )
76
-                // console.log(memberships)
77 82
 
83
+                if (hasMatch) {
84
+                    request.server.methods.notify(
85
+                        `${profileId}.stonk`,
86
+                        {
87
+                            name: `${res.target_id} Match Fffound`,
88
+                            type: 'info',
89
+                        },
90
+                        h,
91
+                    )
92
+                    request.server.methods.notify(
93
+                        `${res.target_id}.stonk`,
94
+                        {
95
+                            name: `${profileId} Match Fffound`,
96
+                            type: 'info',
97
+                        },
98
+                        h,
99
+                    )
100
+                }
78 101
                 return h
79 102
                     .response({
80 103
                         ok: true,
81 104
                         handler: pluginConfig.handlerType,
82 105
                         data: {
83 106
                             memberships,
84
-                            hasMatch: memberships.every(
85
-                                membership => membership.is_active == true,
86
-                            ),
107
+                            hasMatch,
108
+                            groupings,
87 109
                         },
88 110
                     })
89 111
                     .code(200)

+ 16
- 23
backend/lib/routes/notification/index.js Visa fil

@@ -3,9 +3,6 @@ const apiSchema = require('../../schemas/api')
3 3
 const errorSchema = require('../../schemas/errors')
4 4
 const params = require('../../schemas/params')
5 5
 
6
-const Stream = require('stream')
7
-const PassThrough = require('stream').PassThrough
8
-
9 6
 const pluginConfig = {
10 7
     handlerType: 'notifictaion',
11 8
     docs: {
@@ -28,27 +25,23 @@ module.exports = {
28 25
         cors: true,
29 26
         handler: async (request, h) => {
30 27
             const { profile_id } = request.params
31
-            const input = new PassThrough({ objectMode: true })
32
-            const eventType = 'stonk'
33
-
34
-            const msg = {
35
-                profile_id,
36
-                name: 'BDGRS',
37
-                price: (500 + Math.floor(Math.random() * 100)).toString(),
38
-                order: null,
39
-                type: 'info',
40
-            }
41
-
42
-            // Write to the input stream
43
-            setInterval(() => {
44
-                msg.order = Math.floor(Math.random() * 2) === 1 ? 'BUY' : 'SELL'
45
-                input.write(msg)
46
-            }, 5000)
47 28
 
48
-            // h.event() Added at plugin registration
49
-            // h is the toolkit
50
-            const streamOptions = { event: `${profile_id}.${eventType}` }
51
-            return h.event(input, h, streamOptions)
29
+            /**
30
+             * Write the initial stream
31
+             * !: this must remain open for notifications to work
32
+             */
33
+            return request.server.methods.notify(
34
+                `${profile_id}.stonk`,
35
+                {
36
+                    profile_id,
37
+                    name: 'BDGRS',
38
+                    price: (500 + Math.floor(Math.random() * 100)).toString(),
39
+                    order: Math.floor(Math.random() * 2) === 1 ? 'BUY' : 'SELL',
40
+                    type: 'info',
41
+                },
42
+                h,
43
+                true,
44
+            )
52 45
         },
53 46
 
54 47
         /** Validate based on validators object */

+ 10
- 5
backend/lib/routes/profile/patch-queue.js Visa fil

@@ -16,7 +16,10 @@ const pluginConfig = {
16 16
 
17 17
 const responseSchemas = {
18 18
     response: Joi.array().items(
19
-        Joi.alternatives().try(Joi.number(), profileSchema.single),
19
+        Joi.alternatives().try(
20
+            Joi.number().optional(),
21
+            profileSchema.single.optional(),
22
+        ),
20 23
     ),
21 24
     error: errorSchema.single,
22 25
 }
@@ -47,14 +50,16 @@ module.exports = {
47 50
                 reinsert,
48 51
             )
49 52
             const queueIds = updatedQueue.map(entry => entry.target_id)
53
+
50 54
             const res = {
51 55
                 ok: true,
52 56
                 handler: pluginConfig.handlerType,
53
-                data: queueIds,
54
-            }
55
-            if (include_profile) {
56
-                res.data = await profileService.getProfilesFor(queueIds)
57
+                data:
58
+                    include_profile == true
59
+                        ? await profileService.getProfilesFor(queueIds)
60
+                        : queueIds,
57 61
             }
62
+
58 63
             try {
59 64
                 return h.response(res).code(200)
60 65
             } catch (err) {

+ 2
- 2
backend/lib/schemas/responses.js Visa fil

@@ -6,7 +6,7 @@ const singleResponse = Joi.object({
6 6
     response_key_id: Joi.number(),
7 7
     response_id: Joi.number(),
8 8
     profile_id: Joi.number(),
9
-    val: Joi.string(),
9
+    val: Joi.string().allow(null, ''),
10 10
 }).label('response_single')
11 11
 
12 12
 const singleResponseKey = Joi.object({
@@ -20,5 +20,5 @@ module.exports = {
20 20
     single: singleResponse,
21 21
     list: Joi.array().items(singleResponse).label('response_list'),
22 22
     key: singleResponseKey,
23
-    keys: Joi.array().items(singleResponseKey).label('question_list')
23
+    keys: Joi.array().items(singleResponseKey).label('question_list'),
24 24
 }

+ 3
- 2
backend/lib/services/matchqueue.js Visa fil

@@ -74,12 +74,13 @@ module.exports = class MatchQueueService extends Schmervice.Service {
74 74
      */
75 75
     async markAsDeleted(profileId, targetId, reinsert) {
76 76
         const { MatchQueue } = this.server.models()
77
+        /** Always set row to deleted */
77 78
         await MatchQueue.query()
79
+            .where('profile_id', profileId)
80
+            .andWhere('target_id', targetId)
78 81
             .patch({
79 82
                 is_deleted: true,
80 83
             })
81
-            .where('profile_id', profileId)
82
-            .andWhere('target_id', targetId)
83 84
             .first()
84 85
 
85 86
         if (reinsert) {

+ 27
- 42
backend/lib/services/membership.js Visa fil

@@ -10,32 +10,14 @@ module.exports = class MembershipService extends Schmervice.Service {
10 10
      * @param {number} profileId
11 11
      * @returns {Array} List of all grouping_ids for user
12 12
      */
13
-    async _getGroupingIdsForProfileId(profileId, type, active) {
13
+    async _getGroupingIdsForProfileId(profileId) {
14 14
         const { Membership } = this.server.models()
15 15
 
16 16
         /** Grab every Membership associated with this id */
17
-        let allMemberships = []
18
-
19
-        if (type && active == 'any') {
20
-            allMemberships = await Membership.query()
21
-                .where({ profile_id: profileId })
22
-                .where({ membership_type: type })
23
-        } else if (type) {
24
-            allMemberships = await Membership.query()
25
-                .where({ profile_id: profileId })
26
-                .where({ membership_type: type })
27
-                .where({ is_active: true })
28
-        } else if (active == 'any') {
29
-            allMemberships = await Membership.query().where({
30
-                profile_id: profileId,
31
-            })
32
-        } else {
33
-            allMemberships = await Membership.query()
34
-                .where({ profile_id: profileId })
35
-                .where({ is_active: true })
36
-        }
37
-
38
-        /** Copy a list of the just the Groupings */
17
+        const allMemberships = await Membership.query().where({
18
+            profile_id: profileId,
19
+        })
20
+        /** Copy a list of the just the Grouping ids */
39 21
         const groupingIdsToGrab = allMemberships.map(
40 22
             membership => membership.grouping_id,
41 23
         )
@@ -43,7 +25,10 @@ module.exports = class MembershipService extends Schmervice.Service {
43 25
         /** Uncomment to dedupe the list just in case */
44 26
         return [...new Set(groupingIdsToGrab)]
45 27
     }
46
-
28
+    async _getGroupings(groupingIds, txn) {
29
+        const { Grouping } = this.server.models()
30
+        return await Grouping.query(txn).whereIn('grouping_id', groupingIds)
31
+    }
47 32
     /**
48 33
      * Internal method to create a new grouping
49 34
      * @param {object} groupingToTry from payload data
@@ -81,13 +66,11 @@ module.exports = class MembershipService extends Schmervice.Service {
81 66
      */
82 67
     async findGroupingsByProfileId(profileId, type) {
83 68
         const { Grouping } = this.server.models()
84
-
85 69
         const dedupedGroupings = await this._getGroupingIdsForProfileId(
86 70
             profileId,
87 71
             type,
88
-            'any',
89 72
         )
90
-
73
+        console.log('dedupedGroupings :>> ', dedupedGroupings)
91 74
         /** Grab just the Groupings this id has a Membership for */
92 75
         return await Grouping.query()
93 76
             .whereIn('grouping_id', dedupedGroupings)
@@ -95,18 +78,15 @@ module.exports = class MembershipService extends Schmervice.Service {
95 78
     }
96 79
 
97 80
     async _groupingIdsInCommon(profileId, targetId) {
98
-        const dedupedUserGroupingIds = await this._getGroupingIdsForProfileId(
99
-            profileId,
100
-        )
101
-        const dedupedTargetGroupingIds = await this._getGroupingIdsForProfileId(
102
-            targetId,
103
-        )
104
-
105
-        /** Return true if both people have a group in common */
106
-        return dedupedUserGroupingIds.filter(groupingId =>
107
-            dedupedTargetGroupingIds.includes(groupingId),
108
-        )
81
+        const uids = await this._getGroupingIdsForProfileId(profileId)
82
+        const tids = await this._getGroupingIdsForProfileId(targetId)
83
+        const common = []
84
+        for (let i in uids) {
85
+            if (tids.indexOf(uids[i]) !== -1) common.push(uids[i])
86
+        }
87
+        return common.sort((x, y) => x - y)
109 88
     }
89
+
110 90
     async _patchMembership(memberships, profileId, patch) {
111 91
         const { Membership } = this.server.models()
112 92
 
@@ -114,7 +94,7 @@ module.exports = class MembershipService extends Schmervice.Service {
114 94
         for (let membershipInfo of memberships) {
115 95
             await Membership.query()
116 96
                 .where('membership_id', membershipInfo.membership_id)
117
-                .where('user_id', profileId)
97
+                .where('profile_id', profileId)
118 98
                 .patch(patch)
119 99
         }
120 100
     }
@@ -139,7 +119,7 @@ module.exports = class MembershipService extends Schmervice.Service {
139 119
 
140 120
         if (matchingGroupingIds.length) {
141 121
             /** Grab all memberships associated with groupingIds */
142
-            const memberships = await Membership.query().whereIn(
122
+            let memberships = await Membership.query().whereIn(
143 123
                 'grouping_id',
144 124
                 matchingGroupingIds,
145 125
             )
@@ -150,10 +130,12 @@ module.exports = class MembershipService extends Schmervice.Service {
150 130
             })
151 131
 
152 132
             /** Make a new query to get updated information */
153
-            return await Membership.query().whereIn(
133
+            memberships = await Membership.query().whereIn(
154 134
                 'grouping_id',
155 135
                 matchingGroupingIds,
156 136
             )
137
+            const groupings = await this._getGroupings(matchingGroupingIds, txn)
138
+            return { memberships, groupings }
157 139
         } else {
158 140
             /**
159 141
              * If both have NO grouping in common, create a membership
@@ -183,7 +165,10 @@ module.exports = class MembershipService extends Schmervice.Service {
183 165
                 is_active: false,
184 166
             })
185 167
 
186
-            return [userMembership, targetMembership]
168
+            return {
169
+                memberships: [userMembership, targetMembership],
170
+                groupings: [],
171
+            }
187 172
         }
188 173
     }
189 174
 

+ 0
- 128
backend/lib/services/notification.js Visa fil

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

+ 1003
- 0
frontend/package-lock.json
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 1
- 0
frontend/package.json Visa fil

@@ -22,6 +22,7 @@
22 22
         "@prettier/plugin-pug": "^1.19.2",
23 23
         "@vitejs/plugin-vue": "^2.2.4",
24 24
         "@vue/compiler-sfc": "^3.2.31",
25
+        "ava": "^4.3.3",
25 26
         "cross-env": "^7.0.3",
26 27
         "eslint": "^8.11.0",
27 28
         "eslint-config-prettier": "^8.5.0",

+ 14
- 18
frontend/src/App.vue Visa fil

@@ -1,19 +1,18 @@
1 1
 <template lang="pug">
2 2
 w-app
3
-    div.nav(style="display: flex; justify-content: space-between;")
3
+    div.nav(v-if="isLoggedIn" style="display: flex; justify-content: space-between;")
4 4
         header
5 5
             h2 home - profile: {{ getPid }}
6 6
         w-drawer(v-model="openDrawer")
7
-            h2 content
7
+            SideBar(
8
+                :pid="getPid"
9
+                @updatePid="setPid"
10
+                @hide="showSidebar = false"
11
+            )
8 12
         w-button(@click="openDrawer = true" outline="")
9 13
             | Active Chats
10
-    SideBar(
11
-        v-if="showSidebar"
12
-        :pid="getPid"
13
-        @updatePid="setPid"
14
-        @hide="showSidebar = false"
15
-    )
16 14
     RouterView(
15
+        v-if="isLoggedIn"
17 16
         :pid="getPid"
18 17
         @updatePid="setPid"
19 18
         @show-sidebar="showSidebar = !showSidebar"
@@ -38,7 +37,12 @@ export default {
38 37
         openDrawer: false,
39 38
     }),
40 39
     computed: {
41
-        getPid: () => (currentProfile.id.value ? currentProfile.id.value : 999),
40
+        getPid: () => {
41
+            return currentProfile.id.value ? currentProfile.id.value : null
42
+        },
43
+        isLoggedIn: () => {
44
+            return currentProfile.isLoggedIn
45
+        },
42 46
     },
43 47
     async created() {
44 48
         /** Get questions so we can compare against profile responses */
@@ -62,15 +66,7 @@ export default {
62 66
             if (currentProfile.isLoggedIn) {
63 67
                 currentProfile.logout()
64 68
             }
65
-            await currentProfile.login(profileId)
66
-
67
-            if (currentProfile.isLoggedIn) {
68
-                console.warn(
69
-                    `setting up Chatter and Toaster for ${this.getPid}...`,
70
-                )
71
-                currentProfile.setupChatter()
72
-                currentProfile.setupToaster(this.$waveui.notify)
73
-            }
69
+            await currentProfile.login(profileId, this.$waveui.notify)
74 70
             console.log('---')
75 71
             //  step 3: subscribe to the this.subscriptions array
76 72
             // view current subscriptions on the currentProfile.chatter.subscriptions

+ 31
- 20
frontend/src/components/ProfileCardList.vue Visa fil

@@ -19,11 +19,10 @@ section.profile-card-list(:style="{'display': 'flex', 'overflow':'auto',}")
19 19
                     h4 {{ profile.name }}
20 20
                     nav.swipe_icons
21 21
                         //- Accept
22
-                        button(@click="chat(profile.pid)") chat
23
-                        button(@click="accept") Accept
22
+                        button(@click="accept(profile.pid)") Accept
24 23
                         button(@click="view(profile.pid)") view
25 24
                         //- Pass
26
-                        button(@click="pass") Pass
25
+                        button(@click="pass(profile.pid)") Pass
27 26
     
28 27
     .match-layout(
29 28
         v-else
@@ -39,14 +38,12 @@ section.profile-card-list(:style="{'display': 'flex', 'overflow':'auto',}")
39 38
                     h4 {{ profile.name }}
40 39
                     nav.swipe_icons
41 40
                         button(@click="view(profile.pid)") view
42
-                        button(@click="chat(profile.pid)") chat
43 41
 </template>
44 42
 
45 43
 <script setup>
46 44
 import { useRouter } from 'vue-router'
47 45
 import {
48 46
     updateQueueByProfileId,
49
-    fetchMembershipsByProfileId,
50 47
     postMembershipByProfileId,
51 48
     currentProfile,
52 49
 } from '../services'
@@ -89,42 +86,56 @@ const randomize = max => {
89 86
 
90 87
 // AHP Button behavior
91 88
 
92
-const accept = async () => {
89
+const accept = async targetId => {
90
+    if (targetId == props.pid) return
93 91
     // need to pass these arguments (profileId, targetId, status)
94 92
     // the url structure is
95 93
     // const charmander = await db.get(`/profile/{profile_id}/queue/{target_id}/delete?include_profile=true&reinsert=false`)
96 94
     // http://localhost:3001/api/profile/38/queue/9/delete?include_profile=true&reinsert=true
97 95
     const profileId = props.pid
98
-    const targetId = props.profiles[0].pid
99
-    updateQueueByProfileId(profileId, targetId, false)
100
-    // TODO: next step is grouping/membership
101
-    const checkMembership = await fetchMembershipsByProfileId(profileId)
102
-    if (!checkMembership.length) {
103
-        postMembershipByProfileId({ profileId, targetId })
96
+    await updateQueueByProfileId(profileId, targetId, false)
97
+    const { membershipMatch, groupingName } = await postMembershipByProfileId({
98
+        profileId,
99
+        targetId,
100
+    })
101
+
102
+    // Reuse old grouping name if theres a match
103
+    let channel = groupingName
104
+    console.log('membershipMatch :>> ', membershipMatch)
105
+    if (membershipMatch?.hasMatch) {
106
+        const [time, intiator, target] = groupingName.split('_')
107
+        channel = membershipMatch.groupings[0].grouping_name
108
+        console.log('channel :>> ', channel)
104 109
     }
110
+    await subscribeToChannel(channel)
105 111
     emit('reload')
106 112
 }
113
+
107 114
 const view = pid => {
108 115
     router.push({ path: `/matches/${pid}` })
109 116
 }
110
-const chat = async pid => {
117
+
118
+const subscribeToChannel = async channelName => {
111 119
     // create a chatter reference from the current profile
112 120
     const chatter = currentProfile.chatter
113 121
     // console.log('mock sender:', pid)
114
-    
115
-    /**  publish a new message to the chatter with the channel and the message & title is optional
122
+
123
+    /**
124
+     * publish a new message to the chatter with the channel and the message & title is optional
116 125
      */
117
-    const res = await chatter.publish(chatter.subscriptions[1], {
126
+    // You MUST send chatter channels as an array in an object
127
+    chatter.subscribe({ channels: [channelName] })
128
+    const res = await chatter.publish(channelName, {
118 129
         title: 'New Message',
119
-        description: 'This is the checking to see if we are subscribed to a channel!',
130
+        description: `This is the checking to see if we are subscribed to the ${channelName} channel!`,
120 131
     })
121 132
     // PubNub response will be a timecode of when the message was published
122 133
     console.log('res:', res)
123
-
124 134
     //router.push({ path: `/chat/${pid}` })
125 135
 }
126
-const pass = () => {
127
-    const targetId = props.profiles[0].pid
136
+
137
+const pass = targetId => {
138
+    if (targetId == props.pid) return
128 139
     updateQueueByProfileId(props.pid, targetId, true)
129 140
     emit('reload')
130 141
 }

+ 1
- 1
frontend/src/components/SideBar.vue Visa fil

@@ -1,7 +1,7 @@
1 1
 <template lang="pug">
2 2
 aside.sidebar
3 3
     .temp-control-box
4
-        input(v-model="switchToPID")
4
+        input(v-model="switchToPID" @keyup.enter="$emit('updatePid', switchToPID)")
5 5
         button(@click="$emit('updatePid', switchToPID)") switch profile
6 6
     
7 7
     Messages(:matches="matches" :pid="pid")

+ 21
- 34
frontend/src/services/chat.service.js Visa fil

@@ -1,7 +1,7 @@
1 1
 import PubNub from 'pubnub'
2 2
 
3
-// custom services 
4
-import {fetchMembershipsByProfileId} from '../services/grouping.service'
3
+// custom services
4
+import { fetchMembershipsByProfileId } from '../services/grouping.service'
5 5
 
6 6
 /**
7 7
  * Provider method holder
@@ -49,7 +49,7 @@ const testMessage = new ChatMessage({
49 49
     title: 'testing',
50 50
     description: 'hello world!',
51 51
 })
52
-const MAIN_CHANNEL = 'Channel-Barcelona'
52
+const MAIN_CHANNEL = 'Channel-Siimee'
53 53
 
54 54
 /** Singleton that holds all our chat information */
55 55
 class Chatter {
@@ -58,10 +58,6 @@ class Chatter {
58 58
      * @return {Chatter} our chatter instance object
59 59
      */
60 60
     constructor() {
61
-        // Map of each active chat
62
-        // * this is where we will store all of our groupings on from the backend on user login...
63
-        this.groupings = {}
64
-
65 61
         // Our pubnub instance
66 62
         this.provider = null
67 63
 
@@ -70,10 +66,10 @@ class Chatter {
70 66
 
71 67
         // Setup the main channel
72 68
         //  subscriptions array will be built dynamically from the "this.groupings" object
73
-        this.subscriptions = [MAIN_CHANNEL, 'Channel-LosAngeles']
69
+        this.subscriptions = [MAIN_CHANNEL]
74 70
         this.listeners = {
75 71
             status: async e => {
76
-                await this.publish(this.subscriptions[2], testMessage)
72
+                // await this.publish(this.subscriptions[0], testMessage)
77 73
                 if (e.category !== 'PNConnectedCategory') return
78 74
             },
79 75
             message: this._onMessage,
@@ -98,16 +94,12 @@ class Chatter {
98 94
     async setup(uuid) {
99 95
         this.uuid = `${uuid}`
100 96
         this.provider = await setupPubnub(this.uuid)
101
-            
97
+
102 98
         //  step 1: build the this.groupings object from the backend
103
-        // ? .then() to wait for the groupings to be fetched before subscribing to channels
104
-        this.getGroupingsByProfileId(this.uuid).then(() => {
105
-            this._listenFor({ listeners: this.listeners })
106
-            this._subscribe(this.subscriptions)
107
-        })
108
-        
109
-        console.log('this.subscriptions', this.subscriptions)
110
-       
99
+        // ? .await for the groupings to be fetched before subscribing to channels
100
+        await this._setupAllChannels(this.uuid)
101
+        this._listenFor({ listeners: this.listeners })
102
+        this.subscribe({ channels: this.subscriptions })
111 103
     }
112 104
     /**
113 105
      * Send a message to a channel
@@ -119,39 +111,34 @@ class Chatter {
119 111
      */
120 112
     async publish(channel, message) {
121 113
         console.log('publishing message to channel:', channel)
122
-        return await providerMethods['publish']({ channel, message })
114
+        return providerMethods.publish({ channel, message })
123 115
     }
124 116
     /**
125 117
      * Subscribe to a channels
126 118
      * Facade so we can hide provider specific methods
127 119
      * @param {array} channels
128 120
      */
129
-    _subscribe(channels) {
130
-        providerMethods['subscribe']({ channels })
121
+    subscribe({ channels }) {
122
+        providerMethods.subscribe({ channels })
131 123
     }
132 124
     /**
133 125
      * Listen to events and set callbacks
134 126
      * Facade so we can hide provider specific methods
135 127
      */
136 128
     _listenFor({ listeners }) {
137
-        providerMethods['listen'](listeners)
129
+        providerMethods.listen(listeners)
138 130
     }
139
-    
140 131
     //  step 2: build the this.subscriptions array from the this.groupings object
141 132
     // fetch all groupings for this profile and then store them in the chatter groupings object for reference
142
-    async getGroupingsByProfileId(profileId) {
143
-        console.log('fetching groupings for profileId:', profileId)
133
+    async _setupAllChannels(profileId) {
144 134
         const groupings = await fetchMembershipsByProfileId(profileId)
145
-        this.groupings = groupings
146
-        this.createChannelNamesByGroupings(this.groupings)
135
+        console.log(`fetched groupings for profileId: ${profileId}`, groupings)
136
+        groupings.forEach(grouping => {
137
+            this.subscriptions.push(grouping.grouping_name)
138
+        })
147 139
     }
148
-    // building a list of channel names from the groupings object.grouping_name
149
-    createChannelNamesByGroupings(groupings) {
150
-        groupings.forEach(item => {
151
-            this.subscriptions.push(item.grouping_name)
152
-        });
153
-        
154
-        
140
+    stop() {
141
+        console.warn('chatter stop no implemented')
155 142
     }
156 143
 }
157 144
 

+ 3
- 4
frontend/src/services/grouping.service.js Visa fil

@@ -34,17 +34,16 @@ const postMembershipByProfileId = async ({
34 34
     targetId,
35 35
     groupingType = 'match',
36 36
 }) => {
37
-    const utcDateInSeconds = Date.now()/1000
37
+    const utcDateInSeconds = Date.now() / 1000
38 38
     const membership = {
39 39
         target_id: targetId,
40 40
         grouping_type: groupingType,
41 41
         grouping_name: `${utcDateInSeconds}_${profileId}_${targetId}`,
42 42
     }
43
-    console.warn(`${groupingType} created between ${profileId} and ${targetId}`)
44
-    const createdMembershipRecord = await db.post(
43
+    const membershipMatch = await db.post(
45 44
         `/membership/${profileId}/join`,
46 45
         membership,
47 46
     )
48
-    return createdMembershipRecord
47
+    return { membershipMatch, groupingName: membership.grouping_name }
49 48
 }
50 49
 export { fetchMembershipsByProfileId, postMembershipByProfileId }

+ 7
- 2
frontend/src/services/login.service.js Visa fil

@@ -57,18 +57,23 @@ class Login {
57 57
      * @param {number} profileId
58 58
      * @returns {number} stored reactive id
59 59
      */
60
-    async login(profileId) {
60
+    async login(profileId, cb) {
61 61
         console.warn('logging in:', profileId)
62 62
         this.id.value = parseInt(profileId)
63
+
64
+        this.setupChatter()
65
+        this.setupToaster(cb)
66
+
63 67
         return this.id.value
64 68
     }
65 69
     logout() {
70
+        console.warn('logging out:', this.id.value)
66 71
         this.id.value = null
67 72
         if (this.toaster) {
68 73
             this.toaster.stop()
69 74
         }
70 75
         if (this.chatter) {
71
-            this.toaster.stop()
76
+            this.chatter.stop()
72 77
         }
73 78
     }
74 79
 

+ 13
- 12
frontend/src/services/queue.service.js Visa fil

@@ -8,21 +8,21 @@ import { Profile } from '../entities'
8 8
  */
9 9
 const fetchQueueByProfileId = async profileId => {
10 10
     let queue
11
-    
11
+
12 12
     try {
13
-        queue = await db.get(
14
-            `/profile/${profileId}/queue?include_profile=true`,
15
-        )
16
-        if(!queue?.length) {
13
+        queue = await db.get(`/profile/${profileId}/queue?include_profile=true`)
14
+        if (!queue?.length) {
17 15
             throw 'Could not retrieve match queue. Please take the survey and rescore.'
18 16
         }
19 17
     } catch (err) {
20 18
         console.error(err)
21 19
     }
22 20
 
23
-    return queue ? queue.map(profileData => {
24
-        return new Profile({ email: 'fixme@gmail.com', ...profileData })
25
-    }) : []
21
+    return queue
22
+        ? queue.map(profileData => {
23
+              return new Profile({ email: 'fixme@gmail.com', ...profileData })
24
+          })
25
+        : []
26 26
 }
27 27
 
28 28
 /**
@@ -35,11 +35,12 @@ const fetchQueueByProfileId = async profileId => {
35 35
 const updateQueueByProfileId = async (profileId, targetId, reinsert) => {
36 36
     const updateQueue = await db.patch(
37 37
         `/profile/${profileId}/queue/${targetId}/delete?include_profile=true&reinsert=${reinsert}`,
38
-        [ targetId ],
39 38
     )
40
-    return updateQueue ? updateQueue.map(profileData => {
41
-        return new Profile({ email: 'fixme@gmail.com', ...profileData })
42
-    }) : []
39
+    return updateQueue
40
+        ? updateQueue.map(profileData => {
41
+              return new Profile({ email: 'fixme@gmail.com', ...profileData })
42
+          })
43
+        : []
43 44
 }
44 45
 
45 46
 export { fetchQueueByProfileId, updateQueueByProfileId }

Laddar…
Avbryt
Spara