Skip to content

Commit cfe34ea

Browse files
committed
feat: add pagination
1 parent 1421988 commit cfe34ea

File tree

4 files changed

+97
-8
lines changed

4 files changed

+97
-8
lines changed

src/notifications/domain/repositories/INotificationsRepository.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { Notification } from '../models/Notification'
22

33
export interface INotificationsRepository {
4-
getAllNotificationsByUser(inAppNotificationFormat?: boolean): Promise<Notification[]>
4+
getAllNotificationsByUser(
5+
inAppNotificationFormat?: boolean,
6+
onlyUnread?: boolean,
7+
limit?: number,
8+
offset?: number
9+
): Promise<Notification[]>
510
deleteNotification(notificationId: number): Promise<void>
611
getUnreadNotificationsCount(): Promise<number>
712
markNotificationAsRead(notificationId: number): Promise<void>

src/notifications/domain/useCases/GetAllNotificationsByUser.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,17 @@ export class GetAllNotificationsByUser implements UseCase<Notification[]> {
1111
* @param inAppNotificationFormat - Optional parameter to retrieve fields needed for in-app notifications
1212
* @returns {Promise<Notification[]>} - A promise that resolves to an array of Notification instances.
1313
*/
14-
async execute(inAppNotificationFormat?: boolean): Promise<Notification[]> {
14+
async execute(
15+
inAppNotificationFormat?: boolean,
16+
onlyUnread?: boolean,
17+
limit?: number,
18+
offset?: number
19+
): Promise<Notification[]> {
1520
return (await this.notificationsRepository.getAllNotificationsByUser(
16-
inAppNotificationFormat
21+
inAppNotificationFormat,
22+
onlyUnread,
23+
limit,
24+
offset
1725
)) as Notification[]
1826
}
1927
}

src/notifications/infra/repositories/NotificationsRepository.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,28 @@ export class NotificationsRepository extends ApiRepository implements INotificat
77
private readonly notificationsResourceName: string = 'notifications'
88

99
public async getAllNotificationsByUser(
10-
inAppNotificationFormat?: boolean
10+
inAppNotificationFormat?: boolean,
11+
onlyUnread?: boolean,
12+
limit?: number,
13+
offset?: number
1114
): Promise<Notification[]> {
12-
const queryParams = inAppNotificationFormat ? { inAppNotificationFormat: 'true' } : undefined
15+
const queryParams = new URLSearchParams()
16+
if (inAppNotificationFormat) {
17+
queryParams.set('inAppNotificationFormat', 'true')
18+
}
19+
if (onlyUnread) {
20+
queryParams.set('onlyUnread', 'true')
21+
}
22+
if (typeof limit === 'number') {
23+
queryParams.set('limit', limit.toString())
24+
}
25+
if (typeof offset === 'number') {
26+
queryParams.set('offset', offset.toString())
27+
}
1328
return this.doGet(
1429
this.buildApiEndpoint(this.notificationsResourceName, 'all'),
1530
true,
16-
queryParams
31+
queryParams.toString().length > 0 ? queryParams : undefined
1732
)
1833
.then((response) => {
1934
const notifications = response.data.data.notifications

test/integration/notifications/NotificationsRepository.test.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import {
99
NotificationType
1010
} from '../../../src/notifications/domain/models/Notification'
1111
import { createDataset, CreatedDatasetIdentifiers } from '../../../src/datasets'
12-
import { publishDatasetViaApi, waitForNoLocks } from '../../testHelpers/datasets/datasetHelper'
12+
import {
13+
publishDatasetViaApi,
14+
waitForNoLocks,
15+
deletePublishedDatasetViaApi
16+
} from '../../testHelpers/datasets/datasetHelper'
1317
import { WriteError } from '../../../src'
1418
import { createCollection } from '../../../src/collections'
1519
import {
@@ -95,7 +99,24 @@ describe('NotificationsRepository', () => {
9599
expect(notification).toHaveProperty('displayAsRead')
96100
})
97101

98-
test('should find notification with ASSIGNROLE type that has not been deleted', async () => {
102+
test('should return only unread notifications when onlyUnread is true (if any exist)', async () => {
103+
const notifications: Notification[] = await sut.getAllNotificationsByUser(true, true)
104+
105+
expect(Array.isArray(notifications)).toBe(true)
106+
expect(Array.isArray(notifications)).toBe(true)
107+
expect(notifications.every((n) => n.displayAsRead === false)).toBe(true)
108+
})
109+
110+
test('should paginate results using limit and offset', async () => {
111+
const limit = 1
112+
const page1: Notification[] = await sut.getAllNotificationsByUser(true, undefined, limit, 0)
113+
const page2: Notification[] = await sut.getAllNotificationsByUser(true, undefined, limit, 1)
114+
115+
expect(page1.length).toBeLessThanOrEqual(limit)
116+
expect(page2.length).toBeLessThanOrEqual(limit)
117+
118+
// Always run the assertion, but only if both pages have one notification each
119+
expect(page1.length !== 1 || page2.length !== 1 || page1[0].id !== page2[0].id).toBe(true)
99120
const notifications: Notification[] = await sut.getAllNotificationsByUser(true)
100121

101122
const assignRoleNotification = notifications.find(
@@ -116,6 +137,46 @@ describe('NotificationsRepository', () => {
116137
expect(assignRoleNotification?.roleAssignments?.[0]).toHaveProperty('definitionPointId')
117138
})
118139

140+
test('should generate 5+ notifications and verify pagination across pages', async () => {
141+
const createdDatasets: CreatedDatasetIdentifiers[] = []
142+
try {
143+
const howMany = 5
144+
for (let i = 0; i < howMany; i++) {
145+
const ids = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
146+
createdDatasets.push(ids)
147+
await publishDatasetViaApi(ids.numericId)
148+
await waitForNoLocks(ids.numericId, 10)
149+
}
150+
151+
const limit = 5
152+
const page1: Notification[] = await sut.getAllNotificationsByUser(true, undefined, limit, 0)
153+
const page2: Notification[] = await sut.getAllNotificationsByUser(true, undefined, limit, 5)
154+
const page3: Notification[] = await sut.getAllNotificationsByUser(true, undefined, limit, 10)
155+
expect(page1.length).toBeLessThanOrEqual(limit)
156+
expect(page2.length).toBeLessThanOrEqual(limit)
157+
expect(page3.length).toBeLessThanOrEqual(limit)
158+
159+
const ids1 = new Set(page1.map((n) => n.id))
160+
const ids2 = new Set(page2.map((n) => n.id))
161+
const ids3 = new Set(page3.map((n) => n.id))
162+
163+
const intersects = (a: Set<number>, b: Set<number>): boolean => {
164+
for (const x of a) {
165+
if (b.has(x)) return true
166+
}
167+
return false
168+
}
169+
170+
expect(page1.length === 0 || page2.length === 0 || !intersects(ids1, ids2)).toBe(true)
171+
expect(page1.length === 0 || page3.length === 0 || !intersects(ids1, ids3)).toBe(true)
172+
expect(page2.length === 0 || page3.length === 0 || !intersects(ids2, ids3)).toBe(true)
173+
} finally {
174+
for (const d of createdDatasets) {
175+
await deletePublishedDatasetViaApi(d.persistentId)
176+
}
177+
}
178+
})
179+
119180
test('should create a collection and find the notification with CREATEDV type', async () => {
120181
const testCollectionAlias = 'test-notification-collection'
121182
const createdCollectionId = await createCollection.execute(

0 commit comments

Comments
 (0)