From 598b05ae9a97982f3e650d5094b1ef1ee6346d66 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Thu, 4 Dec 2025 16:53:36 +0100 Subject: [PATCH 1/5] feat: add use case for getting default contributor role --- .../repositories/ICollectionsRepository.ts | 4 +++ .../useCases/GetDefaultContributorRole.ts | 25 +++++++++++++++++++ src/collections/index.ts | 5 +++- .../repositories/CollectionsRepository.ts | 19 ++++++++++++++ ...CollectionDefaultContributorRolePayload.ts | 7 ++++++ ...ctionDefaultContributorRoleTransformers.ts | 15 +++++++++++ 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 src/collections/domain/useCases/GetDefaultContributorRole.ts create mode 100644 src/collections/infra/repositories/transformers/CollectionDefaultContributorRolePayload.ts create mode 100644 src/collections/infra/repositories/transformers/collectionDefaultContributorRoleTransformers.ts diff --git a/src/collections/domain/repositories/ICollectionsRepository.ts b/src/collections/domain/repositories/ICollectionsRepository.ts index ed6cf4ff..4caf9e47 100644 --- a/src/collections/domain/repositories/ICollectionsRepository.ts +++ b/src/collections/domain/repositories/ICollectionsRepository.ts @@ -14,6 +14,7 @@ import { CollectionSummary } from '../models/CollectionSummary' import { AllowedStorageDrivers } from '../models/AllowedStorageDrivers' import { StorageDriver } from '../../../core/domain/models/StorageDriver' import { LinkingObjectType } from '../useCases/GetCollectionsForLinking' +import { Role } from '../../../roles/domain/models/Role' export interface ICollectionsRepository { getCollection(collectionIdOrAlias: number | string): Promise @@ -39,6 +40,9 @@ export interface ICollectionsRepository { getCollectionUserPermissions( collectionIdOrAlias: number | string ): Promise + getDefaultContributorRole( + collectionIdOrAlias: number | string + ): Promise getCollectionItems( collectionId?: string, limit?: number, diff --git a/src/collections/domain/useCases/GetDefaultContributorRole.ts b/src/collections/domain/useCases/GetDefaultContributorRole.ts new file mode 100644 index 00000000..83a0ad7c --- /dev/null +++ b/src/collections/domain/useCases/GetDefaultContributorRole.ts @@ -0,0 +1,25 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' +import { ROOT_COLLECTION_ID } from '../models/Collection' +import { Role } from '../../../roles/domain/models/Role' + +export class GetDefaultContributorRole implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Returns the default Role that is assigned to contributors in the given collection. + * + * @param {number | string} [collectionIdOrAlias = ':root'] - A generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId) + * If this parameter is not set, the default value is: ':root' + * @returns {Promise} + */ + async execute( + collectionIdOrAlias: number | string = ROOT_COLLECTION_ID + ): Promise { + return await this.collectionsRepository.getDefaultContributorRole(collectionIdOrAlias) + } +} diff --git a/src/collections/index.ts b/src/collections/index.ts index b62ed9e8..62407906 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -20,6 +20,7 @@ import { GetCollectionsForLinking } from './domain/useCases/GetCollectionsForLin import { SetCollectionStorageDriver } from './domain/useCases/SetCollectionStorageDriver' import { DeleteCollectionStorageDriver } from './domain/useCases/DeleteCollectionStorageDriver' import { GetAllowedCollectionStorageDrivers } from './domain/useCases/GetAllowedCollectionStorageDrivers' +import { GetDefaultContributorRole } from './domain/useCases/GetDefaultContributorRole' const collectionsRepository = new CollectionsRepository() @@ -46,6 +47,7 @@ const deleteCollectionStorageDriver = new DeleteCollectionStorageDriver(collecti const getAllowedCollectionStorageDrivers = new GetAllowedCollectionStorageDrivers( collectionsRepository ) +const getDefaultContributorRole = new GetDefaultContributorRole(collectionsRepository) export { getCollection, @@ -68,7 +70,8 @@ export { getCollectionsForLinking, setCollectionStorageDriver, deleteCollectionStorageDriver, - getAllowedCollectionStorageDrivers + getAllowedCollectionStorageDrivers, + getDefaultContributorRole } export { Collection, CollectionInputLevel, CollectionTheme } from './domain/models/Collection' export { CollectionFacet } from './domain/models/CollectionFacet' diff --git a/src/collections/infra/repositories/CollectionsRepository.ts b/src/collections/infra/repositories/CollectionsRepository.ts index ccf22729..78183779 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -42,6 +42,10 @@ import { CollectionSummary } from '../../domain/models/CollectionSummary' import { AllowedStorageDrivers } from '../../domain/models/AllowedStorageDrivers' import { StorageDriver } from '../../../core/domain/models/StorageDriver' import { LinkingObjectType } from '../../domain/useCases/GetCollectionsForLinking' +import { Role } from '../../../roles/domain/models/Role' +import { + transformCollectionDefaultContributorRoleToRole +} from './transformers/collectionDefaultContributorRoleTransformers' export interface NewCollectionRequestPayload { alias: string @@ -225,6 +229,21 @@ export class CollectionsRepository extends ApiRepository implements ICollections }) } + public async getDefaultContributorRole( + collectionIdOrAlias: number | string + ): Promise { + return this.doGet( + `/${this.collectionsResourceName}/${collectionIdOrAlias}/defaultContributorRole`, + true + ) + .then((response) => + transformCollectionDefaultContributorRoleToRole(response) + ) + .catch((error) => { + throw error + }) + } + public async getCollectionItems( collectionId?: string, limit?: number, diff --git a/src/collections/infra/repositories/transformers/CollectionDefaultContributorRolePayload.ts b/src/collections/infra/repositories/transformers/CollectionDefaultContributorRolePayload.ts new file mode 100644 index 00000000..649d58f9 --- /dev/null +++ b/src/collections/infra/repositories/transformers/CollectionDefaultContributorRolePayload.ts @@ -0,0 +1,7 @@ +export interface CollectionDefaultContributorRolePayload { + alias: string + name: string + permissions: Array + description: string + id: number +} \ No newline at end of file diff --git a/src/collections/infra/repositories/transformers/collectionDefaultContributorRoleTransformers.ts b/src/collections/infra/repositories/transformers/collectionDefaultContributorRoleTransformers.ts new file mode 100644 index 00000000..3c03e550 --- /dev/null +++ b/src/collections/infra/repositories/transformers/collectionDefaultContributorRoleTransformers.ts @@ -0,0 +1,15 @@ +import { AxiosResponse } from 'axios' +import { Role } from '../../../../roles/domain/models/Role' + +export const transformCollectionDefaultContributorRoleToRole = ( + response: AxiosResponse +): Role => { + const defaultContributorRolePayload = response.data.data + return { + alias: defaultContributorRolePayload.alias, + name: defaultContributorRolePayload.name, + permissions: defaultContributorRolePayload.permissions, + description: defaultContributorRolePayload.description, + id: defaultContributorRolePayload.id + } +} From 2c69aaf53fdcc54ad06dbd5faef802ed743eb347 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Tue, 18 Aug 2026 15:48:08 +0200 Subject: [PATCH 2/5] docs: update CHANGELOG to include getDefaultContributorRole use case --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad4a912c..b06e8841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel - Datasets: Added `exportDatasetMetadata` use case, repository method, and `ExportedDatasetMetadata` response type to support exporting dataset metadata by numeric id or persistent id through Dataverse endpoint `GET /datasets/export`. - Collections: Added `allowedDatasetTypes` field to the [Collection](./src/collections/domain/models/Collection.ts) model. This field is optional and only populated the feature is enabled on the installation and configured on the collection. - Collections: Added theme information when retrieving a collection using `getCollection`. +- Collections: Added `getDefaultContributorRole` use case. ### Changed From 06d6504ce1d9c0221fb25e2a6db895fb2d033086 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Fri, 6 Nov 2026 16:49:30 +0100 Subject: [PATCH 3/5] test: add use case for getting default contributor role --- .../collections/CollectionsRepository.test.ts | 51 ++++++++++++++++++- .../collections/CollectionsRepository.test.ts | 35 +++++++++++++ .../GetDefaultContributorRole.test.ts | 27 ++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 test/unit/collections/GetDefaultContributorRole.test.ts diff --git a/test/integration/collections/CollectionsRepository.test.ts b/test/integration/collections/CollectionsRepository.test.ts index 0e30d5f2..a8d44dbb 100644 --- a/test/integration/collections/CollectionsRepository.test.ts +++ b/test/integration/collections/CollectionsRepository.test.ts @@ -16,7 +16,10 @@ import { getDatasetFiles, restrictFile, deleteFile, - linkDataset + linkDataset, + setDefaultContributorRole, + RoleAlias, + deleteCollection } from '../../../src' import { ApiConfig } from '../../../src' import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' @@ -59,6 +62,7 @@ import { DvObjectFeaturedItemDTO, FeaturedItemsDTO } from '../../../src/collections/domain/dtos/FeaturedItemsDTO' +import { createSuperAdminRoleArray } from '../../testHelpers/roles/roleHelper' describe('CollectionsRepository', () => { const testCollectionAlias = 'collectionsRepositoryTestCollection' @@ -1738,6 +1742,51 @@ describe('CollectionsRepository', () => { }) }) + describe('getDefaultContributorRole', () => { + const sut = new CollectionsRepository() + const collectionAlias = 'getDefaultContributorRoleIntegration' + let collectionId: number | undefined + + beforeAll(async () => { + collectionId = await createCollection.execute(createCollectionDTO(collectionAlias)) + await setDefaultContributorRole.execute(collectionId, RoleAlias.CURATOR) + }) + + afterAll(async () => { + if (collectionId !== undefined) { + await deleteCollection.execute(collectionId) + } + }) + + test.each(['numeric id', 'alias'])( + 'should return the configured default role by %s', + async (identifierType) => { + if (collectionId === undefined) { + throw new Error('Test collection was not created') + } + const identifier = identifierType === 'numeric id' ? collectionId : collectionAlias + const expectedRole = createSuperAdminRoleArray().find((role) => role.alias === 'curator') + + const actual = await sut.getDefaultContributorRole(identifier) + + expect(actual).toStrictEqual({ + ...expectedRole + }) + } + ) + + test('should return error when collection does not exist', async () => { + const invalidCollectionAlias = 'invalid-collection-alias' + const expectedError = new ReadError( + `[404] Can't find dataverse with identifier='${invalidCollectionAlias}'` + ) + + await expect(sut.getDefaultContributorRole(invalidCollectionAlias)).rejects.toThrow( + expectedError + ) + }) + }) + describe('getMyDataCollectionItems', () => { let testDatasetIds: CreatedDatasetIdentifiers let testSubCollectionCreated = false diff --git a/test/unit/collections/CollectionsRepository.test.ts b/test/unit/collections/CollectionsRepository.test.ts index 075034cc..ae9d8ac1 100644 --- a/test/unit/collections/CollectionsRepository.test.ts +++ b/test/unit/collections/CollectionsRepository.test.ts @@ -903,6 +903,41 @@ describe('CollectionsRepository', () => { }) }) + describe('getDefaultContributorRole', () => { + test.each([123, 'test-collection', ':root'])( + 'should return the default role for %s', + async (identifier) => { + const role = { + id: 7, + alias: 'curator', + name: 'Curator', + description: 'Can edit and publish datasets.', + permissions: ['EditDataset', 'PublishDataset'] + } + jest.spyOn(axios, 'get').mockResolvedValue({ + data: { status: 'OK', data: { ...role } } + }) + + const actual = await sut.getDefaultContributorRole(identifier) + + expect(axios.get).toHaveBeenCalledWith( + `${TestConstants.TEST_API_URL}/dataverses/${identifier}/defaultContributorRole`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY + ) + expect(actual).toStrictEqual(role) + } + ) + + test('should return error result on error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) + let error = undefined as unknown as ReadError + + await sut.getDefaultContributorRole('missing').catch((e) => (error = e)) + + expect(error).toBeInstanceOf(ReadError) + }) + }) + describe('setDefaultContributorRole', () => { const testRoleAlias = RoleAlias.CURATOR diff --git a/test/unit/collections/GetDefaultContributorRole.test.ts b/test/unit/collections/GetDefaultContributorRole.test.ts new file mode 100644 index 00000000..b410699a --- /dev/null +++ b/test/unit/collections/GetDefaultContributorRole.test.ts @@ -0,0 +1,27 @@ +import { GetDefaultContributorRole } from '../../../src/collections/domain/useCases/GetDefaultContributorRole' +import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository' +import { ReadError } from '../../../src' +import { createRoleModel } from '../../testHelpers/roles/roleHelper' + +describe('GetDefaultContributorRole', () => { + test('should return the default role on repository success', async () => { + const role = createRoleModel() + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.getDefaultContributorRole = jest.fn().mockResolvedValue(role) + const testGetDefaultContributorRole = new GetDefaultContributorRole(collectionRepositoryStub) + + const actual = await testGetDefaultContributorRole.execute(1) + + expect(actual).toEqual(role) + }) + + test('should return error result on repository error', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.getDefaultContributorRole = jest + .fn() + .mockRejectedValue(new ReadError()) + const testGetDefaultContributorRole = new GetDefaultContributorRole(collectionRepositoryStub) + + await expect(testGetDefaultContributorRole.execute(1)).rejects.toThrow(ReadError) + }) +}) From c584556abf3320c23b132b01564c037d0852450a Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Fri, 6 Nov 2026 17:15:12 +0100 Subject: [PATCH 4/5] style: improve formatting for GetDefaultContributorRole use case --- .../domain/repositories/ICollectionsRepository.ts | 4 +--- .../domain/useCases/GetDefaultContributorRole.ts | 4 +--- .../infra/repositories/CollectionsRepository.ts | 12 +++--------- .../CollectionDefaultContributorRolePayload.ts | 2 +- .../collectionDefaultContributorRoleTransformers.ts | 4 +--- 5 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/collections/domain/repositories/ICollectionsRepository.ts b/src/collections/domain/repositories/ICollectionsRepository.ts index 1c800108..6b3b0540 100644 --- a/src/collections/domain/repositories/ICollectionsRepository.ts +++ b/src/collections/domain/repositories/ICollectionsRepository.ts @@ -41,9 +41,7 @@ export interface ICollectionsRepository { getCollectionUserPermissions( collectionIdOrAlias: number | string ): Promise - getDefaultContributorRole( - collectionIdOrAlias: number | string - ): Promise + getDefaultContributorRole(collectionIdOrAlias: number | string): Promise setDefaultContributorRole( collectionIdOrAlias: number | string, roleAlias: RoleAlias | string diff --git a/src/collections/domain/useCases/GetDefaultContributorRole.ts b/src/collections/domain/useCases/GetDefaultContributorRole.ts index 83a0ad7c..c6167796 100644 --- a/src/collections/domain/useCases/GetDefaultContributorRole.ts +++ b/src/collections/domain/useCases/GetDefaultContributorRole.ts @@ -17,9 +17,7 @@ export class GetDefaultContributorRole implements UseCase { * If this parameter is not set, the default value is: ':root' * @returns {Promise} */ - async execute( - collectionIdOrAlias: number | string = ROOT_COLLECTION_ID - ): Promise { + async execute(collectionIdOrAlias: number | string = ROOT_COLLECTION_ID): Promise { return await this.collectionsRepository.getDefaultContributorRole(collectionIdOrAlias) } } diff --git a/src/collections/infra/repositories/CollectionsRepository.ts b/src/collections/infra/repositories/CollectionsRepository.ts index 7227afbf..c186f6e0 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -44,9 +44,7 @@ import { StorageDriver } from '../../../core/domain/models/StorageDriver' import { LinkingObjectType } from '../../domain/useCases/GetCollectionsForLinking' import { RoleAlias } from '../../../roles/domain/models/RoleAlias' import { Role } from '../../../roles/domain/models/Role' -import { - transformCollectionDefaultContributorRoleToRole -} from './transformers/collectionDefaultContributorRoleTransformers' +import { transformCollectionDefaultContributorRoleToRole } from './transformers/collectionDefaultContributorRoleTransformers' export interface NewCollectionRequestPayload { alias: string @@ -230,16 +228,12 @@ export class CollectionsRepository extends ApiRepository implements ICollections }) } - public async getDefaultContributorRole( - collectionIdOrAlias: number | string - ): Promise { + public async getDefaultContributorRole(collectionIdOrAlias: number | string): Promise { return this.doGet( `/${this.collectionsResourceName}/${collectionIdOrAlias}/defaultContributorRole`, true ) - .then((response) => - transformCollectionDefaultContributorRoleToRole(response) - ) + .then((response) => transformCollectionDefaultContributorRoleToRole(response)) .catch((error) => { throw error }) diff --git a/src/collections/infra/repositories/transformers/CollectionDefaultContributorRolePayload.ts b/src/collections/infra/repositories/transformers/CollectionDefaultContributorRolePayload.ts index 649d58f9..84e885bb 100644 --- a/src/collections/infra/repositories/transformers/CollectionDefaultContributorRolePayload.ts +++ b/src/collections/infra/repositories/transformers/CollectionDefaultContributorRolePayload.ts @@ -4,4 +4,4 @@ export interface CollectionDefaultContributorRolePayload { permissions: Array description: string id: number -} \ No newline at end of file +} diff --git a/src/collections/infra/repositories/transformers/collectionDefaultContributorRoleTransformers.ts b/src/collections/infra/repositories/transformers/collectionDefaultContributorRoleTransformers.ts index 3c03e550..36868451 100644 --- a/src/collections/infra/repositories/transformers/collectionDefaultContributorRoleTransformers.ts +++ b/src/collections/infra/repositories/transformers/collectionDefaultContributorRoleTransformers.ts @@ -1,9 +1,7 @@ import { AxiosResponse } from 'axios' import { Role } from '../../../../roles/domain/models/Role' -export const transformCollectionDefaultContributorRoleToRole = ( - response: AxiosResponse -): Role => { +export const transformCollectionDefaultContributorRoleToRole = (response: AxiosResponse): Role => { const defaultContributorRolePayload = response.data.data return { alias: defaultContributorRolePayload.alias, From 867cadf8ecf0aeec2af33bd7153083be4fdd8ed6 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Fri, 6 Nov 2026 17:22:15 +0100 Subject: [PATCH 5/5] docs: add use case for getting default contributor role --- docs/useCases.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/useCases.md b/docs/useCases.md index 64a2c8d6..26e4f908 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -19,6 +19,7 @@ The different use cases currently available in the package are classified below, - [List My Data Collection Items](#list-my-data-collection-items) - [Get Collection Featured Items](#get-collection-featured-items) - [Get Collections for Linking](#get-collections-for-linking) + - [Get Default Contributor Role](#get-default-contributor-role) - [Collections write use cases](#collections-write-use-cases) - [Create a Collection](#create-a-collection) - [Set Collection Storage Driver](#set-collection-storage-driver) @@ -516,6 +517,38 @@ Notes: - When the first argument is `'collection'`, the second argument can be a numeric collection id or a collection alias. - When the first argument is `'dataset'`, the second argument must be the dataset persistent identifier string (e.g., `doi:...`). +#### Get Default Contributor Role + +Returns the default [Role](../src/roles/domain/models/Role.ts) assigned to a user who creates a dataset in the collection. + +Requires the `ManageDataversePermissions` permission on the collection. + +##### Example call: + +```typescript +import { getDefaultContributorRole } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const collectionIdOrAlias = 'classicLiterature' + +getDefaultContributorRole + .execute(collectionIdOrAlias) + .then((role) => { + /* ... */ + }) + +/* ... */ +``` + +_See [use case](../src/collections/domain/useCases/GetDefaultContributorRole.ts)_ implementation. + +The `collectionIdOrAlias` is a generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId). + +If no collection identifier is specified, the default collection identifier; `:root` will be used. + +The call returns a `Promise` and rejects with a `ReadError` if the collection cannot be read, including when it does not exist or the caller lacks permission. + ### Collections Write Use Cases #### Create a Collection