Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
91 changes: 91 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ The different use cases currently available in the package are classified below,
- [Get Application Terms of Use](#get-application-terms-of-use)
- [Contact](#Contact)
- [Send Feedback to Object Contacts](#send-feedback-to-object-contacts)
- [Notifications](#Notifications)
- [Get All Notifications by User](#get-all-notifications-by-user)
- [Delete Notification](#delete-notification)
- [Get Unread Count](#get-unread-count)
- [Mark As Read](#mark-as-read)
- [Search](#Search)
- [Get Search Services](#get-search-services)

Expand Down Expand Up @@ -2096,6 +2101,92 @@ In ContactDTO, it takes the following information:
- **body**: the email body to send.
- **fromEmail**: the email to list in the reply-to field.

## Notifications

#### Get All Notifications by User

Returns a [Notification](../src/notifications/domain/models/Notification.ts) array containing all notifications for the current authenticated user.

##### Example call:

```typescript
import { getAllNotificationsByUser } from '@iqss/dataverse-client-javascript'

/* ... */

getAllNotificationsByUser.execute().then((notifications: Notification[]) => {
/* ... */
})

/* ... */
```

_See [use case](../src/notifications/domain/useCases/GetAllNotificationsByUser.ts) implementation_.

#### Delete Notification

Deletes a specific notification for the current authenticated user by its ID.

##### Example call:

```typescript
import { deleteNotification } from '@iqss/dataverse-client-javascript'

/* ... */

const notificationId = 123

deleteNotification.execute(notificationId: number).then(() => {
/* ... */
})

/* ... */
```

_See [use case](../src/notifications/domain/useCases/DeleteNotification.ts) implementation_.

#### Get Unread Count

Returns the number of unread notifications for the current authenticated user.

##### Example call:

```typescript
import { getUnreadCount } from '@iqss/dataverse-client-javascript'

/* ... */

getUnreadCount.execute().then((count: number) => {
console.log(`You have ${count} unread notifications`)
})

/* ... */
```

_See [use case](../src/notifications/domain/useCases/GetUnreadCount.ts) implementation_.

#### Mark As Read

Marks a specific notification as read for the current authenticated user. This operation is idempotent - marking an already-read notification as read will not cause an error.

##### Example call:

```typescript
import { markAsRead } from '@iqss/dataverse-client-javascript'

/* ... */

const notificationId = 123

markAsRead.execute(notificationId).then(() => {
console.log('Notification marked as read')
})

/* ... */
```

_See [use case](../src/notifications/domain/useCases/MarkAsRead.ts) implementation_.

## Search

#### Get Search Services
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export * from './collections'
export * from './metadataBlocks'
export * from './files'
export * from './contactInfo'
export * from './notifications'
export * from './search'
72 changes: 72 additions & 0 deletions src/notifications/domain/models/Notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
export enum NotificationType {
ASSIGNROLE = 'ASSIGNROLE',
REVOKEROLE = 'REVOKEROLE',
CREATEDV = 'CREATEDV',
CREATEDS = 'CREATEDS',
CREATEACC = 'CREATEACC',
SUBMITTEDDS = 'SUBMITTEDDS',
RETURNEDDS = 'RETURNEDDS',
PUBLISHEDDS = 'PUBLISHEDDS',
REQUESTFILEACCESS = 'REQUESTFILEACCESS',
GRANTFILEACCESS = 'GRANTFILEACCESS',
REJECTFILEACCESS = 'REJECTFILEACCESS',
FILESYSTEMIMPORT = 'FILESYSTEMIMPORT',
CHECKSUMIMPORT = 'CHECKSUMIMPORT',
CHECKSUMFAIL = 'CHECKSUMFAIL',
CONFIRMEMAIL = 'CONFIRMEMAIL',
APIGENERATED = 'APIGENERATED',
INGESTCOMPLETED = 'INGESTCOMPLETED',
INGESTCOMPLETEDWITHERRORS = 'INGESTCOMPLETEDWITHERRORS',
PUBLISHFAILED_PIDREG = 'PUBLISHFAILED_PIDREG',
WORKFLOW_SUCCESS = 'WORKFLOW_SUCCESS',
WORKFLOW_FAILURE = 'WORKFLOW_FAILURE',
STATUSUPDATED = 'STATUSUPDATED',
DATASETCREATED = 'DATASETCREATED',
DATASETMENTIONED = 'DATASETMENTIONED',
GLOBUSUPLOADCOMPLETED = 'GLOBUSUPLOADCOMPLETED',
GLOBUSUPLOADCOMPLETEDWITHERRORS = 'GLOBUSUPLOADCOMPLETEDWITHERRORS',
GLOBUSDOWNLOADCOMPLETED = 'GLOBUSDOWNLOADCOMPLETED',
GLOBUSDOWNLOADCOMPLETEDWITHERRORS = 'GLOBUSDOWNLOADCOMPLETEDWITHERRORS',
REQUESTEDFILEACCESS = 'REQUESTEDFILEACCESS',
GLOBUSUPLOADREMOTEFAILURE = 'GLOBUSUPLOADREMOTEFAILURE',
GLOBUSUPLOADLOCALFAILURE = 'GLOBUSUPLOADLOCALFAILURE',
PIDRECONCILED = 'PIDRECONCILED'
}

export interface RoleAssignment {
id: number
assignee: string
definitionPointId: number
roleId: number
roleName: string
_roleAlias: string
}

export interface Notification {
id: number
type: NotificationType
subjectText?: string
messageText?: string
sentTimestamp: string
displayAsRead: boolean
installationBrandName?: string
userGuidesBaseUrl?: string
userGuidesVersion?: string
userGuidesSectionPath?: string
roleAssignments?: RoleAssignment[]
dataverseAlias?: string
dataverseDisplayName?: string
Comment thread
ChengShi-1 marked this conversation as resolved.
Outdated
datasetPersistentIdentifier?: string
datasetDisplayName?: string
ownerPersistentIdentifier?: string
ownerAlias?: string
ownerDisplayName?: string
requestorFirstName?: string
requestorLastName?: string
requestorEmail?: string
dataFileId?: number
dataFileDisplayName?: string
currentCurationStatus?: string
additionalInfo?: string
objectDeleted?: boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Notification } from '../models/Notification'

export interface INotificationsRepository {
getAllNotificationsByUser(inAppNotificationFormat?: boolean): Promise<Notification[]>
deleteNotification(notificationId: number): Promise<void>
getUnreadCount(): Promise<number>
markAsRead(notificationId: number): Promise<void>
}
16 changes: 16 additions & 0 deletions src/notifications/domain/useCases/DeleteNotification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { INotificationsRepository } from '../repositories/INotificationsRepository'

/**
* Use case for deleting a specific notification for the current user.
*
* @param notificationId - The ID of the notification to delete.
* @returns {Promise<void>} - A promise that resolves when the notification is deleted.
*/
export class DeleteNotification implements UseCase<void> {
constructor(private readonly notificationsRepository: INotificationsRepository) {}

async execute(notificationId: number): Promise<void> {
return this.notificationsRepository.deleteNotification(notificationId)
}
}
19 changes: 19 additions & 0 deletions src/notifications/domain/useCases/GetAllNotificationsByUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { Notification } from '../models/Notification'
import { INotificationsRepository } from '../repositories/INotificationsRepository'

export class GetAllNotificationsByUser implements UseCase<Notification[]> {
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.
*/
async execute(inAppNotificationFormat?: boolean): Promise<Notification[]> {
return (await this.notificationsRepository.getAllNotificationsByUser(
inAppNotificationFormat
)) as Notification[]
}
}
19 changes: 19 additions & 0 deletions src/notifications/domain/useCases/GetUnreadCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { INotificationsRepository } from '../repositories/INotificationsRepository'

export class GetUnreadCount implements UseCase<number> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to follow the naming convention we have in other use cases, change to GetUnreadNotificationsCount?

private notificationsRepository: INotificationsRepository

constructor(notificationsRepository: INotificationsRepository) {
this.notificationsRepository = notificationsRepository
}

/**
* Use case for retrieving the number of unread notifications for the current user.
*
* @returns {Promise<number>} - A promise that resolves to the number of unread notifications.
*/
async execute(): Promise<number> {
return await this.notificationsRepository.getUnreadCount()
}
}
20 changes: 20 additions & 0 deletions src/notifications/domain/useCases/MarkAsRead.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { INotificationsRepository } from '../repositories/INotificationsRepository'

export class MarkAsRead implements UseCase<void> {
Comment thread
ChengShi-1 marked this conversation as resolved.
Outdated
private notificationsRepository: INotificationsRepository

constructor(notificationsRepository: INotificationsRepository) {
this.notificationsRepository = notificationsRepository
}

/**
* Use case for marking a notification as read.
*
* @param notificationId - The ID of the notification to mark as read.
* @returns {Promise<void>} - A promise that resolves when the notification is marked as read.
*/
async execute(notificationId: number): Promise<void> {
return await this.notificationsRepository.markAsRead(notificationId)
}
}
16 changes: 16 additions & 0 deletions src/notifications/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { NotificationsRepository } from './infra/repositories/NotificationsRepository'
import { GetAllNotificationsByUser } from './domain/useCases/GetAllNotificationsByUser'
import { DeleteNotification } from './domain/useCases/DeleteNotification'
import { GetUnreadCount } from './domain/useCases/GetUnreadCount'
import { MarkAsRead } from './domain/useCases/MarkAsRead'

const notificationsRepository = new NotificationsRepository()

const getAllNotificationsByUser = new GetAllNotificationsByUser(notificationsRepository)
const deleteNotification = new DeleteNotification(notificationsRepository)
const getUnreadCount = new GetUnreadCount(notificationsRepository)
const markAsRead = new MarkAsRead(notificationsRepository)

export { getAllNotificationsByUser, deleteNotification, getUnreadCount, markAsRead }

export { Notification, NotificationType, RoleAssignment } from './domain/models/Notification'
46 changes: 46 additions & 0 deletions src/notifications/infra/repositories/NotificationsRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ApiRepository } from '../../../core/infra/repositories/ApiRepository'
import { INotificationsRepository } from '../../domain/repositories/INotificationsRepository'
import { Notification } from '../../domain/models/Notification'

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
return this.doGet(
this.buildApiEndpoint(this.notificationsResourceName, 'all'),
true,
queryParams
)
.then((response) => response.data.data.notifications as Notification[])
.catch((error) => {
throw error
})
}

public async deleteNotification(notificationId: number): Promise<void> {
return this.doDelete(
this.buildApiEndpoint(this.notificationsResourceName, notificationId.toString())
)
.then(() => undefined)
.catch((error) => {
throw error
})
}

public async getUnreadCount(): Promise<number> {
return this.doGet(
this.buildApiEndpoint(this.notificationsResourceName, 'unreadCount'),
true
).then((response) => response.data.data.unreadCount as number)
}

public async markAsRead(notificationId: number): Promise<void> {
return this.doPut(
this.buildApiEndpoint(this.notificationsResourceName, 'markAsRead', notificationId),
{}
).then(() => undefined)
}
}
27 changes: 27 additions & 0 deletions test/functional/notifications/DeleteNotification.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ApiConfig, deleteNotification, getAllNotificationsByUser, WriteError } from '../../../src'
import { TestConstants } from '../../testHelpers/TestConstants'
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'

describe('execute', () => {
beforeEach(async () => {
ApiConfig.init(
TestConstants.TEST_API_URL,
DataverseApiAuthMechanism.API_KEY,
process.env.TEST_API_KEY
)
})

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

await deleteNotification.execute(notificationId)

const notificationsAfterDelete = await getAllNotificationsByUser.execute()
expect(notificationsAfterDelete.length).toBe(notifications.length - 1)
})

test('should throw an error when the notification id does not exist', async () => {
await expect(deleteNotification.execute(123)).rejects.toThrow(WriteError)
})
})
Loading
Loading