From b9604c61b432a6918733bba89e9476c675964f8e Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 22 Jul 2025 16:16:14 -0400 Subject: [PATCH 1/7] feat: update get citation in other format --- docs/useCases.md | 31 ++++++ src/datasets/domain/models/CitationFormats.ts | 7 ++ .../domain/models/CitationResponse.ts | 7 ++ .../repositories/IDatasetsRepository.ts | 8 ++ .../GetDatasetCitationInOtherFormats.ts | 36 ++++++ .../infra/repositories/DatasetsRepository.ts | 30 +++++ .../datasets/DatasetsRepository.test.ts | 103 ++++++++++++++++++ .../GetDatasetCitationInOtherFormats.test.ts | 39 +++++++ 8 files changed, 261 insertions(+) create mode 100644 src/datasets/domain/models/CitationFormats.ts create mode 100644 src/datasets/domain/models/CitationResponse.ts create mode 100644 src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts create mode 100644 test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts diff --git a/docs/useCases.md b/docs/useCases.md index 372704a8..b1a00c6c 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -564,6 +564,37 @@ The `datasetId` parameter can be a string, for persistent identifiers, or a numb There is an optional third parameter called `includeDeaccessioned`, which indicates whether to consider deaccessioned versions or not in the dataset search. If not set, the default value is `false`. +#### Get Dataset Citation In Other Formats + +Retrieves the citation for a dataset in a specified bibliographic format. + +##### Example call: + +```typescript +import { getDatasetCitationInOtherFormats } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const datasetId = 2 +const datasetVersionId = '1.0' + +getDatasetCitationInOtherFormats + .execute(datasetId, datasetVersionId, format) + .then((citationText: CitationResponse) => { + /* ... */ + }) + +/* ... */ +``` + +_See [use case](../src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts) implementation_. + +Supported formats include 'EndNote' (XML), 'RIS' (plain text), 'BibTeX' (plain text), 'CSLJson' (JSON), and 'Internal' (HTML). The response contains the raw citation content in the requested format, the format type, and the content type (MIME type). + +The `datasetId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers. + +There is an optional third parameter called `includeDeaccessioned`, which indicates whether to consider deaccessioned versions or not in the dataset search. If not set, the default value is `false`. + #### Get Dataset Citation Text By Private URL Token Returns the Dataset citation text, given an associated Private URL Token. diff --git a/src/datasets/domain/models/CitationFormats.ts b/src/datasets/domain/models/CitationFormats.ts new file mode 100644 index 00000000..8fde9898 --- /dev/null +++ b/src/datasets/domain/models/CitationFormats.ts @@ -0,0 +1,7 @@ +export enum CitationFormats { + Internal = 'Internal', + EndNote = 'EndNote', + RIS = 'RIS', + BibTeX = 'BibTeX', + CSLJson = 'CSL' +} diff --git a/src/datasets/domain/models/CitationResponse.ts b/src/datasets/domain/models/CitationResponse.ts new file mode 100644 index 00000000..f30349b9 --- /dev/null +++ b/src/datasets/domain/models/CitationResponse.ts @@ -0,0 +1,7 @@ +import { CitationFormats } from './CitationFormats' + +export type CitationResponse = { + content: string + format: CitationFormats + contentType: string +} diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 52a6c1cd..4096be44 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -10,6 +10,8 @@ import { DatasetVersionDiff } from '../models/DatasetVersionDiff' import { DatasetDownloadCount } from '../models/DatasetDownloadCount' import { DatasetVersionSummaryInfo } from '../models/DatasetVersionSummaryInfo' import { DatasetLinkedCollection } from '../models/DatasetLinkedCollection' +import { CitationFormats } from '../models/CitationFormats' +import { CitationResponse } from '../models/CitationResponse' export interface IDatasetsRepository { getDataset( @@ -65,4 +67,10 @@ export interface IDatasetsRepository { linkDataset(datasetId: number, collectionAlias: string): Promise unlinkDataset(datasetId: number, collectionAlias: string): Promise getDatasetLinkedCollections(datasetId: number | string): Promise + getDatasetCitationInOtherFormats( + datasetId: number, + datasetVersionId: string, + format: CitationFormats, + includeDeaccessioned?: boolean + ): Promise } diff --git a/src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts b/src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts new file mode 100644 index 00000000..0f470043 --- /dev/null +++ b/src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts @@ -0,0 +1,36 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { IDatasetsRepository } from '../repositories/IDatasetsRepository' +import { DatasetNotNumberedVersion } from '../models/DatasetNotNumberedVersion' +import { CitationResponse } from '../models/CitationResponse' +import { CitationFormats } from '../models/CitationFormats' + +export class GetDatasetCitationInOtherFormats implements UseCase { + private datasetsRepository: IDatasetsRepository + + constructor(datasetsRepository: IDatasetsRepository) { + this.datasetsRepository = datasetsRepository + } + + /** + * Returns the dataset citation in the specified format. + * + * @param {number} datasetId - The dataset identifier. + * @param {string | DatasetNotNumberedVersion} [datasetVersionId=DatasetNotNumberedVersion.LATEST] - The dataset version identifier, which can be a version-specific string (e.g., '1.0') or a DatasetNotNumberedVersion enum value. Defaults to LATEST. + * @param {CitationFormats} format - The citation format to return. One of: 'EndNote', 'RIS', 'BibTeX', 'CSLJson', 'Internal'. + * @param {boolean} [includeDeaccessioned=false] - Whether to include deaccessioned versions in the search. Defaults to false. + * @returns {Promise} The citation content, format, and content type. + */ + async execute( + datasetId: number, + datasetVersionId: string | DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST, + format: CitationFormats, + includeDeaccessioned = false + ): Promise { + return await this.datasetsRepository.getDatasetCitationInOtherFormats( + datasetId, + datasetVersionId, + format, + includeDeaccessioned + ) + } +} diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 95e82b77..ec311008 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -21,7 +21,9 @@ import { transformDatasetVersionDiffResponseToDatasetVersionDiff } from './trans import { DatasetDownloadCount } from '../../domain/models/DatasetDownloadCount' import { DatasetVersionSummaryInfo } from '../../domain/models/DatasetVersionSummaryInfo' import { DatasetLinkedCollection } from '../../domain/models/DatasetLinkedCollection' +import { CitationFormats } from '../../domain/models/CitationFormats' import { transformDatasetLinkedCollectionsResponseToDatasetLinkedCollection } from './transformers/datasetLinkedCollectionsTransformers' +import { CitationResponse } from '../../domain/models/CitationResponse' export interface GetAllDatasetPreviewsQueryParams { per_page?: number @@ -95,6 +97,34 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi }) } + public async getDatasetCitationInOtherFormats( + datasetId: number, + datasetVersionId: string | 'LATEST' = 'LATEST', + format: CitationFormats, + includeDeaccessioned = false + ): Promise { + const endpoint = this.buildApiEndpoint( + this.datasetsResourceName, + `versions/${datasetVersionId}/citation/${format}`, + datasetId + ) + + const queryParams = { includeDeaccessioned } + + try { + const response = await this.doGet(endpoint, true, queryParams) + + return { + content: response.data, + format, + contentType: response.headers['content-type'] ?? 'text/plain' + } + } catch (error) { + console.error(`[DatasetsRepository] Error fetching citation:`, error) + throw error + } + } + public async getPrivateUrlDatasetCitation(token: string): Promise { return this.doGet( this.buildApiEndpoint(this.datasetsResourceName, `privateUrlDatasetVersion/${token}/citation`) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 4cc25c68..5c7d97ce 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -51,6 +51,7 @@ import { import { FilesRepository } from '../../../src/files/infra/repositories/FilesRepository' import { DirectUploadClient } from '../../../src/files/infra/clients/DirectUploadClient' import { createTestFileUploadDestination } from '../../testHelpers/files/fileUploadDestinationHelper' +import { CitationFormats } from '../../../src/datasets/domain/models/CitationFormats' const TEST_DIFF_DATASET_DTO: DatasetDTO = { license: { @@ -492,6 +493,108 @@ describe('DatasetsRepository', () => { }) }) + describe('getDatasetCitationInOtherFormats', () => { + let testDatasetIds: CreatedDatasetIdentifiers + + beforeAll(async () => { + testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + }) + + afterAll(async () => { + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + }) + + test('should return citation in BibTeX format', async () => { + const citation = await sut.getDatasetCitationInOtherFormats( + testDatasetIds.numericId, + DatasetNotNumberedVersion.LATEST, + CitationFormats.BibTeX + ) + + expect(typeof citation.content).toBe('string') + expect(citation.format).toBe(CitationFormats.BibTeX) + expect(citation.contentType).toMatch(/text\/plain/) + }) + + test('should return citation in RIS format', async () => { + const citation = await sut.getDatasetCitationInOtherFormats( + testDatasetIds.numericId, + DatasetNotNumberedVersion.LATEST, + CitationFormats.RIS + ) + + expect(typeof citation.content).toBe('string') + expect(citation.format).toBe(CitationFormats.RIS) + expect(citation.contentType).toMatch(/text\/plain/) + }) + + test('should return citation in CSLJson format', async () => { + const citation = await sut.getDatasetCitationInOtherFormats( + testDatasetIds.numericId, + DatasetNotNumberedVersion.LATEST, + CitationFormats.CSLJson + ) + + expect(typeof citation.content).toBe('object') + expect(citation.format).toBe(CitationFormats.CSLJson) + expect(citation.contentType).toMatch(/application\/json/) + }) + + test('should return citation in EndNote format', async () => { + const citation = await sut.getDatasetCitationInOtherFormats( + testDatasetIds.numericId, + DatasetNotNumberedVersion.LATEST, + CitationFormats.EndNote + ) + + expect(typeof citation.content).toBe('string') + expect(citation.format).toBe(CitationFormats.EndNote) + expect(citation.contentType).toMatch('text/xml;charset=UTF-8') + }) + + test('should return citation in Internal format', async () => { + const citation = await sut.getDatasetCitationInOtherFormats( + testDatasetIds.numericId, + DatasetNotNumberedVersion.LATEST, + CitationFormats.Internal + ) + + expect(typeof citation.content).toBe('string') + expect(citation.format).toBe(CitationFormats.Internal) + expect(citation.contentType).toMatch(/text\/html/) + }) + + test('should return error when dataset does not exist', async () => { + const nonExistentId = 9999999 + const expectedError = new ReadError(`[404] Dataset with ID ${nonExistentId} not found.`) + + await expect( + sut.getDatasetCitationInOtherFormats( + nonExistentId, + DatasetNotNumberedVersion.LATEST, + CitationFormats.RIS + ) + ).rejects.toThrow(expectedError) + }) + + test('should return citation for deaccessioned dataset when includeDeaccessioned = true', async () => { + await publishDatasetViaApi(testDatasetIds.numericId) + await waitForNoLocks(testDatasetIds.numericId, 10) + await deaccessionDatasetViaApi(testDatasetIds.numericId, '1.0') + + const citation = await sut.getDatasetCitationInOtherFormats( + testDatasetIds.numericId, + DatasetNotNumberedVersion.LATEST, + CitationFormats.RIS, + true + ) + + expect(typeof citation.content).toBe('string') + expect(citation.format).toBe(CitationFormats.RIS) + expect(citation.contentType).toMatch(/text\/plain/) + }) + }) + describe('getDatasetVersionDiff', () => { let testDatasetIds: CreatedDatasetIdentifiers diff --git a/test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts b/test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts new file mode 100644 index 00000000..0e4d15cc --- /dev/null +++ b/test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts @@ -0,0 +1,39 @@ +import { GetDatasetCitationInOtherFormats } from '../../../src/datasets/domain/useCases/GetDatasetCitationInOtherFormats' +import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository' +import { ReadError } from '../../../src/core/domain/repositories/ReadError' +import { CitationFormats } from '../../../src/datasets/domain/models/CitationFormats' +import { DatasetNotNumberedVersion } from '../../../src/datasets/domain/models/DatasetNotNumberedVersion' +import { CitationResponse } from '../../../src/datasets/domain/models/CitationResponse' + +describe('GetDatasetCitationInOtherFormats.execute', () => { + const testDatasetId = 1 + const testFormat: CitationFormats = CitationFormats.BibTeX + const testVersion: DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST + + test('should return citation response on repository success', async () => { + const expectedCitation: CitationResponse = { + content: '@data{example, ...}', + format: CitationFormats.BibTeX, + contentType: 'text/plain' + } + + const datasetsRepositoryStub: IDatasetsRepository = { + getDatasetCitationInOtherFormats: jest.fn().mockResolvedValue(expectedCitation) + } as unknown as IDatasetsRepository + + const sut = new GetDatasetCitationInOtherFormats(datasetsRepositoryStub) + + const actual = await sut.execute(testDatasetId, testVersion, testFormat as CitationFormats) + expect(actual).toEqual(expectedCitation) + }) + + test('should throw ReadError on repository failure', async () => { + const datasetsRepositoryStub: IDatasetsRepository = { + getDatasetCitationInOtherFormats: jest.fn().mockRejectedValue(new ReadError()) + } as unknown as IDatasetsRepository + + const sut = new GetDatasetCitationInOtherFormats(datasetsRepositoryStub) + + await expect(sut.execute(testDatasetId, testVersion, testFormat)).rejects.toThrow(ReadError) + }) +}) From 76784603dbf198eca8b0379ee05ccfc5073b197f Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 23 Jul 2025 15:01:30 -0400 Subject: [PATCH 2/7] fix: update the response format --- src/datasets/domain/models/CitationResponse.ts | 5 +---- .../infra/repositories/DatasetsRepository.ts | 17 ++++------------- .../datasets/DatasetsRepository.test.ts | 6 ------ .../GetDatasetCitationInOtherFormats.test.ts | 1 - 4 files changed, 5 insertions(+), 24 deletions(-) diff --git a/src/datasets/domain/models/CitationResponse.ts b/src/datasets/domain/models/CitationResponse.ts index f30349b9..8bb8f2da 100644 --- a/src/datasets/domain/models/CitationResponse.ts +++ b/src/datasets/domain/models/CitationResponse.ts @@ -1,7 +1,4 @@ -import { CitationFormats } from './CitationFormats' - export type CitationResponse = { - content: string - format: CitationFormats + content: string | object contentType: string } diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index ec311008..117d797f 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -108,20 +108,11 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi `versions/${datasetVersionId}/citation/${format}`, datasetId ) + const response = await this.doGet(endpoint, true, { includeDeaccessioned }) - const queryParams = { includeDeaccessioned } - - try { - const response = await this.doGet(endpoint, true, queryParams) - - return { - content: response.data, - format, - contentType: response.headers['content-type'] ?? 'text/plain' - } - } catch (error) { - console.error(`[DatasetsRepository] Error fetching citation:`, error) - throw error + return { + content: response.data, + contentType: response.headers['content-type'] } } diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 5c7d97ce..bf336ec1 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -512,7 +512,6 @@ describe('DatasetsRepository', () => { ) expect(typeof citation.content).toBe('string') - expect(citation.format).toBe(CitationFormats.BibTeX) expect(citation.contentType).toMatch(/text\/plain/) }) @@ -524,7 +523,6 @@ describe('DatasetsRepository', () => { ) expect(typeof citation.content).toBe('string') - expect(citation.format).toBe(CitationFormats.RIS) expect(citation.contentType).toMatch(/text\/plain/) }) @@ -536,7 +534,6 @@ describe('DatasetsRepository', () => { ) expect(typeof citation.content).toBe('object') - expect(citation.format).toBe(CitationFormats.CSLJson) expect(citation.contentType).toMatch(/application\/json/) }) @@ -548,7 +545,6 @@ describe('DatasetsRepository', () => { ) expect(typeof citation.content).toBe('string') - expect(citation.format).toBe(CitationFormats.EndNote) expect(citation.contentType).toMatch('text/xml;charset=UTF-8') }) @@ -560,7 +556,6 @@ describe('DatasetsRepository', () => { ) expect(typeof citation.content).toBe('string') - expect(citation.format).toBe(CitationFormats.Internal) expect(citation.contentType).toMatch(/text\/html/) }) @@ -590,7 +585,6 @@ describe('DatasetsRepository', () => { ) expect(typeof citation.content).toBe('string') - expect(citation.format).toBe(CitationFormats.RIS) expect(citation.contentType).toMatch(/text\/plain/) }) }) diff --git a/test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts b/test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts index 0e4d15cc..7e13e029 100644 --- a/test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts +++ b/test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts @@ -13,7 +13,6 @@ describe('GetDatasetCitationInOtherFormats.execute', () => { test('should return citation response on repository success', async () => { const expectedCitation: CitationResponse = { content: '@data{example, ...}', - format: CitationFormats.BibTeX, contentType: 'text/plain' } From 997a4fb0e3386c8297db36d018a2bf8331fc4479 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 25 Jul 2025 10:23:34 -0400 Subject: [PATCH 3/7] Revert "fix RolesRepository integration test" This reverts commit d492fea846751ee1cdfcb43e9158ed4bbe9baa5a. --- test/testHelpers/roles/roleHelper.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/testHelpers/roles/roleHelper.ts b/test/testHelpers/roles/roleHelper.ts index 3e641cda..cf48cc3c 100644 --- a/test/testHelpers/roles/roleHelper.ts +++ b/test/testHelpers/roles/roleHelper.ts @@ -42,9 +42,7 @@ export const createSuperAdminRoleArray = (): Role[] => { 'ManageDatasetPermissions', 'ManageFilePermissions', 'PublishDataverse', - 'LinkDataverse', 'PublishDataset', - 'LinkDataset', 'DeleteDataverse', 'DeleteDatasetDraft' ], @@ -101,11 +99,10 @@ export const createSuperAdminRoleArray = (): Role[] => { 'ManageDatasetPermissions', 'ManageFilePermissions', 'PublishDataset', - 'LinkDataset', 'DeleteDatasetDraft' ], description: - 'For datasets, a person who can edit License + Terms, edit Permissions, and publish and link datasets.', + 'For datasets, a person who can edit License + Terms, edit Permissions, and publish datasets.', id: 7 }, { From a3abce006eeb03998f10f2f5d2938b0202bc255f Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 25 Jul 2025 11:30:18 -0400 Subject: [PATCH 4/7] fix: test --- test/integration/datasets/DatasetsRepository.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 17ae2f89..11a9295d 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -545,7 +545,7 @@ describe('DatasetsRepository', () => { ) expect(typeof citation.content).toBe('string') - expect(citation.contentType).toMatch('text/xml;charset=UTF-8') + expect(citation.contentType).toMatch(/text\/xml/) }) test('should return citation in Internal format', async () => { From e9c4f98c1f7f57c94bec4e54b6f97abd9c11a4c9 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Thu, 31 Jul 2025 13:52:58 -0400 Subject: [PATCH 5/7] fix: change the names and stringify json object --- docs/useCases.md | 2 +- .../{CitationFormats.ts => CitationFormat.ts} | 2 +- .../domain/models/CitationResponse.ts | 4 ---- .../domain/models/FormattedCitation.ts | 4 ++++ .../repositories/IDatasetsRepository.ts | 8 ++++---- .../GetDatasetCitationInOtherFormats.ts | 14 ++++++------- .../infra/repositories/DatasetsRepository.ts | 20 +++++++++++++------ .../datasets/DatasetsRepository.test.ts | 18 ++++++++--------- .../GetDatasetCitationInOtherFormats.test.ts | 10 +++++----- 9 files changed, 45 insertions(+), 37 deletions(-) rename src/datasets/domain/models/{CitationFormats.ts => CitationFormat.ts} (77%) delete mode 100644 src/datasets/domain/models/CitationResponse.ts create mode 100644 src/datasets/domain/models/FormattedCitation.ts diff --git a/docs/useCases.md b/docs/useCases.md index b1a00c6c..44b25e02 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -580,7 +580,7 @@ const datasetVersionId = '1.0' getDatasetCitationInOtherFormats .execute(datasetId, datasetVersionId, format) - .then((citationText: CitationResponse) => { + .then((citationText: FormattedCitation) => { /* ... */ }) diff --git a/src/datasets/domain/models/CitationFormats.ts b/src/datasets/domain/models/CitationFormat.ts similarity index 77% rename from src/datasets/domain/models/CitationFormats.ts rename to src/datasets/domain/models/CitationFormat.ts index 8fde9898..56d923d6 100644 --- a/src/datasets/domain/models/CitationFormats.ts +++ b/src/datasets/domain/models/CitationFormat.ts @@ -1,4 +1,4 @@ -export enum CitationFormats { +export enum CitationFormat { Internal = 'Internal', EndNote = 'EndNote', RIS = 'RIS', diff --git a/src/datasets/domain/models/CitationResponse.ts b/src/datasets/domain/models/CitationResponse.ts deleted file mode 100644 index 8bb8f2da..00000000 --- a/src/datasets/domain/models/CitationResponse.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type CitationResponse = { - content: string | object - contentType: string -} diff --git a/src/datasets/domain/models/FormattedCitation.ts b/src/datasets/domain/models/FormattedCitation.ts new file mode 100644 index 00000000..1057db64 --- /dev/null +++ b/src/datasets/domain/models/FormattedCitation.ts @@ -0,0 +1,4 @@ +export type FormattedCitation = { + content: string + contentType: string +} diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 4096be44..72e66fd4 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -10,8 +10,8 @@ import { DatasetVersionDiff } from '../models/DatasetVersionDiff' import { DatasetDownloadCount } from '../models/DatasetDownloadCount' import { DatasetVersionSummaryInfo } from '../models/DatasetVersionSummaryInfo' import { DatasetLinkedCollection } from '../models/DatasetLinkedCollection' -import { CitationFormats } from '../models/CitationFormats' -import { CitationResponse } from '../models/CitationResponse' +import { CitationFormat } from '../models/CitationFormat' +import { FormattedCitation } from '../models/FormattedCitation' export interface IDatasetsRepository { getDataset( @@ -70,7 +70,7 @@ export interface IDatasetsRepository { getDatasetCitationInOtherFormats( datasetId: number, datasetVersionId: string, - format: CitationFormats, + format: CitationFormat, includeDeaccessioned?: boolean - ): Promise + ): Promise } diff --git a/src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts b/src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts index 0f470043..a447c107 100644 --- a/src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts +++ b/src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts @@ -1,10 +1,10 @@ import { UseCase } from '../../../core/domain/useCases/UseCase' import { IDatasetsRepository } from '../repositories/IDatasetsRepository' import { DatasetNotNumberedVersion } from '../models/DatasetNotNumberedVersion' -import { CitationResponse } from '../models/CitationResponse' -import { CitationFormats } from '../models/CitationFormats' +import { FormattedCitation } from '../models/FormattedCitation' +import { CitationFormat } from '../models/CitationFormat' -export class GetDatasetCitationInOtherFormats implements UseCase { +export class GetDatasetCitationInOtherFormats implements UseCase { private datasetsRepository: IDatasetsRepository constructor(datasetsRepository: IDatasetsRepository) { @@ -16,16 +16,16 @@ export class GetDatasetCitationInOtherFormats implements UseCase} The citation content, format, and content type. + * @returns {Promise} The citation content, format, and content type. */ async execute( datasetId: number, datasetVersionId: string | DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST, - format: CitationFormats, + format: CitationFormat, includeDeaccessioned = false - ): Promise { + ): Promise { return await this.datasetsRepository.getDatasetCitationInOtherFormats( datasetId, datasetVersionId, diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 117d797f..9c585eb6 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -21,9 +21,9 @@ import { transformDatasetVersionDiffResponseToDatasetVersionDiff } from './trans import { DatasetDownloadCount } from '../../domain/models/DatasetDownloadCount' import { DatasetVersionSummaryInfo } from '../../domain/models/DatasetVersionSummaryInfo' import { DatasetLinkedCollection } from '../../domain/models/DatasetLinkedCollection' -import { CitationFormats } from '../../domain/models/CitationFormats' +import { CitationFormat } from '../../domain/models/CitationFormat' import { transformDatasetLinkedCollectionsResponseToDatasetLinkedCollection } from './transformers/datasetLinkedCollectionsTransformers' -import { CitationResponse } from '../../domain/models/CitationResponse' +import { FormattedCitation } from '../../domain/models/FormattedCitation' export interface GetAllDatasetPreviewsQueryParams { per_page?: number @@ -100,9 +100,9 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi public async getDatasetCitationInOtherFormats( datasetId: number, datasetVersionId: string | 'LATEST' = 'LATEST', - format: CitationFormats, + format: CitationFormat, includeDeaccessioned = false - ): Promise { + ): Promise { const endpoint = this.buildApiEndpoint( this.datasetsResourceName, `versions/${datasetVersionId}/citation/${format}`, @@ -110,9 +110,17 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi ) const response = await this.doGet(endpoint, true, { includeDeaccessioned }) + const contentType = response.headers['content-type'] + let content: string + if (contentType && contentType.includes('application/json')) { + content = JSON.stringify(response.data) + } else { + content = response.data + } + return { - content: response.data, - contentType: response.headers['content-type'] + content, + contentType } } diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 11a9295d..7d14dfc3 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -51,7 +51,7 @@ import { import { FilesRepository } from '../../../src/files/infra/repositories/FilesRepository' import { DirectUploadClient } from '../../../src/files/infra/clients/DirectUploadClient' import { createTestFileUploadDestination } from '../../testHelpers/files/fileUploadDestinationHelper' -import { CitationFormats } from '../../../src/datasets/domain/models/CitationFormats' +import { CitationFormat } from '../../../src/datasets/domain/models/CitationFormat' const TEST_DIFF_DATASET_DTO: DatasetDTO = { license: { @@ -508,7 +508,7 @@ describe('DatasetsRepository', () => { const citation = await sut.getDatasetCitationInOtherFormats( testDatasetIds.numericId, DatasetNotNumberedVersion.LATEST, - CitationFormats.BibTeX + CitationFormat.BibTeX ) expect(typeof citation.content).toBe('string') @@ -519,7 +519,7 @@ describe('DatasetsRepository', () => { const citation = await sut.getDatasetCitationInOtherFormats( testDatasetIds.numericId, DatasetNotNumberedVersion.LATEST, - CitationFormats.RIS + CitationFormat.RIS ) expect(typeof citation.content).toBe('string') @@ -530,10 +530,10 @@ describe('DatasetsRepository', () => { const citation = await sut.getDatasetCitationInOtherFormats( testDatasetIds.numericId, DatasetNotNumberedVersion.LATEST, - CitationFormats.CSLJson + CitationFormat.CSLJson ) - expect(typeof citation.content).toBe('object') + expect(typeof citation.content).toBe('string') expect(citation.contentType).toMatch(/application\/json/) }) @@ -541,7 +541,7 @@ describe('DatasetsRepository', () => { const citation = await sut.getDatasetCitationInOtherFormats( testDatasetIds.numericId, DatasetNotNumberedVersion.LATEST, - CitationFormats.EndNote + CitationFormat.EndNote ) expect(typeof citation.content).toBe('string') @@ -552,7 +552,7 @@ describe('DatasetsRepository', () => { const citation = await sut.getDatasetCitationInOtherFormats( testDatasetIds.numericId, DatasetNotNumberedVersion.LATEST, - CitationFormats.Internal + CitationFormat.Internal ) expect(typeof citation.content).toBe('string') @@ -567,7 +567,7 @@ describe('DatasetsRepository', () => { sut.getDatasetCitationInOtherFormats( nonExistentId, DatasetNotNumberedVersion.LATEST, - CitationFormats.RIS + CitationFormat.RIS ) ).rejects.toThrow(expectedError) }) @@ -580,7 +580,7 @@ describe('DatasetsRepository', () => { const citation = await sut.getDatasetCitationInOtherFormats( testDatasetIds.numericId, DatasetNotNumberedVersion.LATEST, - CitationFormats.RIS, + CitationFormat.RIS, true ) diff --git a/test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts b/test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts index 7e13e029..60f462f6 100644 --- a/test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts +++ b/test/unit/datasets/GetDatasetCitationInOtherFormats.test.ts @@ -1,17 +1,17 @@ import { GetDatasetCitationInOtherFormats } from '../../../src/datasets/domain/useCases/GetDatasetCitationInOtherFormats' import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository' import { ReadError } from '../../../src/core/domain/repositories/ReadError' -import { CitationFormats } from '../../../src/datasets/domain/models/CitationFormats' +import { CitationFormat } from '../../../src/datasets/domain/models/CitationFormat' import { DatasetNotNumberedVersion } from '../../../src/datasets/domain/models/DatasetNotNumberedVersion' -import { CitationResponse } from '../../../src/datasets/domain/models/CitationResponse' +import { FormattedCitation } from '../../../src/datasets/domain/models/FormattedCitation' describe('GetDatasetCitationInOtherFormats.execute', () => { const testDatasetId = 1 - const testFormat: CitationFormats = CitationFormats.BibTeX + const testFormat: CitationFormat = CitationFormat.BibTeX const testVersion: DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST test('should return citation response on repository success', async () => { - const expectedCitation: CitationResponse = { + const expectedCitation: FormattedCitation = { content: '@data{example, ...}', contentType: 'text/plain' } @@ -22,7 +22,7 @@ describe('GetDatasetCitationInOtherFormats.execute', () => { const sut = new GetDatasetCitationInOtherFormats(datasetsRepositoryStub) - const actual = await sut.execute(testDatasetId, testVersion, testFormat as CitationFormats) + const actual = await sut.execute(testDatasetId, testVersion, testFormat as CitationFormat) expect(actual).toEqual(expectedCitation) }) From 3f01e901ef667b58998490b0ce879f93512654ad Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 5 Aug 2025 09:29:18 -0400 Subject: [PATCH 6/7] fix: update to dataset index.tx --- src/datasets/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/datasets/index.ts b/src/datasets/index.ts index a7a7a14b..51e7a844 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -23,6 +23,7 @@ import { DeleteDatasetDraft } from './domain/useCases/DeleteDatasetDraft' import { LinkDataset } from './domain/useCases/LinkDataset' import { UnlinkDataset } from './domain/useCases/UnlinkDataset' import { GetDatasetLinkedCollections } from './domain/useCases/GetDatasetLinkedCollections' +import { GetDatasetCitationInOtherFormats } from './domain/useCases/GetDatasetCitationInOtherFormats' const datasetsRepository = new DatasetsRepository() @@ -60,6 +61,7 @@ const deleteDatasetDraft = new DeleteDatasetDraft(datasetsRepository) const linkDataset = new LinkDataset(datasetsRepository) const unlinkDataset = new UnlinkDataset(datasetsRepository) const getDatasetLinkedCollections = new GetDatasetLinkedCollections(datasetsRepository) +const getDatasetCitationInOtherFormats = new GetDatasetCitationInOtherFormats(datasetsRepository) export { getDataset, @@ -80,7 +82,8 @@ export { deleteDatasetDraft, linkDataset, unlinkDataset, - getDatasetLinkedCollections + getDatasetLinkedCollections, + getDatasetCitationInOtherFormats } export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion' export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions' From a95794648937457a47746db1e18b14671caa9f90 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 5 Aug 2025 10:31:58 -0400 Subject: [PATCH 7/7] fix: update datasetID to allow persistent id --- .../domain/repositories/IDatasetsRepository.ts | 2 +- .../useCases/GetDatasetCitationInOtherFormats.ts | 4 ++-- src/datasets/infra/repositories/DatasetsRepository.ts | 4 ++-- test/integration/datasets/DatasetsRepository.test.ts | 11 +++++++++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 72e66fd4..83390444 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -68,7 +68,7 @@ export interface IDatasetsRepository { unlinkDataset(datasetId: number, collectionAlias: string): Promise getDatasetLinkedCollections(datasetId: number | string): Promise getDatasetCitationInOtherFormats( - datasetId: number, + datasetId: number | string, datasetVersionId: string, format: CitationFormat, includeDeaccessioned?: boolean diff --git a/src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts b/src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts index a447c107..07bc4fdb 100644 --- a/src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts +++ b/src/datasets/domain/useCases/GetDatasetCitationInOtherFormats.ts @@ -14,14 +14,14 @@ export class GetDatasetCitationInOtherFormats implements UseCase} The citation content, format, and content type. */ async execute( - datasetId: number, + datasetId: number | string, datasetVersionId: string | DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST, format: CitationFormat, includeDeaccessioned = false diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 9c585eb6..b3b0b91e 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -78,7 +78,7 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi } public async getDatasetCitation( - datasetId: number, + datasetId: number | string, datasetVersionId: string, includeDeaccessioned: boolean ): Promise { @@ -98,7 +98,7 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi } public async getDatasetCitationInOtherFormats( - datasetId: number, + datasetId: number | string, datasetVersionId: string | 'LATEST' = 'LATEST', format: CitationFormat, includeDeaccessioned = false diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 7d14dfc3..c0251eb5 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -515,6 +515,17 @@ describe('DatasetsRepository', () => { expect(citation.contentType).toMatch(/text\/plain/) }) + test('should return citation in BibTeX format using persistent id', async () => { + const citation = await sut.getDatasetCitationInOtherFormats( + testDatasetIds.persistentId, + DatasetNotNumberedVersion.LATEST, + CitationFormat.BibTeX + ) + + expect(typeof citation.content).toBe('string') + expect(citation.contentType).toMatch(/text\/plain/) + }) + test('should return citation in RIS format', async () => { const citation = await sut.getDatasetCitationInOtherFormats( testDatasetIds.numericId,