Skip to content

Commit db3d83a

Browse files
committed
Changed: support adding multiple uploaded files to a dataset with extended metadata
1 parent 3d1bf98 commit db3d83a

File tree

9 files changed

+80
-53
lines changed

9 files changed

+80
-53
lines changed

docs/useCases.md

+17-17
Original file line numberDiff line numberDiff line change
@@ -909,41 +909,41 @@ The `progress` parameter represents a callback function that allows the caller t
909909

910910
The `abortController` is a built-in mechanism in modern web browsers that allows the cancellation of asynchronous operations. It works in conjunction with an associated AbortSignal, which will be passed to the file uploading API calls to monitor whether the operation should be aborted, if the caller decides to cancel the operation midway.
911911

912-
##### Add Uploaded File to the Dataset
912+
##### Add Uploaded Files to the Dataset
913913

914-
This use case involves adding a file that has been previously uploaded to remote storage to the dataset.
914+
This use case involves adding files that have been previously uploaded to remote storage to the dataset.
915915

916916
###### Example call:
917917

918918
```typescript
919-
import { addUploadedFileToDataset } from '@iqss/dataverse-client-javascript'
919+
import { addUploadedFilesToDataset } from '@iqss/dataverse-client-javascript'
920920
import { UploadedFileDTO } from '@iqss/dataverse-client-javascript'
921921

922922
/* ... */
923923

924924
const datasetId: number | string = 123
925-
const uploadedFileDTO: UploadedFileDTO = {
926-
fileName: 'the-file-name',
927-
storageId: 'localstack1://mybucket:19121faf7e7-2c40322ff54e',
928-
checksumType: 'md5',
929-
checksumValue: 'ede3d3b685b4e137ba4cb2521329a75e',
930-
mimeType: 'text/plain'
931-
}
932-
933-
addUploadedFileToDataset.execute(datasetId, uploadedFileDTO).then(() => {
934-
console.log('File added to the dataset successfully.')
925+
const uploadedFileDTOs: UploadedFileDTO[] = [
926+
{
927+
fileName: 'the-file-name',
928+
storageId: 'localstack1://mybucket:19121faf7e7-2c40322ff54e',
929+
checksumType: 'md5',
930+
checksumValue: 'ede3d3b685b4e137ba4cb2521329a75e',
931+
mimeType: 'text/plain'
932+
}
933+
]
934+
935+
addUploadedFilesToDataset.execute(datasetId, uploadedFileDTOs).then(() => {
936+
console.log('Files added to the dataset successfully.')
935937
})
936938

937939
/* ... */
938940
```
939941

940-
_See [use case](../src/files/domain/useCases/AddUploadedFileToDataset.ts) implementation_.
942+
_See [use case](../src/files/domain/useCases/AddUploadedFilesToDataset.ts) implementation_.
941943

942944
The `datasetId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers.
943945

944-
The `uploadedFileDTO` parameter is an instance of [UploadedFileDTO](../src/files/domain/dtos/UploadedFileDTO.ts) and includes properties related to the uploaded file. These properties should be calculated from the File Blob object previously sent through the upload file use case.
945-
946-
The `storageId` parameter represents the storage identifier obtained after a successful call to the UploadFile use case.
946+
The `uploadedFileDTOs` parameter is an array of [UploadedFileDTO](../src/files/domain/dtos/UploadedFileDTO.ts) and includes properties related to the uploaded files. These properties should be calculated from the uploaded File Blob objects and the resulting storage identifiers from the Upload File use case.
947947

948948
##### Error handling:
949949

src/files/domain/dtos/UploadedFileDTO.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
export interface UploadedFileDTO {
22
fileName: string
3+
description?: string
4+
directoryLabel?: string
5+
categories?: string[]
6+
restrict?: boolean
37
storageId: string
48
checksumValue: string
59
checksumType: string

src/files/domain/repositories/IFilesRepository.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ export interface IFilesRepository {
5555

5656
getFileUploadDestination(datasetId: number | string, file: File): Promise<FileUploadDestination>
5757

58-
addUploadedFileToDataset(
58+
addUploadedFilesToDataset(
5959
datasetId: number | string,
60-
uploadedFileDTO: UploadedFileDTO
60+
uploadedFileDTOs: UploadedFileDTO[]
6161
): Promise<undefined>
6262
}

src/files/domain/useCases/AddUploadedFileToDataset.ts renamed to src/files/domain/useCases/AddUploadedFilesToDataset.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,27 @@ import { UseCase } from '../../../core/domain/useCases/UseCase'
22
import { UploadedFileDTO } from '../dtos/UploadedFileDTO'
33
import { IFilesRepository } from '../repositories/IFilesRepository'
44

5-
export class AddUploadedFileToDataset implements UseCase<void> {
5+
export class AddUploadedFilesToDataset implements UseCase<void> {
66
private filesRepository: IFilesRepository
77

88
constructor(filesRepository: IFilesRepository) {
99
this.filesRepository = filesRepository
1010
}
1111

1212
/**
13-
* Adds a file that has been uploaded to remote storage to the dataset.
13+
* Adds files that have been uploaded to remote storage to the dataset.
1414
*
1515
* This method completes the flow initiated by the UploadFile use case, which involves adding the uploaded file to the specified dataset.
1616
* (https://guides.dataverse.org/en/latest/developers/s3-direct-upload-api.html#adding-the-uploaded-file-to-the-dataset)
1717
*
18-
* Note: This use case can be used independently of the UploadFile use case, e.g., supporting scenarios in which the file already exists in S3 or has been uploaded via some out-of-band method.
18+
* Note: This use case can be used independently of the UploadFile use case, e.g., supporting scenarios in which the files already exist in S3 or have been uploaded via some out-of-band method.
1919
*
2020
* @param {number | string} [datasetId] - The dataset identifier, which can be a string (for persistent identifiers) or a number (for numeric identifiers).
21-
* @param {UploadedFileDTO} [uploadedFileDTO] - A file DTO associated with the uploaded file.
21+
* @param {UploadedFileDTO[]} [uploadedFileDTOs] - File DTOs associated with the uploaded files.
2222
* @returns {Promise<void>} A promise that resolves when the file has been successfully added to the dataset.
2323
* @throws {DirectUploadClientError} - If there are errors while performing the operation.
2424
*/
25-
async execute(datasetId: number | string, uploadedFileDTO: UploadedFileDTO): Promise<void> {
26-
await this.filesRepository.addUploadedFileToDataset(datasetId, uploadedFileDTO)
25+
async execute(datasetId: number | string, uploadedFileDTOs: UploadedFileDTO[]): Promise<void> {
26+
await this.filesRepository.addUploadedFilesToDataset(datasetId, uploadedFileDTOs)
2727
}
2828
}

src/files/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { GetFileCitation } from './domain/useCases/GetFileCitation'
1010
import { GetFileAndDataset } from './domain/useCases/GetFileAndDataset'
1111
import { UploadFile } from './domain/useCases/UploadFile'
1212
import { DirectUploadClient } from './infra/clients/DirectUploadClient'
13-
import { AddUploadedFileToDataset } from './domain/useCases/AddUploadedFileToDataset'
13+
import { AddUploadedFilesToDataset } from './domain/useCases/AddUploadedFilesToDataset'
1414

1515
const filesRepository = new FilesRepository()
1616
const directUploadClient = new DirectUploadClient(filesRepository)
@@ -25,7 +25,7 @@ const getFile = new GetFile(filesRepository)
2525
const getFileAndDataset = new GetFileAndDataset(filesRepository)
2626
const getFileCitation = new GetFileCitation(filesRepository)
2727
const uploadFile = new UploadFile(directUploadClient)
28-
const addUploadedFileToDataset = new AddUploadedFileToDataset(filesRepository)
28+
const addUploadedFilesToDataset = new AddUploadedFilesToDataset(filesRepository)
2929

3030
export {
3131
getDatasetFiles,
@@ -38,7 +38,7 @@ export {
3838
getFileAndDataset,
3939
getFileCitation,
4040
uploadFile,
41-
addUploadedFileToDataset
41+
addUploadedFilesToDataset
4242
}
4343

4444
export { FileModel as File, FileEmbargo, FileChecksum } from './domain/models/FileModel'

src/files/infra/repositories/FilesRepository.ts

+28-7
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ export interface GetFilesTotalDownloadSizeQueryParams {
4242
searchText?: string
4343
}
4444

45+
export interface UploadedFileRequestBody {
46+
fileName: string
47+
description: string
48+
directoryLabel: string
49+
categories: string[]
50+
restrict: boolean
51+
checksum: ChecksumRequestBody
52+
mimeType: string
53+
storageIdentifier: string
54+
}
55+
56+
export interface ChecksumRequestBody {
57+
'@value': string
58+
'@type': string
59+
}
60+
4561
export class FilesRepository extends ApiRepository implements IFilesRepository {
4662
private readonly datasetsResourceName: string = 'datasets'
4763
private readonly filesResourceName: string = 'files'
@@ -223,21 +239,26 @@ export class FilesRepository extends ApiRepository implements IFilesRepository {
223239
})
224240
}
225241

226-
public async addUploadedFileToDataset(
242+
public async addUploadedFilesToDataset(
227243
datasetId: number | string,
228-
uploadedFileDTO: UploadedFileDTO
244+
uploadedFileDTOs: UploadedFileDTO[]
229245
): Promise<undefined> {
230-
const fileArray = [
231-
{
246+
const fileArray: UploadedFileRequestBody[] = []
247+
uploadedFileDTOs.forEach((uploadedFileDTO) => {
248+
fileArray.push({
232249
fileName: uploadedFileDTO.fileName,
233250
checksum: {
234251
'@value': uploadedFileDTO.checksumValue,
235252
'@type': uploadedFileDTO.checksumType.toUpperCase()
236253
},
237254
mimeType: uploadedFileDTO.mimeType,
238-
storageIdentifier: uploadedFileDTO.storageId
239-
}
240-
]
255+
storageIdentifier: uploadedFileDTO.storageId,
256+
...(uploadedFileDTO.description && { description: uploadedFileDTO.description }),
257+
...(uploadedFileDTO.categories && { categories: uploadedFileDTO.categories }),
258+
...(uploadedFileDTO.restrict && { restrict: uploadedFileDTO.restrict }),
259+
...(uploadedFileDTO.directoryLabel && { directoryLabel: uploadedFileDTO.directoryLabel })
260+
})
261+
})
241262
const formData = new FormData()
242263
formData.append('jsonData', JSON.stringify(fileArray))
243264
return this.doPost(

test/integration/files/DirectUpload.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ describe('Direct Upload', () => {
121121
mimeType: singlepartFile.type
122122
}
123123

124-
await filesRepositorySut.addUploadedFileToDataset(testDataset1Ids.numericId, uploadedFileDTO)
124+
await filesRepositorySut.addUploadedFilesToDataset(testDataset1Ids.numericId, [uploadedFileDTO])
125125

126126
datasetFiles = await filesRepositorySut.getDatasetFiles(
127127
testDataset1Ids.numericId,
@@ -184,7 +184,7 @@ describe('Direct Upload', () => {
184184
mimeType: multipartFile.type
185185
}
186186

187-
await filesRepositorySut.addUploadedFileToDataset(testDataset2Ids.numericId, uploadedFileDTO)
187+
await filesRepositorySut.addUploadedFilesToDataset(testDataset2Ids.numericId, [uploadedFileDTO])
188188

189189
datasetFiles = await filesRepositorySut.getDatasetFiles(
190190
testDataset2Ids.numericId,
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,37 @@
11
import { DirectUploadClientError } from '../../../src/files/domain/clients/DirectUploadClientError'
2-
import { AddUploadedFileToDataset } from '../../../src/files/domain/useCases/AddUploadedFileToDataset'
2+
import { AddUploadedFilesToDataset } from '../../../src/files/domain/useCases/AddUploadedFilesToDataset'
33
import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'
44

55
describe('execute', () => {
6-
const testUploadedFileDTO = {
7-
fileName: 'testfile',
8-
storageId: 'testStorageId',
9-
checksumValue: 'testChecksumValue',
10-
checksumType: 'md5',
11-
mimeType: 'test/type'
12-
}
6+
const testUploadedFileDTOs = [
7+
{
8+
fileName: 'testfile',
9+
storageId: 'testStorageId',
10+
checksumValue: 'testChecksumValue',
11+
checksumType: 'md5',
12+
mimeType: 'test/type'
13+
}
14+
]
1315

1416
test('should return undefined on client success', async () => {
1517
const filesRepositoryStub: IFilesRepository = {} as IFilesRepository
16-
filesRepositoryStub.addUploadedFileToDataset = jest.fn().mockResolvedValue(undefined)
18+
filesRepositoryStub.addUploadedFilesToDataset = jest.fn().mockResolvedValue(undefined)
1719

18-
const sut = new AddUploadedFileToDataset(filesRepositoryStub)
20+
const sut = new AddUploadedFilesToDataset(filesRepositoryStub)
1921

20-
const actual = await sut.execute(1, testUploadedFileDTO)
22+
const actual = await sut.execute(1, testUploadedFileDTOs)
2123

2224
expect(actual).toEqual(undefined)
2325
})
2426

2527
test('should return error on client error', async () => {
2628
const filesRepositoryStub: IFilesRepository = {} as IFilesRepository
27-
filesRepositoryStub.addUploadedFileToDataset = jest
29+
filesRepositoryStub.addUploadedFilesToDataset = jest
2830
.fn()
2931
.mockRejectedValue(new DirectUploadClientError('test', 'test', 'test'))
3032

31-
const sut = new AddUploadedFileToDataset(filesRepositoryStub)
33+
const sut = new AddUploadedFilesToDataset(filesRepositoryStub)
3234

33-
await expect(sut.execute(1, testUploadedFileDTO)).rejects.toThrow(DirectUploadClientError)
35+
await expect(sut.execute(1, testUploadedFileDTOs)).rejects.toThrow(DirectUploadClientError)
3436
})
3537
})

test/unit/files/DirectUploadClient.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ describe('uploadFile', () => {
7979
const filesRepositoryStub: IFilesRepository = {} as IFilesRepository
8080
const testDestination: FileUploadDestination = createSingleFileUploadDestinationModel()
8181
filesRepositoryStub.getFileUploadDestination = jest.fn().mockResolvedValue(testDestination)
82-
filesRepositoryStub.addUploadedFileToDataset = jest.fn().mockResolvedValue(undefined)
82+
filesRepositoryStub.addUploadedFilesToDataset = jest.fn().mockResolvedValue(undefined)
8383

8484
jest.spyOn(axios, 'put').mockResolvedValue(undefined)
8585

@@ -199,7 +199,7 @@ describe('uploadFile', () => {
199199
filesRepositoryStub.getFileUploadDestination = jest
200200
.fn()
201201
.mockResolvedValue(testMultipartDestination)
202-
filesRepositoryStub.addUploadedFileToDataset = jest.fn().mockResolvedValue(undefined)
202+
filesRepositoryStub.addUploadedFilesToDataset = jest.fn().mockResolvedValue(undefined)
203203

204204
jest
205205
.spyOn(axios, 'put')

0 commit comments

Comments
 (0)