From 0793d91884d45313243a6d568787ee478b4b2a60 Mon Sep 17 00:00:00 2001 From: BatLeDev Date: Wed, 10 Jun 2026 14:49:31 +0200 Subject: [PATCH] feat: support '*' wildcard on department for subscriptions A subscription can now use `sender.department: '*'` to receive every event of an organization regardless of department. Previously the `*` value was stored and matched literally, so such a subscription matched nothing. `getSubscriptionsFilter` now broadens the department clause: a root event (no department) matches subscriptions without a department or with `*`, and a departmental event matches its own department or `*`. Broadcast events (`department: '*'`) keep matching every subscription as before. Also add `` to ui/index.html. Ref: koumoul/plateforme work item 1739 --- api/src/events/operations.ts | 12 ++++++++++-- tests/events.unit.spec.ts | 14 ++++++++++++-- tests/subscriptions.api.spec.ts | 27 +++++++++++++++++++++++++++ ui/index.html | 1 + 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/api/src/events/operations.ts b/api/src/events/operations.ts index 500ca36..8674279 100644 --- a/api/src/events/operations.ts +++ b/api/src/events/operations.ts @@ -33,11 +33,19 @@ export const getSubscriptionsFilter = (event: Event): Filter => { subscriptionsFilter['sender.id'] = event.sender.id if (event.sender.role) subscriptionsFilter['sender.role'] = event.sender.role if (event.sender.department) { + // a departmental event also matches subscriptions using the '*' wildcard (any department) if (event.sender.department !== '*') { - subscriptionsFilter['sender.department'] = event.sender.department + subscriptionsFilter.$or = [ + { 'sender.department': event.sender.department }, + { 'sender.department': '*' } + ] } } else { - subscriptionsFilter['sender.department'] = { $exists: false } + // a root event matches subscriptions without department, or using the '*' wildcard + subscriptionsFilter.$or = [ + { 'sender.department': { $exists: false } }, + { 'sender.department': '*' } + ] } } else { subscriptionsFilter.sender = { $exists: false } diff --git a/tests/events.unit.spec.ts b/tests/events.unit.spec.ts index 6020909..1a5aa12 100644 --- a/tests/events.unit.spec.ts +++ b/tests/events.unit.spec.ts @@ -62,7 +62,12 @@ test.describe('getSubscriptionsFilter', () => { const filter = getSubscriptionsFilter(event) expect(filter['sender.type']).toBe('organization') expect(filter['sender.id']).toBe('org1') - expect(filter['sender.department']).toEqual({ $exists: false }) + // a root event matches subscriptions without department, or using the '*' wildcard + expect(filter.$or).toEqual([ + { 'sender.department': { $exists: false } }, + { 'sender.department': '*' } + ]) + expect(filter['sender.department']).toBeUndefined() }) test('filters by sender with department', () => { @@ -71,7 +76,12 @@ test.describe('getSubscriptionsFilter', () => { sender: { type: 'organization', id: 'org1', department: 'dep1' } } const filter = getSubscriptionsFilter(event) - expect(filter['sender.department']).toBe('dep1') + // a departmental event also matches subscriptions using the '*' wildcard (any department) + expect(filter.$or).toEqual([ + { 'sender.department': 'dep1' }, + { 'sender.department': '*' } + ]) + expect(filter['sender.department']).toBeUndefined() }) test('wildcard department does not filter', () => { diff --git a/tests/subscriptions.api.spec.ts b/tests/subscriptions.api.spec.ts index b08ef3e..f7454f2 100644 --- a/tests/subscriptions.api.spec.ts +++ b/tests/subscriptions.api.spec.ts @@ -140,6 +140,33 @@ test.describe('subscriptions', () => { expect(res.data.count).toBe(1) }) + test('should send any-department subscription notifications for root and departmental events', async () => { + // a root member subscribes to every department of the org using the '*' wildcard + const res = await admin1.post('/api/subscriptions', { + topic: { key: 'topic1' }, + sender: { type: 'organization', id: 'test1', department: '*', name: 'Test Organization 1' } + }) + expect(res.data.visibility).toBe('private') + + // a root event (no department) must reach the wildcard subscription + await axPush.post('/api/events', [{ + date: new Date().toISOString(), + topic: { key: 'topic1' }, + title: 'a root notification', + sender: { type: 'organization', id: 'test1', name: 'Test Organization 1' } + }]) + // a departmental event must also reach the wildcard subscription + await axPush.post('/api/events', [{ + date: new Date().toISOString(), + topic: { key: 'topic1' }, + title: 'a departmental notification', + sender: { type: 'organization', id: 'test1', department: 'dep1', name: 'Test Organization 1' } + }]) + + const notifs = await admin1.get('/api/notifications') + expect(notifs.data.count).toBe(2) + }) + test('should send a private department notification to member of right department in sender organization', async () => { let res = await user1.post('/api/subscriptions', { topic: { key: 'topic1' }, diff --git a/ui/index.html b/ui/index.html index dd7b41d..8a95361 100644 --- a/ui/index.html +++ b/ui/index.html @@ -3,6 +3,7 @@ +