Skip to content

Commit f9e7389

Browse files
committed
feat: use case set dataset license
1 parent 9d02003 commit f9e7389

File tree

9 files changed

+338
-2
lines changed

9 files changed

+338
-2
lines changed

docs/useCases.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ The different use cases currently available in the package are classified below,
4444
- [Datasets write use cases](#datasets-write-use-cases)
4545
- [Create a Dataset](#create-a-dataset)
4646
- [Update a Dataset](#update-a-dataset)
47+
- [Update a Dataset License](#update-a-dataset-license)
4748
- [Publish a Dataset](#publish-a-dataset)
4849
- [Deaccession a Dataset](#deaccession-a-dataset)
4950
- [Delete a Draft Dataset](#delete-a-draft-dataset)
@@ -977,6 +978,43 @@ updateDataset.execute(datasetId, datasetDTO)
977978

978979
_See [use case](../src/datasets/domain/useCases/UpdateDataset.ts) implementation_.
979980

981+
#### Update a Dataset License
982+
983+
Updates the license of a dataset by applying it to the draft version. If no draft exists, a new one is automatically created by the API. Supports predefined licenses (by name) or custom terms of use and access.
984+
985+
##### Example calls:
986+
987+
```typescript
988+
import {
989+
updateDatasetLicense,
990+
DatasetLicenseUpdateRequest
991+
} from '@iqss/dataverse-client-javascript'
992+
993+
/* ... */
994+
995+
const datasetId = 1
996+
997+
const predefinedPayload: DatasetLicenseUpdateRequest = { name: 'CC BY 4.0' }
998+
await updateDatasetLicense.execute(datasetId, predefinedPayload)
999+
1000+
const customPayload: DatasetLicenseUpdateRequest = {
1001+
customTerms: {
1002+
termsOfUse: 'Your terms of use',
1003+
confidentialityDeclaration: 'Your confidentiality declaration',
1004+
specialPermissions: 'Your special permissions',
1005+
restrictions: 'Your restrictions',
1006+
citationRequirements: 'Your citation requirements',
1007+
depositorRequirements: 'Your depositor requirements',
1008+
conditions: 'Your conditions',
1009+
disclaimer: 'Your disclaimer'
1010+
}
1011+
}
1012+
1013+
updateDatasetLicense.execute(datasetId, customPayload)
1014+
```
1015+
1016+
_See [use case](../src/datasets/domain/useCases/UpdateDatasetLicense.ts) implementation_.
1017+
9801018
The `datasetId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers.
9811019

9821020
#### Publish a Dataset
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { CustomTerms } from '../../domain/models/Dataset'
2+
3+
export interface DatasetLicenseUpdateRequest {
4+
name?: string
5+
customTerms?: CustomTerms
6+
}

src/datasets/domain/repositories/IDatasetsRepository.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { CitationFormat } from '../models/CitationFormat'
1414
import { FormattedCitation } from '../models/FormattedCitation'
1515
import { DatasetTemplate } from '../models/DatasetTemplate'
1616
import { DatasetType } from '../models/DatasetType'
17+
import { DatasetLicenseUpdateRequest } from '../dtos/DatasetLicenseUpdateRequest'
1718

1819
export interface IDatasetsRepository {
1920
getDataset(
@@ -89,4 +90,8 @@ export interface IDatasetsRepository {
8990
licenses: string[]
9091
): Promise<void>
9192
deleteDatasetType(datasetTypeId: number): Promise<void>
93+
updateDatasetLicense(
94+
datasetId: number | string,
95+
payload: DatasetLicenseUpdateRequest
96+
): Promise<void>
9297
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { IDatasetsRepository } from '../repositories/IDatasetsRepository'
3+
import { DatasetLicenseUpdateRequest } from '../dtos/DatasetLicenseUpdateRequest'
4+
5+
export class UpdateDatasetLicense implements UseCase<void> {
6+
private readonly datasetsRepository: IDatasetsRepository
7+
8+
constructor(datasetsRepository: IDatasetsRepository) {
9+
this.datasetsRepository = datasetsRepository
10+
}
11+
12+
/**
13+
* Updates the license of a dataset by applying it to the draft version. If no draft exists, a new one is created by the API.
14+
* Supports either predefined license by name or custom terms of use and access.
15+
*
16+
* @param {number | string} datasetId - The dataset identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
17+
* @param {DatasetLicenseUpdateRequest} payload - The payload containing the license name or custom terms of use and access.
18+
* @returns {Promise<void>} - This method does not return anything upon successful completion.
19+
*/
20+
async execute(datasetId: number | string, payload: DatasetLicenseUpdateRequest): Promise<void> {
21+
return this.datasetsRepository.updateDatasetLicense(datasetId, payload)
22+
}
23+
}

src/datasets/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { SetAvailableLicensesForDatasetType } from './domain/useCases/SetAvailab
3232
import { DeleteDatasetType } from './domain/useCases/DeleteDatasetType'
3333
import { GetDatasetCitationInOtherFormats } from './domain/useCases/GetDatasetCitationInOtherFormats'
3434
import { GetDatasetTemplates } from './domain/useCases/GetDatasetTemplates'
35+
import { UpdateDatasetLicense } from './domain/useCases/UpdateDatasetLicense'
3536

3637
const datasetsRepository = new DatasetsRepository()
3738

@@ -80,6 +81,7 @@ const setAvailableLicensesForDatasetType = new SetAvailableLicensesForDatasetTyp
8081
const deleteDatasetType = new DeleteDatasetType(datasetsRepository)
8182
const getDatasetCitationInOtherFormats = new GetDatasetCitationInOtherFormats(datasetsRepository)
8283
const getDatasetTemplates = new GetDatasetTemplates(datasetsRepository)
84+
const updateDatasetLicense = new UpdateDatasetLicense(datasetsRepository)
8385

8486
export {
8587
getDataset,
@@ -109,7 +111,8 @@ export {
109111
addDatasetType,
110112
linkDatasetTypeWithMetadataBlocks,
111113
setAvailableLicensesForDatasetType,
112-
deleteDatasetType
114+
deleteDatasetType,
115+
updateDatasetLicense
113116
}
114117
export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion'
115118
export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions'
@@ -136,6 +139,7 @@ export {
136139
DatasetMetadataBlockValuesDTO,
137140
DatasetMetadataChildFieldValueDTO
138141
} from './domain/dtos/DatasetDTO'
142+
export { DatasetLicenseUpdateRequest } from './domain/dtos/DatasetLicenseUpdateRequest'
139143
export { DatasetDeaccessionDTO } from './domain/dtos/DatasetDeaccessionDTO'
140144
export { CreatedDatasetIdentifiers } from './domain/models/CreatedDatasetIdentifiers'
141145
export { VersionUpdateType } from './domain/models/Dataset'

src/datasets/infra/repositories/DatasetsRepository.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { DatasetTemplate } from '../../domain/models/DatasetTemplate'
2929
import { DatasetTemplatePayload } from './transformers/DatasetTemplatePayload'
3030
import { transformDatasetTemplatePayloadToDatasetTemplate } from './transformers/datasetTemplateTransformers'
3131
import { DatasetType } from '../../domain/models/DatasetType'
32+
import { DatasetLicenseUpdateRequest } from '../../domain/dtos/DatasetLicenseUpdateRequest'
3233

3334
export interface GetAllDatasetPreviewsQueryParams {
3435
per_page?: number
@@ -448,4 +449,18 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
448449
throw error
449450
})
450451
}
452+
453+
public async updateDatasetLicense(
454+
datasetId: number | string,
455+
payload: DatasetLicenseUpdateRequest
456+
): Promise<void> {
457+
return this.doPut(
458+
this.buildApiEndpoint(this.datasetsResourceName, 'license', datasetId),
459+
payload
460+
)
461+
.then(() => undefined)
462+
.catch((error) => {
463+
throw error
464+
})
465+
}
451466
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import {
2+
ApiConfig,
3+
createDataset,
4+
getDataset,
5+
publishDataset,
6+
updateDatasetLicense,
7+
DatasetLicenseUpdateRequest
8+
} from '../../../src'
9+
import { TestConstants } from '../../testHelpers/TestConstants'
10+
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
11+
import {
12+
waitForNoLocks,
13+
deleteUnpublishedDatasetViaApi,
14+
deletePublishedDatasetViaApi
15+
} from '../../testHelpers/datasets/datasetHelper'
16+
import { DatasetNotNumberedVersion, VersionUpdateType } from '../../../src/datasets'
17+
18+
describe('execute', () => {
19+
beforeEach(async () => {
20+
ApiConfig.init(
21+
TestConstants.TEST_API_URL,
22+
DataverseApiAuthMechanism.API_KEY,
23+
process.env.TEST_API_KEY
24+
)
25+
})
26+
27+
test('should update the license of a draft dataset (predefined by name)', async () => {
28+
const created = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
29+
30+
const payload: DatasetLicenseUpdateRequest = { name: 'CC BY 4.0' }
31+
const response = await updateDatasetLicense.execute(created.numericId, payload)
32+
33+
expect(response).toBeUndefined()
34+
35+
const after = await getDataset.execute(
36+
created.numericId,
37+
DatasetNotNumberedVersion.DRAFT,
38+
false,
39+
false
40+
)
41+
expect(after.license?.name).toBe('CC BY 4.0')
42+
43+
await deleteUnpublishedDatasetViaApi(created.numericId)
44+
})
45+
46+
test('should update the license of a published dataset (custom terms creates draft)', async () => {
47+
const created = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
48+
49+
await publishDataset.execute(created.numericId, VersionUpdateType.MAJOR)
50+
await waitForNoLocks(created.numericId, 10)
51+
52+
const payload: DatasetLicenseUpdateRequest = {
53+
customTerms: {
54+
termsOfUse: 'Updated terms of use (functional test)'
55+
}
56+
}
57+
const response = await updateDatasetLicense.execute(created.numericId, payload)
58+
59+
expect(response).toBeUndefined()
60+
61+
const draft = await getDataset.execute(
62+
created.numericId,
63+
DatasetNotNumberedVersion.DRAFT,
64+
false,
65+
false
66+
)
67+
expect(draft.license).toBeUndefined()
68+
expect(draft.termsOfUse.customTerms?.termsOfUse).toBe('Updated terms of use (functional test)')
69+
70+
await deletePublishedDatasetViaApi(created.persistentId)
71+
})
72+
})

test/integration/datasets/DatasetsRepository.test.ts

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import {
2727
addDatasetType,
2828
deleteDatasetType,
2929
linkDatasetTypeWithMetadataBlocks,
30-
setAvailableLicensesForDatasetType
30+
setAvailableLicensesForDatasetType,
31+
DatasetLicenseUpdateRequest
3132
} from '../../../src/datasets'
3233
import { ApiConfig, WriteError } from '../../../src'
3334
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
@@ -1800,4 +1801,148 @@ describe('DatasetsRepository', () => {
18001801
})
18011802
})
18021803
})
1804+
1805+
describe('updateDatasetLicense', () => {
1806+
test('should update the license of a published dataset', async () => {
1807+
const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
1808+
await publishDatasetViaApi(testDatasetIds.numericId)
1809+
await waitForNoLocks(testDatasetIds.numericId, 10)
1810+
1811+
const DatasetBefore = await sut.getDataset(
1812+
testDatasetIds.numericId,
1813+
DatasetNotNumberedVersion.LATEST,
1814+
false,
1815+
false
1816+
)
1817+
expect(DatasetBefore.license?.name).toBe('CC0 1.0') // default license
1818+
1819+
const payload: DatasetLicenseUpdateRequest = { name: 'CC BY 4.0' }
1820+
await sut.updateDatasetLicense(testDatasetIds.numericId, payload)
1821+
1822+
const DatasetAfter = await sut.getDataset(
1823+
testDatasetIds.numericId,
1824+
DatasetNotNumberedVersion.LATEST,
1825+
false,
1826+
false
1827+
)
1828+
expect(DatasetAfter.license?.name).toBe('CC BY 4.0')
1829+
})
1830+
1831+
test('should update the license of a draft dataset', async () => {
1832+
const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
1833+
1834+
const DatasetBefore = await sut.getDataset(
1835+
testDatasetIds.numericId,
1836+
DatasetNotNumberedVersion.LATEST,
1837+
false,
1838+
false
1839+
)
1840+
expect(DatasetBefore.license?.name).toBe('CC0 1.0') // default license
1841+
const predefined: DatasetLicenseUpdateRequest = { name: 'CC BY 4.0' }
1842+
await sut.updateDatasetLicense(testDatasetIds.numericId, predefined)
1843+
1844+
const datasetAfter = await sut.getDataset(
1845+
testDatasetIds.numericId,
1846+
DatasetNotNumberedVersion.DRAFT,
1847+
false,
1848+
false
1849+
)
1850+
1851+
expect(datasetAfter.license?.name).toBe('CC BY 4.0')
1852+
1853+
await deleteUnpublishedDatasetViaApi(testDatasetIds.numericId)
1854+
})
1855+
1856+
test('should set custom terms of use and access on the draft version', async () => {
1857+
const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
1858+
1859+
const custom: DatasetLicenseUpdateRequest = {
1860+
customTerms: {
1861+
termsOfUse: 'Your terms of use',
1862+
confidentialityDeclaration: 'Your confidentiality declaration',
1863+
specialPermissions: 'Your special permissions',
1864+
restrictions: 'Your restrictions',
1865+
citationRequirements: 'Your citation requirements',
1866+
depositorRequirements: 'Your depositor requirements',
1867+
conditions: 'Your conditions',
1868+
disclaimer: 'Your disclaimer'
1869+
}
1870+
}
1871+
const actual = await sut.updateDatasetLicense(testDatasetIds.numericId, custom)
1872+
1873+
expect(actual).toBeUndefined()
1874+
1875+
const datasetAfter = await sut.getDataset(
1876+
testDatasetIds.numericId,
1877+
DatasetNotNumberedVersion.DRAFT,
1878+
false,
1879+
false
1880+
)
1881+
1882+
expect(datasetAfter.license).toBeUndefined()
1883+
expect(datasetAfter.termsOfUse.customTerms?.termsOfUse).toBe('Your terms of use')
1884+
1885+
await deletePublishedDatasetViaApi(testDatasetIds.persistentId)
1886+
})
1887+
1888+
test('should set custom terms of use and access on the published version', async () => {
1889+
const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
1890+
await publishDatasetViaApi(testDatasetIds.numericId)
1891+
await waitForNoLocks(testDatasetIds.numericId, 10)
1892+
1893+
const custom: DatasetLicenseUpdateRequest = {
1894+
customTerms: {
1895+
termsOfUse: 'Your terms of use',
1896+
confidentialityDeclaration: 'Your confidentiality declaration',
1897+
specialPermissions: 'Your special permissions',
1898+
restrictions: 'Your restrictions',
1899+
citationRequirements: 'Your citation requirements',
1900+
depositorRequirements: 'Your depositor requirements',
1901+
conditions: 'Your conditions',
1902+
disclaimer: 'Your disclaimer'
1903+
}
1904+
}
1905+
const actual = await sut.updateDatasetLicense(testDatasetIds.numericId, custom)
1906+
1907+
expect(actual).toBeUndefined()
1908+
1909+
const datasetAfter = await sut.getDataset(
1910+
testDatasetIds.numericId,
1911+
DatasetNotNumberedVersion.DRAFT,
1912+
false,
1913+
false
1914+
)
1915+
1916+
expect(datasetAfter.license).toBeUndefined()
1917+
expect(datasetAfter.termsOfUse.customTerms?.termsOfUse).toBe('Your terms of use')
1918+
1919+
await deleteUnpublishedDatasetViaApi(testDatasetIds.numericId)
1920+
})
1921+
1922+
test('should return error when dataset does not exist', async () => {
1923+
const expectedError = new WriteError(
1924+
`[404] Dataset with ID ${nonExistentTestDatasetId} not found.`
1925+
)
1926+
1927+
await expect(
1928+
sut.updateDatasetLicense(nonExistentTestDatasetId, { name: 'CC BY 4.0' })
1929+
).rejects.toThrow(expectedError)
1930+
})
1931+
1932+
test('should accept persistent id when updating license on draft dataset', async () => {
1933+
const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
1934+
1935+
await sut.updateDatasetLicense(testDatasetIds.persistentId, { name: 'CC BY 4.0' })
1936+
1937+
const draftAfter = await sut.getDataset(
1938+
testDatasetIds.persistentId,
1939+
DatasetNotNumberedVersion.DRAFT,
1940+
false,
1941+
false
1942+
)
1943+
expect(draftAfter.license?.name).toBe('CC BY 4.0')
1944+
1945+
await deleteUnpublishedDatasetViaApi(testDatasetIds.numericId)
1946+
})
1947+
})
18031948
})

0 commit comments

Comments
 (0)