From 6a58087ace829242a4a965d43e6ed62e3733abef Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Thu, 17 Jul 2025 20:15:28 +0200 Subject: [PATCH] preparing for sending documents Signed-off-by: Stephan Richter --- .../srsoftware/umbrella/core/Constants.java | 3 ++ .../umbrella/core/api}/PostBox.java | 4 +- .../umbrella/core/api/Translator.java | 1 - .../umbrella/core}/model/Attachment.java | 22 ++++---- .../umbrella/core}/model/Envelope.java | 26 +++++----- .../umbrella/core}/model/Message.java | 18 +++---- .../umbrella/core/model/UmbrellaUser.java | 45 +++------------- .../srsoftware/umbrella/core/model/User.java | 52 +++++++++++++++++++ core/src/test/java/TranslatorTest.java | 10 ++-- documents/build.gradle.kts | 8 +-- .../umbrella/documents/DocumentApi.java | 12 +++-- .../umbrella/documents/SqliteDb.java | 2 +- .../umbrella/documents/TemplateDoc.java | 2 +- .../umbrella/documents/model/Customer.java | 11 +++- .../documents/model/CustomerSettings.java | 3 +- .../umbrella/message/Constants.java | 3 -- .../umbrella/message/MessageSystem.java | 15 +++--- .../message/model/CombinedMessage.java | 2 + .../srsoftware/umbrella/user/UserModule.java | 15 ++++-- 19 files changed, 145 insertions(+), 109 deletions(-) rename {messages/src/main/java/de/srsoftware/umbrella/message/model => core/src/main/java/de/srsoftware/umbrella/core/api}/PostBox.java (50%) rename {messages/src/main/java/de/srsoftware/umbrella/message => core/src/main/java/de/srsoftware/umbrella/core}/model/Attachment.java (96%) rename {messages/src/main/java/de/srsoftware/umbrella/message => core/src/main/java/de/srsoftware/umbrella/core}/model/Envelope.java (65%) rename {messages/src/main/java/de/srsoftware/umbrella/message => core/src/main/java/de/srsoftware/umbrella/core}/model/Message.java (92%) create mode 100644 core/src/main/java/de/srsoftware/umbrella/core/model/User.java diff --git a/core/src/main/java/de/srsoftware/umbrella/core/Constants.java b/core/src/main/java/de/srsoftware/umbrella/core/Constants.java index e041e87..83cd633 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/Constants.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/Constants.java @@ -33,9 +33,12 @@ public class Constants { public static final String ESTIMATED_TIME = "estimated_time"; public static final String EXPIRATION = "expiration"; + public static final String FALLBACK_LANG = "de"; public static final String GET = "GET"; public static final String ID = "id"; + public static final String JSONARRAY = "json array"; + public static final String JSONOBJECT = "json object"; public static final String KEY = "key"; public static final String LANGUAGE = "language"; public static final String LOGIN = "login"; diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/PostBox.java b/core/src/main/java/de/srsoftware/umbrella/core/api/PostBox.java similarity index 50% rename from messages/src/main/java/de/srsoftware/umbrella/message/model/PostBox.java rename to core/src/main/java/de/srsoftware/umbrella/core/api/PostBox.java index 57c398b..74e8b48 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/model/PostBox.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/PostBox.java @@ -1,5 +1,7 @@ /* © SRSoftware 2025 */ -package de.srsoftware.umbrella.message.model; +package de.srsoftware.umbrella.core.api; + +import de.srsoftware.umbrella.core.model.Envelope; public interface PostBox { public void send(Envelope envelope); diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/Translator.java b/core/src/main/java/de/srsoftware/umbrella/core/api/Translator.java index 989c0c8..9979c0f 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/api/Translator.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/Translator.java @@ -1,7 +1,6 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.core.api; -import java.util.HashMap; import java.util.Map; import java.util.TreeMap; diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/Attachment.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Attachment.java similarity index 96% rename from messages/src/main/java/de/srsoftware/umbrella/message/model/Attachment.java rename to core/src/main/java/de/srsoftware/umbrella/core/model/Attachment.java index bffc120..dbf8e59 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/model/Attachment.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Attachment.java @@ -1,5 +1,5 @@ /* © SRSoftware 2025 */ -package de.srsoftware.umbrella.message.model; +package de.srsoftware.umbrella.core.model; import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidFieldException; @@ -16,16 +16,6 @@ public record Attachment(String name, String mime, byte[] content) { private static final Base64.Decoder BASE64 = Base64.getDecoder(); - public static Attachment of(JSONObject json) throws UmbrellaException { - for (var key : Set.of(NAME, MIME, DATA)) { - if (!json.has(key)) throw missingFieldException(key); - } - if (!(json.get(NAME) instanceof String name)) throw invalidFieldException(NAME,STRING); - if (!(json.get(MIME) instanceof String mime)) throw invalidFieldException(MIME,STRING); - if (!(json.get(DATA) instanceof String data)) throw invalidFieldException(DATA,STRING); - return new Attachment(name,mime, BASE64.decode(data)); - } - @Override public boolean equals(Object o) { if (!(o instanceof Attachment that)) return false; @@ -36,4 +26,14 @@ public record Attachment(String name, String mime, byte[] content) { public int hashCode() { return Objects.hash(name, mime, Arrays.hashCode(content)); } + + public static Attachment of(JSONObject json) throws UmbrellaException { + for (var key : Set.of(NAME, MIME, DATA)) { + if (!json.has(key)) throw missingFieldException(key); + } + if (!(json.get(NAME) instanceof String name)) throw invalidFieldException(NAME,STRING); + if (!(json.get(MIME) instanceof String mime)) throw invalidFieldException(MIME,STRING); + if (!(json.get(DATA) instanceof String data)) throw invalidFieldException(DATA,STRING); + return new Attachment(name,mime, BASE64.decode(data)); + } } diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/Envelope.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java similarity index 65% rename from messages/src/main/java/de/srsoftware/umbrella/message/model/Envelope.java rename to core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java index 1fbe6e4..8343118 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/model/Envelope.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java @@ -1,14 +1,12 @@ /* © SRSoftware 2025 */ -package de.srsoftware.umbrella.message.model; +package de.srsoftware.umbrella.core.model; +import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidFieldException; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; -import static de.srsoftware.umbrella.message.Constants.*; import static java.text.MessageFormat.format; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; -import de.srsoftware.umbrella.core.model.EmailAddress; -import de.srsoftware.umbrella.core.model.UmbrellaUser; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -18,13 +16,13 @@ import org.json.JSONObject; public class Envelope { private Message message; - private Set receivers; + private Set receivers; - public Envelope(Message message, UmbrellaUser receiver){ + public Envelope(Message message, User receiver){ this(message,new HashSet<>(Set.of(receiver))); } - public Envelope(Message message, HashSet receivers) { + public Envelope(Message message, HashSet receivers) { this.message = message; this.receivers = receivers; } @@ -40,16 +38,16 @@ public class Envelope { var message = Message.from(json); var obj = json.get(RECEIVERS); if (obj instanceof JSONObject) obj = new JSONArray(List.of(obj)); - if (!(obj instanceof JSONArray receiverList)) throw invalidFieldException(RECEIVERS,JSONARRAY); - var receivers = new HashSet(); + if (!(obj instanceof JSONArray receiverList)) throw invalidFieldException(RECEIVERS, JSONARRAY); + var receivers = new HashSet(); for (var o : receiverList){ - if (!(o instanceof JSONObject receiverData)) throw invalidFieldException("entries of "+RECEIVERS,JSONOBJECT); - receivers.add(UmbrellaUser.of(receiverData)); + if (!(o instanceof JSONObject receiverData)) throw invalidFieldException("entries of "+ RECEIVERS, JSONOBJECT); + receivers.add(User.of(receiverData)); } return new Envelope(message,receivers); } - public boolean isFor(UmbrellaUser receiver) { + public boolean isFor(User receiver) { return receivers.contains(receiver); } @@ -57,12 +55,12 @@ public class Envelope { return message; } - public Set receivers(){ + public Set receivers(){ return receivers; } @Override public String toString() { - return format("{0} (to: {1}), subject: {2}",getClass().getSimpleName(),receivers.stream().map(UmbrellaUser::email).map(EmailAddress::toString).collect(Collectors.joining(", ")),message.subject()); + return format("{0} (to: {1}), subject: {2}",getClass().getSimpleName(),receivers.stream().map(User::email).map(EmailAddress::toString).collect(Collectors.joining(", ")),message.subject()); } } diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/Message.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Message.java similarity index 92% rename from messages/src/main/java/de/srsoftware/umbrella/message/model/Message.java rename to core/src/main/java/de/srsoftware/umbrella/core/model/Message.java index 3e71058..2ebf159 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/model/Message.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Message.java @@ -1,25 +1,29 @@ /* © SRSoftware 2025 */ -package de.srsoftware.umbrella.message.model; +package de.srsoftware.umbrella.core.model; import static de.srsoftware.tools.Optionals.isSet; import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidFieldException; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; -import static de.srsoftware.umbrella.message.Constants.*; import static java.text.MessageFormat.format; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; -import de.srsoftware.umbrella.core.model.UmbrellaUser; import java.util.*; import org.json.JSONArray; import org.json.JSONObject; public record Message(UmbrellaUser sender, String subject, String body, Map fills, List attachments) { + @Override + public boolean equals(Object o) { + if (!(o instanceof Message message)) return false; + return Objects.equals(sender, message.sender) && Objects.equals(subject, message.subject) && Objects.equals(body, message.body) && Objects.equals(attachments, message.attachments); + } + public static Message from(JSONObject json) throws UmbrellaException { for (var key : Set.of(SENDER, SUBJECT, BODY)) { if (!json.has(key)) throw missingFieldException(key); } - if (!(json.get(SENDER) instanceof JSONObject senderObject)) throw invalidFieldException(SENDER,JSONOBJECT); + if (!(json.get(SENDER) instanceof JSONObject senderObject)) throw invalidFieldException(SENDER, JSONOBJECT); if (!(json.get(SUBJECT) instanceof String subject && isSet(subject))) throw invalidFieldException(SUBJECT,STRING); if (!(json.get(BODY) instanceof String body && isSet(body))) throw invalidFieldException(BODY,STRING); @@ -40,12 +44,6 @@ public record Message(UmbrellaUser sender, String subject, String body, Map\n"); source = source.replace("",value); } - return new StringContent(source); + return new StringContent(source,content.mimeType()); } return precursor; } diff --git a/documents/src/main/java/de/srsoftware/umbrella/documents/model/Customer.java b/documents/src/main/java/de/srsoftware/umbrella/documents/model/Customer.java index 3fdfcc9..cbd0822 100644 --- a/documents/src/main/java/de/srsoftware/umbrella/documents/model/Customer.java +++ b/documents/src/main/java/de/srsoftware/umbrella/documents/model/Customer.java @@ -18,13 +18,15 @@ public final class Customer implements Mappable { private String name; private String email; private String taxNumber; + private String language; private final Set dirtyFields = new HashSet<>(); - public Customer(String id, String name, String email, String taxNumber) { + public Customer(String id, String name, String email, String taxNumber, String language) { this.id = id; this.name = name; this.email = email; this.taxNumber = taxNumber; + this.language = language; } public void clean() { @@ -59,6 +61,10 @@ public final class Customer implements Mappable { return !dirtyFields.isEmpty(); } + public String language(){ + return language; + } + public String name() { return name; } @@ -68,7 +74,8 @@ public final class Customer implements Mappable { if (!json.has(NAME) || !(json.get(NAME) instanceof String name)) throw missingFieldException(NAME); if (!json.has(EMAIL) || !(json.get(EMAIL) instanceof String email)) throw missingFieldException(EMAIL); if (!json.has(FIELD_TAX_ID) || !(json.get(FIELD_TAX_ID) instanceof String taxId)) throw missingFieldException(FIELD_TAX_ID); - return new Customer(id,name,email,taxId); + var lang = json.has(LANGUAGE) && json.get(LANGUAGE) instanceof String l ? l : FALLBACK_LANG; + return new Customer(id,name,email,taxId,lang); } public void patch(JSONObject json) { diff --git a/documents/src/main/java/de/srsoftware/umbrella/documents/model/CustomerSettings.java b/documents/src/main/java/de/srsoftware/umbrella/documents/model/CustomerSettings.java index c8ac8b1..f069d4b 100644 --- a/documents/src/main/java/de/srsoftware/umbrella/documents/model/CustomerSettings.java +++ b/documents/src/main/java/de/srsoftware/umbrella/documents/model/CustomerSettings.java @@ -2,10 +2,9 @@ package de.srsoftware.umbrella.documents.model; -import de.srsoftware.tools.Mappable; - import static de.srsoftware.umbrella.documents.Constants.*; +import de.srsoftware.tools.Mappable; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/Constants.java b/messages/src/main/java/de/srsoftware/umbrella/message/Constants.java index 755f4f4..2473ba8 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/Constants.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/Constants.java @@ -16,10 +16,7 @@ public class Constants { public static final String FIELD_HOST = "host"; public static final String FIELD_PORT = "port"; public static final String HOST = "mail.smtp.host"; - public static final String JSONARRAY = "json array"; - public static final String JSONOBJECT = "json object"; public static final String PORT = "mail.smtp.port"; - public static final String RECEIVERS = "receivers"; public static final String SSL = "mail.smtp.ssl.enable"; public static final String SUBMISSION = "submission"; diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java index 3b62f51..0c87198 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java @@ -9,12 +9,13 @@ import static de.srsoftware.umbrella.message.Constants.*; import static java.lang.System.Logger.Level.*; import de.srsoftware.configuration.Configuration; +import de.srsoftware.umbrella.core.api.PostBox; import de.srsoftware.umbrella.core.api.Translator; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; +import de.srsoftware.umbrella.core.model.Envelope; import de.srsoftware.umbrella.core.model.UmbrellaUser; +import de.srsoftware.umbrella.core.model.User; import de.srsoftware.umbrella.message.model.CombinedMessage; -import de.srsoftware.umbrella.message.model.Envelope; -import de.srsoftware.umbrella.message.model.PostBox; import jakarta.activation.DataHandler; import jakarta.mail.Message; import jakarta.mail.MessagingException; @@ -31,7 +32,7 @@ import java.util.function.BiFunction; public class MessageSystem implements PostBox { public static final System.Logger LOG = System.getLogger(MessageSystem.class.getSimpleName()); private final Timer timer = new Timer(); - private record Receiver(UmbrellaUser user, de.srsoftware.umbrella.message.model.Message message){} + private record Receiver(User user, de.srsoftware.umbrella.core.model.Message message){} private class SubmissionTask extends TimerTask{ @@ -99,11 +100,11 @@ public class MessageSystem implements PostBox { private synchronized void processMessages(Integer scheduledHour) { LOG.log(INFO,"Running {0}…",scheduledHour == null ? "instantly" : "scheduled at "+scheduledHour); var queue = new ArrayList<>(this.queue); - var dueRecipients = new ArrayList(); - List recipients = queue.stream().map(Envelope::receivers).flatMap(Set::stream).filter(Objects::nonNull).distinct().toList(); + var dueRecipients = new ArrayList(); + List recipients = queue.stream().map(Envelope::receivers).flatMap(Set::stream).filter(Objects::nonNull).distinct().toList(); { // for known users: get notification preferences, fallback to _immediately_ for unknown users - for (UmbrellaUser recv : recipients) { + for (User recv : recipients) { if (recv instanceof UmbrellaUser uu) { try { if (!db.getSettings(uu).sendAt(scheduledHour)) continue; @@ -157,7 +158,7 @@ public class MessageSystem implements PostBox { } - private void send(CombinedMessage message, UmbrellaUser receiver, Date date) throws MessagingException { + private void send(CombinedMessage message, User receiver, Date date) throws MessagingException { LOG.log(TRACE,"Sending combined message to {0}…",receiver); session = session(); MimeMessage msg = new MimeMessage(session); diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java b/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java index ced3efb..309408a 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java @@ -5,6 +5,8 @@ import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.TRACE; import static java.text.MessageFormat.format; +import de.srsoftware.umbrella.core.model.Attachment; +import de.srsoftware.umbrella.core.model.Message; import de.srsoftware.umbrella.core.model.UmbrellaUser; import java.util.*; import java.util.function.BiFunction; diff --git a/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java b/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java index b6d4e6b..6e8bcda 100644 --- a/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java +++ b/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java @@ -30,14 +30,14 @@ import de.srsoftware.configuration.Configuration; import de.srsoftware.tools.Path; import de.srsoftware.tools.SessionToken; import de.srsoftware.umbrella.core.BaseHandler; +import de.srsoftware.umbrella.core.api.PostBox; import de.srsoftware.umbrella.core.api.UserService; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.EmailAddress; +import de.srsoftware.umbrella.core.model.Envelope; +import de.srsoftware.umbrella.core.model.Message; import de.srsoftware.umbrella.core.model.Token; import de.srsoftware.umbrella.core.model.UmbrellaUser; -import de.srsoftware.umbrella.message.MessageSystem; -import de.srsoftware.umbrella.message.model.Envelope; -import de.srsoftware.umbrella.message.model.Message; import de.srsoftware.umbrella.user.api.LoginServiceDb; import de.srsoftware.umbrella.user.api.UserDb; import de.srsoftware.umbrella.user.model.*; @@ -74,7 +74,7 @@ public class UserModule extends BaseHandler implements UserService { private final LoginServiceDb logins; private final HashMap stateMap = new HashMap<>(); // map from state to OIDC provider name private final HashMap tokenMap = new HashMap<>(); - private final MessageSystem messages; + private final PostBox messages; static { try { @@ -84,7 +84,7 @@ public class UserModule extends BaseHandler implements UserService { } } - public UserModule(Configuration config, MessageSystem messageSystem) throws UmbrellaException { + public UserModule(Configuration config, PostBox messageSystem) throws UmbrellaException { var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingConfigException(CONFIG_DATABASE)); // may be splitted in separate db files later logins = new SqliteDB(connect(dbFile)); @@ -443,6 +443,11 @@ public class UserModule extends BaseHandler implements UserService { } } + @Override + public PostBox postBox() { + return messages; + } + private boolean postCreate(HttpExchange ex) throws IOException, UmbrellaException { var optUser = loadUser(ex); if (!(optUser.isPresent() && optUser.get() instanceof DbUser dbUser)) return unauthorized(ex);