Skip to content

Commit b0929de

Browse files
committed
feat: feat: add use case to edit terms of access
1 parent cfe34ea commit b0929de

File tree

11 files changed

+341
-2
lines changed

11 files changed

+341
-2
lines changed

docs/useCases.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,38 @@ The `versionUpdateType` parameter can be a [VersionUpdateType](../src/datasets/d
10121012
- `VersionUpdateType.MAJOR`
10131013
- `VersionUpdateType.UPDATE_CURRENT`
10141014

1015+
#### Update Terms of Access
1016+
1017+
Updates the Terms of Access for restricted files on a dataset.
1018+
1019+
##### Example call:
1020+
1021+
```typescript
1022+
import { updateTermsOfAccess } from '@iqss/dataverse-client-javascript'
1023+
1024+
/* ... */
1025+
1026+
const datasetId = 3
1027+
1028+
await updateTermsOfAccess.execute(datasetId, {
1029+
fileAccessRequest: true,
1030+
termsOfAccessForRestrictedFiles: 'Your terms of access for restricted files',
1031+
dataAccessPlace: 'Your data access place',
1032+
originalArchive: 'Your original archive',
1033+
availabilityStatus: 'Your availability status',
1034+
contactForAccess: 'Your contact for access',
1035+
sizeOfCollection: 'Your size of collection',
1036+
studyCompletion: 'Your study completion'
1037+
})
1038+
```
1039+
1040+
_See [use case](../src/datasets/domain/useCases/UpdateTermsOfAccess.ts) implementation_.
1041+
1042+
Notes:
1043+
1044+
- If the dataset is already published, this action creates a DRAFT version containing the new terms.
1045+
- Unspecified fields are treated as omissions: sending only `fileAccessRequest` will update that field and leave all other terms absent (undefined). In practice, the new values you send fully replace the previous set of terms — so if you omit a field, you are effectively clearing it unless you include its original value in the new input.
1046+
10151047
#### Deaccession a Dataset
10161048

10171049
Deaccession a Dataset, given its identifier, version, and deaccessionDatasetDTO to perform.

src/datasets/domain/models/Dataset.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export interface CustomTerms {
5151
conditions?: string
5252
disclaimer?: string
5353
}
54+
5455
export interface TermsOfAccess {
5556
fileAccessRequest: boolean
5657
termsOfAccessForRestrictedFiles?: string

src/datasets/domain/repositories/IDatasetsRepository.ts

Lines changed: 2 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 { TermsOfAccess } from '../models/Dataset'
1718

1819
export interface IDatasetsRepository {
1920
getDataset(
@@ -90,4 +91,5 @@ export interface IDatasetsRepository {
9091
licenses: string[]
9192
): Promise<void>
9293
deleteDatasetType(datasetTypeId: number): Promise<void>
94+
updateTermsOfAccess(datasetId: number | string, termsOfAccess: TermsOfAccess): Promise<void>
9395
}

src/datasets/domain/useCases/SetAvailableLicensesForDatasetType.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export class SetAvailableLicensesForDatasetType implements UseCase<void> {
1010

1111
/**
1212
* Sets the available licenses for a given dataset type. This limits the license options when creating a dataset of this type.
13+
*
14+
* @param {number | string} [datasetTypeId] - The dataset type identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
15+
* @param {string[]} licenses - The licenses to set for the dataset type.
16+
* @returns {Promise<void>} - This method does not return anything upon successful completion.
1317
*/
1418
async execute(datasetTypeId: number | string, licenses: string[]): Promise<void> {
1519
return await this.datasetsRepository.setAvailableLicensesForDatasetType(datasetTypeId, licenses)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { IDatasetsRepository } from '../repositories/IDatasetsRepository'
3+
import { TermsOfAccess } from '../models/Dataset'
4+
5+
export class UpdateTermsOfAccess implements UseCase<void> {
6+
private datasetsRepository: IDatasetsRepository
7+
8+
constructor(datasetsRepository: IDatasetsRepository) {
9+
this.datasetsRepository = datasetsRepository
10+
}
11+
12+
/**
13+
* Sets the terms of access for a given dataset.
14+
*
15+
* @param {number | string} [datasetId] - The dataset identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
16+
* @param {TermsOfAccess} termsOfAccess - The terms of access to set for the dataset.
17+
* @returns {Promise<void>} - This method does not return anything upon successful completion.
18+
*/
19+
async execute(datasetId: number | string, termsOfAccess: TermsOfAccess): Promise<void> {
20+
return await this.datasetsRepository.updateTermsOfAccess(datasetId, termsOfAccess)
21+
}
22+
}

src/datasets/index.ts

Lines changed: 3 additions & 0 deletions
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 { UpdateTermsOfAccess } from './domain/useCases/UpdateTermsOfAccess'
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 updateTermsOfAccess = new UpdateTermsOfAccess(datasetsRepository)
8385

8486
export {
8587
getDataset,
@@ -104,6 +106,7 @@ export {
104106
getDatasetAvailableCategories,
105107
getDatasetCitationInOtherFormats,
106108
getDatasetTemplates,
109+
updateTermsOfAccess,
107110
getDatasetAvailableDatasetTypes,
108111
getDatasetAvailableDatasetType,
109112
addDatasetType,

src/datasets/infra/repositories/DatasetsRepository.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ 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 { TermsOfAccess } from '../../domain/models/Dataset'
33+
import { transformTermsOfAccessToUpdatePayload } from './transformers/termsOfAccessTransformers'
3234

3335
export interface GetAllDatasetPreviewsQueryParams {
3436
per_page?: number
@@ -451,4 +453,20 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
451453
throw error
452454
})
453455
}
456+
457+
public async updateTermsOfAccess(
458+
datasetId: number | string,
459+
termsOfAccess: TermsOfAccess
460+
): Promise<void> {
461+
return this.doPut(
462+
this.buildApiEndpoint(this.datasetsResourceName, 'access', datasetId),
463+
transformTermsOfAccessToUpdatePayload(
464+
termsOfAccess as TermsOfAccess & { termsOfAccess?: string }
465+
)
466+
)
467+
.then(() => undefined)
468+
.catch((error) => {
469+
throw error
470+
})
471+
}
454472
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { TermsOfAccess } from '../../../domain/models/Dataset'
2+
3+
type TermsOfAccessInput = TermsOfAccess & { termsOfAccess?: string }
4+
5+
export const transformTermsOfAccessToUpdatePayload = (terms: TermsOfAccessInput) => {
6+
const {
7+
fileAccessRequest,
8+
dataAccessPlace,
9+
originalArchive,
10+
availabilityStatus,
11+
contactForAccess,
12+
sizeOfCollection,
13+
studyCompletion
14+
} = terms
15+
16+
const termsOfAccessForRestrictedFiles =
17+
terms.termsOfAccess ?? terms.termsOfAccessForRestrictedFiles
18+
19+
return {
20+
customTermsOfAccess: {
21+
fileAccessRequest,
22+
termsOfAccess: termsOfAccessForRestrictedFiles,
23+
dataAccessPlace,
24+
originalArchive,
25+
availabilityStatus,
26+
contactForAccess,
27+
sizeOfCollection,
28+
studyCompletion
29+
}
30+
}
31+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { TestConstants } from '../../testHelpers/TestConstants'
2+
import {
3+
ApiConfig,
4+
DataverseApiAuthMechanism
5+
} from '../../../src/core/infra/repositories/ApiConfig'
6+
import {
7+
createDataset,
8+
DatasetNotNumberedVersion,
9+
getDataset,
10+
updateTermsOfAccess
11+
} from '../../../src/datasets'
12+
import { WriteError } from '../../../src'
13+
14+
describe('UpdateTermsOfAccess (functional)', () => {
15+
beforeAll(() => {
16+
ApiConfig.init(
17+
TestConstants.TEST_API_URL,
18+
DataverseApiAuthMechanism.API_KEY,
19+
process.env.TEST_API_KEY
20+
)
21+
})
22+
23+
test('should update terms of access with provided fields', async () => {
24+
const ids = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
25+
26+
await updateTermsOfAccess.execute(ids.numericId, {
27+
fileAccessRequest: true,
28+
termsOfAccessForRestrictedFiles: 'Your terms',
29+
dataAccessPlace: 'Place'
30+
})
31+
32+
const dataset = await getDataset.execute(
33+
ids.numericId,
34+
DatasetNotNumberedVersion.LATEST,
35+
false,
36+
false
37+
)
38+
39+
expect(dataset.termsOfUse.termsOfAccess.fileAccessRequest).toBe(true)
40+
expect(dataset.termsOfUse.termsOfAccess.termsOfAccessForRestrictedFiles).toBe('Your terms')
41+
expect(dataset.termsOfUse.termsOfAccess.dataAccessPlace).toBe('Place')
42+
})
43+
44+
test('should throw when dataset does not exist', async () => {
45+
await expect(
46+
updateTermsOfAccess.execute(999999, {
47+
fileAccessRequest: false
48+
})
49+
).rejects.toBeInstanceOf(WriteError)
50+
})
51+
})

test/integration/datasets/DatasetsRepository.test.ts

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import {
2727
addDatasetType,
2828
deleteDatasetType,
2929
linkDatasetTypeWithMetadataBlocks,
30-
setAvailableLicensesForDatasetType
30+
setAvailableLicensesForDatasetType,
31+
updateTermsOfAccess
3132
} from '../../../src/datasets'
3233
import { ApiConfig, WriteError } from '../../../src'
3334
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
@@ -36,7 +37,8 @@ import {
3637
Author,
3738
DatasetContact,
3839
DatasetDescription,
39-
Publication
40+
Publication,
41+
TermsOfAccess
4042
} from '../../../src/datasets/domain/models/Dataset'
4143
import {
4244
createCollectionViaApi,
@@ -1850,4 +1852,143 @@ describe('DatasetsRepository', () => {
18501852
})
18511853
})
18521854
})
1855+
1856+
describe('updateTermsOfAccess', () => {
1857+
let testDatasetIds: CreatedDatasetIdentifiers
1858+
1859+
console.log(
1860+
'authentication',
1861+
TestConstants.TEST_API_URL,
1862+
DataverseApiAuthMechanism.API_KEY,
1863+
process.env.TEST_API_KEY
1864+
)
1865+
beforeAll(async () => {
1866+
testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
1867+
})
1868+
1869+
test('should update the terms of access for a dataset', async () => {
1870+
const datasetBefore = await sut.getDataset(
1871+
testDatasetIds.numericId,
1872+
DatasetNotNumberedVersion.LATEST,
1873+
false,
1874+
false
1875+
)
1876+
1877+
const termsOfAccessBefore: TermsOfAccess = {
1878+
fileAccessRequest: true,
1879+
termsOfAccessForRestrictedFiles: undefined,
1880+
dataAccessPlace: undefined,
1881+
originalArchive: undefined,
1882+
availabilityStatus: undefined,
1883+
contactForAccess: undefined,
1884+
sizeOfCollection: undefined,
1885+
studyCompletion: undefined
1886+
}
1887+
expect(datasetBefore.termsOfUse.termsOfAccess).toEqual(termsOfAccessBefore)
1888+
1889+
const termsOfAccessAfter: TermsOfAccess = {
1890+
fileAccessRequest: false,
1891+
termsOfAccessForRestrictedFiles: 'Your terms of access for restricted files',
1892+
dataAccessPlace: 'Your data access place',
1893+
originalArchive: 'Your original archive',
1894+
availabilityStatus: 'Your availability status',
1895+
contactForAccess: 'Your contact for access',
1896+
sizeOfCollection: 'Your size of collection',
1897+
studyCompletion: 'Your study completion'
1898+
}
1899+
1900+
await updateTermsOfAccess.execute(testDatasetIds.numericId, termsOfAccessAfter)
1901+
1902+
const datasetAfter = await sut.getDataset(
1903+
testDatasetIds.numericId,
1904+
DatasetNotNumberedVersion.LATEST,
1905+
false,
1906+
false
1907+
)
1908+
1909+
expect(datasetAfter.termsOfUse.termsOfAccess).toEqual(termsOfAccessAfter)
1910+
})
1911+
1912+
test('should throw error when dataset does not exist', async () => {
1913+
const nonExistentId = 999999
1914+
await expect(
1915+
updateTermsOfAccess.execute(nonExistentId, {
1916+
fileAccessRequest: true
1917+
})
1918+
).rejects.toBeInstanceOf(WriteError)
1919+
})
1920+
1921+
test('should accept only fileAccessRequest field', async () => {
1922+
const ids = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
1923+
1924+
await updateTermsOfAccess.execute(ids.numericId, {
1925+
fileAccessRequest: false
1926+
})
1927+
1928+
const dataset = await sut.getDataset(
1929+
ids.numericId,
1930+
DatasetNotNumberedVersion.LATEST,
1931+
false,
1932+
false
1933+
)
1934+
1935+
expect(dataset.termsOfUse.termsOfAccess.fileAccessRequest).toBe(false)
1936+
expect(dataset.termsOfUse.termsOfAccess.dataAccessPlace).toBeUndefined()
1937+
expect(dataset.termsOfUse.termsOfAccess.originalArchive).toBeUndefined()
1938+
expect(dataset.termsOfUse.termsOfAccess.availabilityStatus).toBeUndefined()
1939+
expect(dataset.termsOfUse.termsOfAccess.contactForAccess).toBeUndefined()
1940+
expect(dataset.termsOfUse.termsOfAccess.sizeOfCollection).toBeUndefined()
1941+
expect(dataset.termsOfUse.termsOfAccess.studyCompletion).toBeUndefined()
1942+
})
1943+
1944+
test('should work when identifying dataset by persistent id', async () => {
1945+
const ids = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
1946+
1947+
await updateTermsOfAccess.execute(ids.persistentId, {
1948+
termsOfAccessForRestrictedFiles: 'Persistent terms',
1949+
fileAccessRequest: false
1950+
})
1951+
1952+
const dataset = await sut.getDataset(
1953+
ids.persistentId,
1954+
DatasetNotNumberedVersion.LATEST,
1955+
false,
1956+
false
1957+
)
1958+
1959+
expect(dataset.persistentId).toBe(ids.persistentId)
1960+
expect(dataset.termsOfUse.termsOfAccess.fileAccessRequest).toBe(false)
1961+
expect(dataset.termsOfUse.termsOfAccess.termsOfAccessForRestrictedFiles).toBe(
1962+
'Persistent terms'
1963+
)
1964+
})
1965+
1966+
test('should update terms on a published dataset (creates a draft)', async () => {
1967+
const ids = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
1968+
1969+
await publishDataset.execute(ids.numericId, VersionUpdateType.MAJOR)
1970+
await waitForNoLocks(ids.numericId, 10)
1971+
1972+
await updateTermsOfAccess.execute(ids.numericId, {
1973+
fileAccessRequest: true,
1974+
termsOfAccessForRestrictedFiles: 'Updated after publish'
1975+
})
1976+
1977+
await waitForNoLocks(ids.numericId, 10)
1978+
1979+
const dataset = await sut.getDataset(
1980+
ids.numericId,
1981+
DatasetNotNumberedVersion.LATEST,
1982+
false,
1983+
false
1984+
)
1985+
1986+
expect(dataset.versionInfo.state).toBe('DRAFT')
1987+
expect(dataset.termsOfUse.termsOfAccess.termsOfAccessForRestrictedFiles).toBe(
1988+
'Updated after publish'
1989+
)
1990+
1991+
await deletePublishedDatasetViaApi(ids.persistentId)
1992+
})
1993+
})
18531994
})

0 commit comments

Comments
 (0)