Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel

### Fixed

- In GetAllNotificationsByUser use case, additionalInfo field is returned as an object instead of a string.
- In GetAllNotificationsByUser use case, added support for filtering unread messages and pagination.

### Removed

- Removed date fields validations in create and update dataset use cases, since validation is already handled in the backend and SPA frontend (other clients should perform client side validation also). This avoids duplicated logic and keeps the package focused on its core responsibility.
Expand Down
2 changes: 1 addition & 1 deletion src/notifications/domain/models/Notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,6 @@ export interface Notification {
dataFileId?: number
dataFileDisplayName?: string
currentCurationStatus?: string
additionalInfo?: string
additionalInfo?: Record<string, unknown>
objectDeleted?: boolean
}
6 changes: 6 additions & 0 deletions src/notifications/domain/models/NotificationSubset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Notification } from './Notification'

export interface NotificationSubset {
notifications: Notification[]
totalNotificationCount: number
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Notification } from '../models/Notification'
import { NotificationSubset } from '../models/NotificationSubset'

export interface INotificationsRepository {
getAllNotificationsByUser(inAppNotificationFormat?: boolean): Promise<Notification[]>
getAllNotificationsByUser(
inAppNotificationFormat?: boolean,
onlyUnread?: boolean,
limit?: number,
offset?: number
): Promise<NotificationSubset>
deleteNotification(notificationId: number): Promise<void>
getUnreadNotificationsCount(): Promise<number>
markNotificationAsRead(notificationId: number): Promise<void>
Expand Down
23 changes: 17 additions & 6 deletions src/notifications/domain/useCases/GetAllNotificationsByUser.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { Notification } from '../models/Notification'
import { INotificationsRepository } from '../repositories/INotificationsRepository'
import { NotificationSubset } from '../models/NotificationSubset'

export class GetAllNotificationsByUser implements UseCase<Notification[]> {
export class GetAllNotificationsByUser implements UseCase<NotificationSubset> {
constructor(private readonly notificationsRepository: INotificationsRepository) {}

/**
* Use case for retrieving all notifications for the current user.
*
* @param inAppNotificationFormat - Optional parameter to retrieve fields needed for in-app notifications
* @returns {Promise<Notification[]>} - A promise that resolves to an array of Notification instances.
* @param onlyUnread - Optional parameter to filter only unread notifications
* @param limit - Optional parameter to limit the number of notifications returned
* @param offset - Optional parameter to skip a number of notifications (for pagination)
* @returns {Promise<NotificationSubset>} - A promise that resolves to an array of Notification instances.
*/
async execute(inAppNotificationFormat?: boolean): Promise<Notification[]> {
async execute(
inAppNotificationFormat?: boolean,
onlyUnread?: boolean,
limit?: number,
offset?: number
): Promise<NotificationSubset> {
return (await this.notificationsRepository.getAllNotificationsByUser(
inAppNotificationFormat
)) as Notification[]
inAppNotificationFormat,
onlyUnread,
limit,
offset
)) as NotificationSubset
}
}
20 changes: 15 additions & 5 deletions src/notifications/infra/repositories/NotificationsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,39 @@ import { ApiRepository } from '../../../core/infra/repositories/ApiRepository'
import { INotificationsRepository } from '../../domain/repositories/INotificationsRepository'
import { Notification } from '../../domain/models/Notification'
import { NotificationPayload } from '../transformers/NotificationPayload'
import { NotificationSubset } from '../../domain/models/NotificationSubset'

export class NotificationsRepository extends ApiRepository implements INotificationsRepository {
private readonly notificationsResourceName: string = 'notifications'

public async getAllNotificationsByUser(
inAppNotificationFormat?: boolean
): Promise<Notification[]> {
const queryParams = inAppNotificationFormat ? { inAppNotificationFormat: 'true' } : undefined
inAppNotificationFormat?: boolean,
onlyUnread?: boolean,
limit?: number,
offset?: number
): Promise<NotificationSubset> {
const queryParams = new URLSearchParams()

if (inAppNotificationFormat) queryParams.set('inAppNotificationFormat', 'true')
if (onlyUnread) queryParams.set('onlyUnread', 'true')
if (limit !== undefined) queryParams.set('limit', limit.toString())
if (offset !== undefined) queryParams.set('offset', offset.toString())
return this.doGet(
this.buildApiEndpoint(this.notificationsResourceName, 'all'),
true,
queryParams
)
.then((response) => {
const notifications = response.data.data.notifications
return notifications.map((notification: NotificationPayload) => {
const notifications = response.data.data.map((notification: NotificationPayload) => {
const { dataverseDisplayName, dataverseAlias, ...restNotification } = notification
return {
...restNotification,
...(dataverseDisplayName && { collectionDisplayName: dataverseDisplayName }),
...(dataverseAlias && { collectionAlias: dataverseAlias })
}
}) as Notification[]
const totalNotificationCount = response.data.totalCount
return { notifications, totalNotificationCount }
})
.catch((error) => {
throw error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ export interface NotificationPayload {
dataFileId?: number
dataFileDisplayName?: string
currentCurationStatus?: string
additionalInfo?: string
additionalInfo?: Record<string, unknown>
objectDeleted?: boolean
}
9 changes: 6 additions & 3 deletions test/functional/notifications/DeleteNotification.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ describe('execute', () => {
})

test('should successfully delete a notification for authenticated user', async () => {
const notifications = await getAllNotificationsByUser.execute()
const notificationSubset = await getAllNotificationsByUser.execute()
const notifications = notificationSubset.notifications
const notificationId = notifications[notifications.length - 1].id

await deleteNotification.execute(notificationId)

const notificationsAfterDelete = await getAllNotificationsByUser.execute()
expect(notificationsAfterDelete.length).toBe(notifications.length - 1)
const notificationsAfterDeleteSubset = await getAllNotificationsByUser.execute()
const notificationsAfterDelete = notificationsAfterDeleteSubset.notifications
const deletedExists = notificationsAfterDelete.some((n) => n.id === notificationId)
expect(deletedExists).toBe(false)
})

test('should throw an error when the notification id does not exist', async () => {
Expand Down
24 changes: 19 additions & 5 deletions test/functional/notifications/GetAllNotificationsByUser.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApiConfig, getAllNotificationsByUser, Notification } from '../../../src'
import { ApiConfig, getAllNotificationsByUser } from '../../../src'
import { TestConstants } from '../../testHelpers/TestConstants'
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'

import { NotificationSubset } from '../../../src/notifications/domain/models/NotificationSubset'
describe('execute', () => {
beforeEach(async () => {
ApiConfig.init(
Expand All @@ -12,26 +12,40 @@ describe('execute', () => {
})

test('should successfully return notifications for authenticated user', async () => {
const notifications: Notification[] = await getAllNotificationsByUser.execute()
const result: NotificationSubset = await getAllNotificationsByUser.execute()
const notifications = result.notifications

expect(notifications).not.toBeNull()
expect(Array.isArray(notifications)).toBe(true)
})

test('should have correct notification properties if notifications exist', async () => {
const notifications = await getAllNotificationsByUser.execute()
const result: NotificationSubset = await getAllNotificationsByUser.execute()
const notifications = result.notifications

expect(notifications[0]).toHaveProperty('id')
expect(notifications[0]).toHaveProperty('type')
expect(notifications[0]).toHaveProperty('sentTimestamp')
})

test('should have correct in-app notification properties when inAppNotificationFormat is true', async () => {
const notifications = await getAllNotificationsByUser.execute(true)
const result: NotificationSubset = await getAllNotificationsByUser.execute(true)
const notifications = result.notifications

expect(notifications[0]).toHaveProperty('id')
expect(notifications[0]).toHaveProperty('type')
expect(notifications[0]).toHaveProperty('sentTimestamp')
expect(notifications[0]).toHaveProperty('displayAsRead')
})

test('should have correct in-app notification properties when filter and paging params are set', async () => {
const result: NotificationSubset = await getAllNotificationsByUser.execute(true, true, 1, 0)
const notifications = result.notifications

expect(notifications[0]).toHaveProperty('id')
expect(notifications[0]).toHaveProperty('type')
expect(notifications[0]).toHaveProperty('sentTimestamp')
expect(notifications[0]).toHaveProperty('displayAsRead')
expect(notifications.length).toBeLessThanOrEqual(1)
})
})
Loading