diff --git a/src/BreCalClient/AboutDialog.xaml b/src/BreCalClient/AboutDialog.xaml
index 76a4af8..f43da10 100644
--- a/src/BreCalClient/AboutDialog.xaml
+++ b/src/BreCalClient/AboutDialog.xaml
@@ -21,12 +21,12 @@
-
+
diff --git a/src/BreCalClient/AboutDialog.xaml.cs b/src/BreCalClient/AboutDialog.xaml.cs
index d2def00..005d6fb 100644
--- a/src/BreCalClient/AboutDialog.xaml.cs
+++ b/src/BreCalClient/AboutDialog.xaml.cs
@@ -57,11 +57,11 @@ namespace BreCalClient
this.LoginResult.UserPhone = this.textBoxUserPhone.Text.Trim();
this.LoginResult.UserEmail = this.textBoxUserEmail.Text.Trim();
this.LoginResult.NotifyEmail = this.checkboxEMailNotify.IsChecked ?? false;
- this.LoginResult.NotifyPopup = this.checkboxPushNotify.IsChecked ?? false;
- this.LoginResult.NotifyOn.Clear();
+ this.LoginResult.NotifyPopup = this.checkboxPushNotify.IsChecked ?? false;
if ((this.checkListBoxEventSelection.SelectedItems.Count > 0) && (this.LoginResult.NotifyOn == null))
this.LoginResult.NotifyOn = new();
- foreach(NotificationType nt in this.checkListBoxEventSelection.SelectedItems)
+ this.LoginResult.NotifyOn.Clear();
+ foreach (NotificationType nt in this.checkListBoxEventSelection.SelectedItems)
this.LoginResult.NotifyOn.Add(nt);
this.ChangeUserSettingsRequested?.Invoke();
}
diff --git a/src/BreCalClient/AppNotification.cs b/src/BreCalClient/AppNotification.cs
index 46faaa5..7db4d5c 100644
--- a/src/BreCalClient/AppNotification.cs
+++ b/src/BreCalClient/AppNotification.cs
@@ -8,24 +8,17 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using ToastNotifications.Core;
using BreCalClient.misc.Model;
-using System.Runtime.CompilerServices;
namespace BreCalClient
{
- internal class AppNotification
+ internal class AppNotification(int id)
{
- private static readonly Dictionary _notifications = new();
- private readonly int _id;
- private static readonly ObservableCollection _notificationsCollection = new();
-
- public AppNotification(int id)
- {
- _id = id;
- }
+ private static readonly Dictionary _notifications = [];
+ private static readonly ObservableCollection _notificationsCollection = [];
#region Properties
- public int Id { get { return _id; } }
+ public int Id { get { return id; } }
public string? NotificationType
{
@@ -95,7 +88,7 @@ namespace BreCalClient
SaveNotifications();
}
- internal static bool UpdateNotifications(List notifications, System.Collections.Concurrent.ConcurrentDictionary currentShipcalls, ToastViewModel vm)
+ internal static bool UpdateNotifications(List notifications, System.Collections.Concurrent.ConcurrentDictionary currentShipcalls, ToastViewModel vm, LoginResult loginResult)
{
bool result = false;
@@ -121,6 +114,10 @@ namespace BreCalClient
if (!iAmAssigned) continue;
}
+ // filter out notifications the user is not interested in
+ if((notification.Type != null) && !loginResult.NotifyOn.Contains(notification.Type.Value))
+ continue;
+
if (!_notificationsCollection.Where(x => x.Id == notification.Id).Any())
{
List newList = new(_notificationsCollection);
@@ -176,9 +173,8 @@ namespace BreCalClient
if (!string.IsNullOrEmpty(ap.Message))
toastText += $" \n{ap.Message}";
- if (!_notifications.ContainsKey(notification.Id))
+ if (_notifications.TryAdd(notification.Id, ap))
{
- _notifications.Add(notification.Id, ap);
App.Current.Dispatcher.Invoke(() =>
{
vm.ShowAppNotification(toastText, options);
@@ -197,7 +193,7 @@ namespace BreCalClient
internal static void SaveNotifications()
{
if (Properties.Settings.Default.Notifications == null)
- Properties.Settings.Default.Notifications = new();
+ Properties.Settings.Default.Notifications = [];
else
Properties.Settings.Default.Notifications.Clear();
foreach (int notification_id in _notifications.Keys)
diff --git a/src/BreCalClient/MainWindow.xaml.cs b/src/BreCalClient/MainWindow.xaml.cs
index 724f984..ed94257 100644
--- a/src/BreCalClient/MainWindow.xaml.cs
+++ b/src/BreCalClient/MainWindow.xaml.cs
@@ -52,7 +52,7 @@ namespace BreCalClient
private readonly ConcurrentDictionary _allShipcallsDict = new();
private readonly ConcurrentDictionary _allShipCallsControlDict = new();
- private readonly List _visibleControlModels = new();
+ private readonly List _visibleControlModels = [];
private readonly ShipcallApi _shipcallApi;
private readonly UserApi _userApi;
@@ -195,7 +195,7 @@ namespace BreCalClient
}
catch (ApiException ex)
{
- if ((ex.ErrorContent != null && ((string)ex.ErrorContent).StartsWith("{"))) {
+ if ((ex.ErrorContent != null && ((string)ex.ErrorContent).StartsWith('{'))) {
Error? anError = JsonConvert.DeserializeObject((string)ex.ErrorContent);
if ((anError != null) && anError.ErrorField.Equals("invalid credentials"))
this.labelLoginResult.Content = BreCalClient.Resources.Resources.textWrongCredentials;
@@ -309,7 +309,7 @@ namespace BreCalClient
scmOut.Shipcall.DepartureBerthId = esc.ShipcallModel.Shipcall?.ArrivalBerthId;
if (esc.ShipcallModel.Shipcall != null)
{
- scmOut.Shipcall.Participants = new();
+ scmOut.Shipcall.Participants = [];
scmOut.Shipcall.Participants.AddRange(esc.ShipcallModel.Shipcall.Participants);
foreach(ParticipantType pType in esc.ShipcallModel.AssignedParticipants.Keys)
scmOut.AssignedParticipants[pType] = esc.ShipcallModel.AssignedParticipants[pType];
@@ -418,7 +418,7 @@ namespace BreCalClient
{
this.searchFilterControl.SearchFilter.Ports.Clear();
- List berths = new();
+ List berths = [];
foreach (Port port in comboBoxPorts.SelectedItems)
{
this.searchFilterControl.SearchFilter.Ports.Add(port.Id);
@@ -449,8 +449,8 @@ namespace BreCalClient
_historyDialog.Closed += (sender, e) => { this._historyDialog = null; };
_historyDialog.HistoryItemSelected += (x) =>
{
- if(_allShipCallsControlDict.ContainsKey(x))
- _allShipCallsControlDict[x].BringIntoView();
+ if(_allShipCallsControlDict.TryGetValue(x, out ShipcallControl? value))
+ value.BringIntoView();
};
_historyDialog.Show();
}
@@ -514,14 +514,14 @@ namespace BreCalClient
SearchFilterModel? currentFilter = null;
if (SearchFilterModel.filterMap != null)
{
- if((_loginResult != null) && SearchFilterModel.filterMap.ContainsKey(_loginResult.Id))
+ if((_loginResult != null) && SearchFilterModel.filterMap.TryGetValue(_loginResult.Id, out SearchFilterModel? value))
{
- currentFilter = SearchFilterModel.filterMap[_loginResult.Id];
+ currentFilter = value;
}
}
else
{
- SearchFilterModel.filterMap = new();
+ SearchFilterModel.filterMap = [];
}
if (currentFilter == null)
{
@@ -607,7 +607,7 @@ namespace BreCalClient
// load times for each shipcall
List currentTimes = await _timesApi.TimesGetAsync(shipcall.Id);
- if (!_allShipcallsDict.ContainsKey(shipcall.Id))
+ if (!_allShipcallsDict.TryGetValue(shipcall.Id, out ShipcallControlModel? value))
{
// add entry
ShipcallControlModel scm = new()
@@ -619,10 +619,9 @@ namespace BreCalClient
}
else
{
- // update entry
- _allShipcallsDict[shipcall.Id].Shipcall = shipcall;
- _allShipcallsDict[shipcall.Id].Times = currentTimes;
- UpdateShipcall(_allShipcallsDict[shipcall.Id]);
+ value.Shipcall = shipcall;
+ value.Times = currentTimes;
+ UpdateShipcall(value);
}
}
@@ -674,7 +673,7 @@ namespace BreCalClient
if (_loginResult?.NotifyPopup ?? false)
{
List notifications = await _staticApi.NotificationsGetAsync();
- AppNotification.UpdateNotifications(notifications, _allShipcallsDict, _vm);
+ AppNotification.UpdateNotifications(notifications, _allShipcallsDict, _vm, _loginResult);
}
}
}
@@ -689,8 +688,8 @@ namespace BreCalClient
_allShipcallsDict[scm.Shipcall.Id] = scm;
Shipcall shipcall = scm.Shipcall;
- if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId))
- scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId].Ship;
+ if (BreCalLists.ShipLookupDict.TryGetValue(shipcall.ShipId, out ShipModel? value))
+ scm.Ship = value.Ship;
if (shipcall.Type == ShipcallType.Arrival)
{
@@ -723,8 +722,8 @@ namespace BreCalClient
{
if(scm.Shipcall == null) return;
Shipcall shipcall = scm.Shipcall;
- if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId))
- scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId].Ship;
+ if (BreCalLists.ShipLookupDict.TryGetValue(shipcall.ShipId, out ShipModel? value))
+ scm.Ship = value.Ship;
if (shipcall.Type == ShipcallType.Arrival)
{
@@ -969,10 +968,10 @@ namespace BreCalClient
foreach (ShipcallControlModel visibleModel in this._visibleControlModels)
{
if (visibleModel.Shipcall == null) continue; // should not happen
- if (this._allShipCallsControlDict.ContainsKey(visibleModel.Shipcall.Id))
+ if (this._allShipCallsControlDict.TryGetValue(visibleModel.Shipcall.Id, out ShipcallControl? value))
{
- this._allShipCallsControlDict[visibleModel.Shipcall.Id].RefreshData();
- this.stackPanel.Children.Add(this._allShipCallsControlDict[visibleModel.Shipcall.Id]);
+ value.RefreshData();
+ this.stackPanel.Children.Add(value);
}
}
}
@@ -1122,8 +1121,8 @@ namespace BreCalClient
}
else
{
- if(editControl.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
- editControl.Times.ParticipantId = editControl.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId;
+ if(editControl.ShipcallModel.AssignedParticipants.TryGetValue(ParticipantType.AGENCY, out ParticipantAssignment? value))
+ editControl.Times.ParticipantId = value.ParticipantId;
}
editControl.Times.ParticipantType = (int)ParticipantType.AGENCY;
if(editControl.ShowDialog() ?? false)
@@ -1139,9 +1138,9 @@ namespace BreCalClient
}
// always try to be the agent, even if we are BSMD
- if (editControl.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
+ if (editControl.ShipcallModel.AssignedParticipants.TryGetValue(ParticipantType.AGENCY, out ParticipantAssignment? value))
{
- editControl.Times.ParticipantId = editControl.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId;
+ editControl.Times.ParticipantId = value.ParticipantId;
}
else
{
@@ -1190,7 +1189,7 @@ namespace BreCalClient
// (if the special-flag is enabled). Assigned Agency: ShipcallParticipantMap(id=628, shipcall_id=115, participant_id=10,
// type=8, created=datetime.datetime(2024, 8, 28, 15, 13, 14), modified=None) with Flags: 42\"}
- Match m = Regex.Match(message, "\\{(.*)\\}");
+ Match m = ErrorRegex().Match(message);
if ((m != null) && m.Success)
{
try
@@ -1235,7 +1234,10 @@ namespace BreCalClient
e.Handled = true;
}
+ [GeneratedRegex("\\{(.*)\\}")]
+ private static partial Regex ErrorRegex();
+
#endregion
-
+
}
}
diff --git a/src/BreCalClient/Resources/Resources.de.resx b/src/BreCalClient/Resources/Resources.de.resx
index 834875f..5c2755a 100644
--- a/src/BreCalClient/Resources/Resources.de.resx
+++ b/src/BreCalClient/Resources/Resources.de.resx
@@ -595,4 +595,7 @@
Der Anlauf wurde storniert
+
+ Benachrichtigung bei
+
\ No newline at end of file
diff --git a/src/server/BreCal/api/user.py b/src/server/BreCal/api/user.py
index b77c12c..27d7436 100644
--- a/src/server/BreCal/api/user.py
+++ b/src/server/BreCal/api/user.py
@@ -2,8 +2,7 @@ from flask import Blueprint, request
from ..schemas import model
from .. import impl
from ..services.auth_guard import auth_guard
-import json
-import logging
+
from marshmallow import ValidationError
from . import verify_if_request_is_json
from BreCal.validators.validation_error import create_dynamic_exception_response, create_validation_error_response
@@ -16,11 +15,11 @@ def PutUser():
try:
verify_if_request_is_json(request)
-
+
content = request.get_json(force=True)
loadedModel = model.UserSchema().load(data=content, many=False, partial=True)
return impl.user.PutUser(loadedModel)
-
+
except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400)
diff --git a/src/server/BreCal/database/sql_queries.py b/src/server/BreCal/database/sql_queries.py
index ade5b26..47e4bfa 100644
--- a/src/server/BreCal/database/sql_queries.py
+++ b/src/server/BreCal/database/sql_queries.py
@@ -237,7 +237,7 @@ class SQLQuery():
@staticmethod
def get_user()->str:
query = "SELECT id, participant_id, first_name, last_name, user_name, user_email, user_phone, password_hash, " +\
- "api_key, notify_email, notify_whatsapp, notify_signal, notify_popup, created, modified FROM user " +\
+ "api_key, notify_email, notify_whatsapp, notify_signal, notify_popup, notify_event, created, modified FROM user " +\
"WHERE user_name = ?username? OR user_email = ?username?"
return query
diff --git a/src/server/BreCal/impl/login.py b/src/server/BreCal/impl/login.py
index c65d630..2810b6a 100644
--- a/src/server/BreCal/impl/login.py
+++ b/src/server/BreCal/impl/login.py
@@ -8,6 +8,7 @@ from .. import local_db
from ..services import jwt_handler
from BreCal.database.sql_queries import SQLQuery
+
def GetUser(options):
try:
@@ -18,7 +19,7 @@ def GetUser(options):
# query = SQLQuery.get_user()
# data = commands.query(query, model=model.User, param={"username" : options["username"]})
data = commands.query("SELECT id, participant_id, first_name, last_name, user_name, user_email, user_phone, password_hash, " +
- "api_key, notify_email, notify_whatsapp, notify_signal, notify_popup, created, modified FROM user " +
+ "api_key, notify_email, notify_whatsapp, notify_signal, notify_popup, notify_event, created, modified FROM user " +
"WHERE user_name = ?username? OR user_email = ?username?",
model=model.User, param={"username" : options["username"]})
@@ -35,7 +36,8 @@ def GetUser(options):
"notify_email": data[0].notify_email,
"notify_whatsapp": data[0].notify_whatsapp,
"notify_signal": data[0].notify_signal,
- "notify_popup": data[0].notify_popup
+ "notify_popup": data[0].notify_popup,
+ "notify_on": model.bitflag_to_list(data[0].notify_event)
}
token = jwt_handler.generate_jwt(payload=result, lifetime=120) # generate token valid 60 mins
result["token"] = token # add token to user data
diff --git a/src/server/BreCal/impl/user.py b/src/server/BreCal/impl/user.py
index 6229bc8..6d7fd45 100644
--- a/src/server/BreCal/impl/user.py
+++ b/src/server/BreCal/impl/user.py
@@ -35,7 +35,7 @@ def PutUser(schemaModel):
# should this be refactored?
# Also, what about the 'user_name'?
# 'participant_id' would also not trigger an update in isolation
- if "first_name" in schemaModel or "last_name" in schemaModel or "user_phone" in schemaModel or "user_email" in schemaModel or "notify_email" in schemaModel or "notify_whatsapp" in schemaModel or "notify_signal" in schemaModel or "notify_popup" in schemaModel:
+ if "first_name" in schemaModel or "last_name" in schemaModel or "user_phone" in schemaModel or "user_email" in schemaModel or "notify_email" in schemaModel or "notify_whatsapp" in schemaModel or "notify_signal" in schemaModel or "notify_popup" in schemaModel or "notify_on" in schemaModel:
# query = SQLQuery.get_user_put(schemaModel)
query = "UPDATE user SET "
isNotFirst = False
@@ -49,7 +49,14 @@ def PutUser(schemaModel):
if isNotFirst:
query += ", "
isNotFirst = True
- query += key + " = ?" + key + "? "
+
+ if key != "notify_on":
+ query += key + " = ?" + key + "? "
+ else:
+ flag_value = model.list_to_bitflag(schemaModel["notify_on"])
+ query += "notify_event = " + str(flag_value) + " "
+
+
query += "WHERE id = ?id?"
affected_rows = commands.execute(query, param=schemaModel)
diff --git a/src/server/BreCal/schemas/model.py b/src/server/BreCal/schemas/model.py
index 4a75daa..5dd51ad 100644
--- a/src/server/BreCal/schemas/model.py
+++ b/src/server/BreCal/schemas/model.py
@@ -10,11 +10,12 @@ from typing import List
import json
import re
import datetime
+
from BreCal.validators.time_logic import validate_time_is_in_not_too_distant_future
from BreCal.validators.validation_base_utils import check_if_string_has_special_characters
from BreCal.database.enums import ParticipantType, ParticipantFlag
-# from BreCal. ... import check_if_user_is_bsmd_type
+
def obj_dict(obj):
if isinstance(obj, datetime.datetime):
@@ -84,6 +85,21 @@ class NotificationType(IntEnum):
def _missing_(cls, value):
return cls.undefined
+def bitflag_to_list(bitflag: int) -> list[NotificationType]:
+ if bitflag is None:
+ return []
+ """Converts an integer bitflag to a list of NotificationType enums."""
+ return [nt for nt in NotificationType if bitflag & (1 << (nt.value - 1))]
+
+def list_to_bitflag(notifications: fields.List) -> int:
+ """Converts a list of NotificationType enums to an integer bitflag."""
+ try:
+ iter(notifications)
+ return sum(1 << (nt.value - 1) for nt in notifications)
+ except TypeError as te:
+ return 0
+
+
class ShipcallType(IntEnum):
undefined = 0
arrival = 1
@@ -497,6 +513,7 @@ class UserSchema(Schema):
notify_whatsapp = fields.Bool(allow_none=True, required=False)
notify_signal = fields.Bool(allow_none=True, required=False)
notify_popup = fields.Bool(allow_none=True, required=False)
+ notify_on = fields.List(fields.Enum(NotificationType), required=False, allow_none=True)
@validates("user_phone")
def validate_user_phone(self, value):
@@ -507,7 +524,7 @@ class UserSchema(Schema):
@validates("user_email")
def validate_user_email(self, value):
- if value and not re.match(r"[^@]+@[^@]+\.[^@]+", value) in value:
+ if value and not re.match(r"[^@]+@[^@]+\.[^@]+", value):
raise ValidationError({"user_email":f"invalid email address"})
@@ -556,10 +573,15 @@ class User:
notify_popup: bool
created: datetime
modified: datetime
+ ports: List[NotificationType] = field(default_factory=list)
+ notify_event: List[NotificationType] = field(default_factory=list)
def __hash__(self):
return hash(id)
+ def wants_notification(self, notification_type: NotificationType):
+ return notification_type in self.notify_event
+
@dataclass
class Ship:
id: int
diff --git a/src/server/BreCal/services/schedule_routines.py b/src/server/BreCal/services/schedule_routines.py
index 0ac3c8f..9971fe2 100644
--- a/src/server/BreCal/services/schedule_routines.py
+++ b/src/server/BreCal/services/schedule_routines.py
@@ -229,14 +229,14 @@ def SendNotifications():
users = users_dict[notification.participant_id]
for user in users:
# send notification to user
- if user.notify_email:
+ if user.notify_email and user.wants_notifications(notification.type):
if user not in email_dict:
email_dict[user] = []
email_dict[user].append(notification)
- if user.notify_whatsapp:
+ if user.notify_whatsapp and user.wants_notifications(notification.type):
# TBD
pass
- if user.notify_signal:
+ if user.notify_signal and user.wants_notifications(notification.type):
# TBD
pass
diff --git a/src/server/BreCal/stubs/user.py b/src/server/BreCal/stubs/user.py
index 908f512..8e6c6e3 100644
--- a/src/server/BreCal/stubs/user.py
+++ b/src/server/BreCal/stubs/user.py
@@ -18,18 +18,19 @@ def get_user_simple():
created = datetime.datetime.now()
modified = created+datetime.timedelta(seconds=10)
-
+
notify_email = True
notify_whatsapp = True
notify_signal = True
notify_popup = True
+
user = User(
- user_id,
- participant_id,
- first_name,
- last_name,
- user_name,
+ user_id,
+ participant_id,
+ first_name,
+ last_name,
+ user_name,
user_email,
user_phone,
password_hash,