Skip to content

Commit

Permalink
[MODLISTS-62] Create new list version endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
ncovercash committed Dec 18, 2023
1 parent cd104de commit fa58a1b
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 32 deletions.
18 changes: 18 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@
"methods": ["GET"],
"pathPattern": "/lists/{id}/contents",
"permissionsRequired": ["lists.item.contents.get"]
},{
"methods": ["GET"],
"pathPattern": "/lists/{id}/versions",
"permissionsRequired": ["lists.item.versions.collection.get"]
},{
"methods": ["GET"],
"pathPattern": "/lists/{id}/versions/{versionNumber}",
"permissionsRequired": ["lists.item.versions.item.get"]
},{
"methods": ["GET"],
"pathPattern": "/lists/{id}/exports/{exportId}/download",
Expand Down Expand Up @@ -148,6 +156,16 @@
"permissionName": "lists.configuration.get",
"displayName": "Lists: Get configuration of list application",
"description": "Get configuration of lists application"
},
{
"permissionName": "lists.item.versions.collection.get",
"displayName": "Lists: Get previous versions of a list",
"description": "Get the previous versions of a list"
},
{
"permissionName": "lists.item.contents.get",
"displayName": "Lists: Get a previous version of a list",
"description": "Get a previous version of a list"
}
],
"requires": [
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources</source>
<source>${project.build.directory}/generated-sources/src/main/java</source>
</sources>
</configuration>
</execution>
Expand Down
11 changes: 6 additions & 5 deletions src/main/java/org/folio/list/controller/ListController.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,11 @@ public ResponseEntity<Void> cancelRefresh(UUID listId) {

@Override
public ResponseEntity<List<ListVersionDTO>> getListVersions(UUID listId) {
var listVersionDto = listService.getListVersions(listId);
if (listVersionDto == null || listVersionDto.isEmpty()) {
throw new ListNotFoundException(listId, ListActions.READ);
}
return new ResponseEntity<>(listVersionDto, HttpStatus.OK);
return new ResponseEntity<>(listService.getListVersions(listId), HttpStatus.OK);
}

@Override
public ResponseEntity<ListVersionDTO> getListVersion(UUID listId, Integer version) {
return new ResponseEntity<>(listService.getListVersion(listId, version), HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.folio.list.exception;

import java.util.UUID;
import org.folio.list.domain.dto.ListAppError;
import org.folio.list.domain.dto.Parameter;
import org.folio.list.services.ListActions;
import org.springframework.http.HttpStatus;

public class VersionNotFoundException extends AbstractListException {

private static final String ERROR_CODE = "version.not.found";
private static final String ERROR_MESSAGE_TEMPLATE =
"List (id=%s) does not have a version number %d";

private final UUID listId;
private final int versionNumber;
private final ListActions failedAction;
private final HttpStatus httpStatus;

public VersionNotFoundException(
UUID listId,
int versionNumber,
ListActions failedAction
) {
this(listId, versionNumber, failedAction, HttpStatus.NOT_FOUND);
}

public VersionNotFoundException(
UUID listId,
int versionNumber,
ListActions failedAction,
HttpStatus httpStatus
) {
this.listId = listId;
this.versionNumber = versionNumber;
this.httpStatus = httpStatus;
this.failedAction = failedAction;
}

@Override
public HttpStatus getHttpStatus() {
return httpStatus;
}

@Override
public String getMessage() {
return String.format(ERROR_MESSAGE_TEMPLATE, listId, versionNumber);
}

@Override
public ListAppError getError() {
return new ListAppError()
.code(getErrorCode(failedAction, ERROR_CODE))
.message(getMessage())
.addParametersItem(new Parameter().key("id").value(listId.toString()))
.addParametersItem(
new Parameter()
.key("versionNumber")
.value(Integer.toString(versionNumber))
);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package org.folio.list.repository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.folio.list.domain.ListVersion;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.UUID;

@Repository
public interface ListVersionRepository extends CrudRepository<ListVersion, UUID> {
public interface ListVersionRepository
extends CrudRepository<ListVersion, UUID> {
List<ListVersion> findByListId(UUID listId);
Optional<ListVersion> findByListIdAndVersion(UUID listId, int version);
}
26 changes: 25 additions & 1 deletion src/main/java/org/folio/list/services/ListService.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.folio.list.domain.dto.ListUpdateRequestDTO;
import org.folio.list.exception.ListNotFoundException;
import org.folio.list.exception.RefreshInProgressDuringShutdownException;
import org.folio.list.exception.VersionNotFoundException;
import org.folio.list.mapper.*;
import org.folio.list.repository.ListContentsRepository;
import org.folio.list.repository.ListVersionRepository;
Expand All @@ -41,6 +42,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.annotation.Nonnull;

import java.time.OffsetDateTime;
import java.util.*;
Expand Down Expand Up @@ -217,15 +219,37 @@ public void cancelRefresh(UUID listId) {
list.refreshCancelled(executionContext.getUserId());
}

@Nonnull
public List<ListVersionDTO> getListVersions(UUID listId) {
log.info("Getting versions of the list {}", listId);
log.info("Checking that list {} is accessible and exists before getting versions", listId);

ListEntity list = listRepository.findById(listId).orElseThrow(() -> new ListNotFoundException(listId, ListActions.READ));
validationService.assertSharedOrOwnedByUser(list, ListActions.READ);

log.info("Getting all versions of the list {}", listId);

return listVersionRepository
.findByListId(listId)
.stream()
.map(listVersionMapper::toListVersionDTO)
.toList();
}

@Nonnull
public ListVersionDTO getListVersion(UUID listId, int version) {
log.info("Checking that list {} is accessible and exists before getting version {}", listId, version);

ListEntity list = listRepository.findById(listId).orElseThrow(() -> new ListNotFoundException(listId, ListActions.READ));
validationService.assertSharedOrOwnedByUser(list, ListActions.READ);

log.info("Getting version {} of the list {}", version, listId);

return listVersionRepository
.findByListIdAndVersion(listId, version)
.map(listVersionMapper::toListVersionDTO)
.orElseThrow(() -> new VersionNotFoundException(listId, version, ListActions.READ));
}

private void deleteListAndContents(ListEntity list) {
listContentsRepository.deleteContents(list.getId());
listRepository.deleteById(list.getId());
Expand Down
36 changes: 36 additions & 0 deletions src/main/resources/swagger.api/list.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,34 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/ListAppError"
/lists/{id}/versions/{versionNumber}:
get:
operationId: getListVersion
tags:
- list
description: Get a specific version of the specified list.
parameters:
- $ref: '#/components/parameters/id'
- $ref: '#/components/parameters/versionNumber'
responses:
'200':
description: 'Get the requested list version'
content:
application/json:
schema:
$ref: "#/components/schemas/ListVersionDTO"
'404':
description: List or version not found
content:
application/json:
schema:
$ref: "#/components/schemas/ListAppError"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/ListAppError"
components:
parameters:
id:
Expand All @@ -392,6 +420,14 @@ components:
schema:
type: string
format: UUID
versionNumber:
name: versionNumber
in: path
required: true
description: Integer number of the requested version
schema:
type: integer
minimum: 1
schemas:
ListDTO:
$ref: schemas/ListDTO.json
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
package org.folio.list.controller;

import static org.folio.spring.integration.XOkapiHeaders.USER_ID;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.List;
import java.util.UUID;
import org.folio.list.domain.dto.ListVersionDTO;
import org.folio.list.exception.ListNotFoundException;
import org.folio.list.services.ListService;
Expand All @@ -12,20 +22,9 @@
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

import java.util.List;
import java.util.UUID;


import static org.folio.spring.integration.XOkapiHeaders.USER_ID;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(ListController.class)
class ListControllerVersionTest {
class ListControllerVersionTest {

private static final String TENANT_ID = "test-tenant";

@Autowired
Expand All @@ -40,22 +39,42 @@ void testListVersion() throws Exception {
UUID userId = UUID.randomUUID();
ListVersionDTO listVersionDTO1 = TestDataFixture.getListVersionDTO();
ListVersionDTO listVersionDTO2 = TestDataFixture.getListVersionDTO();
List<ListVersionDTO> listVersionDTO = List.of(listVersionDTO1, listVersionDTO2);
List<ListVersionDTO> listVersionDTO = List.of(
listVersionDTO1,
listVersionDTO2
);

var requestBuilder = get("/lists/" + listId + "/versions")
.contentType(APPLICATION_JSON)
.header(XOkapiHeaders.TENANT, TENANT_ID)
.header(USER_ID, userId);

when(listService.getListVersions(listId)).thenReturn(listVersionDTO);
mockMvc.perform(requestBuilder)
mockMvc
.perform(requestBuilder)
.andExpect(status().isOk())
.andExpect(jsonPath("$.[0].id", Matchers.is(listVersionDTO1.getId().toString())))
.andExpect(jsonPath("$.[0].listId", Matchers.is(listVersionDTO1.getListId().toString())))
.andExpect(
jsonPath("$.[0].id", Matchers.is(listVersionDTO1.getId().toString()))
)
.andExpect(
jsonPath(
"$.[0].listId",
Matchers.is(listVersionDTO1.getListId().toString())
)
)
.andExpect(jsonPath("$.[0].name", Matchers.is(listVersionDTO1.getName())))
.andExpect(jsonPath("$.[1].id", Matchers.is(listVersionDTO2.getId().toString())))
.andExpect(jsonPath("$.[1].listId", Matchers.is(listVersionDTO2.getListId().toString())))
.andExpect(jsonPath("$.[1].name", Matchers.is(listVersionDTO2.getName())));
.andExpect(
jsonPath("$.[1].id", Matchers.is(listVersionDTO2.getId().toString()))
)
.andExpect(
jsonPath(
"$.[1].listId",
Matchers.is(listVersionDTO2.getListId().toString())
)
)
.andExpect(
jsonPath("$.[1].name", Matchers.is(listVersionDTO2.getName()))
);
}

@Test
Expand All @@ -67,7 +86,8 @@ void shouldReturnHttp404WhenListNotFound() throws Exception {
when(listService.getListVersions(UUID.randomUUID()))
.thenThrow(ListNotFoundException.class);

mockMvc.perform(requestBuilder)
mockMvc
.perform(requestBuilder)
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.code", is("read-list.not.found")));
}
Expand Down

0 comments on commit fa58a1b

Please sign in to comment.