Merge branch 'RED-7416-apidoc' into 'master'

OpenAPI Specification for DocuMine

Closes RED-7416

See merge request redactmanager/persistence-service!142
This commit is contained in:
Kilian Schüttler 2023-09-26 17:52:16 +02:00
commit 6f8e319abf
14 changed files with 2790 additions and 2569 deletions

View File

@ -290,7 +290,7 @@ public class DossierTemplateController implements DossierTemplateResource {
private DossierTemplateModel convert(DossierTemplate dossierTemplate) {
return DossierTemplateModel.builder()
.dossierTemplateId(dossierTemplate.getId())
.id(dossierTemplate.getId())
.name(dossierTemplate.getName())
.description(dossierTemplate.getDescription())
.dateAdded(dossierTemplate.getDateAdded())
@ -300,7 +300,7 @@ public class DossierTemplateController implements DossierTemplateResource {
.validFrom(dossierTemplate.getValidFrom())
.validTo(dossierTemplate.getValidTo())
.downloadFileTypes(dossierTemplate.getDownloadFileTypes())
.dossierTemplateStatus(DossierTemplateStatus.valueOf(dossierTemplate.getDossierTemplateStatus().name()))
.status(DossierTemplateStatus.valueOf(dossierTemplate.getDossierTemplateStatus().name()))
.keepImageMetadata(dossierTemplate.isKeepImageMetadata())
.keepHiddenText(dossierTemplate.isKeepHiddenText())
.keepOverlappingObjects(dossierTemplate.isKeepOverlappingObjects())

View File

@ -0,0 +1,257 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.GET_RSS;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentOverrideService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.ComponentLogResource;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RSSResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntityReference;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntryValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentsOverrides;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.rss.RSSFileResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.rss.RSSResponse;
import com.iqser.red.service.redaction.report.v1.api.model.rss.DetailedRSSFileResponse;
import com.iqser.red.service.redaction.report.v1.api.model.rss.DetailedRSSResponse;
import com.iqser.red.service.redaction.report.v1.api.model.rss.SCMComponent;
import com.iqser.red.service.redaction.report.v1.api.model.rss.ScmAnnotation;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.RequiredArgsConstructor;
@Deprecated(forRemoval = true)
@RestController
@RequiredArgsConstructor
@ConditionalOnProperty(name = "application.rss.component-log.enabled", havingValue = "true")
public class RSSComponentLogController implements RSSResource {
private final ComponentOverrideService componentOverrideService;
private final AuditPersistenceService auditPersistenceService;
private final ComponentLogService componentLogService;
private final StatusController statusController;
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public RSSResponse getRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId) {
List<FileStatus> dossierFiles;
if (StringUtils.isBlank(fileId)) {
dossierFiles = statusController.getDossierStatus(dossierId);
} else {
dossierFiles = List.of(statusController.getFileStatus(dossierId, fileId));
}
List<RSSFileResponse> fileResponses = dossierFiles.stream().map(this::getRssResponse).toList();
return new RSSResponse(fileResponses);
}
private RSSFileResponse getRssResponse(FileStatus file) {
var componentLog = componentLogService.getComponentLog(file.getDossierId(), file.getId(), true);
Map<String, String> results = new HashMap<>();
for (var entry : componentLog.getComponentLogEntries()) {
if (entry.getComponentValues().size() <= 1) {
results.put(entry.getName(), entry.getComponentValues().get(0).getValue());
continue;
}
List<ComponentLogEntryValue> componentValues = entry.getComponentValues();
for (int i = 0, componentValuesSize = componentValues.size(); i < componentValuesSize; i++) {
ComponentLogEntryValue v = componentValues.get(i);
results.put(entry.getName() + "_" + (i + 1), v.getValue());
}
}
return RSSFileResponse.builder().filename(file.getFilename()).result(results).build();
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public DetailedRSSResponse getDetailedRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId) {
List<FileStatus> dossierFiles;
if (StringUtils.isBlank(fileId)) {
dossierFiles = statusController.getDossierStatus(dossierId);
} else {
dossierFiles = List.of(statusController.getFileStatus(dossierId, fileId));
}
List<DetailedRSSFileResponse> fileResponses = dossierFiles.stream().map(this::getDetailedRssResponse).toList();
return new DetailedRSSResponse(fileResponses);
}
private DetailedRSSFileResponse getDetailedRssResponse(FileStatus file) {
var componentLog = componentLogService.getComponentLog(file.getDossierId(), file.getId(), true);
Map<String, SCMComponent> results = new HashMap<>();
for (var entry : componentLog.getComponentLogEntries()) {
if (entry.getComponentValues().size() <= 1) {
results.put(entry.getName(), toSCMComponent(entry.getComponentValues().get(0)));
continue;
}
List<ComponentLogEntryValue> componentValues = entry.getComponentValues();
for (int i = 0, componentValuesSize = componentValues.size(); i < componentValuesSize; i++) {
ComponentLogEntryValue v = componentValues.get(i);
results.put(entry.getName() + "_" + (i + 1), toSCMComponent(v));
}
}
return DetailedRSSFileResponse.builder().filename(file.getFilename()).result(results).build();
}
private SCMComponent toSCMComponent(ComponentLogEntryValue v) {
return SCMComponent.builder()
.value(v.getValue())
.originalValue(v.getOriginalValue())
.transformation(v.getValueDescription())
.scmAnnotations(v.getComponentLogEntityReferences().stream().map(this::toScmAnnotation).toList())
.build();
}
private ScmAnnotation toScmAnnotation(ComponentLogEntityReference er) {
return ScmAnnotation.builder().type(er.getType()).pages(Set.of(er.getPage())).ruleIdentifier(er.getEntityRuleId()).reason("").build();
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void addOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ComponentsOverrides componentsOverrides) {
var componentLog = componentLogService.getComponentLog(dossierId, fileId);
var allComponents = componentLog.getComponentLogEntries();
componentOverrideService.addOverrides(dossierId, fileId, componentsOverrides);
componentsOverrides.getComponentOverrides().forEach((componentName, overrideValue) -> auditOverride(dossierId, fileId, componentName, overrideValue, allComponents));
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public ComponentsOverrides getOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
return componentOverrideService.getOverrides(dossierId, fileId);
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void revertOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RevertOverrideRequest revertOverrideRequest) {
var componentLog = componentLogService.getComponentLog(dossierId, fileId);
var allComponents = componentLog.getComponentLogEntries();
componentOverrideService.revertOverrides(dossierId, fileId, revertOverrideRequest);
revertOverrideRequest.getComponents().forEach(componentNameToRevert -> auditOverrideRevert(dossierId, fileId, componentNameToRevert, allComponents));
}
private void auditOverride(String dossierId, String fileId, String componentName, String overrideValue, List<ComponentLogEntry> allComponentLogEntries) {
Optional<ComponentLogEntry> component = allComponentLogEntries.stream().filter(c -> c.getName().equals(componentName)).findFirst();
String originalValue = getOriginalValue(component);
String value = getValue(component);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component is overwritten with value")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
"ComponentName",
componentName,
"Action",
"MODIFY",
"OriginalValue",
originalValue,
"OldValue",
value,
"NewValue",
overrideValue))
.build());
}
private void auditOverrideRevert(String dossierId, String fileId, String componentNameToRevert, List<ComponentLogEntry> allComponentLogEntries) {
Optional<ComponentLogEntry> component = allComponentLogEntries.stream().filter(c -> c.getName().equals(componentNameToRevert)).findFirst();
String originalValue = getOriginalValue(component);
String value = getValue(component);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component override for was reverted")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
"ComponentName",
componentNameToRevert,
"Action",
"REVERT",
"OriginalValue",
originalValue,
"OldValue",
value,
"NewValue",
originalValue))
.build());
}
private String getValue(Optional<ComponentLogEntry> component) {
return component.map(ComponentLogEntry::getComponentValues)
.stream()
.map(a -> a.stream().map(ComponentLogEntryValue::getValue).collect(Collectors.joining(", ")))
.findFirst()
.orElse("");
}
private static String getOriginalValue(Optional<ComponentLogEntry> component) {
return component.map(ComponentLogEntry::getComponentValues)
.stream()
.map(a -> a.stream().map(ComponentLogEntryValue::getOriginalValue).collect(Collectors.joining(", ")))
.findFirst()
.orElse("");
}
}

View File

@ -5,6 +5,7 @@ import static com.iqser.red.service.persistence.management.v1.processor.roles.Ac
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@ -29,6 +30,7 @@ import lombok.RequiredArgsConstructor;
@Deprecated(forRemoval = true)
@RestController
@RequiredArgsConstructor
@ConditionalOnProperty(name = "application.rss.component-log.enabled", havingValue = "false")
public class RSSController implements RSSResource {
private final RssReportClient rssReportClient;

View File

@ -95,9 +95,9 @@ public class ComponentControllerV2 implements ComponentResource {
return EntityReference.builder()
.id(componentLogEntityReference.getId())
.entityRule(componentLogEntityReference.getEntityRuleId())
.entityRuleId(componentLogEntityReference.getEntityRuleId())
.type(componentLogEntityReference.getType())
.pages(Set.of(componentLogEntityReference.getPage()))
.page(componentLogEntityReference.getPage())
.build();
}

View File

@ -4,7 +4,9 @@ import com.iqser.red.persistence.service.v1.external.api.impl.controller.Dossier
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DossierTemplateController;
import com.iqser.red.service.persistence.management.v1.processor.exception.DossierNotFoundException;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DownloadFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v2.api.external.model.DocuMineDossierRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.DossierList;
import com.iqser.red.service.persistence.service.v2.api.external.resource.DossierResource;
import io.swagger.v3.oas.annotations.Parameter;
@ -19,6 +21,8 @@ import org.springframework.web.bind.annotation.RestController;
import static com.iqser.red.service.persistence.management.v1.processor.exception.DossierNotFoundException.DOSSIER_NOT_FOUND_MESSAGE;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierTemplateResource.DOSSIER_TEMPLATE_ID_PARAM;
import java.util.Set;
@RestController
@RequiredArgsConstructor
@Tag(name = "2. Dossier endpoints", description = "Provides operations related to dossiers")
@ -63,12 +67,13 @@ public class DossierControllerV2 implements DossierResource {
public ResponseEntity<Dossier> createDossierOrUpdateDossier(@Parameter(name = DOSSIER_TEMPLATE_ID_PARAM, description = "The identifier of the dossier template that is used for the dossier.", required = true) @PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@RequestBody DossierRequest dossier) {
@RequestBody DocuMineDossierRequest dossier) {
return dossierController.createDossierOrUpdateDossier(dossier);
dossierTemplateController.getDossierTemplate(dossierTemplateId);
return dossierController.createDossierOrUpdateDossier(mapToDossierRequest(dossierTemplateId, dossier));
}
public void deleteDossier(@Parameter(name = DOSSIER_TEMPLATE_ID_PARAM, description = "The identifier of the dossier template that is used for the dossier.", required = true) @PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Parameter(name = DOSSIER_ID_PARAM, description = "The identifier of the dossier to retrieve.", required = true) @PathVariable(DOSSIER_ID_PARAM) String dossierId) {
@ -77,5 +82,21 @@ public class DossierControllerV2 implements DossierResource {
dossierController.deleteDossier(dossierId);
}
private static DossierRequest mapToDossierRequest(String dossierTemplateId, DocuMineDossierRequest dossier) {
return DossierRequest.builder()
.dossierId(dossier.getId())
.dossierName(dossier.getName())
.dossierTemplateId(dossierTemplateId)
.description(dossier.getDescription())
.ownerId(dossier.getOwnerId())
.memberIds(dossier.getMemberIds())
.approverIds(dossier.getMemberIds()) // for DocuMine, the members are always set as approvers
.downloadFileTypes(Set.of(DownloadFileType.ORIGINAL))
.reportTemplateIds(dossier.getReportTemplateIds())
.watermarkId(null)
.previewWatermarkId(null)
.dossierStatusId(dossier.getDossierStatusId())
.build();
}
}

View File

@ -0,0 +1,51 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.Set;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DownloadFileType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(name="DossierRequest", description = "Object containing information about a dossier.")
public class DocuMineDossierRequest {
@Schema(description = "The id of the dossier, can be null for create requests.")
private String id;
@NonNull
@Schema(description = "The name of the dossier. Must be unique.")
private String name;
@Schema(description = "The dossier's description (optional).")
private String description;
@Schema(description = "The date when the dossier is due.")
private OffsetDateTime dueDate;
@Schema(description = "The id of the owning user.")
private String ownerId;
@Builder.Default
@Schema(description = "The id(s) of members associated to this dossier.")
private Set<String> memberIds = new HashSet<>();
@Builder.Default
@Schema(description = "Id(s) of the word report templates used to generate downloads")
private Set<String> reportTemplateIds = new HashSet<>();
@Schema(description = "The dossierStatusId for this dossier. can be null for update request.")
private String dossierStatusId;
}

View File

@ -3,12 +3,14 @@ package com.iqser.red.service.persistence.service.v2.api.external.model;
import java.util.HashSet;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Data
@Builder
@ -19,12 +21,12 @@ public class EntityReference {
private String id;
@JacksonXmlCData
private String type;
@JacksonXmlCData
private String entityRule;
private String entityRuleId;
@JacksonXmlCData
private String value;
@Builder.Default
private Set<Integer> pages = new HashSet<>();
private Integer page;
}

View File

@ -8,6 +8,8 @@ import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
@Data
@Builder
@NoArgsConstructor
@ -15,5 +17,5 @@ import java.util.List;
public class FileComponentsList {
@Builder.Default
private List<FileComponents> fileComponents = new ArrayList<>();
private List<FileComponents> files = new ArrayList<>();
}

View File

@ -2,6 +2,7 @@ package com.iqser.red.service.persistence.service.v2.api.external.resource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v2.api.external.model.DocuMineDossierRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.DossierList;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -55,7 +56,7 @@ public interface DossierResource {
@Operation(summary = "Creates or updates a dossier for a specific dossier template.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully saved the dossier."), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body"), @ApiResponse(responseCode = "409", description = "Duplicate")})
ResponseEntity<Dossier> createDossierOrUpdateDossier(@Parameter(name = DOSSIER_TEMPLATE_ID_PARAM, description = "The identifier of the dossier template that is used for the dossier.", required = true) @PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@RequestBody DossierRequest dossier);
@RequestBody DocuMineDossierRequest dossier);
@ResponseStatus(value = HttpStatus.NO_CONTENT)

View File

@ -9,6 +9,7 @@ tenant-user-management-service.url: "http://tenant-user-management-service:8080/
application:
type: "RedactManager"
rss.component-log.enabled: false
server:
port: 8080

View File

@ -19,8 +19,8 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
public class DossierTemplateModel {
@Schema(description = "The Rule Set Id. Generated by the system on create.")
private String dossierTemplateId;
@Schema(description = "The dossier template identifier. Generated by the system on create.")
private String id;
@Schema(description = "The name of this dossierTemplate. Must be set on create / update requests")
private String name;
@ -51,7 +51,7 @@ public class DossierTemplateModel {
private Set<DownloadFileType> downloadFileTypes = new HashSet<>();
@Schema(description = "Status of dossier template.")
private DossierTemplateStatus dossierTemplateStatus;
private DossierTemplateStatus status;
@Schema(description = "Representing the setting if the metadata of images in pdfs should get kept, or removed")
private boolean keepImageMetadata;
@ -71,9 +71,34 @@ public class DossierTemplateModel {
@Schema(description = "Flag that specifies the watermark removal in documents will be performed before the OCR processing")
private boolean removeWatermark;
public String getId() {
return dossierTemplateId;
// TODO: The following getters and setter ensure backwards compatibility. Remove them as soon as UI does not use them anymore
@Deprecated
public String getDossierTemplateId() {
return id;
}
@Deprecated
public void setDossierTemplateId(String id) {
this.id = id;
}
@Deprecated
public DossierTemplateStatus getDossierTemplateStatus() {
return status;
}
@Deprecated
public void setDossierTemplateStatus(DossierTemplateStatus status) {
this.status = status;
}
}

View File

@ -4,6 +4,7 @@ import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DownloadFileType;
@ -19,7 +20,7 @@ import lombok.NoArgsConstructor;
public class Dossier {
private String id;
private String dossierName;
private String name;
private OffsetDateTime date;
private String description;
private String ownerId;
@ -38,7 +39,27 @@ public class Dossier {
private String dossierStatusId;
private DossierVisibility visibility;
public String getDossierId(){
// TODO: The following getters and setter ensure backwards compatibility. Remove them as soon as UI does not use them anymore
@Deprecated
public String getDossierId() {
return id;
}
@Deprecated
public String getDossierName() {
return name;
}
@Deprecated
public void setDossierName(String name) {
this.name = name;
}
}