From 55bd10cb31b56e384d2ff2db40df5cc10e8f8a7a Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Tue, 4 Nov 2025 10:49:32 +0100 Subject: [PATCH 001/127] feat: add config setting "bearerTokenGetFunction" --- src/core/infra/repositories/ApiConfig.ts | 5 ++++- src/core/infra/repositories/apiConfigBuilders.ts | 12 +++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/core/infra/repositories/ApiConfig.ts b/src/core/infra/repositories/ApiConfig.ts index dd0144ca..04444b2b 100644 --- a/src/core/infra/repositories/ApiConfig.ts +++ b/src/core/infra/repositories/ApiConfig.ts @@ -3,17 +3,20 @@ export class ApiConfig { static dataverseApiAuthMechanism: DataverseApiAuthMechanism static dataverseApiKey?: string static bearerTokenLocalStorageKey?: string + static bearerTokenGetFunction?: () => string | null static init( dataverseApiUrl: string, dataverseApiAuthMechanism: DataverseApiAuthMechanism, dataverseApiKey?: string, - bearerTokenLocalStorageKey?: string + bearerTokenLocalStorageKey?: string, + bearerTokenGetFunction?: () => string ) { this.dataverseApiUrl = dataverseApiUrl this.dataverseApiAuthMechanism = dataverseApiAuthMechanism this.dataverseApiKey = dataverseApiKey this.bearerTokenLocalStorageKey = bearerTokenLocalStorageKey + this.bearerTokenGetFunction = bearerTokenGetFunction } } diff --git a/src/core/infra/repositories/apiConfigBuilders.ts b/src/core/infra/repositories/apiConfigBuilders.ts index c245c58e..5030b0b1 100644 --- a/src/core/infra/repositories/apiConfigBuilders.ts +++ b/src/core/infra/repositories/apiConfigBuilders.ts @@ -41,13 +41,19 @@ export const buildRequestConfig = ( break case DataverseApiAuthMechanism.BEARER_TOKEN: { - if (!ApiConfig.bearerTokenLocalStorageKey) { + if (!(ApiConfig.bearerTokenLocalStorageKey || ApiConfig.bearerTokenGetFunction)) { throw new Error( - 'Bearer token local storage key is not set in the ApiConfig, when using bearer token auth mechanism you must set the bearerTokenLocalStorageKey' + 'Bearer token local storage key or get function is not set in the ApiConfig, when using bearer token auth mechanism you must set the bearerTokenLocalStorageKey or bearerTokenGetFunction' ) } - const token = getLocalStorageItem(ApiConfig.bearerTokenLocalStorageKey) + let token + + if (ApiConfig.bearerTokenLocalStorageKey) { + token = getLocalStorageItem(ApiConfig.bearerTokenLocalStorageKey) + } else if (ApiConfig.bearerTokenGetFunction) { + token = ApiConfig.bearerTokenGetFunction() + } if (token) { requestConfig.headers.Authorization = `Bearer ${token}` From f906ca4ccb50a9901ff4813ce3f1a3e2c4941c22 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Thu, 27 Nov 2025 10:50:46 +0100 Subject: [PATCH 002/127] feat: add use case for getting dataset versions --- src/datasets/domain/models/DatasetVersion.ts | 31 ++++++++++++++ .../repositories/IDatasetsRepository.ts | 8 ++++ .../domain/useCases/GetDatasetVersions.ts | 32 +++++++++++++++ src/datasets/index.ts | 4 ++ .../infra/repositories/DatasetsRepository.ts | 40 +++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 src/datasets/domain/models/DatasetVersion.ts create mode 100644 src/datasets/domain/useCases/GetDatasetVersions.ts diff --git a/src/datasets/domain/models/DatasetVersion.ts b/src/datasets/domain/models/DatasetVersion.ts new file mode 100644 index 00000000..b502faa4 --- /dev/null +++ b/src/datasets/domain/models/DatasetVersion.ts @@ -0,0 +1,31 @@ +import { DatasetLicense, DatasetMetadataBlocks, DatasetVersionState } from "./Dataset"; +import { FilePayload } from "../../../files/infra/repositories/transformers/FilePayload"; + +export interface DatasetVersion { + id: number + datasetId: number + datasetPersistentId: string + alternativePersistentId?: string + datasetType: string + storageIdentifier: string + versionNumber?: number + versionMinorNumber?: number + internalVersionNumber: number + versionState: DatasetVersionState + isInReviewState: boolean + latestVersionPublishingState: DatasetVersionState + lastUpdateTime: string + releaseTime?: string + createTime: string + publicationDate: string + citationDate: string + license: DatasetLicense + fileAccessRequest: boolean + files?: Array + metadataBlocks?: DatasetMetadataBlocks +} + +export interface DatasetVersionSubset { + versions: DatasetVersion[] + totalCount: number +} \ No newline at end of file diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 8a52f8f9..73c07408 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -9,6 +9,7 @@ import { MetadataBlock } from '../../../metadataBlocks' import { DatasetVersionDiff } from '../models/DatasetVersionDiff' import { DatasetDownloadCount } from '../models/DatasetDownloadCount' import { DatasetVersionSummarySubset } from '../models/DatasetVersionSummaryInfo' +import { DatasetVersionSubset } from '../models/DatasetVersion' import { DatasetLinkedCollection } from '../models/DatasetLinkedCollection' import { CitationFormat } from '../models/CitationFormat' import { FormattedCitation } from '../models/FormattedCitation' @@ -73,6 +74,13 @@ export interface IDatasetsRepository { limit?: number, offset?: number ): Promise + getDatasetVersions( + datasetId: number | string, + limit?: number, + offset?: number, + excludeMetadataBlocks?: boolean, + excludeFiles?: boolean + ): Promise deleteDatasetDraft(datasetId: number | string): Promise linkDataset(datasetId: number | string, collectionIdOrAlias: number | string): Promise unlinkDataset(datasetId: number | string, collectionIdOrAlias: number | string): Promise diff --git a/src/datasets/domain/useCases/GetDatasetVersions.ts b/src/datasets/domain/useCases/GetDatasetVersions.ts new file mode 100644 index 00000000..fc55f440 --- /dev/null +++ b/src/datasets/domain/useCases/GetDatasetVersions.ts @@ -0,0 +1,32 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { DatasetVersionSubset } from '../models/DatasetVersion' +import { IDatasetsRepository } from '../repositories/IDatasetsRepository' + +export class GetDatasetVersions implements UseCase { + private datasetsRepository: IDatasetsRepository + + constructor(datasetsRepository: IDatasetsRepository) { + this.datasetsRepository = datasetsRepository + } + + /** + * Returns a list of versions for a given dataset including (optionally) metadata blocks and files. + * Draft versions will only be available to users who have permission to view unpublished drafts. + * + * @param {number | string} [datasetId] - The dataset identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers). + * @param {number} [limit] - Limit for pagination (optional). + * @param {number} [offset] - Offset for pagination (optional). + * @param {boolean} [excludeMetadataBlocks] - Exclude metadata blocks (optional, default: false). + * @param {boolean} [excludeFiles] - Exclude files (optional, default: true). + * @returns {Promise} - A DatasetVersionSubset containing the versions and total count. + */ + async execute( + datasetId: number | string, + limit?: number, + offset?: number, + excludeMetadataBlocks?: boolean, + excludeFiles?: boolean + ): Promise { + return await this.datasetsRepository.getDatasetVersions(datasetId, limit, offset, excludeMetadataBlocks, excludeFiles) + } +} diff --git a/src/datasets/index.ts b/src/datasets/index.ts index b8edb5b3..19a2fda6 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -19,6 +19,7 @@ import { GetDatasetVersionDiff } from './domain/useCases/GetDatasetVersionDiff' import { DeaccessionDataset } from './domain/useCases/DeaccessionDataset' import { GetDatasetDownloadCount } from './domain/useCases/GetDatasetDownloadCount' import { GetDatasetVersionsSummaries } from './domain/useCases/GetDatasetVersionsSummaries' +import { GetDatasetVersions } from './domain/useCases/GetDatasetVersions' import { DeleteDatasetDraft } from './domain/useCases/DeleteDatasetDraft' import { LinkDataset } from './domain/useCases/LinkDataset' import { UnlinkDataset } from './domain/useCases/UnlinkDataset' @@ -67,6 +68,7 @@ const updateDataset = new UpdateDataset( const deaccessionDataset = new DeaccessionDataset(datasetsRepository) const getDatasetDownloadCount = new GetDatasetDownloadCount(datasetsRepository) const getDatasetVersionsSummaries = new GetDatasetVersionsSummaries(datasetsRepository) +const getDatasetVersions = new GetDatasetVersions(datasetsRepository) const deleteDatasetDraft = new DeleteDatasetDraft(datasetsRepository) const linkDataset = new LinkDataset(datasetsRepository) const unlinkDataset = new UnlinkDataset(datasetsRepository) @@ -101,6 +103,7 @@ export { deaccessionDataset, getDatasetDownloadCount, getDatasetVersionsSummaries, + getDatasetVersions, deleteDatasetDraft, linkDataset, unlinkDataset, @@ -133,6 +136,7 @@ export { TermsOfUse } from './domain/models/Dataset' export { DatasetPreview } from './domain/models/DatasetPreview' +export { DatasetVersion } from './domain/models/DatasetVersion' export { DatasetVersionDiff } from './domain/models/DatasetVersionDiff' export { DatasetPreviewSubset } from './domain/models/DatasetPreviewSubset' export { diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 849cf658..4e1ac92f 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -21,6 +21,7 @@ import { DatasetVersionDiff } from '../../domain/models/DatasetVersionDiff' import { transformDatasetVersionDiffResponseToDatasetVersionDiff } from './transformers/datasetVersionDiffTransformers' import { DatasetDownloadCount } from '../../domain/models/DatasetDownloadCount' import { DatasetVersionSummarySubset } from '../../domain/models/DatasetVersionSummaryInfo' +import { DatasetVersionSubset } from '../../domain/models/DatasetVersion' import { DatasetLinkedCollection } from '../../domain/models/DatasetLinkedCollection' import { CitationFormat } from '../../domain/models/CitationFormat' import { transformDatasetLinkedCollectionsResponseToDatasetLinkedCollection } from './transformers/datasetLinkedCollectionsTransformers' @@ -337,6 +338,45 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi }) } + public async getDatasetVersions( + datasetId: string | number, + limit?: number, + offset?: number, + excludeMetadataBlocks?: boolean, + excludeFiles?: boolean + ): Promise { + const queryParams = new URLSearchParams() + + if (limit) { + queryParams.set('limit', limit.toString()) + } + + if (offset) { + queryParams.set('offset', offset.toString()) + } + + if (excludeMetadataBlocks !== undefined) { + queryParams.set('excludeMetadataBlocks', excludeMetadataBlocks.toString()) + } + + if (excludeFiles !== undefined) { + queryParams.set('excludeFiles', excludeFiles.toString()) + } + + return this.doGet( + this.buildApiEndpoint(this.datasetsResourceName, 'versions', datasetId), + true, + queryParams + ) + .then((response) => ({ + versions: response.data.data, + totalCount: response.data.totalCount + })) + .catch((error) => { + throw error + }) + } + public async deleteDatasetDraft(datasetId: string | number): Promise { return this.doDelete( this.buildApiEndpoint(this.datasetsResourceName, 'versions/:draft', datasetId) From 7ed3bce0d490c2628091260d051e90b814e7c409 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Thu, 27 Nov 2025 10:56:51 +0100 Subject: [PATCH 003/127] docs: add use case for getting dataset versions --- docs/useCases.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/useCases.md b/docs/useCases.md index 3254e4a5..3fd43643 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -37,6 +37,7 @@ The different use cases currently available in the package are classified below, - [Get User Permissions on a Dataset](#get-user-permissions-on-a-dataset) - [Get Differences between Two Dataset Versions](#get-differences-between-two-dataset-versions) - [List All Datasets](#list-all-datasets) + - [Get Dataset Versions](#get-dataset-versions) - [Get Dataset Versions Summaries](#get-dataset-versions-summaries) - [Get Dataset Linked Collections](#get-dataset-linked-collections) - [Get Dataset Available Categories](#get-dataset-available-categories) @@ -871,6 +872,36 @@ Note that `collectionId` is an optional parameter to filter datasets by collecti The `DatasetPreviewSubset`returned instance contains a property called `totalDatasetCount` which is necessary for pagination. +#### Get Dataset Versions + +Returns the total count of versions and an array of [DatasetVersion](../src/datasets/domain/models/DatasetVersion.ts) that contains information about every specific version. + +##### Example call: + +```typescript +import { getDatasetVersions } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const datasetId = 'doi:10.77777/FK2/AAAAAA' + +getDatasetVersions + .execute(datasetId) + .then((datasetVersions: DatasetVersionSubset) => { + /* ... */ + }) + +/* ... */ +``` + +_See [use case](../src/datasets/domain/useCases/GetDatasetVersions.ts) implementation_. + +- The `datasetId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers. +- **limit**: (number) Limit for pagination. +- **offset**: (number) Offset for pagination. +- **excludeMetadataBlocks**: (boolean) Exclude metadata blocks (default: false). +- **excludesFiles**: (boolean) Exclude files (default: true). + #### Get Dataset Versions Summaries Returns the total count of versions and an array of [DatasetVersionSummaryInfo](../src/datasets/domain/models/DatasetVersionSummaryInfo.ts) that contains information about what changed in every specific version. From 6de066f93a9f168d39b4af184d16bd7cc8ae8082 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Thu, 27 Nov 2025 13:53:32 +0100 Subject: [PATCH 004/127] test: add use case for getting dataset versions --- .../datasets/DatasetsRepository.test.ts | 198 ++++++++++++++++++ .../datasets/datasetVersionsHelper.ts | 26 +++ test/unit/datasets/GetDatasetVersions.test.ts | 40 ++++ 3 files changed, 264 insertions(+) create mode 100644 test/testHelpers/datasets/datasetVersionsHelper.ts create mode 100644 test/unit/datasets/GetDatasetVersions.test.ts diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 5e3fa4b1..d495b5f9 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -1555,6 +1555,204 @@ describe('DatasetsRepository', () => { }, 180000) }) + describe('getDatasetVersions', () => { + const testDatasetVersionsCollectionAlias = 'testDatasetVersionsCollection' + + beforeAll(async () => { + await createCollectionViaApi(testDatasetVersionsCollectionAlias) + await publishCollectionViaApi(testDatasetVersionsCollectionAlias) + await setStorageDriverViaApi(testDatasetVersionsCollectionAlias, 'LocalStack') + }) + + afterAll(async () => { + await deleteCollectionViaApi(testDatasetVersionsCollectionAlias) + }) + + test('should return dataset versions when dataset exists', async () => { + const testDatasetIds = await createDataset.execute( + TestConstants.TEST_NEW_DATASET_DTO, + testDatasetVersionsCollectionAlias + ) + + const actual = await sut.getDatasetVersions(testDatasetIds.numericId) + + expect(actual.versions.length).toBeGreaterThan(0) + expect(actual.totalCount).toBeGreaterThan(0) + expect(actual.versions[0].versionState).toBe('DRAFT') + expect(actual.versions[0].latestVersionPublishingState).toBe('DRAFT') + expect(actual.versions[0].isInReviewState).toBe(false) + + await deleteUnpublishedDatasetViaApi(testDatasetIds.numericId) + }) + + test('should return dataset versions correctly after first publish', async () => { + const testDatasetIds = await createDataset.execute( + TestConstants.TEST_NEW_DATASET_DTO, + testDatasetVersionsCollectionAlias + ) + await publishDataset.execute(testDatasetIds.numericId, VersionUpdateType.MAJOR) + + await waitForNoLocks(testDatasetIds.numericId, 10) + + const actual = await sut.getDatasetVersions(testDatasetIds.numericId) + + expect(actual.versions.length).toBeGreaterThan(0) + expect(actual.totalCount).toBeGreaterThan(0) + expect(actual.versions[0].versionNumber).toBe(1) + expect(actual.versions[0].versionMinorNumber).toBe(0) + expect(actual.versions[0].versionState).toBe('RELEASED') + expect(actual.versions[0].isInReviewState).toBe(false) + + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + }) + + test('should return dataset versions correctly after deaccessioned', async () => { + const testDatasetIds = await createDataset.execute( + TestConstants.TEST_NEW_DATASET_DTO, + testDatasetVersionsCollectionAlias + ) + await publishDataset.execute(testDatasetIds.numericId, VersionUpdateType.MAJOR) + + await waitForNoLocks(testDatasetIds.numericId, 10) + + await deaccessionDatasetViaApi(testDatasetIds.numericId, '1.0') + + const actual = await sut.getDatasetVersions(testDatasetIds.numericId) + + expect(actual.versions.length).toBeGreaterThan(0) + expect(actual.totalCount).toBeGreaterThan(0) + expect(actual.versions[0].versionNumber).toBe(1) + expect(actual.versions[0].versionMinorNumber).toBe(0) + expect(actual.versions[0].versionState).toBe('DEACCESSIONED') + expect(actual.versions[0].isInReviewState).toBe(false) + + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + }) + + test('should return dataset versions correctly after 1st publish and metadata fields update', async () => { + const testDatasetIds = await createDataset.execute( + TestConstants.TEST_NEW_DATASET_DTO, + testDatasetVersionsCollectionAlias + ) + await publishDataset.execute(testDatasetIds.numericId, VersionUpdateType.MAJOR) + + await waitForNoLocks(testDatasetIds.numericId, 10) + + const metadataBlocksRepository = new MetadataBlocksRepository() + const citationMetadataBlock = await metadataBlocksRepository.getMetadataBlockByName( + 'citation' + ) + + await sut.updateDataset( + testDatasetIds.numericId, + { + license: createDatasetLicenseModel(true), + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: 'Updated Dataset Title' + } + } + ] + }, + [citationMetadataBlock] + ) + + const actual = await sut.getDatasetVersions(testDatasetIds.numericId) + + expect(actual.versions.length).toEqual(2) + expect(actual.totalCount).toEqual(2) + expect(actual.versions[0].versionState).toBe('DRAFT') + expect(actual.versions[0].isInReviewState).toBe(false) + + expect(actual.versions[1].versionNumber).toBe(1) + expect(actual.versions[1].versionMinorNumber).toBe(0) + expect(actual.versions[1].versionState).toBe('RELEASED') + + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + }) + + test('should return error when dataset does not exist', async () => { + const expectedError = new ReadError( + `[404] Dataset with ID ${nonExistentTestDatasetId} not found.` + ) + + await expect(sut.getDatasetVersions(nonExistentTestDatasetId)).rejects.toThrow( + expectedError + ) + }) + + test('should return dataset versions with pagination', async () => { + const testDatasetIds = await createDataset.execute( + TestConstants.TEST_NEW_DATASET_DTO, + testDatasetVersionsCollectionAlias + ) + + await publishDataset.execute(testDatasetIds.numericId, VersionUpdateType.MAJOR) + await waitForNoLocks(testDatasetIds.numericId, 10) + + const metadataBlocksRepository = new MetadataBlocksRepository() + const citationMetadataBlock = await metadataBlocksRepository.getMetadataBlockByName( + 'citation' + ) + + for (let i = 1; i <= 21; i++) { + await sut.updateDataset( + testDatasetIds.numericId, + { + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: `Updated Dataset Title - Version ${i}` + } + } + ] + }, + [citationMetadataBlock] + ) + + await publishDataset.execute(testDatasetIds.numericId, VersionUpdateType.MINOR) + await waitForNoLocks(testDatasetIds.numericId, 10) + } + + const firstPage = await sut.getDatasetVersions(testDatasetIds.numericId, 5, 0) + + expect(firstPage.versions.length).toBe(5) + expect(firstPage.totalCount).toBe(22) + expect(firstPage.versions[0].versionNumber).toBe(1) + expect(firstPage.versions[0].versionMinorNumber).toBe(21) + expect(firstPage.versions[4].versionNumber).toBe(1) + expect(firstPage.versions[4].versionMinorNumber).toBe(17) + + // Test pagination with limit=5, offset=5 (second page) + const secondPage = await sut.getDatasetVersions(testDatasetIds.numericId, 5, 5) + expect(secondPage.versions.length).toBe(5) + expect(secondPage.totalCount).toBe(22) + expect(firstPage.versions[0].versionNumber).toBe(1) + expect(firstPage.versions[0].versionMinorNumber).toBe(16) + expect(firstPage.versions[4].versionNumber).toBe(1) + expect(firstPage.versions[4].versionMinorNumber).toBe(12) + + // Test pagination with limit=5, offset=10 (third page) + const thirdPage = await sut.getDatasetVersions(testDatasetIds.numericId, 5, 10) + expect(thirdPage.versions.length).toBe(5) + expect(thirdPage.totalCount).toBe(22) + expect(firstPage.versions[0].versionNumber).toBe(1) + expect(firstPage.versions[0].versionMinorNumber).toBe(11) + expect(firstPage.versions[4].versionNumber).toBe(1) + expect(firstPage.versions[4].versionMinorNumber).toBe(7) + + // Test that all versions are returned without pagination + const allVersions = await sut.getDatasetVersions(testDatasetIds.numericId) + expect(allVersions.versions.length).toBe(22) // 1 initial + 21 updates + expect(allVersions.totalCount).toBe(22) + + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + }, 180000) + }) + describe('getDatasetDownloadCount', () => { const testGetDatasetDownloadCountCollectionAlias = 'testGetDatasetDownloadCountCollection' let testDatasetIds: CreatedDatasetIdentifiers diff --git a/test/testHelpers/datasets/datasetVersionsHelper.ts b/test/testHelpers/datasets/datasetVersionsHelper.ts new file mode 100644 index 00000000..8ffc911c --- /dev/null +++ b/test/testHelpers/datasets/datasetVersionsHelper.ts @@ -0,0 +1,26 @@ +import { DatasetVersion, DatasetVersionState } from '../../../src' + +export const createDatasetVersionModel = ( + props?: Partial +): DatasetVersion => ({ + id: 1, + datasetId: 1, + datasetPersistentId: 'doi:10.5072/FK2/AAENBT', + datasetType: 'dataset', + storageIdentifier: 's3://datasets/1', + internalVersionNumber: 1, + versionState: DatasetVersionState.DRAFT, + latestVersionPublishingState: DatasetVersionState.DRAFT, + isInReviewState: false, + lastUpdateTime: '2021-01-01T00:00:00Z', + createTime: '2021-01-01T00:00:00Z', + publicationDate: '2021-01-01', + citationDate: '2021-01-01', + license: { + 'name': 'CC BY 4.0 (Creative Commons Attribution 4.0 International)', + 'uri': 'cc-by', + 'iconUri': 'https://licensebuttons.net/l/by/4.0/88x31.png' + }, + fileAccessRequest: false, + ...props +}) diff --git a/test/unit/datasets/GetDatasetVersions.test.ts b/test/unit/datasets/GetDatasetVersions.test.ts new file mode 100644 index 00000000..c9507ce5 --- /dev/null +++ b/test/unit/datasets/GetDatasetVersions.test.ts @@ -0,0 +1,40 @@ +import { ReadError } from '../../../src/core/domain/repositories/ReadError' +import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository' +import { createDatasetVersionModel } from '../../testHelpers/datasets/datasetVersionsHelper' +import { GetDatasetVersions } from '../../../src/datasets/domain/useCases/GetDatasetVersions' +import { DatasetVersionSubset } from '../../../src/datasets/domain/models/DatasetVersion' + +const testDatasetId = 1 + +describe('execute', () => { + test('should return dataset versions summaries on repository success', async () => { + const testDatasetVersionsSubset: DatasetVersionSubset = { + versions: [createDatasetVersionModel()], + totalCount: 1 + } + const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository + datasetsRepositoryStub.getDatasetVersionsSummaries = jest + .fn() + .mockResolvedValue(testDatasetVersionsSubset) + const sut = new GetDatasetVersions(datasetsRepositoryStub) + + const actual = await sut.execute(testDatasetId) + + expect(actual).toEqual(testDatasetVersionsSubset) + expect(datasetsRepositoryStub.getDatasetVersionsSummaries).toHaveBeenCalledWith( + testDatasetId, + undefined, + undefined + ) + }) + + test('should return error result on repository error', async () => { + const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository + datasetsRepositoryStub.getDatasetVersionsSummaries = jest + .fn() + .mockRejectedValue(new ReadError()) + const sut = new GetDatasetVersions(datasetsRepositoryStub) + + await expect(sut.execute(testDatasetId)).rejects.toThrow(ReadError) + }) +}) From cd2a19684c8ae87bee3702ebcc25a0db9cc43874 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Fri, 23 Jan 2026 16:03:27 +0100 Subject: [PATCH 005/127] fix: remove "isInReviewState" flag from dataset versions --- src/datasets/domain/models/DatasetVersion.ts | 1 - test/integration/datasets/DatasetsRepository.test.ts | 4 ---- test/testHelpers/datasets/datasetVersionsHelper.ts | 1 - 3 files changed, 6 deletions(-) diff --git a/src/datasets/domain/models/DatasetVersion.ts b/src/datasets/domain/models/DatasetVersion.ts index b502faa4..c4eea836 100644 --- a/src/datasets/domain/models/DatasetVersion.ts +++ b/src/datasets/domain/models/DatasetVersion.ts @@ -12,7 +12,6 @@ export interface DatasetVersion { versionMinorNumber?: number internalVersionNumber: number versionState: DatasetVersionState - isInReviewState: boolean latestVersionPublishingState: DatasetVersionState lastUpdateTime: string releaseTime?: string diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index d495b5f9..ae93c78b 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -1580,7 +1580,6 @@ describe('DatasetsRepository', () => { expect(actual.totalCount).toBeGreaterThan(0) expect(actual.versions[0].versionState).toBe('DRAFT') expect(actual.versions[0].latestVersionPublishingState).toBe('DRAFT') - expect(actual.versions[0].isInReviewState).toBe(false) await deleteUnpublishedDatasetViaApi(testDatasetIds.numericId) }) @@ -1601,7 +1600,6 @@ describe('DatasetsRepository', () => { expect(actual.versions[0].versionNumber).toBe(1) expect(actual.versions[0].versionMinorNumber).toBe(0) expect(actual.versions[0].versionState).toBe('RELEASED') - expect(actual.versions[0].isInReviewState).toBe(false) await deletePublishedDatasetViaApi(testDatasetIds.persistentId) }) @@ -1624,7 +1622,6 @@ describe('DatasetsRepository', () => { expect(actual.versions[0].versionNumber).toBe(1) expect(actual.versions[0].versionMinorNumber).toBe(0) expect(actual.versions[0].versionState).toBe('DEACCESSIONED') - expect(actual.versions[0].isInReviewState).toBe(false) await deletePublishedDatasetViaApi(testDatasetIds.persistentId) }) @@ -1664,7 +1661,6 @@ describe('DatasetsRepository', () => { expect(actual.versions.length).toEqual(2) expect(actual.totalCount).toEqual(2) expect(actual.versions[0].versionState).toBe('DRAFT') - expect(actual.versions[0].isInReviewState).toBe(false) expect(actual.versions[1].versionNumber).toBe(1) expect(actual.versions[1].versionMinorNumber).toBe(0) diff --git a/test/testHelpers/datasets/datasetVersionsHelper.ts b/test/testHelpers/datasets/datasetVersionsHelper.ts index 8ffc911c..0b52b01e 100644 --- a/test/testHelpers/datasets/datasetVersionsHelper.ts +++ b/test/testHelpers/datasets/datasetVersionsHelper.ts @@ -11,7 +11,6 @@ export const createDatasetVersionModel = ( internalVersionNumber: 1, versionState: DatasetVersionState.DRAFT, latestVersionPublishingState: DatasetVersionState.DRAFT, - isInReviewState: false, lastUpdateTime: '2021-01-01T00:00:00Z', createTime: '2021-01-01T00:00:00Z', publicationDate: '2021-01-01', From 2f0ded8fb523017de19f6536b3d24f20344537c0 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Fri, 23 Jan 2026 16:13:54 +0100 Subject: [PATCH 006/127] fix: remove "excludeFiles" flag from "Get Dataset Versions" use case --- docs/useCases.md | 1 - src/datasets/domain/repositories/IDatasetsRepository.ts | 3 +-- src/datasets/domain/useCases/GetDatasetVersions.ts | 6 ++---- src/datasets/infra/repositories/DatasetsRepository.ts | 7 +------ 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/docs/useCases.md b/docs/useCases.md index 3fd43643..6c5fc39f 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -900,7 +900,6 @@ _See [use case](../src/datasets/domain/useCases/GetDatasetVersions.ts) implement - **limit**: (number) Limit for pagination. - **offset**: (number) Offset for pagination. - **excludeMetadataBlocks**: (boolean) Exclude metadata blocks (default: false). -- **excludesFiles**: (boolean) Exclude files (default: true). #### Get Dataset Versions Summaries diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 73c07408..51d6133a 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -78,8 +78,7 @@ export interface IDatasetsRepository { datasetId: number | string, limit?: number, offset?: number, - excludeMetadataBlocks?: boolean, - excludeFiles?: boolean + excludeMetadataBlocks?: boolean ): Promise deleteDatasetDraft(datasetId: number | string): Promise linkDataset(datasetId: number | string, collectionIdOrAlias: number | string): Promise diff --git a/src/datasets/domain/useCases/GetDatasetVersions.ts b/src/datasets/domain/useCases/GetDatasetVersions.ts index fc55f440..ea0e60c3 100644 --- a/src/datasets/domain/useCases/GetDatasetVersions.ts +++ b/src/datasets/domain/useCases/GetDatasetVersions.ts @@ -17,16 +17,14 @@ export class GetDatasetVersions implements UseCase { * @param {number} [limit] - Limit for pagination (optional). * @param {number} [offset] - Offset for pagination (optional). * @param {boolean} [excludeMetadataBlocks] - Exclude metadata blocks (optional, default: false). - * @param {boolean} [excludeFiles] - Exclude files (optional, default: true). * @returns {Promise} - A DatasetVersionSubset containing the versions and total count. */ async execute( datasetId: number | string, limit?: number, offset?: number, - excludeMetadataBlocks?: boolean, - excludeFiles?: boolean + excludeMetadataBlocks?: boolean ): Promise { - return await this.datasetsRepository.getDatasetVersions(datasetId, limit, offset, excludeMetadataBlocks, excludeFiles) + return await this.datasetsRepository.getDatasetVersions(datasetId, limit, offset, excludeMetadataBlocks) } } diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 4e1ac92f..4cd4ed6f 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -342,8 +342,7 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi datasetId: string | number, limit?: number, offset?: number, - excludeMetadataBlocks?: boolean, - excludeFiles?: boolean + excludeMetadataBlocks?: boolean ): Promise { const queryParams = new URLSearchParams() @@ -359,10 +358,6 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi queryParams.set('excludeMetadataBlocks', excludeMetadataBlocks.toString()) } - if (excludeFiles !== undefined) { - queryParams.set('excludeFiles', excludeFiles.toString()) - } - return this.doGet( this.buildApiEndpoint(this.datasetsResourceName, 'versions', datasetId), true, From a316ca7023bc7fdec732687caf7caf774833457f Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Fri, 5 Dec 2025 17:26:26 +0100 Subject: [PATCH 007/127] feat: add use case for setting default contributor role --- .../repositories/ICollectionsRepository.ts | 4 +++ .../useCases/SetDefaultContributorRole.ts | 26 +++++++++++++++++++ src/collections/index.ts | 5 +++- .../repositories/CollectionsRepository.ts | 14 ++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/collections/domain/useCases/SetDefaultContributorRole.ts diff --git a/src/collections/domain/repositories/ICollectionsRepository.ts b/src/collections/domain/repositories/ICollectionsRepository.ts index bc8960c8..8a67c5c8 100644 --- a/src/collections/domain/repositories/ICollectionsRepository.ts +++ b/src/collections/domain/repositories/ICollectionsRepository.ts @@ -25,6 +25,10 @@ export interface ICollectionsRepository { getCollectionUserPermissions( collectionIdOrAlias: number | string ): Promise + setDefaultContributorRole( + collectionIdOrAlias: number | string, + roleAlias: string + ): Promise getCollectionItems( collectionId?: string, limit?: number, diff --git a/src/collections/domain/useCases/SetDefaultContributorRole.ts b/src/collections/domain/useCases/SetDefaultContributorRole.ts new file mode 100644 index 00000000..7e3f09c0 --- /dev/null +++ b/src/collections/domain/useCases/SetDefaultContributorRole.ts @@ -0,0 +1,26 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' +import { ROOT_COLLECTION_ID } from '../models/Collection' + +export class SetDefaultContributorRole implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Sets 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' + * @param {string} [roleAlias] - The alias of the role to be assigned + * @returns {Promise} + */ + async execute( + collectionIdOrAlias: number | string = ROOT_COLLECTION_ID, + roleAlias: string + ): Promise { + return await this.collectionsRepository.setDefaultContributorRole(collectionIdOrAlias, roleAlias) + } +} diff --git a/src/collections/index.ts b/src/collections/index.ts index 59e2e50b..0849bb2a 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -16,6 +16,7 @@ import { LinkCollection } from './domain/useCases/LinkCollection' import { UnlinkCollection } from './domain/useCases/UnlinkCollection' import { GetCollectionLinks } from './domain/useCases/GetCollectionLinks' import { GetCollectionsForLinking } from './domain/useCases/GetCollectionsForLinking' +import { SetDefaultContributorRole } from './domain/useCases/SetDefaultContributorRole' const collectionsRepository = new CollectionsRepository() @@ -36,6 +37,7 @@ const linkCollection = new LinkCollection(collectionsRepository) const unlinkCollection = new UnlinkCollection(collectionsRepository) const getCollectionLinks = new GetCollectionLinks(collectionsRepository) const getCollectionsForLinking = new GetCollectionsForLinking(collectionsRepository) +const setDefaultContributorRole = new SetDefaultContributorRole(collectionsRepository) export { getCollection, @@ -54,7 +56,8 @@ export { linkCollection, unlinkCollection, getCollectionLinks, - getCollectionsForLinking + getCollectionsForLinking, + setDefaultContributorRole } export { Collection, CollectionInputLevel } 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 e0e459b0..b71a676e 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -167,6 +167,20 @@ export class CollectionsRepository extends ApiRepository implements ICollections }) } + public async setDefaultContributorRole( + collectionIdOrAlias: number | string, + roleAlias: string + ): Promise { + return this.doPut( + `/${this.collectionsResourceName}/${collectionIdOrAlias}/defaultContributorRole/${roleAlias}`, + {} + ) + .then(() => undefined) + .catch((error) => { + throw error + }) + } + public async getCollectionItems( collectionId?: string, limit?: number, From 3a32476788acafa5ab25187d342d22e4331c090d Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Mon, 26 Jan 2026 16:13:50 +0100 Subject: [PATCH 008/127] fix: update GetDatasetVersions.test.ts --- test/unit/datasets/GetDatasetVersions.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/unit/datasets/GetDatasetVersions.test.ts b/test/unit/datasets/GetDatasetVersions.test.ts index c9507ce5..1119da29 100644 --- a/test/unit/datasets/GetDatasetVersions.test.ts +++ b/test/unit/datasets/GetDatasetVersions.test.ts @@ -7,13 +7,13 @@ import { DatasetVersionSubset } from '../../../src/datasets/domain/models/Datase const testDatasetId = 1 describe('execute', () => { - test('should return dataset versions summaries on repository success', async () => { + test('should return dataset versions on repository success', async () => { const testDatasetVersionsSubset: DatasetVersionSubset = { versions: [createDatasetVersionModel()], totalCount: 1 } const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository - datasetsRepositoryStub.getDatasetVersionsSummaries = jest + datasetsRepositoryStub.getDatasetVersions = jest .fn() .mockResolvedValue(testDatasetVersionsSubset) const sut = new GetDatasetVersions(datasetsRepositoryStub) @@ -21,16 +21,17 @@ describe('execute', () => { const actual = await sut.execute(testDatasetId) expect(actual).toEqual(testDatasetVersionsSubset) - expect(datasetsRepositoryStub.getDatasetVersionsSummaries).toHaveBeenCalledWith( + expect(datasetsRepositoryStub.getDatasetVersions).toHaveBeenCalledWith( testDatasetId, undefined, + undefined, undefined ) }) test('should return error result on repository error', async () => { const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository - datasetsRepositoryStub.getDatasetVersionsSummaries = jest + datasetsRepositoryStub.getDatasetVersions = jest .fn() .mockRejectedValue(new ReadError()) const sut = new GetDatasetVersions(datasetsRepositoryStub) From dca9254b0c56264e97a1cdb644eaa39d29953751 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Mon, 26 Jan 2026 16:23:59 +0100 Subject: [PATCH 009/127] test: add test for setting default contributor role --- .../SetDefaultContributorRole.test.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/unit/collections/SetDefaultContributorRole.test.ts diff --git a/test/unit/collections/SetDefaultContributorRole.test.ts b/test/unit/collections/SetDefaultContributorRole.test.ts new file mode 100644 index 00000000..ee95a3f2 --- /dev/null +++ b/test/unit/collections/SetDefaultContributorRole.test.ts @@ -0,0 +1,29 @@ +import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository' +import { WriteError } from '../../../src' +import { SetDefaultContributorRole } from '../../../src/collections/domain/useCases/SetDefaultContributorRole' + +describe('execute', () => { + test('should set default contributor role on repository success', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.setDefaultContributorRole = jest.fn().mockResolvedValue(undefined) + const testSetDefaultContributorRole = new SetDefaultContributorRole( + collectionRepositoryStub + ) + + await expect(testSetDefaultContributorRole.execute(1, "curator")).resolves.toBeUndefined() + expect(collectionRepositoryStub.setDefaultContributorRole).toHaveBeenCalledWith(1, "curator") + }) + + test('should return error result on repository error', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.setDefaultContributorRole = jest + .fn() + .mockRejectedValue(new WriteError()) + const testSetDefaultContributorRole = new SetDefaultContributorRole( + collectionRepositoryStub + ) + + await expect(testSetDefaultContributorRole.execute(1, "curator")).rejects.toThrow(WriteError) + expect(collectionRepositoryStub.setDefaultContributorRole).toHaveBeenCalledWith(1, "curator") + }) +}) From 8115382872bb39e19a2f0578d7a2539e0046e1ab Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Mon, 26 Jan 2026 16:28:47 +0100 Subject: [PATCH 010/127] docs: add docs for setting default contributor role --- docs/useCases.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/useCases.md b/docs/useCases.md index f77a40e1..94ff5f16 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -25,6 +25,7 @@ The different use cases currently available in the package are classified below, - [Update Collection Featured Items](#update-collection-featured-items) - [Delete Collection Featured Items](#delete-collection-featured-items) - [Delete a Collection Featured Item](#delete-a-collection-featured-item) + - [Set Default Contributor Role](#set-default-contributor-role) - [Templates](#Templates) - [Templates read use cases](#templates-read-use-cases) - [Get a Template](#get-a-template) @@ -576,6 +577,27 @@ deleteCollectionFeaturedItem.execute(featuredItemId) _See [use case](../src/collections/domain/useCases/DeleteCollectionFeaturedItem.ts)_ definition. +#### Set Default Contributor Role + +Sets the default contributor role of a collection, given a collection identifier and a role alias. + +##### Example call: + +```typescript +import { setDefaultContributorRole } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const collectionIdOrAlias = 12345 +const roleAlias = 'curator' + +setDefaultContributorRole.execute(collectionIdOrAlias, roleAlias) + +/* ... */ +``` + +_See [use case](../src/collections/domain/useCases/SetDefaultContributorRole.ts)_ definition. + ## Templates ### Templates Read Use Cases From 3bd415071040081186870e5cd71c78bdf25b0685 Mon Sep 17 00:00:00 2001 From: Vera Clemens Date: Wed, 10 Dec 2025 15:53:28 +0100 Subject: [PATCH 011/127] feat: return theme when reading a collection --- src/collections/domain/models/Collection.ts | 12 ++++++++++++ .../repositories/transformers/CollectionPayload.ts | 12 ++++++++++++ .../transformers/collectionTransformers.ts | 5 ++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/collections/domain/models/Collection.ts b/src/collections/domain/models/Collection.ts index cbcf01ca..5e49210d 100644 --- a/src/collections/domain/models/Collection.ts +++ b/src/collections/domain/models/Collection.ts @@ -16,6 +16,18 @@ export interface Collection { isMetadataBlockRoot: boolean isFacetRoot: boolean childCount: number + theme?: CollectionTheme +} + +export interface CollectionTheme { + id: number + logo: string + tagline: string + linkUrl: string + linkColor: string + textColor: string + backgroundColor: string + logoBackgroundColor?: string } export interface CollectionInputLevel { diff --git a/src/collections/infra/repositories/transformers/CollectionPayload.ts b/src/collections/infra/repositories/transformers/CollectionPayload.ts index f478ca13..cc75a1fa 100644 --- a/src/collections/infra/repositories/transformers/CollectionPayload.ts +++ b/src/collections/infra/repositories/transformers/CollectionPayload.ts @@ -14,6 +14,18 @@ export interface CollectionPayload { isMetadataBlockRoot: boolean isFacetRoot: boolean childCount: number + theme?: CollectionThemePayload +} + +export interface CollectionThemePayload { + id: number + logo: string + tagline: string + linkUrl: string + linkColor: string + textColor: string + backgroundColor: string + logoBackgroundColor?: string } export interface CollectionInputLevelPayload { diff --git a/src/collections/infra/repositories/transformers/collectionTransformers.ts b/src/collections/infra/repositories/transformers/collectionTransformers.ts index fa23b8ed..3f42f822 100644 --- a/src/collections/infra/repositories/transformers/collectionTransformers.ts +++ b/src/collections/infra/repositories/transformers/collectionTransformers.ts @@ -3,7 +3,7 @@ import { AxiosResponse } from 'axios' import { CollectionContactPayload, CollectionInputLevelPayload, - CollectionPayload + CollectionPayload, } from './CollectionPayload' import { transformPayloadToOwnerNode } from '../../../../core/infra/repositories/transformers/dvObjectOwnerNodeTransformer' import { CollectionFacet } from '../../../domain/models/CollectionFacet' @@ -74,6 +74,9 @@ const transformPayloadToCollection = (collectionPayload: CollectionPayload): Col isFacetRoot: collectionPayload.isFacetRoot, description: collectionPayload.description, childCount: collectionPayload.childCount, + ...(collectionPayload.theme && { + theme: collectionPayload.theme + }), ...(collectionPayload.isPartOf && { isPartOf: transformPayloadToOwnerNode(collectionPayload.isPartOf) }), From c6a42d4b3e0512a726625945b6368a31af1b059d Mon Sep 17 00:00:00 2001 From: gerlach Date: Fri, 6 Feb 2026 11:51:43 +0100 Subject: [PATCH 012/127] fix: Use Partial CollectionDTO for UpdateCollection --- src/collections/domain/repositories/ICollectionsRepository.ts | 2 +- src/collections/domain/useCases/UpdateCollection.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/collections/domain/repositories/ICollectionsRepository.ts b/src/collections/domain/repositories/ICollectionsRepository.ts index cae28415..8569ad13 100644 --- a/src/collections/domain/repositories/ICollectionsRepository.ts +++ b/src/collections/domain/repositories/ICollectionsRepository.ts @@ -45,7 +45,7 @@ export interface ICollectionsRepository { ): Promise updateCollection( collectionIdOrAlias: number | string, - updatedCollection: CollectionDTO + updatedCollection: Partial ): Promise getCollectionFeaturedItems(collectionIdOrAlias: number | string): Promise updateCollectionFeaturedItems( diff --git a/src/collections/domain/useCases/UpdateCollection.ts b/src/collections/domain/useCases/UpdateCollection.ts index f1068086..b32a6e05 100644 --- a/src/collections/domain/useCases/UpdateCollection.ts +++ b/src/collections/domain/useCases/UpdateCollection.ts @@ -19,7 +19,7 @@ export class UpdateCollection implements UseCase { */ async execute( collectionIdOrAlias: number | string, - updatedCollection: CollectionDTO + updatedCollection: Partial ): Promise { return await this.collectionsRepository.updateCollection(collectionIdOrAlias, updatedCollection) } From ab21d01f84d7230dcd688856bbfbe01a3a5909d6 Mon Sep 17 00:00:00 2001 From: gerlach Date: Fri, 6 Feb 2026 12:51:25 +0100 Subject: [PATCH 013/127] fix: Updated CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e56fe504..19ca1bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel ### Fixed +- Allow partial CollectionDTO for UpdateCollection use case + ### 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. From eee6e62a432b32bb061413bd51046c427239b50d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 18:15:16 +0000 Subject: [PATCH 014/127] chore(deps): bump axios from 1.12.2 to 1.13.5 Bumps [axios](https://github.com/axios/axios) from 1.12.2 to 1.13.5. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.12.2...v1.13.5) --- updated-dependencies: - dependency-name: axios dependency-version: 1.13.5 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package-lock.json | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 40941f67..4c56d617 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2582,13 +2582,13 @@ } }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -4458,15 +4458,16 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -4514,9 +4515,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", From 1cb805e0b2c1e5125a5e386e671984dad3b3dd84 Mon Sep 17 00:00:00 2001 From: Vera Clemens <16904069+vera@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:57:29 +0100 Subject: [PATCH 015/127] Apply suggestion from @ekraffmiller Co-authored-by: Ellen Kraffmiller --- src/core/infra/repositories/ApiConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/infra/repositories/ApiConfig.ts b/src/core/infra/repositories/ApiConfig.ts index 04444b2b..24a24d6e 100644 --- a/src/core/infra/repositories/ApiConfig.ts +++ b/src/core/infra/repositories/ApiConfig.ts @@ -10,7 +10,7 @@ export class ApiConfig { dataverseApiAuthMechanism: DataverseApiAuthMechanism, dataverseApiKey?: string, bearerTokenLocalStorageKey?: string, - bearerTokenGetFunction?: () => string + bearerTokenGetFunction?: () => string | null ) { this.dataverseApiUrl = dataverseApiUrl this.dataverseApiAuthMechanism = dataverseApiAuthMechanism From 7f37fe15c9dfa69f7d1d0d326dae8ac6e9327405 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Mon, 23 Feb 2026 10:18:06 -0500 Subject: [PATCH 016/127] add use cases for editing template to TemplatesRepository.ts --- .../dtos/UpdateTemplateLicenseTermsDTO.ts | 6 + .../domain/dtos/UpdateTemplateMetadataDTO.ts | 7 ++ .../repositories/ITemplatesRepository.ts | 13 ++ .../useCases/UpdateTemplateLicenseTerms.ts | 15 +++ .../domain/useCases/UpdateTemplateMetadata.ts | 19 +++ .../useCases/UpdateTemplateTermsOfAccess.ts | 15 +++ src/templates/index.ts | 18 ++- .../infra/repositories/TemplatesRepository.ts | 43 +++++++ .../templateTermsOfAccessTransformers.ts | 30 +++++ test/environment/.env | 4 +- .../UpdateTemplateLicenseTerms.test.ts | 42 +++++++ .../templates/UpdateTemplateMetadata.test.ts | 76 +++++++++++ .../UpdateTemplateTermsOfAccess.test.ts | 42 +++++++ ...TemplatesRepositoryTemplateUpdates.test.ts | 118 ++++++++++++++++++ .../UpdateTemplateLicenseTerms.test.ts | 36 ++++++ .../templates/UpdateTemplateMetadata.test.ts | 45 +++++++ .../UpdateTemplateTermsOfAccess.test.ts | 35 ++++++ 17 files changed, 561 insertions(+), 3 deletions(-) create mode 100644 src/templates/domain/dtos/UpdateTemplateLicenseTermsDTO.ts create mode 100644 src/templates/domain/dtos/UpdateTemplateMetadataDTO.ts create mode 100644 src/templates/domain/useCases/UpdateTemplateLicenseTerms.ts create mode 100644 src/templates/domain/useCases/UpdateTemplateMetadata.ts create mode 100644 src/templates/domain/useCases/UpdateTemplateTermsOfAccess.ts create mode 100644 src/templates/infra/repositories/transformers/templateTermsOfAccessTransformers.ts create mode 100644 test/functional/templates/UpdateTemplateLicenseTerms.test.ts create mode 100644 test/functional/templates/UpdateTemplateMetadata.test.ts create mode 100644 test/functional/templates/UpdateTemplateTermsOfAccess.test.ts create mode 100644 test/unit/templates/TemplatesRepositoryTemplateUpdates.test.ts create mode 100644 test/unit/templates/UpdateTemplateLicenseTerms.test.ts create mode 100644 test/unit/templates/UpdateTemplateMetadata.test.ts create mode 100644 test/unit/templates/UpdateTemplateTermsOfAccess.test.ts diff --git a/src/templates/domain/dtos/UpdateTemplateLicenseTermsDTO.ts b/src/templates/domain/dtos/UpdateTemplateLicenseTermsDTO.ts new file mode 100644 index 00000000..b476a07d --- /dev/null +++ b/src/templates/domain/dtos/UpdateTemplateLicenseTermsDTO.ts @@ -0,0 +1,6 @@ +import { CustomTerms } from '../../../datasets/domain/models/Dataset' + +export interface UpdateTemplateLicenseTermsDTO { + name?: string + customTerms?: CustomTerms +} diff --git a/src/templates/domain/dtos/UpdateTemplateMetadataDTO.ts b/src/templates/domain/dtos/UpdateTemplateMetadataDTO.ts new file mode 100644 index 00000000..3504fcea --- /dev/null +++ b/src/templates/domain/dtos/UpdateTemplateMetadataDTO.ts @@ -0,0 +1,7 @@ +import { TemplateFieldDTO, TemplateInstructionDTO } from './CreateTemplateDTO' + +export interface UpdateTemplateMetadataDTO { + name?: string + fields?: TemplateFieldDTO[] + instructions?: TemplateInstructionDTO[] +} diff --git a/src/templates/domain/repositories/ITemplatesRepository.ts b/src/templates/domain/repositories/ITemplatesRepository.ts index 3482d340..135aec3c 100644 --- a/src/templates/domain/repositories/ITemplatesRepository.ts +++ b/src/templates/domain/repositories/ITemplatesRepository.ts @@ -1,8 +1,21 @@ import { CreateTemplateDTO } from '../dtos/CreateTemplateDTO' +import { UpdateTemplateLicenseTermsDTO } from '../dtos/UpdateTemplateLicenseTermsDTO' +import { UpdateTemplateMetadataDTO } from '../dtos/UpdateTemplateMetadataDTO' import { Template } from '../models/Template' +import { TermsOfAccess } from '../../../datasets/domain/models/Dataset' export interface ITemplatesRepository { createTemplate(collectionIdOrAlias: number | string, template: CreateTemplateDTO): Promise + updateTemplateMetadata( + templateId: number, + payload: UpdateTemplateMetadataDTO, + replace?: boolean + ): Promise + updateTemplateLicenseTerms( + templateId: number, + payload: UpdateTemplateLicenseTermsDTO + ): Promise + updateTemplateTermsOfAccess(templateId: number, termsOfAccess: TermsOfAccess): Promise getTemplate(templateId: number): Promise