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?')}
+
\ 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;