diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d79d6828..a1db6da3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,6 +116,7 @@ jobs: APP_ROOT: ${{ needs.setup_workflow_env.outputs.APP_ROOT }} SERVICE_ID: ${{ needs.setup_workflow_env.outputs.SERVICE_ID }} PUBLIC_URL: ${{ needs.setup_workflow_env.outputs.PUBLIC_URL }} + do_not_generate_additional_host_names: "true" POSTGRES_ENABLED: ${{ needs.setup_workflow_env.outputs.POSTGRES_ENABLED == 'true'}} default_port: "${{ needs.setup_workflow_env.outputs.default_port}}" submodules: ${{ needs.setup_workflow_env.outputs.submodules }} @@ -131,6 +132,7 @@ jobs: APP_ROOT: ${{ needs.setup_workflow_env.outputs.APP_ROOT }} SERVICE_ID: ${{ needs.setup_workflow_env.outputs.SERVICE_ID }} PUBLIC_URL: ${{ needs.setup_workflow_env.outputs.PUBLIC_URL }} + do_not_generate_additional_host_names: "true" POSTGRES_ENABLED: ${{ needs.setup_workflow_env.outputs.POSTGRES_ENABLED == 'true'}} default_port: "${{ needs.setup_workflow_env.outputs.default_port}}" submodules: ${{ needs.setup_workflow_env.outputs.submodules }} diff --git a/chart/templates/deployment-maketplace.yaml b/chart/templates/deployment-maketplace.yaml index 200eae85..49a27c6f 100644 --- a/chart/templates/deployment-maketplace.yaml +++ b/chart/templates/deployment-maketplace.yaml @@ -50,7 +50,7 @@ spec: path: /api/property-types port: 8080 scheme: HTTP - initialDelaySeconds: 30 + initialDelaySeconds: 90 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 3 @@ -65,7 +65,7 @@ spec: path: /api/property-types port: 8080 scheme: HTTP - initialDelaySeconds: 30 + initialDelaySeconds: 90 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 3 diff --git a/src/main/java/eu/sshopencloud/marketplace/controllers/sources/SourceController.java b/src/main/java/eu/sshopencloud/marketplace/controllers/sources/SourceController.java index 232fc668..a8ac04e8 100644 --- a/src/main/java/eu/sshopencloud/marketplace/controllers/sources/SourceController.java +++ b/src/main/java/eu/sshopencloud/marketplace/controllers/sources/SourceController.java @@ -1,7 +1,8 @@ package eu.sshopencloud.marketplace.controllers.sources; import eu.sshopencloud.marketplace.controllers.PageTooLargeException; -import eu.sshopencloud.marketplace.dto.search.PaginatedSearchItemsBasic; +import eu.sshopencloud.marketplace.dto.items.ItemBasicDto; +import eu.sshopencloud.marketplace.dto.items.PaginatedItemsBasic; import eu.sshopencloud.marketplace.dto.sources.PaginatedSources; import eu.sshopencloud.marketplace.dto.sources.SourceCore; import eu.sshopencloud.marketplace.dto.sources.SourceDto; @@ -68,19 +69,19 @@ public void deleteSource(@PathVariable("id") long id) { @Operation(summary = "Get list of items for given source") @GetMapping(path = "/{sourceId}/items", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getItemsForSource(@PathVariable("sourceId") Long sourceId, + public ResponseEntity> getItemsForSource(@PathVariable("sourceId") Long sourceId, @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "perpage", required = false) Integer perpage) throws PageTooLargeException { - return ResponseEntity.ok(itemService.getItemsBySource(sourceId, pageCoordsValidator.validate(page, perpage))); + return ResponseEntity.ok(itemService.getItemsBySource(sourceId, null, pageCoordsValidator.validate(page, perpage))); } @Operation(summary = "Get list of items for given source and id of an item in this source") @GetMapping(path = "/{sourceId}/items/{sourceItemId}", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getItemsForSourceAndSourceItemId(@PathVariable("sourceId") Long sourceId, - @PathVariable("sourceItemId") String sourceItemId, - @RequestParam(value = "page", required = false) Integer page, - @RequestParam(value = "perpage", required = false) Integer perpage) + public ResponseEntity> getItemsForSourceAndSourceItemId(@PathVariable("sourceId") Long sourceId, + @PathVariable("sourceItemId") String sourceItemId, + @RequestParam(value = "page", required = false) Integer page, + @RequestParam(value = "perpage", required = false) Integer perpage) throws PageTooLargeException { return ResponseEntity.ok(itemService.getItemsBySource(sourceId, sourceItemId, pageCoordsValidator.validate(page, perpage))); } diff --git a/src/main/java/eu/sshopencloud/marketplace/repositories/items/ItemRepository.java b/src/main/java/eu/sshopencloud/marketplace/repositories/items/ItemRepository.java index 0e245ffd..eca8952b 100644 --- a/src/main/java/eu/sshopencloud/marketplace/repositories/items/ItemRepository.java +++ b/src/main/java/eu/sshopencloud/marketplace/repositories/items/ItemRepository.java @@ -150,4 +150,19 @@ Optional getMinLastUpdateDateOfActiveItemByStatusAndNotCategory(I " WHERE i.status = :status AND v.active = true AND v.persistentId = :persistentId AND i.category <> :notCategory") Optional findActiveByPersistentIdAndStatusAndCategoryNot(String persistentId, ItemStatus status, ItemCategory notCategory); + + @Query("select i from Item i, VersionedItem v" + + " WHERE i.versionedItem = v AND v.active = true AND i.status=:status AND i.source.id = :sourceId AND (:sourceItemId is null OR i.sourceItemId = :sourceItemId)" + + " ORDER BY i.label, i.id") + Page getActiveItemsBySourceOrderByLabel(Long sourceId, String sourceItemId, ItemStatus status, Pageable pageable); + + @Query("select i from Item i, VersionedItem v" + + " WHERE i.versionedItem = v AND v.active = true AND i.source.id = :sourceId AND (:sourceItemId is null OR i.sourceItemId = :sourceItemId)" + + " ORDER BY i.label, i.id") + Page getActiveItemsBySourceOrderByLabel(Long sourceId, String sourceItemId, Pageable pageable); + + @Query("select i from Item i, VersionedItem v" + + " WHERE i.versionedItem = v AND v.active = true AND (i.status=:status OR i.informationContributor.id = :userId) AND i.source.id = :sourceId AND (:sourceItemId is null OR i.sourceItemId = :sourceItemId)" + + " ORDER BY i.label, i.id") + Page getActiveOrOwnedItemsBySourceOrderByLabel(Long sourceId, String sourceItemId, ItemStatus status, Long userId, Pageable pageable); } diff --git a/src/main/java/eu/sshopencloud/marketplace/services/items/ItemsService.java b/src/main/java/eu/sshopencloud/marketplace/services/items/ItemsService.java index ea6a60ab..092f9de4 100644 --- a/src/main/java/eu/sshopencloud/marketplace/services/items/ItemsService.java +++ b/src/main/java/eu/sshopencloud/marketplace/services/items/ItemsService.java @@ -19,6 +19,7 @@ import eu.sshopencloud.marketplace.repositories.sources.SourceRepository; import eu.sshopencloud.marketplace.services.auth.LoggedInUserHolder; import eu.sshopencloud.marketplace.services.search.SearchConverter; +import jakarta.persistence.EntityNotFoundException; import org.apache.commons.lang3.StringUtils; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.response.QueryResponse; @@ -27,7 +28,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import jakarta.persistence.EntityNotFoundException; import java.util.*; import java.util.stream.Collectors; @@ -112,17 +112,51 @@ private Sort.Order getSortOrderByItemOrder(ItemOrder itemOrder, boolean directPr } - public PaginatedSearchItemsBasic getItemsBySource(Long sourceId, PageCoords pageCoords) { - return getItemsBySource(sourceId, null, pageCoords); + public PaginatedSearchItemsBasic searchItemsBySource(Long sourceId, PageCoords pageCoords) { + return searchItemsBySource(sourceId, null, pageCoords); } - public PaginatedSearchItemsBasic getItemsBySource(Long sourceId, String sourceItemId, PageCoords pageCoords) { + public PaginatedSearchItemsBasic searchItemsBySource(Long sourceId, String sourceItemId, PageCoords pageCoords) { Source source = sourceRepository.findById(sourceId) .orElseThrow(() -> new EntityNotFoundException("Unable to find " + Source.class.getName() + " with id " + sourceId)); return findLatestItemsForSource(source, sourceItemId, pageCoords); } + public PaginatedItemsBasic getItemsBySource(Long sourceId, String sourceItemId, PageCoords pageCoords) { + Source source = sourceRepository.findById(sourceId) + .orElseThrow(() -> new EntityNotFoundException("Unable to find " + Source.class.getName() + " with id " + sourceId)); + + return getItemsBySourceOrderByLabel(source.getId(), sourceItemId, pageCoords); + } + + public PaginatedItemsBasic getItemsBySourceOrderByLabel(Long sourceId, String sourceItemId, PageCoords pageCoords) { + Pageable pageable = PageRequest.of(pageCoords.getPage() - 1, pageCoords.getPerpage()); // DB counts from page 0 + + Page itemsPage; + User currentUser = LoggedInUserHolder.getLoggedInUser(); + if (currentUser == null || !currentUser.isModerator()) { + if (currentUser == null || !currentUser.isContributor()) { + itemsPage = itemRepository.getActiveItemsBySourceOrderByLabel(sourceId, sourceItemId, ItemStatus.APPROVED, pageable); + } + else { + itemsPage = itemRepository.getActiveOrOwnedItemsBySourceOrderByLabel(sourceId, sourceItemId, ItemStatus.APPROVED, currentUser.getId(), pageable); + } + } + else { + itemsPage = itemRepository.getActiveItemsBySourceOrderByLabel(sourceId, sourceItemId, pageable); + } + + return PaginatedItemsBasic.builder() + .items(itemsPage.stream().map(ItemConverter::convertItem).collect(Collectors.toList())) + .hits(itemsPage.getTotalElements()) + .count(itemsPage.getSize()) + .page(pageCoords.getPage()) + .perpage(pageCoords.getPerpage()) + .pages((int) Math.ceil((double)itemsPage.getTotalElements() / (double)pageCoords.getPerpage())) + .build(); + } + public PaginatedSearchItemsBasic findLatestItemsForSource(Source source, String sourceItemId, PageCoords pageCoords) { Pageable pageable = PageRequest.of(pageCoords.getPage() - 1, pageCoords.getPerpage()); // SOLR counts from page 0 @@ -133,7 +167,7 @@ public PaginatedSearchItemsBasic findLatestItemsForSource(Source source, String queryBuilder.append(" AND source_item_id:"); queryBuilder.append("\"").append(sourceItemId).append("\""); } -// Criteria queryCriteria = new SimpleStringCriteria(queryBuilder.toString()); + SolrQuery solrQuery = new SolrQuery(queryBuilder.toString()); User currentUser = LoggedInUserHolder.getLoggedInUser(); diff --git a/src/test/java/eu/sshopencloud/marketplace/controllers/sources/SourceControllerITCase.java b/src/test/java/eu/sshopencloud/marketplace/controllers/sources/SourceControllerITCase.java index 19b1996c..960461bd 100644 --- a/src/test/java/eu/sshopencloud/marketplace/controllers/sources/SourceControllerITCase.java +++ b/src/test/java/eu/sshopencloud/marketplace/controllers/sources/SourceControllerITCase.java @@ -1,10 +1,18 @@ package eu.sshopencloud.marketplace.controllers.sources; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import eu.sshopencloud.marketplace.conf.TestJsonMapper; import eu.sshopencloud.marketplace.conf.auth.LogInTestClient; +import eu.sshopencloud.marketplace.dto.actors.ActorCore; +import eu.sshopencloud.marketplace.dto.actors.ActorId; +import eu.sshopencloud.marketplace.dto.actors.ActorRoleId; +import eu.sshopencloud.marketplace.dto.datasets.DatasetCore; +import eu.sshopencloud.marketplace.dto.datasets.DatasetDto; +import eu.sshopencloud.marketplace.dto.items.ItemContributorId; import eu.sshopencloud.marketplace.dto.sources.SourceCore; import eu.sshopencloud.marketplace.dto.sources.SourceDto; +import eu.sshopencloud.marketplace.dto.sources.SourceId; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.MethodOrderer; @@ -20,6 +28,10 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; @@ -281,4 +293,101 @@ public void shouldGetItemsBySourceIdAndSourceItemId() throws Exception { .andExpect(jsonPath("items[0].label", is("Webinar on DH"))); } + @Test + public void shouldUpdateActorRelatedToItemAngGetThisItem() throws Exception { + Long sourceId = 2L; + String sourceItemId = "rT8gg"; + Long actorIdToUpdate = 4L; + + // create datasets that relate to the actor we are going to update + ItemContributorId contributor = new ItemContributorId(new ActorId(4L), new ActorRoleId("contributor")); + + List datasetsPIDs = new ArrayList<>(); + + IntStream.range(1, 11).forEach(i -> { + DatasetCore dataset = new DatasetCore(); + dataset.setLabel("Test dataset with source and actor " + i); + dataset.setDescription("Lorem ipsum"); + SourceId source = new SourceId(); + source.setId(sourceId); + dataset.setSource(source); + dataset.setSourceItemId(sourceItemId); + dataset.setContributors(List.of(contributor)); + + String payload = null; + try { + payload = mapper.writeValueAsString(dataset); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + log.debug("JSON: " + payload); + + try { + mvc.perform(post("/api/datasets") + .content(payload) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", MODERATOR_JWT)) + .andExpect(status().isOk()) + .andExpect(jsonPath("status", is("approved"))) + .andExpect(jsonPath("category", is("dataset"))) + .andExpect(jsonPath("label", is("Test dataset with source and actor " + i))) + .andExpect(jsonPath("description", is("Lorem ipsum"))) + .andExpect(jsonPath("properties", hasSize(0))) + .andExpect(jsonPath("source.id", is(2))) + .andExpect(jsonPath("source.label", is("Programming Historian"))) + .andExpect(jsonPath("source.url", is("https://programminghistorian.org"))) + .andExpect(jsonPath("sourceItemId", is("rT8gg"))).andDo(h -> { + datasetsPIDs.add(mapper.readValue(h.getResponse().getContentAsString(), DatasetDto.class).getPersistentId()); + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + ActorCore actor = new ActorCore(); + actor.setName("Test actor"); + actor.setEmail("test@example.org"); + List affiliations = new ArrayList<>(); + ActorId affiliation1 = new ActorId(); + affiliation1.setId(1L); + affiliations.add(affiliation1); + actor.setAffiliations(affiliations); + + String payload = TestJsonMapper.serializingObjectMapper().writeValueAsString(actor); + log.debug("JSON: " + payload); + + mvc.perform(put("/api/actors/{id}", actorIdToUpdate) + .content(payload) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", MODERATOR_JWT)) + .andExpect(status().isOk()) + .andExpect(jsonPath("name", is("Test actor"))) + .andExpect(jsonPath("email", is("test@example.org"))) + .andExpect(jsonPath("affiliations", hasSize(1))) + .andExpect(jsonPath("affiliations[0].name", is("Austrian Academy of Sciences"))); + + mvc.perform(get("/api/sources/{sourceId}/items/{sourceItemId}", sourceId, sourceItemId) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", ADMINISTRATOR_JWT)) + .andExpect(jsonPath("items", hasSize(11))) + .andExpect(jsonPath("items[0].persistentId", is(datasetsPIDs.getFirst()))) + .andExpect(jsonPath("items[0].category", is("dataset"))) + .andExpect(jsonPath("items[0].label", is("Test dataset with source and actor 1"))); + + mvc.perform(get("/api/sources/{sourceId}/items/{sourceItemId}", sourceId, sourceItemId) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", CONTRIBUTOR_JWT)) + .andExpect(jsonPath("items", hasSize(11))) + .andExpect(jsonPath("items[0].persistentId", is(datasetsPIDs.getFirst()))) + .andExpect(jsonPath("items[0].category", is("dataset"))) + .andExpect(jsonPath("items[0].label", is("Test dataset with source and actor 1"))); + + mvc.perform(get("/api/sources/{sourceId}/items/{sourceItemId}", sourceId, sourceItemId) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("items", hasSize(11))) + .andExpect(jsonPath("items[0].persistentId", is(datasetsPIDs.getFirst()))) + .andExpect(jsonPath("items[0].category", is("dataset"))) + .andExpect(jsonPath("items[0].label", is("Test dataset with source and actor 1"))); + } + }