diff --git a/docs/useCases.md b/docs/useCases.md index ae3aaae0..f1feac41 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -75,6 +75,9 @@ The different use cases currently available in the package are classified below, - [Delete Current API Token](#delete-current-api-token) - [Recreate Current API Token](#recreate-current-api-token) - [Register User](#register-user) +- [Roles](#Roles) + - [Roles read use cases](#roles-read-use-cases) + - [Get User Selectable Roles](#get-user-selectable-roles) - [Info](#Info) - [Get Dataverse Backend Version](#get-dataverse-backend-version) - [Get Maximum Embargo Duration In Months](#get-maximum-embargo-duration-in-months) @@ -1842,6 +1845,28 @@ registerUser.execute(userDTO) _See [use case](../src/users/domain/useCases/RegisterUser.ts) implementation_. +## Roles + +### Get User Selectable Roles + +Returns a [Role](../src/roles/domain/models/Role.ts) array that the calling user can use as filters when searching within their data. + +##### Example call: + +```typescript +import { getUserSelectableRoles } from '@iqss/dataverse-client-javascript' + +/* ... */ + +getUserSelectableRoles.execute().then((roles: Role[]) => { + /* ... */ +}) + +/* ... */ +``` + +_See [use case](../src/roles/domain/useCases/GetUserSelectableRoles.ts) implementation_. + ## Info #### Get Dataverse Backend Version diff --git a/src/index.ts b/src/index.ts index 2abdf089..89a79af6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export * from './core' export * from './info' export * from './users' +export * from './roles' export * from './auth' export * from './datasets' export * from './collections' diff --git a/src/roles/domain/models/Role.ts b/src/roles/domain/models/Role.ts new file mode 100644 index 00000000..b133d9a5 --- /dev/null +++ b/src/roles/domain/models/Role.ts @@ -0,0 +1,7 @@ +export interface Role { + id: number + name: string + alias: string + description: string + permissions: string[] +} diff --git a/src/roles/domain/repositories/IRolesRepository.ts b/src/roles/domain/repositories/IRolesRepository.ts new file mode 100644 index 00000000..c968e6f5 --- /dev/null +++ b/src/roles/domain/repositories/IRolesRepository.ts @@ -0,0 +1,5 @@ +import { Role } from '../models/Role' + +export interface IRolesRepository { + getUserSelectableRoles(): Promise +} diff --git a/src/roles/domain/repositories/transformers/RolePayload.ts b/src/roles/domain/repositories/transformers/RolePayload.ts new file mode 100644 index 00000000..4a2028a2 --- /dev/null +++ b/src/roles/domain/repositories/transformers/RolePayload.ts @@ -0,0 +1,7 @@ +export interface RolePayload { + id: number + name: string + alias: string + description: string + permissions: string[] +} diff --git a/src/roles/domain/repositories/transformers/roleTransformers.ts b/src/roles/domain/repositories/transformers/roleTransformers.ts new file mode 100644 index 00000000..97d4ab42 --- /dev/null +++ b/src/roles/domain/repositories/transformers/roleTransformers.ts @@ -0,0 +1,15 @@ +import { AxiosResponse } from 'axios' +import { Role } from '../../models/Role' +import { RolePayload } from './RolePayload' + +export const transformRolesUserSelectableResponseToRoles = (response: AxiosResponse): Role[] => { + const roleUserSelectablePayload = response.data.data as RolePayload[] + + return roleUserSelectablePayload.map((role: RolePayload) => ({ + id: role.id, + name: role.name, + alias: role.alias, + description: role.description, + permissions: role.permissions + })) +} diff --git a/src/roles/domain/useCases/GetUserSelectableRoles.ts b/src/roles/domain/useCases/GetUserSelectableRoles.ts new file mode 100644 index 00000000..111f4f1e --- /dev/null +++ b/src/roles/domain/useCases/GetUserSelectableRoles.ts @@ -0,0 +1,16 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { Role } from '../models/Role' +import { IRolesRepository } from '../repositories/IRolesRepository' + +export class GetUserSelectableRoles implements UseCase { + constructor(private readonly rolesRepository: IRolesRepository) {} + + /** + * Returns the appropriate roles that the calling user can use as filters when searching within their data. + * + * @returns {Promise} - A promise that resolves to an array of Role instances. + */ + async execute(): Promise { + return (await this.rolesRepository.getUserSelectableRoles()) as Role[] + } +} diff --git a/src/roles/index.ts b/src/roles/index.ts new file mode 100644 index 00000000..8bd9e276 --- /dev/null +++ b/src/roles/index.ts @@ -0,0 +1,10 @@ +import { RolesRepository } from './infra/repositories/RolesRepository' +import { GetUserSelectableRoles } from './domain/useCases/GetUserSelectableRoles' + +const rolesRepository = new RolesRepository() + +const getUserSelectableRoles = new GetUserSelectableRoles(rolesRepository) + +export { getUserSelectableRoles } + +export { Role } from './domain/models/Role' diff --git a/src/roles/infra/repositories/RolesRepository.ts b/src/roles/infra/repositories/RolesRepository.ts new file mode 100644 index 00000000..1ec7f3af --- /dev/null +++ b/src/roles/infra/repositories/RolesRepository.ts @@ -0,0 +1,15 @@ +import { ApiRepository } from '../../../core/infra/repositories/ApiRepository' +import { IRolesRepository } from '../../domain/repositories/IRolesRepository' +import { Role } from '../../domain/models/Role' +import { transformRolesUserSelectableResponseToRoles } from '../../domain/repositories/transformers/roleTransformers' + +export class RolesRepository extends ApiRepository implements IRolesRepository { + private readonly rolesResourceName: string = 'roles' + public async getUserSelectableRoles(): Promise { + return this.doGet(`/${this.rolesResourceName}/userSelectable`, true) + .then((response) => transformRolesUserSelectableResponseToRoles(response)) + .catch((error) => { + throw error + }) + } +} diff --git a/test/integration/roles/RolesRepository.test.ts b/test/integration/roles/RolesRepository.test.ts new file mode 100644 index 00000000..89a26843 --- /dev/null +++ b/test/integration/roles/RolesRepository.test.ts @@ -0,0 +1,23 @@ +import { + ApiConfig, + DataverseApiAuthMechanism +} from '../../../src/core/infra/repositories/ApiConfig' +import { TestConstants } from '../../testHelpers/TestConstants' +import { RolesRepository } from '../../../src/roles/infra/repositories/RolesRepository' +import { createSuperAdminRoleArray } from '../../testHelpers/roles/roleHelper' + +describe('RolesRepository', () => { + const sut: RolesRepository = new RolesRepository() + + describe('getUserSelectableRoles', () => { + test('should return list of selectable roles for authenticated user', async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + const actual = await sut.getUserSelectableRoles() + expect(actual).toStrictEqual(createSuperAdminRoleArray()) + }) + }) +}) diff --git a/test/testHelpers/roles/roleHelper.ts b/test/testHelpers/roles/roleHelper.ts new file mode 100644 index 00000000..cf48cc3c --- /dev/null +++ b/test/testHelpers/roles/roleHelper.ts @@ -0,0 +1,116 @@ +import { Role } from '../../../src/roles/domain/models/Role' + +export const createRoleModel = (): Role => { + return { + id: 1, + name: 'admin', + alias: 'Admin', + description: + 'A person who has all permissions for dataverses, datasets, and files, including approving requests for restricted data.', + permissions: [ + 'AddDataverse', + 'AddDataset', + 'ViewUnpublishedDataverse', + 'ViewUnpublishedDataset' + ] + } +} +export const createRoleModelArray = (count: number): Role[] => { + return Array.from({ length: count }, (_, index) => ({ + id: index + 1, + name: `role${index + 1}`, + alias: `Role ${index + 1}`, + description: `Description for role ${index + 1}`, + permissions: [`Permission${index + 1}`] + })) +} + +export const createSuperAdminRoleArray = (): Role[] => { + return [ + { + alias: 'admin', + name: 'Admin', + permissions: [ + 'AddDataverse', + 'AddDataset', + 'ViewUnpublishedDataverse', + 'ViewUnpublishedDataset', + 'DownloadFile', + 'EditDataverse', + 'EditDataset', + 'ManageDataversePermissions', + 'ManageDatasetPermissions', + 'ManageFilePermissions', + 'PublishDataverse', + 'PublishDataset', + 'DeleteDataverse', + 'DeleteDatasetDraft' + ], + description: + 'A person who has all permissions for dataverses, datasets, and files, including approving requests for restricted data.', + id: 1 + }, + { + alias: 'fileDownloader', + name: 'File Downloader', + permissions: ['DownloadFile'], + description: 'A person who can download a published file.', + id: 2 + }, + { + alias: 'fullContributor', + name: 'Dataverse + Dataset Creator', + permissions: ['AddDataverse', 'AddDataset'], + description: 'A person who can add subdataverses and datasets within a dataverse.', + id: 3 + }, + { + alias: 'dvContributor', + name: 'Dataverse Creator', + permissions: ['AddDataverse'], + description: 'A person who can add subdataverses within a dataverse.', + id: 4 + }, + { + alias: 'dsContributor', + name: 'Dataset Creator', + permissions: ['AddDataset'], + description: 'A person who can add datasets within a dataverse.', + id: 5 + }, + { + alias: 'contributor', + name: 'Contributor', + permissions: ['ViewUnpublishedDataset', 'DownloadFile', 'EditDataset', 'DeleteDatasetDraft'], + description: + 'For datasets, a person who can edit License + Terms, and then submit them for review.', + id: 6 + }, + { + alias: 'curator', + name: 'Curator', + permissions: [ + 'AddDataverse', + 'AddDataset', + 'ViewUnpublishedDataverse', + 'ViewUnpublishedDataset', + 'DownloadFile', + 'EditDataset', + 'ManageDatasetPermissions', + 'ManageFilePermissions', + 'PublishDataset', + 'DeleteDatasetDraft' + ], + description: + 'For datasets, a person who can edit License + Terms, edit Permissions, and publish datasets.', + id: 7 + }, + { + alias: 'member', + name: 'Member', + permissions: ['ViewUnpublishedDataverse', 'ViewUnpublishedDataset', 'DownloadFile'], + description: 'A person who can view both unpublished dataverses and datasets.', + id: 8 + } + ] +} diff --git a/test/unit/roles/GetUserSelectableRoles.test.ts b/test/unit/roles/GetUserSelectableRoles.test.ts new file mode 100644 index 00000000..7637e3f7 --- /dev/null +++ b/test/unit/roles/GetUserSelectableRoles.test.ts @@ -0,0 +1,25 @@ +import { ReadError } from '../../../src' +import { IRolesRepository } from '../../../src/roles/domain/repositories/IRolesRepository' +import { createRoleModelArray } from '../../testHelpers/roles/roleHelper' +import { GetUserSelectableRoles } from '../../../src/roles/domain/useCases/GetUserSelectableRoles' + +describe('execute', () => { + test('should return roles array on repository success', async () => { + const rolesRepositoryStub: IRolesRepository = {} as IRolesRepository + const testRoles = createRoleModelArray(5) + rolesRepositoryStub.getUserSelectableRoles = jest.fn().mockResolvedValue(testRoles) + const sut = new GetUserSelectableRoles(rolesRepositoryStub) + + const actual = await sut.execute() + + expect(actual).toEqual(testRoles) + }) + + test('should return error result on repository error', async () => { + const rolesRepositoryStub: IRolesRepository = {} as IRolesRepository + rolesRepositoryStub.getUserSelectableRoles = jest.fn().mockRejectedValue(new ReadError()) + const sut = new GetUserSelectableRoles(rolesRepositoryStub) + + await expect(sut.execute()).rejects.toThrow(ReadError) + }) +})