@live-change/notification-service 0.2.31

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/definition.js ADDED
@@ -0,0 +1,18 @@
1
+ const app = require("@live-change/framework").app()
2
+
3
+ const userService = require("@live-change/user-service")
4
+ const relationsPlugin = require('@live-change/relations-plugin')
5
+
6
+ const definition = app.createServiceDefinition({
7
+ name: "notification",
8
+ use: [ relationsPlugin, userService ]
9
+ })
10
+
11
+ const config = definition.config
12
+
13
+ definition.clientConfig = {
14
+ contactTypes: config.contactTypes,
15
+ notificationTypes: config.notificationTypes
16
+ }
17
+
18
+ module.exports = definition
package/index.js ADDED
@@ -0,0 +1,8 @@
1
+ const app = require("@live-change/framework").app()
2
+
3
+ const definition = require('./definition.js')
4
+
5
+ require('./notification.js')
6
+ require('./settings.js')
7
+
8
+ module.exports = definition
@@ -0,0 +1,333 @@
1
+ const app = require("@live-change/framework").app()
2
+
3
+ const definition = require('./definition.js')
4
+ const config = definition.config
5
+
6
+ const User = definition.foreignModel('users', 'User')
7
+ const Session = definition.foreignModel('session', 'Session')
8
+
9
+
10
+ const Notification = definition.model({
11
+ name: "Notification",
12
+ sessionOrUserItem: {
13
+ sortBy: ['time', 'readState']
14
+ },
15
+ properties: {
16
+ time: {
17
+ type: Date,
18
+ validation: ['nonEmpty']
19
+ },
20
+ state: {
21
+ type: String
22
+ },
23
+ readState: {
24
+ type: String,
25
+ defaultValue: 'new'
26
+ },
27
+ notificationType: {
28
+ type: String,
29
+ validation: ['nonEmpty']
30
+ },
31
+ ...config.fields
32
+ },
33
+ indexes: {
34
+ unreadNotifications: {
35
+ function: async function(input, output) {
36
+ await input.table('notification_Notification')
37
+ .map((obj) => obj && obj.readState == 'new' && ({
38
+ id: `"${obj.ownerType}":"${obj.owner}"_${obj.id}`,
39
+ ownerType: obj.ownerType, owner: obj.owner,
40
+ to: obj.id
41
+ }))
42
+ .to(output)
43
+ }
44
+ },
45
+ unreadNotificationsCount: { /// For counting
46
+ function: async function(input, output) {
47
+ const unreadIndex = await input.index('notification_Notification_unreadNotifications')
48
+ await unreadIndex.onChange(
49
+ async (obj, oldObj) => {
50
+ const { ownerType, owner } = obj || oldObj
51
+ const group = `"${ownerType}":"${owner}"`
52
+ const prefix = group + '_'
53
+ const count = await unreadIndex.count({ gt: prefix, lt: prefix + '\xFF' })
54
+ output.put({
55
+ id: group,
56
+ count
57
+ })
58
+ }
59
+ )
60
+ }
61
+ }
62
+ }
63
+ })
64
+
65
+ definition.event({
66
+ name: "created",
67
+ async execute({ notification, data }) {
68
+ await Notification.create(notification, { ...data, id: notification })
69
+ }
70
+ })
71
+
72
+ definition.event({
73
+ name: "marked",
74
+ async execute({ notification, state }) {
75
+ if(state === 'read'){
76
+ await Notification.update(notification, { state: state, readState: state })
77
+ } else {
78
+ await Notification.update(notification, { state })
79
+ }
80
+ }
81
+ })
82
+
83
+ definition.event({
84
+ name: "readState",
85
+ async execute({ notification, readState }) {
86
+ await Notification.update(notification, { readState: readState })
87
+ }
88
+ })
89
+
90
+ definition.event({
91
+ name: "allRead",
92
+ async execute({ ownerType, owner }) {
93
+ const update = { readState: 'read' }
94
+ const prefix = `"${ownerType}":"${owner}":"new"_`
95
+ console.log("MARK ALL AS READ PREFIX", prefix)
96
+ await app.dao.request(['database', 'query'], app.databaseName, `(${
97
+ async (input, output, { tableName, indexName, update, range }) => {
98
+ await input.index(indexName).range(range).onChange((obj, oldObj) => {
99
+ if(obj) output.table(tableName).update(obj.to, [{ op: 'merge', value: update }])
100
+ })
101
+ }
102
+ })`, {
103
+ tableName: Notification.tableName,
104
+ indexName: Notification.tableName + "_byOwnerAndReadState",
105
+ update,
106
+ range: {
107
+ gte: prefix,
108
+ lte: prefix + "\xFF\xFF\xFF\xFF"
109
+ }
110
+ })
111
+ }
112
+ })
113
+
114
+ definition.event({
115
+ name: "allDeleted",
116
+ async execute({ ownerType, owner }) {
117
+ const prefix = `"${ownerType}":"${owner}"_`
118
+ console.log("MARK ALL AS READ PREFIX", prefix)
119
+ await app.dao.request(['database', 'query'], app.databaseName, `(${
120
+ async (input, output, { tableName, indexName, update, range }) => {
121
+ await input.index(indexName).range(range).onChange((obj, oldObj) => {
122
+ if(obj) output.table(tableName).delete(obj.to)
123
+ })
124
+ }
125
+ })`, {
126
+ tableName: Notification.tableName,
127
+ indexName: Notification.tableName + "_byOwner",
128
+ range: {
129
+ gte: prefix,
130
+ lte: prefix + "\xFF\xFF\xFF\xFF"
131
+ }
132
+ })
133
+ }
134
+ })
135
+
136
+ definition.event({
137
+ name: "deleted",
138
+ async execute({ notification }) {
139
+ await Notification.delete(notification)
140
+ }
141
+ })
142
+
143
+ definition.event({
144
+ name: "emailNotification",
145
+ async execute({ user, notifications }) {
146
+ await Promise.all(notifications.map(notification => Notification.update(notification, { emailState: 'sent' })))
147
+ }
148
+ })
149
+
150
+ definition.view({
151
+ name: "myNotifications",
152
+ properties: {
153
+ ...app.Range
154
+ },
155
+ returns: {
156
+ type: Array,
157
+ of: {
158
+ type: Notification
159
+ }
160
+ },
161
+ autoSlice: true,
162
+ access: (params, { client }) => !!client.user, // only for logged in
163
+ async daoPath(range, {client, service}, method) {
164
+ const prefix = client.user
165
+ ? ["user_User", client.user]
166
+ : ["session_Session", client.session]
167
+ if(!Number.isSafeInteger(range.limit)) range.limit = 100
168
+ const path = Notification.sortedIndexRangePath('byOwnerAndTime', prefix, range)
169
+ /*const notifications = await app.dao.get(path)
170
+ console.log("NOTIFICATIONS", path,
171
+ "\n RESULTS", notifications.length, notifications.map(m => m.id))*/
172
+ return Notification.sortedIndexRangePath('byOwnerAndTime', prefix, range)
173
+ }
174
+ })
175
+
176
+ definition.view({
177
+ name: "myUnreadCount",
178
+ properties: {
179
+ },
180
+ returns: {
181
+ type: Object
182
+ },
183
+ async daoPath({ }, { client, service }, method) {
184
+ const id = client.user
185
+ ? `"user_User":"${client.user}"`
186
+ : `"session_Session":"${client.session}"`
187
+ console.log("UNREAD", 'unreadNotificationsCount', id)
188
+ return ['database', 'indexObject', app.databaseName, 'notification_Notification_unreadNotificationsCount', id]
189
+ }
190
+ })
191
+
192
+ definition.trigger({
193
+ name: "notify",
194
+ properties: {
195
+ user: {
196
+ type: User,
197
+ },
198
+ session: {
199
+ type: Session,
200
+ },
201
+ notificationType: {
202
+ type: String,
203
+ validation: ['nonEmpty']
204
+ },
205
+ ...config.fields
206
+ },
207
+ async execute(params , { service }, emit) {
208
+ const { user, session } = params
209
+ if(!user && !session) throw new Error("session or user required")
210
+ const notification = app.generateUid()
211
+ const time = new Date()
212
+ let data = {}
213
+ for(const key in config.fields) data[key] = params[key]
214
+ emit({
215
+ type: "created",
216
+ notification,
217
+ data: { ...data, user, session, time, readState: 'new' }
218
+ })
219
+ await app.trigger({
220
+ type: 'notificationCreated',
221
+ notification,
222
+ ...data
223
+ })
224
+ return notification
225
+ }
226
+ })
227
+
228
+ async function notificationAccess({ notification }, { client, visibilityTest }) {
229
+ if(visibilityTest) return true
230
+ const notificationRow = await Notification.get(notification)
231
+ if(!notificationRow) throw 'notFound'
232
+ return client.user
233
+ ? notificationRow.ownerType == 'user_User' && notificationRow.owner == client.user
234
+ : notificationRow.ownerType == 'session_Session' && notificationRow.owner == client.session
235
+ }
236
+
237
+ definition.action({
238
+ name: "mark",
239
+ properties: {
240
+ notification: {
241
+ type: Notification
242
+ },
243
+ state: {
244
+ type: String
245
+ }
246
+ },
247
+ access: notificationAccess,
248
+ async execute({ notification, state }, { client, service }, emit) {
249
+ emit({
250
+ type: "marked",
251
+ notification,
252
+ state
253
+ })
254
+ }
255
+ })
256
+
257
+ definition.action({
258
+ name: "toggleReadStatus",
259
+ properties: {
260
+ notification: {
261
+ type: Notification
262
+ },
263
+ read: {
264
+ type: Boolean
265
+ }
266
+ },
267
+ access: notificationAccess,
268
+ async execute({ notification, readState }, { client, service }, emit) {
269
+ emit({
270
+ type: "readState",
271
+ notification,
272
+ readState
273
+ })
274
+ }
275
+ })
276
+
277
+ definition.action({
278
+ name: "markAllAsRead",
279
+ properties: {
280
+ },
281
+ access: async ({}, { client, visibilityTest }) => {
282
+ if(visibilityTest) return true
283
+ return true
284
+ },
285
+ async execute({ notification, readState }, { client, service }, emit) {
286
+ const [ ownerType, owner ] = client.user
287
+ ? [ 'user_User', client.user ]
288
+ : [ 'session_Session', client.session ]
289
+ console.log("MARK ALL AS READ!!", ownerType, owner)
290
+ emit({
291
+ type: "allRead",
292
+ ownerType, owner
293
+ })
294
+ }
295
+ })
296
+
297
+ definition.action({
298
+ name: "delete",
299
+ properties: {
300
+ notification: {
301
+ type: Notification
302
+ }
303
+ },
304
+ access: notificationAccess,
305
+ async execute({ notification }, { client, service }, emit) {
306
+ emit({
307
+ type: "deleted",
308
+ notification
309
+ })
310
+ }
311
+ })
312
+
313
+ definition.action({
314
+ name: "deleteAll",
315
+ properties: {
316
+ },
317
+ access: async ({}, { client, visibilityTest }) => {
318
+ if(visibilityTest) return true
319
+ return true
320
+ },
321
+ async execute({ notification, readState }, { client, service }, emit) {
322
+ const [ ownerType, owner ] = client.user
323
+ ? [ 'user_User', client.user ]
324
+ : [ 'session_Session', client.session ]
325
+ console.log("DELETE ALL!!", ownerType, owner)
326
+ emit({
327
+ type: "allDeleted",
328
+ ownerType, owner
329
+ })
330
+ }
331
+ })
332
+
333
+ module.exports = definition
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@live-change/notification-service",
3
+ "version": "0.2.31",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "NODE_ENV=test tape tests/*"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/live-change/live-change-services.git"
12
+ },
13
+ "license": "MIT",
14
+ "bugs": {
15
+ "url": "https://github.com/live-change/live-change-services/issues"
16
+ },
17
+ "homepage": "https://github.com/live-change/live-change-services",
18
+ "author": {
19
+ "email": "michal@laszczewski.pl",
20
+ "name": "Michał Łaszczewski",
21
+ "url": "https://www.viamage.com/"
22
+ },
23
+ "dependencies": {
24
+ "@live-change/framework": "0.6.4"
25
+ },
26
+ "gitHead": "be1924699748fe7abcddabf207f9290e734bac6b"
27
+ }
package/settings.js ADDED
@@ -0,0 +1,32 @@
1
+ const App = require("@live-change/framework")
2
+ const app = App.app()
3
+
4
+ const definition = require('./definition.js')
5
+ const config = definition.config
6
+
7
+ async function clientOwnsContact({ user }, { contactType, contact }) {
8
+ console.log("ACC", user, contactType, contact)
9
+ if(!user) return false
10
+ const [service, model] = contactType.split('_')
11
+ if(service[0].toUpperCase() + service.slice(1) != model) return false
12
+ if(!config.contactTypes.includes(service)) return false
13
+ const contactData = await app.dao.get(['database', 'tableObject', app.databaseName, contactType, contact ])
14
+ return contactData.user == user
15
+ }
16
+
17
+ const NotificationSetting = definition.model({
18
+ name: "NotificationSetting",
19
+ propertyOfAny: {
20
+ to: ['contact', 'notification'],
21
+ readAccess: (params, {client, context, visibilityTest}) =>
22
+ visibilityTest || clientOwnsContact(client, params),
23
+ writeAccess: (params, {client, context, visibilityTest}) =>
24
+ visibilityTest || clientOwnsContact(client, params)
25
+ },
26
+ properties: {
27
+ active: Boolean,
28
+ lastUpdate: { type: Date }
29
+ }
30
+ })
31
+
32
+ module.exports = { NotificationSetting }