diff --git a/CHANGELOG.md b/CHANGELOG.md index 01908608..be773c3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel ### Added +- Datasets: Added `getDatasetStorageDriver` use case and repository method to support Dataverse endpoint `GET /datasets/{identifier}/storageDriver`, for retrieving dataset storage driver configuration with properties: name, type, label, directUpload, directDownload, and uploadOutOfBand. - Datasets: Added `updateDatasetLicense` use case and repository method to support Dataverse endpoint `PUT /datasets/{id}/license`, for updating dataset license or custom terms - New Use Case: [Get Collections For Linking Use Case](./docs/useCases.md#get-collections-for-linking). - New Use Case: [Create a Dataset Template](./docs/useCases.md#create-a-dataset-template) under Collections. diff --git a/docs/useCases.md b/docs/useCases.md index 3254e4a5..ed6c72ed 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -41,6 +41,7 @@ The different use cases currently available in the package are classified below, - [Get Dataset Linked Collections](#get-dataset-linked-collections) - [Get Dataset Available Categories](#get-dataset-available-categories) - [Get Dataset Templates](#get-dataset-templates) + - [Get Dataset Storage Driver](#get-dataset-storage-driver) - [Get Dataset Available Dataset Types](#get-dataset-available-dataset-types) - [Get Dataset Available Dataset Type](#get-dataset-available-dataset-type) - [Datasets write use cases](#datasets-write-use-cases) @@ -1351,6 +1352,30 @@ getDatasetTemplates.execute(collectionIdOrAlias).then((datasetTemplates: Dataset _See [use case](../src/datasets/domain/useCases/GetDatasetTemplates.ts)_ definition. +#### Get Dataset Storage Driver + +Returns a [StorageDriver](../src/datasets/domain/models/StorageDriver.ts) instance with storage driver configuration for a dataset, including properties like name, type, label, and upload/download capabilities. + +##### Example call: + +```typescript +import { getDatasetStorageDriver } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const datasetId = 'doi:10.77777/FK2/AAAAAA' + +getDatasetStorageDriver.execute(datasetId).then((storageDriver: StorageDriver) => { + /* ... */ +}) + +/* ... */ +``` + +_See [use case](../src/datasets/domain/useCases/GetDatasetStorageDriver.ts) implementation_. + +The `datasetId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers. + #### Add a Dataset Type Adds a dataset types that can be used at dataset creation. diff --git a/src/datasets/domain/models/StorageDriver.ts b/src/datasets/domain/models/StorageDriver.ts new file mode 100644 index 00000000..9c04b400 --- /dev/null +++ b/src/datasets/domain/models/StorageDriver.ts @@ -0,0 +1,8 @@ +export interface StorageDriver { + name: string + type: string + label: string + directUpload: boolean + directDownload: boolean + uploadOutOfBand: boolean +} diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 8a52f8f9..48e29de6 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -17,6 +17,7 @@ import { DatasetType } from '../models/DatasetType' import { TermsOfAccess } from '../models/Dataset' import { DatasetLicenseUpdateRequest } from '../dtos/DatasetLicenseUpdateRequest' import { DatasetTypeDTO } from '../dtos/DatasetTypeDTO' +import { StorageDriver } from '../models/StorageDriver' export interface IDatasetsRepository { getDataset( @@ -102,4 +103,5 @@ export interface IDatasetsRepository { datasetId: number | string, payload: DatasetLicenseUpdateRequest ): Promise + getDatasetStorageDriver(datasetId: number | string): Promise } diff --git a/src/datasets/domain/useCases/GetDatasetStorageDriver.ts b/src/datasets/domain/useCases/GetDatasetStorageDriver.ts new file mode 100644 index 00000000..361d1db9 --- /dev/null +++ b/src/datasets/domain/useCases/GetDatasetStorageDriver.ts @@ -0,0 +1,21 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { IDatasetsRepository } from '../repositories/IDatasetsRepository' +import { StorageDriver } from '../models/StorageDriver' + +export class GetDatasetStorageDriver implements UseCase { + private datasetsRepository: IDatasetsRepository + + constructor(datasetsRepository: IDatasetsRepository) { + this.datasetsRepository = datasetsRepository + } + + /** + * Returns the storage driver information for a given Dataset. + * + * @param {number | string} [datasetId] - The dataset identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers). + * @returns {Promise} + */ + async execute(datasetId: number | string): Promise { + return this.datasetsRepository.getDatasetStorageDriver(datasetId) + } +} diff --git a/src/datasets/index.ts b/src/datasets/index.ts index b8edb5b3..84b22e75 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -34,6 +34,7 @@ import { GetDatasetCitationInOtherFormats } from './domain/useCases/GetDatasetCi import { GetDatasetTemplates } from './domain/useCases/GetDatasetTemplates' import { UpdateTermsOfAccess } from './domain/useCases/UpdateTermsOfAccess' import { UpdateDatasetLicense } from './domain/useCases/UpdateDatasetLicense' +import { GetDatasetStorageDriver } from './domain/useCases/GetDatasetStorageDriver' const datasetsRepository = new DatasetsRepository() @@ -84,6 +85,7 @@ const getDatasetCitationInOtherFormats = new GetDatasetCitationInOtherFormats(da const getDatasetTemplates = new GetDatasetTemplates(datasetsRepository) const updateTermsOfAccess = new UpdateTermsOfAccess(datasetsRepository) const updateDatasetLicense = new UpdateDatasetLicense(datasetsRepository) +const getDatasetStorageDriver = new GetDatasetStorageDriver(datasetsRepository) export { getDataset, @@ -115,7 +117,8 @@ export { linkDatasetTypeWithMetadataBlocks, setAvailableLicensesForDatasetType, deleteDatasetType, - updateDatasetLicense + updateDatasetLicense, + getDatasetStorageDriver } export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion' export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions' @@ -154,3 +157,4 @@ export { export { DatasetLinkedCollection } from './domain/models/DatasetLinkedCollection' export { DatasetType } from './domain/models/DatasetType' export { DatasetTypeDTO } from './domain/dtos/DatasetTypeDTO' +export { StorageDriver } from './domain/models/StorageDriver' diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 849cf658..c50b30e1 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -33,6 +33,7 @@ import { TermsOfAccess } from '../../domain/models/Dataset' import { transformTermsOfAccessToUpdatePayload } from './transformers/termsOfAccessTransformers' import { DatasetLicenseUpdateRequest } from '../../domain/dtos/DatasetLicenseUpdateRequest' import { DatasetTypeDTO } from '../../domain/dtos/DatasetTypeDTO' +import { StorageDriver } from '../../domain/models/StorageDriver' export interface GetAllDatasetPreviewsQueryParams { per_page?: number @@ -515,4 +516,15 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi throw error }) } + + public async getDatasetStorageDriver(datasetId: number | string): Promise { + return this.doGet( + this.buildApiEndpoint(this.datasetsResourceName, 'storageDriver', datasetId), + true + ) + .then((response) => response.data.data as StorageDriver) + .catch((error) => { + throw error + }) + } } diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 5e3fa4b1..0f472b1f 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -2244,4 +2244,28 @@ describe('DatasetsRepository', () => { await deleteUnpublishedDatasetViaApi(testDatasetIds.numericId) }) }) + + describe('getDatasetStorageDriver', () => { + let testDatasetIds: CreatedDatasetIdentifiers + + beforeAll(async () => { + testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + await publishDatasetViaApi(testDatasetIds.numericId) + await waitForNoLocks(testDatasetIds.numericId, 10) + }) + + afterAll(async () => { + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + }) + + test('should return storage driver info for dataset', async () => { + const storageDriver = await sut.getDatasetStorageDriver(testDatasetIds.numericId) + expect(storageDriver).toHaveProperty('name') + expect(storageDriver).toHaveProperty('type') + expect(storageDriver).toHaveProperty('label') + expect(typeof storageDriver.directUpload).toBe('boolean') + expect(typeof storageDriver.directDownload).toBe('boolean') + expect(typeof storageDriver.uploadOutOfBand).toBe('boolean') + }) + }) }) diff --git a/test/unit/datasets/GetDatasetStorageDriver.test.ts b/test/unit/datasets/GetDatasetStorageDriver.test.ts new file mode 100644 index 00000000..bd55d164 --- /dev/null +++ b/test/unit/datasets/GetDatasetStorageDriver.test.ts @@ -0,0 +1,51 @@ +import { GetDatasetStorageDriver } from '../../../src/datasets/domain/useCases/GetDatasetStorageDriver' +import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository' +import { StorageDriver } from '../../../src/datasets/domain/models/StorageDriver' +import { ReadError } from '../../../src/core/domain/repositories/ReadError' + +describe('GetDatasetStorageDriver (unit)', () => { + const testStorageDriver: StorageDriver = { + name: 'local', + type: 'filesystem', + label: 'Local Storage', + directUpload: true, + directDownload: true, + uploadOutOfBand: false + } + + test('should return storage driver on repository success', async () => { + const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository + datasetsRepositoryStub.getDatasetStorageDriver = jest.fn().mockResolvedValue(testStorageDriver) + const sut = new GetDatasetStorageDriver(datasetsRepositoryStub) + + const actual = await sut.execute(1) + + expect(actual).toEqual(testStorageDriver) + expect(actual.name).toBe('local') + expect(actual.type).toBe('filesystem') + expect(actual.label).toBe('Local Storage') + expect(actual.directUpload).toBe(true) + expect(actual.directDownload).toBe(true) + expect(actual.uploadOutOfBand).toBe(false) + }) + + test('should return storage driver when using persistent id', async () => { + const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository + datasetsRepositoryStub.getDatasetStorageDriver = jest.fn().mockResolvedValue(testStorageDriver) + const sut = new GetDatasetStorageDriver(datasetsRepositoryStub) + + const actual = await sut.execute('doi:10.77777/FK2/AAAAAA') + + expect(actual).toEqual(testStorageDriver) + }) + + test('should return error result on repository error', async () => { + const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository + datasetsRepositoryStub.getDatasetStorageDriver = jest + .fn() + .mockRejectedValue(new ReadError('[404] Dataset not found')) + const sut = new GetDatasetStorageDriver(datasetsRepositoryStub) + + await expect(sut.execute(1)).rejects.toThrow(ReadError) + }) +})