Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add file attachments to iris-messages #621

Draft
wants to merge 44 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
a9ef3a1
feat: add first draft of iris-message list (mockup only)
mad-nuts Nov 19, 2021
87f6c47
feat: improve first draft of iris-message list (mockup only)
mad-nuts Nov 22, 2021
f939445
feat: add first draft for message details view (mockup only)
mad-nuts Nov 22, 2021
17a2079
feat: add first draft for message creation view (mockup only)
mad-nuts Nov 23, 2021
4b4a183
feat: add iris message controller, db-tables, etc. - work in progress
mad-nuts Nov 30, 2021
6083065
feat: add iris-message creation (work in progress) and list view with…
mad-nuts Dec 1, 2021
90decee
feat: add iris-message-details view and edit functionality (set isRea…
mad-nuts Dec 2, 2021
cf6eb9a
feat: enhance routing when navigating back to message list from messa…
mad-nuts Dec 6, 2021
977746f
feat: add attachments upload & download to iris messages
mad-nuts Dec 6, 2021
ca6ed62
feat: fetch message recipients by EPS client
mad-nuts Dec 7, 2021
a7ecf22
feat: add functionality for inbound messages
mad-nuts Dec 8, 2021
30d7ee0
feat: add tests for iris-messages
mad-nuts Dec 10, 2021
05e62f1
feat: add e2e tests and data normalizer for iris-messages
mad-nuts Dec 13, 2021
b0b22c8
fix: add message dummy bridge to avoid circular dependency caused by …
mad-nuts Dec 13, 2021
b7501d9
feat: add bff controller tests for iris messages
mad-nuts Dec 16, 2021
6a2bf71
feat: add SQL dialects to iris messages + improve folder initialization
mad-nuts Dec 17, 2021
bd480a1
feat: add database tests for iris-messages
mad-nuts Dec 20, 2021
4dbf7ea
feat: add eps controller tests for iris-messages
mad-nuts Dec 20, 2021
4651df0
feat: add service tests for iris-messages (wip)
mad-nuts Dec 20, 2021
8fc99b5
feat: iris-message: allow empty files (e.g. text files without content)
mad-nuts Dec 21, 2021
d1a06a9
feat: add service tests for iris-messages
mad-nuts Dec 21, 2021
bbff99d
feat: add message builder tests for iris-messages
mad-nuts Dec 21, 2021
7a08bdc
fix: fix message file column type / length
mad-nuts Dec 21, 2021
480e256
feat: enhance content-type handling and add file type restrictions fo…
mad-nuts Dec 22, 2021
acfab84
fix: fix iris-message test data
mad-nuts Dec 22, 2021
aefcc1a
feat: add zipCode search for iris-message-contacts / health departments
mad-nuts Jan 19, 2022
cd4dda5
fix: apply changes in the rpc client configuration to the EPSIrisMess…
mad-nuts Jan 20, 2022
092a185
fix: fix e2e tests for iris-messages
mad-nuts Jan 24, 2022
7a7694b
feat: disable file attachments functionality (but leave it in code fo…
mad-nuts Jan 24, 2022
edc9534
feat: rename Attachment to FileAttachment to be able to distinguish d…
mad-nuts Jan 25, 2022
11b8abc
fix: fix iris-message-file foreign key in db migrations
mad-nuts Jan 27, 2022
2e30ff5
feat: enable EPS for health department communication and add a filter…
mad-nuts Feb 14, 2022
dd86d85
chore: adapts SQL scripts after rebase and adds `[…]_by` columns
jekutzsche Feb 15, 2022
f1ad275
chore: fixes an error of lombok
jekutzsche Feb 15, 2022
7436fe6
chore: uses the identifiers direct and removes the usage of UUID and …
jekutzsche Feb 15, 2022
8b47b6d
chore: removes creation of index on primary keys
jekutzsche Feb 15, 2022
ae21e3a
chore: uses static import for MockMvcRequestBuilders
jekutzsche Feb 15, 2022
407e40f
chore: small code improvements
jekutzsche Feb 15, 2022
ac5803c
chore: adds test for search with Hibernate Search
jekutzsche Feb 15, 2022
0ce0f9e
chore: moves error messages from `ErrorMessages` to `messages.propert…
jekutzsche Feb 16, 2022
0bb0d61
refactor: remove deprecated contentType. Rework iris-message structur…
mad-nuts Feb 16, 2022
3e07e0e
feat: move iris message file attachment functionality to a separate f…
mad-nuts Feb 16, 2022
c75d2c5
fix: move iris-message-file table creation to a new migration file (V…
mad-nuts Feb 16, 2022
a82bf1b
fix: apply changes / fixes from ga-messenger branch
mad-nuts Feb 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions iris-client-bff/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import iris.client_bff.cases.eps.CaseDataController;
import iris.client_bff.events.eps.EventDataController;
import iris.client_bff.iris_messages.eps.IrisMessageDataController;
import lombok.AllArgsConstructor;

import org.springframework.context.annotation.Bean;
Expand All @@ -21,6 +22,8 @@ public class DataSubmissionConfig {

EventDataController eventDataController;

IrisMessageDataController irisMessageDataController;

@Bean(name = DATA_SUBMISSION_ENDPOINT)
public CompositeJsonServiceExporter jsonRpcServiceImplExporter() {
return createCompositeJsonServiceExporter();
Expand All @@ -34,7 +37,7 @@ public CompositeJsonServiceExporter jsonRpcServiceImplExporterWithSlash() {
private CompositeJsonServiceExporter createCompositeJsonServiceExporter() {

CompositeJsonServiceExporter compositeJsonServiceExporter = new CompositeJsonServiceExporter();
compositeJsonServiceExporter.setServices(new Object[] { caseDataController, eventDataController });
compositeJsonServiceExporter.setServices(new Object[] { caseDataController, eventDataController, irisMessageDataController });
compositeJsonServiceExporter.setAllowExtraParams(true); // Used to allow the EPS to add common parameters (e.g. a signature) and not have to change all methods.

return compositeJsonServiceExporter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import iris.client_bff.cases.CaseDataRequest.DataRequestIdentifier;
import iris.client_bff.events.EventDataRequest;
import iris.client_bff.events.model.Location.LocationIdentifier;
import iris.client_bff.iris_messages.IrisMessage;
import iris.client_bff.iris_messages.IrisMessageFolder;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -24,8 +26,10 @@
import org.hibernate.search.mapper.orm.massindexing.MassIndexer;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hibernate.search.mapper.pojo.bridge.IdentifierBridge;
import org.hibernate.search.mapper.pojo.bridge.ValueBridge;
import org.hibernate.search.mapper.pojo.bridge.runtime.IdentifierBridgeFromDocumentIdentifierContext;
import org.hibernate.search.mapper.pojo.bridge.runtime.IdentifierBridgeToDocumentIdentifierContext;
import org.hibernate.search.mapper.pojo.bridge.runtime.ValueBridgeToIndexedValueContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
Expand Down Expand Up @@ -138,6 +142,58 @@ public LocationIdentifier fromDocumentIdentifier(String value,
return value == null ? null : LocationIdentifier.of(value);
}
});

context.bridges().exactType(IrisMessage.IrisMessageIdentifier.class)
.identifierBridge(new IdentifierBridge<>() {

@Override
public String toDocumentIdentifier(
IrisMessage.IrisMessageIdentifier value,
IdentifierBridgeToDocumentIdentifierContext context
) {
return value.toString();
}

@Override
public IrisMessage.IrisMessageIdentifier fromDocumentIdentifier(
String value,
IdentifierBridgeFromDocumentIdentifierContext context
) {
return value == null ? null : IrisMessage.IrisMessageIdentifier.of(value);
}
});

context.bridges().exactType(IrisMessageFolder.IrisMessageFolderIdentifier.class)
.identifierBridge(new IdentifierBridge<>() {

@Override
public String toDocumentIdentifier(
IrisMessageFolder.IrisMessageFolderIdentifier value,
IdentifierBridgeToDocumentIdentifierContext context
) {
return value.toString();
}

@Override
public IrisMessageFolder.IrisMessageFolderIdentifier fromDocumentIdentifier(
String value,
IdentifierBridgeFromDocumentIdentifierContext context
) {
return value == null ? null : IrisMessageFolder.IrisMessageFolderIdentifier.of(value);
}
});

context.bridges().exactType(IrisMessageFolder.IrisMessageFolderIdentifier.class)
.valueBridge(new ValueBridge<IrisMessageFolder.IrisMessageFolderIdentifier, String>() {
@Override
public String toIndexedValue(
IrisMessageFolder.IrisMessageFolderIdentifier value,
ValueBridgeToIndexedValueContext context
) {
return value == null ? null : value.toString();
}
});

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ public <T> SearchResult<T> search(String search, Pageable pageable, String[] fie
}

private PredicateFinalStep createQuery(String keyword, String[] fields,
UnaryOperator<BooleanPredicateClausesStep<?>> statusMatchFunc, SearchPredicateFactory f) {
UnaryOperator<BooleanPredicateClausesStep<?>> searchInterceptor, SearchPredicateFactory f) {

var boolPred = f.bool();

for (var keywordPart : StringUtils.split(keyword)) {
boolPred = boolPred.must(f2 -> createQueryPart(keywordPart, fields, f2));
}

return statusMatchFunc.apply(boolPred);
return searchInterceptor.apply(boolPred);
}

private PredicateFinalStep createQueryPart(String keyword, String[] fields, SearchPredicateFactory f) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
package iris.client_bff.core.web.error;

import static org.apache.commons.lang3.ArrayUtils.*;
import static org.apache.commons.lang3.StringUtils.*;

import iris.client_bff.core.web.filter.ApplicationRequestSizeLimitFilter.BlockLimitExceededException;
import iris.client_bff.events.exceptions.IRISDataRequestException;
import iris.client_bff.search_client.exceptions.IRISSearchException;
import iris.client_bff.ui.messages.ErrorMessages;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.util.unit.DataSize;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
public class GlobalControllerExceptionHandler extends ResponseEntityExceptionHandler {

private final MessageSourceAccessor messages;

@ResponseStatus(value = HttpStatus.SERVICE_UNAVAILABLE, reason = ErrorMessages.LOCATION_SEARCH)
@ExceptionHandler(IRISSearchException.class)
public void handleIRISSearchException(IRISSearchException ex) {
Expand Down Expand Up @@ -48,6 +56,16 @@ public void handleException(Exception ex) throws Exception {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, getInternalMessage(ex));
}

@ExceptionHandler(MaxUploadSizeExceededException.class)
public void handleMaxUploadSizeExceededException(MaxUploadSizeExceededException ex) {

var maxSize = DataSize.ofBytes(ex.getMaxUploadSize());

throw new ResponseStatusException(
HttpStatus.PAYLOAD_TOO_LARGE,
messages.getMessage("iris_message.max_upload_file_size", toArray(maxSize.toMegabytes() + " MB")));
}

private String getInternalMessage(Exception ex) {

var msg = defaultString(ex.getMessage());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package iris.client_bff.hd_search;

import org.apache.commons.lang3.exception.ExceptionUtils;

import java.io.Serial;

public class HdSearchException extends RuntimeException {

@Serial
private static final long serialVersionUID = -3649251457000674951L;

public HdSearchException(String failedMethod, Throwable cause) {
super("Call to '" + failedMethod + "' failed", cause);
}

public HdSearchException(String message) {
super(message);
}

public HdSearchException(Throwable cause) {
super(cause);
}

public String getErrorMessage() {
return ExceptionUtils.getRootCause(this).getMessage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package iris.client_bff.hd_search;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class HealthDepartment {

private String name;
private String rkiCode;
private String epsName;
private String department;
private Address address;
private ContactData contactData;
private ContactData covid19ContactData;
private ContactData entryContactData;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
private class Address {
private String street;
private String zipCode;
private String city;
}

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
private class ContactData {
private String phone;
private String fax;
@JsonProperty("eMail")
private String eMail;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package iris.client_bff.hd_search.eps;

import com.googlecode.jsonrpc4j.JsonRpcHttpClient;
import iris.client_bff.config.BackendServiceProperties;
import iris.client_bff.hd_search.HdSearchException;
import iris.client_bff.hd_search.HealthDepartment;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@AllArgsConstructor
public class EPSHdSearchClient {

private final JsonRpcHttpClient epsRpcClient;
private BackendServiceProperties config;

public List<HealthDepartment> searchForHd(String search) {

var methodName = config.getEndpoint() + ".searchForHd";
Map<String, String> payload = Map.of("searchKeyword", search);

try {
return Arrays.stream(epsRpcClient.invoke(methodName, payload, HealthDepartment[].class)).toList();
} catch (Throwable t) {
throw new HdSearchException(methodName, t);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package iris.client_bff.iris_messages;

import iris.client_bff.core.Aggregate;
import iris.client_bff.core.Id;
import lombok.*;
import org.hibernate.search.engine.backend.types.Sortable;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.*;

import javax.persistence.*;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Entity
@Table(name = "iris_message")
@Indexed
@Data
@EqualsAndHashCode(callSuper = true, exclude = "folder")
@NoArgsConstructor
public class IrisMessage extends Aggregate<IrisMessage, IrisMessage.IrisMessageIdentifier> {

public static final int SUBJECT_MAX_LENGTH = 500;
public static final int BODY_MAX_LENGTH = 6000;

{
id = IrisMessage.IrisMessageIdentifier.of(UUID.randomUUID());
}

@ManyToOne
@JoinColumn(name="folder_id", nullable=false)
@IndexedEmbedded(includeEmbeddedObjectId = true)
private IrisMessageFolder folder;

@Column(nullable = false)
@KeywordField(sortable = Sortable.YES, normalizer = "german")
private String subject;

@Column(nullable = false)
private String body;

@Column(nullable = false)
@Embedded
@IndexedEmbedded
@AttributeOverrides({
@AttributeOverride( name = "id", column = @Column(name = "hd_author_id")),
@AttributeOverride( name = "name", column = @Column(name = "hd_author_name"))
})
private IrisMessageHdContact hdAuthor;

@Column(nullable = false)
@Embedded
@IndexedEmbedded
@AttributeOverrides({
@AttributeOverride( name = "id", column = @Column(name = "hd_recipient_id")),
@AttributeOverride( name = "name", column = @Column(name = "hd_recipient_name"))
})
private IrisMessageHdContact hdRecipient;

private Boolean isRead;

@OneToMany(mappedBy = "message", cascade = CascadeType.ALL, orphanRemoval = true)
private List<IrisMessageFile> fileAttachments = new ArrayList<>();

@Embeddable
@EqualsAndHashCode
@RequiredArgsConstructor(staticName = "of")
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
public static class IrisMessageIdentifier implements Id, Serializable {

@Serial
private static final long serialVersionUID = 1140444389070674189L;

private final UUID id;

/**
* for JSON deserialization
*/
public static IrisMessage.IrisMessageIdentifier of(String uuid) {
return of(UUID.fromString(uuid));
}

@Override
public String toString() {
return id.toString();
}

public UUID toUUID() {
return id;
}
}

}
Loading