Compare commits

..

1 Commits

973 changed files with 14068 additions and 567209 deletions

0
.dev/docker-compose.yaml Executable file → Normal file
View File

8
.gitignore vendored
View File

@ -26,11 +26,3 @@
**/.DS_Store
**/classpath-data.json
**/dependencies-and-licenses-overview.txt
gradle.properties
gradlew
gradlew.bat
gradle/
**/.gradle
**/build

View File

@ -3,22 +3,4 @@ variables:
include:
- project: 'gitlab/gitlab'
ref: 'main'
file: 'ci-templates/gradle_java.yml'
deploy:
stage: deploy
tags:
- dind
script:
- echo "Building with gradle version ${BUILDVERSION}"
- gradle -Pversion=${BUILDVERSION} publish
- gradle bootBuildImage --publishImage -PbuildbootDockerHostNetwork=true -Pversion=${BUILDVERSION}
- echo "BUILDVERSION=$BUILDVERSION" >> version.env
artifacts:
reports:
dotenv: version.env
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH =~ /^release/
- if: $CI_COMMIT_BRANCH =~ /^feature/
- if: $CI_COMMIT_TAG
file: 'ci-templates/maven_java.yml'

View File

@ -1,57 +0,0 @@
![badge](https://img.shields.io/badge/license-knecon-brightgreen)
## Description
*TODO*
## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [License](#license)
- [Tests](#tests)
- [API](#api)
## Installation
*TODO*
## Usage
*TODO*
## License
![badge](https://img.shields.io/badge/license-knecon-brightgreen)<br />
This application is covered by the proprietary knecon license.
## Tests
*TODO*
## API
### Files
The OpenAPI specifications for RedactManager and Documine are located here:<br> `./persistence-service-v1/persistence-service-external-api-v2/src/main/resources/api`
The Redocly configuration file `redocly.yaml` is located in the root directory of the project. It contains the settings for linting the specifications and generating the API documentation. If you run the redocly commands from there, you do not need to specify the path to the configuration file.
The Redocly files are located in the `redocly` directory:
* The `templates` directory contains the [Handlebars](https://handlebarsjs.com/) templates for RedactManager and Documine, respectively.
* The `build` directory is the default output directory for the generated API documentation.
### Redocly Installation
Execute the following command to install the Redocly CLI.
```bash
npm install -g @redocly/cli@latest
```
### Redocly Linting
Execute the following commands in the root directory of the project to lint the OpenAPI specifications.
Please refer to the Redocly documentation for detailed information on the linting rules.
```bash
redocly lint redactmanager
redocly lint documine
```
### Redocly Docs Generation
Execute the following commands in the root directory of the project to generate the API documentation.
```bash
redocly build-docs redactmanager --output ./redocly/build/redactmanager.html
redocly build-docs documine --output ./redocly/build/documine.html
```

View File

@ -1,7 +0,0 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
}

View File

@ -1,79 +0,0 @@
plugins {
`java-library`
`maven-publish`
pmd
checkstyle
jacoco
}
val redactionServiceVersion by rootProject.extra { "4.290.0" }
val pdftronRedactionServiceVersion by rootProject.extra { "4.90.0-RED10115.0" }
val redactionReportServiceVersion by rootProject.extra { "4.81.0" }
val searchServiceVersion by rootProject.extra { "2.90.0" }
val documentVersion by rootProject.extra { "4.433.0" }
repositories {
mavenLocal()
mavenCentral()
maven {
url = uri("https://nexus.knecon.com/repository/gindev/");
credentials {
username = providers.gradleProperty("mavenUser").getOrNull();
password = providers.gradleProperty("mavenPassword").getOrNull();
}
}
}
group = "com.iqser.red.service"
java.sourceCompatibility = JavaVersion.VERSION_17
java.targetCompatibility = JavaVersion.VERSION_17
pmd {
isConsoleOutput = true
}
tasks.pmdMain {
pmd.ruleSetFiles = files("${rootDir}/config/pmd/pmd.xml")
}
tasks.pmdTest {
pmd.ruleSetFiles = files("${rootDir}/config/pmd/test_pmd.xml")
}
tasks.named<Test>("test") {
useJUnitPlatform()
reports {
junitXml.outputLocation.set(layout.buildDirectory.dir("reports/junit"))
}
minHeapSize = "512m"
maxHeapSize = "2048m"
}
tasks.test {
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
}
tasks.jacocoTestReport {
dependsOn(tasks.test) // tests are required to run before generating the report
reports {
xml.required.set(true)
csv.required.set(false)
html.outputLocation.set(layout.buildDirectory.dir("jacocoHtml"))
}
}
java {
withJavadocJar()
}
allprojects {
tasks.withType<Javadoc> {
options {
this as StandardJavadocDocletOptions
addBooleanOption("Xdoclint:none", true)
addStringOption("Xmaxwarns", "1")
}
}
}

View File

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
<property
name="severity"
value="error"/>
<module name="TreeWalker">
<module name="SuppressWarningsHolder"/>
<module name="MissingDeprecated"/>
<module name="MissingOverride"/>
<module name="AnnotationLocation"/>
<module name="JavadocStyle"/>
<module name="NonEmptyAtclauseDescription"/>
<module name="IllegalImport"/>
<module name="RedundantImport"/>
<module name="RedundantModifier"/>
<module name="EmptyBlock"/>
<module name="DefaultComesLast"/>
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="ExplicitInitialization"/>
<module name="IllegalInstantiation"/>
<module name="ModifiedControlVariable"/>
<module name="MultipleVariableDeclarations"/>
<module name="PackageDeclaration"/>
<module name="ParameterAssignment"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="StringLiteralEquality"/>
<module name="OneStatementPerLine"/>
<module name="FinalClass"/>
<module name="ArrayTypeStyle"/>
<module name="UpperEll"/>
<module name="OuterTypeFilename"/>
</module>
<module name="FileTabCharacter"/>
<module name="SuppressWarningsFilter"/>
</module>

View File

@ -1,20 +0,0 @@
<?xml version="1.0"?>
<ruleset name="Custom ruleset"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<description>
Knecon ruleset checks the code for bad stuff
</description>
<rule ref="category/java/errorprone.xml">
<exclude name="MissingSerialVersionUID"/>
<exclude name="AvoidLiteralsInIfCondition"/>
<exclude name="AvoidDuplicateLiterals"/>
<exclude name="NullAssignment"/>
<exclude name="AssignmentInOperand"/>
<exclude name="BeanMembersShouldSerialize"/>
</rule>
</ruleset>

View File

@ -1,22 +0,0 @@
<?xml version="1.0"?>
<ruleset name="Custom ruleset"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<description>
Knecon test ruleset checks the code for bad stuff
</description>
<rule ref="category/java/errorprone.xml">
<exclude name="MissingSerialVersionUID"/>
<exclude name="AvoidLiteralsInIfCondition"/>
<exclude name="AvoidDuplicateLiterals"/>
<exclude name="NullAssignment"/>
<exclude name="AssignmentInOperand"/>
<exclude name="TestClassWithoutTestCases"/>
<exclude name="BeanMembersShouldSerialize"/>
</rule>
</ruleset>

View File

@ -1 +0,0 @@
version = 2.0-SNAPSHOT

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.knecon.fforesight</groupId>
<artifactId>platform-docker-dependency</artifactId>
<version>0.1.0</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>persistence-service-image-v1</artifactId>
<groupId>com.iqser.red.service</groupId>
<version>2.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<service.server>persistence-service-server-v1</service.server>
<platform.jar>${service.server}.jar</platform.jar>
<docker.skip.push>false</docker.skip.push>
<docker.image.name>${docker.image.prefix}/${service.server}</docker.image.name>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>download-platform-jar</id>
<phase>prepare-package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${service.server}</artifactId>
<version>${project.version}</version>
<type>jar</type>
<overWrite>true</overWrite>
<destFileName>${platform.jar}</destFileName>
</dependency>
</artifactItems>
<outputDirectory>${docker.build.directory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<images>
<image>
<name>${docker.image.name}</name>
<build>
<dockerFileDir>${docker.build.directory}</dockerFileDir>
<args>
<PLATFORM_JAR>${platform.jar}</PLATFORM_JAR>
</args>
<tags>
<tag>${docker.image.version}</tag>
<tag>latest</tag>
</tags>
</build>
</image>
</images>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@ -0,0 +1,9 @@
FROM red/base-image:2.0.2
ARG PLATFORM_JAR
ENV PLATFORM_JAR ${PLATFORM_JAR}
ENV USES_ELASTICSEARCH false
COPY ["${PLATFORM_JAR}", "/"]

View File

@ -1,12 +0,0 @@
plugins {
id("com.iqser.red.service.java-conventions")
id("io.spring.dependency-management") version "1.1.6"
id("org.sonarqube") version "4.4.1.3373"
id("io.freefair.lombok") version "8.6"
}
dependencies {
api(project(":persistence-service-processor-v1"))
}
description = "persistence-service-external-api-impl-v1"

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>persistence-service-v1</artifactId>
<groupId>com.iqser.red.service</groupId>
<version>2.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>persistence-service-external-api-impl-v1</artifactId>
<properties>
<slf4j.version>1.7.30</slf4j.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>persistence-service-processor-v1</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -3,6 +3,7 @@ package com.iqser.red.persistence.service.v1.external.api.impl;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class PersistenceServiceExternalApiConfiguration {

View File

@ -3,23 +3,18 @@ package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_APP_CONFIG;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_APP_CONFIG;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.ApplicationConfigurationEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.ApplicationConfigService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.ApplicationConfigurationResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.ApplicationConfig;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import jakarta.persistence.Column;
import jakarta.validation.Valid;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -29,24 +24,14 @@ import lombok.extern.slf4j.Slf4j;
public class ApplicationConfigurationController implements ApplicationConfigurationResource {
private final ApplicationConfigService applicationConfigService;
private final AuditPersistenceService auditPersistenceService;
@Override
@PreAuthorize("hasAuthority('" + WRITE_APP_CONFIG + "')")
public ApplicationConfig createOrUpdateAppConfig(@Valid @RequestBody ApplicationConfig appConfig) {
var result = MagicConverter.convert(applicationConfigService.saveApplicationConfiguration(convert(appConfig)),
ApplicationConfig.class);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId("ApplicationConfig")
.category(AuditCategory.SETTINGS.name())
.message("Application config has been changed.")
.build());
return result;
return MagicConverter.convert(applicationConfigService.saveApplicationConfiguration(MagicConverter.convert(appConfig, ApplicationConfigurationEntity.class)),
ApplicationConfig.class);
}
@ -57,18 +42,4 @@ public class ApplicationConfigurationController implements ApplicationConfigurat
return MagicConverter.convert(applicationConfigService.getApplicationConfig(), ApplicationConfig.class);
}
private ApplicationConfigurationEntity convert(ApplicationConfig appConfig){
var entity = ApplicationConfigurationEntity.builder()
.downloadCleanupDownloadFilesHours(appConfig.getDownloadCleanupDownloadFilesHours())
.downloadCleanupNotDownloadFilesHours(appConfig.getDownloadCleanupNotDownloadFilesHours())
.softDeleteCleanupTime(appConfig.getSoftDeleteCleanupTime())
.build();
if(appConfig.getHardDeleteCleanupRetryTime() != null){
entity.setHardDeleteCleanupRetryTime(appConfig.getHardDeleteCleanupRetryTime());
}
return entity;
}
}

View File

@ -1,7 +1,7 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.SEARCH_AUDIT_LOG;
import static com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter.convert;
import static com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter.convert;
import java.util.List;
@ -36,11 +36,11 @@ public class AuditController implements AuditResource {
var auditModels = convert(auditPersistenceService.search(auditSearchRequest), AuditModel.class);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(auditSearchRequest.getObjectId())
.category(AuditCategory.AUDIT.name())
.message("Audit Log has been viewed.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(auditSearchRequest.getObjectId())
.category(AuditCategory.AUDIT.name())
.message("Audit Log has been viewed.")
.build());
return new AuditResponse(auditModels.getElements(), auditModels.getTotalHits(), auditModels.getPage(), auditModels.getPageSize());
}

View File

@ -11,12 +11,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.service.CustomPermissionService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.CustomPermissionMappingResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.permission.CustomPermissionMappingModel;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.RequiredArgsConstructor;
@ -25,7 +21,6 @@ import lombok.RequiredArgsConstructor;
public class CustomPermissionMappingController implements CustomPermissionMappingResource {
private final CustomPermissionService customPermissionService;
private final AuditPersistenceService auditPersistenceService;
@Override
@ -41,14 +36,6 @@ public class CustomPermissionMappingController implements CustomPermissionMappin
public void saveCustomPermissionMappings(@PathVariable(TARGET_OBJECT_NAME) String targetObject, @RequestBody List<CustomPermissionMappingModel> customPermissionMappingModels) {
customPermissionService.saveCustomPermissionMappings(targetObject, customPermissionMappingModels);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(targetObject)
.category(AuditCategory.SETTINGS.name())
.message("Custom permissions have been changed.")
.build());
}

View File

@ -1,4 +1,4 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -8,9 +8,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@ -23,28 +23,24 @@ import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryDifferenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.management.v1.processor.utils.TypeValueMapper;
import com.iqser.red.service.persistence.service.v1.api.external.model.UpdateEntries;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DictionaryResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.CreateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.Dictionary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.UpdateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dictionary.Dictionary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dictionary.DictionaryDifferenceResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.Colors;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import feign.FeignException;
import jakarta.validation.Valid;
@ -58,8 +54,6 @@ public class DictionaryController implements DictionaryResource {
private final DictionaryService dictionaryService;
private final AuditPersistenceService auditClient;
private final AccessControlService accessControlService;
private final DictionaryDifferenceService dictionaryDifferenceService;
@Override
@ -72,12 +66,12 @@ public class DictionaryController implements DictionaryResource {
addEntries(type, dossierTemplateId, entries, removeCurrent, dossierId, dictionaryEntryType);
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary entries were added.")
.details(Map.of("Type", type, "Number", entries.size()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary entries were added.")
.details(Map.of("Type", type, "Number", entries.size()))
.build());
}
@ -86,8 +80,6 @@ public class DictionaryController implements DictionaryResource {
if (dossierId == null) {
dictionaryService.addGlobalEntries(type, dossierTemplateId, entries, removeCurrent, dictionaryEntryType);
} else {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
dictionaryService.addDossierEntries(type, dossierTemplateId, entries, removeCurrent, dossierId, dictionaryEntryType);
}
}
@ -102,37 +94,12 @@ public class DictionaryController implements DictionaryResource {
deleteEntries(type, dossierTemplateId, Arrays.asList(entry), dossierId, dictionaryEntryType);
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary entry was deleted.")
.details(Map.of("Type", type, "Value", entry))
.build());
}
@Override
public void updateEntries(String type, String dossierTemplateId, UpdateEntries updateEntries, String dossierId, DictionaryEntryType dictionaryEntryType) {
if (dossierId == null) {
dictionaryService.updateGlobalEntries(type, dossierTemplateId, updateEntries.entriesToAdd(), updateEntries.entriesToDelete(), dictionaryEntryType);
} else {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
dictionaryService.updateDossierEntries(type, dossierTemplateId, updateEntries.entriesToAdd(), updateEntries.entriesToDelete(), dossierId, dictionaryEntryType);
}
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary entries were updated.")
.details(Map.of("Type",
type,
"Number of added entries",
updateEntries.entriesToAdd() == null ? 0 : updateEntries.entriesToAdd().size(),
"Number of deleted entries",
updateEntries.entriesToDelete() == null ? 0 : updateEntries.entriesToDelete().size()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary entry was deleted.")
.details(Map.of("Type", type, "Value", entry))
.build());
}
@ -146,17 +113,16 @@ public class DictionaryController implements DictionaryResource {
if (dossierId == null) {
dictionaryService.deleteGlobalEntries(type, dossierTemplateId, entries, dictionaryEntryType);
} else {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
dictionaryService.deleteDossierEntries(type, dossierTemplateId, entries, dossierId, dictionaryEntryType);
}
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary entries were deleted.")
.details(Map.of("Type", type, "Number", entries.size()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary entries were deleted.")
.details(Map.of("Type", type, "Number", entries.size()))
.build());
}
@ -168,12 +134,12 @@ public class DictionaryController implements DictionaryResource {
dictionaryService.updateGlobalType(type, dossierTemplateId, typeValue);
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was updated.")
.details(Map.of("Type", type))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was updated.")
.details(Map.of("Type", type))
.build());
}
@ -183,34 +149,36 @@ public class DictionaryController implements DictionaryResource {
Type result = dictionaryService.addGlobalType(typeValue);
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(typeValue.getDossierTemplateId())
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was added.")
.details(Map.of("Type", typeValue.getType()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(typeValue.getDossierTemplateId())
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was added.")
.details(Map.of("Type", typeValue.getType()))
.build());
return MagicConverter.convert(result, TypeValue.class, new TypeValueMapper());
}
@Override
public void deleteType(@PathVariable(TYPE_PARAMETER_NAME) String type, @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
public void deleteType(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
dictionaryService.deleteGlobalType(type, dossierTemplateId);
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was deleted.")
.details(Map.of("Type", type))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was deleted.")
.details(Map.of("Type", type))
.build());
}
@Override
public void deleteTypes(@RequestBody List<String> types, @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
public void deleteTypes(@RequestBody List<String> types,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
List<String> errorIds = new ArrayList<>();
@ -219,12 +187,12 @@ public class DictionaryController implements DictionaryResource {
dictionaryService.deleteGlobalType(type, dossierTemplateId);
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was deleted.")
.details(Map.of("Type", type))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was deleted.")
.details(Map.of("Type", type))
.build());
} catch (FeignException e) {
errorIds.add(type);
}
@ -262,25 +230,19 @@ public class DictionaryController implements DictionaryResource {
validateFile(file);
try {
addEntries(type,
dossierTemplateId,
new String(file.getBytes(), StandardCharsets.UTF_8).lines()
.collect(Collectors.toList()),
true,
dossierId,
dictionaryEntryType);
addEntries(type, dossierTemplateId, new String(file.getBytes(), StandardCharsets.UTF_8).lines().collect(Collectors.toList()), true, dossierId, dictionaryEntryType);
} catch (IOException e) {
log.debug(e.getMessage(), e);
throw new BadRequestException("Could not upload file.", e);
}
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary has been uploaded.")
.details(Map.of("Type", type))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary has been uploaded.")
.details(Map.of("Type", type))
.build());
}
@ -329,52 +291,26 @@ public class DictionaryController implements DictionaryResource {
return dictionaryService.getDictionaryForType(type, dossierTemplateId, dossierId);
}
@Override
public Dictionary getMergedDictionaries(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME) String dossierId) {
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME) String dossierId) {
return dictionaryService.getMergedDictionaryForType(type, dossierTemplateId, dossierId);
}
@Override
public void setColors(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody Colors colors) {
dictionaryService.setColors(dossierTemplateId, colors);
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Colors have been changed.")
.build());
}
@Override
public void changeFlags(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@PathVariable(value = DOSSIER_ID_PARAMETER_NAME) String dossierId,
@RequestParam(value = "addToDictionaryAction") boolean addToDictionaryAction) {
dictionaryService.changeAddToDictionary(type, dossierTemplateId, dossierId, addToDictionaryAction);
}
@Override
public DictionaryDifferenceResponse getDictionaryDifference(String dossierTemplateId, Set<String> types) {
DictionaryDifferenceResponse dictionaryDifferenceResponse = dictionaryDifferenceService.calculatedDictionaryDifference(dossierTemplateId, types);
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary differences was calculated.")
.details(Map.of("DictionaryDifferenceResponse", dictionaryDifferenceResponse, "types", types))
.build());
return dictionaryDifferenceResponse;
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Colors have been changed.")
.build());
}
}

View File

@ -3,12 +3,14 @@ package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_DIGITAL_SIGNATURE;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_DIGITAL_SIGNATURE;
import java.util.Base64;
import java.nio.charset.StandardCharsets;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DigitalSignatureEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DigitalSignatureKmsEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.DigitalSignatureKmsService;
@ -23,7 +25,6 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.DigitalSign
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.DigitalSignature;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.DigitalSignatureType;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.RequiredArgsConstructor;
@ -52,11 +53,11 @@ public class DigitalSignatureController implements DigitalSignatureResource {
digitalSignatureTypeService.setActiveDigitalSignatureType(digitalSignatureType);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature type has been updated.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature type has been updated.")
.build());
}
@ -66,11 +67,11 @@ public class DigitalSignatureController implements DigitalSignatureResource {
DigitalSignatureViewModel digitalSignatureViewModel = convertToView(digitalSignatureService.saveDigitalSignature(convert(digitalSignatureModel)));
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature has been saved.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature has been saved.")
.build());
return digitalSignatureViewModel;
}
@ -82,11 +83,11 @@ public class DigitalSignatureController implements DigitalSignatureResource {
digitalSignatureService.updateDigitalSignature(convert(digitalSignatureModel));
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature has been updated.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature has been updated.")
.build());
}
@ -104,11 +105,11 @@ public class DigitalSignatureController implements DigitalSignatureResource {
digitalSignatureService.deleteDigitalSignature();
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature has been deleted.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature has been deleted.")
.build());
}
@ -118,11 +119,11 @@ public class DigitalSignatureController implements DigitalSignatureResource {
DigitalSignatureKmsViewModel result = convert(digitalSignatureKmsService.saveDigitalSignature(digitalSignature));
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital KMS signature has been saved.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital KMS signature has been saved.")
.build());
return result;
}
@ -141,11 +142,11 @@ public class DigitalSignatureController implements DigitalSignatureResource {
digitalSignatureKmsService.deleteDigitalSignature();
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital KMS signature has been deleted.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital KMS signature has been deleted.")
.build());
}
@ -191,7 +192,7 @@ public class DigitalSignatureController implements DigitalSignatureResource {
.location(digitalSignature.getLocation())
.password(digitalSignature.getPassword())
.reason(digitalSignature.getReason())
.privateKey(Base64.getDecoder().decode(digitalSignature.getBase64EncodedPrivateKey()))
.privateKey(Base64Utils.decodeFromString(digitalSignature.getPrivateKey()))
.build();
}

View File

@ -6,7 +6,6 @@ import static com.iqser.red.service.persistence.management.v1.processor.roles.Ac
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_DOSSIER_ATTRIBUTES_CONFIG;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_FILE_ATTRIBUTES;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -16,15 +15,13 @@ 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.persistence.DossierPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.DossierAttributeConfigMapper;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierAttributeConfigEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierAttributesManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierAttributeConfigPersistenceService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DossierAttributesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierAttributes;
@ -39,7 +36,6 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class DossierAttributesController implements DossierAttributesResource {
private final DossierPersistenceService dossierPersistenceService;
private final DossierAttributeConfigPersistenceService dossierAttributeConfigPersistenceService;
private final AuditPersistenceService auditPersistenceService;
private final DossierAttributesManagementService dossierAttributesManagementService;
@ -51,15 +47,13 @@ public class DossierAttributesController implements DossierAttributesResource {
public DossierAttributesConfig setDossierAttributesConfig(String dossierTemplateId, DossierAttributesConfig dossierAttributesConfig) {
var result = MagicConverter.convert(dossierAttributeConfigPersistenceService.setDossierAttributesConfig(dossierTemplateId,
MagicConverter.convert(dossierAttributesConfig.getDossierAttributeConfigs(),
DossierAttributeConfigEntity.class)),
DossierAttributeConfig.class, new DossierAttributeConfigMapper());
MagicConverter.convert(dossierAttributesConfig.getDossierAttributeConfigs(), DossierAttributeConfigEntity.class)), DossierAttributeConfig.class);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Changed dossier attributes base configuration.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Changed dossier attributes base configuration.")
.build());
return new DossierAttributesConfig(result);
}
@ -71,15 +65,13 @@ public class DossierAttributesController implements DossierAttributesResource {
@RequestBody DossierAttributeConfig dossierAttribute) {
var result = MagicConverter.convert(dossierAttributeConfigPersistenceService.addOrUpdateDossierAttribute(dossierTemplateId,
MagicConverter.convert(dossierAttribute,
DossierAttributeConfigEntity.class)),
DossierAttributeConfig.class, new DossierAttributeConfigMapper());
MagicConverter.convert(dossierAttribute, DossierAttributeConfigEntity.class)), DossierAttributeConfig.class);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier attributes added/updated")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier attributes added/updated")
.build());
return result;
}
@ -91,12 +83,12 @@ public class DossierAttributesController implements DossierAttributesResource {
dossierAttributeConfigPersistenceService.deleteDossierAttribute(dossierAttributeId);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier attributes removed")
.details(Map.of("DossierAttributeId", dossierAttributeId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier attributes removed")
.details(Map.of("DossierAttributeId", dossierAttributeId))
.build());
}
@ -106,12 +98,12 @@ public class DossierAttributesController implements DossierAttributesResource {
dossierAttributeConfigPersistenceService.deleteDossierAttributes(dossierAttributeIds);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier attributes removed")
.details(Map.of("DossierAttributeId", dossierAttributeIds))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier attributes removed")
.details(Map.of("DossierAttributeId", dossierAttributeIds))
.build());
}
@ -119,22 +111,21 @@ public class DossierAttributesController implements DossierAttributesResource {
public DossierAttributesConfig getDossierAttributesConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {
var result = dossierAttributeConfigPersistenceService.getDossierAttributes(dossierTemplateId);
return new DossierAttributesConfig(MagicConverter.convert(result, DossierAttributeConfig.class, new DossierAttributeConfigMapper()));
return new DossierAttributesConfig(MagicConverter.convert(result, DossierAttributeConfig.class));
}
@PreAuthorize("hasAuthority('" + WRITE_FILE_ATTRIBUTES + "')")
public DossierAttributes setDossierAttributes(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody DossierAttributes dossierAttributes) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsDossierOwner(dossierId);
var result = dossierAttributesManagementService.setDossierAttributes(dossierId, dossierAttributes.getDossierAttributeList());
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Changed dossier attributes.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Changed dossier attributes.")
.build());
return new DossierAttributes(result);
}
@ -143,15 +134,14 @@ public class DossierAttributesController implements DossierAttributesResource {
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_ATTRIBUTES + "')")
public DossierAttributes addOrUpdateDossierAttribute(String dossierId, DossierAttribute dossierAttribute) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsDossierOwner(dossierId);
DossierAttribute result = dossierAttributesManagementService.addOrUpdateDossierAttribute(dossierId, dossierAttribute);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Added or updated dossier attributes.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Added or updated dossier attributes.")
.build());
return new DossierAttributes(List.of(result)); // TODO should be single Object???
}
@ -159,19 +149,13 @@ public class DossierAttributesController implements DossierAttributesResource {
@PreAuthorize("hasAuthority('" + READ_DOSSIER_ATTRIBUTES + "')")
public DossierAttributes getDossierAttributes(String dossierId) {
//check if dossier exists before verifying permissions
dossierPersistenceService.findByDossierId(dossierId);
List<DossierAttribute> result = Collections.emptyList();
if (accessControlService.hasUserViewPermissionsForDossier(dossierId)) {
result = dossierAttributesManagementService.getDossierAttributes(dossierId);
}
var result = dossierAttributesManagementService.getDossierAttributes(dossierId);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Got dossier attributes.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Got dossier attributes.")
.build());
return new DossierAttributes(result);
}
@ -180,15 +164,14 @@ public class DossierAttributesController implements DossierAttributesResource {
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_ATTRIBUTES + "')")
public void deleteDossierAttribute(String dossierId, String dossierAttributeId) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsDossierOwner(dossierId);
dossierAttributesManagementService.deleteDossierAttribute(dossierId, dossierAttributeId);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Changed dossier attributes.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Changed dossier attributes.")
.build());
}

View File

@ -10,30 +10,20 @@ import java.time.OffsetDateTime;
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;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierAttributeEntity;
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;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
@ -43,18 +33,17 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Lists;
import com.iqser.red.service.persistence.management.v1.processor.service.users.model.User;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
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.websocket.WebsocketService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierAttributePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.model.User;
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.service.v1.api.external.resource.DossierResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierChangeEntry;
@ -65,9 +54,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.Audit
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;
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.DossierAttributes;
import com.iqser.red.service.persistence.service.v1.api.shared.model.notification.NotificationType;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -81,15 +68,11 @@ 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;
private final AccessControlService accessControlService;
private final DossierACLService dossierACLService;
private final DossierCreatorService dossierCreatorService;
private final WebsocketService websocketService;
private final DossierAttributePersistenceService dossierAttributePersistenceService;
@Override
@ -112,20 +95,6 @@ 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) {
@ -140,8 +109,8 @@ public class DossierController implements DossierResource {
Set<String> approvers = getAndValidateMembers(ownerId, dossierRequest.getApproverIds());
members.addAll(approvers);
if ((dossierRequest.getDownloadFileTypes() == null || dossierRequest.getDownloadFileTypes().isEmpty()) && (dossierRequest.getReportTemplateIds() == null
|| dossierRequest.getReportTemplateIds().isEmpty())) {
if ((dossierRequest.getDownloadFileTypes() == null || dossierRequest.getDownloadFileTypes()
.isEmpty()) && (dossierRequest.getReportTemplateIds() == null || dossierRequest.getReportTemplateIds().isEmpty())) {
throw new BadRequestException("Download and report types cannot both be empty");
}
@ -156,16 +125,16 @@ public class DossierController implements DossierResource {
// update using data from request and computed owner/members/approvers
Dossier updatedDossier = dossierManagementService.updateDossier(CreateOrUpdateDossierRequest.builder()
.dossierName(dossierRequest.getDossierName())
.description(dossierRequest.getDescription())
.dossierTemplateId(dossierRequest.getDossierTemplateId())
.downloadFileTypes(dossierRequest.getDownloadFileTypes())
.dueDate(dossierRequest.getDueDate())
.reportTemplateIds(new ArrayList<>(dossierRequest.getReportTemplateIds()))
.watermarkId(dossierRequest.getWatermarkId())
.previewWatermarkId(dossierRequest.getPreviewWatermarkId())
.dossierStatusId(dossierRequest.getDossierStatusId())
.build(), existingDossier.getId());
.dossierName(dossierRequest.getDossierName())
.description(dossierRequest.getDescription())
.dossierTemplateId(dossierRequest.getDossierTemplateId())
.downloadFileTypes(dossierRequest.getDownloadFileTypes())
.dueDate(dossierRequest.getDueDate())
.reportTemplateIds(new ArrayList<>(dossierRequest.getReportTemplateIds()))
.watermarkId(dossierRequest.getWatermarkId())
.previewWatermarkId(dossierRequest.getPreviewWatermarkId())
.dossierStatusId(dossierRequest.getDossierStatusId())
.build(), existingDossier.getId());
dossierACLService.updateDossierACL(members, approvers, ownerId, updatedDossier.getId());
dossierACLService.enhanceDossierWithACLData(updatedDossier);
@ -173,81 +142,72 @@ public class DossierController implements DossierResource {
updateFileStatusForDossierFiles(updatedDossier.getId(), members);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(updatedDossier.getId())
.category(AuditCategory.DOSSIER.name())
.message("Dossier has been updated.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(updatedDossier.getId())
.category(AuditCategory.DOSSIER.name())
.message("Dossier has been updated.")
.build());
if (existingDossier.getOwnerId() == null || !existingDossier.getOwnerId().equals(ownerId)) {
if (ownerId != null && !ownerId.equals(KeycloakSecurity.getUserId())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(ownerId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOSSIER_OWNER_SET.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build());
.userId(ownerId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOSSIER_OWNER_SET.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build());
}
if (existingDossier.getOwnerId() != null && !existingDossier.getOwnerId().equals(KeycloakSecurity.getUserId())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(existingDossier.getOwnerId())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOSSIER_OWNER_REMOVED.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build());
.userId(existingDossier.getOwnerId())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOSSIER_OWNER_REMOVED.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build());
}
}
Set<String> uniqueMembers = new LinkedHashSet<>(members);
uniqueMembers.addAll(approvers);
uniqueMembers.stream()
.filter(member -> !member.equals(ownerId) && !member.equals(KeycloakSecurity.getUserId()) && (existingDossier.getMemberIds() == null
|| !existingDossier.getMemberIds().contains(member)))
Stream.concat(members.stream(), approvers.stream())
.filter(member -> !member.equals(ownerId) && !member.equals(KeycloakSecurity.getUserId()) && (existingDossier.getMemberIds() == null || !existingDossier.getMemberIds()
.contains(member)))
.forEach(member -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_BECOMES_DOSSIER_MEMBER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_BECOMES_DOSSIER_MEMBER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
if (existingDossier.getMemberIds() != null) {
existingDossier.getMemberIds()
.stream()
.filter(member -> !members.contains(member) && !approvers.contains(member) && !member.equals(KeycloakSecurity.getUserId()))
.forEach(member -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_REMOVED_AS_DOSSIER_MEMBER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_REMOVED_AS_DOSSIER_MEMBER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
}
approvers.stream()
.filter(approver -> !KeycloakSecurity.getUserId().equals(approver) && existingDossier.getMemberIds() != null && existingDossier.getMemberIds()
.contains(approver) && (existingDossier.getApproverIds() == null || !existingDossier.getApproverIds().contains(approver)))
.forEach(approver -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(approver)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_PROMOTED_TO_APPROVER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
.userId(approver)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_PROMOTED_TO_APPROVER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
members.stream()
.filter(member -> !member.equals(KeycloakSecurity.getUserId())
&& existingDossier.getApproverIds() != null
&& existingDossier.getApproverIds().contains(member)
&& !approvers.contains(member))
.filter(member -> !member.equals(KeycloakSecurity.getUserId()) && existingDossier.getApproverIds() != null && existingDossier.getApproverIds()
.contains(member) && !approvers.contains(member))
.forEach(member -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_DEGRADED_TO_REVIEWER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
websocketService.sendDossierEvent(dossierRequest.getDossierId(), DossierEventType.UPDATE);
updatedDossier.setDossierAttributes(convertDossierAttributes(dossierAttributePersistenceService.getDossierAttributes(updatedDossier.getId())));
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_DEGRADED_TO_REVIEWER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
@ -257,13 +217,12 @@ public class DossierController implements DossierResource {
Dossier created = createNewDossier(dossierRequest, ownerId, members, approvers);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(created.getId())
.category(AuditCategory.DOSSIER.name())
.message("Dossier has been created.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(created.getId())
.category(AuditCategory.DOSSIER.name())
.message("Dossier has been created.")
.build());
websocketService.sendDossierEvent(created.getId(), DossierEventType.CREATE);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(created, httpHeaders, HttpStatus.CREATED);
@ -289,9 +248,7 @@ public class DossierController implements DossierResource {
}
// check he has a manager role, thus he can be the owner
if (user.isPresent() && user.get().getRoles()
.stream()
.noneMatch(ApplicationRoles.RED_MANAGER_ROLE::equals)) {
if (user.isPresent() && user.get().getRoles().stream().noneMatch(ApplicationRoles.RED_MANAGER_ROLE::equals)) {
throw new BadRequestException("Make sure provided user id has the manager role.");
}
@ -303,8 +260,7 @@ public class DossierController implements DossierResource {
private Set<String> getAndValidateMembers(String ownerId, Set<String> memberIds) {
Set<String> actualMemberIds = memberIds == null ? new TreeSet<>() : memberIds;
if (actualMemberIds.stream()
.anyMatch(Objects::isNull)) {
if (actualMemberIds.stream().anyMatch(Objects::isNull)) {
throw new BadRequestException("Member IDs cannot be null");
}
@ -318,10 +274,7 @@ public class DossierController implements DossierResource {
Set<String> deletedUserIds = userService.removeDeletedUsers(actualMemberIds);
actualMemberIds.removeAll(deletedUserIds);
}
if (users.stream()
.anyMatch(u -> u.getRoles()
.stream()
.noneMatch(VALID_MEMBER_ROLES::contains))) {
if (users.stream().anyMatch(u -> u.getRoles().stream().noneMatch(VALID_MEMBER_ROLES::contains))) {
throw new BadRequestException("Make sure each provided member id has the permission to be a member of a dossier.");
}
return actualMemberIds;
@ -340,36 +293,32 @@ public class DossierController implements DossierResource {
private void updateFileStatusForDossierFiles(String dossierId, Collection<String> members) {
fileStatusManagementService.getDossierStatus(dossierId)
.stream()
.filter(fileStatus -> !fileStatus.isSoftOrHardDeleted())
.forEach(f -> {
fileStatusManagementService.getDossierStatus(dossierId).stream().filter(fileStatus -> !fileStatus.isSoftOrHardDeleted()).forEach(f -> {
if (f.getAssignee() != null && !members.contains(f.getAssignee())) {
fileStatusManagementService.setCurrentFileAssignee(dossierId, f.getId(), null);
}
});
if (f.getAssignee() != null && !members.contains(f.getAssignee())) {
fileStatusManagementService.setCurrentFileAssignee(dossierId, f.getId(), null);
}
});
}
private Dossier createNewDossier(DossierRequest dossier, String ownerId, Set<String> members, Set<String> approvers) {
Dossier newDossier = dossierCreatorService.addDossier(CreateOrUpdateDossierRequest.builder()
.dossierName(dossier.getDossierName().trim())
.description(dossier.getDescription())
.dossierTemplateId(dossier.getDossierTemplateId())
.downloadFileTypes(dossier.getDownloadFileTypes())
.dueDate(dossier.getDueDate())
.reportTemplateIds(dossier.getReportTemplateIds()
!= null ? new ArrayList<>(dossier.getReportTemplateIds()) : Lists.newArrayList())
.watermarkId(dossier.getWatermarkId())
.previewWatermarkId(dossier.getPreviewWatermarkId())
.dossierStatusId(dossier.getDossierStatusId())
.build(), members, approvers, ownerId);
Dossier newDossier = dossierManagementService.addDossier(CreateOrUpdateDossierRequest.builder()
.dossierName(dossier.getDossierName())
.description(dossier.getDescription())
.dossierTemplateId(dossier.getDossierTemplateId())
.downloadFileTypes(dossier.getDownloadFileTypes())
.dueDate(dossier.getDueDate())
.reportTemplateIds(dossier.getReportTemplateIds() != null ? new ArrayList<>(dossier.getReportTemplateIds()) : Lists.newArrayList())
.watermarkId(dossier.getWatermarkId())
.previewWatermarkId(dossier.getPreviewWatermarkId())
.dossierStatusId(dossier.getDossierStatusId())
.build());
dossierACLService.updateDossierACL(members, approvers, ownerId, newDossier.getId());
dossierACLService.enhanceDossierWithACLData(newDossier);
newDossier.setDossierAttributes(new DossierAttributes());
return newDossier;
@ -391,114 +340,92 @@ public class DossierController implements DossierResource {
}
@PreAuthorize("hasAuthority('" + DELETE_DOSSIER + "')")
@PreAuthorize("hasAuthority('" + DELETE_DOSSIER + "') && hasPermission(#dossierId, 'Dossier', 'ACCESS_OBJECT')")
public void deleteDossier(@PathVariable(DOSSIER_ID_PARAM) String dossierId) {
Dossier dossier = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, true, false));
accessControlService.checkAccessPermissionsToDossier(dossierId);
var dossierToBeDeleted = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, true, false));
if (dossier.getOwnerId() != null && !dossier.getOwnerId().equals(KeycloakSecurity.getUserId())) {
throw new AccessDeniedException("Can not delete dossier that is owned by a different user");
}
dossierManagementService.softDeleteDossier(dossierId);
dossierManagementService.delete(dossierId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier moved to trash.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier moved to trash.")
.build());
dossier.getMemberIds()
dossierToBeDeleted.getMemberIds()
.stream()
.filter(m -> !KeycloakSecurity.getUserId().equals(m))
.forEach(member -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOSSIER_DELETED.name())
.target(Map.of("dossierId", dossierId, "dossierName", dossier.getDossierName()))
.build()));
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOSSIER_DELETED.name())
.target(Map.of("dossierId", dossierId, "dossierName", dossierToBeDeleted.getDossierName()))
.build()));
websocketService.sendDossierEvent(dossierId, DossierEventType.SOFT_DELETE);
}
@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 + "')")
@PostAuthorize("hasPermission(#dossierId, 'Dossier', 'VIEW_OBJECT')")
public Dossier getDossier(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_DELETED_PARAM, defaultValue = "false", required = false) boolean includeDeleted) {
accessControlService.checkViewPermissionsToDossier(dossierId);
var dossier = dossierManagementService.getDossierById(dossierId, includeArchived, includeDeleted);
dossier.setDossierAttributes(convertDossierAttributes(dossierAttributePersistenceService.getDossierAttributes(dossierId)));
return dossierACLService.enhanceDossierWithACLData(dossier);
return dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, includeArchived, includeDeleted));
}
@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);
return enhanceDossiersWithAttributeAndACLData(dossiers);
return dossierManagementService.getAllDossiers(includeArchived, includeDeleted).stream().map(dossierACLService::enhanceDossierWithACLData).collect(Collectors.toList());
}
@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);
return enhanceDossiersWithAttributeAndACLData(dossiers);
return dossierManagementService.getAllDossiersForDossierTemplateId(dossierTemplateId, includeArchived, includeDeleted)
.stream()
.map(dossierACLService::enhanceDossierWithACLData)
.collect(Collectors.toList());
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getSoftDeletedDossiers() {
var dossiers = dossierManagementService.getSoftDeletedDossiers();
return enhanceDossiersWithAttributeAndACLData(dossiers);
return dossierManagementService.getSoftDeletedDossiers().stream().map(dossierACLService::enhanceDossierWithACLData).collect(Collectors.toList());
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getArchivedDossiers() {
var dossiers = dossierManagementService.getArchivedDossiers();
return enhanceDossiersWithAttributeAndACLData(dossiers);
return dossierManagementService.getArchivedDossiers().stream().map(dossierACLService::enhanceDossierWithACLData).collect(Collectors.toList());
}
@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);
return enhanceDossiersWithAttributeAndACLData(dossiers);
return dossierManagementService.getArchivedDossiersForDossierTemplateId(dossierTemplateId)
.stream()
.map(dossierACLService::enhanceDossierWithACLData)
.collect(Collectors.toList());
}
@ -513,30 +440,27 @@ public class DossierController implements DossierResource {
for (String dossierId : dossierIds) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier archived.")
.build());
websocketService.sendDossierEvent(dossierId, DossierEventType.ARCHIVE);
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier archived.")
.build());
}
}
@PreAuthorize("hasAuthority('" + UNARCHIVE_DOSSIER + "')")
@PreFilter("hasPermission(filterObject, 'Dossier', 'ACCESS_OBJECT')")
public void unarchiveDossiers(@RequestBody Set<String> dossierIds) {
dossierManagementService.unarchiveDossiers(dossierIds);
for (String dossierId : dossierIds) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier restored from archive.")
.build());
websocketService.sendDossierEvent(dossierId, DossierEventType.UNARCHIVE);
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier restored from archive.")
.build());
}
}
@ -545,19 +469,20 @@ public class DossierController implements DossierResource {
@PreFilter("hasPermission(filterObject, 'Dossier', 'ACCESS_OBJECT')")
public void hardDeleteDossiers(@RequestParam(DOSSIER_ID_PARAM) Set<String> dossierIds) {
var filteredDossierIds = filterDossierIdsByOwnedKeepUnowned(dossierIds);
for (String dossierId : dossierIds) {
accessControlService.verifyUserIsDossierOwner(dossierId);
}
dossierManagementService.hardDeleteDossiers(dossierIds);
dossierManagementService.hardDeleteDossiers(filteredDossierIds);
for (String dossierId : filteredDossierIds) {
for (String dossierId : dossierIds) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier permanently deleted.")
.build());
websocketService.sendDossierEvent(dossierId, DossierEventType.HARD_DELETE);
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier permanently deleted.")
.build());
}
}
@ -566,68 +491,17 @@ public class DossierController implements DossierResource {
@PreFilter("hasPermission(filterObject, 'Dossier', 'ACCESS_OBJECT')")
public void undeleteDossiers(@RequestBody Set<String> dossierIds) {
var filteredDossierIds = filterDossierIdsByOwnedKeepUnowned(dossierIds);
dossierManagementService.undeleteDossiers(dossierIds);
for (String dossierId : dossierIds) {
dossierManagementService.undeleteDossiers(filteredDossierIds);
for (String dossierId : filteredDossierIds) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier restored from trash.")
.build());
websocketService.sendDossierEvent(dossierId, DossierEventType.UNDELETE);
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier restored from trash.")
.build());
}
}
private Set<String> filterDossierIdsByOwnedKeepUnowned(Set<String> dossierIds) {
return dossierIds.stream()
.map(id -> dossierManagementService.getDossierById(id, true, true))
.map(dossierACLService::enhanceDossierWithACLData)
.filter(dossier -> dossier.getOwnerId() == null || dossier.getOwnerId().equals(KeycloakSecurity.getUserId()))
.map(Dossier::getId)
.collect(Collectors.toSet());
}
private DossierAttributes convertDossierAttributes(List<DossierAttributeEntity> dossierAttributeEntities) {
Map<String, String> attributeIdToValue = new HashMap<>();
for (DossierAttributeEntity dossierAttributeEntity : dossierAttributeEntities) {
attributeIdToValue.put(dossierAttributeEntity.getId().getDossierAttributeConfigId(), dossierAttributeEntity.getValue());
}
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

@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierStatsService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DossierStatsResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierStats;
@ -27,14 +26,12 @@ import lombok.extern.slf4j.Slf4j;
public class DossierStatsController implements DossierStatsResource {
private final DossierStatsService dossierStatsService;
private final AccessControlService accessControlService;
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "') && hasPermission(#dossierId, 'Dossier', 'VIEW_OBJECT')")
public DossierStats getDossierStats(@PathVariable(DOSSIER_ID_PARAM) String dossierId) {
accessControlService.checkViewPermissionsToDossier(dossierId);
return dossierStatsService.getDossierStats(dossierId);
}
@ -44,9 +41,7 @@ public class DossierStatsController implements DossierStatsResource {
@PreFilter("hasPermission(filterObject, 'Dossier', 'VIEW_OBJECT')")
public List<DossierStats> getDossierStats(@RequestBody Set<String> dossierIds) {
return dossierIds.stream()
.map(dossierStatsService::getDossierStats)
.collect(Collectors.toList());
return dossierIds.stream().map(dossierStatsService::getDossierStats).collect(Collectors.toList());
}
}

View File

@ -4,6 +4,7 @@ import static com.iqser.red.service.persistence.management.v1.processor.roles.Ac
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_DOSSIER_STATUS;
import java.util.List;
import java.util.stream.Collectors;
import jakarta.transaction.Transactional;
@ -14,17 +15,14 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.ColorUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.DossierStatusMapper;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DossierStatusResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierStatusRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierStatusRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierStatusInfo;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -35,7 +33,6 @@ import lombok.extern.slf4j.Slf4j;
public class DossierStatusController implements DossierStatusResource {
private final DossierStatusPersistenceService dossierStatusPersistenceService;
private final AuditPersistenceService auditPersistenceService;
@Override
@ -55,22 +52,13 @@ public class DossierStatusController implements DossierStatusResource {
throw new BadRequestException("The rank must not be negative");
}
var response = dossierStatusPersistenceService.createOrUpdateDossierStatus(CreateOrUpdateDossierStatusRequest.builder()
.dossierStatusId(dossierStatusRequest.getDossierStatusId())
.name(dossierStatusRequest.getName())
.description(dossierStatusRequest.getDescription())
.dossierTemplateId(dossierStatusRequest.getDossierTemplateId())
.rank(dossierStatusRequest.getRank())
.color(dossierStatusRequest.getColor())
.build());
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierStatusRequest.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier states have been updated.")
.build());
.dossierStatusId(dossierStatusRequest.getDossierStatusId())
.name(dossierStatusRequest.getName())
.description(dossierStatusRequest.getDescription())
.dossierTemplateId(dossierStatusRequest.getDossierTemplateId())
.rank(dossierStatusRequest.getRank())
.color(dossierStatusRequest.getColor())
.build());
return MagicConverter.convert(response, DossierStatusInfo.class);
}
@ -79,7 +67,10 @@ public class DossierStatusController implements DossierStatusResource {
@PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')")
public List<DossierStatusInfo> getAllDossierStatusForTemplate(@PathVariable("dossierTemplateId") String dossierTemplateId) {
return dossierStatusPersistenceService.getAllDossierStatusForTemplate(dossierTemplateId);
return dossierStatusPersistenceService.getAllDossierStatusForTemplate(dossierTemplateId)
.stream()
.map(d -> MagicConverter.convert(d, DossierStatusInfo.class))
.collect(Collectors.toList());
}
@ -87,7 +78,10 @@ public class DossierStatusController implements DossierStatusResource {
@PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')")
public List<DossierStatusInfo> getAllDossierStatuses(@RequestBody List<String> dossierTemplateIds) {
return dossierStatusPersistenceService.getAllDossierStatuses(dossierTemplateIds);
return dossierStatusPersistenceService.getAllDossierStatuses(dossierTemplateIds)
.stream()
.map(d -> MagicConverter.convert(d, DossierStatusInfo.class))
.collect(Collectors.toList());
}
@ -96,7 +90,7 @@ public class DossierStatusController implements DossierStatusResource {
@PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')")
public DossierStatusInfo getDossierStatus(@PathVariable("dossierStatusId") String dossierStatusId) {
return dossierStatusPersistenceService.getDossierStatusInfo(dossierStatusId);
return MagicConverter.convert(dossierStatusPersistenceService.getDossierStatus(dossierStatusId), DossierStatusInfo.class, new DossierStatusMapper());
}
@ -105,17 +99,7 @@ public class DossierStatusController implements DossierStatusResource {
public void deleteDossierStatus(@PathVariable("dossierStatusId") String dossierStatusId,
@RequestParam(value = DOSSIER_STATUS_REPLACE_ID, required = false) String replaceDossierStatusId) {
var dossierTemplateId = dossierStatusPersistenceService.getDossierStatus(dossierStatusId).getDossierTemplateId();
dossierStatusPersistenceService.deleteDossierStatus(dossierStatusId, replaceDossierStatusId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier state has been deleted.")
.build());
}
}

View File

@ -8,7 +8,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -42,7 +41,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateStats;
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.Dossier;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.models.ExportDownloadRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.importexport.ExportDownloadRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.importexport.ImportDossierTemplateRequest;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
@ -77,11 +76,11 @@ public class DossierTemplateController implements DossierTemplateResource {
DossierTemplateModel response = convert(dossierTemplateManagementService.createOrUpdateDossierTemplate(dossierTemplate));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(response.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier Template has been added or updated")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(response.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier Template has been added or updated")
.build());
return response;
} catch (FeignException e) {
throw processFeignException(e);
@ -94,10 +93,7 @@ public class DossierTemplateController implements DossierTemplateResource {
@PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
public List<DossierTemplateModel> getAllDossierTemplates() {
return dossierTemplateManagementService.getAllDossierTemplates()
.stream()
.map(this::convert)
.collect(Collectors.toList());
return dossierTemplateManagementService.getAllDossierTemplates().stream().map(this::convert).collect(Collectors.toList());
}
@ -120,23 +116,22 @@ public class DossierTemplateController implements DossierTemplateResource {
String userId = KeycloakSecurity.getUserId();
List<Dossier> dossiers = dossierManagementService.getAllDossiers(true, false);
if (dossiers != null && dossiers.stream()
.anyMatch(dossier -> dossier.getDossierTemplateId().equals(dossierTemplateId))) {
if (dossiers != null && dossiers.stream().anyMatch(dossier -> dossier.getDossierTemplateId().equals(dossierTemplateId))) {
throw new ConflictException("Can not delete dossier template because there are dossiers based on it");
}
dossierTemplateManagementService.deleteDossierTemplate(dossierTemplateId, userId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier Template has been deleted")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier Template has been deleted")
.build());
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_TEMPLATES + "')")
@PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
public void deleteDossierTemplates(@RequestBody List<String> dossierTemplateIds) {
String userId = KeycloakSecurity.getUserId();
@ -145,18 +140,17 @@ public class DossierTemplateController implements DossierTemplateResource {
for (String dossierTemplateId : dossierTemplateIds) {
try {
List<Dossier> dossiers = dossierManagementService.getAllDossiers(true, false);
if (dossiers != null && dossiers.stream()
.anyMatch(dossier -> dossier.getDossierTemplateId().equals(dossierTemplateId))) {
if (dossiers != null && dossiers.stream().anyMatch(dossier -> dossier.getDossierTemplateId().equals(dossierTemplateId))) {
throw new ConflictException("Can not delete dossier template because there are dossiers based on it");
}
dossierTemplateManagementService.deleteDossierTemplate(dossierTemplateId, userId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier template has been deleted")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier template has been deleted")
.build());
} catch (FeignException e) {
errorIds.add(dossierTemplateId);
}
@ -179,11 +173,11 @@ public class DossierTemplateController implements DossierTemplateResource {
DossierTemplateModel response = convert(dossierTemplateManagementService.cloneDossierTemplate(dossierTemplateId, cloneDossierTemplateRequest));
auditPersistenceService.audit(AuditRequest.builder()
.userId(userId)
.objectId(response.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier Template has been cloned")
.build());
.userId(userId)
.objectId(response.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier Template has been cloned")
.build());
return response;
} catch (FeignException e) {
throw processFeignException(e);
@ -226,12 +220,12 @@ public class DossierTemplateController implements DossierTemplateResource {
ExportDownloadRequest request = ExportDownloadRequest.builder().dossierTemplateId(dossierTemplateId).userId(KeycloakSecurity.getUserId()).build();
var response = dossierTemplateManagementService.prepareExportDownload(request);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(response.getValue())
.category(AuditCategory.DOWNLOAD.name())
.message("Export Download was prepared")
.details(Map.of("dossierTemplateId", request.getDossierTemplateId()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(response.getValue())
.category(AuditCategory.DOWNLOAD.name())
.message("Export Download was prepared")
.details(Map.of("dossierTemplateId", request.getDossierTemplateId()))
.build());
return new DownloadResponse(response.getValue());
} catch (FeignException e) {
throw processFeignException(e);
@ -249,7 +243,7 @@ public class DossierTemplateController implements DossierTemplateResource {
if (originalFileName == null || originalFileName.isEmpty()) {
throw new BadRequestException("Could not upload file, no filename provided.");
}
var extension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1).toLowerCase(Locale.ROOT);
var extension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1).toLowerCase();
if ("zip".equalsIgnoreCase(extension)) {
if (StringUtils.isEmpty(dossierTemplateId) && updateExistingDossierTemplate) {
throw new BadRequestException("Could not update with dossier template empty");
@ -267,12 +261,12 @@ public class DossierTemplateController implements DossierTemplateResource {
.build();
DossierTemplate loadedDossierTemplate = dossierTemplateManagementService.importDossierTemplate(request);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(loadedDossierTemplate.getId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier template was imported")
.details(Map.of("dossierTemplateId", loadedDossierTemplate.getId()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(loadedDossierTemplate.getId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier template was imported")
.details(Map.of("dossierTemplateId", loadedDossierTemplate.getId()))
.build());
return convert(loadedDossierTemplate);
} catch (IOException e) {
throw new BadRequestException(e.getMessage(), e);
@ -288,8 +282,7 @@ public class DossierTemplateController implements DossierTemplateResource {
private void enhanceDossierTemplateStatsWithACLMemberDetails(DossierTemplateStats stats) {
Set<String> members = new HashSet<>();
stats.getDossiersInTemplate()
.forEach(d -> members.addAll(dossierACLService.getMembers(d)));
stats.getDossiersInTemplate().forEach(d -> members.addAll(dossierACLService.getMembers(d)));
stats.setNumberOfPeople(members.size());
}
@ -297,7 +290,7 @@ public class DossierTemplateController implements DossierTemplateResource {
private DossierTemplateModel convert(DossierTemplate dossierTemplate) {
return DossierTemplateModel.builder()
.id(dossierTemplate.getId())
.dossierTemplateId(dossierTemplate.getId())
.name(dossierTemplate.getName())
.description(dossierTemplate.getDescription())
.dateAdded(dossierTemplate.getDateAdded())
@ -307,13 +300,11 @@ public class DossierTemplateController implements DossierTemplateResource {
.validFrom(dossierTemplate.getValidFrom())
.validTo(dossierTemplate.getValidTo())
.downloadFileTypes(dossierTemplate.getDownloadFileTypes())
.status(DossierTemplateStatus.valueOf(dossierTemplate.getDossierTemplateStatus().name()))
.dossierTemplateStatus(DossierTemplateStatus.valueOf(dossierTemplate.getDossierTemplateStatus().name()))
.keepImageMetadata(dossierTemplate.isKeepImageMetadata())
.keepHiddenText(dossierTemplate.isKeepHiddenText())
.keepOverlappingObjects(dossierTemplate.isKeepOverlappingObjects())
.applyDictionaryUpdatesToAllDossiersByDefault(dossierTemplate.isApplyDictionaryUpdatesToAllDossiersByDefault())
.ocrByDefault(dossierTemplate.isOcrByDefault())
.removeWatermark(dossierTemplate.isRemoveWatermark())
.build();
}

View File

@ -2,8 +2,8 @@ package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.PROCESS_DOWNLOAD;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_DOWNLOAD_STATUS;
import static com.iqser.red.service.persistence.management.v1.processor.utils.DownloadBufferUtils.fileProxyStreamForDownload;
import java.io.BufferedInputStream;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -12,23 +12,25 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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 org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.persistence.service.v1.external.api.impl.service.OneTimeTokenService;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.DownloadService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
@ -48,10 +50,8 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.download.Do
import com.iqser.red.service.persistence.service.v1.api.shared.model.download.DownloadStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.download.DownloadWithOptionRequest;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.knecon.fforesight.tenantcommons.TenantContext;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@ -70,33 +70,24 @@ public class DownloadController implements DownloadResource {
private final AuditPersistenceService auditPersistenceService;
private final OneTimeTokenService oneTimeTokenDownloadService;
private final AccessControlService accessControlService;
private final FileManagementStorageService fileManagementStorageService;
private final FileStatusManagementService fileStatusManagementService;
private final String REPORT_INFO = "/REPORT_INFO.json";
@Value("${storage.backend}")
@Value("${storage.backend:s3}")
private String storageBackend;
@Deprecated(forRemoval = true)
@PreAuthorize("hasAuthority('" + PROCESS_DOWNLOAD + "')")
public DownloadResponse prepareDownload(@RequestBody PrepareDownloadRequest request) {
if (request.getIncludeUnprocessed() == null) {
request.setIncludeUnprocessed(true);
}
// check the user is non-member or reviewer
accessControlService.verifyUserIsDossierOwnerOrApprover(request.getDossierId());
var response = downloadService.prepareDownload(convert(request));
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(response.getValue())
.category(AuditCategory.DOWNLOAD.name())
.message("Download was prepared")
.details(Map.of("dossierId", request.getDossierId()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(response.getValue())
.category(AuditCategory.DOWNLOAD.name())
.message("Download was prepared")
.details(Map.of("dossierId", request.getDossierId()))
.build());
return new DownloadResponse(response.getValue());
}
@ -104,10 +95,6 @@ public class DownloadController implements DownloadResource {
@PreAuthorize("hasAuthority('" + PROCESS_DOWNLOAD + "')")
public DownloadResponse prepareDownload(@RequestBody PrepareDownloadWithOptionRequest request) {
if (request.getIncludeUnprocessed() == null) {
request.setIncludeUnprocessed(true);
}
validateDossierId(request.getDossierId());
validateAndFilterFileIds(request);
@ -123,12 +110,12 @@ public class DownloadController implements DownloadResource {
var response = downloadService.prepareDownload(convert(request));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(response.getValue())
.category(AuditCategory.DOWNLOAD.name())
.message("Download was prepared")
.details(Map.of("dossierId", request.getDossierId()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(response.getValue())
.category(AuditCategory.DOWNLOAD.name())
.message("Download was prepared")
.details(Map.of("dossierId", request.getDossierId()))
.build());
return new DownloadResponse(response.getValue());
}
@ -138,7 +125,7 @@ public class DownloadController implements DownloadResource {
if (StringUtils.isBlank(dossierId)) {
throw new BadRequestException("Empty dossier id");
}
dossierService.getDossierById(dossierId, true, false);
dossierService.getDossierById(dossierId, true, true);
accessControlService.verifyUserIsDossierOwnerOrApprover(dossierId);
}
@ -148,36 +135,23 @@ public class DownloadController implements DownloadResource {
List<FileModel> validFiles = fileStatusService.getDossierStatus(request.getDossierId());
var fileIds = request.getFileIds();
if (fileIds != null && !fileIds.isEmpty()) { // validate the ids provided
if(fileIds.size() == 1) {
fileStatusManagementService.getFileStatus(fileIds.get(0), false);
}
validFiles = validFiles.stream()
.filter(f -> fileIds.contains(f.getId()))
.collect(Collectors.toList());
validFiles = validFiles.stream().filter(f -> fileIds.contains(f.getId())).collect(Collectors.toList());
if (validFiles.isEmpty()) {
throw new NotFoundException("No provided file id was found");
throw new NotFoundException("No file id provided is found");
}
} // otherwise consider the files from dossier
var validFilesAndNotProcessed = validFiles.stream()
.filter(f -> !(f.getAnalysisVersion() > 0 && f.getNumberOfAnalyses() > 0 && !f.isSoftOrHardDeleted()))
.collect(Collectors.toList());
var validFilesAndNotProcessed = validFiles.stream().filter(f -> !(f.getAnalysisVersion() > 0 && f.getNumberOfAnalyses() > 0)).collect(Collectors.toList());
if (!validFilesAndNotProcessed.isEmpty()) {
throw new BadRequestException("At least a file is in its initial analysis process");
}
request.setFileIds(validFiles.stream()
.map(FileModel::getId)
.collect(Collectors.toList()));
var approvedFiles = validFiles.stream()
.filter(f -> f.getWorkflowStatus().equals(WorkflowStatus.APPROVED))
.toList();
request.setFileIds(validFiles.stream().map(FileModel::getId).collect(Collectors.toList()));
var approvedFiles = validFiles.stream().filter(f -> f.getWorkflowStatus().equals(WorkflowStatus.APPROVED)).toList();
// special corner case: unapproved files, no reports and only REDACTED type selected
if (approvedFiles.isEmpty()
&& (request.getReportTemplateIds() == null || request.getReportTemplateIds().isEmpty())
&& request.getDownloadFileTypes() != null
&& request.getDownloadFileTypes().size() == 1
&& request.getDownloadFileTypes().contains(DownloadFileType.REDACTED)) {
if (approvedFiles.isEmpty() && (request.getReportTemplateIds() == null || request.getReportTemplateIds()
.isEmpty()) && request.getDownloadFileTypes() != null && request.getDownloadFileTypes().size() == 1 && request.getDownloadFileTypes()
.contains(DownloadFileType.REDACTED)) {
throw new BadRequestException("Unapproved files in redacted state with no reports cannot be included");
}
}
@ -192,7 +166,6 @@ public class DownloadController implements DownloadResource {
.downloadFileTypes(request.getDownloadFileTypes())
.reportTemplateIds(request.getReportTemplateIds())
.redactionPreviewColor(request.getRedactionPreviewColor())
.includeUnprocessed(request.getIncludeUnprocessed())
.build();
}
@ -201,29 +174,15 @@ public class DownloadController implements DownloadResource {
@PreAuthorize("hasAuthority('" + PROCESS_DOWNLOAD + "')")
public void deleteDownloadStatus(@RequestBody RemoveDownloadRequest removeDownloadRequest) {
removeDownloadRequest.getStorageIds()
.forEach(storageId -> {
downloadService.deleteDownloadStatus(JSONPrimitive.of(storageId));
fileManagementStorageService.deleteObject(storageId);
if (storageBackend.equals("s3")) {
var storageIdForS3 = generateReportJsonStorageIdForS3(storageId);
log.info("Deleting Report Json from S3 Storage {}", storageIdForS3);
fileManagementStorageService.deleteObject(storageIdForS3);
} else {
var storageIdForAzure = generateReportJsonStorageIdForAzure(storageId);
log.info("Deleting Report Json from Azure Storage {}", storageIdForAzure);
fileManagementStorageService.deleteObject(storageIdForAzure);
}
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(storageId)
.category(AuditCategory.DOWNLOAD.name())
.message("Remove Prepared Download")
.build());
});
removeDownloadRequest.getStorageIds().forEach(storageId -> {
downloadService.deleteDownloadStatus(JSONPrimitive.of(storageId));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(storageId)
.category(AuditCategory.DOWNLOAD.name())
.message("Remove Prepared Download")
.build());
});
}
@ -236,32 +195,23 @@ public class DownloadController implements DownloadResource {
}
@SneakyThrows
@PreAuthorize("hasAuthority('" + PROCESS_DOWNLOAD + "')")
public void downloadFile(@RequestParam(STORAGE_ID) String storageId) {
var requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
public ResponseEntity<FileSystemResource> downloadFile(@RequestParam(STORAGE_ID) String storageId,
@RequestParam(value = "inline", required = false, defaultValue = FALSE) boolean inline) {
var userId = KeycloakSecurity.getUserId();
var downloadStatus = getDownloadStatus(storageId, userId);
var fileDownloadStream = getFileForDownload(storageId, userId);
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment" + "; filename*=utf-8''" + StringEncodingUtils.urlEncode(downloadStatus.getFilename()));
response.setHeader("Content-Length", String.valueOf(downloadStatus.getFileSize()));
org.apache.commons.io.IOUtils.copyLarge(fileDownloadStream.getInputStream(), response.getOutputStream());
response.flushBuffer();
return getResponseEntity(inline, fileDownloadStream, downloadStatus.getFilename(), MediaType.parseMediaType("application/zip"));
}
private DownloadStatus getDownloadStatus(String storageId, String userId) {
// TODO Add endpoint to get single download status for userId and storageId.
var downloadStatusResponse = downloadService.getDownloadStatus(userId);
Optional<DownloadStatus> downloadStatusOptional = downloadStatusResponse.stream()
.filter(ds -> ds.getStorageId().equals(storageId))
.findFirst();
Optional<DownloadStatus> downloadStatusOptional = downloadStatusResponse.stream().filter(ds -> ds.getStorageId().equals(storageId)).findFirst();
return downloadStatusOptional.orElseThrow(() -> new NotFoundException("Download status not found for this user"));
}
@ -273,20 +223,34 @@ public class DownloadController implements DownloadResource {
var response = storageService.getObject(TenantContext.getTenantId(), storageId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(userId)
.objectId(storageId)
.category(AuditCategory.DOWNLOAD.name())
.message("File was downloaded.")
.build());
.userId(userId)
.objectId(storageId)
.category(AuditCategory.DOWNLOAD.name())
.message("File was downloaded.")
.build());
downloadService.setDownloaded(JSONPrimitive.of(storageId));
return new InputStreamResource(new BufferedInputStream(response.getInputStream()));
return response;
} catch (Exception e) {
throw new NotFoundException(e.getMessage(), e);
}
}
@SneakyThrows
private ResponseEntity<FileSystemResource> getResponseEntity(boolean inline, InputStreamResource resource, String filename, MediaType mediaType) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(mediaType);
if (filename != null) {
httpHeaders.add("Content-Disposition", inline ? "inline" : "attachment" + "; filename*=utf-8''" + StringEncodingUtils.urlEncode(filename));
}
return new ResponseEntity<>(fileProxyStreamForDownload(resource.getInputStream()), httpHeaders, HttpStatus.OK);
}
@Override
@PreAuthorize("hasAuthority('" + PROCESS_DOWNLOAD + "')")
public JSONPrimitive<String> generateOneTimeToken(@RequestBody JSONPrimitive<String> storageIdWrapper) {
@ -297,46 +261,27 @@ public class DownloadController implements DownloadResource {
@Override
@SneakyThrows
public void downloadFileUsingOTT(@PathVariable(OTT) String oneTimeToken, @RequestParam(value = "tenantId") String tenantId) {
public ResponseEntity<FileSystemResource> downloadFileUsingOTT(@PathVariable(OTT) String oneTimeToken,
@RequestParam(value = "inline", required = false, defaultValue = FALSE) boolean inline,
@RequestParam(value = "tenantId") String tenantId) {
TenantContext.setTenantId(tenantId);
var token = oneTimeTokenDownloadService.getToken(oneTimeToken);
var requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
log.debug("downloadFileUsingOTT '{}'", oneTimeToken);
var token = oneTimeTokenDownloadService.getToken(oneTimeToken);
var downloadStatus = getDownloadStatus(token.getStorageId(), token.getUserId());
var fileDownloadStream = getFileForDownload(token.getStorageId(), token.getUserId());
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment" + "; filename*=utf-8''" + StringEncodingUtils.urlEncode(downloadStatus.getFilename()));
response.setHeader("Content-Length", String.valueOf(downloadStatus.getFileSize()));
TenantContext.clear();
return getResponseEntity(inline, fileDownloadStream, downloadStatus.getFilename(), MediaType.parseMediaType("application/zip"));
org.apache.commons.io.IOUtils.copyLarge(fileDownloadStream.getInputStream(), response.getOutputStream());
response.flushBuffer();
}
private DownloadRequest convert(PrepareDownloadRequest request) {
return DownloadRequest.builder()
.dossierId(request.getDossierId())
.userId(KeycloakSecurity.getUserId())
.fileIds(request.getFileIds())
.includeUnprocessed(request.getIncludeUnprocessed())
.build();
}
private String generateReportJsonStorageIdForS3(String storageId) {
return storageId.substring(0, storageId.length() - 3) + REPORT_INFO;
}
private String generateReportJsonStorageIdForAzure(String storageId) {
return storageId.substring(0, storageId.length() - 4) + REPORT_INFO;
return DownloadRequest.builder().dossierId(request.getDossierId()).userId(KeycloakSecurity.getUserId()).fileIds(request.getFileIds()).build();
}
}

View File

@ -1,80 +0,0 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_REDACTION_LOG;
import java.util.ArrayList;
import java.util.List;
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.mapper.EntityLogResponseMapper;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.EntityLogService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.EntityLogResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.FilteredEntityLogRequest;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class EntityLogController implements EntityLogResource {
private final EntityLogService entityLogService;
private final AccessControlService accessControlService;
private final EntityLogResponseMapper entityLogResponseMapper = EntityLogResponseMapper.INSTANCE;
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public EntityLogResponse getEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "excludedTypes", required = false, defaultValue = "") List<String> excludedTypes,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed) {
accessControlService.checkViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
return entityLogResponseMapper.toLogResponse(entityLogService.getEntityLog(dossierId,
fileId,
excludedTypes == null ? new ArrayList<>() : excludedTypes,
includeUnprocessed));
}
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public EntityLogResponse getFilteredEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody FilteredEntityLogRequest filteredEntityLogRequest) {
accessControlService.checkViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
return entityLogResponseMapper.toLogResponse(entityLogService.getFilteredEntityLog(dossierId, fileId, filteredEntityLogRequest));
}
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public EntityLogResponse getEntityLogWithEntriesOnPages(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "pageNumbers", defaultValue = "") List<Integer> pageNumbers) {
accessControlService.checkViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
return entityLogResponseMapper.toLogResponse(entityLogService.getEntityLogWithEntriesOnPages(dossierId, fileId, pageNumbers));
}
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public EntityLogResponse getEntityLogDeltaUpdate(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@PathVariable(ANALYSIS_NUMBER) Integer analysisNumber,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed) {
accessControlService.checkViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
return entityLogResponseMapper.toLogResponse(entityLogService.getEntityLogWithEntriesWithEntriesSinceAnalysisNumber(dossierId, fileId, analysisNumber, includeUnprocessed));
}
}

View File

@ -1,5 +1,29 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import java.time.OffsetDateTime;
import java.util.Map;
import java.util.stream.Collectors;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.iqser.red.commons.spring.ErrorMessage;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
@ -7,30 +31,10 @@ import com.iqser.red.service.persistence.management.v1.processor.exception.Confl
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.mchange.rmi.NotAuthorizedException;
import io.swagger.v3.oas.annotations.Hidden;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import java.time.OffsetDateTime;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@RequiredArgsConstructor
@RestControllerAdvice
@ -40,7 +44,6 @@ public class ExternalControllerAdvice {
private final Scheduler scheduler;
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.NOT_FOUND)
@ExceptionHandler(value = NotFoundException.class)
@ -53,7 +56,6 @@ public class ExternalControllerAdvice {
/* error handling */
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = BadRequestException.class)
@ -63,7 +65,6 @@ public class ExternalControllerAdvice {
}
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.CONFLICT)
@ExceptionHandler(value = {ConflictException.class})
@ -82,15 +83,6 @@ public class ExternalControllerAdvice {
}
@ResponseBody
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
@ExceptionHandler({NotAuthorizedException.class})
public ErrorMessage handleNotAuthorizedException(NotAuthorizedException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@ResponseBody
@ResponseStatus(value = HttpStatus.FORBIDDEN)
@ExceptionHandler({NotAllowedException.class})
@ -100,7 +92,6 @@ public class ExternalControllerAdvice {
}
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler({org.springframework.security.acls.model.NotFoundException.class})
@ -117,21 +108,17 @@ public class ExternalControllerAdvice {
}
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler({MethodArgumentNotValidException.class})
public ErrorMessage handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
var errorList = e.getFieldErrors();
String errorListAsString = errorList.stream()
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
.collect(Collectors.joining(", "));
String errorListAsString = errorList.stream().map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()).collect(Collectors.joining(", "));
return new ErrorMessage(OffsetDateTime.now(), String.format("You have empty/wrong formatted parameters: %s", errorListAsString));
}
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler({MissingServletRequestPartException.class})
@ -141,7 +128,6 @@ public class ExternalControllerAdvice {
}
@Hidden
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({HttpMessageNotReadableException.class})

View File

@ -13,9 +13,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.model.websocket.FileEventType;
import com.iqser.red.service.persistence.management.v1.processor.service.websocket.WebsocketService;
import com.iqser.red.service.persistence.management.v1.processor.utils.FileAttributeConfigMapper;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.FileAttributesGeneralConfigurationEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileAttributeConfigEntity;
@ -26,7 +23,7 @@ import com.iqser.red.service.persistence.management.v1.processor.service.FileAtt
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileAttributeConfigPersistenceService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.FileAttributesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttributes;
@ -36,7 +33,6 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileAttributeConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -50,7 +46,6 @@ public class FileAttributesController implements FileAttributesResource {
private final AuditPersistenceService auditPersistenceService;
private final FileStatusService fileStatusService;
private final AccessControlService accessControlService;
private final WebsocketService websocketService;
@Override
@ -61,23 +56,20 @@ public class FileAttributesController implements FileAttributesResource {
throw new BadRequestException("Invalid encoding setting");
}
fileAttributeConfigPersistenceService.setFileAttributesGeneralConfig(dossierTemplateId,
MagicConverter.convert(fileAttributesConfig, FileAttributesGeneralConfigurationEntity.class));
fileAttributeConfigPersistenceService.setFileAttributesConfig(dossierTemplateId,
MagicConverter.convert(fileAttributesConfig.getFileAttributeConfigs(), FileAttributeConfigEntity.class));
MagicConverter.convert(fileAttributesConfig, FileAttributesGeneralConfigurationEntity.class));
var result = fileAttributeConfigPersistenceService.setFileAttributesConfig(dossierTemplateId,
MagicConverter.convert(fileAttributesConfig.getFileAttributeConfigs(), FileAttributeConfigEntity.class));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Changed file attributes base configuration & attribute configuration ( CSV Import )")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Changed file attributes base configuration & attribute configuration ( CSV Import )")
.build());
return FileAttributesConfig.builder()
.filenameMappingColumnHeaderName(fileAttributesConfig.getFilenameMappingColumnHeaderName())
.delimiter(fileAttributesConfig.getDelimiter())
.encoding(fileAttributesConfig.getEncoding())
.fileAttributeConfigs(MagicConverter.convert(fileAttributeConfigPersistenceService.getFileAttributes(dossierTemplateId),
FileAttributeConfig.class,
new FileAttributeConfigMapper()))
.fileAttributeConfigs(MagicConverter.convert(result, FileAttributeConfig.class))
.build();
}
@ -85,21 +77,18 @@ public class FileAttributesController implements FileAttributesResource {
@Override
@PreAuthorize("hasAuthority('" + WRITE_FILE_ATTRIBUTES_CONFIG + "')")
public FileAttributeConfig addOrUpdateFileAttribute(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @Valid @RequestBody FileAttributeConfig fileAttribute) {
public FileAttributeConfig addOrUpdateFileAttribute(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody FileAttributeConfig fileAttribute) {
var result = fileAttributeConfigPersistenceService.addOrUpdateFileAttribute(dossierTemplateId, MagicConverter.convert(fileAttribute, FileAttributeConfigEntity.class));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("File attributes added/updated")
.details(Map.of("FileAttributeName",
fileAttribute.getLabel() != null ? fileAttribute.getLabel() : "",
"dossierTemplateId",
dossierTemplateId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("File attributes added/updated")
.details(Map.of("FileAttributeName", fileAttribute.getLabel() != null ? fileAttribute.getLabel() : "", "dossierTemplateId", dossierTemplateId))
.build());
return MagicConverter.convert(result, FileAttributeConfig.class, new FileAttributeConfigMapper());
return MagicConverter.convert(result, FileAttributeConfig.class);
}
@ -109,12 +98,12 @@ public class FileAttributesController implements FileAttributesResource {
fileAttributeConfigPersistenceService.deleteFileAttribute(fileAttributeId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("File attributes removed")
.details(Map.of("FileAttributeId", fileAttributeId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("File attributes removed")
.details(Map.of("FileAttributeId", fileAttributeId))
.build());
}
@ -124,12 +113,12 @@ public class FileAttributesController implements FileAttributesResource {
fileAttributeConfigPersistenceService.deleteFileAttributes(fileAttributeIds);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("File attributes removed")
.details(Map.of("FileAttributeId", fileAttributeIds))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("File attributes removed")
.details(Map.of("FileAttributeId", fileAttributeIds))
.build());
}
@ -140,7 +129,7 @@ public class FileAttributesController implements FileAttributesResource {
FileAttributesGeneralConfiguration generalConfig = new FileAttributesGeneralConfiguration();
try {
generalConfig = MagicConverter.convert(fileAttributeConfigPersistenceService.getFileAttributesGeneralConfiguration(dossierTemplateId),
FileAttributesGeneralConfiguration.class);
FileAttributesGeneralConfiguration.class);
} catch (Exception e) {
log.debug("No general config defined", e);
}
@ -148,7 +137,7 @@ public class FileAttributesController implements FileAttributesResource {
.filenameMappingColumnHeaderName(generalConfig.getFilenameMappingColumnHeaderName())
.delimiter(generalConfig.getDelimiter())
.encoding(generalConfig.getEncoding())
.fileAttributeConfigs(MagicConverter.convert(fileAttributeConfigs, FileAttributeConfig.class, new FileAttributeConfigMapper()))
.fileAttributeConfigs(MagicConverter.convert(fileAttributeConfigs, FileAttributeConfig.class))
.build();
}
@ -156,7 +145,6 @@ public class FileAttributesController implements FileAttributesResource {
@PreAuthorize("hasAuthority('" + WRITE_FILE_ATTRIBUTES + "')")
public void setFileAttributes(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody FileAttributes fileAttributes) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
var file = fileStatusService.getStatus(fileId);
if (file.getWorkflowStatus().equals(WorkflowStatus.APPROVED)) {
@ -167,13 +155,12 @@ public class FileAttributesController implements FileAttributesResource {
}
accessControlService.verifyUserIsMemberOrApprover(dossierId);
fileAttributesManagementService.setFileAttributes(dossierId, fileId, fileAttributes.getAttributeIdToValue());
websocketService.sendFileEvent(dossierId, fileId, FileEventType.UPDATE);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("File attributes has been edited for a document.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("File attributes has been edited for a document.")
.build());
}

View File

@ -6,12 +6,12 @@ import static com.iqser.red.service.persistence.management.v1.processor.roles.Ac
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import static com.iqser.red.service.persistence.management.v1.processor.utils.DownloadBufferUtils.fileProxyStreamForDownload;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -21,19 +21,16 @@ 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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Sets;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.client.pdftronredactionservice.PDFTronClient;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.InternalServerErrorException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.StorageIdUtils;
@ -44,10 +41,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.RotatePages
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
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.FileType;
import com.iqser.red.storage.commons.exception.StorageException;
import com.iqser.red.storage.commons.exception.StorageObjectDoesNotExist;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.knecon.fforesight.tenantcommons.TenantContext;
import feign.FeignException;
@ -70,8 +64,6 @@ public class FileManagementController implements FileManagementResource {
private final ReanalysisService reanalysisService;
private final StorageService storageService;
private final FileStatusManagementService fileStatusManagementService;
private final FileStatusService fileStatusService;
private final FileManagementStorageService fileManagementStorageService;
@Timed
@ -79,15 +71,14 @@ public class FileManagementController implements FileManagementResource {
@PreAuthorize("hasAuthority('" + DELETE_FILE + "')")
public void deleteFile(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
fileService.deleteFile(dossierId, fileId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("File has been deleted.")
.details(Map.of("fileId", fileId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("File has been deleted.")
.details(Map.of("fileId", fileId))
.build());
}
@ -96,18 +87,17 @@ public class FileManagementController implements FileManagementResource {
@PreAuthorize("hasAuthority('" + DELETE_FILE + "')")
public void deleteFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody List<String> fileIds) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
List<String> errorIds = new ArrayList<>();
for (String fileId : fileIds) {
try {
fileService.deleteFile(dossierId, fileId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Files have been deleted.")
.details(Map.of("Size", fileIds.size()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Files have been deleted.")
.details(Map.of("Size", fileIds.size()))
.build());
} catch (Exception e) {
errorIds.add(fileId);
}
@ -125,105 +115,53 @@ public class FileManagementController implements FileManagementResource {
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "inline", required = false, defaultValue = FALSE) boolean inline) {
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
return getResponseEntityForPDFDocument(fileId, dossierId, FileType.ORIGIN, inline);
}
@Timed
@Override
@PreAuthorize("hasAuthority('" + DOWNLOAD_ORIGINAL_FILE + "')")
public ResponseEntity<?> downloadViewerDocument(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "inline", required = false, defaultValue = FALSE) boolean inline) {
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
// Viewer Document Returns
if (storageService.objectExists(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, FileType.VIEWER_DOCUMENT))) {
return getResponseEntityForPDFDocument(fileId, dossierId, FileType.VIEWER_DOCUMENT, inline);
} else {
return getResponseEntityForPDFDocument(fileId, dossierId, FileType.ORIGIN, inline);
}
}
private ResponseEntity<?> getResponseEntityForPDFDocument(String fileId, String dossierId, FileType fileType, boolean inline) {
String storageId = StorageIdUtils.getStorageId(dossierId, fileId, fileType);
try {
var file = fileStatusManagementService.getFileStatus(fileId);
var pdfFileStream = fileManagementStorageService.getObject(TenantContext.getTenantId(), storageId);
return getResponseEntity(inline, pdfFileStream, file.getFilename());
} catch (StorageObjectDoesNotExist e) {
throw new NotFoundException(String.format("File \"%s\" not found!, the dossier with ID \"%s\" probably does not exist.", storageId, dossierId));
} catch (StorageException e) {
var untouchedFileStream = storageService.getObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, FileType.ORIGIN));
return getResponseEntity(inline, untouchedFileStream, file.getFilename(), MediaType.APPLICATION_PDF);
} catch (FeignException e) {
if (e.status() == HttpStatus.NOT_FOUND.value()) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
}
log.debug(e.getMessage(), e);
throw new InternalServerErrorException(e.getMessage());
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@SneakyThrows
private ResponseEntity<?> getResponseEntity(boolean inline, InputStream resource, String filename) {
private ResponseEntity<?> getResponseEntity(boolean inline, InputStreamResource resource, String filename, MediaType mediaType) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_PDF);
httpHeaders.setContentType(mediaType);
if (filename != null) {
httpHeaders.add(DOWNLOAD_HEADER_NAME, inline ? "inline" : "attachment" + "; filename*=utf-8''" + StringEncodingUtils.urlEncode(filename));
}
return new ResponseEntity<>(fileProxyStreamForDownload(resource), httpHeaders, HttpStatus.OK);
return new ResponseEntity<>(fileProxyStreamForDownload(resource.getInputStream()), httpHeaders, HttpStatus.OK);
}
@Override
@PreAuthorize("hasAuthority('" + DELETE_FILE + "')")
@Deprecated(forRemoval = true)
public void hardDeleteFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(FILE_IDS) Set<String> fileIds) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
for (String fileId : fileIds) {
if (fileStatusManagementService.getFileStatus(fileId).getAssignee() != null) {
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
}
}
fileService.hardDeleteFiles(dossierId, new ArrayList<>(fileIds));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Files has been hard deleted.")
.details(Map.of("FileIds", fileIds))
.build());
}
@Override
@PreAuthorize("hasAuthority('" + DELETE_FILE + "')")
@ResponseStatus(HttpStatus.ACCEPTED)
public void hardDeleteFiles(String dossierId, @RequestBody List<String> fileIds) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
fileIds.forEach(fileId -> {
try {
if (fileStatusManagementService.getFileStatus(fileId).getAssignee() != null) {
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
}
} catch (NotFoundException e) {
log.warn("File {} to be deleted was not found.", fileId);
}
});
fileService.hardDeleteFiles(dossierId, fileIds);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Files have been hard deleted.")
.details(Map.of("FileIds", fileIds))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Files has been hard deleted.")
.details(Map.of("FileIds", fileIds))
.build());
}
@ -231,16 +169,15 @@ public class FileManagementController implements FileManagementResource {
@PreAuthorize("hasAuthority('" + DELETE_FILE + "')")
public void restoreFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody Set<String> fileIds) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsDossierOwnerOrApproverOrAssignedReviewer(dossierId, fileIds);
verifyUserIsDossierOwnerOrApproverOrAssignedReviewer(dossierId, fileIds);
fileService.undeleteFiles(dossierId, fileIds);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Files has been restored.")
.details(Map.of("FileIds", fileIds))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Files has been restored.")
.details(Map.of("FileIds", fileIds))
.build());
}
@ -248,45 +185,55 @@ public class FileManagementController implements FileManagementResource {
@PreAuthorize("hasAuthority('" + ROTATE_PAGE + "')")
public void rotatePages(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RotatePagesRequest rotatePagesRequest) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsReviewer(dossierId, fileId);
try {
pdfTronClient.rotate(com.iqser.red.service.pdftron.redaction.v1.api.model.RotatePagesRequest.builder()
.dossierId(dossierId)
.fileId(fileId)
.pages(rotatePagesRequest.getPages())
.build());
fileService.deleteViewerDocument(dossierId, fileId);
.dossierId(dossierId)
.fileId(fileId)
.pages(rotatePagesRequest.getPages())
.build());
fileStatusManagementService.updateFileModificationDate(fileId);
FileModel fileModel = fileStatusManagementService.getFileStatus(fileId);
if (!fileModel.isExcludedFromAutomaticAnalysis()) {
// Re- ocr is files with pdftron ocr service is breaking files and not needed with azure.
// TODO find a better solution for this.
// if (fileModel.getOcrStartTime() != null || fileModel.getOcrEndTime() != null) {
// reanalysisService.ocrFile(dossierId, fileId, true);
// } else {
fileStatusService.setStatusFullReprocess(dossierId, fileId, true, true, false);
// }
if (fileModel.getOcrStartTime() != null || fileModel.getOcrEndTime() != null) {
reanalysisService.ocrFile(dossierId, fileId, true);
} else {
reanalysisService.reanalyzeFiles(dossierId, Sets.newHashSet(fileId), true);
}
}
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("Pages have been rotated.")
.details(Map.of("Pages", rotatePagesRequest.getPages().keySet()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("Pages have been rotated.")
.details(Map.of("Pages", rotatePagesRequest.getPages().keySet()))
.build());
} catch (FeignException e) {
throw processFeignException(e);
}
}
private void verifyUserIsDossierOwnerOrApproverOrAssignedReviewer(String dossierId, Set<String> fileIds) {
try {
accessControlService.verifyUserIsDossierOwnerOrApprover(dossierId);
} catch (AccessDeniedException e1) {
try {
for (String fileId : fileIds) {
accessControlService.verifyUserIsReviewer(dossierId, fileId);
}
} catch (NotAllowedException e2) {
throw new NotAllowedException("User must be dossier owner, approver or assigned reviewer of the file.");
}
}
}
}

View File

@ -7,19 +7,16 @@ import static com.iqser.red.service.persistence.management.v1.processor.roles.Ac
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import static com.iqser.red.service.persistence.management.v1.processor.utils.StorageIdUtils.getStorageId;
import java.io.InputStream;
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.RestController;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.Highlights;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.TextHighlightConversionOperation;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.TextHighlightConversionRequest;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.TextHighlights;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.HighlightsResource;
@ -42,25 +39,20 @@ public class HighlightsController implements HighlightsResource {
private final AccessControlService accessControlService;
private final FileStatusService fileStatusService;
private final ReanalysisService reanalysisService;
private final FileManagementStorageService fileManagementStorageService;
@SneakyThrows
@PreAuthorize("hasAuthority('" + GET_HIGHLIGHTS + "')")
public TextHighlights getHighlights(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
public Highlights getHighlights(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
fileStatusService.getStatus(fileId);
if (storageService.objectExists(TenantContext.getTenantId(), getStorageId(dossierId, fileId, FileType.TEXT_HIGHLIGHTS))) {
try (InputStream stream = fileManagementStorageService.getObject(TenantContext.getTenantId(), getStorageId(dossierId, fileId, FileType.TEXT_HIGHLIGHTS))) {
TextHighlights highlights = objectMapper.readValue(stream, TextHighlights.class);
stream.close();
return highlights;
}
return objectMapper.readValue(storageService.getObject(TenantContext.getTenantId(), getStorageId(dossierId, fileId, FileType.TEXT_HIGHLIGHTS)).getInputStream(),
Highlights.class);
}
return new TextHighlights();
return new Highlights();
}
@ -68,7 +60,6 @@ public class HighlightsController implements HighlightsResource {
public void convertHighlights(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody AnnotationIds annotationIds) {
try {
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
@ -83,7 +74,6 @@ public class HighlightsController implements HighlightsResource {
public void deleteHighlights(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody AnnotationIds annotationIds) {
try {
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
@ -99,7 +89,6 @@ public class HighlightsController implements HighlightsResource {
public void deleteImportedRedactions(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody AnnotationIds annotationIds) {
try {
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);

View File

@ -1,58 +0,0 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.GET_SIMILAR_IMAGES;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.commons.spring.ErrorMessage;
import com.iqser.red.service.persistence.management.v1.processor.service.ImageSimilarityService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.ImageSimilaritySearchResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.image.ImageSimilaritySearchRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.image.ImageSimilaritySearchResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ImageDocument;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequiredArgsConstructor
@Slf4j
public class ImageSimilaritySearchController implements ImageSimilaritySearchResource {
private final ImageSimilarityService imageSimilarityService;
@SneakyThrows
@PreAuthorize("hasAuthority('" + GET_SIMILAR_IMAGES + "')")
public ResponseEntity getSimilarImages(@RequestBody ImageSimilaritySearchRequest imageSimilaritySearchRequest) {
log.info("received similiar image search request {}", imageSimilaritySearchRequest);
if (imageSimilaritySearchRequest.getAnnotationId().isEmpty() || (imageSimilaritySearchRequest.getScope().getFileId().isEmpty() && imageSimilaritySearchRequest.getScope()
.getTemplateId()
.isEmpty() && imageSimilaritySearchRequest.getScope().getDossierId().isEmpty())) {
return new ResponseEntity<>(new ErrorMessage(OffsetDateTime.now(), "Required parameter missing"), HttpStatus.BAD_REQUEST);
}
List<ImageDocument> similarImages = this.imageSimilarityService.findSimilarImages(imageSimilaritySearchRequest.getAnnotationId(),
imageSimilaritySearchRequest.getDistance(),
imageSimilaritySearchRequest.getScope());
List<String> similarImagesIds = new ArrayList<>();
List<Byte> similarImagesThumbnails = new ArrayList<>();
similarImages.stream()
.forEach(doc -> {
similarImagesIds.add(doc.getImageId());
similarImagesThumbnails.add(doc.getThumbnail());
});
ImageSimilaritySearchResponse imageSimilaritySearchResponse = new ImageSimilaritySearchResponse(similarImagesIds, similarImagesThumbnails);
return new ResponseEntity<>(imageSimilaritySearchResponse, HttpStatus.OK);
}
}

View File

@ -5,21 +5,22 @@ import static com.iqser.red.service.persistence.management.v1.processor.roles.Ac
import java.util.List;
import jakarta.transaction.Transactional;
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.RestController;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.LegalBasisMappingPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.LegalBasisMappingResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.legalbasis.LegalBasis;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
@RestController
@ -32,15 +33,15 @@ public class LegalBasisMappingController implements LegalBasisMappingResource {
@Override
@PreAuthorize("hasAuthority('" + WRITE_LEGAL_BASIS + "')")
public void deleteLegalBasis(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody List<String> legalBasisTechnicalNames) {
public void deleteLegalBasis(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody List<String> legalBasisNames) {
legalBasisMappingPersistenceService.deleteLegalBasis(dossierTemplateId, legalBasisTechnicalNames);
legalBasisMappingPersistenceService.deleteLegalBasis(dossierTemplateId, legalBasisNames);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Legal basis mapping has been changed.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Legal basis mapping has been changed.")
.build());
}
@ -50,11 +51,11 @@ public class LegalBasisMappingController implements LegalBasisMappingResource {
legalBasisMappingPersistenceService.addOrUpdateLegalBasis(dossierTemplateId, legalBasis);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Legal basis mapping has been changed.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Legal basis mapping has been changed.")
.build());
}
@ -63,14 +64,13 @@ public class LegalBasisMappingController implements LegalBasisMappingResource {
legalBasisMappingPersistenceService.setLegalBasisMapping(dossierTemplateId, legalBasisMapping);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Legal basis mapping has been changed.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Legal basis mapping has been changed.")
.build());
}
@Transactional
@PreAuthorize("hasAuthority('" + READ_LEGAL_BASIS + "')")
public List<LegalBasis> getLegalBasisMapping(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {

View File

@ -2,14 +2,19 @@ package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_LICENSE;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.UPDATE_LICENSE;
import static com.iqser.red.service.persistence.management.v1.processor.service.LicenseUtilityService.*;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.service.FileFormatValidationService;
import com.iqser.red.service.persistence.management.v1.processor.service.LicenseUtilityService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.LicenseResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.Feature;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.FeatureType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.License;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.RedactionLicenseModel;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.tenantcommons.TenantContext;
@ -23,9 +28,18 @@ import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
public class LicenseController implements LicenseResource {
private static final String LICENSE_OBJECT_ID = "license/redact-manager-license.json";
private static final String DEMO_PDFTRON_LICENSE = "demo:1650351709282:7bd235e003000000004ec28a6743e1163a085e2115de2536ab6e2cfe5a";
private static final String LICENSE_CUSTOMER = "LICENSE_CUSTOMER";
private static final String LICENSE_START = "LICENSE_START";
private static final String LICENSE_END = "LICENSE_END";
private static final String LICENSE_PAGE_COUNT = "LICENSE_PAGE_COUNT";
private static final String PDFTRON_FRONTEND_LICENSE = "PDFTRON_FRONTEND_LICENSE";
private static final String PDFTRON_FEATURE = "pdftron";
private static final String PROCESSING_PAGES_FEATURE = "processingPages";
private static final String DEFAULT_PROCESSING_PAGES = "200000";
private final StorageService storageService;
private final LicenseUtilityService licenseUtilityService;
private final FileFormatValidationService fileFormatValidationService;
private final Environment environment;
@Override
@ -34,7 +48,6 @@ public class LicenseController implements LicenseResource {
public void updateLicense(RedactionLicenseModel licenseModel) {
storageService.storeJSONObject(TenantContext.getTenantId(), LICENSE_OBJECT_ID, licenseModel);
fileFormatValidationService.evictValidFileFormatsForTenant(TenantContext.getTenantId());
}
@ -42,16 +55,80 @@ public class LicenseController implements LicenseResource {
@PreAuthorize("hasAuthority('" + READ_LICENSE + "')")
public RedactionLicenseModel getLicense() {
String tenantId = TenantContext.getTenantId();
if (storageService.objectExists(tenantId, LICENSE_OBJECT_ID)) {
if (storageService.objectExists(TenantContext.getTenantId(), LICENSE_OBJECT_ID)) {
try {
return storageService.readJSONObject(tenantId, LICENSE_OBJECT_ID, RedactionLicenseModel.class);
return storageService.readJSONObject(TenantContext.getTenantId(), LICENSE_OBJECT_ID, RedactionLicenseModel.class);
} catch (Exception e) {
return licenseUtilityService.generateDemoLicense();
return generateDemoLicense();
}
} else {
return licenseUtilityService.generateDemoLicense();
return generateDemoLicense();
}
}
private RedactionLicenseModel generateDemoLicense() {
License demo = new License();
demo.setId("RedactManager");
demo.setName("RedactManager License");
demo.setProduct("RedactManager");
var licensedTo = environment.getProperty(LICENSE_CUSTOMER, "RedactManager Demo License");
demo.setLicensedTo(licensedTo);
var licenseStartDate = parseDate(environment.getProperty(LICENSE_START));
var start = licenseStartDate != null ? licenseStartDate : OffsetDateTime.now().withDayOfYear(1).withHour(0).withMinute(0).withSecond(0).truncatedTo(ChronoUnit.SECONDS);
demo.setValidFrom(start);
var licenseEndDate = parseDate(environment.getProperty(LICENSE_END));
var end = licenseEndDate != null ? licenseEndDate : OffsetDateTime.now()
.withMonth(12)
.withDayOfMonth(31)
.withHour(0)
.withMinute(0)
.withSecond(0)
.truncatedTo(ChronoUnit.SECONDS);
demo.setValidUntil(end);
var pdftronLicense = environment.getProperty(PDFTRON_FRONTEND_LICENSE, DEMO_PDFTRON_LICENSE);
demo.getFeatures().add(Feature.builder().name(PDFTRON_FEATURE).type(FeatureType.STRING).value(pdftronLicense).build());
var pageCount = Long.parseLong(environment.getProperty(LICENSE_PAGE_COUNT, DEFAULT_PROCESSING_PAGES));
demo.getFeatures().add(Feature.builder().name(PROCESSING_PAGES_FEATURE).type(FeatureType.NUMBER).value(pageCount).build());
var demoLicense = new RedactionLicenseModel();
demoLicense.setActiveLicense("RedactManager");
demoLicense.getLicenses().add(demo);
return demoLicense;
}
private OffsetDateTime parseDate(String date) {
if (StringUtils.isEmpty(date)) {
return null;
}
var parts = date.split("-");
if (parts.length != 3) {
return null;
}
try {
return OffsetDateTime.now()
.withYear(Integer.parseInt(parts[2]))
.withMonth(Integer.parseInt(parts[1]))
.withDayOfMonth(Integer.parseInt(parts[0]))
.withHour(0)
.withMinute(0)
.withSecond(0)
.truncatedTo(ChronoUnit.SECONDS);
} catch (Exception e) {
log.debug("Failed to parse date: {}", date);
return null;
}
}
}

View File

@ -33,11 +33,11 @@ public class LicenseReportController implements LicenseReportResource {
LicenseReport licenseReport = licenseReportService.getLicenseReport(reportRequest);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(LICENSE_AUDIT_KEY)
.category(AuditCategory.LICENSE.name())
.message("License report has been viewed.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(LICENSE_AUDIT_KEY)
.category(AuditCategory.LICENSE.name())
.message("License report has been viewed.")
.build());
return licenseReport;
}

View File

@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.NotificationResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.NotificationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
@ -35,48 +35,38 @@ public class NotificationController implements NotificationResource {
return JSONPrimitive.of(notificationPersistenceService.hasNewNotificationsSince(KeycloakSecurity.getUserId(), since.getValue()));
}
@PreAuthorize("hasAuthority('" + UPDATE_NOTIFICATIONS + "')")
public void toggleNotificationSeen(@RequestBody List<String> notificationIds, @RequestParam(SET_SEEN_PARAM) boolean setSeen) {
notificationIds.stream()
.map(Long::valueOf)
.forEach(notificationId -> {
if (setSeen) {
notificationPersistenceService.setSeenDate(KeycloakSecurity.getUserId(), notificationId, OffsetDateTime.now());
} else {
notificationPersistenceService.setSeenDate(KeycloakSecurity.getUserId(), notificationId, null);
}
});
notificationIds.stream().map(Long::valueOf).forEach(notificationId -> {
if (setSeen) {
notificationPersistenceService.setSeenDate(KeycloakSecurity.getUserId(), notificationId, OffsetDateTime.now());
} else {
notificationPersistenceService.setSeenDate(KeycloakSecurity.getUserId(), notificationId, null);
}
});
}
@PreAuthorize("hasAuthority('" + UPDATE_NOTIFICATIONS + "')")
public void toggleNotificationRead(@RequestBody List<String> notificationIds, @RequestParam(SET_READ_PARAM) boolean setRead) {
notificationIds.stream()
.map(Long::valueOf)
.forEach(notificationId -> {
if (setRead) {
notificationPersistenceService.setReadDate(KeycloakSecurity.getUserId(), notificationId, OffsetDateTime.now());
} else {
notificationPersistenceService.setReadDate(KeycloakSecurity.getUserId(), notificationId, null);
}
});
notificationIds.stream().map(Long::valueOf).forEach(notificationId -> {
if (setRead) {
notificationPersistenceService.setReadDate(KeycloakSecurity.getUserId(), notificationId, OffsetDateTime.now());
} else {
notificationPersistenceService.setReadDate(KeycloakSecurity.getUserId(), notificationId, null);
}
});
}
@PreAuthorize("hasAuthority('" + UPDATE_NOTIFICATIONS + "')")
public void delete(@RequestBody List<String> notificationIds) {
notificationIds.stream()
.map(Long::valueOf)
.forEach(notificationId -> {
notificationPersistenceService.softDelete(KeycloakSecurity.getUserId(), notificationId);
});
notificationIds.stream().map(Long::valueOf).forEach(notificationId -> {
notificationPersistenceService.softDelete(KeycloakSecurity.getUserId(), notificationId);
});
}
@PreAuthorize("hasAuthority('" + READ_NOTIFICATIONS + "')")
public NotificationResponse getNotifications(@RequestParam(INCLUDE_SEEN_PARAM) boolean includeSeen) {

View File

@ -3,15 +3,15 @@ package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_NOTIFICATIONS;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.UPDATE_NOTIFICATIONS;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.service.NotificationPreferencesService;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPreferencesPersistenceService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.NotificationPreferencesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.notification.NotificationPreferences;
@ -21,15 +21,13 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class NotificationPreferencesController implements NotificationPreferencesResource {
private final NotificationPreferencesService notificationPreferencesService;
private final NotificationPreferencesPersistenceService notificationPreferencesPersistenceService;
@Override
@PreAuthorize("hasAuthority('" + READ_NOTIFICATIONS + "')")
public NotificationPreferences getNotificationPreferences() {
return notificationPreferencesService.getOrCreateNotificationPreferences(KeycloakSecurity.getUserId());
return notificationPreferencesPersistenceService.getOrCreateNotificationPreferences(KeycloakSecurity.getUserId());
}

View File

@ -0,0 +1,138 @@
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.Map;
import java.util.stream.Collectors;
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.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionreportservice.RssReportClient;
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.RSSResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
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.DetailedRSSResponse;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class RSSController implements RSSResource {
private final RssReportClient rssReportClient;
private final ComponentOverrideService componentOverrideService;
private final AuditPersistenceService auditPersistenceService;
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public RSSResponse getRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId) {
return convert(rssReportClient.getRSS(dossierId, fileId));
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
private RSSResponse convert(com.iqser.red.service.redaction.report.v1.api.model.rss.RSSResponse rssResponse) {
return new RSSResponse(rssResponse.getFiles().stream().map(this::convert).collect(Collectors.toList()));
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
private RSSFileResponse convert(com.iqser.red.service.redaction.report.v1.api.model.rss.RSSFileResponse rssFileResponse) {
return new RSSFileResponse(rssFileResponse.getFilename(), rssFileResponse.getResult());
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public DetailedRSSResponse getDetailedRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId) {
return rssReportClient.getDetailedRSS(dossierId, fileId);
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void addOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ComponentsOverrides componentsOverrides) {
var rssReport = rssReportClient.getDetailedRSS(dossierId, fileId);
var components = rssReport.getFiles().get(0).getResult();
componentOverrideService.addOverrides(dossierId, fileId, componentsOverrides);
componentsOverrides.getComponentOverrides()
.forEach((key, value) -> 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",
key,
"Action",
"MODIFY",
"OriginalValue",
components.get(key).getOriginalValue(),
"OldValue",
components.get(key).getValue() != null ? components.get(key).getValue() : components.get(key).getOriginalValue(),
"NewValue",
value))
.build()));
}
@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 rssReport = rssReportClient.getDetailedRSS(dossierId, fileId);
var components = rssReport.getFiles().get(0).getResult();
componentOverrideService.revertOverrides(dossierId, fileId, revertOverrideRequest);
revertOverrideRequest.getComponents()
.forEach(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",
component,
"Action",
"REVERT",
"OriginalValue",
components.get(component).getOriginalValue(),
"OldValue",
components.get(component).getValue() != null ? components.get(component).getValue() : components.get(component).getOriginalValue(),
"NewValue",
components.get(component).getOriginalValue()))
.build()));
}
}

View File

@ -8,6 +8,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
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.RequestBody;
@ -41,38 +42,40 @@ public class ReanalysisController implements ReanalysisResource {
private final AuditPersistenceService auditPersistenceService;
private final AccessControlService accessControlService;
@PreAuthorize("hasAuthority('" + REANALYZE_DOSSIER + "')")
public void reanalyzeDossier(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
} catch (AccessDeniedException e) {
throw new NotFoundException("Object not found");
}
accessControlService.verifyUserHasAccessPermissions(dossierId);
reanalysisService.reanalyzeDossier(dossierId, force);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Reanalyse dossier was triggered")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Reanalyse dossier was triggered")
.build());
}
@PreAuthorize("hasAuthority('" + REANALYZE_FILE + "')")
public void reanalyzeFile(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
reanalysisService.reanalyzeFiles(dossierId, Sets.newHashSet(fileId), force);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("Reanalyse file was triggered")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("Reanalyse file was triggered")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
}
@ -82,16 +85,15 @@ public class ReanalysisController implements ReanalysisResource {
@RequestBody List<String> fileIds,
@RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
reanalysisService.reanalyzeFiles(dossierId, new HashSet<>(fileIds), force);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("Reanalyse files was triggered")
.details(Map.of(DOSSIER_ID, dossierId, "number", fileIds.size()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("Reanalyse files was triggered")
.details(Map.of(DOSSIER_ID, dossierId, "number", fileIds.size()))
.build());
}
@ -100,16 +102,21 @@ public class ReanalysisController implements ReanalysisResource {
@PreAuthorize("hasAuthority('" + REANALYZE_DOSSIER + "')")
public void ocrDossier(@PathVariable(DOSSIER_ID) String dossierId) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
} catch (AccessDeniedException e) {
throw new NotFoundException("Object not found");
}
accessControlService.verifyUserHasAccessPermissions(dossierId);
reanalysisService.ocrDossier(dossierId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("OCR and reanalyse dossier was triggered")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("OCR and reanalyse dossier was triggered")
.build());
}
@ -118,19 +125,17 @@ public class ReanalysisController implements ReanalysisResource {
@PreAuthorize("hasAuthority('" + REANALYZE_FILE + "')")
public void ocrFile(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force,
@RequestParam(value = ALL_PAGES, required = false, defaultValue = FALSE) boolean allPages) {
@RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
validateOCR(dossierId, fileId);
reanalysisService.ocrFile(dossierId, fileId, force, allPages);
reanalysisService.ocrFile(dossierId, fileId, force);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("OCR and reanalyse file was triggered")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("OCR and reanalyse file was triggered")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
}
@ -139,35 +144,32 @@ public class ReanalysisController implements ReanalysisResource {
@PreAuthorize("hasAuthority('" + REANALYZE_FILE + "')")
public void ocrFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody Set<String> fileIds) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
fileIds.forEach(fileId -> validateOCR(dossierId, fileId));
reanalysisService.ocrFiles(dossierId, fileIds, false);
reanalysisService.ocrFiles(dossierId, fileIds);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("OCR and reanalyse was triggered")
.details(Map.of(DOSSIER_ID, dossierId, "number", fileIds.size()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("OCR and reanalyse was triggered")
.details(Map.of(DOSSIER_ID, dossierId, "number", fileIds.size()))
.build());
}
@PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_FILE + "')")
public void toggleAutomaticAnalysis(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(EXCLUDED_STATUS_PARAM) boolean excludedFromAutomaticAnalysis) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsReviewer(dossierId, fileId);
fileStatusManagementService.toggleAutomaticAnalysis(dossierId, fileId, excludedFromAutomaticAnalysis);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Toggle Exclusion status: File excluded from automatic analysis: " + excludedFromAutomaticAnalysis)
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Toggle Exclusion status: File excluded from automatic analysis: " + excludedFromAutomaticAnalysis)
.build());
}
@ -178,7 +180,6 @@ public class ReanalysisController implements ReanalysisResource {
@PathVariable(FILE_ID) String fileId,
@RequestParam(name = EXCLUDED_STATUS_PARAM, required = false, defaultValue = "false") boolean excluded) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
var status = fileStatusManagementService.getFileStatus(fileId);
if (!(status.getAssignee() == null && status.isExcluded())) { // Needed to include documents after 3.0 migration.
accessControlService.verifyUserIsReviewer(dossierId, fileId);
@ -186,15 +187,14 @@ public class ReanalysisController implements ReanalysisResource {
fileStatusManagementService.toggleExclusion(dossierId, fileId, excluded);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Toggle Exclusion status: File excluded from analysis: " + excluded)
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Toggle Exclusion status: File excluded from analysis: " + excluded)
.build());
}
@PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_FILE + "')")
public void toggleAutomaticAnalysisForList(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody Set<String> fileIds,
@ -213,7 +213,6 @@ public class ReanalysisController implements ReanalysisResource {
}
}
@PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_FILE + "')")
public void toggleExclusionForList(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody Set<String> fileIds,
@ -248,11 +247,11 @@ public class ReanalysisController implements ReanalysisResource {
fileStatusManagementService.excludePages(dossierId, fileId, excludedPages);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Page exclusions added for file")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Page exclusions added for file")
.build());
}
@ -272,11 +271,11 @@ public class ReanalysisController implements ReanalysisResource {
fileStatusManagementService.includePages(dossierId, fileId, includePages);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Page inclusions added for file")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Page inclusions added for file")
.build());
}
@ -285,15 +284,14 @@ public class ReanalysisController implements ReanalysisResource {
@RequestParam(value = "dropIndex", required = false, defaultValue = FALSE) boolean dropIndex,
@RequestBody List<String> fileIds) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
reanalysisService.reindex(dossierId, dropIndex, new HashSet<>(fileIds));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId("redaction")
.category(AuditCategory.INDEX.name())
.message("Reindexing has been triggered" + (dropIndex ? " (with drop index)." : "."))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId("redaction")
.category(AuditCategory.INDEX.name())
.message("Reindexing has been triggered" + (dropIndex ? " (with drop index)." : "."))
.build());
}
@ -301,9 +299,6 @@ public class ReanalysisController implements ReanalysisResource {
private void validateOCR(String dossierId, String fileId) {
var status = fileStatusManagementService.getFileStatus(fileId);
if (status.isSoftOrHardDeleted()) {
throw new NotFoundException("File does not exist");
}
if (status.getWorkflowStatus() == WorkflowStatus.APPROVED) {
throw new BadRequestException("Cannot OCR approved file");
}

View File

@ -5,6 +5,7 @@ import static com.iqser.red.service.persistence.management.v1.processor.service.
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@ -14,15 +15,20 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.RedactionLogService;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DocumentResource;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RedactionLogResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.FilteredRedactionLogRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid;
import com.iqser.red.storage.commons.exception.StorageObjectDoesNotExist;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.tenantcommons.TenantContext;
import feign.FeignException;
@ -31,23 +37,33 @@ import lombok.SneakyThrows;
@RestController
@RequiredArgsConstructor
public class DocumentController implements DocumentResource {
public class RedactionLogController implements RedactionLogResource {
private final RedactionLogService redactionLogService;
private final StorageService storageService;
private final FileStatusService fileStatusService;
private final FileManagementStorageService fileManagementStorageService;
private final AccessControlService accessControlService;
@SneakyThrows
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public ResponseEntity<?> getDocumentText(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
// check access to resources and check for deletion
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
public RedactionLog getRedactionLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "excludedType", required = false) List<String> excludedTypes,
@RequestParam(value = "withManualRedactions", required = false, defaultValue = "true") boolean withManualRedactions,
@RequestParam(value = "includeFalsePositives", required = false, defaultValue = "false") boolean includeFalsePositives) {
try {
return buildZipFileResponseEntity(fileId, dossierId, FileType.DOCUMENT_TEXT);
return redactionLogService.getRedactionLog(dossierId, fileId, excludedTypes, withManualRedactions, includeFalsePositives);
} catch (FeignException e) {
throw processFeignException(e);
}
}
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public SectionGrid getSectionGrid(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
try {
return redactionLogService.getSectionGrid(dossierId, fileId);
} catch (FeignException e) {
throw processFeignException(e);
}
@ -56,44 +72,25 @@ public class DocumentController implements DocumentResource {
@SneakyThrows
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public ResponseEntity<?> getDocumentPositions(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
// check access to resources and check for deletion
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
public ResponseEntity<?> getSectionText(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
try {
return buildZipFileResponseEntity(fileId, dossierId, FileType.DOCUMENT_POSITION);
} catch (FeignException e) {
throw processFeignException(e);
}
}
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.parseMediaType("application/zip"));
@SneakyThrows
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public ResponseEntity<?> getDocumentStructure(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
var fileStatus = fileStatusService.getStatus(fileId);
String filename = fileStatus.getFilename();
if (filename != null) {
var index = filename.lastIndexOf(".");
String prefix = filename.substring(0, index);
filename = prefix + ".json";
httpHeaders.add("Content-Disposition", "attachment; filename=" + prefix + ".zip");
}
// check access to resources and check for deletion
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
try {
return buildZipFileResponseEntity(fileId, dossierId, FileType.DOCUMENT_STRUCTURE);
} catch (FeignException e) {
throw processFeignException(e);
}
}
byte[] zipBytes = getZippedBytes(dossierId, fileId, filename, FileType.TEXT);
return new ResponseEntity<>(zipBytes, httpHeaders, HttpStatus.OK);
@SneakyThrows
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public ResponseEntity<?> getDocumentPages(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
// check access to resources and check for deletion
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
try {
return buildZipFileResponseEntity(fileId, dossierId, FileType.DOCUMENT_PAGES);
} catch (FeignException e) {
throw processFeignException(e);
}
@ -104,10 +101,6 @@ public class DocumentController implements DocumentResource {
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public ResponseEntity<?> getSimplifiedSectionText(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
// check access to resources and check for deletion
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
try {
HttpHeaders httpHeaders = new HttpHeaders();
@ -133,23 +126,19 @@ public class DocumentController implements DocumentResource {
}
private ResponseEntity<byte[]> buildZipFileResponseEntity(String fileId, String dossierId, FileType fileType) throws IOException {
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public RedactionLog getFilteredRedactionLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody FilteredRedactionLogRequest filteredRedactionLogRequest) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.parseMediaType("application/zip"));
try {
var fileStatus = fileStatusService.getStatus(fileId);
String filename = fileStatus.getFilename();
if (filename != null) {
var index = filename.lastIndexOf(".");
String prefix = filename.substring(0, index);
filename = prefix + ".json";
httpHeaders.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=utf-8''" + StringEncodingUtils.urlEncode(prefix) + ".zip");
return redactionLogService.getFilteredRedactionLog(dossierId, fileId, filteredRedactionLogRequest);
} catch (FeignException e) {
throw processFeignException(e);
}
byte[] zipBytes = getZippedBytes(dossierId, fileId, filename, fileType);
httpHeaders.setContentLength(zipBytes.length);
return new ResponseEntity<>(zipBytes, httpHeaders, HttpStatus.OK);
}
@ -157,11 +146,10 @@ public class DocumentController implements DocumentResource {
try {
String objectId = dossierId + "/" + fileId + "." + fileType.name() + fileType.getExtension();
var inputStreamResource = storageService.getObject(TenantContext.getTenantId(), objectId);
try (var inputStream = fileManagementStorageService.getObject(TenantContext.getTenantId(), objectId)) {
byte[] input = inputStream.readAllBytes();
inputStream.close();
return zipBytes(filename, input);
try (var inputStream = inputStreamResource.getInputStream()) {
return zipBytes(filename, inputStream.readAllBytes());
}
} catch (StorageObjectDoesNotExist e) {

View File

@ -13,7 +13,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
@ -28,17 +27,17 @@ import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionreportservice.PlaceholderClient;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionreportservice.ReportTemplatePlaceholderClient;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.ReportTemplateService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ReportTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.service.v1.api.external.resource.ReportTemplateResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
@ -50,8 +49,6 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.ReportTemplateUploadRequest;
import com.iqser.red.storage.commons.exception.StorageObjectDoesNotExist;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.knecon.fforesight.tenantcommons.TenantContext;
import feign.FeignException;
@ -61,20 +58,16 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
@SuppressWarnings("PMD")
public class ReportTemplateController implements ReportTemplateResource {
private final ReportTemplatePlaceholderClient reportTemplatePlaceholderClient;
private final PlaceholderClient placeholderClient;
private final AuditPersistenceService auditPersistenceService;
private final DossierAttributeConfigPersistenceService dossierAttributeConfigPersistenceService;
private final DossierTemplatePersistenceService dossierTemplatePersistenceService;
private final FileAttributeConfigPersistenceService fileAttributeConfigPersistenceService;
private final StorageService storageService;
private final ReportTemplatePersistenceService reportTemplatePersistenceService;
private final ReportTemplateService reportTemplateService;
private final FileManagementStorageService fileManagementStorageService;
@Override
@ -99,24 +92,21 @@ public class ReportTemplateController implements ReportTemplateResource {
try {
if (file.getOriginalFilename() != null) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
var cleanupName = FilenameUtils.getName(file.getOriginalFilename());
ReportTemplateUploadRequest reportTemplateUploadRequest = ReportTemplateUploadRequest.builder()
.template(file.getBytes())
.fileName(cleanupName)
.fileName(file.getOriginalFilename())
.dossierTemplateId(dossierTemplateId)
.activeByDefault(activeByDefault)
.multiFileReport(multiFileReport)
.build();
var reportTemplate = reportTemplateService.uploadTemplate(reportTemplateUploadRequest);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(reportTemplate.getTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Report template was uploaded.")
.details(Map.of("DossierTemplateId", dossierTemplateId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(reportTemplate.getTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Report template was uploaded.")
.details(Map.of("DossierTemplateId", dossierTemplateId))
.build());
return reportTemplate;
} else {
throw new BadRequestException("Could not upload file, no filename provided.");
@ -142,20 +132,18 @@ public class ReportTemplateController implements ReportTemplateResource {
try {
var reportTemplate = reportTemplatePersistenceService.find(templateId);
InputStream inputStream = fileManagementStorageService.getObject(TenantContext.getTenantId(), reportTemplate.getStorageId());
byte[] file = IOUtils.toByteArray(inputStream);
inputStream.close();
return getResponseEntity(file, reportTemplate.getFileName());
byte[] file = IOUtils.toByteArray(storageService.getObject(TenantContext.getTenantId(), reportTemplate.getStorageId()).getInputStream());
return getResponseEntity(file, reportTemplate.getFileName(), MediaType.APPLICATION_OCTET_STREAM);
} catch (StorageObjectDoesNotExist | IOException e) {
throw new NotFoundException("Template does not exist");
}
}
private ResponseEntity<?> getResponseEntity(byte[] file, String filename) {
private ResponseEntity<?> getResponseEntity(byte[] file, String filename, MediaType mediaType) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
httpHeaders.setContentType(mediaType);
if (filename != null) {
httpHeaders.add("Content-Disposition", "attachment; filename*=utf-8''" + StringEncodingUtils.urlEncode(filename));
@ -174,14 +162,13 @@ public class ReportTemplateController implements ReportTemplateResource {
var storageId = reportTemplatePersistenceService.find(templateId).getStorageId();
storageService.deleteObject(TenantContext.getTenantId(), storageId);
reportTemplatePersistenceService.delete(templateId);
reportTemplatePlaceholderClient.evictReportTemplateCache();
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(templateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Report template was deleted.")
.details(Map.of("DossierTemplateId", dossierTemplateId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(templateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Report template was deleted.")
.details(Map.of("DossierTemplateId", dossierTemplateId))
.build());
}

View File

@ -2,6 +2,7 @@ package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_RULES;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_RULES;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -16,27 +17,19 @@ import org.springframework.http.ResponseEntity;
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.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionservice.RedactionClient;
import com.iqser.red.service.persistence.management.v1.processor.exception.FileUploadException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.RulesValidationService;
import com.iqser.red.service.persistence.management.v1.processor.exception.InvalidRulesException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.RulesValidationMapper;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RulesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.Rules;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequestModel;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import feign.FeignException;
@ -51,89 +44,52 @@ public class RulesController implements RulesResource {
private static final String DOWNLOAD_FILE_NAME = "rules.drl";
private final RulesPersistenceService rulesPersistenceService;
private final RulesValidationService rulesValidationService;
private final RedactionClient redactionServiceClient;
private final AuditPersistenceService auditPersistenceService;
private final FileStatusPersistenceService fileStatusPersistenceService;
@Override
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<DroolsValidationResponse> upload(@RequestBody RulesUploadRequestModel rules) {
RulesUploadRequest rulesUploadRequest = RulesUploadRequest.fromModel(rules);
DroolsValidationResponse droolsValidationResponse = new DroolsValidationResponse();
public void upload(@RequestBody Rules rules) {
try {
var droolsValidation = rulesValidationService.validateRules(rulesUploadRequest.getRuleFileType(), rulesUploadRequest.getRules());
droolsValidationResponse = RulesValidationMapper.createFromDroolsValidation(droolsValidation);
if (!droolsValidation.isCompiled()) {
return new ResponseEntity<>(droolsValidationResponse, !rules.isDryRun() ? HttpStatus.UNPROCESSABLE_ENTITY : HttpStatus.OK);
}
redactionServiceClient.testRules(rules.getRules());
} catch (FeignException e) {
if (e.status() == HttpStatus.BAD_REQUEST.value()) {
throw new BadRequestException("The provided rule string is not a valid drools rule file!");
throw new InvalidRulesException("Rules could not be updated, validation check failed: " + e.getMessage());
}
throw processFeignException(e);
}
if (!rules.isDryRun()) {
rulesPersistenceService.setRules(rulesUploadRequest.getRules(), rulesUploadRequest.getDossierTemplateId(), rulesUploadRequest.getRuleFileType());
fileStatusPersistenceService.resetErrorCounter(rules.getDossierTemplateId());
}
rulesPersistenceService.setRules(rules.getRules(), rules.getDossierTemplateId());
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(rulesUploadRequest.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message(String.format("%s Rules have been updated", rulesUploadRequest.getRuleFileType()))
.build());
return new ResponseEntity<>(droolsValidationResponse, HttpStatus.OK);
.userId(KeycloakSecurity.getUserId())
.objectId(rules.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Rules have been updated")
.build());
}
@Override
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public RulesResponse download(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
public Rules download(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
return download(dossierTemplateId, RuleFileType.ENTITY);
}
@Override
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public RulesResponse download(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType) {
var ruleEntityOptional = rulesPersistenceService.getRules(dossierTemplateId, ruleFileType);
if (ruleEntityOptional.isEmpty()) {
throw new NotFoundException(String.format("No rule file of type %s found for dossierTemplateId %s", ruleFileType, dossierTemplateId));
}
return new RulesResponse(ruleEntityOptional.get().getValue(), dossierTemplateId, ruleEntityOptional.get().isTimeoutDetected());
return new Rules(rulesPersistenceService.getRules(dossierTemplateId).getValue(), dossierTemplateId);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<DroolsValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@RequestPart(name = "file") MultipartFile file) {
return uploadFile(dossierTemplateId, RuleFileType.ENTITY, dryRun, file);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<DroolsValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@RequestPart(name = "file") MultipartFile file) {
public void uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestPart(name = "file") MultipartFile file) {
try {
return upload(new RulesUploadRequestModel(new String(file.getBytes(), StandardCharsets.UTF_8), dossierTemplateId, ruleFileType, dryRun));
upload(new Rules(new String(file.getBytes(), StandardCharsets.UTF_8), dossierTemplateId));
} catch (IOException e) {
throw new FileUploadException("Could not upload file.", e);
}
}
@ -141,31 +97,15 @@ public class RulesController implements RulesResource {
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public ResponseEntity<?> downloadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
return downloadFile(dossierTemplateId, RuleFileType.ENTITY);
}
@Override
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public ResponseEntity<?> downloadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType) {
byte[] data = download(dossierTemplateId, ruleFileType).getRules().getBytes(StandardCharsets.UTF_8);
byte[] data = download(dossierTemplateId).getRules().getBytes(StandardCharsets.UTF_8);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
httpHeaders.add("Content-Disposition", "attachment; filename*=utf-8''" + ruleFileType.name() + "_" + DOWNLOAD_FILE_NAME);
httpHeaders.add("Content-Disposition", "attachment; filename*=utf-8\"" + DOWNLOAD_FILE_NAME + "\"");
InputStream is = new ByteArrayInputStream(data);
return new ResponseEntity<>(new InputStreamResource(is), httpHeaders, HttpStatus.OK);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public void unlockRules(String dossierTemplateId, RuleFileType ruleFileType) {
rulesPersistenceService.resetTimeoutDetected(dossierTemplateId, ruleFileType);
}
}

View File

@ -1,9 +1,6 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_FILE_STATUS;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.SET_REVIEWER;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.SET_STATUS_APPROVED;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.SET_STATUS_UNDER_APPROVAL;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.*;
import java.time.OffsetDateTime;
import java.util.ArrayList;
@ -14,50 +11,38 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import jakarta.transaction.Transactional;
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.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.ApprovalVerificationService;
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.users.UserService;
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;
import com.iqser.red.service.persistence.service.v1.api.external.resource.StatusResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttributes;
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;
import com.iqser.red.service.persistence.service.v1.api.shared.model.notification.NotificationType;
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;
@ -77,8 +62,6 @@ public class StatusController implements StatusResource {
private final AccessControlService accessControlService;
private final NotificationPersistenceService notificationPersistenceService;
private final DossierACLService dossierACLService;
private final ApprovalVerificationService approvalVerificationService;
private final FilterByPermissionsService filterByPermissionsService;
@Override
@ -94,28 +77,6 @@ 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) {
@ -127,7 +88,7 @@ public class StatusController implements StatusResource {
List<FileStatus> statusList = fileStatusManagementService.getDossierStatus(dossierId)
.stream()
.filter(fileStatus -> !fileStatus.isSoftOrHardDeleted())
.map(FileStatusMapper::toFileStatus)
.map(this::convert)
.collect(Collectors.toList());
response.put(dossierId, statusList);
} catch (AccessDeniedException e) {
@ -144,6 +105,7 @@ public class StatusController implements StatusResource {
@Transactional
public Map<String, List<FileStatus>> getSoftDeletedFilesForDossiers(@RequestBody List<String> dossierIds) {
List<FileStatus> statusList;
List<String> dossiersWithViewPermissions = new ArrayList<>();
for (var dossierId : dossierIds) {
try {
@ -156,11 +118,9 @@ public class StatusController implements StatusResource {
if (dossiersWithViewPermissions.isEmpty()) {
return new HashMap<>();
}
return fileStatusManagementService.getSoftDeletedForDossierList(dossiersWithViewPermissions)
.stream()
.map(FileStatusMapper::toFileStatus)
.collect(Collectors.groupingBy(FileStatus::getDossierId, Collectors.toList()));
statusList = fileStatusManagementService.getSoftDeletedForDossierList(dossiersWithViewPermissions).stream().map(this::convert).collect(Collectors.toList());
return statusList.stream().collect(Collectors.groupingBy(FileStatus::getDossierId, Collectors.toList()));
}
@ -173,7 +133,7 @@ public class StatusController implements StatusResource {
return fileStatusManagementService.getDossierStatus(dossierId)
.stream()
.filter(fileStatus -> !fileStatus.isSoftOrHardDeleted())
.map(FileStatusMapper::toFileStatus)
.map(this::convert)
.collect(Collectors.toList());
} catch (AccessDeniedException e) {
@ -182,12 +142,10 @@ public class StatusController implements StatusResource {
}
@PreAuthorize("hasAuthority('" + READ_FILE_STATUS + "')")
public FileStatus getFileStatus(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
return FileStatusMapper.toFileStatus(fileStatusManagementService.getFileStatus(fileId));
return convert(fileStatusManagementService.getFileStatus(fileId));
}
@ -197,7 +155,6 @@ public class StatusController implements StatusResource {
@PathVariable(FILE_ID) String fileId,
@RequestParam(name = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsMemberOrApprover(dossierId);
log.debug("Requested [setFileReviewer] for dossier: {} / file: {} / reviewer: {}", dossierId, fileId, assigneeId);
@ -211,39 +168,39 @@ public class StatusController implements StatusResource {
fileStatusManagementService.setCurrentFileAssignee(dossierId, fileId, assigneeId);
if (assigneeId == null) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Reviewer was unassigned from document")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Reviewer was unassigned from document")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
} else {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Reviewer was assigned to document")
.details(Map.of(DOSSIER_ID, dossierId, "reviewer", assigneeId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Reviewer was assigned to document")
.details(Map.of(DOSSIER_ID, dossierId, "reviewer", assigneeId))
.build());
}
if (assigneeId != null && !assigneeId.equals(KeycloakSecurity.getUserId())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(assigneeId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(WorkflowStatus.APPROVED.equals(fileStatus.getWorkflowStatus()) ? NotificationType.ASSIGN_APPROVER.name() : NotificationType.ASSIGN_REVIEWER.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
.userId(assigneeId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.ASSIGN_REVIEWER.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
}
if (assigneeId == null || fileStatus.getAssignee() != null && !fileStatus.getAssignee().equals(assigneeId) && !KeycloakSecurity.getUserId()
.equals(fileStatus.getAssignee())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(fileStatus.getAssignee())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.UNASSIGNED_FROM_FILE.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
.userId(fileStatus.getAssignee())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.UNASSIGNED_FROM_FILE.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
}
}
@ -258,9 +215,7 @@ public class StatusController implements StatusResource {
throw new BadRequestException("Unknown user=" + assigneeId);
}
// check he has a manager role, thus he can be the owner
if (user.get().getRoles()
.stream()
.noneMatch(VALID_MEMBER_ROLES::contains)) {
if (user.get().getRoles().stream().noneMatch(VALID_MEMBER_ROLES::contains)) {
throw new BadRequestException("Make sure each provided member id has the permission to be a member of a dossier.");
}
// check assignee is a member
@ -279,101 +234,83 @@ public class StatusController implements StatusResource {
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
var fileStatus = fileStatusManagementService.getFileStatus(fileId);
setStatusUnderReviewForFile(dossierId, fileId, assigneeId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Document status was changed to Under Review")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Document status was changed to Under Review")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
if (assigneeId != null && !assigneeId.equals(KeycloakSecurity.getUserId())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(assigneeId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.ASSIGN_REVIEWER.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
.userId(assigneeId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.ASSIGN_REVIEWER.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
}
generatePossibleUnassignedFromFileNotification(dossierId, fileId, fileStatus, assigneeId);
}
@PreAuthorize("hasAuthority('" + SET_STATUS_UNDER_APPROVAL + "')")
public void setStatusUnderApproval(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(name = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
var fileStatus = fileStatusManagementService.getFileStatus(fileId);
setStatusUnderApprovalForFile(dossierId, fileId, assigneeId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Document status was changed to Under Approval")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Document status was changed to Under Approval")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
if (assigneeId != null && !assigneeId.equals(KeycloakSecurity.getUserId())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(assigneeId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.ASSIGN_APPROVER.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
.userId(assigneeId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.ASSIGN_APPROVER.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
}
generatePossibleUnassignedFromFileNotification(dossierId, fileId, fileStatus, assigneeId);
}
@PreAuthorize("hasAuthority('" + SET_STATUS_APPROVED + "')")
public ApproveResponse setStatusApproved(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = FORCE_REQUEST_PARAM, required = false, defaultValue = "false") boolean force) {
public void setStatusApproved(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsApprover(dossierId);
ApproveResponse approveResponse = new ApproveResponse(fileId, false, new ArrayList<>());
if (!force) {
approveResponse = approvalVerificationService.verifyApprovalOfFile(dossierId, fileId);
setStatusApprovedForFile(dossierId, fileId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Document status was changed to Approved")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
var dossier = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, false, false));
if (!dossier.getOwnerId().equals(KeycloakSecurity.getUserId())) {
var fileStatus = fileStatusManagementService.getFileStatus(fileId);
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(dossier.getOwnerId())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOCUMENT_APPROVED.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
}
if (!approveResponse.isHasWarnings()) {
setStatusApprovedForFile(dossierId, fileId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Document status was changed to Approved")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
var dossier = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, false, false));
if (dossier.getOwnerId() == null) {
throw new ConflictException("Dossier has no owner!");
}
if (!dossier.getOwnerId().equals(KeycloakSecurity.getUserId())) {
var fileStatus = fileStatusManagementService.getFileStatus(fileId);
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(dossier.getOwnerId())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOCUMENT_APPROVED.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
}
}
return approveResponse;
}
@ -407,19 +344,14 @@ public class StatusController implements StatusResource {
private void generatePossibleUnassignedFromFileNotification(String dossierId, String fileId, FileModel oldFileStatus, String newAssigneeId) {
if (oldFileStatus.getAssignee() == null
|| newAssigneeId == null && oldFileStatus.getAssignee() == null
|| oldFileStatus.getAssignee().equals(newAssigneeId)
|| KeycloakSecurity.getUserId().equals(oldFileStatus.getAssignee())) {
return;
if (oldFileStatus.getAssignee() != null && !oldFileStatus.getAssignee().equals(newAssigneeId) && !KeycloakSecurity.getUserId().equals(oldFileStatus.getAssignee())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(oldFileStatus.getAssignee())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.UNASSIGNED_FROM_FILE.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, oldFileStatus.getFilename()))
.build());
}
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(oldFileStatus.getAssignee())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.UNASSIGNED_FROM_FILE.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, oldFileStatus.getFilename()))
.build());
}
@ -450,29 +382,18 @@ public class StatusController implements StatusResource {
@Override
@PreAuthorize("hasAuthority('" + SET_STATUS_APPROVED + "')")
public List<ApproveResponse> setStatusApprovedForList(String dossierId,
List<String> fileIds,
@RequestParam(value = FORCE_REQUEST_PARAM, required = false, defaultValue = "false") boolean force) {
public void setStatusApprovedForList(String dossierId, List<String> fileIds) {
List<ApproveResponse> approveResponses = new ArrayList<>();
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsApprover(dossierId);
dossierManagementService.getDossierById(dossierId, false, false);
if (fileIds.size() > 50) {
throw new BadRequestException("Maximum amount of files that can be approved at once is 50.");
}
fileIds.forEach(fileId -> approveResponses.add(setStatusApproved(dossierId, fileId, force)));
return approveResponses;
fileIds.forEach(fileId -> setStatusApproved(dossierId, fileId));
}
@PreAuthorize("hasAuthority('" + SET_REVIEWER + "')")
public void setStatusNewForList(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody List<String> fileIds) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
for (var fileId : fileIds) {
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
var fileStatus = fileStatusManagementService.getFileStatus(fileId);
@ -488,16 +409,12 @@ public class StatusController implements StatusResource {
}
@PreAuthorize("hasAuthority('" + READ_FILE_STATUS + "')")
public List<FileStatus> getSoftDeletedDossierStatus(@PathVariable(DOSSIER_ID) String dossierId) {
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
return fileStatusManagementService.getSoftDeletedDossierStatus(dossierId)
.stream()
.map(FileStatusMapper::toFileStatus)
.collect(Collectors.toList());
return fileStatusManagementService.getSoftDeletedDossierStatus(dossierId).stream().map(this::convert).collect(Collectors.toList());
} catch (AccessDeniedException e) {
return new ArrayList<>();
@ -517,4 +434,61 @@ public class StatusController implements StatusResource {
fileStatusManagementService.setStatusApproved(dossierId, fileId, KeycloakSecurity.getUserId());
}
private FileStatus convert(FileModel status) {
return FileStatus.builder()
.dossierId(status.getDossierId())
.dossierArchived(status.isDossierArchived())
.dossierStatusId(status.getDossierStatusId())
.dossierTemplateId(status.getDossierTemplateId())
.fileId(status.getId())
.filename(status.getFilename())
.processingStatus(ProcessingStatus.valueOf(status.getProcessingStatus().name()))
.workflowStatus(WorkflowStatus.valueOf(status.getWorkflowStatus().name()))
.numberOfPages(status.getNumberOfPages())
.added(status.getAdded())
.lastUpdated(status.getLastUpdated())
.numberOfAnalyses(status.getNumberOfAnalyses())
.assignee(status.getAssignee())
.lastReviewer(status.getLastReviewer())
.lastApprover(status.getLastApprover())
.hasUpdates(status.isHasUpdates())
.hasImages(status.isHasImages())
.hasRequests(status.isHasSuggestions())
.hasSuggestions(status.isHasSuggestions())
.excludedFromAutomaticAnalysis(status.isExcludedFromAutomaticAnalysis())
.hasHints(status.isHasHints())
.hasRedactions(status.isHasRedactions())
.ocrEndTime(status.getOcrEndTime())
.ocrStartTime(status.getOcrStartTime())
.numberOfOCRedPages(status.getNumberOfOCRedPages() != null ? status.getNumberOfOCRedPages() : 0)
.numberOfPagesToOCR(status.getNumberOfPagesToOCR() != null ? status.getNumberOfPagesToOCR() : 0)
.hasAnnotationComments(status.isHasAnnotationComments())
.uploader(status.getUploader())
.dictionaryVersion(status.getDictionaryVersion())
.rulesVersion(status.getRulesVersion())
.legalBasisVersion(status.getLegalBasisVersion())
.lastProcessed(status.getLastProcessed())
.approvalDate(status.getApprovalDate())
.lastUploaded(status.getLastUploaded())
.analysisDuration(status.getAnalysisDuration())
.fileAttributes(new FileAttributes(status.getFileAttributes()))
.dossierDictionaryVersion(status.getDossierDictionaryVersion())
.excluded(status.isExcluded())
.excludedPages(status.getExcludedPages())
.softDeletedTime(status.getDeleted())
.analysisRequired(status.isAnalysisRequired())
.lastFileAttributeChange(status.getLastFileAttributeChange())
.redactionModificationDate(status.getRedactionModificationDate())
.fileManipulationDate(status.getFileManipulationDate())
.lastManualChangeDate(status.getLastManualChangeDate())
.hasHighlights(status.isHasHighlights())
.lastIndexed(status.getLastIndexed())
.fileSize(status.getFileSize())
.fileErrorInfo(status.getFileErrorInfo())
.build();
}
}

View File

@ -1,5 +1,6 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_DOSSIER;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
@ -40,7 +41,11 @@ public class StatusReportController implements StatusReportResource {
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
public ResponseEntity<?> generateStatusReport(@PathVariable(DOSSIER_ID) String dossierId) {
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
} catch (AccessDeniedException e) {
throw new NotFoundException("Object not found");
}
try {
StatusReportResponse statusReportResponse = statusReportClient.generateStatusReport(dossierId);

View File

@ -1,176 +0,0 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.IMPORT_FILES;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.USE_SUPPORT_CONTROLLER;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
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 org.springframework.web.multipart.MultipartFile;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.service.DatasetExchangeService;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.service.FileExchangeImportService;
import com.iqser.red.service.persistence.management.v1.processor.migration.MigrationController;
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.ReanalysisService;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.service.FileExchangeExportService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ImportResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ReanalysisSettings;
import com.iqser.red.service.persistence.service.v1.api.external.resource.SupportResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DownloadResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatusFilter;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileExchangeExportRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ReanalyzeFilesResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
@PreAuthorize("hasAuthority('" + USE_SUPPORT_CONTROLLER + "')")
public class SupportController implements SupportResource {
private final ReanalysisService reanalysisService;
private final FileStatusManagementService fileStatusManagementService;
private final FileExchangeExportService fileExchangeExportService;
private final FileExchangeImportService fileExchangeImportService;
private final DatasetExchangeService datasetExchangeService;
@Override
public ReanalyzeFilesResponse reanalyzeFiles(String dossierTemplateId, ReanalysisSettings reanalysisSettings) {
return new ReanalyzeFilesResponse(reanalysisService.reanalyzeTemplate(dossierTemplateId, reanalysisSettings));
}
@Override
public void reanalyzeAllRelevantErrorFiles(@RequestParam(value = FULL_REANALYSIS_PARAM, required = false, defaultValue = FALSE) boolean repeatStructureAnalysis,
@RequestParam(value = RUN_OCR_PARAM, required = false, defaultValue = FALSE) boolean runOcr) {
reanalysisService.reanalyzeAllRelevantErrorFiles(repeatStructureAnalysis, runOcr);
}
@Override
public void reanalyzeErrorFilesBulkForDossier(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody List<String> fileIds,
@RequestParam(value = FULL_REANALYSIS_PARAM, required = false, defaultValue = FALSE) boolean repeatStructureAnalysis,
@RequestParam(value = RUN_OCR_PARAM, required = false, defaultValue = FALSE) boolean runOcr) {
reanalysisService.reanalyzeGivenErrorFilesInDossier(dossierId, new HashSet<>(fileIds), repeatStructureAnalysis, runOcr);
}
private static Predicate<FileModel> matchesStatusFilters(FileStatusFilter fileStatusFilter) {
FileStatusFilter filter = Optional.ofNullable(fileStatusFilter)
.orElseGet(FileStatusFilter::new);
if (filter.getProcessingStatusList() == null) {
filter.setProcessingStatusList(new ArrayList<>());
}
if (filter.getWorkflowStatusList() == null) {
filter.setWorkflowStatusList(new ArrayList<>());
}
return fileStatus -> (filter.getProcessingStatusList().isEmpty() || filter.getProcessingStatusList().contains(fileStatus.getProcessingStatus()))
&& (filter.getWorkflowStatusList().isEmpty() || filter.getWorkflowStatusList().contains(fileStatus.getWorkflowStatus()))
&& (filter.isIncludeSoftDeletedFiles() || fileStatus.getDeleted() == null)
&& (filter.isIncludeHardDeletedFiles() || fileStatus.getHardDeletedTime() == null);
}
@Override
public List<FileStatus> getFileStatus(@RequestBody FileStatusFilter fileStatusFilter) {
return fileStatusManagementService.getAllFileStatuses()
.stream()
.filter(matchesStatusFilters(fileStatusFilter))
.map(FileStatusMapper::toFileStatus)
.collect(Collectors.toList());
}
@Override
public List<FileStatus> getFileStatusForDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody FileStatusFilter fileStatusFilter) {
return fileStatusManagementService.getAllDossierTemplateStatus(dossierTemplateId)
.stream()
.filter(matchesStatusFilters(fileStatusFilter))
.map(FileStatusMapper::toFileStatus)
.collect(Collectors.toList());
}
@Override
public List<FileStatus> exportFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody FileStatusFilter fileStatusFilter) {
return fileStatusManagementService.getAllDossierStatus(dossierId)
.stream()
.filter(matchesStatusFilters(fileStatusFilter))
.map(FileStatusMapper::toFileStatus)
.collect(Collectors.toList());
}
@Override
public DownloadResponse exportDataset(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {
return datasetExchangeService.prepareExport(dossierTemplateId);
}
@Override
public DownloadResponse exportFiles(String dossierTemplateId, FileExchangeExportRequest exportRequest) {
return fileExchangeExportService.prepareExportDownload(dossierTemplateId, exportRequest);
}
@Override
@PreAuthorize("hasAuthority('" + IMPORT_FILES + "')")
public ImportResponse importFiles(MultipartFile file) {
byte[] bytes;
try {
bytes = file.getBytes();
} catch (IOException e) {
throw new BadRequestException("File could not be read and is likely corrupted.", e);
}
return fileExchangeImportService.importFileExchangeArchive(KeycloakSecurity.getUserId(), bytes);
}
@Override
@PreAuthorize("hasAuthority('" + IMPORT_FILES + "')")
public ImportResponse importDataset(MultipartFile file) {
byte[] bytes;
try {
bytes = file.getBytes();
} catch (IOException e) {
throw new BadRequestException("File could not be read and is likely corrupted.", e);
}
return datasetExchangeService.importDataset(KeycloakSecurity.getUserId(), bytes);
}
}

View File

@ -1,6 +1,5 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.UPLOAD_FILE;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import java.io.ByteArrayOutputStream;
@ -8,7 +7,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -16,18 +15,16 @@ import java.util.UUID;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.io.IOUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.pdftron.redaction.v1.api.model.ByteContentDocument;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileFormatValidationService;
import com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisService;
import com.iqser.red.service.persistence.management.v1.processor.service.UploadService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
@ -36,12 +33,9 @@ import com.iqser.red.service.persistence.service.v1.api.external.resource.Upload
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileUploadResult;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.knecon.fforesight.tenantcommons.TenantContext;
import feign.FeignException;
import io.micrometer.core.annotation.Timed;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
@ -50,181 +44,144 @@ import lombok.extern.slf4j.Slf4j;
@RestController
@RequiredArgsConstructor
@Slf4j
@SuppressWarnings("PMD")
public class UploadController implements UploadResource {
private static final int THRESHOLD_ENTRIES = 10000; // Maximum number of files allowed
private static final int THRESHOLD_SIZE = 1000000000; // 1 GB total unzipped data
private static final double THRESHOLD_RATIO = 10; // Max allowed compression ratio
private static final int THRESHOLD_ENTRIES = 10000;
private static final int THRESHOLD_SIZE = 1000000000; // 1 GB
private static final double THRESHOLD_RATIO = 10;
private static final List<String> VALID_FILE_EXTENSIONS = List.of("pdf", "docx", "doc", "xls", "xlsx", "ppt", "pptx");
private final UploadService uploadService;
private final ReanalysisService reanalysisService;
private final AccessControlService accessControlService;
private final AuditPersistenceService auditPersistenceService;
private final FileFormatValidationService fileFormatValidationService;
@Timed
@Override
public FileUploadResult upload(@RequestPart(name = "file") MultipartFile file,
@PathVariable(DOSSIER_ID) String dossierId,
@RequestParam(value = "keepManualRedactions", required = false, defaultValue = "false") boolean keepManualRedactions,
@Parameter(name = DISABLE_AUTOMATIC_ANALYSIS_PARAM, description = "Disables automatic redaction for the uploaded file, imports only imported redactions") @RequestParam(value = DISABLE_AUTOMATIC_ANALYSIS_PARAM, required = false, defaultValue = "false") boolean disableAutomaticAnalysis) {
@RequestParam(value = "keepManualRedactions", required = false, defaultValue = "false") boolean keepManualRedactions) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
String originalFilename = file.getOriginalFilename();
if (originalFilename == null) {
if (file.getOriginalFilename() == null) {
throw new BadRequestException("Could not upload file, no filename provided.");
}
String extension = getExtension(originalFilename);
var extension = getExtension(file.getOriginalFilename());
try {
return switch (extension) {
case "zip" -> handleZip(dossierId, file.getBytes(), keepManualRedactions, disableAutomaticAnalysis);
case "csv" -> uploadService.importCsv(dossierId, file.getBytes());
default -> {
validateExtensionOrThrow(extension);
yield uploadService.processSingleFile(dossierId, originalFilename, file.getBytes(), keepManualRedactions, disableAutomaticAnalysis);
}
};
switch (extension) {
case "zip":
return handleZip(dossierId, file.getBytes(), keepManualRedactions);
case "csv":
return uploadService.importCsv(dossierId, file.getBytes());
default:
if (VALID_FILE_EXTENSIONS.contains(extension)) {
return uploadService.processSingleFile(dossierId, file.getOriginalFilename(), file.getBytes(), keepManualRedactions);
} else {
throw new BadRequestException("Invalid file uploaded");
}
}
} catch (IOException e) {
throw new BadRequestException("Failed to process file: " + e.getMessage(), e);
throw new BadRequestException(e.getMessage(), e);
}
}
@PreAuthorize("hasAuthority('" + UPLOAD_FILE + "')")
public void importRedactions(@RequestPart(name = "file") MultipartFile file,
@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "pageInclusionRequest", required = false) Set<Integer> pageInclusionRequest) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
try {
reanalysisService.importRedactions(ByteContentDocument.builder().dossierId(dossierId).fileId(fileId).document(file.getBytes()).pages(pageInclusionRequest).build());
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Redactions were imported")
.details(Map.of("dossierId", dossierId))
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Redactions were imported")
.details(Map.of("dossierId", dossierId))
.build());
} catch (IOException e) {
throw new BadRequestException("Failed to import redactions: " + e.getMessage(), e);
throw new BadRequestException(e.getMessage(), e);
} catch (FeignException e) {
throw processFeignException(e);
}
}
private void validateExtensionOrThrow(String extension) {
private String getExtension(String fileName) {
if (!fileFormatValidationService.getAllFileFormats().contains(extension)) {
throw new BadRequestException("Invalid file uploaded (unrecognized extension).");
}
if (!fileFormatValidationService.getValidFileFormatsForTenant(TenantContext.getTenantId()).contains(extension)) {
throw new NotAllowedException("Insufficient permissions for this file type.");
}
return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
}
/**
* 1. Write the uploaded content to a temp ZIP file
* 2. Check the number of entries and reject if too big or if symlinks found
* 3. Unzip and process each file, while checking size and ratio.
*/
private FileUploadResult handleZip(String dossierId, byte[] fileContent, boolean keepManualRedactions, boolean disableAutomaticAnalysis) throws IOException {
private FileUploadResult handleZip(String dossierId, byte[] fileContent, boolean keepManualRedactions) throws IOException {
File tempZip = FileUtils.createTempFile(UUID.randomUUID().toString(), ".zip");
try (FileOutputStream fos = new FileOutputStream(tempZip)) {
IOUtils.write(fileContent, fos);
File tempFile = FileUtils.createTempFile(UUID.randomUUID().toString(), ".zip");
try (var fileOutputStream = new FileOutputStream(tempFile)) {
IOUtils.write(fileContent, fileOutputStream);
}
validateZipEntries(tempZip);
try {
ZipData zipData = processZipContents(tempZip, dossierId, keepManualRedactions, disableAutomaticAnalysis);
checkForSymlinks(tempFile);
var zipData = unzip(tempFile, dossierId, keepManualRedactions);
if (zipData.csvBytes != null) {
try {
FileUploadResult csvResult = uploadService.importCsv(dossierId, zipData.csvBytes);
zipData.fileUploadResult.getProcessedAttributes().addAll(csvResult.getProcessedAttributes());
zipData.fileUploadResult.getProcessedFileIds().addAll(csvResult.getProcessedFileIds());
var importResult = uploadService.importCsv(dossierId, zipData.csvBytes);
zipData.fileUploadResult.getProcessedAttributes().addAll(importResult.getProcessedAttributes());
zipData.fileUploadResult.getProcessedFileIds().addAll(importResult.getProcessedFileIds());
} catch (Exception e) {
log.debug("CSV file inside ZIP failed to import", e);
}
} else if (zipData.fileUploadResult.getFileIds().isEmpty()) {
if (zipData.containedUnpermittedFiles) {
throw new NotAllowedException("Zip file contains unpermitted files.");
} else {
throw new BadRequestException("Only unsupported files in the ZIP.");
log.debug("CSV file inside ZIP failed", e);
// TODO return un-processed files to client
}
}
return zipData.fileUploadResult;
} finally {
if (!tempZip.delete()) {
log.warn("Could not delete temporary ZIP file: {}", tempZip);
boolean isDeleted = tempFile.delete();
if (!isDeleted) {
log.warn("tempFile could not be deleted");
}
}
}
private void validateZipEntries(File tempZip) throws IOException {
try (FileInputStream fis = new FileInputStream(tempZip); ZipFile zipFile = new ZipFile(fis.getChannel())) {
int count = 0;
var entries = zipFile.getEntries();
while (entries.hasMoreElements()) {
ZipArchiveEntry ze = entries.nextElement();
private void checkForSymlinks(File tempFile) throws IOException {
try (var fis = new FileInputStream(tempFile); var zipFile = new ZipFile(fis.getChannel())) {
for (var entryEnum = zipFile.getEntries(); entryEnum.hasMoreElements(); ) {
var ze = entryEnum.nextElement();
if (ze.isUnixSymlink()) {
throw new BadRequestException("ZIP-files with symlinks are not allowed.");
}
if (!ze.isDirectory() && !ze.getName().startsWith(".")) {
count++;
if (count > THRESHOLD_ENTRIES) {
throw new BadRequestException("ZIP-Bomb detected: too many entries.");
}
throw new BadRequestException("ZIP-files with symlinks are not allowed");
}
}
}
}
private ZipData processZipContents(File tempZip, String dossierId, boolean keepManualRedactions, boolean disableAutomaticAnalysis) throws IOException {
private ZipData unzip(File tempFile, String dossierId, boolean keepManualRedactions) throws IOException {
ZipData zipData = new ZipData();
var zipData = new ZipData();
try (FileInputStream fis = new FileInputStream(tempZip); ZipFile zipFile = new ZipFile(fis.getChannel())) {
try (var fis = new FileInputStream(tempFile); var zipFile = new ZipFile(fis.getChannel())) {
var entries = zipFile.getEntries();
while (entries.hasMoreElements()) {
ZipArchiveEntry entry = entries.nextElement();
for (var entryEnum = zipFile.getEntries(); entryEnum.hasMoreElements(); ) {
var ze = entryEnum.nextElement();
zipData.totalEntryArchive++;
if (entry.isDirectory() || entry.getName().startsWith(".")) {
continue;
}
byte[] entryBytes = readEntryWithRatioCheck(entry, zipFile);
zipData.totalSizeArchive += entryBytes.length;
if (zipData.totalSizeArchive > THRESHOLD_SIZE) {
throw new BadRequestException("ZIP-Bomb detected (exceeds total size limit).");
}
String extension = getExtension(entry.getName());
if ("csv".equalsIgnoreCase(extension)) {
zipData.csvBytes = entryBytes;
} else {
handleRegularFile(dossierId, entryBytes, extension, extractFileName(entry.getName()), zipData, keepManualRedactions, disableAutomaticAnalysis);
if (!ze.isDirectory()) {
processFileZipEntry(ze, zipFile, dossierId, keepManualRedactions, zipData);
}
}
}
@ -232,70 +189,65 @@ public class UploadController implements UploadResource {
}
private byte[] readEntryWithRatioCheck(ZipArchiveEntry entry, ZipFile zipFile) throws IOException {
private void processFileZipEntry(ZipArchiveEntry ze, ZipFile zipFile, String dossierId, boolean keepManualRedactions, ZipData zipData) throws IOException {
long compressedSize = entry.getCompressedSize() > 0 ? entry.getCompressedSize() : 1;
try (var is = zipFile.getInputStream(entry); var bos = new ByteArrayOutputStream()) {
var extension = getExtension(ze.getName());
final String fileName;
if (ze.getName().lastIndexOf("/") >= 0) {
fileName = ze.getName().substring(ze.getName().lastIndexOf("/") + 1);
} else {
fileName = ze.getName();
}
byte[] buffer = new byte[4096];
int bytesRead;
int totalUncompressed = 0;
if (fileName.startsWith(".")) {
return;
}
while ((bytesRead = is.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
totalUncompressed += bytesRead;
var entryAsBytes = readCurrentZipEntry(ze, zipFile);
zipData.totalSizeArchive += entryAsBytes.length;
double ratio = (double) totalUncompressed / compressedSize;
if (ratio > THRESHOLD_RATIO) {
throw new BadRequestException("ZIP-Bomb detected (compression ratio too high).");
// 1. the uncompressed data size is too much for the application resource capacity
// 2. too many entries in the archive can lead to inode exhaustion of the file-system
if (zipData.totalSizeArchive > THRESHOLD_SIZE || zipData.totalEntryArchive > THRESHOLD_ENTRIES) {
throw new BadRequestException("ZIP-Bomb detected.");
}
if ("csv".equals(extension)) {
zipData.csvBytes = entryAsBytes;
} else if (VALID_FILE_EXTENSIONS.contains(extension)) {
try {
var result = uploadService.processSingleFile(dossierId, fileName, entryAsBytes, keepManualRedactions);
zipData.fileUploadResult.getFileIds().addAll(result.getFileIds());
} catch (Exception e) {
log.debug("PDF File inside ZIP failed", e);
// TODO return un-processed files to client
}
}
}
private byte[] readCurrentZipEntry(ZipArchiveEntry ze, ZipFile zipFile) throws IOException {
var bos = new ByteArrayOutputStream();
try (var entryStream = zipFile.getInputStream(ze)) {
var buffer = new byte[2048];
var nBytes = 0;
int totalSizeEntry = 0;
while ((nBytes = entryStream.read(buffer)) > 0) {
bos.write(buffer, 0, nBytes);
totalSizeEntry += nBytes;
double compressionRatio = (float) totalSizeEntry / ze.getCompressedSize();
if (compressionRatio > THRESHOLD_RATIO) {
// ratio between compressed and uncompressed data is highly suspicious, looks like a Zip Bomb Attack
throw new BadRequestException("ZIP-Bomb detected.");
}
}
return bos.toByteArray();
}
}
private void handleRegularFile(String dossierId,
byte[] fileBytes,
String extension,
String fileName,
ZipData zipData,
boolean keepManualRedactions,
boolean disableAutomaticAnalysis) {
if (!fileFormatValidationService.getAllFileFormats().contains(extension)) {
zipData.containedUnpermittedFiles = false;
return;
}
if (!fileFormatValidationService.getValidFileFormatsForTenant(TenantContext.getTenantId()).contains(extension)) {
zipData.containedUnpermittedFiles = true;
return;
}
try {
FileUploadResult result = uploadService.processSingleFile(dossierId, fileName, fileBytes, keepManualRedactions, disableAutomaticAnalysis);
zipData.fileUploadResult.getFileIds().addAll(result.getFileIds());
} catch (Exception e) {
log.debug("Failed to process file '{}' in ZIP: {}", fileName, e.getMessage(), e);
}
}
private String extractFileName(String path) {
int idx = path.lastIndexOf('/');
return (idx >= 0) ? path.substring(idx + 1) : path;
}
private String getExtension(String fileName) {
int idx = fileName.lastIndexOf('.');
if (idx < 0) {
return "";
}
return fileName.substring(idx + 1).toLowerCase(Locale.ROOT);
return bos.toByteArray();
}
@ -304,8 +256,8 @@ public class UploadController implements UploadResource {
byte[] csvBytes;
int totalSizeArchive;
int totalEntryArchive;
FileUploadResult fileUploadResult = new FileUploadResult();
boolean containedUnpermittedFiles;
}

View File

@ -1,60 +0,0 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_USER_STATS;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.UserStatsResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.UserStats;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class UserStatsController implements UserStatsResource {
private final UserService userService;
private final DossierService dossierService;
private final FileStatusPersistenceService fileStatusPersistenceService;
private final DossierACLService dossierACLService;
@Override
@PreAuthorize("hasAuthority('" + READ_USER_STATS + "')")
public ResponseEntity<UserStats> getUserStats(String userId) {
if (userService.getUserById(userId).isEmpty()) {
throw new NotFoundException(String.format("The user with id %s is not found.", userId));
}
List<String> dossierMemberships = new ArrayList<>();
List<String> dossierOwnerships = new ArrayList<>();
dossierService.getAllDossiers()
.stream()
.filter(dossierEntity -> dossierEntity.getHardDeletedTime() == null)
.forEach(d -> {
if (dossierACLService.getMembers(d.getId()).contains(userId)) {
dossierMemberships.add(d.getId());
}
if (dossierACLService.getOwners(d.getId()).contains(userId)) {
dossierOwnerships.add(d.getId());
}
});
return new ResponseEntity<>(new UserStats(dossierMemberships.size(), dossierOwnerships.size(), this.fileStatusPersistenceService.getNumberOfAssignedFiles(userId)),
HttpStatus.OK);
}
}

View File

@ -11,12 +11,10 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.LegalBasisMappingPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.VersionsResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.VersionsResponse;
import lombok.RequiredArgsConstructor;
@ -27,8 +25,6 @@ public class VersionsController implements VersionsResource {
private final DictionaryPersistenceService dictionaryPersistenceService;
private final RulesPersistenceService rulesPersistenceService;
private final AccessControlService accessControlService;
private final LegalBasisMappingPersistenceService legalBasisMappingPersistenceService;
@Override
@ -37,9 +33,7 @@ public class VersionsController implements VersionsResource {
var result = new HashMap<String, VersionsResponse>();
dossierTemplateIds.forEach(rsId -> {
VersionsResponse response = new VersionsResponse(dictionaryPersistenceService.getVersion(rsId),
rulesPersistenceService.getVersion(rsId, RuleFileType.ENTITY),
legalBasisMappingPersistenceService.getVersion(rsId));
VersionsResponse response = new VersionsResponse(dictionaryPersistenceService.getVersion(rsId), rulesPersistenceService.getRules(rsId).getVersion());
result.put(rsId, response);
});
@ -51,10 +45,7 @@ public class VersionsController implements VersionsResource {
@PreAuthorize("hasAuthority('" + READ_VERSIONS + "')")
public Long getDossierDictionaryVersion(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @PathVariable(DOSSIER_ID_PARAM) String dossierId) {
if (accessControlService.hasUserViewPermissionsForDossier(dossierId)) {
return dictionaryPersistenceService.getVersionForDossier(dossierId);
}
return 0L;
return dictionaryPersistenceService.getVersionForDossier(dossierId);
}
}

View File

@ -14,7 +14,7 @@ import com.iqser.red.service.persistence.management.v1.processor.service.AccessC
import com.iqser.red.service.persistence.management.v1.processor.service.AnalysisFlagsCalculationService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ViewedPagesPersistenceService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.ViewedPagesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ViewedPages;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ViewedPagesRequest;
@ -31,30 +31,34 @@ public class ViewedPagesController implements ViewedPagesResource {
private final AccessControlService accessControlService;
private final ViewedPagesPersistenceService viewedPagesPersistenceService;
private final FileStatusService fileStatusService;
private final AnalysisFlagsCalculationService analysisFlagsCalculationService;
@PreAuthorize("hasAuthority('" + MANAGE_VIEWED_PAGES + "')")
public void addPage(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ViewedPagesRequest viewedPagesRequest) {
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
accessControlService.verifyUserIsReviewer(dossierId, fileId);
viewedPagesPersistenceService.insertPage(fileId, KeycloakSecurity.getUserId(), viewedPagesRequest.getPage());
var file = fileStatusService.getStatus(fileId);
analysisFlagsCalculationService.calculateFlags(file.getDossierId(), fileId);
}
@PreAuthorize("hasAuthority('" + MANAGE_VIEWED_PAGES + "')")
public void removePage(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @PathVariable(PAGE) int page) {
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
accessControlService.verifyUserIsReviewer(dossierId, fileId);
viewedPagesPersistenceService.removePage(fileId, KeycloakSecurity.getUserId(), page);
var file = fileStatusService.getStatus(fileId);
analysisFlagsCalculationService.calculateFlags(file.getDossierId(), fileId);
}
@PreAuthorize("hasAuthority('" + MANAGE_VIEWED_PAGES + "')")
public ViewedPages getViewedPages(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
accessControlService.verifyUserIsReviewer(dossierId, fileId);
try {
var pages = MagicConverter.convert(viewedPagesPersistenceService.findViewedPages(fileId, KeycloakSecurity.getUserId()), ViewedPage.class);

View File

@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.RestController;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.service.WatermarkService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.WatermarkResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.WatermarkModel;
@ -40,11 +40,11 @@ public class WatermarkController implements WatermarkResource {
watermark.setCreatedBy(userId);
WatermarkModel result = MagicConverter.convert(watermarkService.createOrUpdateWatermark(watermark), WatermarkModel.class);
auditPersistenceService.audit(AuditRequest.builder()
.userId(userId)
.objectId(result.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Watermark has been changed.")
.build());
.userId(userId)
.objectId(result.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Watermark has been changed.")
.build());
return result;
}
@ -72,11 +72,11 @@ public class WatermarkController implements WatermarkResource {
String dossierTemplateId = watermarkService.getWatermark(watermarkId).getDossierTemplateId();
watermarkService.deleteWatermark(watermarkId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Watermark has been deleted.")
.build());
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Watermark has been deleted.")
.build());
}

View File

@ -11,6 +11,7 @@ import com.iqser.red.persistence.service.v1.external.api.impl.model.OneTimeToken
@Service
public class OneTimeTokenCacheService {
@Cacheable(value = OTT_CACHE, key = "#tokenId")
public OneTimeToken cacheOTT(String tokenId, OneTimeToken token) {
// empty

View File

@ -1,15 +0,0 @@
plugins {
id("com.iqser.red.service.java-conventions")
id("io.freefair.lombok") version "8.6"
}
dependencies {
api(project(":persistence-service-processor-v1"))
api(project(":persistence-service-external-api-v2"))
api(project(":persistence-service-external-api-impl-v1"))
implementation("org.mapstruct:mapstruct:1.5.5.Final")
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.5.Final")
}
description = "persistence-service-external-api-impl-v2"

View File

@ -1,10 +0,0 @@
package com.iqser.red.persistence.service.v2.external.api.impl;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class PersistenceServiceExternalApiConfigurationV2 {
}

View File

@ -1,202 +0,0 @@
package com.iqser.red.persistence.service.v2.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.GET_RSS;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierResource.DOSSIER_ID_PARAM;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierTemplateResource.DOSSIER_TEMPLATE_ID_PARAM;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.FileResource.FILE_ID_PARAM;
import java.util.ArrayList;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DossierController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.StatusController;
import com.iqser.red.persistence.service.v2.external.api.impl.mapper.ComponentMapper;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.CurrentApplicationTypeProvider;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.model.User;
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.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.iqser.red.service.persistence.service.v2.api.external.model.BulkComponentsRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.Component;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentOverrideList;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileComponents;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileComponentsList;
import com.iqser.red.service.persistence.service.v2.api.external.resource.ComponentResource;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.knecon.fforesight.tenantcommons.TenantProvider;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
@RestController
@RequiredArgsConstructor
@Tag(name = "4. Component endpoints", description = "Provides operations related to components")
public class ComponentControllerV2 implements ComponentResource {
private final AccessControlService accessControlService;
private final ComponentLogService componentLogService;
private final UserService userService;
private final StatusController statusController;
private final FileStatusService fileStatusService;
private final DossierController dossierController;
private final DossierTemplatePersistenceService dossierTemplatePersistenceService;
private final CurrentApplicationTypeProvider currentApplicationTypeProvider;
private final ComponentMapper componentMapper = ComponentMapper.INSTANCE;
@Value("${documine.components.filesLimit:100}")
private int documineComponentsFilesLimit = 100;
@Override
public FileComponents getComponents(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId,
@RequestParam(name = INCLUDE_DETAILS_PARAM, defaultValue = "false", required = false) boolean includeDetails) {
checkApplicationType();
validateUserRoles(KeycloakSecurity.getUserId());
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
var componentLog = componentLogService.getComponentLog(dossierId, fileId);
return componentMapper.toFileComponents(componentLog, dossierTemplateId, dossierId, fileId, fileStatusService.getFileName(fileId), includeDetails);
}
private void validateUserRoles(String userId) {
Optional<User> userOptional = userService.getUserById(userId);
if (userOptional.isPresent()) {
if (userOptional.get().getRoles()
.stream()
.noneMatch(ApplicationRoles.VALID_MEMBER_ROLES::contains)) {
throw new NotAllowedException("User doesn't have appropriate roles");
}
}
}
@Override
public FileComponentsList getComponentsOfDossier(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestParam(name = INCLUDE_DETAILS_PARAM, defaultValue = "false", required = false) boolean includeDetails) {
checkApplicationType();
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
var dossierFiles = statusController.getDossierStatus(dossierId);
if(dossierFiles.size() > documineComponentsFilesLimit) {
throw new BadRequestException(String.format("The dossier you requested components for contains %s files this is above the limit of %s files for this endpoint, please use the POST %s", dossierFiles.size(), documineComponentsFilesLimit, FILE_PATH + BULK_COMPONENTS_PATH));
}
return new FileComponentsList(dossierFiles.stream()
.map(file -> getComponents(dossierTemplateId, dossierId, file.getFileId(), includeDetails))
.toList());
}
@Override
public FileComponentsList getComponentsForFiles(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestParam(name = INCLUDE_DETAILS_PARAM, defaultValue = "false", required = false) boolean includeDetails,
@RequestBody BulkComponentsRequest bulkComponentsRequest){
if(bulkComponentsRequest.getFileIds().size() > documineComponentsFilesLimit) {
throw new BadRequestException(String.format("You requested components for %s files this is above the limit of %s files for this endpoint, lower the fileIds in the request", bulkComponentsRequest.getFileIds().size(), documineComponentsFilesLimit));
}
checkApplicationType();
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
dossierController.getDossier(dossierId, false, false);
return new FileComponentsList(bulkComponentsRequest.getFileIds().stream()
.map(fileId -> getComponents(dossierTemplateId, dossierId, fileId, includeDetails))
.toList());
}
@Override
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void addOverride(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId,
@RequestBody Component override) {
checkApplicationType();
accessControlService.verifyUserIsReviewer(dossierId, fileId);
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
componentLogService.overrideComponent(dossierId, fileId, componentMapper.toComponentLogEntry(override));
}
@Override
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public ComponentOverrideList getOverrides(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId) {
checkApplicationType();
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
FileModel status = fileStatusService.getStatus(fileId);
var overrides = componentLogService.getComponentLog(dossierId, fileId).getComponentLogEntries()
.stream()
.filter(ComponentLogEntry::isOverridden)
.collect(Collectors.toList());
var componentOverrides = componentMapper.toComponents(overrides);
return ComponentOverrideList.builder()
.dossierTemplateId(dossierTemplateId)
.dossierId(dossierId)
.fileId(fileId)
.componentOverrides(status.isSoftOrHardDeleted() ? new ArrayList<>() : componentOverrides)
.build();
}
@Override
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void revertOverrides(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId,
@RequestBody RevertOverrideRequest revertOverrideRequest) {
checkApplicationType();
accessControlService.verifyUserIsReviewer(dossierId, fileId);
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
componentLogService.revertOverrides(dossierId, fileId, revertOverrideRequest);
}
private void checkApplicationType() {
if (!currentApplicationTypeProvider.isDocuMine()) {
throw new NotAllowedException("Components can only be accessed in DocuMine");
}
}
}

View File

@ -1,205 +0,0 @@
package com.iqser.red.persistence.service.v2.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_DOSSIER_ATTRIBUTES;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierTemplateResource.DOSSIER_TEMPLATE_ID_PARAM;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
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.google.common.collect.Sets;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DossierController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DossierTemplateController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DownloadController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.StatusController;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.CurrentApplicationTypeProvider;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierAttributesManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DownloadStatusPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.PrepareDownloadWithOptionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
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.v1.api.shared.model.dossiertemplate.dossier.DossierAttribute;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierAttributes;
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.model.DownloadRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.DownloadStatus;
import com.iqser.red.service.persistence.service.v2.api.external.resource.DossierResource;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
@RestController
@RequiredArgsConstructor
@Tag(name = "2. Dossier endpoints", description = "Provides operations related to dossiers")
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class DossierControllerV2 implements DossierResource {
DossierTemplateController dossierTemplateController;
DossierController dossierController;
AccessControlService accessControlService;
DossierAttributesManagementService dossierAttributesManagementService;
AuditPersistenceService auditPersistenceService;
DownloadController downloadController;
StatusController statusController;
DownloadStatusPersistenceService downloadStatusPersistenceService;
CurrentApplicationTypeProvider currentApplicationTypeProvider;
public DossierList getDossiers(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@RequestParam(name = INCLUDE_ACTIVE_PARAM, defaultValue = "true", required = false) boolean includeActive,
@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_SOFT_DELETED_PARAM, defaultValue = "false", required = false) boolean includeSoftDeleted) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
var dossiers = dossierController.getDossiersForDossierTemplate(dossierTemplateId, includeArchived, includeSoftDeleted);
if (!includeActive) {
return new DossierList(dossiers.stream()
.filter(dossier -> dossier.getSoftDeletedTime() != null || dossier.getArchivedTime() != null)
.toList());
}
return new DossierList(dossiers);
}
public Dossier getDossier(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestParam(name = INCLUDE_SOFT_DELETED_PARAM, defaultValue = "false", required = false) boolean includeSoftDeleted) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
return dossierController.getDossier(dossierId, true, includeSoftDeleted);
}
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 DocuMineDossierRequest 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,
@RequestParam(name = DELETE_PERMANENTLY_PARAM, defaultValue = "false", required = false) boolean deletePermanently) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
if (deletePermanently) {
dossierController.hardDeleteDossiers(Sets.newHashSet(dossierId));
} else {
dossierController.deleteDossier(dossierId);
}
}
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_ATTRIBUTES + "')")
public void setDossierAttributes(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestBody DossierAttributes dossierAttributes) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.verifyUserIsDossierOwner(dossierId);
var dossierAttributeList = dossierAttributes.getAttributeIdToValue().entrySet()
.stream()
.map(entry -> new DossierAttribute(dossierId, entry.getKey(), entry.getValue()))
.toList();
dossierAttributesManagementService.setDossierAttributes(dossierId, dossierAttributeList);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Changed dossier attributes.")
.build());
}
public DownloadStatus prepareDossierDownload(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestBody DownloadRequest downloadRequest) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
var storageId = downloadController.prepareDownload(PrepareDownloadWithOptionRequest.builder()
.dossierId(dossierId)
.fileIds(statusController.getDossierStatus(dossierId)
.stream()
.map(FileStatus::getId)
.toList())
.reportTemplateIds(downloadRequest.getReportTemplateIds())
.downloadFileTypes(downloadRequest.getDownloadFileTypes())
.redactionPreviewColor(downloadRequest.getRedactionPreviewColor())
.includeUnprocessed(false)
.build()).getStorageId();
var status = downloadStatusPersistenceService.getStatus(storageId);
return DownloadStatus.builder()
.id(status.getUuid()) // This is a workaround the real is the storageId.
.userId(status.getUserId())
.filename(status.getFilename())
.mimeType(status.getMimeType())
.errorCause(status.getErrorCause())
.status(status.getStatus())
.creationDate(status.getCreationDate())
.lastDownload(status.getLastDownload())
.fileSize(status.getFileSize())
.dossierId(status.getDossier().getId())
.fileIds(status.getFiles()
.stream()
.map(FileEntity::getId)
.toList())
.downloadFileTypes(status.getDownloadFileTypes()
.stream()
.toList())
.reportTemplateIds(downloadRequest.getReportTemplateIds())
.build();
}
private 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(currentApplicationTypeProvider.isDocuMine() ? dossier.getMemberIds() : dossier.getApproverIds()) // 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

@ -1,656 +0,0 @@
package com.iqser.red.persistence.service.v2.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.GET_REPORT_TEMPLATES;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_DATA_FORMATS;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_DOSSIER_ATTRIBUTES_CONFIG;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_DOSSIER_STATUS;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_FILE_ATTRIBUTES_CONFIG;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_RULES;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_DATA_FORMATS;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_RULES;
import static java.lang.String.format;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.google.common.base.Strings;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DossierTemplateController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.FileAttributesController;
import com.iqser.red.persistence.service.v2.external.api.impl.mapper.ComponentMappingMapper;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DateFormatsEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.RuleSetEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.ComponentDefinitionEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.model.ComponentMappingDownloadModel;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentDefinitionService;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentMappingService;
import com.iqser.red.service.persistence.management.v1.processor.service.DateFormatsValidationService;
import com.iqser.red.service.persistence.management.v1.processor.service.RulesValidationService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DateFormatsPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ReportTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.RulesValidationMapper;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
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.ComponentDefinition;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionAddRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionUpdateRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentMappingMetadata;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DateFormatPatternErrorMessage;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentMappingMetadataModel;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentMappingSummary;
import com.iqser.red.service.persistence.service.v2.api.external.model.DossierAttributeDefinition;
import com.iqser.red.service.persistence.service.v2.api.external.model.DossierAttributeDefinitionList;
import com.iqser.red.service.persistence.service.v2.api.external.model.DossierStatusDefinition;
import com.iqser.red.service.persistence.service.v2.api.external.model.DossierStatusDefinitionList;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileAttributeDefinition;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileAttributeDefinitionList;
import com.iqser.red.service.persistence.service.v2.api.external.model.ReportTemplate;
import com.iqser.red.service.persistence.service.v2.api.external.model.ReportTemplateList;
import com.iqser.red.service.persistence.service.v2.api.external.resource.DossierTemplateResource;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import feign.FeignException;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.experimental.FieldDefaults;
@RestController
@RequiredArgsConstructor
@Tag(name = "1. Dossier templates endpoints", description = "Provides operations related to dossier templates")
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class DossierTemplateControllerV2 implements DossierTemplateResource {
private static final String RULES_DOWNLOAD_FILE_NAME_SUFFIX = "-rules.drl";
private static final String DATE_FORMAT_FILE_NAME = "date_formats.txt";
DossierTemplateController dossierTemplateController;
RulesPersistenceService rulesPersistenceService;
DateFormatsPersistenceService dateFormatsPersistenceService;
RulesValidationService rulesValidationService;
DateFormatsValidationService dateFormatsValidationService;
AuditPersistenceService auditPersistenceService;
FileAttributesController fileAttributesController;
ComponentMappingService componentMappingService;
ComponentMappingMapper componentMappingMapper = ComponentMappingMapper.INSTANCE;
DossierTemplatePersistenceService dossierTemplatePersistenceService;
DossierStatusPersistenceService dossierStatusPersistenceService;
DossierAttributeConfigPersistenceService dossierAttributeConfigPersistenceService;
ComponentDefinitionService componentDefinitionService;
ReportTemplatePersistenceService reportTemplatePersistenceService;
public List<DossierTemplateModel> getAllDossierTemplates() {
return dossierTemplateController.getAllDossierTemplates();
}
public DossierTemplateModel getDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
return dossierTemplateController.getDossierTemplate(dossierTemplateId);
}
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<DroolsValidationResponse> uploadEntityRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false, defaultValue = "false") boolean dryRun) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
return uploadRules(dossierTemplateId, RuleFileType.ENTITY, file, dryRun);
}
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public ResponseEntity<?> downloadEntityRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
return downloadRules(dossierTemplateId, RuleFileType.ENTITY);
}
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<DroolsValidationResponse> uploadComponentRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false, defaultValue = "false") boolean dryRun) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
return uploadRules(dossierTemplateId, RuleFileType.COMPONENT, file, dryRun);
}
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public ResponseEntity<?> downloadComponentRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
return downloadRules(dossierTemplateId, RuleFileType.COMPONENT);
}
@SneakyThrows
@PreAuthorize("hasAuthority('" + WRITE_DATA_FORMATS + "')")
public ResponseEntity<?> uploadDateFormats(String dossierTemplateId, MultipartFile file) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
String originalFilename = file.getOriginalFilename();
if (originalFilename == null || !originalFilename.endsWith(".txt")) {
throw new BadRequestException("Only .txt files can be parsed.");
}
String dateFormats = new String(file.getBytes(), StandardCharsets.UTF_8);
List<DateFormatPatternErrorMessage> dateFormatPatternErrorMessages = dateFormatsValidationService.validateDateFormats(dateFormats);
if (!dateFormatPatternErrorMessages.isEmpty()) {
return new ResponseEntity<>(dateFormatPatternErrorMessages, HttpStatus.UNPROCESSABLE_ENTITY);
}
dateFormatsPersistenceService.setDateFormats(dateFormats, dossierTemplateId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("date formats have been updated")
.build());
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@SneakyThrows
@PreAuthorize("hasAuthority('" + READ_DATA_FORMATS + "')")
public ResponseEntity<?> downloadDateFormats(String dossierTemplateId) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
Optional<DateFormatsEntity> dateFormatsOptional = dateFormatsPersistenceService.getDateFormats(dossierTemplateId);
if (dateFormatsOptional.isEmpty()) {
throw new NotFoundException(String.format("No date formats found for dossierTemplateId %s", dossierTemplateId));
}
var data = dateFormatsOptional.get().getValue().getBytes(StandardCharsets.UTF_8);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
httpHeaders.add("Content-Disposition", "attachment" + "; filename*=utf-8''" + StringEncodingUtils.urlEncode(DATE_FORMAT_FILE_NAME));
InputStream is = new ByteArrayInputStream(data);
return new ResponseEntity<>(new InputStreamResource(is), httpHeaders, HttpStatus.OK);
}
@PreAuthorize("hasAuthority('" + READ_FILE_ATTRIBUTES_CONFIG + "')")
public FileAttributeDefinitionList getFileAttributeDefinitions(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
var fileAttributeConfigs = fileAttributesController.getFileAttributesConfiguration(dossierTemplateId);
var csvImportSettings = FileAttributeDefinitionList.CsvImportSettings.builder()
.encoding(fileAttributeConfigs.getEncoding())
.delimiter(fileAttributeConfigs.getDelimiter())
.filenameMappingCsvColumnHeader(fileAttributeConfigs.getFilenameMappingColumnHeaderName())
.build();
var fileAttributeDefinitions = fileAttributeConfigs.getFileAttributeConfigs()
.stream()
.map(fileAttributeConfig -> FileAttributeDefinition.builder()
.id(fileAttributeConfig.getId())
.name(fileAttributeConfig.getLabel())
.type(fileAttributeConfig.getType())
.mappedCsvColumnHeader(fileAttributeConfig.getCsvColumnHeader())
.reportingPlaceholder(fileAttributeConfig.getPlaceholder())
.displaySettings(FileAttributeDefinition.DisplaySettings.builder()
.primaryAttribute(fileAttributeConfig.isPrimaryAttribute())
.editable(fileAttributeConfig.isEditable())
.filterable(fileAttributeConfig.isFilterable())
.displayedInFileList(fileAttributeConfig.isDisplayedInFileList())
.build())
.includeInCsvExport(fileAttributeConfig.isIncludeInCsvExport())
.build())
.toList();
return new FileAttributeDefinitionList(csvImportSettings, fileAttributeDefinitions);
}
@Override
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public ComponentMappingSummary getComponentMappingSummaries(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
List<com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentMappingMetadata> summaries = componentMappingService.getMetaDataByDossierTemplateId(
dossierTemplateId);
List<ComponentMappingMetadataModel> componentMappingMetadataModelList = componentMappingMapper.toModelList(summaries);
return new ComponentMappingSummary(dossierTemplateId, componentMappingMetadataModelList);
}
@Override
@SneakyThrows
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ComponentMappingMetadataModel uploadMapping(String dossierTemplateId, MultipartFile file, String name, String encoding, String delimiter, String quoteChar) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
if (Strings.isNullOrEmpty(file.getOriginalFilename()) || !file.getOriginalFilename().endsWith(".csv")) {
throw new BadRequestException(format("File name \"%s\" does not end with .csv", file.getOriginalFilename()));
}
String fileName = file.getOriginalFilename();
String nameToUse = Strings.isNullOrEmpty(name) ? fileName.replaceAll(".csv$", "") : name;
if (Strings.isNullOrEmpty(nameToUse)) {
throw new BadRequestException(format("The provided file name \"%s\" is not valid!", nameToUse));
}
char cleanDelimiter = getDelimiter(delimiter);
char cleanQuoteChar = getQuoteChar(quoteChar);
Path mappingFile = saveToFile(file);
try {
ComponentMappingMetadata metaData = componentMappingService.create(dossierTemplateId,
nameToUse,
fileName,
cleanDelimiter,
encoding,
mappingFile.toFile(),
cleanQuoteChar);
return componentMappingMapper.toModel(metaData);
} finally {
Files.deleteIfExists(mappingFile);
}
}
@Override
@SneakyThrows
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ComponentMappingMetadataModel updateMapping(String dossierTemplateId,
String componentMappingId,
MultipartFile file,
String name,
String encoding,
String delimiter,
String quoteChar) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
String nameToUse = validateFileName(file, name);
char cleanDelimiter = getDelimiter(delimiter);
char cleanQuoteChar = getQuoteChar(quoteChar);
Path mappingFile = saveToFile(file);
try {
ComponentMappingMetadata resultMetaData = componentMappingService.update(dossierTemplateId,
componentMappingId,
nameToUse,
encoding,
cleanDelimiter,
mappingFile.toFile(),
file.getOriginalFilename(),
cleanQuoteChar);
return componentMappingMapper.toModel(resultMetaData);
} finally {
Files.deleteIfExists(mappingFile);
}
}
private static char getDelimiter(String delimiter) {
if (Strings.isNullOrEmpty(delimiter)) {
throw new BadRequestException("The provided delimiter is not valid! Can't be null or empty.");
} else if (delimiter.length() != 1) {
throw new BadRequestException(format("The provided delimiter %s is not valid! Only a single character is allowed.", delimiter));
}
return delimiter.charAt(0);
}
private static char getQuoteChar(String quoteChar) {
if (Strings.isNullOrEmpty(quoteChar)) {
throw new BadRequestException("The provided quoteChar is not valid! Can't be null or empty.");
} else if (quoteChar.length() != 1) {
throw new BadRequestException(format("The provided quoteChar %s is not valid! Only a single character is allowed.", quoteChar));
}
return quoteChar.charAt(0);
}
private static String validateFileName(MultipartFile file, String name) {
if (Strings.isNullOrEmpty(file.getOriginalFilename()) || !file.getOriginalFilename().endsWith(".csv")) {
throw new BadRequestException(format("File name \"%s\" does not end with .csv", file.getOriginalFilename()));
}
String fileName = file.getOriginalFilename();
String nameToUse = Strings.isNullOrEmpty(name) ? fileName.replaceAll(".csv$", "") : name;
if (Strings.isNullOrEmpty(nameToUse)) {
throw new BadRequestException(format("The provided file name \"%s\" is not valid!", nameToUse));
}
return nameToUse;
}
@Override
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public ResponseEntity<?> downloadMapping(String dossierTemplateId, String componentMappingId) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
ComponentMappingDownloadModel mappingDownloadModel = componentMappingService.getMappingForDownload(dossierTemplateId, componentMappingId);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
httpHeaders.add("Content-Disposition",
"attachment"
+ "; filename*="
+ mappingDownloadModel.encoding().toLowerCase(Locale.US)
+ "''"
+ StringEncodingUtils.urlEncode(mappingDownloadModel.fileName()));
return new ResponseEntity<>(mappingDownloadModel.mappingFileResource(), httpHeaders, HttpStatus.OK);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<?> deleteMapping(String dossierTemplateId, String componentMappingId) {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
try {
componentMappingService.delete(dossierTemplateId, componentMappingId);
} catch (Exception ignored) {
}
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')")
public DossierStatusDefinitionList getDossierStatusDefinitions(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
getDossierTemplate(dossierTemplateId);
return new DossierStatusDefinitionList(dossierStatusPersistenceService.getAllDossierStatusForTemplate(dossierTemplateId)
.stream()
.map(dossierStatusInfo -> DossierStatusDefinition.builder()
.id(dossierStatusInfo.getId())
.name(dossierStatusInfo.getName())
.description(dossierStatusInfo.getDescription())
.rank(dossierStatusInfo.getRank())
.color(dossierStatusInfo.getColor())
.dossierCount(dossierStatusInfo.getDossierCount() != null ? dossierStatusInfo.getDossierCount() : 0)
.build())
.toList());
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER_ATTRIBUTES_CONFIG + "')")
public DossierAttributeDefinitionList getDossierAttributeDefinitions(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
getDossierTemplate(dossierTemplateId);
return new DossierAttributeDefinitionList(dossierAttributeConfigPersistenceService.getDossierAttributes(dossierTemplateId)
.stream()
.map(config -> DossierAttributeDefinition.builder()
.id(config.getId())
.name(config.getLabel())
.type(config.getType())
.reportingPlaceholder(config.getPlaceholder())
.displaySettings(DossierAttributeDefinition.DossierDisplaySettings.builder()
.editable(config.isEditable())
.filterable(config.isFilterable())
.displayedInDossierList(config.isDisplayedInDossierList())
.build())
.build())
.toList());
}
@PreAuthorize("hasAuthority('" + GET_REPORT_TEMPLATES + "')")
public ReportTemplateList getReportTemplates(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
getDossierTemplate(dossierTemplateId);
var templates = reportTemplatePersistenceService.findByDossierTemplateId(dossierTemplateId);
return new ReportTemplateList(templates.stream()
.map(t -> ReportTemplate.builder()
.id(t.getTemplateId())
.name(t.getFileName())
.createdOn(t.getUploadDate())
.multiFile(t.isMultiFileReport())
.preSelect(t.isActiveByDefault())
.build())
.toList());
}
@Override
public List<ComponentDefinition> createComponents(String dossierTemplateId, List<ComponentDefinitionAddRequest> componentDefinitionAddRequests) {
if (componentDefinitionAddRequests.isEmpty()) {
return Collections.emptyList();
}
List<ComponentDefinition> componentDefinitions = componentDefinitionService.createComponents(dossierTemplateId, componentDefinitionAddRequests);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Components added.")
.details(Map.of("Number of added components", componentDefinitions.size()))
.build());
return componentDefinitions;
}
@Override
public List<ComponentDefinition> getComponents(String dossierTemplateId, boolean includeSoftDeleted) {
return componentDefinitionService.getComponentsByDossierTemplateId(dossierTemplateId, includeSoftDeleted);
}
@Override
public ComponentDefinition getComponent(String dossierTemplateId, String componentId) {
return componentDefinitionService.getComponentByDossierTemplateIdAndComponentId(dossierTemplateId, componentId);
}
@Override
public List<ComponentDefinition> updateComponents(String dossierTemplateId, List<ComponentDefinitionUpdateRequest> componentDefinitionUpdateRequests) {
if (componentDefinitionUpdateRequests.isEmpty()) {
return Collections.emptyList();
}
List<ComponentDefinition> componentDefinitions = componentDefinitionService.updateComponents(dossierTemplateId, componentDefinitionUpdateRequests);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Components updated.")
.details(Map.of("Number of updated components", componentDefinitions.size()))
.build());
return componentDefinitions;
}
@Override
public void deleteComponents(String dossierTemplateId, List<String> componentIds) {
List<ComponentDefinitionEntity> components = componentDefinitionService.deleteComponents(dossierTemplateId, componentIds);
if (!components.isEmpty()) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Components deleted.")
.details(Map.of("Number of deleted components", components.size()))
.build());
}
}
@Override
public List<ComponentDefinition> restoreComponents(String dossierTemplateId, List<String> componentIds) {
List<ComponentDefinition> components = componentDefinitionService.restoreComponents(dossierTemplateId, componentIds);
if (!components.isEmpty()) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Components restored.")
.details(Map.of("Number of restored components", components.size()))
.build());
}
return components;
}
@Override
public List<ComponentDefinition> reorderComponents(String dossierTemplateId, List<String> componentIds) {
List<ComponentDefinition> orderedComponents = componentDefinitionService.reorderComponents(dossierTemplateId, componentIds);
if (!orderedComponents.isEmpty()) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Components reordered.")
.details(Map.of("Number of reordered components", orderedComponents.size()))
.build());
}
return orderedComponents;
}
@SneakyThrows
private static Path saveToFile(MultipartFile file) {
Path mappingFile = Files.createTempFile(file.getName(), ".csv");
try (var out = new FileOutputStream(mappingFile.toFile())) {
out.write(file.getBytes());
}
return mappingFile;
}
private ResponseEntity<?> downloadRules(String dossierTemplateId, RuleFileType ruleFileType) {
Optional<RuleSetEntity> ruleEntityOptional = rulesPersistenceService.getRules(dossierTemplateId, ruleFileType);
if (ruleEntityOptional.isEmpty()) {
throw new NotFoundException(String.format("No rule file of type %s found for dossierTemplateId %s", ruleFileType, dossierTemplateId));
}
var data = ruleEntityOptional.get().getValue().getBytes(StandardCharsets.UTF_8);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
httpHeaders.add("Content-Disposition",
"attachment" + "; filename*=utf-8''" + StringEncodingUtils.urlEncode(ruleFileType.name().toLowerCase(Locale.ROOT) + RULES_DOWNLOAD_FILE_NAME_SUFFIX));
InputStream is = new ByteArrayInputStream(data);
return new ResponseEntity<>(new InputStreamResource(is), httpHeaders, HttpStatus.OK);
}
@SneakyThrows
private ResponseEntity<DroolsValidationResponse> uploadRules(String dossierTemplateId, RuleFileType ruleFileType, MultipartFile file, boolean dryRun) {
var rulesUploadRequest = RulesUploadRequest.builder()
.rules(new String(file.getBytes(), StandardCharsets.UTF_8))
.dossierTemplateId(dossierTemplateId)
.ruleFileType(ruleFileType)
.build();
DroolsValidationResponse rulesValidationResponse = new DroolsValidationResponse();
try {
var droolsValidation = rulesValidationService.validateRules(rulesUploadRequest.getRuleFileType(), rulesUploadRequest.getRules());
rulesValidationResponse = RulesValidationMapper.createFromDroolsValidation(droolsValidation);
if (!droolsValidation.isCompiled()) {
return new ResponseEntity<>(rulesValidationResponse, !dryRun ? HttpStatus.UNPROCESSABLE_ENTITY : HttpStatus.OK);
}
} catch (FeignException e) {
if (e.status() == HttpStatus.BAD_REQUEST.value()) {
throw new BadRequestException("The provided rule string is not a valid drools rule file!");
}
}
if (!dryRun) {
rulesPersistenceService.setRules(rulesUploadRequest.getRules(), rulesUploadRequest.getDossierTemplateId(), rulesUploadRequest.getRuleFileType());
}
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(rulesUploadRequest.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message(format("%s rules have been %s", rulesUploadRequest.getRuleFileType(), dryRun ? "validated" : "updated"))
.build());
return new ResponseEntity<>(rulesValidationResponse, HttpStatus.OK);
}
}

View File

@ -1,126 +0,0 @@
package com.iqser.red.persistence.service.v2.external.api.impl.controller;
import java.util.List;
import java.util.Optional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DownloadController;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.ReportTemplateEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DownloadStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.model.User;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RemoveDownloadRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.DownloadStatus;
import com.iqser.red.service.persistence.service.v2.api.external.model.DownloadStatusList;
import com.iqser.red.service.persistence.service.v2.api.external.resource.DownloadResource;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
@Tag(name = "5. Downloads", description = "Operations related to download packages.")
public class DownloadControllerV2 implements DownloadResource {
private final DownloadController downloadController;
private final DownloadStatusPersistenceService downloadStatusPersistenceService;
private final UserService userService;
@Transactional
public DownloadStatusList getDownloadStatusList() {
validateUserRoles(KeycloakSecurity.getUserId());
var downloads = downloadStatusPersistenceService.getStatusesByUser(KeycloakSecurity.getUserId());
return new DownloadStatusList(downloads.stream().map(
status ->
DownloadStatus.builder()
.id(status.getUuid()) // This is a workaround the real id is the storageId.
.userId(status.getUserId())
.filename(status.getFilename())
.mimeType(status.getMimeType())
.errorCause(status.getErrorCause())
.status(status.getStatus())
.creationDate(status.getCreationDate())
.lastDownload(status.getLastDownload())
.fileSize(status.getFileSize())
.dossierId(status.getDossier() != null ? status.getDossier().getId() : null)
.fileIds(status.getFiles()
.stream()
.map(FileEntity::getId)
.toList())
.downloadFileTypes(status.getDownloadFileTypes()
.stream()
.toList())
.reportTemplateIds(status.getReports().stream().map(ReportTemplateEntity::getTemplateId).toList())
.build()).toList()
);
}
@Transactional
public DownloadStatus getDownloadStatus(@PathVariable(DOWNLOAD_ID_PARAM) String downloadId) {
validateUserRoles(KeycloakSecurity.getUserId());
var status = downloadStatusPersistenceService.getStatusesByUuid(downloadId);
return DownloadStatus.builder()
.id(status.getUuid()) // This is a workaround the real id is the storageId.
.userId(status.getUserId())
.filename(status.getFilename())
.mimeType(status.getMimeType())
.errorCause(status.getErrorCause())
.status(status.getStatus())
.creationDate(status.getCreationDate())
.lastDownload(status.getLastDownload())
.fileSize(status.getFileSize())
.dossierId(status.getDossier().getId())
.fileIds(status.getFiles()
.stream()
.map(FileEntity::getId)
.toList())
.downloadFileTypes(status.getDownloadFileTypes()
.stream()
.toList())
.reportTemplateIds(status.getReports().stream().map(ReportTemplateEntity::getTemplateId).toList())
.build();
}
public void deleteDownload(@PathVariable(DOWNLOAD_ID_PARAM) String downloadId) {
validateUserRoles(KeycloakSecurity.getUserId());
var status = downloadStatusPersistenceService.getStatusesByUuid(downloadId);
downloadController.deleteDownloadStatus(new RemoveDownloadRequest(List.of(status.getStorageId())));
}
public void download(@PathVariable(DOWNLOAD_ID_PARAM) String downloadId) {
validateUserRoles(KeycloakSecurity.getUserId());
var status = downloadStatusPersistenceService.getStatusesByUuid(downloadId);
downloadController.downloadFile(status.getStorageId());
}
private void validateUserRoles(String userId) {
Optional<User> userOptional = userService.getUserById(userId);
if (userOptional.isPresent()) {
if (userOptional.get().getRoles()
.stream()
.noneMatch(ApplicationRoles.VALID_MEMBER_ROLES::contains)) {
throw new NotAllowedException("User doesn't have appropriate roles");
}
}
}
}

View File

@ -1,205 +0,0 @@
package com.iqser.red.persistence.service.v2.external.api.impl.controller;
import java.time.OffsetDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.iqser.red.commons.spring.ErrorMessage;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.mchange.rmi.NotAuthorizedException;
import io.swagger.v3.oas.annotations.Hidden;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ExternalControllerAdviceV2 {
private final Scheduler scheduler;
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.NOT_FOUND)
@ExceptionHandler(value = NotFoundException.class)
public ErrorMessage handleContentNotFoundException(NotFoundException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
/* error handling */
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = BadRequestException.class)
public ErrorMessage handleBadRequestException(BadRequestException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.CONFLICT)
@ExceptionHandler(value = {ConflictException.class})
protected ErrorMessage handleConflictException(ConflictException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@ResponseBody
@ResponseStatus(value = HttpStatus.FORBIDDEN)
@ExceptionHandler({AccessDeniedException.class})
public ErrorMessage handleAccessDeniedException(AccessDeniedException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@ResponseBody
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
@ExceptionHandler({NotAuthorizedException.class})
public ErrorMessage handleNotAuthorizedException(NotAuthorizedException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@ResponseBody
@ResponseStatus(value = HttpStatus.FORBIDDEN)
@ExceptionHandler({NotAllowedException.class})
public ErrorMessage handleNotAllowedException(NotAllowedException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler({org.springframework.security.acls.model.NotFoundException.class})
public ErrorMessage handleACLNotFound(org.springframework.security.acls.model.NotFoundException e) {
// in case this error occurs on a rest request / force trigger the sync job
try {
scheduler.triggerJob(new JobKey("SyncUserPermissionsJob"), new JobDataMap(Map.of("tenantId", TenantContext.getTenantId())));
} catch (SchedulerException ex) {
log.debug("Failed to force trigger SyncUserPermissionsJob", ex);
}
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler({MethodArgumentNotValidException.class})
public ErrorMessage handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
var errorList = e.getFieldErrors();
String errorListAsString = errorList.stream()
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
.collect(Collectors.joining(", "));
return new ErrorMessage(OffsetDateTime.now(), String.format("You have empty/wrong formatted parameters: %s", errorListAsString));
}
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler({MissingServletRequestPartException.class})
public ErrorMessage handleMissingServletRequestPartException(MissingServletRequestPartException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@Hidden
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({HttpMessageNotReadableException.class})
public ErrorMessage handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
var cause = e.getCause();
if (cause instanceof InvalidFormatException) {
InvalidFormatException invalidFormatException = (InvalidFormatException) cause;
Class<?> targetType = invalidFormatException.getTargetType();
if (targetType != null && targetType.isEnum()) {
return new ErrorMessage(OffsetDateTime.now(), String.format("Unsupported value for %s", targetType.getSimpleName()));
}
return new ErrorMessage(OffsetDateTime.now(), cause.getMessage());
}
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@Hidden
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = DataIntegrityViolationException.class)
public ErrorMessage handleDataIntegrityViolationException(DataIntegrityViolationException exception) {
String message = Objects.requireNonNull(exception.getRootCause()).getMessage();
if (message.contains("uq_component_definition_technical_name_template")) {
message = "A component with the same technical name already exists in the given dossier template.";
} else if (message.contains("value too long for type character varying")) {
message = "Value is too long. Please use a shorter value.";
} else {
message = "Database error occurred.";
}
return new ErrorMessage(OffsetDateTime.now(), message);
}
@Order(10000)
public static class BinderControllerAdvice {
@InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
// This code protects Spring Core from a "Remote Code Execution" attack (dubbed "Spring4Shell").
// By applying this mitigation, you prevent the "Class Loader Manipulation" attack vector from firing.
// For more details, see this post: https://www.lunasec.io/docs/blog/spring-rce-vulnerabilities/
String[] denylist = new String[]{"class.*", "Class.*", "*.class.*", "*.Class.*"};
dataBinder.setDisallowedFields(denylist);
}
}
}

View File

@ -1,209 +0,0 @@
package com.iqser.red.persistence.service.v2.external.api.impl.controller;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierResource.DOSSIER_ID_PARAM;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierResource.INCLUDE_ACTIVE_PARAM;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierResource.INCLUDE_ARCHIVED_PARAM;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierResource.INCLUDE_SOFT_DELETED_PARAM;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierTemplateResource.DOSSIER_TEMPLATE_ID_PARAM;
import java.util.ArrayList;
import java.util.List;
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.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DossierController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DossierTemplateController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DownloadController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.FileAttributesController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.FileManagementController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.StatusController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.UploadController;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
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.persistence.DownloadStatusPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttributes;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileUploadResult;
import com.iqser.red.service.persistence.service.v1.api.shared.model.PrepareDownloadWithOptionRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.BulkDownloadRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.DownloadRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.DownloadStatus;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileDeleteRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileStatusList;
import com.iqser.red.service.persistence.service.v2.api.external.resource.FileResource;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
@Tag(name = "3. File endpoints", description = "Provides operations related to files")
public class FileControllerV2 implements FileResource {
private final UploadController uploadController;
private final StatusController statusController;
private final DossierController dossierController;
private final FileManagementController fileManagementController;
private final FileStatusManagementService fileStatusManagementService;
private final FileAttributesController fileAttributesController;
private final DossierTemplateController dossierTemplateController;
private final DownloadController downloadController;
private final DownloadStatusPersistenceService downloadStatusPersistenceService;
public FileUploadResult upload(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestPart(name = FILE_PARAM) MultipartFile file,
@RequestParam(value = KEEP_MANUAL_CHANGES_PARAM, required = false, defaultValue = "false") boolean keepManualChanges,
@RequestParam(value = DISABLE_AUTOMATIC_ANALYSIS_PARAM, required = false, defaultValue = "false") boolean disableAutomaticAnalysis) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
return uploadController.upload(file, dossierId, keepManualChanges, disableAutomaticAnalysis);
}
public FileStatusList getDossierStatus(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestParam(name = INCLUDE_ACTIVE_PARAM, defaultValue = "true", required = false) boolean includeActive,
@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_SOFT_DELETED_PARAM, defaultValue = "false", required = false) boolean includeSoftDeleted) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
dossierController.getDossier(dossierId, includeArchived, includeSoftDeleted);
List<FileStatus> fileStatusList = new ArrayList<>();
if (!includeArchived && dossierController.getDossier(dossierId, false, includeSoftDeleted).getArchivedTime() != null) {
return new FileStatusList(fileStatusList);
}
if (includeActive) {
fileStatusList.addAll(statusController.getDossierStatus(dossierId));
}
if (includeSoftDeleted) {
fileStatusList.addAll(statusController.getSoftDeletedDossierStatus(dossierId));
}
return new FileStatusList(fileStatusList);
}
public FileStatus getFileStatus(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId,
@RequestParam(name = INCLUDE_SOFT_DELETED_PARAM, defaultValue = "false", required = false) boolean includeSoftDeleted) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
dossierController.getDossier(dossierId, true, includeSoftDeleted);
return FileStatusMapper.toFileStatus(fileStatusManagementService.getFileStatus(fileId, includeSoftDeleted));
}
public void deleteFile(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId,
@RequestParam(name = DELETE_PERMANENTLY_PARAM, defaultValue = "false", required = false) boolean deletePermanently) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
if (deletePermanently) {
fileManagementController.hardDeleteFiles(dossierId, List.of(fileId));
} else {
fileManagementController.deleteFile(dossierId, fileId);
}
}
public void deleteFiles(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestBody FileDeleteRequest fileDeleteRequest,
@RequestParam(name = DELETE_PERMANENTLY_PARAM, defaultValue = "false", required = false) boolean deletePermanently) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
if (deletePermanently) {
fileManagementController.hardDeleteFiles(dossierId, new ArrayList<>(fileDeleteRequest.getFileIds()));
} else {
fileManagementController.deleteFiles(dossierId, fileDeleteRequest.getFileIds());
}
}
public void setFileAttributes(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId,
@RequestBody FileAttributes fileAttributes) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
fileAttributesController.setFileAttributes(dossierId, fileId, fileAttributes);
}
public DownloadStatus prepareFileDownload(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId,
@RequestBody DownloadRequest downloadRequest) {
return prepareBulkDownload(dossierTemplateId,
dossierId,
BulkDownloadRequest.builder()
.reportTemplateIds(downloadRequest.getReportTemplateIds())
.downloadFileTypes(downloadRequest.getDownloadFileTypes())
.redactionPreviewColor(downloadRequest.getRedactionPreviewColor())
.fileIds(List.of(fileId))
.build());
}
public DownloadStatus prepareBulkDownload(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestBody BulkDownloadRequest bulkDownloadRequest) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
var storageId = downloadController.prepareDownload(PrepareDownloadWithOptionRequest.builder()
.dossierId(dossierId)
.fileIds(bulkDownloadRequest.getFileIds())
.reportTemplateIds(bulkDownloadRequest.getReportTemplateIds())
.downloadFileTypes(bulkDownloadRequest.getDownloadFileTypes())
.redactionPreviewColor(bulkDownloadRequest.getRedactionPreviewColor())
.includeUnprocessed(false)
.build()).getStorageId();
var status = downloadStatusPersistenceService.getStatus(storageId);
return DownloadStatus.builder()
.id(status.getUuid()) // This is a workaround the real is the storageId.
.userId(status.getUserId())
.filename(status.getFilename())
.mimeType(status.getMimeType())
.errorCause(status.getErrorCause())
.status(status.getStatus())
.creationDate(status.getCreationDate())
.lastDownload(status.getLastDownload())
.fileSize(status.getFileSize())
.dossierId(status.getDossier().getId())
.fileIds(status.getFiles()
.stream()
.map(FileEntity::getId)
.toList())
.downloadFileTypes(status.getDownloadFileTypes()
.stream()
.toList())
.reportTemplateIds(bulkDownloadRequest.getReportTemplateIds())
.build();
}
}

View File

@ -1,27 +0,0 @@
package com.iqser.red.persistence.service.v2.external.api.impl.controller;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.LicenseReportController;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.LicenseReport;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.LicenseReportRequest;
import com.iqser.red.service.persistence.service.v2.api.external.resource.LicenseResource;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@Tag(name = "7. License", description = "Operations related to license information and usage metrics.")
public class LicenseControllerV2 implements LicenseResource {
private final LicenseReportController licenseReportController;
public LicenseReport getReport(@RequestBody LicenseReportRequest reportRequest) {
return licenseReportController.getReport(reportRequest);
}
}

View File

@ -1,75 +0,0 @@
package com.iqser.red.persistence.service.v2.external.api.impl.mapper;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog;
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.v2.api.external.model.Component;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentValue;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileComponents;
@Mapper
public interface ComponentMapper {
ComponentMapper INSTANCE = Mappers.getMapper(ComponentMapper.class);
@Mapping(target = "entityReferences", source = "componentLogEntityReferences")
ComponentValue toComponentValue(ComponentLogEntryValue entry);
@Mapping(target = "componentLogEntityReferences", source = "entityReferences")
ComponentLogEntryValue toComponentLogEntry(ComponentValue value);
List<ComponentValue> toComponentValues(List<ComponentLogEntryValue> entries);
List<ComponentLogEntryValue> toComponentLogEntries(List<ComponentValue> values);
@Mapping(target = "componentValues", source = "values")
Component toComponent(ComponentLogEntry entry);
List<Component> toComponents(List<ComponentLogEntry> entries);
@Mapping(target = "values", source = "componentValues")
ComponentLogEntry toComponentLogEntry(Component component);
default FileComponents toFileComponents(ComponentLog componentLog, String dossierTemplateId, String dossierId, String fileId, String fileName, boolean includeDetails) {
Map<String, List<String>> basicComponent = new LinkedHashMap<>();
for (ComponentLogEntry componentLogEntry : componentLog.getComponentLogEntries()) {
basicComponent.put(componentLogEntry.getName(),
componentLogEntry.getValues()
.stream()
.map(ComponentLogEntryValue::getValue)
.toList());
}
Map<String, Component> componentsDetails = new LinkedHashMap<>();
if (includeDetails) {
for (ComponentLogEntry entry : componentLog.getComponentLogEntries()) {
componentsDetails.put(entry.getName(), toComponent(entry));
}
}
return FileComponents.builder()
.dossierTemplateId(dossierTemplateId)
.dossierId(dossierId)
.filename(fileName)
.fileId(fileId)
.components(basicComponent)
.componentDetails(componentsDetails)
.build();
}
}

View File

@ -1,28 +0,0 @@
package com.iqser.red.persistence.service.v2.external.api.impl.mapper;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentMappingMetadata;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentMappingMetadataModel;
@Mapper
public interface ComponentMappingMapper {
ComponentMappingMapper INSTANCE = Mappers.getMapper(ComponentMappingMapper.class);
ComponentMappingMetadataModel toModel(ComponentMappingMetadata componentMappingMetadata);
List<ComponentMappingMetadataModel> toModelList(List<ComponentMappingMetadata> componentMappingMetadata);
ComponentMappingMetadata toDto(ComponentMappingMetadataModel componentMappingMetadataModel);
List<ComponentMappingMetadata> toDtoList(List<ComponentMappingMetadataModel> componentMappingMetadataModels);
}

View File

@ -1,16 +0,0 @@
package com.iqser.red.persistence.service.v2.external.api.impl;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class IdentityTest {
@Test
public void mockTest() {
int i = 1;
assertThat(i).isEqualTo(1);
}
}

View File

@ -1,37 +0,0 @@
plugins {
id("com.iqser.red.service.java-conventions")
}
dependencies {
api(project(":persistence-service-internal-api-v1"))
api("com.iqser.red.service:pdftron-redaction-service-api-v1:${rootProject.extra.get("pdftronRedactionServiceVersion")}") {
exclude(group = "com.iqser.red.service", module = "persistence-service-internal-api-v1")
exclude(group = "com.iqser.red.service", module = "persistence-service-shared-api-v1")
}
api("com.iqser.red.service:redaction-service-api-v1:${rootProject.extra.get("redactionServiceVersion")}") {
exclude(group = "com.iqser.red.service", module = "persistence-service-internal-api-v1")
exclude(group = "com.iqser.red.service", module = "persistence-service-shared-api-v1")
}
api("com.iqser.red.service:redaction-report-service-api-v1:${rootProject.extra.get("redactionReportServiceVersion")}") {
exclude(group = "com.iqser.red.service", module = "persistence-service-internal-api-v1")
exclude(group = "com.iqser.red.service", module = "persistence-service-shared-api-v1")
}
api("com.iqser.red.service:search-service-api-v1:${rootProject.extra.get("searchServiceVersion")}") {
exclude(group = "com.iqser.red.service", module = "persistence-service-internal-api-v1")
exclude(group = "com.iqser.red.service", module = "persistence-service-shared-api-v1")
}
api("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.2")
api("com.google.guava:guava:31.1-jre")
api("org.springframework.boot:spring-boot-starter-security:3.1.3")
api("org.springframework.boot:spring-boot-starter-validation:3.1.3")
api("com.iqser.red.commons:jackson-commons:2.1.0")
api(project(":persistence-service-shared-api-v1"))
testImplementation("com.iqser.red.commons:test-commons:2.1.0")
testImplementation("org.springframework.boot:spring-boot-starter-test:3.0.4")
compileOnly("org.springdoc:springdoc-openapi-ui:1.6.13")
api("io.github.openfeign:feign-core:12.2")
compileOnly("org.springframework:spring-web:6.0.6")
}
description = "persistence-service-external-api-v1"

View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>persistence-service-v1</artifactId>
<groupId>com.iqser.red.service</groupId>
<version>2.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>persistence-service-external-api-v1</artifactId>
<dependencies>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>persistence-service-internal-api-v1</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>com.iqser.red.service</groupId>
<artifactId>redaction-service-api-v1</artifactId>
</exclusion>
<exclusion>
<groupId>com.iqser.red.service</groupId>
<artifactId>pdftron-redaction-service-api-v1</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>pdftron-redaction-service-api-v1</artifactId>
<exclusions>
<exclusion>
<groupId>com.iqser.red.service</groupId>
<artifactId>redaction-service-api-v1</artifactId>
</exclusion>
<exclusion>
<groupId>com.iqser.red.service</groupId>
<artifactId>persistence-service-api-v1</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>redaction-service-api-v1</artifactId>
<exclusions>
<exclusion>
<groupId>com.iqser.red.service</groupId>
<artifactId>pdftron-redaction-service-api-v1</artifactId>
</exclusion>
<exclusion>
<groupId>com.iqser.red.service</groupId>
<artifactId>persistence-service-api-v1</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>redaction-report-service-api-v1</artifactId>
<exclusions>
<exclusion>
<groupId>com.iqser.red.service</groupId>
<artifactId>persistence-service-api-v1</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>search-service-api-v1</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.13.4</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- spring -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.iqser.red.commons</groupId>
<artifactId>jackson-commons</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>com.iqser.red.commons</groupId>
<artifactId>test-commons</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>persistence-service-shared-api-v1</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,7 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.external.model;
import java.util.List;
public record UpdateEntries(List<String> entriesToAdd, List<String> entriesToDelete) {
}

View File

@ -1,5 +1,6 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;

View File

@ -1,7 +1,6 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import java.util.List;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -17,18 +16,15 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.service.persistence.service.v1.api.external.model.UpdateEntries;
import com.iqser.red.service.persistence.service.v1.api.shared.model.CreateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.Dictionary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.UpdateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dictionary.Dictionary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dictionary.DictionaryDifferenceResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.Colors;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -47,21 +43,16 @@ public interface DictionaryResource {
String ENTRY_PATH_VARIABLE = "/{" + ENTRY_PARAMETER_NAME + "}";
String DOSSIER_TEMPLATE_PARAMETER_NAME = "dossierTemplateId";
String DOSSIER_TEMPLATE_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_PARAMETER_NAME + "}";
String DOSSIER_TEMPLATE_PATH_VARIABLE = "/{dossierTemplateId}";
String UPLOAD = "/upload";
String DOWNLOAD = "/download";
String MERGED = "/merged";
String DELETE = "/delete";
String DIFFERENCE = "/difference";
String UPDATE = "/update";
String UPDATE_FLAG = "/updateFlag";
String COLOR_REST_PATH = ExternalApi.BASE_PATH + "/color";
String DOSSIER_ID_PARAMETER_NAME = "dossierId";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID_PARAMETER_NAME + "}";
String INCLUDE_DELETED_PARAMETER_NAME = "includeDeleted";
@ -72,7 +63,7 @@ public interface DictionaryResource {
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = DICTIONARY_REST_PATH + TYPE_PATH_VARIABLE + DOSSIER_TEMPLATE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Add dictionary entries with entry type.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully added the dictionary entries."), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier or the entry type is not found."), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully added the dictionary entries."), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The " + "entry type is not found.")})
void addEntry(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestBody List<String> entries,
@ -84,7 +75,7 @@ public interface DictionaryResource {
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = DICTIONARY_REST_PATH + DELETE + TYPE_PATH_VARIABLE + DOSSIER_TEMPLATE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Delete dictionary entries with entry type.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully deleted the dictionary entries."), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier or the entry type is not found."), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully deleted the dictionary entries."), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The " + "entry type is not found.")})
void deleteEntries(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestBody List<String> entries,
@ -95,7 +86,7 @@ public interface DictionaryResource {
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(value = DICTIONARY_REST_PATH + TYPE_PATH_VARIABLE + DOSSIER_TEMPLATE_PATH_VARIABLE + ENTRY_PATH_VARIABLE)
@Operation(summary = "Delete dictionary entry with entry type.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully deleted the dictionary entry."), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier or the entry type is not found."), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully deleted the dictionary entry."), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The " + "entry type is not found.")})
void deleteEntry(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@PathVariable(ENTRY_PARAMETER_NAME) String entry,
@ -103,17 +94,6 @@ public interface DictionaryResource {
@RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType);
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = DICTIONARY_REST_PATH + UPDATE + TYPE_PATH_VARIABLE + DOSSIER_TEMPLATE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Update dictionary entries with type.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully updated the dictionary entries."), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier or the entry type is not found."), @ApiResponse(responseCode = "403", description = "Forbidden")})
void updateEntries(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestBody UpdateEntries updateEntries,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
@RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType);
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = TYPE_PATH + TYPE_PATH_VARIABLE + DOSSIER_TEMPLATE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Updates colors, hint and caseInsensitive of an entry type. Only label, colors and description are updatable for system managed entry types", description = "None")
@ -126,8 +106,7 @@ public interface DictionaryResource {
@ResponseStatus(HttpStatus.OK)
@PostMapping(value = TYPE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Creates entry type with colors, hint and caseInsensitive", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully created the entry type with colors, hint "
+ "and caseInsensitive"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "409", description = "The entry type already exists, could not be added again.")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully created the entry type with colors, hint " + "and caseInsensitive"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "409", description = "The entry type already exists, could not be added again.")})
TypeValue addType(@RequestBody CreateTypeValue typeValue);
@ -135,19 +114,21 @@ public interface DictionaryResource {
@DeleteMapping(value = TYPE_PATH + TYPE_PATH_VARIABLE + DOSSIER_TEMPLATE_PATH_VARIABLE)
@Operation(summary = "Deletes entry type", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully deleted the entry type with value and all its entries"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The entry type is not found.")})
void deleteType(@PathVariable(TYPE_PARAMETER_NAME) String type, @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
void deleteType(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = TYPE_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + DELETE)
@Operation(summary = "Deletes entry types", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully deleted the entry types with value and all their entries"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The entry type is not found.")})
void deleteTypes(@RequestBody List<String> types, @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
void deleteTypes(@RequestBody List<String> types,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
@GetMapping(value = TYPE_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Retrieve all entry types", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully retrieved all the entry types"), @ApiResponse(responseCode = "404", description = "Not found")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully retrieved all the entry types")})
TypeResponse getAllTypes(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
@RequestParam(value = INCLUDE_DELETED_PARAMETER_NAME, required = false, defaultValue = "false") boolean includeDeleted);
@ -155,8 +136,7 @@ public interface DictionaryResource {
@GetMapping(value = DICTIONARY_REST_PATH + TYPE_PATH_VARIABLE + DOSSIER_TEMPLATE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Retrieves all dictionary entries of an entry type", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully retrieved all the dictionary entries of "
+ "the entry type."), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier or the entry type is not found.")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully retrieved all the dictionary entries of " + "the entry type."), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The entry type is not found.")})
Dictionary getDictionaryForType(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId);
@ -171,8 +151,8 @@ public interface DictionaryResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = DICTIONARY_REST_PATH + UPLOAD + TYPE_PATH_VARIABLE + DOSSIER_TEMPLATE_PATH_VARIABLE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Upload a text-file with 1 entry per line and add each line as an entry to a dictionary for a specific type")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Dictionary upload successful."), @ApiResponse(responseCode = "400", description = "Dictionary could not be uploaded."), @ApiResponse(responseCode = "404", description = "The dossier is not found."), @ApiResponse(responseCode = "403", description = "Forbidden")})
void uploadDictionary(@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file", required = false) MultipartFile file,
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Dictionary upload successful."), @ApiResponse(responseCode = "400", description = "Dictionary could not be uploaded.")})
void uploadDictionary(@RequestPart(name = "file", required = false) MultipartFile file,
@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
@ -186,21 +166,19 @@ public interface DictionaryResource {
*/
@ResponseBody
@Operation(summary = "Returns file containing the the dictionary entries for given type..")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "The dossier is not found.")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@GetMapping(value = DICTIONARY_REST_PATH + DOWNLOAD + TYPE_PATH_VARIABLE + DOSSIER_TEMPLATE_PATH_VARIABLE)
ResponseEntity<?> downloadDictionary(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
@RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType);
@GetMapping(value = DICTIONARY_REST_PATH + MERGED + TYPE_PATH_VARIABLE + DOSSIER_TEMPLATE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = DICTIONARY_REST_PATH + MERGED + TYPE_PATH_VARIABLE+ DOSSIER_TEMPLATE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Retrieves the merged dictionary for the given type, dossier template and dossier", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully retrieved all the dictionary entries"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier or the entry type is not found.")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully retrieved all the dictionary entries"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The entry type is not found.")})
Dictionary getMergedDictionaries(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME) String dossierId);
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME) String dossierId);
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(summary = "Set system colors for redaction")
@ -215,20 +193,4 @@ public interface DictionaryResource {
@GetMapping(value = COLOR_REST_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
Colors getColors(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = DICTIONARY_REST_PATH + UPDATE_FLAG + TYPE_PATH_VARIABLE + DOSSIER_TEMPLATE_PATH_VARIABLE + DOSSIER_ID_PATH_VARIABLE)
@Operation(summary = "Updates flags regarding to selected type, dossier and dossier template.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully updated the flags of the type."), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The entry type is not found.")})
void changeFlags(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAMETER_NAME) String dossierId,
@RequestParam(value = "addToDictionaryAction") boolean addToDictionaryAction);
@PostMapping(value = DICTIONARY_REST_PATH + DIFFERENCE + DOSSIER_TEMPLATE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Returns the difference between the dictionaries of the dossier template and all the dossiers inside the template for a list of given types.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully returned DictionaryDifferenceResponse."), @ApiResponse(responseCode = "400", description = "The request is not valid.")})
DictionaryDifferenceResponse getDictionaryDifference(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody Set<String> types);
}
}

View File

@ -1,58 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ResponseStatus(value = HttpStatus.OK)
public interface DocumentResource {
String DOCUMENT_TEXT_PATH = ExternalApi.BASE_PATH + "/documentText";
String DOCUMENT_POSITIONS_PATH = ExternalApi.BASE_PATH + "/documentPositions";
String DOCUMENT_PAGES_PATH = ExternalApi.BASE_PATH + "/documentPages";
String DOCUMENT_STRUCTURE_PATH = ExternalApi.BASE_PATH + "/documentStructure";
String SIMPLIFIED_SECTION_TEXT_PATH = ExternalApi.BASE_PATH + "/simplifiedSectionText";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String DOSSIER_ID = "dossierId";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID + "}";
@GetMapping(value = DOCUMENT_TEXT_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Gets the text blocks of a document for a fileId", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / document text is not found.")})
ResponseEntity<?> getDocumentText(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@GetMapping(value = DOCUMENT_POSITIONS_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Gets the positions of the text blocks of a document for a fileId", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier /file / document positions is not found.")})
ResponseEntity<?> getDocumentPositions(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@GetMapping(value = DOCUMENT_STRUCTURE_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Gets the document structure for a fileId", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / document structure is not found.")})
ResponseEntity<?> getDocumentStructure(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@GetMapping(value = DOCUMENT_PAGES_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Gets the page information of a document for a fileId", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / page information is not found.")})
ResponseEntity<?> getDocumentPages(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@GetMapping(value = SIMPLIFIED_SECTION_TEXT_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Gets the simplified section text for a fileId", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / simplified section text is not found.")})
ResponseEntity<?> getSimplifiedSectionText(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
}

View File

@ -48,9 +48,7 @@ public interface DossierAttributesResource {
@Operation(summary = "Set dossier attributes base configuration.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@PutMapping(value = DOSSIER_ATTRIBUTES_PATH
+ CONFIG_PATH
+ DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PutMapping(value = DOSSIER_ATTRIBUTES_PATH + CONFIG_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
DossierAttributesConfig setDossierAttributesConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody DossierAttributesConfig dossierAttributesConfig);
@ -58,9 +56,7 @@ public interface DossierAttributesResource {
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Add or update a dossier attribute in base configuration.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@PostMapping(value = DOSSIER_ATTRIBUTES_PATH
+ CONFIG_PATH
+ DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = DOSSIER_ATTRIBUTES_PATH + CONFIG_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
DossierAttributeConfig addOrUpdateDossierAttributeConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody DossierAttributeConfig dossierAttributes);
@ -89,7 +85,7 @@ public interface DossierAttributesResource {
@Operation(summary = "Set dossier attributes to an existing dossier", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@PostMapping(value = DOSSIER_ATTRIBUTES_PATH + SET_PATH + DOSSIER_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
DossierAttributes setDossierAttributes(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody DossierAttributes dossierAttributes);
@ -98,7 +94,7 @@ public interface DossierAttributesResource {
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Add or update a dossier attribute in existing dossier.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@PostMapping(value = DOSSIER_ATTRIBUTES_PATH + UPDATE_PATH + DOSSIER_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
DossierAttributes addOrUpdateDossierAttribute(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody DossierAttribute dossierAttribute);
@ -106,7 +102,7 @@ public interface DossierAttributesResource {
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Get the dossier attributes.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@GetMapping(value = DOSSIER_ATTRIBUTES_PATH + DOSSIER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
DossierAttributes getDossierAttributes(@PathVariable(DOSSIER_ID) String dossierId);
@ -115,7 +111,7 @@ public interface DossierAttributesResource {
@ResponseBody
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(summary = "Delete a specific dossier attribute.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "NO_CONTENT"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "NO_CONTENT")})
@DeleteMapping(value = DOSSIER_ATTRIBUTES_PATH + SET_PATH + DOSSIER_ID_PATH_VARIABLE + DOSSIER_ATTRIBUTE_ID_PATH)
void deleteDossierAttribute(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(DOSSIER_ATTRIBUTE_ID) String dossierAttributeId);

View File

@ -2,7 +2,6 @@ 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;
@ -18,7 +17,6 @@ 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;
@ -31,12 +29,10 @@ 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";
@ -66,24 +62,18 @@ 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)
@Operation(summary = "Creates or updates a dossier.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully saved the dossier."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID provided or attempted to change dossier-template for a dossier with files."), @ApiResponse(responseCode = "409", description = "Duplicate"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully saved the dossier."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID provided or attempted to change dossier-template for a dossier with files."), @ApiResponse(responseCode = "409", description = "Duplicate")})
ResponseEntity<Dossier> createDossierOrUpdateDossier(@RequestBody DossierRequest dossier);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@DeleteMapping(value = DOSSIER_REST_PATH + DOSSIER_ID_PATH_PARAM)
@Operation(summary = "Deletes an existing dossier.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully deleted the dossier."), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully deleted the dossier."), @ApiResponse(responseCode = "404", description = "Not found")})
void deleteDossier(@PathVariable(DOSSIER_ID_PARAM) String dossierId);
@ -105,13 +95,6 @@ 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
@ -150,7 +133,7 @@ public interface DossierResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = ARCHIVE_DOSSIERS_PATH + ARCHIVE_PATH)
@Operation(summary = "Archives an existing dossier.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully archived the dossiers."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID entered to archive dossier."), @ApiResponse(responseCode = "403", description = "Forbidden operation while archiving."), @ApiResponse(responseCode = "404", description = "Dossier not found")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully archived the dossier."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID entered to archive dossier."), @ApiResponse(responseCode = "403", description = "Forbidden operation while archiving."), @ApiResponse(responseCode = "404", description = "Dossier not found")})
void archiveDossiers(@RequestBody Set<String> dossierIds);
@ -164,14 +147,14 @@ public interface DossierResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@DeleteMapping(value = DELETED_DOSSIERS_PATH + HARD_DELETE_PATH)
@Operation(summary = "Hard deletes existing dossiers.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully hard deleted the dossiers."), @ApiResponse(responseCode = "404", description = "Not found")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully hard deleted the dossier."), @ApiResponse(responseCode = "404", description = "Not found")})
void hardDeleteDossiers(@RequestParam(DOSSIER_ID_PARAM) Set<String> dossierIds);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@ResponseBody
@PostMapping(value = DELETED_DOSSIERS_PATH + UNDELETE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Restores dossiers.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully restored the dossiers."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID entered to restore dossier."), @ApiResponse(responseCode = "403", description = "Forbidden operation while restoring."), @ApiResponse(responseCode = "409", description = "Conflict occurred while restoring.")})
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully restored the dossiers."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID entered to restore dossier."), @ApiResponse(responseCode = "403", description = "Forbidden operation while restoring."), @ApiResponse(responseCode = "409", description = "Conflict occurred while restoring.")})
void undeleteDossiers(@RequestBody Set<String> dossierIds);
}

View File

@ -20,7 +20,6 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateStats;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -95,37 +94,9 @@ public interface DossierTemplateResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = DOSSIER_TEMPLATE_PATH + IMPORT_PATH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Receives an archive to import", description = """
Import process stuck in one of the steps:"
0 - Reading the archive content
1 - store information about the dossier template
2 - store the colors
3 - store the watermarks
4 - store the dossier status
5 - store the dossier attributes
6 - store the file attributes
7 - store the report templates
8 - store the legal basis
9 - store the file attribute configuration
10 - store the component definitions
11 - store the component mappings
12 - store the types and entities
13 - store the rules and component rules""")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Archive have successfully imported"), @ApiResponse(responseCode = "400"), @ApiResponse(responseCode = "404", description = "The dossier template to update does not exist")})
DossierTemplateModel importDossierTemplate(@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Operation(summary = "Receives an archive to import", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Archive have successfully imported"), @ApiResponse(responseCode = "400", description = "Validation failed during import"), @ApiResponse(responseCode = "404", description = "The dossier template to update does not exist")})
DossierTemplateModel importDossierTemplate(@RequestPart(name = "file") MultipartFile file,
@RequestParam(value = DOSSIER_TEMPLATE_ID, required = false) String dossierTemplateId,
@RequestParam(value = "updateExistingDossierTemplate", required = false, defaultValue = "false") boolean updateExistingDossierTemplate);

View File

@ -1,7 +1,9 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@ -35,7 +37,6 @@ public interface DownloadResource {
String OTT_PATH = "/with-ott";
@Deprecated(forRemoval = true)
@Operation(summary = "Prepares a download for given fileIds and types", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Success."), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "Dossier or file not found.")})
@PostMapping(value = REST_PATH + "/prepare", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ -62,16 +63,16 @@ public interface DownloadResource {
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Returns a downloadable byte stream of the requested file")
@Operation(summary = "Returns a downloadable byte stream of the requested file", description = "Use the optional \"inline\" request parameter " + "to select, if this report will be opened in the browser.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Download with this Id is no longer available")})
@GetMapping(value = REST_PATH)
void downloadFile(@RequestParam(STORAGE_ID) String storageId);
ResponseEntity<FileSystemResource> downloadFile(@RequestParam(STORAGE_ID) String storageId,
@RequestParam(value = "inline", required = false, defaultValue = FALSE) boolean inline);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Returns a oneTimeToken for a requested download", description = "Use the optional \"inline\" request parameter "
+ "to select, if this report will be opened in the browser.")
@Operation(summary = "Returns a oneTimeToken for a requested download", description = "Use the optional \"inline\" request parameter " + "to select, if this report will be opened in the browser.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@PostMapping(value = REST_PATH + GENERATE_OTT_PATH)
JSONPrimitive<String> generateOneTimeToken(@RequestBody JSONPrimitive<String> storageIdWrapper);
@ -79,9 +80,11 @@ public interface DownloadResource {
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Returns a downloadable byte stream of the requested file using a valid oneTimeToken")
@Operation(summary = "Returns a downloadable byte stream of the requested file using a valid oneTimeToken", description = "Use the optional \"inline\" request parameter " + "to select, if this report will be opened in the browser.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Download with this Id is no longer available"), @ApiResponse(responseCode = "400", description = "OTT is not valid")})
@GetMapping(value = REST_PATH + OTT_PATH + OTT_PATH_VARIABLE)
void downloadFileUsingOTT(@PathVariable(OTT) String oneTimeToken, @RequestParam(value = "tenantId") String tenantId);
ResponseEntity<FileSystemResource> downloadFileUsingOTT(@PathVariable(OTT) String oneTimeToken,
@RequestParam(value = "inline", required = false, defaultValue = FALSE) boolean inline,
@RequestParam(value = "tenantId") String tenantId);
}

View File

@ -1,71 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
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.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.FilteredEntityLogRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ResponseStatus(value = HttpStatus.OK)
public interface EntityLogResource {
String ENTITY_LOG_PATH = ExternalApi.BASE_PATH + "/entityLog";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String DOSSIER_ID = "dossierId";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID + "}";
String ANALYSIS_NUMBER = "analysisNumber";
String ANALYSIS_NUMBER_PATH_VARIABLE = "/{" + ANALYSIS_NUMBER + "}";
String FALSE = "false";
@GetMapping(value = ENTITY_LOG_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the entity log for a fileId", description =
"Gets the entity log for a given file. The flag includeUnprocessed will merge into the entity log all the unprocessed changes if it's set to true."
+ "Default value for the flag is false.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / entity log is not found.")})
EntityLogResponse getEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "excludedTypes", required = false, defaultValue = "") List<String> excludedTypes,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed);
@GetMapping(value = ENTITY_LOG_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE + "/filtered", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the entity log for a fileId greater than the specified date", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / entity log is not found.")})
EntityLogResponse getFilteredEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody FilteredEntityLogRequest filteredEntityLogRequest);
@GetMapping(value = ENTITY_LOG_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE + "/pages", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the entity log for a fileId with all entities found on the given page numbers", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / entity log is not found.")})
EntityLogResponse getEntityLogWithEntriesOnPages(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "pageNumbers", defaultValue = "") List<Integer> pageNumbers);
@GetMapping(value = ENTITY_LOG_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE + ANALYSIS_NUMBER_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the entity log for a fileId with all entities being analysed in or after iteration with given number", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / entity log is not found.")})
EntityLogResponse getEntityLogDeltaUpdate(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@PathVariable(ANALYSIS_NUMBER) Integer analysisNumber,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed);
}

View File

@ -5,5 +5,4 @@ public interface ExternalApi {
// String BASE_PATH = "/api";
String BASE_PATH = "/redaction-gateway-v1";
}

View File

@ -22,7 +22,6 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
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.validation.Valid;
public interface FileAttributesResource {
@ -44,21 +43,14 @@ public interface FileAttributesResource {
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String UTF_ENCODING = "UTF-8";
String ASCII_ENCODING = "ASCII";
String ISO_ENCODING = "ISO-8859-1";
Set<String> encodingList = Sets.newHashSet(ISO_ENCODING, ASCII_ENCODING, UTF_ENCODING);
Set<String> encodingList = Sets.newHashSet("ISO", "ASCII", "UTF-8");
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Set file attributes base configuration and a list of file attributes, ", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@PutMapping(value = FILE_ATTRIBUTES_PATH
+ CONFIG_PATH
+ BASE_CONFIG_PATH
+ DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PutMapping(value = FILE_ATTRIBUTES_PATH + CONFIG_PATH + BASE_CONFIG_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
FileAttributesConfig setFileAttributesConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody FileAttributesConfig fileAttributesConfig);
@ -66,17 +58,14 @@ public interface FileAttributesResource {
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Add or update a file attribute that can be used at importing csv.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@PostMapping(value = FILE_ATTRIBUTES_PATH
+ CONFIG_PATH
+ FILE_ATTRIBUTE_PATH
+ DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
FileAttributeConfig addOrUpdateFileAttribute(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @Valid @RequestBody FileAttributeConfig fileAttributes);
@PostMapping(value = FILE_ATTRIBUTES_PATH + CONFIG_PATH + FILE_ATTRIBUTE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
FileAttributeConfig addOrUpdateFileAttribute(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody FileAttributeConfig fileAttributes);
@ResponseBody
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(summary = "Delete a specific file attribute.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "NO_CONTENT")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "NO_CONTENT")})
@DeleteMapping(value = FILE_ATTRIBUTES_PATH + CONFIG_PATH + FILE_ATTRIBUTE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + FILE_ATTRIBUTE_ID_PATH_VARIABLE)
void deleteFileAttribute(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @PathVariable(FILE_ATTRIBUTE_ID) String fileAttributeId);
@ -85,7 +74,7 @@ public interface FileAttributesResource {
@ResponseBody
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(summary = "Bulk delete file attributes.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "NO_CONTENT")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "NO_CONTENT")})
@PostMapping(value = FILE_ATTRIBUTES_PATH + CONFIG_PATH + FILE_ATTRIBUTE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + "/delete")
void deleteFileAttributes(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody List<String> fileAttributeIds);
@ -100,7 +89,7 @@ public interface FileAttributesResource {
@Operation(summary = "Set file attributes to an existing file", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@PostMapping(value = FILE_ATTRIBUTES_PATH + SET_PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void setFileAttributes(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody FileAttributes fileAttributes);

View File

@ -25,7 +25,6 @@ public interface FileManagementResource {
String DELETE_PATH = ExternalApi.BASE_PATH + "/delete";
String DOWNLOAD_ORIGINAL_PATH = ExternalApi.BASE_PATH + "/download/original";
String DOWNLOAD_VIEWER_DOCUMENT_PATH = ExternalApi.BASE_PATH + "/download/viewer_doc";
String ROTATION_PATH = ExternalApi.BASE_PATH + "/rotate";
String DOSSIER_ID = "dossierId";
@ -43,64 +42,46 @@ public interface FileManagementResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@DeleteMapping(value = DELETE_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Deletes a file for a given dossierId and FileId", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void deleteFile(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = DELETE_PATH + DOSSIER_ID_PATH_VARIABLE)
@Operation(summary = "Deletes a a list of files for a given dossierId", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void deleteFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody List<String> fileIds);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Returns a downloadable byte stream of the original file with the specified fileId", description = "Use the optional \"inline\" request parameter to select, if this downloadAnnotated will be opened in the browser.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Could not prepare file download."), @ApiResponse(responseCode = "404", description = "Not found")})
@Operation(summary = "Returns a downloadable byte stream of the original file with the specified fileId", description = "Use the optional \"inline\" request parameter to select, if " + "this downloadAnnotated will be opened in the browser.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Could not " + "prepare file download.")})
@GetMapping(value = DOWNLOAD_ORIGINAL_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
ResponseEntity<?> downloadOriginal(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "inline", required = false, defaultValue = FALSE) boolean inline);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Returns a downloadable byte stream of the viewer document file with the specified fileId", description = "Use the optional \"inline\" request parameter to select, if this downloadAnnotated will be opened in the browser.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Could not prepare file download."), @ApiResponse(responseCode = "404", description = "Not found")})
@GetMapping(value = DOWNLOAD_VIEWER_DOCUMENT_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
ResponseEntity<?> downloadViewerDocument(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "inline", required = false, defaultValue = FALSE) boolean inline);
@Deprecated(forRemoval = true)
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@DeleteMapping(value = HARD_DELETE_PATH + DOSSIER_ID_PATH_VARIABLE)
@Operation(summary = "Hard deletes an uploaded file.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully hard deleted the file."), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully hard deleted the file."), @ApiResponse(responseCode = "404", description = "Not found")})
void hardDeleteFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(FILE_IDS) Set<String> fileIds);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = HARD_DELETE_PATH + DOSSIER_ID_PATH_VARIABLE)
@Operation(summary = "Hard deletes a list of files", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "202", description = "Asynchronously hard deletes files.")})
void hardDeleteFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody List<String> files);
@ResponseBody
@ResponseStatus(value = HttpStatus.CREATED)
@PostMapping(value = UNDELETE_PATH + DOSSIER_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Restores an deleted file.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "File successfully restored."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID or file ID entered to restore file."), @ApiResponse(responseCode = "403", description = "Forbidden operation while restoring."), @ApiResponse(responseCode = "409", description = "Conflict occurred while restoring."), @ApiResponse(responseCode = "404", description = "Not found")})
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "File successfully restored."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID or file ID entered to restore file."), @ApiResponse(responseCode = "403", description = "Forbidden operation while restoring."), @ApiResponse(responseCode = "409", description = "Conflict occurred while restoring.")})
void restoreFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody Set<String> fileIds);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = ROTATION_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Rotates one or more pages for one file.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Pages successfully rotated."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID, file ID, pages or rotation entered."), @ApiResponse(responseCode = "403", description = "Forbidden operation while rotating."), @ApiResponse(responseCode = "404", description = "Not found")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Pages successfully rotated."), @ApiResponse(responseCode = "400", description = "Incorrect dossier ID, file ID, pages or rotation entered."), @ApiResponse(responseCode = "403", description = "Forbidden operation while rotating.")})
void rotatePages(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RotatePagesRequest rotatePagesRequest);
}

View File

@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.TextHighlights;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.Highlights;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnnotationIds;
import io.swagger.v3.oas.annotations.Operation;
@ -34,39 +34,29 @@ public interface HighlightsResource {
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Gets available highlights for the file", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@GetMapping(value = DOSSIERS_PATH + DOSSIER_ID_PATH_VARIABLE + FILES_PATH + FILE_ID_PATH_VARIABLE + HIGHLIGHTS_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
TextHighlights getHighlights(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
Highlights getHighlights(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@Operation(summary = "Converts highlights to imported redactions", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@PostMapping(value = DOSSIERS_PATH
+ DOSSIER_ID_PATH_VARIABLE
+ FILES_PATH
+ FILE_ID_PATH_VARIABLE
+ HIGHLIGHTS_PATH
+ CONVERT_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
@PostMapping(value = DOSSIERS_PATH + DOSSIER_ID_PATH_VARIABLE + FILES_PATH + FILE_ID_PATH_VARIABLE + HIGHLIGHTS_PATH + CONVERT_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
void convertHighlights(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody AnnotationIds annotationIds);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@Operation(summary = "Removed highlights from the file", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
@PostMapping(value = DOSSIERS_PATH + DOSSIER_ID_PATH_VARIABLE + FILES_PATH + FILE_ID_PATH_VARIABLE + HIGHLIGHTS_PATH + DELETE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
void deleteHighlights(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody AnnotationIds annotationIds);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@Operation(summary = "Deletes wrong imported redactions for a file", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@PostMapping(value = DOSSIERS_PATH
+ DOSSIER_ID_PATH_VARIABLE
+ FILES_PATH
+ FILE_ID_PATH_VARIABLE
+ IMPORTED_REDACTIONS_PATH
+ DELETE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
@PostMapping(value = DOSSIERS_PATH + DOSSIER_ID_PATH_VARIABLE + FILES_PATH + FILE_ID_PATH_VARIABLE + IMPORTED_REDACTIONS_PATH + DELETE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
void deleteImportedRedactions(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody AnnotationIds annotationIds);
}

View File

@ -1,28 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.image.ImageSimilaritySearchRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.image.ImageSimilaritySearchResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
public interface ImageSimilaritySearchResource {
String IMAGE_SIMILARITY_SEARCH_PATH = ExternalApi.BASE_PATH + "/imageSimilaritySearch";
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Gets similiar images to given image", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "400", description = "Bad request: missing parameter")})
@PostMapping(value = IMAGE_SIMILARITY_SEARCH_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<ImageSimilaritySearchResponse> getSimilarImages(@RequestBody ImageSimilaritySearchRequest imageSimilaritySearchRequest);
}

View File

@ -28,15 +28,15 @@ public interface LegalBasisMappingResource {
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = LEGAL_BASIS_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + DELETE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "delete some legal basis by their technical names.", description = "None")
@Operation(summary = "delete some legal basis by their names.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void deleteLegalBasis(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody List<String> legalBasisTechnicalNames);
void deleteLegalBasis(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody List<String> legalBasisNames);
@ResponseStatus(HttpStatus.NO_CONTENT)
@PutMapping(value = LEGAL_BASIS_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Add or update one legalBasis.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "400", description = "Missing required parameter")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void addOrUpdateLegalBasis(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody LegalBasis legalBasis);
@ -50,7 +50,7 @@ public interface LegalBasisMappingResource {
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = LEGAL_BASIS_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Get all legal basis mapping in dossier template.", description = "None")
@Operation(summary = "Set the mapping between legal basis and redaction reason.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<LegalBasis> getLegalBasisMapping(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);

View File

@ -1,5 +1,6 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import java.util.List;
import java.util.Set;
import org.springframework.http.HttpStatus;
@ -9,23 +10,19 @@ import org.springframework.web.bind.annotation.GetMapping;
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.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.CommentResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationComments;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactionResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualAddResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionBulkLocalRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddCommentRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ForceRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.LegalBasisChangeRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RecategorizationBulkLocalRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RecategorizationRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RemoveRedactionBulkLocalRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RemoveRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ResizeRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddCommentRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ApproveRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ForceRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ImageRecategorizationRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.LegalBasisChangeRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RemoveRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ResizeRedactionRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -47,176 +44,306 @@ public interface ManualRedactionResource {
String COMMENT_ID = "commentId";
String COMMENT_ID_PATH_VARIABLE = "/{" + COMMENT_ID + "}";
String FALSE = "false";
String TRUE = "true";
/* Reviewer Operations*/
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/request/add" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a request for a redaction.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse requestAddRedaction(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody AddRedactionRequest addRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/request/add" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a request for a redaction.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> requestBulkAddRedaction(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<AddRedactionRequest> addRedactionRequest);
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/request/remove" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a request to remove a redaction.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse requestRemoveRedaction(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody RemoveRedactionRequest removeRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/request/remove" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a bulk of requests to remove a redaction.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> requestBulkRemoveRedaction(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<RemoveRedactionRequest> removeRedactionRequests);
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/request/force" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a request to force a redaction.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse requestForceRedaction(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody ForceRedactionRequest forceRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/request/force" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a bulk of requests to force a redaction.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> requestBulkForceRedaction(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ForceRedactionRequest> forceRedactionRequests);
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/request/legalBasis" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a request to change the legal basis reason.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse requestLegalBasisChange(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody LegalBasisChangeRequest legalBasisChangeRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/request/legalBasis" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a bulk of requests to change the legal basis reason.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> requestBulkLegalBasisChange(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<LegalBasisChangeRequest> legalBasisChangeRequests);
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/request/recategorize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a request to recategorize a image.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse requestImageRecategorization(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody ImageRecategorizationRequest imageRecategorizationRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/request/recategorize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a bulk of requests to recategorize a image.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> requestBulkImageRecategorization(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ImageRecategorizationRequest> imageRecategorizationRequests);
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/request/resize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a request to resize a redaction.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse requestResizeRedaction(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody ResizeRedactionRequest resizeRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/request/resize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a bulk of requests to resize a redaction.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> requestBulkResizeRedaction(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ResizeRedactionRequest> resizeRedactionRequests);
@Deprecated
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@DeleteMapping(MANUAL_REDACTION_REST_PATH + "/undo" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE)
@Operation(summary = "Undo a manual request or redaction", description = "Can only be done be the " + "user who added the request/redaction.")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void undo(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @PathVariable(ANNOTATION_ID) String annotationId);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@DeleteMapping(MANUAL_REDACTION_REST_PATH + "/bulk/undo" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Undo a list of manual requests or redactions", description = "Can only be done be the " + "user who added the request/redaction.")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
void undo(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<String> annotationIds,
@RequestParam(value = "includeOnlyUnprocessed", required = false, defaultValue = FALSE) boolean includeOnlyUnprocessed,
@RequestParam(value = "includeOnlyLocal", required = false, defaultValue = FALSE) boolean includeOnlyLocal);
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void undo(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody Set<String> annotationIds);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/comment/add"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE
+ ANNOTATION_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/comment/add" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a comment to a redaction/redaction request", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
CommentResponse addComment(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@PathVariable(ANNOTATION_ID) String annotationId,
@RequestBody AddCommentRequestModel addCommentRequest);
@RequestBody AddCommentRequest addCommentRequest);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@DeleteMapping(value = MANUAL_REDACTION_REST_PATH + "/comment/undo" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE + COMMENT_ID_PATH_VARIABLE)
@Operation(summary = "Undo a comment", description = "Can only be done be the user who added" + " the comment.")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void undoComment(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@PathVariable(ANNOTATION_ID) String annotationId,
@PathVariable(COMMENT_ID) String commentId);
/* Approver Operations*/
@Deprecated
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/approve" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Approves a redaction request/ remove redaction request", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void approveRequest(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@PathVariable(ANNOTATION_ID) String annotationId,
@RequestBody ApproveRequest approveRequest);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/approve" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Approves a list of redaction requests/ remove redaction requests", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void approveRequestBulk(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody Set<String> annotationIds);
@Deprecated
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/decline" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE)
@Operation(summary = "Declines a redaction request/ remove redaction request", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void declineRequest(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @PathVariable(ANNOTATION_ID) String annotationId);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/decline" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Declines a redaction request/ remove redaction request list", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void declineRequestBulk(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody Set<String> annotationIds);
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk/redaction/add"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/redaction/add" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a manual redaction", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse addRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse addRedaction(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody AddRedactionRequest addRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/redaction/add" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a manual redaction", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> addRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<AddRedactionRequestModel> addRedactionRequest);
@RequestBody Set<AddRedactionRequest> addRedactionRequest);
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/redaction/remove" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Removes a redaction", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse removeRedaction(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RemoveRedactionRequest removeRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk-local/add"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a bulk of local redactions", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse addRedactionBulkLocal(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody AddRedactionBulkLocalRequestModel addRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk-local/remove"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Removes a bulk of local redactions", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse removeRedactionBulkLocal(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody RemoveRedactionBulkLocalRequestModel removeRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk/redaction/remove"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/redaction/remove" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Removes the redactions list", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse removeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> removeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<RemoveRedactionRequestModel> removeRedactionRequests);
@RequestBody Set<RemoveRedactionRequest> removeRedactionRequests);
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/redaction/force" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Forces a redaction", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse forceRedaction(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ForceRedactionRequest forceRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk/redaction/force"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/redaction/force" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Forces the redactions list", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse forceRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> forceRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ForceRedactionRequestModel> forceRedactionRequests);
@RequestBody Set<ForceRedactionRequest> forceRedactionRequests);
@Deprecated(forRemoval = true)
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk/redaction/legalBasisChange"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/redaction/legalBasisChange" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Changes a legal basis reason", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse legalBasisChange(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody LegalBasisChangeRequest legalBasisChangeRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/redaction/legalBasisChange" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Changes the legal basis reasons list", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse legalBasisChangeBulk(@PathVariable(DOSSIER_ID) String dossierId,
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> legalBasisChangeBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<LegalBasisChangeRequestModel> legalBasisChangeRequests);
@RequestBody Set<LegalBasisChangeRequest> legalBasisChangeRequests);
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/redaction/recategorize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Recategorizes an image", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse recategorizeImage(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody ImageRecategorizationRequest imageRecategorizationRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk/redaction/recategorize"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Recategorizes the list of redaction log entries", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse recategorizeBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<RecategorizationRequestModel> recategorizationRequests);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk-local/recategorize"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Recategorizes the list of redaction log entries", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse recategorizeBulkLocal(@PathVariable(DOSSIER_ID) String dossierId,
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/redaction/recategorize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Recategorizes the images list", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> recategorizeImageBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody RecategorizationBulkLocalRequestModel recategorizationRequest);
@RequestBody Set<ImageRecategorizationRequest> imageRecategorizationRequests);
@Deprecated
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/redaction/resize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Resizes a redaction", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualAddResponse resizeRedaction(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ResizeRedactionRequest resizeRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk/redaction/resize"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/redaction/resize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Resizes the redactions list", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse resizeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> resizeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ResizeRedactionRequestModel> resizeRedactionRequests);
@RequestBody Set<ResizeRedactionRequest> resizeRedactionRequests);
/* Other operations */
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = MANUAL_REDACTION_REST_PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Returns the manual redactions", description = """
If the unprocessed flag is true then only the unprocessed manual redactions are returned. If the flag is false\
all manual redactions are returned. Default value for the flag is false.\
If the includeDictChanges flag is false, only local manual redactions will be returned, otherwise all are returned. Default value for the flag is true.
""")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found")})
ManualRedactions getManualRedactions(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "unprocessed", required = false, defaultValue = FALSE) boolean unprocessed,
@RequestParam(value = "includeDictChanges", required = false, defaultValue = TRUE) boolean includeDictChanges);
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = MANUAL_REDACTION_REST_PATH
+ "/comments"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE
+ ANNOTATION_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Returns the comments for a specific annotation in a specific file", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found")})
AnnotationComments getComments(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @PathVariable(ANNOTATION_ID) String annotationId);
@Operation(summary = "Returns the manual redactions", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ManualRedactions getManualRedactions(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
}

View File

@ -42,6 +42,7 @@ public interface NotificationResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@Operation(summary = "Mark a notifications as seen or unseen", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
@PostMapping(value = NOTIFICATION_PATH + TOGGLE_SEEN_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
void toggleNotificationSeen(@RequestBody List<String> notificationIds, @RequestParam(SET_SEEN_PARAM) boolean setSeen);

View File

@ -0,0 +1,70 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
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 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.RSSResponse;
import com.iqser.red.service.redaction.report.v1.api.model.rss.DetailedRSSResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ResponseStatus(value = HttpStatus.OK)
public interface RSSResource {
String RSS_PATH = ExternalApi.BASE_PATH + "/rss";
String OVERRIDE_PATH = "/override";
String DOSSIER_ID = "dossierId";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID + "}";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
@GetMapping(value = RSS_PATH + DOSSIER_ID_PATH_VARIABLE, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
@Operation(summary = "Returns the RSS response for a dossier", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
RSSResponse getRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId);
@GetMapping(value = RSS_PATH + "/detailed" + DOSSIER_ID_PATH_VARIABLE, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
@Operation(summary = "Returns the RSS response with more details for a dossier", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
DetailedRSSResponse getDetailedRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = RSS_PATH + OVERRIDE_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds overrides for RSS components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
void addOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ComponentsOverrides componentsOverrides);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = RSS_PATH + OVERRIDE_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets overrides for RSS components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ComponentsOverrides getOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = RSS_PATH + OVERRIDE_PATH + "/revert" + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Reverts overrides for RSS components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
void revertOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RevertOverrideRequest revertOverrideRequest);
}

View File

@ -38,18 +38,17 @@ public interface ReanalysisResource {
String EXCLUDED_STATUS_PARAM = "excluded";
String FORCE_PARAM = "force";
String ALL_PAGES = "allPages";
@PostMapping(value = REANALYSIS_REST_PATH + DOSSIER_ID_PATH_VARIABLE)
@Operation(summary = "Reanalyze all files of the dossier.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void reanalyzeDossier(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force);
@PostMapping(value = REANALYSIS_REST_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Reanalyze a file", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void reanalyzeFile(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force);
@ -57,7 +56,7 @@ public interface ReanalysisResource {
@PostMapping(value = REANALYSIS_REST_PATH + DOSSIER_ID_PATH_VARIABLE + BULK_REST_PATH)
@Operation(summary = "Reanalyze multiple files for a dossier", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void reanalyzeFilesForDossier(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody List<String> fileIds,
@RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force);
@ -65,27 +64,26 @@ public interface ReanalysisResource {
@Operation(summary = "Ocr and reanalyze a dossier", description = "None")
@PostMapping(value = OCR_REANALYSIS_REST_PATH + DOSSIER_ID_PATH_VARIABLE)
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void ocrDossier(@PathVariable(DOSSIER_ID) String dossierId);
@Operation(summary = "Ocr and reanalyze a file", description = "None")
@PostMapping(value = OCR_REANALYSIS_REST_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "409", description = "Conflict"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden"), @ApiResponse(responseCode = "400", description = "Cannot OCR approved file")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "409", description = "Conflict")})
void ocrFile(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force,
@RequestParam(value = ALL_PAGES, required = false, defaultValue = FALSE) boolean allPages);
@RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force);
@Operation(summary = "Ocr and reanalyze multiple files for a dossier", description = "None")
@PostMapping(value = OCR_REANALYSIS_REST_PATH + DOSSIER_ID_PATH_VARIABLE + BULK_REST_PATH)
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void ocrFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody Set<String> fileIds);
@Operation(summary = "Exclude or re-include a file to the automatic analysis", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
@PostMapping(value = TOGGLE_AUTOMATIC_ANALYSIS_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
void toggleAutomaticAnalysis(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@ -134,7 +132,7 @@ public interface ReanalysisResource {
@PostMapping(value = REINDEX_REST_PATH)
@Operation(summary = "Reindex a dossier, files of a dossier or all", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void reindex(@RequestParam(value = "dossierId", required = false) String dossierId,
@RequestParam(value = "dropIndex", required = false, defaultValue = FALSE) boolean dropIndex,
@RequestBody List<String> fileIds);

View File

@ -0,0 +1,73 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
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.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.FilteredRedactionLogRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ResponseStatus(value = HttpStatus.OK)
public interface RedactionLogResource {
String REDACTION_LOG_PATH = ExternalApi.BASE_PATH + "/redactionLog";
String SECTION_GRID_PATH = ExternalApi.BASE_PATH + "/sectionGrid";
String SECTION_TEXT_PATH = ExternalApi.BASE_PATH + "/sectionText";
String SIMPLIFIED_SECTION_TEXT_PATH = ExternalApi.BASE_PATH + "/simplifiedSectionText";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String DOSSIER_ID = "dossierId";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID + "}";
@GetMapping(value = REDACTION_LOG_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the redaction log for a fileId", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request " + "contains error."), @ApiResponse(responseCode = "404", description = "The redaction log is not found.")})
RedactionLog getRedactionLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "excludedType", required = false) List<String> excludedTypes,
@RequestParam(value = "withManualRedactions", required = false, defaultValue = "true") boolean withManualRedactions,
@RequestParam(value = "includeFalsePositives", required = false, defaultValue = "false") boolean includeFalsePositives);
@GetMapping(value = SECTION_GRID_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the section grid for a fileId", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request " + "contains error."), @ApiResponse(responseCode = "404", description = "The section grid is not found.")})
SectionGrid getSectionGrid(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@GetMapping(value = SECTION_TEXT_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Gets the section text for a fileId", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request " + "contains error."), @ApiResponse(responseCode = "404", description = "The section text is not found.")})
ResponseEntity<?> getSectionText(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@GetMapping(value = SIMPLIFIED_SECTION_TEXT_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Gets the simplified section text for a fileId", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request " + "contains error."), @ApiResponse(responseCode = "404", description = "The simplified section text is not found.")})
ResponseEntity<?> getSimplifiedSectionText(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@PostMapping(value = REDACTION_LOG_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE + "/filtered", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the redaction log for a fileId grater than the specified date", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request " + "contains error."), @ApiResponse(responseCode = "404", description = "The redaction log is not found.")})
RedactionLog getFilteredRedactionLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody FilteredRedactionLogRequest filteredRedactionLogRequest);
}

View File

@ -23,7 +23,6 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSON
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.ReportTemplate;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -48,11 +47,10 @@ public interface ReportTemplateResource {
@ResponseBody
@ResponseStatus(value = HttpStatus.CREATED)
@PostMapping(value = REPORT_TEMPLATE_UPLOAD_PATH
+ DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = REPORT_TEMPLATE_UPLOAD_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Upload template file for redaction-report", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Report template upload succeeded.")})
ReportTemplate uploadTemplate(@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
ReportTemplate uploadTemplate(@RequestPart(name = "file") MultipartFile file,
@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId,
@RequestParam(value = MULTI_FILE_REPORT, required = false, defaultValue = "false") boolean multiFileReport,
@RequestParam(value = ACTIVE_BY_DEFAULT, required = false, defaultValue = "false") boolean activeByDefault);

View File

@ -6,21 +6,15 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.Rules;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -29,16 +23,10 @@ public interface RulesResource {
String RULES_PATH = ExternalApi.BASE_PATH + "/rules";
String UPLOAD_PATH = "/upload";
String DOWNLOAD_PATH = "/download";
String RESET_PATH = "/reset";
String DOSSIER_TEMPLATE_PARAMETER_NAME = "dossierTemplateId";
String DOSSIER_TEMPLATE_PATH_VARIABLE = "/{dossierTemplateId}";
String RULE_FILE_TYPE_PARAMETER_NAME = "ruleFileType";
String RULE_FILE_TYPE_PATH_VARIABLE = "/{ruleFileType}";
String DRY_RUN_PARAMETER = "dryRun";
/**
* Upload rules to be used by redaction service.
@ -48,8 +36,8 @@ public interface RulesResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = RULES_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Takes object containing string or rules as argument, which will be used by the redaction service.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified."), @ApiResponse(responseCode = "422", description = "Uploaded rules could not be compiled.")})
ResponseEntity<DroolsValidationResponse> upload(@RequestBody RulesUploadRequestModel rules);
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Rules upload successful."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")})
void upload(@RequestBody Rules rules);
@ResponseBody
@ -57,15 +45,7 @@ public interface RulesResource {
@Operation(summary = "Returns object containing the currently used Drools rules.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@GetMapping(value = RULES_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
RulesResponse download(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Returns object containing the currently used Drools rules.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@GetMapping(value = RULES_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + RULE_FILE_TYPE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
RulesResponse download(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType);
Rules download(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
/**
@ -76,20 +56,8 @@ public interface RulesResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = RULES_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + UPLOAD_PATH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Takes object containing string or rules as argument, which will be used by the redaction service.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified."), @ApiResponse(responseCode = "422", description = "Uploaded rules could not be compiled.")})
ResponseEntity<DroolsValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = RULES_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + RULE_FILE_TYPE_PATH_VARIABLE + UPLOAD_PATH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Takes object containing string or rules as argument, which will be used by the redaction service.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done"), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified."), @ApiResponse(responseCode = "422", description = "Uploaded rules could not be compiled.")})
ResponseEntity<DroolsValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file);
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Rules upload successful."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")})
void uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestPart(name = "file") MultipartFile file);
@ResponseBody
@ -99,18 +67,4 @@ public interface RulesResource {
@GetMapping(value = RULES_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + DOWNLOAD_PATH)
ResponseEntity<?> downloadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Returns file containing the currently used Drools rules.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@GetMapping(value = RULES_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + RULE_FILE_TYPE_PATH_VARIABLE + DOWNLOAD_PATH)
ResponseEntity<?> downloadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Resets the timeout detected flag in a Rule file.")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No content")})
@PutMapping(value = RULES_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + RULE_FILE_TYPE_PATH_VARIABLE + RESET_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
void unlockRules(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType);
}

View File

@ -18,7 +18,7 @@ public interface StatusReportResource {
@GetMapping(value = STATUS_REPORT + DOSSIER_ID_PATH_VARIABLE)
@Operation(summary = "Generate status report for dossier", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Status report was generated."), @ApiResponse(responseCode = "404", description = "Not found")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Status report was generated.")})
ResponseEntity<?> generateStatusReport(@PathVariable(DOSSIER_ID) String dossierId);
}

View File

@ -3,7 +3,6 @@ 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;
@ -17,8 +16,6 @@ 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;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -27,7 +24,6 @@ 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";
@ -39,7 +35,6 @@ public interface StatusResource {
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String ASSIGNEE_ID_REQUEST_PARAM = "assigneeId";
String FORCE_REQUEST_PARAM = "force";
String DELETED_PATH = "/softdeleted";
@ -59,14 +54,6 @@ 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)
@ -87,14 +74,14 @@ public interface StatusResource {
@ResponseBody
@GetMapping(value = STATUS_REST_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the status for a file.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found")})
FileStatus getFileStatus(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = STATUS_REST_PATH + ASSIGNEE_REST_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Assigns a user to a a file.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully assigned new owner to dossier."), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully assigned new owner to dossier."), @ApiResponse(responseCode = "404", description = "Not found")})
void setCurrentFileAssignee(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId);
@ -103,7 +90,7 @@ public interface StatusResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = STATUS_REST_PATH + "/under-review" + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Sets the status UNDER_REVIEW for a file.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void setStatusUnderReview(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId);
@ -112,24 +99,22 @@ public interface StatusResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = STATUS_REST_PATH + "/under-approval" + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Sets the status UNDER_APPROVAL for a file.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void setStatusUnderApproval(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId);
@ResponseStatus(value = HttpStatus.OK)
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = STATUS_REST_PATH + "/approved" + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE)
@Operation(summary = "Sets the status APPROVED for a file.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ApproveResponse setStatusApproved(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = FORCE_REQUEST_PARAM, required = false, defaultValue = "false") boolean force);
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void setStatusApproved(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = STATUS_REST_PATH + "/set-assignee" + DOSSIER_ID_PATH_VARIABLE + BULK_REST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Assigns a user for a list of files.", description = "None")
@Operation(summary = "Assign a a user for a list of files.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully assigned new owner to dossier."), @ApiResponse(responseCode = "404", description = "Not found")})
void setAssigneeForList(@PathVariable(DOSSIER_ID) String dossierId,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId,
@ -139,7 +124,7 @@ public interface StatusResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = STATUS_REST_PATH + "/under-review" + DOSSIER_ID_PATH_VARIABLE + BULK_REST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Sets the status UNDER_REVIEW for a list of files.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void setStatusUnderReviewForList(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody List<String> fileIds,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId);
@ -148,25 +133,23 @@ public interface StatusResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = STATUS_REST_PATH + "/under-approval" + DOSSIER_ID_PATH_VARIABLE + BULK_REST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Sets the status UNDER_APPROVAL for a list of files.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void setStatusUnderApprovalForList(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody List<String> fileIds,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId);
@ResponseStatus(value = HttpStatus.OK)
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = STATUS_REST_PATH + "/approved" + DOSSIER_ID_PATH_VARIABLE + BULK_REST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Sets the status APPROVED for a list of files.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
List<ApproveResponse> setStatusApprovedForList(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody List<String> fileIds,
@RequestParam(value = FORCE_REQUEST_PARAM, required = false, defaultValue = "false") boolean force);
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void setStatusApprovedForList(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody List<String> fileIds);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = STATUS_REST_PATH + "/new" + DOSSIER_ID_PATH_VARIABLE + BULK_REST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Sets the status NEW for a list of files.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
void setStatusNewForList(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody List<String> fileIds);

View File

@ -1,178 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
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.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DownloadResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileExchangeExportRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatusFilter;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ImportResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ReanalysisSettings;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ReanalyzeFilesResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public interface SupportResource {
String REANALYSIS_REST_PATH = ExternalApi.BASE_PATH + "/reanalyze";
String ERROR_REANALYSIS_REST_PATH = REANALYSIS_REST_PATH + "/error";
String STATUS_REST_PATH = ExternalApi.BASE_PATH + "/status/filter";
String FILE_EXCHANGE_REST_PATH = ExternalApi.BASE_PATH + "/file-exchange";
String DATASET_EXCHANGE = ExternalApi.BASE_PATH + "/dataset-exchange";
String BULK_REST_PATH = "/bulk";
String DOSSIER_TEMPLATE_ID = "dossierTemplateId";
String DOSSIER_ID = "dossierId";
String FILE_ID = "fileId";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID + "}";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String DOSSIER_TEMPLATE_DOSSIER_TEMPLATE_ID_PATH_VARIABLE = "/dossierTemplateId/{" + DOSSIER_TEMPLATE_ID + "}";
String DOSSIER_TEMPLATE_ID_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_ID + "}";
String DOSSIER_DOSSIER_ID_PATH_VARIABLE = "/dossierId/{" + DOSSIER_ID + "}";
String FALSE = "false";
String FULL_REANALYSIS_PARAM = "fullReanalysis";
String RUN_OCR_PARAM = "runOcr";
String EXPORT = "/export";
String IMPORT = "/import";
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = REANALYSIS_REST_PATH + DOSSIER_TEMPLATE_DOSSIER_TEMPLATE_ID_PATH_VARIABLE)
@Operation(summary = "Reanalyze all files in dossier template", description = """
## Reanalyze Files Endpoint
Use this endpoint to reanalyze all files in a specified Dossier Template. The reanalysis process can be tailored using various filtering options provided in the request body.
### Parameters
- **DossierTemplateId**: Specifies the Dossier Template whose files need to be reanalyzed.
### Request Body Configuration Options
- **dossierIds**: List of dossier IDs to filter. If empty, all dossiers are selected for reanalysis.
- **fileIds**: List of file IDs to filter. If empty, all files are selected for reanalysis.
- **repeatStructureAnalysis**: Boolean. If true, layout parsing and named entity recognition will be repeated.
- **fileStatusFilter**: Use this to create a filter for files to reanalyze. Matches any file if set to null.
""")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ReanalyzeFilesResponse reanalyzeFiles(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody ReanalysisSettings reanalysisSettings);
@PostMapping(value = ERROR_REANALYSIS_REST_PATH)
@Operation(summary = "Reanalyze all files in error state.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "403", description = "Forbidden")})
void reanalyzeAllRelevantErrorFiles(@RequestParam(value = FULL_REANALYSIS_PARAM, required = false, defaultValue = FALSE) boolean repeatStructureAnalysis,
@RequestParam(value = RUN_OCR_PARAM, required = false, defaultValue = FALSE) boolean runOcr);
@PostMapping(value = ERROR_REANALYSIS_REST_PATH + DOSSIER_ID_PATH_VARIABLE + BULK_REST_PATH)
@Operation(summary = "Reanalyze multiple files in error state for a dossier.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
void reanalyzeErrorFilesBulkForDossier(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody List<String> fileIds,
@RequestParam(value = FULL_REANALYSIS_PARAM, required = false, defaultValue = FALSE) boolean repeatStructureAnalysis,
@RequestParam(value = RUN_OCR_PARAM, required = false, defaultValue = FALSE) boolean runOcr);
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@PostMapping(value = STATUS_REST_PATH, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Get the filtered status for all files in the workspace.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<FileStatus> getFileStatus(@RequestBody FileStatusFilter fileStatusFilter);
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@PostMapping(value = STATUS_REST_PATH
+ DOSSIER_TEMPLATE_DOSSIER_TEMPLATE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Get the filtered status for all files in a dossier template.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<FileStatus> getFileStatusForDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody FileStatusFilter fileStatusFilter);
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@PostMapping(value = STATUS_REST_PATH + DOSSIER_DOSSIER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Get the filtered status for all files in a dossier.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<FileStatus> exportFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody FileStatusFilter fileStatusFilter);
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@PostMapping(value = FILE_EXCHANGE_REST_PATH
+ EXPORT
+ DOSSIER_TEMPLATE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Exports all dossiers and files from a given Dossier Template.", description = """
## Export Files Endpoint
Use this endpoint to export a full Dossier Template, including all configurations, dossiers, and files.
The endpoint returns a String storageId, which is used to query the DownloadController for the export zip archive's status and to download the archive.
### Parameters
- **DossierTemplateId**: Specifies the Dossier Template to be exported.
### Request Body Configuration Options
- **dossierIds**: List of dossier IDs to filter. If empty, all dossiers are selected.
- **fileIds**: List of file IDs to filter. If empty, all files are selected.
- **excludeLayoutFiles**: Boolean. If true, excludes DOCUMENT_STRUCTURE/_PAGES/_TEXT/_POSITIONS, SIMPLIFIED_TEXT, and NER_ENTITIES files.
- **excludeManualRedactions**: Boolean. If true, excludes MANUAL_REDACTIONS files.
- **excludeAnalysisLogs**: Boolean. If true, excludes ENTITY_LOG and COMPONENT_LOG (with overrides) files.
- **excludeFileAttributes**: Boolean. If true, excludes file attributes.
- **originFileOnly**: Boolean. If true, exports only the origin file, ignoring untouched and viewer documents.""")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
DownloadResponse exportFiles(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody FileExchangeExportRequest exportRequest);
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@PostMapping(value = DATASET_EXCHANGE + EXPORT + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Exports all dossiers and files from a given Dossier Template.", description = """
## Export Preview Files Endpoint
Use this endpoint to export all files of a DossierTemplate as a Preview file containing all applied annotations as redaction annotations with additional data
The endpoint returns a String storageId, which is used to query the DownloadController for the export zip archive's status and to download the archive.
""")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
DownloadResponse exportDataset(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = FILE_EXCHANGE_REST_PATH + IMPORT, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Imports a file exchange export zip.", description = "Use this endpoint to import a full export of a given Dossier Template including all its configurations, dossiers, and files. Returns the resulting dossierTemplateId.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ImportResponse importFiles(@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = DATASET_EXCHANGE + IMPORT, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Imports a file exchange export zip.", description = "Use this endpoint to import a full export of a given Dossier Template including all its configurations, dossiers, and files. Returns the resulting dossierTemplateId.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ImportResponse importDataset(@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file);
}

View File

@ -15,8 +15,6 @@ import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileUploadResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -28,27 +26,24 @@ public interface UploadResource {
String IMPORT_REDACTIONS_PATH = ExternalApi.BASE_PATH + "/import-redactions";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID + "}";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String DISABLE_AUTOMATIC_ANALYSIS_PARAM = "disableAutomaticAnalysis";
@ResponseBody
@ResponseStatus(value = HttpStatus.CREATED)
@PostMapping(value = UPLOAD_PATH + DOSSIER_ID_PATH_VARIABLE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Receives an uploaded file and returns its fileId.", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "File upload succeeded. Return the fileId of the "
+ "uploaded file."), @ApiResponse(responseCode = "404", description = "Dossier not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
FileUploadResult upload(@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "File upload succeeded. Return the fileId of the " + "uploaded file.")})
FileUploadResult upload(@RequestPart(name = "file") MultipartFile file,
@PathVariable(DOSSIER_ID) String dossierId,
@RequestParam(value = "keepManualRedactions", required = false, defaultValue = "false") boolean keepManualRedactions,
@Parameter(name = DISABLE_AUTOMATIC_ANALYSIS_PARAM, description = "Disables automatic analysis for the uploaded file, imports only imported redactions") @RequestParam(value = DISABLE_AUTOMATIC_ANALYSIS_PARAM, required = false, defaultValue = "false") boolean disableAutomaticAnalysis);
@RequestParam(value = "keepManualRedactions", required = false, defaultValue = "false") boolean keepManualRedactions);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = IMPORT_REDACTIONS_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Imports redactions from a redacted file to a existing file", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok"), @ApiResponse(responseCode = "404", description = "Dossier not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
void importRedactions(@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok")})
void importRedactions(@RequestPart(name = "file") MultipartFile file,
@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "pageInclusionRequest", required = false) Set<Integer> pageInclusionRequest);

View File

@ -1,37 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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.UserStats;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ApiResponses(value = {@ApiResponse(responseCode = "429", description = "Too many requests.")})
public interface UserStatsResource {
String USER_ID_PARAM = "userId";
String USER_ID_PATH_VARIABLE = "/{" + USER_ID_PARAM + "}";
String STATS_PATH = "/user-stats";
String PATH = ExternalApi.BASE_PATH + STATS_PATH;
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@GetMapping(value = PATH + USER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets user stats for user specified by id.", description = "")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found")})
ResponseEntity<UserStats> getUserStats(@Parameter(name = USER_ID_PARAM, description = "The unique identifier of the user whose statistics we want to retrieve.", required = true) @PathVariable(USER_ID_PARAM) String userId);
}

View File

@ -1,38 +0,0 @@
plugins {
id("com.iqser.red.service.java-conventions")
id("io.freefair.lombok") version "8.6"
}
dependencies {
api(project(":persistence-service-external-api-v1"))
api(project(":persistence-service-internal-api-v1"))
api("com.iqser.red.service:pdftron-redaction-service-api-v1:${rootProject.extra.get("pdftronRedactionServiceVersion")}") {
exclude(group = "com.iqser.red.service", module = "persistence-service-internal-api-v1")
exclude(group = "com.iqser.red.service", module = "persistence-service-shared-api-v1")
}
api("com.iqser.red.service:redaction-service-api-v1:${rootProject.extra.get("redactionServiceVersion")}") {
exclude(group = "com.iqser.red.service", module = "persistence-service-internal-api-v1")
exclude(group = "com.iqser.red.service", module = "persistence-service-shared-api-v1")
}
api("com.iqser.red.service:redaction-report-service-api-v1:${rootProject.extra.get("redactionReportServiceVersion")}") {
exclude(group = "com.iqser.red.service", module = "persistence-service-internal-api-v1")
exclude(group = "com.iqser.red.service", module = "persistence-service-shared-api-v1")
}
api("com.iqser.red.service:search-service-api-v1:${rootProject.extra.get("searchServiceVersion")}") {
exclude(group = "com.iqser.red.service", module = "persistence-service-internal-api-v1")
exclude(group = "com.iqser.red.service", module = "persistence-service-shared-api-v1")
}
api("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.2")
api("com.google.guava:guava:31.1-jre")
api("org.springframework.boot:spring-boot-starter-security:3.1.3")
api("org.springframework.boot:spring-boot-starter-validation:3.1.3")
api("com.iqser.red.commons:jackson-commons:2.1.0")
api(project(":persistence-service-shared-api-v1"))
testImplementation("com.iqser.red.commons:test-commons:2.1.0")
testImplementation("org.springframework.boot:spring-boot-starter-test:3.0.4")
compileOnly("org.springdoc:springdoc-openapi-ui:1.6.13")
api("io.github.openfeign:feign-core:12.2")
compileOnly("org.springframework:spring-web:6.0.6")
}
description = "persistence-service-external-api-v2"

View File

@ -1,18 +0,0 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BulkComponentsRequest {
private List<String> fileIds = new ArrayList<>();
}

View File

@ -1,25 +0,0 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DownloadFileType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BulkDownloadRequest {
private List<String> reportTemplateIds = new ArrayList<>();
private Set<DownloadFileType> downloadFileTypes = new HashSet<>();
private String redactionPreviewColor;
private List<String> fileIds = new ArrayList<>();
}

View File

@ -1,28 +0,0 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import java.util.List;
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
@NoArgsConstructor
@AllArgsConstructor
public class Component {
@JacksonXmlCData
@NonNull
private String name;
@JacksonXmlCData
@NonNull
private List<ComponentValue> componentValues;
@JacksonXmlCData
private boolean overridden;
}

View File

@ -1,28 +0,0 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ComponentMappingMetadataModel {
String id;
String name;
String fileName;
Integer version;
List<String> columnLabels;
Integer numberOfLines;
String encoding;
char delimiter;
}

View File

@ -1,22 +0,0 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ComponentMappingSummary {
String dossierTemplateId;
List<ComponentMappingMetadataModel> componentMappingList;
}

View File

@ -1,22 +0,0 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ComponentOverrideList {
String dossierTemplateId;
String dossierId;
String fileId;
List<Component> componentOverrides = new ArrayList<>();
}

Some files were not shown because too many files have changed in this diff Show More