RED-10422 - improved number of requests with help of new changes endpoint

This commit is contained in:
Timo Bejan 2024-11-07 13:24:06 +02:00
parent 601475f1f8
commit 85b0b9fd43
27 changed files with 463 additions and 42 deletions

View File

@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@ -23,7 +24,11 @@ import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.
import com.iqser.red.service.persistence.management.v1.processor.model.websocket.DossierEventType;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierCreatorService;
import com.iqser.red.service.persistence.management.v1.processor.service.FilterByPermissionsService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierChangeResponseV2;
import com.iqser.red.service.persistence.service.v1.api.shared.model.JsonNode;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -76,6 +81,7 @@ public class DossierController implements DossierResource {
private final DossierManagementService dossierManagementService;
private final UserService userService;
private final FilterByPermissionsService filterByPermissionsService;
private final FileStatusManagementService fileStatusManagementService;
private final AuditPersistenceService auditPersistenceService;
private final NotificationPersistenceService notificationPersistenceService;
@ -106,6 +112,20 @@ public class DossierController implements DossierResource {
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
public DossierChangeResponseV2 changesSinceV2(@RequestBody JSONPrimitive<OffsetDateTime> since) {
DossierChangeResponseV2 changes = dossierManagementService.changesSinceV2(since);
// filter only viewables
changes.setFileChanges(filterByPermissionsService.onlyViewableHavingDossierId(changes.getFileChanges()));
changes.setDossierChanges(filterByPermissionsService.onlyViewableHavingDossierId(changes.getDossierChanges()));
return changes;
}
@Override
@PreAuthorize("hasAuthority('" + ADD_UPDATE_DOSSIER + "') && (#dossierRequest.dossierId == null || hasPermission(#dossierRequest.dossierId, 'Dossier', 'ACCESS_OBJECT') )")
public ResponseEntity<Dossier> createDossierOrUpdateDossier(@RequestBody DossierRequest dossierRequest) {
@ -405,6 +425,28 @@ public class DossierController implements DossierResource {
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
public JSONPrimitive<Map<String, Dossier>> getDossiersByIds(@RequestBody JSONPrimitive<Set<String>> dossierIds) {
// filter dossiers based on view
var viewableDossierIds = filterByPermissionsService.onlyViewableDossierIds(dossierIds.getValue());
// load dossiers
var dossiers = dossierManagementService.getDossiersByIds(viewableDossierIds);
// add attributes and ACL - already filtered before loading
enhanceDossiersWithAttributeAndACLData(dossiers,false);
// build response
var responseMap = new LinkedHashMap<String, Dossier>();
for (var dossier : dossiers) {
responseMap.put(dossier.getId(), dossier);
}
return new JSONPrimitive<>(responseMap);
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
public Dossier getDossier(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@ -418,70 +460,45 @@ public class DossierController implements DossierResource {
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getDossiers(@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_DELETED_PARAM, defaultValue = "false", required = false) boolean includeDeleted) {
var dossiers = dossierManagementService.getAllDossiers(includeArchived, includeDeleted)
.stream()
.map(dossierACLService::enhanceDossierWithACLData)
.collect(Collectors.toList());
dossiers.forEach(dossier -> dossier.setDossierAttributes(convertDossierAttributes(dossierAttributePersistenceService.getDossierAttributes(dossier.getId()))));
return dossiers;
var dossiers = dossierManagementService.getAllDossiers(includeArchived, includeDeleted);
return enhanceDossiersWithAttributeAndACLData(dossiers);
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getDossiersForDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_DELETED_PARAM, defaultValue = "false", required = false) boolean includeDeleted) {
var dossiers = dossierManagementService.getAllDossiersForDossierTemplateId(dossierTemplateId, includeArchived, includeDeleted)
.stream()
.map(dossierACLService::enhanceDossierWithACLData)
.collect(Collectors.toList());
dossiers.forEach(dossier -> dossier.setDossierAttributes(convertDossierAttributes(dossierAttributePersistenceService.getDossierAttributes(dossier.getId()))));
return dossiers;
var dossiers = dossierManagementService.getAllDossiersForDossierTemplateId(dossierTemplateId, includeArchived, includeDeleted);
return enhanceDossiersWithAttributeAndACLData(dossiers);
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getSoftDeletedDossiers() {
var dossiers = dossierManagementService.getSoftDeletedDossiers()
.stream()
.map(dossierACLService::enhanceDossierWithACLData)
.collect(Collectors.toList());
dossiers.forEach(dossier -> dossier.setDossierAttributes(convertDossierAttributes(dossierAttributePersistenceService.getDossierAttributes(dossier.getId()))));
return dossiers;
var dossiers = dossierManagementService.getSoftDeletedDossiers();
return enhanceDossiersWithAttributeAndACLData(dossiers);
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getArchivedDossiers() {
var dossiers = dossierManagementService.getArchivedDossiers()
.stream()
.map(dossierACLService::enhanceDossierWithACLData)
.collect(Collectors.toList());
dossiers.forEach(dossier -> dossier.setDossierAttributes(convertDossierAttributes(dossierAttributePersistenceService.getDossierAttributes(dossier.getId()))));
return dossiers;
var dossiers = dossierManagementService.getArchivedDossiers();
return enhanceDossiersWithAttributeAndACLData(dossiers);
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getArchivedDossiersForDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
var dossiers = dossierManagementService.getArchivedDossiersForDossierTemplateId(dossierTemplateId)
.stream()
.map(dossierACLService::enhanceDossierWithACLData)
.collect(Collectors.toList());
dossiers.forEach(dossier -> dossier.setDossierAttributes(convertDossierAttributes(dossierAttributePersistenceService.getDossierAttributes(dossier.getId()))));
return dossiers;
var dossiers = dossierManagementService.getArchivedDossiersForDossierTemplateId(dossierTemplateId);
return enhanceDossiersWithAttributeAndACLData(dossiers);
}
@ -586,5 +603,31 @@ public class DossierController implements DossierResource {
return new DossierAttributes(attributeIdToValue);
}
private List<Dossier> enhanceDossiersWithAttributeAndACLData(List<Dossier> dossiers) {
return enhanceDossiersWithAttributeAndACLData(dossiers, true);
}
private List<Dossier> enhanceDossiersWithAttributeAndACLData(List<Dossier> dossiers, boolean filter) {
// filter first, only load attributes and ACL for viewable dossiers
List<Dossier> filteredDossiers = filter ? filterByPermissionsService.onlyViewableDossiers(dossiers) : dossiers;
// load all attributes at once
var attributes = dossierAttributePersistenceService.getDossierAttributes(filteredDossiers.stream().map(Dossier::getId).collect(Collectors.toSet()));
var attributesMap = new HashMap<String, List<DossierAttributeEntity>>();
for (DossierAttributeEntity attribute : attributes) {
attributesMap.computeIfAbsent(attribute.getId().getDossierId(), k -> new ArrayList<>()).add(attribute);
}
for (var dossier : filteredDossiers) {
// set attributes
dossier.setDossierAttributes(convertDossierAttributes(attributesMap.getOrDefault(dossier.getId(), new ArrayList<>())));
// set ACL data
dossierACLService.enhanceDossierWithACLData(dossier);
}
return filteredDossiers;
}
}

View File

@ -14,11 +14,16 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
@ -30,6 +35,7 @@ import com.iqser.red.service.persistence.management.v1.processor.service.Approva
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusMapper;
import com.iqser.red.service.persistence.management.v1.processor.service.FilterByPermissionsService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService;
@ -39,6 +45,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AddNotificationRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.ProcessingStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;
@ -46,6 +53,9 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.notificatio
import com.iqser.red.service.persistence.service.v1.api.shared.model.warning.ApproveResponse;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -67,6 +77,7 @@ public class StatusController implements StatusResource {
private final NotificationPersistenceService notificationPersistenceService;
private final DossierACLService dossierACLService;
private final ApprovalVerificationService approvalVerificationService;
private final FilterByPermissionsService filterByPermissionsService;
@Override
@ -82,6 +93,23 @@ public class StatusController implements StatusResource {
}
@Override
@PreAuthorize("hasAuthority('" + READ_FILE_STATUS + "')")
public JSONPrimitive<Map<String, List<FileStatus>>> getFilesByIds(@RequestBody JSONPrimitive<Map<String, Set<String>>> filesByDossier) {
// filter dossiers by view
var accessibleDossierIds = filterByPermissionsService.onlyViewableDossierIds(new ArrayList<>(filesByDossier.getValue().keySet()));
var response = new HashMap<String, List<FileStatus>>();
for (var dossierId : accessibleDossierIds) {
var allFoundFiles = fileStatusManagementService.findAllDossierIdAndIds(dossierId, filesByDossier.getValue().get(dossierId));
response.put(dossierId, allFoundFiles.stream().map(FileStatusMapper::toFileStatus).collect(Collectors.toList()));
}
return new JSONPrimitive<>(response);
}
@Override
@PreAuthorize("hasAuthority('" + READ_FILE_STATUS + "')")
public Map<String, List<FileStatus>> getDossierStatus(@RequestBody List<String> dossierIds) {

View File

@ -2,6 +2,7 @@ package com.iqser.red.service.persistence.service.v1.api.external.resource;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.http.HttpStatus;
@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierChangeEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierChangeResponseV2;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierInformation;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
@ -29,10 +31,12 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
public interface DossierResource {
String DOSSIER_REST_PATH = ExternalApi.BASE_PATH + "/dossier";
String BY_ID_PATH = "/by-id";
String DOSSIER_TEMPLATE_PATH = "/dossier-template";
String DOSSIER_INFO_PATH = "/info";
String DELETED_DOSSIERS_PATH = ExternalApi.BASE_PATH + "/deleted-dossiers";
String CHANGES_DETAILS_PATH = "/changes/details";
String CHANGES_DETAILS_V2_PATH = "/changes/details/v2";
String HARD_DELETE_PATH = "/hard-delete";
String UNDELETE_PATH = "/restore";
@ -62,6 +66,12 @@ public interface DossierResource {
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Success")})
List<DossierChangeEntry> changesSince(@RequestBody JSONPrimitive<OffsetDateTime> since);
@ResponseBody
@PostMapping(value = DOSSIER_REST_PATH + CHANGES_DETAILS_V2_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "See if there are changes to dossiers since param", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Success")})
DossierChangeResponseV2 changesSinceV2(@RequestBody JSONPrimitive<OffsetDateTime> since);
@ResponseBody
@PostMapping(value = DOSSIER_REST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ -95,6 +105,13 @@ public interface DossierResource {
List<Dossier> getDossiers(@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_DELETED_PARAM, defaultValue = "false", required = false) boolean includeDeleted);
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@PostMapping(value = DOSSIER_REST_PATH+BY_ID_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets dossiers by ids.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
JSONPrimitive<Map<String,Dossier>> getDossiersByIds(@RequestBody JSONPrimitive<Set<String>> dossierIds);
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody

View File

@ -3,6 +3,7 @@ package com.iqser.red.service.persistence.service.v1.api.external.resource;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.warning.ApproveResponse;
import io.swagger.v3.oas.annotations.Operation;
@ -25,6 +27,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
public interface StatusResource {
String STATUS_REST_PATH = ExternalApi.BASE_PATH + "/status";
String BY_ID_PATH = "/by-id";
String CHANGES_SINCE_PATH = "/changes";
String BULK_REST_PATH = "/bulk";
String ASSIGNEE_REST_PATH = "/set-assignee";
@ -56,6 +59,14 @@ public interface StatusResource {
Map<String, List<FileStatus>> getDossierStatus(@RequestBody List<String> dossierIds);
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@PostMapping(value = STATUS_REST_PATH + BY_ID_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the status for files by dossierId and fileIds.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
JSONPrimitive<Map<String, List<FileStatus>>> getFilesByIds(@RequestBody JSONPrimitive<Map<String, Set<String>>> filesByDossier);
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@PostMapping(value = STATUS_REST_PATH + DELETED_PATH, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)

View File

@ -5,6 +5,7 @@ import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierChangeEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.IHavingDossierId;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import lombok.extern.slf4j.Slf4j;
@ -18,8 +19,8 @@ public class RedObjectIdentityRetrievalStrategy implements ObjectIdentityRetriev
log.debug("Requesting data for object: {}", domainObject);
if (domainObject instanceof Dossier) {
return new ObjectIdentityImpl("Dossier", ((Dossier) domainObject).getId());
} else if (domainObject instanceof DossierChangeEntry) {
return new ObjectIdentityImpl("Dossier", ((DossierChangeEntry) domainObject).getDossierId());
} else if (domainObject instanceof IHavingDossierId) {
return new ObjectIdentityImpl("Dossier", ((IHavingDossierId) domainObject).getDossierId());
} else if (domainObject instanceof String) {
// TODO ACL this will not work once we have more than one type.
return new ObjectIdentityImpl("Dossier", (String) domainObject);

View File

@ -0,0 +1,6 @@
package com.iqser.red.service.persistence.management.v1.processor.model;
public interface DossierIdFilterable {
String getDossierId();
}

View File

@ -3,6 +3,7 @@ package com.iqser.red.service.persistence.management.v1.processor.service;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.DossierNotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.utils.DossierMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierChangeResponseV2;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierInformation;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierRequest;
@ -275,4 +276,16 @@ public class DossierManagementService {
}
public DossierChangeResponseV2 changesSinceV2(JSONPrimitive<OffsetDateTime> since) {
return dossierService.changesSinceV2(since.getValue());
}
@Transactional
public List<Dossier> getDossiersByIds(Set<String> viewableDossierIds) {
return getConvertedAllDossiers(dossierService.getAllDossiers(viewableDossierIds), true,true);
}
}

View File

@ -1,6 +1,7 @@
package com.iqser.red.service.persistence.management.v1.processor.service;
import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@ -14,6 +15,7 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.TypeIdUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierChangeResponseV2;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierChange;
@ -147,7 +149,7 @@ public class DossierService {
}
public List<DossierEntity> getAllDossiers(List<String> dossierIds) {
public List<DossierEntity> getAllDossiers(Collection<String> dossierIds) {
return dossierPersistenceService.findAllDossiers(dossierIds);
}
@ -188,5 +190,8 @@ public class DossierService {
}
public DossierChangeResponseV2 changesSinceV2(OffsetDateTime value) {
return dossierPersistenceService.hasChangesSinceV2(value);
}
}

View File

@ -52,6 +52,11 @@ public class FileStatusManagementService {
.collect(Collectors.toList());
}
public List<FileModel> findAllDossierIdAndIds(String dossierId, Set<String> fileIds) {
return fileStatusService.findAllDossierIdAndIds(dossierId,fileIds);
}
public List<String> getDossierStatusIds(String dossierId, boolean includeDeleted) {

View File

@ -11,6 +11,7 @@ import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@ -1081,4 +1082,16 @@ public class FileStatusService {
return fileStatusPersistenceService.findAllByDossierId(dossierId, includeDeleted);
}
@Transactional
public List<FileModel> findAllDossierIdAndIds(String dossierId, Set<String> fileIds) {
var fileModels = fileStatusPersistenceService.findAllDossierIdAndIds(dossierId, fileIds)
.stream()
.map(entity -> MagicConverter.convert(entity, FileModel.class, new FileModelMapper()))
.toList();
return reanalysisRequiredStatusService.enhanceFileStatusWithAnalysisRequirements(fileModels);
}
}

View File

@ -7,6 +7,7 @@ import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.shared.model.IHavingDossierId;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
/*
@ -17,6 +18,20 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
@Service
public class FilterByPermissionsService {
@PreFilter("hasPermission(filterObject.getDossierId(), 'Dossier', 'VIEW_OBJECT')")
public <T extends IHavingDossierId> List<T> onlyViewableHavingDossierId(List<T> items) {
return items;
}
@PreFilter("hasPermission(filterObject, 'Dossier', 'VIEW_OBJECT')")
public Set<String> onlyViewableDossierIds(Set<String> dossierIds) {
return dossierIds;
}
@PreFilter("hasPermission(filterObject, 'Dossier', 'VIEW_OBJECT')")
public List<String> onlyViewableDossierIds(List<String> dossierIds) {

View File

@ -1,5 +1,6 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence;
import java.util.Collection;
import java.util.List;
import jakarta.transaction.Transactional;
@ -58,6 +59,11 @@ public class DossierAttributePersistenceService {
return dossierAttributeRepository.findByIdDossierId(dossierId);
}
public List<DossierAttributeEntity> getDossierAttributes(Collection<String> dossierIds) {
return dossierAttributeRepository.findByDossierIds(dossierIds);
}
public DossierAttributeEntity findOne(String dossierId, String dossierAttributeId) {

View File

@ -24,6 +24,9 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierTemplateRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FileRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ReportTemplateRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierChangeEntryV2;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierChangeResponseV2;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileChangeEntryV2;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierChange;
@ -180,7 +183,7 @@ public class DossierPersistenceService {
}
public List<DossierEntity> findAllDossiers(List<String> dossierIds) {
public List<DossierEntity> findAllDossiers(Collection<String> dossierIds) {
if (!dossierIds.isEmpty()) {
return dossierRepository.findAllById(dossierIds);
@ -233,6 +236,19 @@ public class DossierPersistenceService {
}
public DossierChangeResponseV2 hasChangesSinceV2(OffsetDateTime since) {
var changedDossiers = dossierRepository.findDossierChangeProjectionByLastUpdatedIsAfter(since.truncatedTo(ChronoUnit.MILLIS));
var changedFiles = fileRepository.findFileChangeProjectionByLastUpdatedIsAfter(since.truncatedTo(ChronoUnit.MILLIS));
var response = new DossierChangeResponseV2();
response.setDossierChanges(changedDossiers.stream().map(d -> new DossierChangeEntryV2(d.getId(), d.getLastUpdated())).collect(Collectors.toList()));
response.setFileChanges(changedFiles.stream().map(f -> new FileChangeEntryV2(f.getId(), f.getDossierId(), f.getLastUpdated())).collect(Collectors.toList()));
return response;
}
public Set<DossierChange> hasChangesSince(OffsetDateTime since) {
var dossiersWithChanges = dossierRepository.findDossierChangeByLastUpdatedIsAfter(since.truncatedTo(ChronoUnit.MILLIS));

View File

@ -3,6 +3,7 @@ package com.iqser.red.service.persistence.management.v1.processor.service.persis
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -713,6 +714,11 @@ public class FileStatusPersistenceService {
return fileRepository.findAllById(fileIds);
}
public List<FileEntity> findAllDossierIdAndIds(String dossierId, Set<String> fileIds) {
return fileRepository.findAllDossierIdAndIds(dossierId, fileIds);
}
public List<String> findAllByDossierId(String dossierId, boolean includeDeleted) {

View File

@ -0,0 +1,9 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection;
import java.time.OffsetDateTime;
public interface DossierChangeProjection {
String getId();
OffsetDateTime getLastUpdated();
}

View File

@ -0,0 +1,10 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection;
import java.time.OffsetDateTime;
public interface FileChangeProjection {
String getId();
String getDossierId();
OffsetDateTime getLastUpdated();
}

View File

@ -1,5 +1,6 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository;
import java.util.Collection;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
@ -13,6 +14,8 @@ public interface DossierAttributeRepository extends JpaRepository<DossierAttribu
List<DossierAttributeEntity> findByIdDossierId(String dossierId);
@Query("SELECT e FROM DossierAttributeEntity e WHERE e.id.dossierId IN :dossierIds")
List<DossierAttributeEntity> findByDossierIds(@Param("dossierIds") Collection<String> dossierIds);
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("DELETE FROM DossierAttributeEntity e WHERE e.id.dossierId = :dossierId")

View File

@ -11,10 +11,14 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection.DossierChangeProjection;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection.DossierIdAndStatusProjection;
public interface DossierRepository extends JpaRepository<DossierEntity, String> {
@Query("select d from DossierEntity d where d.lastUpdated > :since")
List<DossierChangeProjection> findDossierChangeProjectionByLastUpdatedIsAfter(@Param("since") OffsetDateTime since);
@Query("select d.id from DossierEntity d where d.lastUpdated > :since")
List<String> findDossierChangeByLastUpdatedIsAfter(@Param("since") OffsetDateTime since);

View File

@ -3,6 +3,7 @@ package com.iqser.red.service.persistence.management.v1.processor.service.persis
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
@ -12,6 +13,8 @@ import org.springframework.data.repository.query.Param;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.projection.DossierStatsFileProjection;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection.DossierChangeProjection;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection.FileChangeProjection;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection.FilePageCountsProjection;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection.FileProcessingStatusProjection;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.projection.FileWorkflowStatusProjection;
@ -279,6 +282,10 @@ public interface FileRepository extends JpaRepository<FileEntity, String> {
int countSoftDeletedFilesPerDossierId(@Param("dossierId") String dossierId);
@Query("select f from FileEntity f where f.lastUpdated > :since")
List<FileChangeProjection> findFileChangeProjectionByLastUpdatedIsAfter(@Param("since") OffsetDateTime since);
@Query("select distinct f.dossierId from FileEntity f where f.lastUpdated > :since")
List<String> findDossierChangeByLastUpdatedIsAfter(@Param("since") OffsetDateTime since);
@ -449,6 +456,10 @@ public interface FileRepository extends JpaRepository<FileEntity, String> {
@Query("SELECT f.id FROM FileEntity f WHERE f.dossierId = :dossierId AND f.hardDeletedTime IS NULL AND (f.deleted IS NULL or (f.deleted is not null and :includeDeleted = true))")
List<String> findAllByDossierId(@Param("dossierId") String dossierId, @Param("includeDeleted") boolean includeDeleted);
@Query("SELECT f FROM FileEntity f WHERE f.id in :fileIds AND f.dossierId = :dossierId")
List<FileEntity> findAllDossierIdAndIds(@Param("dossierId") String dossierId, @Param("fileIds") Set<String> fileIds);
}

View File

@ -0,0 +1,138 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.springframework.beans.factory.annotation.Autowired;
import com.iqser.red.service.peristence.v1.server.integration.client.CustomPermissionClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileClient;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.FileTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.UserProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.peristence.v1.server.integration.utils.TokenService;
import com.iqser.red.service.persistence.management.v1.processor.acl.RedPermission;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierChangeEntryV2;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileChangeEntryV2;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.permission.CustomPermissionMappingModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.permission.CustomPermissionModel;
import org.junit.jupiter.api.Test;
public class DossierStatsAndGetByIdsTest extends AbstractPersistenceServerServiceTest {
@Autowired
private DossierTemplateTesterAndProvider dossierTemplateTesterAndProvider;
@Autowired
private FileTesterAndProvider fileTesterAndProvider;
@Autowired
private DossierTesterAndProvider dossierTesterAndProvider;
@Autowired
private CustomPermissionClient customPermissionClient;
@Autowired
private TokenService tokenService;
@Autowired
private UserProvider userProvider;
@Autowired
private DossierClient dossierClient;
@Autowired
private FileClient fileClient;
@Test
public void testDossierChangesGetByIdsAndViewPermissions() {
var start = OffsetDateTime.now();
var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
dossierTemplateTesterAndProvider.provideDefaultColors(dossierTemplate.getId());
IntStream.range(0, 5)
.forEach(x -> dossierTesterAndProvider.provideTestDossier(dossierTemplate, "test dossier: " + x));
var allDossiers = dossierClient.getDossiers(true, true);
var dossiersByIds = dossierClient.getDossiersByIds(new JSONPrimitive<>(allDossiers.stream().map(Dossier::getId).collect(Collectors.toSet())));
assertThat(allDossiers).hasSameElementsAs(dossiersByIds.getValue().values());
// test changes for owner
var changes = dossierClient.changesSinceV2(new JSONPrimitive<>(start));
assertThat(changes.getDossierChanges().stream().map(DossierChangeEntryV2::getDossierId)).hasSameElementsAs(allDossiers.stream()
.map(Dossier::getId)
.collect(Collectors.toSet()));
// test changes for owner with new timestamp
var nextChanges = dossierClient.changesSinceV2(new JSONPrimitive<>(OffsetDateTime.now()));
assertThat(nextChanges.getDossierChanges()).isEmpty();
var before = OffsetDateTime.now();
var testFile = fileTesterAndProvider.testAndProvideFile(allDossiers.iterator().next(), "testFile");
nextChanges = dossierClient.changesSinceV2(new JSONPrimitive<>(before));
assertThat(nextChanges.getDossierChanges()).isEmpty();
assertThat(nextChanges.getFileChanges().stream().map(FileChangeEntryV2::getFileId)).containsExactly(testFile.getId());
var filesToLoad = new HashMap<String, Set<String>>();
nextChanges.getFileChanges().forEach(fc -> {
filesToLoad.put(fc.getDossierId(), Set.of(fc.getFileId()));
});
var loadedFiles = fileClient.getFilesByIds(new JSONPrimitive<>(filesToLoad));
assertThat(loadedFiles.getValue().values().stream().flatMap(Collection::stream).collect(Collectors.toSet())).containsExactly(testFile);
// test get byId for other user
tokenService.setUser(userProvider.getAltUserId(), "secret");
dossiersByIds = dossierClient.getDossiersByIds(new JSONPrimitive<>(allDossiers.stream().map(Dossier::getId).collect(Collectors.toSet())));
assertThat(allDossiers).hasSameElementsAs(dossiersByIds.getValue().values());
// test changes for other user
var changesUser2 = dossierClient.changesSinceV2(new JSONPrimitive<>(start));
assertThat(changesUser2.getDossierChanges().stream().map(DossierChangeEntryV2::getDossierId)).hasSameElementsAs(allDossiers.stream()
.map(Dossier::getId)
.collect(Collectors.toSet()));
// test changes for other user with new timestamp
var nextChangesUser2 = dossierClient.changesSinceV2(new JSONPrimitive<>(OffsetDateTime.now()));
assertThat(nextChangesUser2.getDossierChanges()).isEmpty();
setPermissionsNobodyExceptOwnerCanView();
dossiersByIds = dossierClient.getDossiersByIds(new JSONPrimitive<>(allDossiers.stream().map(Dossier::getId).collect(Collectors.toSet())));
assertThat(dossiersByIds.getValue().values()).isEmpty();
// no visible changes either after permissions removed
changesUser2 = dossierClient.changesSinceV2(new JSONPrimitive<>(start));
assertThat(changesUser2.getDossierChanges()).isEmpty();
}
private void setPermissionsNobodyExceptOwnerCanView() {
CustomPermissionModel targetViewPermission = new CustomPermissionModel(RedPermission.VIEW_OBJECT.getMask(),
RedPermission.VIEW_OBJECT.getName(),
RedPermission.VIEW_OBJECT.getSort(),
false);
List<CustomPermissionModel> mappedViewPermissions = new ArrayList<>();
List<CustomPermissionMappingModel> customPermissionMappingModels = new ArrayList<>();
customPermissionMappingModels.add(new CustomPermissionMappingModel(targetViewPermission, mappedViewPermissions));
customPermissionClient.saveCustomPermissionMappings("Dossier", customPermissionMappingModels);
customPermissionClient.syncAllCustomPermissions();
}
}

View File

@ -645,7 +645,10 @@ public abstract class AbstractPersistenceServerServiceTest {
public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
var allRoles = getAllRoles();
UserDetails user = User.withUsername("manageradmin1@test.com").password(passwordEncoder.encode("secret")).roles(allRoles).authorities(getAllRoles()).build();
UserDetails user1 = User.withUsername("manageradmin1@test.com")
.password(passwordEncoder.encode("secret")).roles(allRoles).authorities(getAllRoles()).build();
UserDetails user2 = User.withUsername("manageradmin2@test.com")
.password(passwordEncoder.encode("secret")).roles(allRoles).authorities(getAllRoles()).build();
var allRolesWithoutRedUserOrRedManager = Arrays.stream(allRoles)
.filter(s -> !(s.equalsIgnoreCase(ApplicationRoles.RED_USER_ROLE) || s.equalsIgnoreCase(ApplicationRoles.RED_MANAGER_ROLE)))
.collect(Collectors.toList());
@ -654,7 +657,7 @@ public abstract class AbstractPersistenceServerServiceTest {
.roles(allRolesWithoutRedUserOrRedManager.toArray(new String[0]))
.authorities(getAllRoles())
.build();
return new InMemoryUserDetailsManager(user, userWithoutRedUserOrRedManager);
return new InMemoryUserDetailsManager(user1,user2, userWithoutRedUserOrRedManager);
}

View File

@ -5,7 +5,7 @@ import lombok.Data;
@Data
@AllArgsConstructor
public class DossierChangeEntry {
public class DossierChangeEntry implements IHavingDossierId {
private String dossierId;
private boolean dossierChanges;

View File

@ -0,0 +1,15 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model;
import java.time.OffsetDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class DossierChangeEntryV2 implements IHavingDossierId {
private String dossierId;
private OffsetDateTime lastUpdated;
}

View File

@ -0,0 +1,18 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DossierChangeResponseV2 {
private List<DossierChangeEntryV2> dossierChanges = new ArrayList<>();
private List<FileChangeEntryV2> fileChanges = new ArrayList<>();
}

View File

@ -0,0 +1,18 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model;
import java.time.OffsetDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FileChangeEntryV2 implements IHavingDossierId {
private String fileId;
private String dossierId;
private OffsetDateTime lastUpdated;
}

View File

@ -0,0 +1,6 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model;
public interface IHavingDossierId {
String getDossierId();
}

View File

@ -35,6 +35,7 @@ public class Dossier {
private OffsetDateTime startDate;
private OffsetDateTime dueDate;
private OffsetDateTime archivedTime;
private OffsetDateTime lastUpdated;
private String dossierTemplateId;
private String dossierStatusId;
private DossierVisibility visibility;