diff --git a/core/src/main/java/de/srsoftware/umbrella/core/constants/Constants.java b/core/src/main/java/de/srsoftware/umbrella/core/constants/Constants.java index 7db678a5..ab3124c3 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/constants/Constants.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/constants/Constants.java @@ -2,10 +2,10 @@ package de.srsoftware.umbrella.core.constants; -import java.time.format.DateTimeFormatter; - import static java.nio.charset.StandardCharsets.UTF_8; +import java.time.format.DateTimeFormatter; + public class Constants { private Constants(){ diff --git a/core/src/main/java/de/srsoftware/umbrella/core/constants/Field.java b/core/src/main/java/de/srsoftware/umbrella/core/constants/Field.java index 71e966d0..1996d582 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/constants/Field.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/constants/Field.java @@ -67,8 +67,10 @@ public class Field { public static final String HASH = "hash"; public static final String HEAD = "head"; + public static final String HOURS = "hours"; public static final String ID = "id"; + public static final String INSTANTLY = "instantly"; public static final String ITEM = "item"; public static final String ITEM_CODE = "item_code"; @@ -123,6 +125,7 @@ public class Field { public static final String SENDER = "sender"; public static final String SETTINGS = "settings"; public static final String SHOW_CLOSED = "show_closed"; + public static final String SILENT = "silent"; public static final String SOURCE = "source"; public static final String START_DATE = "start_date"; public static final String START_TIME = "start_time"; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java index 462884e2..6b9f3c52 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java @@ -9,7 +9,6 @@ import static de.srsoftware.umbrella.core.model.Translatable.t; import static java.text.MessageFormat.format; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; - import java.time.LocalDateTime; import java.util.HashSet; import java.util.List; diff --git a/frontend/src/routes/message/Settings.svelte b/frontend/src/routes/message/Settings.svelte index 1afd3987..ce4d15d4 100644 --- a/frontend/src/routes/message/Settings.svelte +++ b/frontend/src/routes/message/Settings.svelte @@ -1,7 +1,162 @@ -
- {t('message settingss')} +
+ {t('message settings')} +

{t('When shall messages be delivered?')}

+ + + + + + + + + + + + + + +
+ +
+ {t('collect messages and send them at')} + + ⎧
+ ⎪
+ ⎪
+ ⎪
+ ⎪
+ ⎪
+ ⎪
+ ⎪
+ ⎨
+ ⎪
+ ⎪
+ ⎪
+ ⎪
+ ⎪
+ ⎪
+ ⎪
+ ⎩ +
+ + + + + + + + + + + + + + + +
+ +
\ No newline at end of file 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 27c23b1e..15a5ecf6 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java @@ -5,9 +5,11 @@ import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.constants.Constants.TIME_FORMATTER; import static de.srsoftware.umbrella.core.constants.Constants.UTF8; import static de.srsoftware.umbrella.core.constants.Field.*; +import static de.srsoftware.umbrella.core.constants.Path.SETTINGS; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingConfig; import static de.srsoftware.umbrella.core.model.Translatable.t; import static de.srsoftware.umbrella.message.Constants.*; +import static de.srsoftware.umbrella.message.model.Schedule.schedule; import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus; import static java.lang.System.Logger.Level.*; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; @@ -24,7 +26,7 @@ import de.srsoftware.umbrella.core.model.Envelope; import de.srsoftware.umbrella.core.model.Token; 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.*; import de.srsoftware.umbrella.messagebus.EventListener; import de.srsoftware.umbrella.messagebus.events.Event; import jakarta.activation.DataHandler; @@ -36,12 +38,13 @@ import jakarta.mail.internet.MimeBodyPart; import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMultipart; import jakarta.mail.util.ByteArrayDataSource; -import org.json.JSONObject; - import java.io.IOException; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; +import org.json.JSONArray; +import org.json.JSONObject; + public class MessageSystem extends BaseHandler implements PostBox, EventListener { public static final System.Logger LOG = System.getLogger(MessageSystem.class.getSimpleName()); private final Timer timer = new Timer(); @@ -120,11 +123,44 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener } } + @Override + public boolean doPatch(Path path, HttpExchange ex) throws IOException { + addCors(ex); + try { + Optional token = SessionToken.from(ex).map(Token::of); + var user = ModuleRegistry.userService().loadUser(token); + if (user.isEmpty()) return unauthorized(ex); + var head = path.pop(); + return switch (head){ + case SETTINGS -> patchSettings(ex,user.get()); + default -> super.doGet(path,ex); + }; + } catch (NumberFormatException e){ + return sendContent(ex,HTTP_BAD_REQUEST,"Invalid project id"); + } catch (UmbrellaException e){ + return send(ex,e); + } + } + private boolean listMessages(HttpExchange ex, UmbrellaUser user) throws IOException { var messages = queue.stream().filter(e -> e.isFor(user)).map(e -> summary(e, user.language())).toList(); return sendContent(ex,messages); } + private boolean patchSettings(HttpExchange ex, UmbrellaUser user) throws IOException { + var json = json(ex); + Settings settings = null; + if (json.has(INSTANTLY) && json.get(INSTANTLY) instanceof Boolean b && b){ + settings = new Instantly(); + } else { + if (json.has(HOURS) && json.get(HOURS) instanceof JSONArray hrs){ + var hours = hrs.toList().stream().filter(v -> v instanceof Integer).map(Integer.class::cast).toList(); + settings = schedule(hours); + } else settings = new Silent(); + } + return sendContent(ex,db.update(user,settings)); + } + private static JSONObject summary(Envelope envelope, String lang) { var sender = envelope.message().sender().name(); var subject = envelope.message().subject().translate(lang); diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java b/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java index deeba630..a22966f7 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java @@ -8,19 +8,19 @@ import static de.srsoftware.umbrella.core.constants.Constants.TABLE_SETTINGS; import static de.srsoftware.umbrella.core.constants.Field.*; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.core.model.Translatable.t; -import static de.srsoftware.umbrella.message.model.Settings.Times; -import static java.lang.System.Logger.Level.WARNING; +import static de.srsoftware.umbrella.message.model.Schedule.schedule; import static java.text.MessageFormat.format; import de.srsoftware.umbrella.core.constants.Text; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.UmbrellaUser; +import de.srsoftware.umbrella.message.model.Instantly; import de.srsoftware.umbrella.message.model.Settings; +import de.srsoftware.umbrella.message.model.Silent; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.HashSet; -import java.util.stream.Collectors; +import java.util.Arrays; public class SqliteMessageDb implements MessageDb{ private static final System.Logger LOG = System.getLogger(SqliteMessageDb.class.getSimpleName()); @@ -100,21 +100,19 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) private Settings toSettings(ResultSet rs) throws SQLException { var submission = rs.getString(VALUE); - var parts = submission.split(","); - var times = new HashSet(); - for (var part : parts) try { - times.add(Times.valueOf(part)); - } catch (IllegalArgumentException e) { - LOG.log(WARNING,"encountered {0}, which is not a valid Times enumeration value!",part); + if (submission.trim().equalsIgnoreCase(INSTANTLY)) return new Instantly(); + try { + var times = Arrays.stream(submission.split(",")).map(Integer::parseInt).toList(); + return schedule(times); + } catch (NumberFormatException nfe){ + return new Silent(); } - return new Settings(times); } @Override public Settings update(UmbrellaUser user, Settings settings) throws UmbrellaException { - var times = settings.times().stream().map(Times::toString).collect(Collectors.joining(",")); try { - replaceInto(TABLE_SUBMISSIONS, USER_ID, VALUE).values(user.id(),times).execute(db).close(); + replaceInto(TABLE_SUBMISSIONS, USER_ID, VALUE).values(user.id(),settings.toString()).execute(db).close(); return settings; } catch (SQLException e) { throw failedToStoreObject("submission data").causedBy(e); diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/Instantly.java b/messages/src/main/java/de/srsoftware/umbrella/message/model/Instantly.java new file mode 100644 index 00000000..0fa4e472 --- /dev/null +++ b/messages/src/main/java/de/srsoftware/umbrella/message/model/Instantly.java @@ -0,0 +1,23 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.message.model; + +import static de.srsoftware.umbrella.core.constants.Field.INSTANTLY; + +import java.util.Map; + +public class Instantly implements Settings{ + @Override + public Map toMap() { + return Map.of(INSTANTLY,true); + } + + @Override + public boolean sendAt(int scheduledHour) { + return false; + } + + @Override + public String toString() { + return INSTANTLY; + } +} diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/Schedule.java b/messages/src/main/java/de/srsoftware/umbrella/message/model/Schedule.java new file mode 100644 index 00000000..8ec9f539 --- /dev/null +++ b/messages/src/main/java/de/srsoftware/umbrella/message/model/Schedule.java @@ -0,0 +1,37 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.message.model; + +import static de.srsoftware.umbrella.core.constants.Field.HOURS; +import static java.util.stream.Collectors.joining; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; + +public class Schedule implements Settings{ + + private final HashSet hours; + + private Schedule(Collection hours){ + this.hours = new HashSet<>(hours); + } + + public static Settings schedule(Collection hours){ + return hours == null || hours.isEmpty() ? new Silent() : new Schedule(hours); + } + + @Override + public boolean sendAt(int scheduledHour) { + return hours.contains(scheduledHour); + } + + @Override + public Map toMap() { + return Map.of(HOURS,hours); + } + + @Override + public String toString() { + return hours.stream().map(Object::toString).collect(joining(",")); + } +} diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/Settings.java b/messages/src/main/java/de/srsoftware/umbrella/message/model/Settings.java index 9562bd18..459ca726 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/model/Settings.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/model/Settings.java @@ -1,38 +1,10 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.message.model; -import static de.srsoftware.umbrella.message.Constants.SUBMISSION; import de.srsoftware.tools.Mappable; -import java.util.Map; -import java.util.Set; - -public record Settings(Set times) implements Mappable { - - public enum Times{ - INSTANTLY, - AT8, - AT10, - AT12, - AT14, - AT16, - AT18, - AT20; - - public boolean matches(int hour){ - if (this == INSTANTLY) return false; - return Integer.parseInt(toString().substring(2)) == hour; - } - } - - public boolean sendAt(Integer scheduledHour) { - return times.contains(Times.INSTANTLY) || (scheduledHour != null && times.stream().anyMatch(time -> time.matches(scheduledHour))); - } - @Override - public Map toMap() { - return Map.of(SUBMISSION,times); - } - +public interface Settings extends Mappable { + boolean sendAt(int scheduledHour); } diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/Silent.java b/messages/src/main/java/de/srsoftware/umbrella/message/model/Silent.java new file mode 100644 index 00000000..64561da3 --- /dev/null +++ b/messages/src/main/java/de/srsoftware/umbrella/message/model/Silent.java @@ -0,0 +1,23 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.message.model; + +import static de.srsoftware.umbrella.core.constants.Field.SILENT; + +import java.util.Map; + +public class Silent implements Settings{ + @Override + public boolean sendAt(int scheduledHour) { + return false; + } + + @Override + public Map toMap() { + return Map.of(SILENT,true); + } + + @Override + public String toString() { + return "silent"; + } +} diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index 55502a0f..c2fcc090 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -188,6 +188,7 @@ "members": "Mitarbeiter", "message": "Nachricht", "messages": "Benachrichtigungen", + "message settings": "Benachrichtigungs-Einstellungen", "miscellaneous_settings": "sonstige Einstellungen", "missing_new_item_id": "Alter Artikel-ID ({0}) wurde keine neue ID zugeordnet!", "mismatch": "ungleich", diff --git a/translations/src/main/resources/en.json b/translations/src/main/resources/en.json index 7a8c44ca..44874b6f 100644 --- a/translations/src/main/resources/en.json +++ b/translations/src/main/resources/en.json @@ -188,6 +188,7 @@ "members": "members", "message": "message", "messages": "messages", + "message settings": "message settings", "miscellaneous_settings": "miscellaneous settings", "missing_new_item_id": "Old item id ({0}) has no new counterpart!", "mismatch": "mismatch", diff --git a/web/src/main/resources/web/css/default.css b/web/src/main/resources/web/css/default.css index 705408a7..e0b559e1 100644 --- a/web/src/main/resources/web/css/default.css +++ b/web/src/main/resources/web/css/default.css @@ -323,6 +323,13 @@ textarea{ max-height: unset; } +.message.settings label{ + display: block; +} +.message.settings td{ + vertical-align: middle; +} + .project th, .task th{ text-align: right;