Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Featured Items Use Cases #235

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ The different use cases currently available in the package are classified below,
- [Get Collection Facets](#get-collection-facets)
- [Get User Permissions on a Collection](#get-user-permissions-on-a-collection)
- [List All Collection Items](#list-all-collection-items)
- [Get Collection Featured Items](#get-collection-featured-items)
- [Collections write use cases](#collections-write-use-cases)
- [Create a Collection](#create-a-collection)
- [Update a Collection](#update-a-collection)
- [Publish a Collection](#publish-a-collection)
- [Update Collection Featured Items](#update-collection-featured-items)
- [Delete Collection Featured Items](#delete-collection-featured-items)
- [Datasets](#Datasets)
- [Datasets read use cases](#datasets-read-use-cases)
- [Get a Dataset](#get-a-dataset)
Expand Down Expand Up @@ -202,6 +205,33 @@ This use case supports the following optional parameters depending on the search
- **offset**: (number) Offset for pagination.
- **collectionSearchCriteria**: ([CollectionSearchCriteria](../src/collections/domain/models/CollectionSearchCriteria.ts)) Supports filtering the collection items by different properties.

#### Get Collection Featured Items

Returns a [CollectionFeaturedItem](../src/collections/domain/models/CollectionFeaturedItem.ts) array containing the featured items of the requested collection, given the collection identifier or alias.

##### Example call:

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

const collectionIdOrAlias = 12345

getCollectionFeaturedItems
.execute(collectionId)
.then((featuredItems: CollectionFeaturedItem[]) => {
/* ... */
})
.catch((error: Error) => {
/* ... */
})
```

_See [use case](../src/collections/domain/useCases/GetCollectionFeaturedItems.ts)_ definition.

The `collectionIdOrAlias` is a generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId).

If no collection identifier is specified, the default collection identifier; `:root` will be used. If you want to search for a different collection, you must add the collection identifier as a parameter in the use case call.

### Collections Write Use Cases

#### Create a Collection
Expand Down Expand Up @@ -285,6 +315,54 @@ The `collectionIdOrAlias` is a generic collection identifier, which can be eithe

_See [use case](../src/collections/domain/useCases/PublishCollection.ts)_ definition.

#### Update Collection Featured Items

Updates all featured items, given a collection identifier and a CollectionFeaturedItemsDTO.

##### Example call:

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

/* ... */

const collectionIdOrAlias = 12345

updateCollectionFeaturedItems
.execute(collectionIdOrAlias)
.then((collectionFeaturedItems: CollectionFeaturedItem[]) => {
/* ... */
})

/* ... */
```

The `collectionIdOrAlias` is a generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId).

_See [use case](../src/collections/domain/useCases/UpdateCollectionFeaturedItems.ts)_ definition.

#### Delete Collection Featured Items

Deletes all featured items from a collection, given a collection identifier.

##### Example call:

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

/* ... */

const collectionIdOrAlias = 12345

deleteCollectionFeaturedItems.execute(collectionIdOrAlias)

/* ... */
```

The `collectionIdOrAlias` is a generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId).

_See [use case](../src/collections/domain/useCases/DeleteCollectionFeaturedItems.ts)_ definition.

## Datasets

### Datasets Read Use Cases
Expand Down
9 changes: 9 additions & 0 deletions src/collections/domain/dtos/CollectionFeaturedItemsDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type CollectionFeaturedItemsDTO = CollectionFeaturedItemDTO[]

export interface CollectionFeaturedItemDTO {
id?: number
content: string
displayOrder: number
file?: File
keepFile: boolean
}
7 changes: 7 additions & 0 deletions src/collections/domain/models/CollectionFeaturedItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface CollectionFeaturedItem {
id: number
content: string
imageFileName?: string
imageFileUrl?: string
displayOrder: number
}
10 changes: 10 additions & 0 deletions src/collections/domain/repositories/ICollectionsRepository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { CollectionDTO } from '../dtos/CollectionDTO'
import { CollectionFeaturedItemsDTO } from '../dtos/CollectionFeaturedItemsDTO'
import { Collection } from '../models/Collection'
import { CollectionFacet } from '../models/CollectionFacet'
import { CollectionFeaturedItem } from '../models/CollectionFeaturedItem'
import { CollectionItemSubset } from '../models/CollectionItemSubset'
import { CollectionSearchCriteria } from '../models/CollectionSearchCriteria'
import { CollectionUserPermissions } from '../models/CollectionUserPermissions'
Expand All @@ -26,4 +28,12 @@ export interface ICollectionsRepository {
collectionIdOrAlias: number | string,
updatedCollection: CollectionDTO
): Promise<void>
getCollectionFeaturedItems(
collectionIdOrAlias: number | string
): Promise<CollectionFeaturedItem[]>
updateCollectionFeaturedItems(
collectionIdOrAlias: number | string,
featuredItemDTOs: CollectionFeaturedItemsDTO
): Promise<CollectionFeaturedItem[]>
deleteCollectionFeaturedItems(collectionIdOrAlias: number | string): Promise<void>
}
23 changes: 23 additions & 0 deletions src/collections/domain/useCases/DeleteCollectionFeaturedItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { ROOT_COLLECTION_ID } from '../models/Collection'
import { ICollectionsRepository } from '../repositories/ICollectionsRepository'

export class DeleteCollectionFeaturedItems implements UseCase<void> {
private collectionsRepository: ICollectionsRepository

constructor(collectionsRepository: ICollectionsRepository) {
this.collectionsRepository = collectionsRepository
}

/**
* Deletes all featured items from a collection, given a collection identifier.
*
* @param {number | string} [collectionIdOrAlias = ':root'] - A generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId)
* If this parameter is not set, the default value is: ':root'
* @returns {Promise<void>} - This method does not return anything upon successful completion.
* @throws {WriteError} - If there are errors while writing data.
*/
async execute(collectionIdOrAlias: number | string = ROOT_COLLECTION_ID): Promise<void> {
return await this.collectionsRepository.deleteCollectionFeaturedItems(collectionIdOrAlias)
}
}
25 changes: 25 additions & 0 deletions src/collections/domain/useCases/GetCollectionFeaturedItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { ICollectionsRepository } from '../repositories/ICollectionsRepository'
import { ROOT_COLLECTION_ID } from '../models/Collection'
import { CollectionFeaturedItem } from '../models/CollectionFeaturedItem'

export class GetCollectionFeaturedItems implements UseCase<CollectionFeaturedItem[]> {
private collectionsRepository: ICollectionsRepository

constructor(collectionsRepository: ICollectionsRepository) {
this.collectionsRepository = collectionsRepository
}

/**
* Returns a CollectionFeaturedItem array containing the featured items of the requested collection, given the collection identifier or alias.
*
* @param {number | string} [collectionIdOrAlias = ':root'] - A generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId)
* If this parameter is not set, the default value is: ':root'
* @returns {Promise<CollectionFeaturedItem[]>}
*/
async execute(
collectionIdOrAlias: number | string = ROOT_COLLECTION_ID
): Promise<CollectionFeaturedItem[]> {
return await this.collectionsRepository.getCollectionFeaturedItems(collectionIdOrAlias)
}
}
32 changes: 32 additions & 0 deletions src/collections/domain/useCases/UpdateCollectionFeaturedItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { CollectionFeaturedItemsDTO } from '../dtos/CollectionFeaturedItemsDTO'
import { ROOT_COLLECTION_ID } from '../models/Collection'
import { CollectionFeaturedItem } from '../models/CollectionFeaturedItem'
import { ICollectionsRepository } from '../repositories/ICollectionsRepository'

export class UpdateCollectionFeaturedItems implements UseCase<CollectionFeaturedItem[]> {
private collectionsRepository: ICollectionsRepository

constructor(collectionsRepository: ICollectionsRepository) {
this.collectionsRepository = collectionsRepository
}

/**
* Updates all featured items, given a collection identifier and a CollectionFeaturedItemsDTO.
*
* @param {number | string} [collectionIdOrAlias = ':root'] - A generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId)
* If this parameter is not set, the default value is: ':root'
* @param {CollectionFeaturedItemsDTO} [newCollectionFeaturedItems] - CollectionFeaturedItemsDTO object including the updated collection featured items data.
* @returns {Promise<CollectionFeaturedItem[]>} -This method returns the updated collection featured items upon successful completion.
* @throws {WriteError} - If there are errors while writing data.
*/
async execute(
collectionIdOrAlias: number | string = ROOT_COLLECTION_ID,
featuredItemsDTO: CollectionFeaturedItemsDTO
): Promise<CollectionFeaturedItem[]> {
return await this.collectionsRepository.updateCollectionFeaturedItems(
collectionIdOrAlias,
featuredItemsDTO
)
}
}
14 changes: 12 additions & 2 deletions src/collections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { GetCollectionUserPermissions } from './domain/useCases/GetCollectionUse
import { GetCollectionItems } from './domain/useCases/GetCollectionItems'
import { PublishCollection } from './domain/useCases/PublishCollection'
import { UpdateCollection } from './domain/useCases/UpdateCollection'

import { GetCollectionFeaturedItems } from './domain/useCases/GetCollectionFeaturedItems'
import { CollectionsRepository } from './infra/repositories/CollectionsRepository'
import { UpdateCollectionFeaturedItems } from './domain/useCases/UpdateCollectionFeaturedItems'
import { DeleteCollectionFeaturedItems } from './domain/useCases/DeleteCollectionFeaturedItems'

const collectionsRepository = new CollectionsRepository()

Expand All @@ -17,6 +19,9 @@ const getCollectionUserPermissions = new GetCollectionUserPermissions(collection
const getCollectionItems = new GetCollectionItems(collectionsRepository)
const publishCollection = new PublishCollection(collectionsRepository)
const updateCollection = new UpdateCollection(collectionsRepository)
const getCollectionFeaturedItems = new GetCollectionFeaturedItems(collectionsRepository)
const updateCollectionFeaturedItems = new UpdateCollectionFeaturedItems(collectionsRepository)
const deleteCollectionFeaturedItems = new DeleteCollectionFeaturedItems(collectionsRepository)

export {
getCollection,
Expand All @@ -25,7 +30,10 @@ export {
getCollectionUserPermissions,
getCollectionItems,
publishCollection,
updateCollection
updateCollection,
getCollectionFeaturedItems,
updateCollectionFeaturedItems,
deleteCollectionFeaturedItems
}
export { Collection, CollectionInputLevel } from './domain/models/Collection'
export { CollectionFacet } from './domain/models/CollectionFacet'
Expand All @@ -34,3 +42,5 @@ export { CollectionDTO, CollectionInputLevelDTO } from './domain/dtos/Collection
export { CollectionPreview } from './domain/models/CollectionPreview'
export { CollectionItemType } from './domain/models/CollectionItemType'
export { CollectionSearchCriteria } from './domain/models/CollectionSearchCriteria'
export { CollectionFeaturedItem } from './domain/models/CollectionFeaturedItem'
export { CollectionFeaturedItemsDTO } from './domain/dtos/CollectionFeaturedItemsDTO'
67 changes: 67 additions & 0 deletions src/collections/infra/repositories/CollectionsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import {
SortType
} from '../../domain/models/CollectionSearchCriteria'
import { CollectionItemType } from '../../domain/models/CollectionItemType'
import { CollectionFeaturedItem } from '../../domain/models/CollectionFeaturedItem'
import { transformCollectionFeaturedItemsPayloadToCollectionFeaturedItems } from './transformers/collectionFeaturedItemsTransformer'
import { CollectionFeaturedItemsDTO } from '../../domain/dtos/CollectionFeaturedItemsDTO'
import { ApiConstants } from '../../../core/infra/repositories/ApiConstants'

export interface NewCollectionRequestPayload {
alias: string
Expand Down Expand Up @@ -240,4 +244,67 @@ export class CollectionsRepository extends ApiRepository implements ICollections
})
}
}

public async getCollectionFeaturedItems(
collectionIdOrAlias: number | string
): Promise<CollectionFeaturedItem[]> {
return this.doGet(`/${this.collectionsResourceName}/${collectionIdOrAlias}/featuredItems`, true)
.then((response) =>
transformCollectionFeaturedItemsPayloadToCollectionFeaturedItems(response.data.data)
)
.catch((error) => {
throw error
})
}

public async updateCollectionFeaturedItems(
collectionIdOrAlias: number | string,
featuredItemsDTO: CollectionFeaturedItemsDTO
): Promise<CollectionFeaturedItem[]> {
const featuredItemsFormData = this.toFeaturedItemsFormData(featuredItemsDTO)

return this.doPut(
`/${this.collectionsResourceName}/${collectionIdOrAlias}/featuredItems`,
featuredItemsFormData,
undefined,
ApiConstants.CONTENT_TYPE_MULTIPART_FORM_DATA
)
.then((response) =>
transformCollectionFeaturedItemsPayloadToCollectionFeaturedItems(response.data.data)
)
.catch((error) => {
throw error
})
}

private toFeaturedItemsFormData(featuredItemsDTO: CollectionFeaturedItemsDTO): FormData {
// This is not really necessary because we are sending displayOrder property anyways, but I wanted to keep the order of the items in the form data
const orderedFeaturedItemsDTO = featuredItemsDTO.sort((a, b) => a.displayOrder - b.displayOrder)

const formData = new FormData()

orderedFeaturedItemsDTO.forEach((item) => {
const { id, content, displayOrder, file, keepFile } = item
const fileName = file ? file.name : ''

formData.append('id', id ? id.toString() : '0')
formData.append('content', content)
formData.append('displayOrder', displayOrder.toString())
formData.append('keepFile', keepFile.toString())
formData.append('fileName', fileName)
if (file) {
formData.append('file', file)
}
})

return formData
}

public async deleteCollectionFeaturedItems(collectionIdOrAlias: number | string): Promise<void> {
return this.doDelete(`/${this.collectionsResourceName}/${collectionIdOrAlias}/featuredItems`)
.then(() => undefined)
.catch((error) => {
throw error
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface CollectionFeaturedItemPayload {
id: number
content: string
imageFileName: string | null
imageFileUrl: string | null
displayOrder: number
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { CollectionFeaturedItem } from '../../../domain/models/CollectionFeaturedItem'
import { CollectionFeaturedItemPayload } from './CollectionFeaturedItemPayload'

export const transformCollectionFeaturedItemsPayloadToCollectionFeaturedItems = (
collectionFeaturedItemsPayload: CollectionFeaturedItemPayload[]
): CollectionFeaturedItem[] => {
return collectionFeaturedItemsPayload
.map((collectionFeaturedItemPayload) => ({
id: collectionFeaturedItemPayload.id,
content: collectionFeaturedItemPayload.content,
imageFileUrl: collectionFeaturedItemPayload.imageFileUrl,
imageFileName: collectionFeaturedItemPayload.imageFileName,
displayOrder: collectionFeaturedItemPayload.displayOrder
}))
.sort((a, b) => a.displayOrder - b.displayOrder)
}
5 changes: 3 additions & 2 deletions src/core/infra/repositories/ApiRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ export abstract class ApiRepository {
public async doPut(
apiEndpoint: string,
data: string | object,
queryParams: object = {}
queryParams: object = {},
contentType: string = ApiConstants.CONTENT_TYPE_APPLICATION_JSON
): Promise<AxiosResponse> {
return await this.doRequest('put', apiEndpoint, data, queryParams)
return await this.doRequest('put', apiEndpoint, data, queryParams, contentType)
}

public async doDelete(apiEndpoint: string, queryParams: object = {}): Promise<AxiosResponse> {
Expand Down
5 changes: 5 additions & 0 deletions src/core/infra/repositories/apiConfigBuilders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export const buildRequestConfig = (
...(abortSignal && { signal: abortSignal })
}

// When using multipart/form-data for axios to work properly its better to avoid setting the content-type and let the browser manage it
if (contentType === ApiConstants.CONTENT_TYPE_MULTIPART_FORM_DATA) {
requestConfig.headers['Content-Type'] = undefined
}

if (!authRequired) {
return requestConfig
}
Expand Down
Loading
Loading