Skip to content

Commit 13c908d

Browse files
authored
feat: work on traceability with addition of event.originator and event.resource (#1)
1 parent a2d0c6e commit 13c908d

File tree

12 files changed

+242
-121
lines changed

12 files changed

+242
-121
lines changed

api/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
"#types/*": "./types/*"
1515
},
1616
"dependencies": {
17-
"@data-fair/lib-common-types": "^1.5.4",
18-
"@data-fair/lib-express": "^1.12.3",
19-
"@data-fair/lib-node": "^2.2.0",
17+
"@data-fair/lib-common-types": "^1.8.0",
18+
"@data-fair/lib-express": "^1.16.1",
19+
"@data-fair/lib-node": "^2.5.1",
2020
"ajv-formats": "^3.0.1",
2121
"ajv-i18n": "^4.2.0",
2222
"config": "^3.3.11",

api/src/events/router.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,46 @@
1-
import type { SortDirection, Filter } from 'mongodb'
1+
import type { Filter, Sort } from 'mongodb'
22
import type { FullEvent } from '#types'
33

44
import { Router } from 'express'
55
import mongo from '#mongo'
66
import config from '#config'
77
import * as postReq from '#doc/events/post-req/index.ts'
88
import { session, mongoPagination, mongoProjection, httpError, assertReqInternalSecret, reqOrigin } from '@data-fair/lib-express/index.js'
9-
import { postEvents, localizeEvent } from './service.ts'
9+
import { postEvents, localizeEvent, cleanEvent } from './service.ts'
1010

1111
const router = Router()
1212
export default router
1313

1414
router.get('', async (req, res, next) => {
15-
const { account, lang } = await session.reqAuthenticated(req)
15+
const sessionState = await session.reqAuthenticated(req)
16+
const { account, lang } = sessionState
1617

1718
const query: Filter<FullEvent> = { 'sender.type': account.type, 'sender.id': account.id }
1819
if (req.query.q && typeof req.query.q === 'string') query.$text = { $search: req.query.q, $language: lang || config.i18n.defaultLocale }
20+
if (typeof req.query.resource === 'string') query['resource.id'] = req.query.resource
1921

2022
const project = mongoProjection(req.query.select, ['_search', 'htmlBody'])
2123

2224
// implement a special pagination based on the fact that we always sort by date
23-
const sort = { date: -1 as SortDirection }
24-
const { skip, size } = mongoPagination(req.query)
25+
const sort: Sort = { date: -1, _id: -1 }
26+
const { skip, size } = mongoPagination(req.query, 20)
2527
if (skip) throw httpError(400, 'skip is not supported, use "before" parameter with the date of the last event of the previous page')
2628
if (req.query.before && typeof req.query.before === 'string') {
27-
const [beforeId, beforeDate] = req.query.before.split(':')
29+
const [beforeDate, beforeId] = req.query.before.split('/')
2830
query.date = { $lte: beforeDate }
29-
query._id = { $ne: beforeId }
31+
// optional beforeId as a tie-breaker
32+
if (beforeId) query._id = { $lt: beforeId }
3033
}
3134

3235
const events = (await mongo.events.find(query).project(project).limit(size).sort(sort).toArray()) as FullEvent[]
3336

34-
const results = events.map(event => localizeEvent(event, lang))
37+
const results = events.map(event => cleanEvent(localizeEvent(event, lang), sessionState))
3538

3639
const response: any = { results }
3740

3841
if (results.length === size) {
3942
const next = new URL(req.originalUrl, reqOrigin(req))
40-
next.searchParams.set('before', results[results.length - 1]._id + ':' + results[results.length - 1].date)
43+
next.searchParams.set('before', results[results.length - 1].date + '/' + results[results.length - 1]._id)
4144
response.next = next.href
4245
}
4346

api/src/events/service.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { sendNotification, prepareSubscriptionNotification } from '../notificati
66
import { createWebhook } from '../webhooks/service.ts'
77
import { nanoid } from 'nanoid'
88
import debugModule from 'debug'
9+
import { type SessionStateAuthenticated } from '@data-fair/lib-express'
910

1011
const debug = debugModule('events')
1112

@@ -62,8 +63,17 @@ export const postEvents = async (events: Event[], extraSubscriptionsFilter?: Fil
6263
}
6364
for (const locale of config.i18n.locales) {
6465
const localizedEvent = localizeEvent(event, locale)
65-
const searchParts: (string | undefined)[] = [...event.topic.key.split(':'), event.topic.title, localizedEvent.title, localizedEvent.body]
66-
event._search.push({ language: locale, text: searchParts.filter(Boolean).join(' ') })
66+
const searchParts: (string | undefined)[] = [...event.topic.key.split(':'), event.topic.title, localizedEvent.title, localizedEvent.body, event.sender.id, event.sender.name]
67+
if (event.originator) {
68+
if (event.originator.organization) {
69+
searchParts.push(event.originator.organization.name, event.originator.organization.id)
70+
}
71+
if (event.originator.user && (!event.originator.organization || (event.sender.type === 'organization' && event.sender.id === event.originator.organization.id))) {
72+
// do not add the user name if the originator is another organization
73+
searchParts.push(event.originator.user.name, event.originator.user.id)
74+
}
75+
}
76+
if (event) { event._search.push({ language: locale, text: searchParts.filter(Boolean).join(' ') }) }
6777
}
6878
eventsBulkOp.insert(event)
6979

@@ -95,3 +105,17 @@ export const postEvents = async (events: Event[], extraSubscriptionsFilter?: Fil
95105
await sendNotification(notification, true)
96106
}
97107
}
108+
109+
export const cleanEvent = (event: LocalizedEvent, sessionState: SessionStateAuthenticated) => {
110+
if (event.originator) {
111+
if (sessionState.account.type === event.sender.type && sessionState.account.id === event.sender.id) {
112+
if (event.originator.organization) {
113+
// hide the user if the event is sent from another organization
114+
if (sessionState.organization?.id !== event.originator.organization.id) {
115+
delete event.originator.user
116+
}
117+
}
118+
}
119+
}
120+
return event
121+
}

api/src/notifications/service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const directoryUrl = config.privateDirectoryUrl
2020

2121
export const prepareSubscriptionNotification = (event: FullEvent, subscription: Subscription): Notification => {
2222
const localizedEvent = localizeEvent(event, subscription.locale)
23+
delete localizedEvent.resource
24+
delete localizedEvent.originator
2325
delete localizedEvent.urlParams
2426
const notification: Notification = {
2527
icon: subscription.icon || config.theme.notificationIcon || config.theme.logo || (subscription.origin + '/events/logo-192x192.png'),

dev/scripts/send-notifications.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import type { PostEventReq } from '../../api/doc/events/post-req/index.js'
2-
import { axiosBuilder } from '@data-fair/lib/node/axios.js'
2+
import { axiosBuilder } from '@data-fair/lib-node/axios.js'
33

44
const ax = axiosBuilder({ params: { key: 'SECRET_EVENTS' }, baseURL: 'http://localhost:8082' })
55

6-
const postEvent = async (event: PostEventReq['body']) => {
7-
await ax.post('/events/api/v1/events', event)
6+
const postEvent = async (events: PostEventReq['body']) => {
7+
await ax.post('/events/api/events', events)
88
}
99

10-
await postEvent({
10+
await postEvent([{
1111
title: 'A notification ' + new Date().toISOString(),
1212
sender: { type: 'organization', id: 'orga1' },
13-
topic: { key: 'topic1', title: 'Topic 1' }
14-
})
13+
topic: { key: 'topic1', title: 'Topic 1' },
14+
date: new Date().toISOString(),
15+
resource: { type: 'dataset', id: 'dataset1', title: 'Dataset 1' },
16+
originator: {
17+
user: { id: 'user1', name: 'User 2' },
18+
organization: { id: 'orga1', name: 'Organization 1' }
19+
}
20+
}])
1521

1622
process.exit()

package-lock.json

Lines changed: 39 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
},
4040
"devDependencies": {
4141
"@commitlint/config-conventional": "^19.2.2",
42-
"@data-fair/lib-node": "^2.2.0",
42+
"@data-fair/lib-node": "^2.5.1",
4343
"@types/config": "^3.3.3",
4444
"@types/debug": "^4.1.12",
4545
"commitlint": "^19.2.2",

test-it/01-events.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,33 @@ describe('events', () => {
3030
topic: { key: 'topic1' },
3131
title: 'a notification',
3232
sender: { type: 'user', id: 'user1', name: 'User 1' }
33+
}, {
34+
date: new Date().toISOString(),
35+
topic: { key: 'topic1' },
36+
title: 'another notification',
37+
sender: { type: 'user', id: 'user1', name: 'User 1' }
38+
}, {
39+
date: new Date().toISOString(),
40+
topic: { key: 'topic1' },
41+
title: 'anotherone',
42+
sender: { type: 'user', id: 'user1', name: 'User 1' }
3343
}])
3444
res = await admin1.get('/api/events')
3545
assert.equal(res.data.results.length, 0)
3646
res = await user1.get('/api/events')
37-
assert.equal(res.data.results.length, 1)
47+
assert.equal(res.data.results.length, 3)
3848
res = await user1.get('/api/events?q=notification')
39-
assert.equal(res.data.results.length, 1)
49+
assert.equal(res.data.results.length, 2)
4050
res = await user1.get('/api/events?q=test')
4151
assert.equal(res.data.results.length, 0)
52+
res = await user1.get('/api/events', { params: { size: 2 } })
53+
assert.equal(res.data.results.length, 2)
54+
assert.ok(res.data.next)
55+
const id1 = res.data.results[0]._id
56+
res = await user1.get(res.data.next)
57+
assert.equal(res.data.results.length, 1)
58+
assert.notEqual(res.data.results[0]._id, id1)
59+
assert.ok(!res.data.next)
4260
})
4361

4462
it('should send an internationalized event', async () => {

ui/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ declare module 'vue' {
1010
ConfirmMenu: typeof import('./src/components/confirm-menu.vue')['default']
1111
DeviceCard: typeof import('./src/components/device-card.vue')['default']
1212
ErrorAlert: typeof import('./src/components/error-alert.vue')['default']
13+
EventsActions: typeof import('./src/components/events-actions.vue')['default']
1314
RegisterDevice: typeof import('./src/components/register-device.vue')['default']
1415
RouterLink: typeof import('vue-router')['RouterLink']
1516
RouterView: typeof import('vue-router')['RouterView']

ui/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
"lint-fix": "eslint --fix ."
1313
},
1414
"dependencies": {
15-
"@data-fair/frame": "^0.8.1",
16-
"@data-fair/lib-vue": "^1.15.5",
15+
"@data-fair/frame": "^0.12.0",
16+
"@data-fair/lib-vue": "^1.19.0",
1717
"@data-fair/lib-vuetify": "^1.9.1",
1818
"@intlify/unplugin-vue-i18n": "^5.2.0",
1919
"@koumoul/v-iframe": "^2.4.4",

0 commit comments

Comments
 (0)