@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 +18 -0
- package/index.js +8 -0
- package/notification.js +333 -0
- package/package.json +27 -0
- package/settings.js +32 -0
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
package/notification.js
ADDED
|
@@ -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 }
|