Skip to content

Commit 6343e69

Browse files
committed
fix: add use case of downloading responses
1 parent de17e97 commit 6343e69

15 files changed

Lines changed: 482 additions & 1 deletion
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface GuestbookResponse {
2+
[key: string]: unknown
3+
guestbookId?: number
4+
dataverseId?: number
5+
}

src/guestbooks/domain/repositories/IGuestbooksRepository.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CreateGuestbookDTO } from '../dtos/CreateGuestbookDTO'
22
import { Guestbook } from '../models/Guestbook'
3+
import { GuestbookResponse } from '../models/GuestbookResponse'
34

45
export interface IGuestbooksRepository {
56
createGuestbook(
@@ -11,6 +12,14 @@ export interface IGuestbooksRepository {
1112
collectionIdOrAlias: number | string,
1213
includeStats?: boolean
1314
): Promise<Guestbook[]>
15+
getGuestbookResponsesByDataverseId(
16+
dataverseId: number | string,
17+
guestbookId?: number
18+
): Promise<GuestbookResponse[]>
19+
downloadGuestbookResponsesByDataverseId(
20+
dataverseId: number | string,
21+
guestbookId?: number
22+
): Promise<string>
1423
setGuestbookEnabled(
1524
collectionIdOrAlias: number | string,
1625
guestbookId: number,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { IGuestbooksRepository } from '../repositories/IGuestbooksRepository'
3+
4+
export class DownloadGuestbookResponsesByDataverseId implements UseCase<string> {
5+
constructor(private readonly guestbooksRepository: IGuestbooksRepository) {}
6+
7+
/**
8+
* Downloads all guestbook responses for a dataverse collection.
9+
*
10+
* The dataverse can be identified by either its alias/identifier or numeric database id.
11+
* The returned string is the raw response body from the Dataverse API, which is typically
12+
* saved by callers as a CSV file or printed directly.
13+
*
14+
* @param {number | string} dataverseId - Dataverse alias/identifier or numeric database id.
15+
* @returns {Promise<string>} Raw response body returned by the Dataverse API.
16+
*/
17+
async execute(dataverseId: number | string): Promise<string> {
18+
return await this.guestbooksRepository.downloadGuestbookResponsesByDataverseId(dataverseId)
19+
}
20+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { IGuestbooksRepository } from '../repositories/IGuestbooksRepository'
3+
4+
export class DownloadGuestbookResponsesOfAGuestbook implements UseCase<string> {
5+
constructor(private readonly guestbooksRepository: IGuestbooksRepository) {}
6+
7+
/**
8+
* Downloads guestbook responses for one guestbook in a dataverse collection.
9+
*
10+
* The dataverse can be identified by either its alias/identifier or numeric database id.
11+
* The returned string is the raw response body from the Dataverse API, which is typically
12+
* saved by callers as a CSV file or printed directly.
13+
*
14+
* @param {number | string} dataverseId - Dataverse alias/identifier or numeric database id.
15+
* @param {number} guestbookId - Guestbook identifier to restrict the export.
16+
* @returns {Promise<string>} Raw response body returned by the Dataverse API.
17+
*/
18+
async execute(dataverseId: number | string, guestbookId: number): Promise<string> {
19+
return await this.guestbooksRepository.downloadGuestbookResponsesByDataverseId(
20+
dataverseId,
21+
guestbookId
22+
)
23+
}
24+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { GuestbookResponse } from '../models/GuestbookResponse'
3+
import { IGuestbooksRepository } from '../repositories/IGuestbooksRepository'
4+
5+
export class GetGuestbookResponsesByDataverseId implements UseCase<GuestbookResponse[]> {
6+
constructor(private readonly guestbooksRepository: IGuestbooksRepository) {}
7+
8+
/**
9+
* Returns all guestbook responses for a dataverse collection.
10+
*
11+
* @param {number | string} dataverseId - Dataverse identifier.
12+
* @returns {Promise<GuestbookResponse[]>}
13+
*/
14+
async execute(dataverseId: number | string): Promise<GuestbookResponse[]> {
15+
return await this.guestbooksRepository.getGuestbookResponsesByDataverseId(dataverseId)
16+
}
17+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { GuestbookResponse } from '../models/GuestbookResponse'
3+
import { IGuestbooksRepository } from '../repositories/IGuestbooksRepository'
4+
5+
export class GetGuestbookResponsesOfAGuestbook implements UseCase<GuestbookResponse[]> {
6+
constructor(private readonly guestbooksRepository: IGuestbooksRepository) {}
7+
8+
/**
9+
* Returns guestbook responses for one guestbook in a dataverse collection.
10+
*
11+
* @param {number | string} dataverseId - Dataverse identifier.
12+
* @param {number} guestbookId - Guestbook identifier filter.
13+
* @returns {Promise<GuestbookResponse[]>}
14+
*/
15+
async execute(dataverseId: number | string, guestbookId: number): Promise<GuestbookResponse[]> {
16+
return await this.guestbooksRepository.getGuestbookResponsesByDataverseId(
17+
dataverseId,
18+
guestbookId
19+
)
20+
}
21+
}

src/guestbooks/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { GuestbooksRepository } from './infra/repositories/GuestbooksRepository'
22
import { CreateGuestbook } from './domain/useCases/CreateGuestbook'
3+
import { DownloadGuestbookResponsesByDataverseId } from './domain/useCases/DownloadGuestbookResponsesByDataverseId'
4+
import { DownloadGuestbookResponsesOfAGuestbook } from './domain/useCases/DownloadGuestbookResponsesOfAGuestbook'
35
import { GetGuestbook } from './domain/useCases/GetGuestbook'
6+
import { GetGuestbookResponsesByDataverseId } from './domain/useCases/GetGuestbookResponsesByDataverseId'
7+
import { GetGuestbookResponsesOfAGuestbook } from './domain/useCases/GetGuestbookResponsesOfAGuestbook'
48
import { GetGuestbooksByCollectionId } from './domain/useCases/GetGuestbooksByCollectionId'
59
import { SetGuestbookEnabled } from './domain/useCases/SetGuestbookEnabled'
610
import { AssignDatasetGuestbook } from './domain/useCases/AssignDatasetGuestbook'
@@ -9,15 +13,31 @@ import { RemoveDatasetGuestbook } from './domain/useCases/RemoveDatasetGuestbook
913
const guestbooksRepository = new GuestbooksRepository()
1014

1115
const createGuestbook = new CreateGuestbook(guestbooksRepository)
16+
const downloadGuestbookResponsesByDataverseId = new DownloadGuestbookResponsesByDataverseId(
17+
guestbooksRepository
18+
)
19+
const downloadGuestbookResponsesOfAGuestbook = new DownloadGuestbookResponsesOfAGuestbook(
20+
guestbooksRepository
21+
)
1222
const getGuestbook = new GetGuestbook(guestbooksRepository)
23+
const getGuestbookResponsesByDataverseId = new GetGuestbookResponsesByDataverseId(
24+
guestbooksRepository
25+
)
26+
const getGuestbookResponsesOfAGuestbook = new GetGuestbookResponsesOfAGuestbook(
27+
guestbooksRepository
28+
)
1329
const getGuestbooksByCollectionId = new GetGuestbooksByCollectionId(guestbooksRepository)
1430
const setGuestbookEnabled = new SetGuestbookEnabled(guestbooksRepository)
1531
const assignDatasetGuestbook = new AssignDatasetGuestbook(guestbooksRepository)
1632
const removeDatasetGuestbook = new RemoveDatasetGuestbook(guestbooksRepository)
1733

1834
export {
1935
createGuestbook,
36+
downloadGuestbookResponsesByDataverseId,
37+
downloadGuestbookResponsesOfAGuestbook,
2038
getGuestbook,
39+
getGuestbookResponsesByDataverseId,
40+
getGuestbookResponsesOfAGuestbook,
2141
getGuestbooksByCollectionId,
2242
setGuestbookEnabled,
2343
assignDatasetGuestbook,
@@ -30,3 +50,4 @@ export {
3050
CreateGuestbookOptionDTO
3151
} from './domain/dtos/CreateGuestbookDTO'
3252
export { Guestbook, GuestbookCustomQuestion, GuestbookOption } from './domain/models/Guestbook'
53+
export { GuestbookResponse } from './domain/models/GuestbookResponse'

src/guestbooks/infra/repositories/GuestbooksRepository.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { ApiRepository } from '../../../core/infra/repositories/ApiRepository'
22
import { CreateGuestbookDTO } from '../../domain/dtos/CreateGuestbookDTO'
33
import { Guestbook } from '../../domain/models/Guestbook'
4+
import { GuestbookResponse } from '../../domain/models/GuestbookResponse'
45
import { IGuestbooksRepository } from '../../domain/repositories/IGuestbooksRepository'
56

67
export class GuestbooksRepository extends ApiRepository implements IGuestbooksRepository {
78
private readonly guestbooksResourceName: string = 'guestbooks'
89
private readonly datasetsResourceName: string = 'datasets'
10+
private readonly dataversesResourceName: string = 'dataverses'
911

1012
public async createGuestbook(
1113
collectionIdOrAlias: number | string,
@@ -47,6 +49,34 @@ export class GuestbooksRepository extends ApiRepository implements IGuestbooksRe
4749
})
4850
}
4951

52+
public async getGuestbookResponsesByDataverseId(
53+
dataverseId: number | string,
54+
guestbookId?: number
55+
): Promise<GuestbookResponse[]> {
56+
const endpoint = `/${this.dataversesResourceName}/${dataverseId}/guestbookResponses`
57+
const queryParams = guestbookId === undefined ? {} : { guestbookId }
58+
59+
return this.doGet(endpoint, true, queryParams)
60+
.then((response) => response.data.data as GuestbookResponse[])
61+
.catch((error) => {
62+
throw error
63+
})
64+
}
65+
66+
public async downloadGuestbookResponsesByDataverseId(
67+
dataverseId: number | string,
68+
guestbookId?: number
69+
): Promise<string> {
70+
const endpoint = `/${this.dataversesResourceName}/${dataverseId}/guestbookResponses`
71+
const queryParams = guestbookId === undefined ? {} : { guestbookId }
72+
73+
return this.doGet(endpoint, true, queryParams)
74+
.then((response) => response.data as string)
75+
.catch((error) => {
76+
throw error
77+
})
78+
}
79+
5080
public async setGuestbookEnabled(
5181
collectionIdOrAlias: number | string,
5282
guestbookId: number,

test/integration/guestbooks/GuestbooksRepository.test.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,43 @@ describe('GuestbooksRepository', () => {
249249
})
250250
})
251251

252+
describe('downloadGuestbookResponsesByDataverseId', () => {
253+
test('should download all guestbook responses for a dataverse collection', async () => {
254+
const setup = await createGuestbookDownloadSetup('all responses export test')
255+
256+
try {
257+
const actual = await sut.downloadGuestbookResponsesByDataverseId(testCollectionAlias)
258+
259+
expect(actual).toContain('Guestbook, Dataset, Dataset PID, Date, Type, File Name')
260+
expect(actual).toContain(setup.guestbookName)
261+
expect(actual).toContain(setup.datasetPersistentId)
262+
expect(actual).toContain(setup.email)
263+
expect(actual).toContain(testTextFile1Name)
264+
} finally {
265+
await cleanupGuestbookDownloadSetup(setup)
266+
}
267+
})
268+
269+
test('should download responses only for the specified guestbook', async () => {
270+
const setup = await createGuestbookDownloadSetup('single guestbook export test')
271+
272+
try {
273+
const actual = await sut.downloadGuestbookResponsesByDataverseId(
274+
testCollectionAlias,
275+
setup.guestbookId
276+
)
277+
278+
expect(actual).toContain('Guestbook, Dataset, Dataset PID, Date, Type, File Name')
279+
expect(actual).toContain(setup.guestbookName)
280+
expect(actual).toContain(setup.datasetPersistentId)
281+
expect(actual).toContain(setup.email)
282+
expect(actual).toContain(testTextFile1Name)
283+
} finally {
284+
await cleanupGuestbookDownloadSetup(setup)
285+
}
286+
})
287+
})
288+
252289
describe('setGuestbookEnabled', () => {
253290
test('should disable guestbook', async () => {
254291
createdGuestbookId = await sut.createGuestbook(testCollectionId, createGuestbookDTO)
@@ -346,4 +383,70 @@ describe('GuestbooksRepository', () => {
346383
})
347384
})
348385
})
386+
387+
const createGuestbookDownloadSetup = async (guestbookName: string) => {
388+
const uniqueSuffix = Date.now().toString()
389+
const guestbookId = await sut.createGuestbook(testCollectionAlias, {
390+
...createGuestbookDTO,
391+
name: `${guestbookName}-${uniqueSuffix}`,
392+
customQuestions: []
393+
})
394+
const datasetIds = await createDataset.execute(
395+
TestConstants.TEST_NEW_DATASET_DTO,
396+
testCollectionAlias
397+
)
398+
await uploadFileViaApi(datasetIds.numericId, testTextFile1Name)
399+
const datasetFiles = await filesRepository.getDatasetFiles(
400+
datasetIds.numericId,
401+
DatasetNotNumberedVersion.LATEST,
402+
false,
403+
FileOrderCriteria.NAME_AZ
404+
)
405+
const fileId = datasetFiles.files[0].id
406+
const email = `guestbook-download-${uniqueSuffix}@example.edu`
407+
408+
await sut.assignDatasetGuestbook(datasetIds.numericId, guestbookId)
409+
await publishDatasetViaApi(datasetIds.numericId)
410+
await waitForNoLocks(datasetIds.numericId, 10)
411+
412+
ApiConfig.init(
413+
TestConstants.TEST_API_URL,
414+
DataverseApiAuthMechanism.BEARER_TOKEN,
415+
undefined,
416+
undefined,
417+
() => null
418+
)
419+
await accessRepository.submitGuestbookForDatafileDownload(fileId, {
420+
guestbookResponse: {
421+
name: `Guestbook Download ${uniqueSuffix}`,
422+
email
423+
}
424+
})
425+
426+
ApiConfig.init(
427+
TestConstants.TEST_API_URL,
428+
DataverseApiAuthMechanism.API_KEY,
429+
process.env.TEST_API_KEY
430+
)
431+
432+
return {
433+
guestbookId,
434+
guestbookName: `${guestbookName}-${uniqueSuffix}`,
435+
datasetNumericId: datasetIds.numericId,
436+
datasetPersistentId: datasetIds.persistentId,
437+
email
438+
}
439+
}
440+
441+
const cleanupGuestbookDownloadSetup = async (setup: {
442+
datasetNumericId: number
443+
datasetPersistentId: string
444+
}) => {
445+
ApiConfig.init(
446+
TestConstants.TEST_API_URL,
447+
DataverseApiAuthMechanism.API_KEY,
448+
process.env.TEST_API_KEY
449+
)
450+
await deletePublishedDatasetViaApi(setup.datasetPersistentId)
451+
}
349452
})

test/testHelpers/collections/collectionHelper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export async function setStorageDriverViaApi(
134134
): Promise<void> {
135135
try {
136136
return await axios.put(
137-
`${TestConstants.TEST_API_URL}/admin/dataverse/${collectionAlias}/storageDriver`,
137+
`${TestConstants.TEST_API_URL}/dataverses/${collectionAlias}/storageDriver`,
138138
driverLabel,
139139
{
140140
headers: { 'Content-Type': 'text/plain', 'X-Dataverse-Key': process.env.TEST_API_KEY }

0 commit comments

Comments
 (0)