Skip to content

Commit

Permalink
Merge branch 'develop' into feat/256-delete-collection-use-case
Browse files Browse the repository at this point in the history
  • Loading branch information
g-saracca committed Feb 24, 2025
2 parents 29932e4 + e689f25 commit 4325fe1
Show file tree
Hide file tree
Showing 38 changed files with 1,448 additions and 33 deletions.
121 changes: 121 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The different use cases currently available in the package are classified below,
- [Create a Dataset](#create-a-dataset)
- [Update a Dataset](#update-a-dataset)
- [Publish a Dataset](#publish-a-dataset)
- [Deaccession a Dataset](#deaccession-a-dataset)
- [Files](#Files)
- [Files read use cases](#files-read-use-cases)
- [Get a File](#get-a-file)
Expand All @@ -50,6 +51,8 @@ The different use cases currently available in the package are classified below,
- [List Files in a Dataset](#list-files-in-a-dataset)
- [Files write use cases](#files-write-use-cases)
- [File Uploading Use Cases](#file-uploading-use-cases)
- [Delete a File](#delete-a-file)
- [Restrict or Unrestrict a File](#restrict-or-unrestrict-a-file)
- [Metadata Blocks](#metadata-blocks)
- [Metadata Blocks read use cases](#metadata-blocks-read-use-cases)
- [Get All Facetable Metadata Fields](#get-all-facetable-metadata-fields)
Expand All @@ -68,6 +71,8 @@ The different use cases currently available in the package are classified below,
- [Get Dataverse Backend Version](#get-dataverse-backend-version)
- [Get Maximum Embargo Duration In Months](#get-maximum-embargo-duration-in-months)
- [Get ZIP Download Limit](#get-zip-download-limit)
- [Contact](#Contact)
- [Send Feedback to Object Contacts](#send-feedback-to-object-contacts)

## Collections

Expand Down Expand Up @@ -772,6 +777,35 @@ The `versionUpdateType` parameter can be a [VersionUpdateType](../src/datasets/d
- `VersionUpdateType.MAJOR`
- `VersionUpdateType.UPDATE_CURRENT`

#### Deaccession a Dataset

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

##### Example call:

```typescript
import { deaccessionDataset } from '@iqss/dataverse-client-javascript'

/* ... */

const datasetId = 1
const version = ':latestPublished'
const deaccessionDatasetDTO = {
deaccessionReason: 'Description of the deaccession reason.',
deaccessionForwardURL: 'https://demo.dataverse.org'
}

deaccessionDataset.execute(datasetId, version, deaccessionDatasetDTO)

/* ... */
```

_See [use case](../src/datasets/domain/useCases/DeaccessionDataset.ts) implementation_.
The `datasetId` parameter can be a string for persistent identifiers, or a number for numeric identifiers.
The `version` parameter should be a string or a [DatasetNotNumberedVersion](../src/datasets/domain/models/DatasetNotNumberedVersion.ts) enum value.

You cannot deaccession a dataset more than once. If you call this endpoint twice for the same dataset version, you will get a not found error on the second call, since the dataset you are looking for will no longer be published since it is already deaccessioned.

## Files

### Files read use cases
Expand Down Expand Up @@ -1222,6 +1256,56 @@ The following error might arise from the `AddUploadedFileToDataset` use case:

- AddUploadedFileToDatasetError: This error indicates that there was an error while adding the uploaded file to the dataset.

#### Delete a File

Deletes a File.

##### Example call:

```typescript
import { deleteFile } from '@iqss/dataverse-client-javascript'

/* ... */

const fileId = 12345

deleteFile.execute(fileId)

/* ... */
```

_See [use case](../src/files/domain/useCases/DeleteFile.ts) implementation_.

The `fileId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers.

Note that the behavior of deleting files depends on if the dataset has ever been published or not.

- If the dataset has never been published, the file will be deleted forever.
- If the dataset has published, the file is deleted from the draft (and future published versions).
- If the dataset has published, the deleted file can still be downloaded because it was part of a published version.

#### Restrict or Unrestrict a File

Restrict or unrestrict an existing file.

##### Example call:

```typescript
import { restrictFile } from '@iqss/dataverse-client-javascript'

/* ... */

const fileId = 12345

restrictFile.execute(fileId, true)

/* ... */
```

_See [use case](../src/files/domain/useCases/RestrictFile.ts) implementation_.

The `fileId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers.

## Metadata Blocks

### Metadata Blocks read use cases
Expand Down Expand Up @@ -1489,3 +1573,40 @@ getZipDownloadLimit.execute().then((downloadLimit: number) => {
```

_See [use case](../src/info/domain/useCases/GetZipDownloadLimit.ts) implementation_.

## Contact

#### Send Feedback to Object Contacts

Returns a [Contact](../src/contactInfo/domain/models/Contact.ts) object, which contains contact return information, showing fromEmail, subject, body.

##### Example call:

```typescript
import { submitContactInfo } from '@iqss/dataverse-client-javascript'

/* ... */

const contactDTO: ContactDTO = {
targedId: 1
subject: 'Data Question',
body: 'Please help me understand your data. Thank you!',
fromEmail: '[email protected]'
}

submitContactInfo.execute(contactDTO)

/* ... */
```

_See [use case](../src/info/domain/useCases/submitContactInfo.ts) implementation_.

The above example would submit feedback to all contacts of a object where the object targetId = 1.

In ContactDTO, it takes the following information:

- **targetId**: the numeric identifier of the collection, dataset, or datafile. Persistent ids and collection aliases are not supported. (Optional)
- **identifier**: the alias of a collection or the persistence id of a dataset or datafile. (Optional)
- **subject**: the email subject line.
- **body**: the email body to send.
- **fromEmail**: the email to list in the reply-to field.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
CollectionPayload
} from './CollectionPayload'
import { transformPayloadToOwnerNode } from '../../../../core/infra/repositories/transformers/dvObjectOwnerNodeTransformer'
import { transformHtmlToMarkdown } from '../../../../datasets/infra/repositories/transformers/datasetTransformers'
import { CollectionFacet } from '../../../domain/models/CollectionFacet'
import { CollectionFacetPayload } from './CollectionFacetPayload'
import {
Expand Down Expand Up @@ -53,10 +52,8 @@ const transformPayloadToCollection = (collectionPayload: CollectionPayload): Col
type: collectionPayload.dataverseType as CollectionType,
isMetadataBlockRoot: collectionPayload.isMetadataBlockRoot,
isFacetRoot: collectionPayload.isFacetRoot,
description: collectionPayload.description,
childCount: collectionPayload.childCount,
...(collectionPayload.description && {
description: transformHtmlToMarkdown(collectionPayload.description)
}),
...(collectionPayload.isPartOf && {
isPartOf: transformPayloadToOwnerNode(collectionPayload.isPartOf)
}),
Expand Down
7 changes: 7 additions & 0 deletions src/contactInfo/domain/dtos/ContactDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface ContactDTO {
targetId?: number
identifier?: string
subject: string
body: string
fromEmail: string
}
5 changes: 5 additions & 0 deletions src/contactInfo/domain/models/Contact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Contact {
fromEmail: string
body: string
subject: string
}
6 changes: 6 additions & 0 deletions src/contactInfo/domain/repositories/IContactRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Contact } from '../models/Contact'
import { ContactDTO } from '../dtos/ContactDTO'

export interface IContactRepository {
submitContactInfo(contactDTO: ContactDTO): Promise<Contact[]>
}
23 changes: 23 additions & 0 deletions src/contactInfo/domain/useCases/SubmitContactInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { ContactDTO } from '../dtos/ContactDTO'
import { Contact } from '../models/Contact'
import { IContactRepository } from '../repositories/IContactRepository'

export class SubmitContactInfo implements UseCase<Contact[]> {
private contactRepository: IContactRepository

constructor(contactRepository: IContactRepository) {
this.contactRepository = contactRepository
}

/**
* Submits contact information and returns a Contact model containing the submitted data.
*
* @param {ContactDTO} contactDTO - The contact information to be submitted.
* @returns {Promise<Contact[]>} A promise resolving to a list of contact.
*/

async execute(contactDTO: ContactDTO): Promise<Contact[]> {
return await this.contactRepository.submitContactInfo(contactDTO)
}
}
9 changes: 9 additions & 0 deletions src/contactInfo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SubmitContactInfo } from './domain/useCases/SubmitContactInfo'
import { ContactRepository } from './infra/repositories/ContactRepository'

const contactRepository = new ContactRepository()
const submitContactInfo = new SubmitContactInfo(contactRepository)

export { submitContactInfo }
export { Contact } from './domain/models/Contact'
export { ContactDTO } from './domain/dtos/ContactDTO'
23 changes: 23 additions & 0 deletions src/contactInfo/infra/repositories/ContactRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ApiRepository } from '../../../core/infra/repositories/ApiRepository'
import { Contact } from '../../domain/models/Contact'
import { IContactRepository } from '../../domain/repositories/IContactRepository'
import { ContactDTO } from '../../domain/dtos/ContactDTO'

export class ContactRepository extends ApiRepository implements IContactRepository {
public async submitContactInfo(contactDTO: ContactDTO): Promise<Contact[]> {
return this.doPost(`/sendfeedback`, contactDTO)
.then((response) => {
const responseData = response.data
const contact: Contact[] = responseData.data.map((item: Contact) => ({
fromEmail: item.fromEmail,
subject: item.subject,
body: item.body
}))

return contact
})
.catch((error) => {
throw error
})
}
}
28 changes: 18 additions & 10 deletions src/core/infra/repositories/ApiRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export abstract class ApiRepository {

public async doPost(
apiEndpoint: string,
data: string | object,
data: string | object | boolean,
queryParams: object = {},
contentType: string = ApiConstants.CONTENT_TYPE_APPLICATION_JSON
): Promise<AxiosResponse> {
Expand All @@ -29,7 +29,7 @@ export abstract class ApiRepository {

public async doPut(
apiEndpoint: string,
data: string | object,
data: string | object | boolean,
queryParams: object = {},
contentType: string = ApiConstants.CONTENT_TYPE_APPLICATION_JSON
): Promise<AxiosResponse> {
Expand All @@ -47,14 +47,16 @@ export abstract class ApiRepository {

protected buildApiEndpoint(
resourceName: string,
operation: string,
resourceId: number | string | undefined = undefined
operation?: string,
resourceId?: number | string
) {
const operationSegment = operation ? `/${operation}` : ''

return typeof resourceId === 'number'
? `/${resourceName}/${resourceId}/${operation}`
? `/${resourceName}/${resourceId}${operationSegment}`
: typeof resourceId === 'string'
? `/${resourceName}/:persistentId/${operation}?persistentId=${resourceId}`
: `/${resourceName}/${operation}`
? `/${resourceName}/:persistentId${operationSegment}?persistentId=${resourceId}`
: `/${resourceName}${operationSegment}`
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -68,12 +70,18 @@ export abstract class ApiRepository {
private async doRequest(
method: 'post' | 'put',
apiEndpoint: string,
data: string | object,
data: string | object | boolean,
queryParams: object = {},
contentType: string = ApiConstants.CONTENT_TYPE_APPLICATION_JSON
): Promise<AxiosResponse> {
const requestData =
contentType == ApiConstants.CONTENT_TYPE_APPLICATION_JSON ? JSON.stringify(data) : data
let requestData = data

if (contentType === ApiConstants.CONTENT_TYPE_APPLICATION_JSON) {
if (typeof data === 'object' || typeof data === 'boolean') {
requestData = JSON.stringify(data)
}
}

const requestUrl = buildRequestUrl(apiEndpoint)
const requestConfig = buildRequestConfig(true, queryParams, contentType)

Expand Down
4 changes: 4 additions & 0 deletions src/datasets/domain/dtos/DatasetDeaccessionDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface DatasetDeaccessionDTO {
deaccessionReason: string
deaccessionForwardURL?: string
}
6 changes: 6 additions & 0 deletions src/datasets/domain/repositories/IDatasetsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DatasetPreviewSubset } from '../models/DatasetPreviewSubset'
import { DatasetUserPermissions } from '../models/DatasetUserPermissions'
import { CreatedDatasetIdentifiers } from '../models/CreatedDatasetIdentifiers'
import { DatasetDTO } from '../dtos/DatasetDTO'
import { DatasetDeaccessionDTO } from '../dtos/DatasetDeaccessionDTO'
import { MetadataBlock } from '../../../metadataBlocks'
import { DatasetVersionDiff } from '../models/DatasetVersionDiff'

Expand Down Expand Up @@ -45,4 +46,9 @@ export interface IDatasetsRepository {
dataset: DatasetDTO,
datasetMetadataBlocks: MetadataBlock[]
): Promise<void>
deaccessionDataset(
datasetId: number | string,
datasetVersionId: string,
deaccessionDTO: DatasetDeaccessionDTO
): Promise<void>
}
31 changes: 31 additions & 0 deletions src/datasets/domain/useCases/DeaccessionDataset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { IDatasetsRepository } from '../repositories/IDatasetsRepository'
import { DatasetDeaccessionDTO } from '../dtos/DatasetDeaccessionDTO'
import { DatasetNotNumberedVersion } from '../models/DatasetNotNumberedVersion'

export class DeaccessionDataset implements UseCase<void> {
private datasetsRepository: IDatasetsRepository

constructor(datasetsRepository: IDatasetsRepository) {
this.datasetsRepository = datasetsRepository
}

/**
* Deaccession a dataset, given a dataset id, a dataset version id, and a DatasetDeaccessionDTO object.
* @param {number | string} [datasetId] - The dataset identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
* @param {string | DatasetNotNumberedVersion} [datasetVersionId] - The dataset version identifier, which can be a version-specific numeric string (for example, 1.0) or a DatasetNotNumberedVersion enum value.
* @returns A promise that resolves when the dataset is deaccessioned
* @throws An error if the dataset could not be deaccessioned
*/
async execute(
datasetId: number | string,
datasetVersionId: string | DatasetNotNumberedVersion,
DatasetDeaccessionDTO: DatasetDeaccessionDTO
): Promise<void> {
return await this.datasetsRepository.deaccessionDataset(
datasetId,
datasetVersionId,
DatasetDeaccessionDTO
)
}
}
Loading

0 comments on commit 4325fe1

Please sign in to comment.