Compare commits

...

10 Commits

29 changed files with 334 additions and 114 deletions

View File

@ -1,8 +1,8 @@
//---------------------- //----------------------
// <auto-generated> // <auto-generated>
// Generated REST API Client Code Generator v1.18.0.0 on 19.01.2025 19:58:28 // Generated REST API Client Code Generator v1.19.0.0 on 05.02.2025 08:46:11
// Using the tool OpenAPI Generator v7.10.0 // Using the tool OpenAPI Generator v7.11.0
// </auto-generated> // </auto-generated>
//---------------------- //----------------------
@ -381,6 +381,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -505,6 +506,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -635,6 +637,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -757,6 +760,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -1148,6 +1152,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -1278,6 +1283,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -1402,6 +1408,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -1879,6 +1886,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -1991,6 +1999,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -2105,6 +2114,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -2223,6 +2233,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -2347,6 +2358,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -2794,6 +2806,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -2918,6 +2931,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -3036,6 +3050,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -3166,6 +3181,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -3521,6 +3537,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -3651,6 +3668,7 @@ namespace BreCalClient.misc.Api
"application/json" "application/json"
}; };
var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes); var localVarContentType = BreCalClient.misc.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
var localVarMultipartFormData = localVarContentType == "multipart/form-data";
if (localVarContentType != null) if (localVarContentType != null)
{ {
localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
@ -4014,7 +4032,7 @@ namespace BreCalClient.misc.Client
{ {
foreach (var value in headerParam.Value) foreach (var value in headerParam.Value)
{ {
request.AddHeader(headerParam.Key, value); request.AddOrUpdateHeader(headerParam.Key, value);
} }
} }
} }
@ -4075,6 +4093,13 @@ namespace BreCalClient.misc.Client
} }
} }
} }
if (options.HeaderParameters != null)
{
if (options.HeaderParameters.TryGetValue("Content-Type", out var contentTypes) && contentTypes.Any(header => header.Contains("multipart/form-data")))
{
request.AlwaysMultipartFormData = true;
}
}
return request; return request;
} }
/// <summary> /// <summary>
@ -6915,7 +6940,8 @@ namespace BreCalClient.misc.Model
/// <param name="notifyPopup">notifyPopup.</param> /// <param name="notifyPopup">notifyPopup.</param>
/// <param name="exp">exp.</param> /// <param name="exp">exp.</param>
/// <param name="token">token.</param> /// <param name="token">token.</param>
public LoginResult(int id = default(int), int participantId = default(int), string firstName = default(string), string lastName = default(string), string userName = default(string), string userPhone = default(string), string userEmail = default(string), bool? notifyEmail = default(bool?), bool? notifyWhatsapp = default(bool?), bool? notifySignal = default(bool?), bool? notifyPopup = default(bool?), float exp = default(float), string token = default(string)) /// <param name="notifyOn">notifyOn.</param>
public LoginResult(int id = default(int), int participantId = default(int), string firstName = default(string), string lastName = default(string), string userName = default(string), string userPhone = default(string), string userEmail = default(string), bool? notifyEmail = default(bool?), bool? notifyWhatsapp = default(bool?), bool? notifySignal = default(bool?), bool? notifyPopup = default(bool?), float exp = default(float), string token = default(string), List<NotificationType> notifyOn = default(List<NotificationType>))
{ {
this.Id = id; this.Id = id;
this.ParticipantId = participantId; this.ParticipantId = participantId;
@ -6930,6 +6956,7 @@ namespace BreCalClient.misc.Model
this.NotifyPopup = notifyPopup; this.NotifyPopup = notifyPopup;
this.Exp = exp; this.Exp = exp;
this.Token = token; this.Token = token;
this.NotifyOn = notifyOn;
} }
/// <summary> /// <summary>
/// Gets or Sets Id /// Gets or Sets Id
@ -7036,6 +7063,14 @@ namespace BreCalClient.misc.Model
[DataMember(Name = "token", EmitDefaultValue = true)] [DataMember(Name = "token", EmitDefaultValue = true)]
public string Token { get; set; } public string Token { get; set; }
/// <summary> /// <summary>
/// Gets or Sets NotifyOn
/// </summary>
/*
<example>[&quot;assignment&quot;,&quot;next24h&quot;]</example>
*/
[DataMember(Name = "notify_on", EmitDefaultValue = true)]
public List<NotificationType> NotifyOn { get; set; }
/// <summary>
/// Returns the string presentation of the object /// Returns the string presentation of the object
/// </summary> /// </summary>
/// <returns>String presentation of the object</returns> /// <returns>String presentation of the object</returns>
@ -7056,6 +7091,7 @@ namespace BreCalClient.misc.Model
sb.Append(" NotifyPopup: ").Append(NotifyPopup).Append("\n"); sb.Append(" NotifyPopup: ").Append(NotifyPopup).Append("\n");
sb.Append(" Exp: ").Append(Exp).Append("\n"); sb.Append(" Exp: ").Append(Exp).Append("\n");
sb.Append(" Token: ").Append(Token).Append("\n"); sb.Append(" Token: ").Append(Token).Append("\n");
sb.Append(" NotifyOn: ").Append(NotifyOn).Append("\n");
sb.Append("}\n"); sb.Append("}\n");
return sb.ToString(); return sb.ToString();
} }
@ -8804,7 +8840,8 @@ namespace BreCalClient.misc.Model
/// <param name="notifyPopup">notifyPopup.</param> /// <param name="notifyPopup">notifyPopup.</param>
/// <param name="notifyWhatsapp">notifyWhatsapp.</param> /// <param name="notifyWhatsapp">notifyWhatsapp.</param>
/// <param name="notifySignal">notifySignal.</param> /// <param name="notifySignal">notifySignal.</param>
public UserDetails(int id = default(int), string oldPassword = default(string), string newPassword = default(string), string firstName = default(string), string lastName = default(string), string userPhone = default(string), string userEmail = default(string), bool? notifyEmail = default(bool?), bool? notifyPopup = default(bool?), bool? notifyWhatsapp = default(bool?), bool? notifySignal = default(bool?)) /// <param name="notifyOn">notifyOn.</param>
public UserDetails(int id = default(int), string oldPassword = default(string), string newPassword = default(string), string firstName = default(string), string lastName = default(string), string userPhone = default(string), string userEmail = default(string), bool? notifyEmail = default(bool?), bool? notifyPopup = default(bool?), bool? notifyWhatsapp = default(bool?), bool? notifySignal = default(bool?), List<NotificationType> notifyOn = default(List<NotificationType>))
{ {
this.Id = id; this.Id = id;
this.OldPassword = oldPassword; this.OldPassword = oldPassword;
@ -8817,6 +8854,7 @@ namespace BreCalClient.misc.Model
this.NotifyPopup = notifyPopup; this.NotifyPopup = notifyPopup;
this.NotifyWhatsapp = notifyWhatsapp; this.NotifyWhatsapp = notifyWhatsapp;
this.NotifySignal = notifySignal; this.NotifySignal = notifySignal;
this.NotifyOn = notifyOn;
} }
/// <summary> /// <summary>
/// Gets or Sets Id /// Gets or Sets Id
@ -8907,6 +8945,14 @@ namespace BreCalClient.misc.Model
[DataMember(Name = "notify_signal", EmitDefaultValue = true)] [DataMember(Name = "notify_signal", EmitDefaultValue = true)]
public bool? NotifySignal { get; set; } public bool? NotifySignal { get; set; }
/// <summary> /// <summary>
/// Gets or Sets NotifyOn
/// </summary>
/*
<example>[&quot;assignment&quot;,&quot;next24h&quot;]</example>
*/
[DataMember(Name = "notify_on", EmitDefaultValue = true)]
public List<NotificationType> NotifyOn { get; set; }
/// <summary>
/// Returns the string presentation of the object /// Returns the string presentation of the object
/// </summary> /// </summary>
/// <returns>String presentation of the object</returns> /// <returns>String presentation of the object</returns>
@ -8925,6 +8971,7 @@ namespace BreCalClient.misc.Model
sb.Append(" NotifyPopup: ").Append(NotifyPopup).Append("\n"); sb.Append(" NotifyPopup: ").Append(NotifyPopup).Append("\n");
sb.Append(" NotifyWhatsapp: ").Append(NotifyWhatsapp).Append("\n"); sb.Append(" NotifyWhatsapp: ").Append(NotifyWhatsapp).Append("\n");
sb.Append(" NotifySignal: ").Append(NotifySignal).Append("\n"); sb.Append(" NotifySignal: ").Append(NotifySignal).Append("\n");
sb.Append(" NotifyOn: ").Append(NotifyOn).Append("\n");
sb.Append("}\n"); sb.Append("}\n");
return sb.ToString(); return sb.ToString();
} }

View File

@ -1538,6 +1538,7 @@ components:
eta: '2023-08-21T08:23:35Z' eta: '2023-08-21T08:23:35Z'
operation: update operation: update
type: shipcall type: shipcall
notification: notification:
type: object type: object
description: a notification created by the engine if a times entry violates a rule description: a notification created by the engine if a times entry violates a rule
@ -1735,6 +1736,14 @@ components:
token: token:
type: string type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
notify_on:
type: array
nullable: true
items:
$ref: '#/components/schemas/NotificationType'
example:
- assignment
- next24h
example: example:
id: 42 id: 42
participant_id: 5 participant_id: 5
@ -1798,6 +1807,14 @@ components:
type: boolean type: boolean
nullable: true nullable: true
example: false example: false
notify_on:
type: array
nullable: true
items:
$ref: '#/components/schemas/NotificationType'
example:
- assignment
- next24h
example: example:
id: 42 id: 42
old_password: oldpassword old_password: oldpassword

View File

@ -0,0 +1,16 @@
# Versionshistorie
## 1.7
### YAML / API
1. Notifications GET: Der Parameter "shipcall_id" ist jetzt optional für den Abruf von Benachrichtigungen.
2. Notification: Enthält jetzt ein neues Feld "participant_id". Ist dieses gesetzt, richtet sich die Benachrichtigung an diesen Teilnehmer. Ist das Feld nicht vorhanden, richtet sich die Benachrichtigung an alle Beteiligten des shipcall
3. Die Benutzerdaten (login_result) enthalten jetzt die Felder (Flags) der Zuordnung für die verschiedenen Benachrichtigungs-Wege, aktuell implementiert ist notify_email und notify_popup. Diese können auch über user_details analog zu Telefonnummer, Name etc. gesetzt werden.
4. Die Enumeration NotificationType enthält jetzt nicht mehr den Benachrichtigungsweg, sondern den Typ des Ereignisses, das die Benachrichtigung ausgelöst hat. Aktuell werden 7 Ereignisse unterschieden.
5. Die Benutzerdaten enthalten eine Liste NotifyOn vom Typ NotificationType. In dieser Aufzählung sind die Ereignisse enthalten, über die der Benutzer benachrichtigt werden will. Wenn diese Liste leer oder nicht vorhanden ist erhält der Benutzer keine Nachrichten, auch wenn er einen Benachrichtigungsweg ausgewählt hat.

View File

@ -8,3 +8,6 @@ ADD CONSTRAINT `FK_NOTIFICATION_PARTICIPANT`
REFERENCES `participant` (`id`) REFERENCES `participant` (`id`)
ON DELETE RESTRICT ON DELETE RESTRICT
ON UPDATE RESTRICT; ON UPDATE RESTRICT;
ALTER TABLE `user`
ADD COLUMN `notify_event` INT NULL COMMENT 'Bitflag of selected notification event types that the user wants to be notified of' AFTER `notify_popup`;

View File

@ -1 +1 @@
1.7.0.5 1.8.0.0

View File

@ -7,11 +7,12 @@
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:p = "clr-namespace:BreCalClient.Resources" xmlns:p = "clr-namespace:BreCalClient.Resources"
mc:Ignorable="d" Left="{local:SettingBinding W1Left}" Top="{local:SettingBinding W1Top}" mc:Ignorable="d" Left="{local:SettingBinding W1Left}" Top="{local:SettingBinding W1Top}"
Title="Help" Height="496" Width="500" Loaded="Window_Loaded"> Title="Help" Height="512" Width="800" Loaded="Window_Loaded">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /> <ColumnDefinition Width="180" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="28" /> <RowDefinition Height="28" />
@ -25,6 +26,7 @@
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="10" /> <RowDefinition Height="10" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
@ -48,7 +50,7 @@
Informatikbüro Daniel Schick Informatikbüro Daniel Schick
</Hyperlink> </Hyperlink>
</TextBlock> </TextBlock>
<Border BorderThickness="0 0 0 2" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Gray" /> <Border BorderThickness="0 0 0 2" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" BorderBrush="Gray" />
<Label FontWeight="DemiBold" Grid.Row="3" Grid.Column="0" Content="{x:Static p:Resources.textChangeContactInfo}" HorizontalContentAlignment="Right"/> <Label FontWeight="DemiBold" Grid.Row="3" Grid.Column="0" Content="{x:Static p:Resources.textChangeContactInfo}" HorizontalContentAlignment="Right"/>
<Label Grid.Row="4" Grid.Column="0" Content="{x:Static p:Resources.textEmail}" HorizontalContentAlignment="Right" /> <Label Grid.Row="4" Grid.Column="0" Content="{x:Static p:Resources.textEmail}" HorizontalContentAlignment="Right" />
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textPhone}" HorizontalContentAlignment="Right" /> <Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textPhone}" HorizontalContentAlignment="Right" />
@ -60,19 +62,21 @@
<Label Grid.Row="8" Grid.Column="1" Content="{x:Static p:Resources.textNotifyEmail}" /> <Label Grid.Row="8" Grid.Column="1" Content="{x:Static p:Resources.textNotifyEmail}" />
<CheckBox HorizontalAlignment="Right" Margin="2" Grid.Column="0" Grid.Row="9" VerticalAlignment="Center" x:Name="checkboxPushNotify" /> <CheckBox HorizontalAlignment="Right" Margin="2" Grid.Column="0" Grid.Row="9" VerticalAlignment="Center" x:Name="checkboxPushNotify" />
<Label Grid.Row="9" Grid.Column="1" Content="{x:Static p:Resources.textNotifyPush}" /> <Label Grid.Row="9" Grid.Column="1" Content="{x:Static p:Resources.textNotifyPush}" />
<Button x:Name="buttonChangeUserFields" Click="buttonChangeUserFields_Click" Grid.Column="1" Grid.Row="10" Margin="2" Content="{x:Static p:Resources.textChange}" Width="80" HorizontalAlignment="Left" IsEnabled="True" /> <Label Grid.Row="7" Grid.Column="2" Content="{x:Static p:Resources.textNotifyOn}" />
<xctk:CheckListBox Grid.Column="2" Grid.Row="8" Grid.RowSpan="3" x:Name="checkListBoxEventSelection" Margin="2" />
<Button x:Name="buttonChangeUserFields" Click="buttonChangeUserFields_Click" Grid.Column="2" Grid.Row="11" Margin="2" Content="{x:Static p:Resources.textChange}" Width="80" HorizontalAlignment="Right" IsEnabled="True" />
<Border BorderThickness="0 0 0 2" Grid.Row="11" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Gray" /> <Border BorderThickness="0 0 0 2" Grid.Row="12" Grid.Column="0" Grid.ColumnSpan="3" BorderBrush="Gray" />
<Label FontWeight="DemiBold" Grid.Row="12" Grid.Column="0" Content="{x:Static p:Resources.textChangePassword}" HorizontalContentAlignment="Right"/> <Label FontWeight="DemiBold" Grid.Row="13" Grid.Column="0" Content="{x:Static p:Resources.textChangePassword}" HorizontalContentAlignment="Right"/>
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textOldPassword}" Grid.Column="1" Grid.Row="12" Margin="2" x:Name="wpBoxOldPassword" TextChanged="wpBoxOldPassword_TextChanged"/> <xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textOldPassword}" Grid.Column="1" Grid.Row="13" Margin="2" x:Name="wpBoxOldPassword" TextChanged="wpBoxOldPassword_TextChanged"/>
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textNewPassword}" Grid.Column="1" Grid.Row="13" Margin="2" x:Name="wpBoxNewPassword" TextChanged="wpBoxOldPassword_TextChanged"/> <xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textNewPassword}" Grid.Column="1" Grid.Row="14" Margin="2" x:Name="wpBoxNewPassword" TextChanged="wpBoxOldPassword_TextChanged"/>
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textRepeatNewPassword}" Grid.Column="1" Grid.Row="14" Margin="2" x:Name="wpBoxNewPasswordRepeat" TextChanged="wpBoxOldPassword_TextChanged"/> <xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textRepeatNewPassword}" Grid.Column="1" Grid.Row="15" Margin="2" x:Name="wpBoxNewPasswordRepeat" TextChanged="wpBoxOldPassword_TextChanged"/>
<Button x:Name="buttonChangePassword" Click="buttonChangePassword_Click" Grid.Column="1" Grid.Row="15" Margin="2" Content="{x:Static p:Resources.textChangePassword}" Width="120" HorizontalAlignment="Left" IsEnabled="False" /> <Button x:Name="buttonChangePassword" Click="buttonChangePassword_Click" Grid.Column="1" Grid.Row="16" Margin="2" Content="{x:Static p:Resources.textChangePassword}" Width="120" HorizontalAlignment="Right" IsEnabled="False" />
<Border BorderThickness="0 0 0 2" Grid.Row="16" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Gray" /> <Border BorderThickness="0 0 0 2" Grid.Row="17" Grid.Column="0" Grid.ColumnSpan="3" BorderBrush="Gray" />
<Button x:Name="buttonClose" Click="buttonClose_Click" Content="{x:Static p:Resources.textClose}" Width="80" Margin="2" Grid.Column="1" Grid.Row="18" HorizontalAlignment="Right" /> <Button x:Name="buttonClose" Click="buttonClose_Click" Content="{x:Static p:Resources.textClose}" Width="80" Margin="2" Grid.Column="2" Grid.Row="19" HorizontalAlignment="Right" />
</Grid> </Grid>
</Window> </Window>

View File

@ -57,7 +57,12 @@ namespace BreCalClient
this.LoginResult.UserPhone = this.textBoxUserPhone.Text.Trim(); this.LoginResult.UserPhone = this.textBoxUserPhone.Text.Trim();
this.LoginResult.UserEmail = this.textBoxUserEmail.Text.Trim(); this.LoginResult.UserEmail = this.textBoxUserEmail.Text.Trim();
this.LoginResult.NotifyEmail = this.checkboxEMailNotify.IsChecked ?? false; this.LoginResult.NotifyEmail = this.checkboxEMailNotify.IsChecked ?? false;
this.LoginResult.NotifyPopup = this.checkboxPushNotify.IsChecked ?? false; this.LoginResult.NotifyPopup = this.checkboxPushNotify.IsChecked ?? false;
if ((this.checkListBoxEventSelection.SelectedItems.Count > 0) && (this.LoginResult.NotifyOn == null))
this.LoginResult.NotifyOn = new();
this.LoginResult.NotifyOn.Clear();
foreach (NotificationType nt in this.checkListBoxEventSelection.SelectedItems)
this.LoginResult.NotifyOn.Add(nt);
this.ChangeUserSettingsRequested?.Invoke(); this.ChangeUserSettingsRequested?.Invoke();
} }
} }
@ -80,12 +85,19 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.checkListBoxEventSelection.ItemsSource = Enum.GetValues(typeof(BreCalClient.misc.Model.NotificationType));
if(LoginResult != null) if(LoginResult != null)
{ {
this.textBoxUserEmail.Text = LoginResult.UserEmail; this.textBoxUserEmail.Text = LoginResult.UserEmail;
this.textBoxUserPhone.Text = LoginResult.UserPhone; this.textBoxUserPhone.Text = LoginResult.UserPhone;
this.checkboxEMailNotify.IsChecked = LoginResult.NotifyEmail; this.checkboxEMailNotify.IsChecked = LoginResult.NotifyEmail;
this.checkboxPushNotify.IsChecked = LoginResult.NotifyPopup; this.checkboxPushNotify.IsChecked = LoginResult.NotifyPopup;
if (LoginResult.NotifyOn != null)
{
foreach (NotificationType nt in LoginResult.NotifyOn)
this.checkListBoxEventSelection.SelectedItems.Add(nt);
}
} }
} }

View File

@ -32,7 +32,7 @@
<value>#1D751F</value> <value>#1D751F</value>
</setting> </setting>
<setting name="APP_TITLE" serializeAs="String"> <setting name="APP_TITLE" serializeAs="String">
<value>!!Bremen calling Testversion!!</value> <value>!!Bremen calling Entwicklungsversion!!</value>
</setting> </setting>
<setting name="LOGO_IMAGE_URL" serializeAs="String"> <setting name="LOGO_IMAGE_URL" serializeAs="String">
<value>https://www.textbausteine.net/</value> <value>https://www.textbausteine.net/</value>

View File

@ -8,24 +8,17 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using ToastNotifications.Core; using ToastNotifications.Core;
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using System.Runtime.CompilerServices;
namespace BreCalClient namespace BreCalClient
{ {
internal class AppNotification internal class AppNotification(int id)
{ {
private static readonly Dictionary<int, AppNotification> _notifications = new(); private static readonly Dictionary<int, AppNotification> _notifications = [];
private readonly int _id; private static readonly ObservableCollection<AppNotification> _notificationsCollection = [];
private static readonly ObservableCollection<AppNotification> _notificationsCollection = new();
public AppNotification(int id)
{
_id = id;
}
#region Properties #region Properties
public int Id { get { return _id; } } public int Id { get { return id; } }
public string? NotificationType public string? NotificationType
{ {
@ -95,7 +88,7 @@ namespace BreCalClient
SaveNotifications(); SaveNotifications();
} }
internal static bool UpdateNotifications(List<Notification> notifications, System.Collections.Concurrent.ConcurrentDictionary<int, ShipcallControlModel> currentShipcalls, ToastViewModel vm) internal static bool UpdateNotifications(List<Notification> notifications, System.Collections.Concurrent.ConcurrentDictionary<int, ShipcallControlModel> currentShipcalls, ToastViewModel vm, LoginResult loginResult)
{ {
bool result = false; bool result = false;
@ -121,6 +114,10 @@ namespace BreCalClient
if (!iAmAssigned) continue; 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()) if (!_notificationsCollection.Where(x => x.Id == notification.Id).Any())
{ {
List<AppNotification> newList = new(_notificationsCollection); List<AppNotification> newList = new(_notificationsCollection);
@ -176,9 +173,8 @@ namespace BreCalClient
if (!string.IsNullOrEmpty(ap.Message)) if (!string.IsNullOrEmpty(ap.Message))
toastText += $" \n{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(() => App.Current.Dispatcher.Invoke(() =>
{ {
vm.ShowAppNotification(toastText, options); vm.ShowAppNotification(toastText, options);
@ -197,7 +193,7 @@ namespace BreCalClient
internal static void SaveNotifications() internal static void SaveNotifications()
{ {
if (Properties.Settings.Default.Notifications == null) if (Properties.Settings.Default.Notifications == null)
Properties.Settings.Default.Notifications = new(); Properties.Settings.Default.Notifications = [];
else else
Properties.Settings.Default.Notifications.Clear(); Properties.Settings.Default.Notifications.Clear();
foreach (int notification_id in _notifications.Keys) foreach (int notification_id in _notifications.Keys)

View File

@ -2,14 +2,14 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework> <TargetFramework>net8.0-windows7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<SignAssembly>True</SignAssembly> <SignAssembly>True</SignAssembly>
<StartupObject>BreCalClient.App</StartupObject> <StartupObject>BreCalClient.App</StartupObject>
<AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile>
<AssemblyVersion>1.7.0.5</AssemblyVersion> <AssemblyVersion>1.8.0.0</AssemblyVersion>
<FileVersion>1.7.0.5</FileVersion> <FileVersion>1.8.0.0</FileVersion>
<Title>Bremen calling client</Title> <Title>Bremen calling client</Title>
<Description>A Windows WPF client for the Bremen calling API.</Description> <Description>A Windows WPF client for the Bremen calling API.</Description>
<ApplicationIcon>containership.ico</ApplicationIcon> <ApplicationIcon>containership.ico</ApplicationIcon>
@ -122,8 +122,8 @@
<PackageReference Include="JsonSubTypes" Version="2.0.1" /> <PackageReference Include="JsonSubTypes" Version="2.0.1" />
<PackageReference Include="log4net" Version="3.0.3" /> <PackageReference Include="log4net" Version="3.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Polly" Version="8.5.0" /> <PackageReference Include="Polly" Version="8.5.1" />
<PackageReference Include="RestSharp" Version="112.1.0" /> <PackageReference Include="RestSharp" Version="112.0.0" />
<PackageReference Include="ToastNotifications" Version="2.5.1" /> <PackageReference Include="ToastNotifications" Version="2.5.1" />
<PackageReference Include="ToastNotifications.Messages" Version="2.5.1" /> <PackageReference Include="ToastNotifications.Messages" Version="2.5.1" />
</ItemGroup> </ItemGroup>

View File

@ -7,7 +7,7 @@
xmlns:p = "clr-namespace:BreCalClient.Resources" xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d" Left="{local:SettingBinding W1Left}" Top="{local:SettingBinding W1Top}" mc:Ignorable="d" Left="{local:SettingBinding W1Left}" Top="{local:SettingBinding W1Top}"
Title="{x:Static p:Resources.textEditTimes}" Height="331" Width="500" Loaded="Window_Loaded" ResizeMode="CanResizeWithGrip" Icon="Resources/containership.ico"> Title="{x:Static p:Resources.textEditTimes}" Height="415" Width="500" Loaded="Window_Loaded" ResizeMode="CanResizeWithGrip" Icon="Resources/containership.ico">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width=".20*" /> <ColumnDefinition Width=".20*" />
@ -22,6 +22,9 @@
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" x:Name="rowt1" />
<RowDefinition Height="28" x:Name="rowt2" />
<RowDefinition Height="28" x:Name="rowt3" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
@ -35,8 +38,10 @@
<Label Grid.Row="4" Grid.Column="0" Content="ATD" HorizontalContentAlignment="Right" x:Name="labelATD" /> <Label Grid.Row="4" Grid.Column="0" Content="ATD" HorizontalContentAlignment="Right" x:Name="labelATD" />
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textLockTime}" HorizontalContentAlignment="Right" /> <Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textLockTime}" HorizontalContentAlignment="Right" />
<Label Grid.Row="6" Grid.Column="0" Content="{x:Static p:Resources.textZoneEntryTime}" HorizontalContentAlignment="Right" /> <Label Grid.Row="6" Grid.Column="0" Content="{x:Static p:Resources.textZoneEntryTime}" HorizontalContentAlignment="Right" />
<Label Grid.Row="7" Grid.Column="0" Content="{x:Static p:Resources.textTidalWindow}" HorizontalContentAlignment="Right" />
<Label Grid.Row="7" Grid.Column="0" Content="{x:Static p:Resources.textRemarks}" HorizontalContentAlignment="Right" /> <Label Grid.Row="8" Grid.Column="0" Content="{x:Static p:Resources.textFrom}" HorizontalContentAlignment="Right" />
<Label Grid.Row="9" Grid.Column="0" Content="{x:Static p:Resources.textTo}" HorizontalContentAlignment="Right" />
<Label Grid.Row="10" Grid.Column="0" Content="{x:Static p:Resources.textRemarks}" HorizontalContentAlignment="Right" />
<Grid Grid.Row="1" Grid.Column="1"> <Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@ -137,7 +142,7 @@
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</local:DateTimePickerExt> </local:DateTimePickerExt>
<!--CheckBox IsEnabled="False" Grid.Row="3" Grid.Column="2" Margin="4,0,0,0" Name="checkBoxLockTimeFixed" VerticalAlignment="Center" /-->
<local:DateTimePickerExt IsEnabled="False" Grid.Row="6" Grid.Column="1" Margin="2" x:Name="datePickerZoneEntry" Format="Custom" FormatString="dd.MM. yyyy HH:mm"> <local:DateTimePickerExt IsEnabled="False" Grid.Row="6" Grid.Column="1" Margin="2" x:Name="datePickerZoneEntry" Format="Custom" FormatString="dd.MM. yyyy HH:mm">
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
@ -149,10 +154,12 @@
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</local:DateTimePickerExt> </local:DateTimePickerExt>
<!--CheckBox IsEnabled="False" Grid.Row="4" Grid.Column="2" Margin="4,0,0,0" Name="checkBoxZoneEntryFixed" VerticalAlignment="Center" /-->
<xctk:DateTimePicker Name="datePickerTidalWindowFrom" Grid.Column="1" Grid.Row="8" Margin="2" IsEnabled="False" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<xctk:DateTimePicker Name="datePickerTidalWindowTo" Grid.Column="1" Grid.Row="9" Margin="2" IsEnabled="False" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<TextBox Grid.Row="7" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="False" IsReadOnly="True" MaxLength="512"/> <TextBox Grid.Row="10" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="False" IsReadOnly="True" MaxLength="512"/>
<StackPanel Grid.Row="8" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right"> <StackPanel Grid.Row="11" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" /> <Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" />
<Button Width="80" Margin="2" Content="{x:Static p:Resources.textCancel}" x:Name="buttonCancel" Click="buttonCancel_Click"/> <Button Width="80" Margin="2" Content="{x:Static p:Resources.textCancel}" x:Name="buttonCancel" Click="buttonCancel_Click"/>
<Button Width="28" x:Name="buttonClearAll" Click="buttonClearAll_Click" Margin="2" IsEnabled="False"> <Button Width="28" x:Name="buttonClearAll" Click="buttonClearAll_Click" Margin="2" IsEnabled="False">

View File

@ -86,6 +86,8 @@ namespace BreCalClient
this.datePickerLockTime.Value = null; this.datePickerLockTime.Value = null;
this.datePickerZoneEntry.Value = null; this.datePickerZoneEntry.Value = null;
this.textBoxRemarks.Text = null; this.textBoxRemarks.Text = null;
this.datePickerTidalWindowFrom.Value = null;
this.datePickerTidalWindowTo.Value = null;
} }
} }
@ -153,6 +155,30 @@ namespace BreCalClient
return false; return false;
} }
if ((Extensions.ParticipantType)this.Times.ParticipantType == Extensions.ParticipantType.PILOT)
{
if ((this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom) || (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) // something has changed
{
if (datePickerTidalWindowTo.Value.IsTooOld() || this.datePickerTidalWindowFrom.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textTideTimesInThePast;
return false;
}
if (this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowFrom.Value > this.datePickerTidalWindowTo.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if ((this.datePickerTidalWindowFrom.Value.HasValue && !this.datePickerTidalWindowTo.Value.HasValue) || (!this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textTidalBothValues;
return false;
}
}
}
return true; return true;
} }
@ -167,6 +193,13 @@ namespace BreCalClient
this.Times.ZoneEntry = this.datePickerZoneEntry.Value; this.Times.ZoneEntry = this.datePickerZoneEntry.Value;
this.Times.Ata = this.datePickerATA.Value; this.Times.Ata = this.datePickerATA.Value;
this.Times.Atd = this.datePickerATD.Value; this.Times.Atd = this.datePickerATD.Value;
Extensions.ParticipantType pType = (Extensions.ParticipantType)this.Times.ParticipantType;
if ((pType == Extensions.ParticipantType.PILOT) && this.ShipcallModel.Shipcall != null)
{
this.ShipcallModel.Shipcall.TidalWindowFrom = this.datePickerTidalWindowFrom.Value;
this.ShipcallModel.Shipcall.TidalWindowTo = this.datePickerTidalWindowTo.Value;
}
} }
private void CopyToControls() private void CopyToControls()
@ -218,6 +251,19 @@ namespace BreCalClient
} }
} }
Extensions.ParticipantType pType = (Extensions.ParticipantType)this.Times.ParticipantType;
if ((pType == Extensions.ParticipantType.PILOT) && this.ShipcallModel.Shipcall != null)
{
this.datePickerTidalWindowFrom.Value = this.ShipcallModel.Shipcall.TidalWindowFrom;
this.datePickerTidalWindowTo.Value = this.ShipcallModel.Shipcall.TidalWindowTo;
}
else
{
this.rowt1.Height = new(0);
this.rowt2.Height = new(0);
this.rowt3.Height = new(0);
}
this.SetLockButton(this.Times.EtaBerthFixed ?? false); this.SetLockButton(this.Times.EtaBerthFixed ?? false);
} }
@ -283,8 +329,12 @@ namespace BreCalClient
this.datePickerLockTime.IsEnabled = true; this.datePickerLockTime.IsEnabled = true;
break; break;
case Extensions.ParticipantType.TUG: case Extensions.ParticipantType.TUG:
this.datePickerZoneEntry.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival);
break;
case Extensions.ParticipantType.PILOT: case Extensions.ParticipantType.PILOT:
this.datePickerZoneEntry.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival); this.datePickerZoneEntry.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival);
this.datePickerTidalWindowFrom.IsEnabled = true;
this.datePickerTidalWindowTo.IsEnabled = true;
break; break;
} }
} }

View File

@ -52,7 +52,7 @@ namespace BreCalClient
private readonly ConcurrentDictionary<int, ShipcallControlModel> _allShipcallsDict = new(); private readonly ConcurrentDictionary<int, ShipcallControlModel> _allShipcallsDict = new();
private readonly ConcurrentDictionary<int, ShipcallControl> _allShipCallsControlDict = new(); private readonly ConcurrentDictionary<int, ShipcallControl> _allShipCallsControlDict = new();
private readonly List<ShipcallControlModel> _visibleControlModels = new(); private readonly List<ShipcallControlModel> _visibleControlModels = [];
private readonly ShipcallApi _shipcallApi; private readonly ShipcallApi _shipcallApi;
private readonly UserApi _userApi; private readonly UserApi _userApi;
@ -195,7 +195,7 @@ namespace BreCalClient
} }
catch (ApiException ex) catch (ApiException ex)
{ {
if ((ex.ErrorContent != null && ((string)ex.ErrorContent).StartsWith("{"))) { if ((ex.ErrorContent != null && ((string)ex.ErrorContent).StartsWith('{'))) {
Error? anError = JsonConvert.DeserializeObject<Error>((string)ex.ErrorContent); Error? anError = JsonConvert.DeserializeObject<Error>((string)ex.ErrorContent);
if ((anError != null) && anError.ErrorField.Equals("invalid credentials")) if ((anError != null) && anError.ErrorField.Equals("invalid credentials"))
this.labelLoginResult.Content = BreCalClient.Resources.Resources.textWrongCredentials; this.labelLoginResult.Content = BreCalClient.Resources.Resources.textWrongCredentials;
@ -309,7 +309,7 @@ namespace BreCalClient
scmOut.Shipcall.DepartureBerthId = esc.ShipcallModel.Shipcall?.ArrivalBerthId; scmOut.Shipcall.DepartureBerthId = esc.ShipcallModel.Shipcall?.ArrivalBerthId;
if (esc.ShipcallModel.Shipcall != null) if (esc.ShipcallModel.Shipcall != null)
{ {
scmOut.Shipcall.Participants = new(); scmOut.Shipcall.Participants = [];
scmOut.Shipcall.Participants.AddRange(esc.ShipcallModel.Shipcall.Participants); scmOut.Shipcall.Participants.AddRange(esc.ShipcallModel.Shipcall.Participants);
foreach(ParticipantType pType in esc.ShipcallModel.AssignedParticipants.Keys) foreach(ParticipantType pType in esc.ShipcallModel.AssignedParticipants.Keys)
scmOut.AssignedParticipants[pType] = esc.ShipcallModel.AssignedParticipants[pType]; scmOut.AssignedParticipants[pType] = esc.ShipcallModel.AssignedParticipants[pType];
@ -368,8 +368,13 @@ namespace BreCalClient
NotifyEmail = _loginResult.NotifyEmail, NotifyEmail = _loginResult.NotifyEmail,
NotifyPopup = _loginResult.NotifyPopup, NotifyPopup = _loginResult.NotifyPopup,
NotifySignal = _loginResult.NotifySignal, NotifySignal = _loginResult.NotifySignal,
NotifyWhatsapp = _loginResult.NotifyWhatsapp NotifyWhatsapp = _loginResult.NotifyWhatsapp,
}; };
if (_loginResult.NotifyOn != null)
{
ud.NotifyOn = new(_loginResult.NotifyOn);
}
try try
{ {
await _userApi.UserUpdateAsync(ud); await _userApi.UserUpdateAsync(ud);
@ -413,7 +418,7 @@ namespace BreCalClient
{ {
this.searchFilterControl.SearchFilter.Ports.Clear(); this.searchFilterControl.SearchFilter.Ports.Clear();
List<Berth> berths = new(); List<Berth> berths = [];
foreach (Port port in comboBoxPorts.SelectedItems) foreach (Port port in comboBoxPorts.SelectedItems)
{ {
this.searchFilterControl.SearchFilter.Ports.Add(port.Id); this.searchFilterControl.SearchFilter.Ports.Add(port.Id);
@ -444,8 +449,8 @@ namespace BreCalClient
_historyDialog.Closed += (sender, e) => { this._historyDialog = null; }; _historyDialog.Closed += (sender, e) => { this._historyDialog = null; };
_historyDialog.HistoryItemSelected += (x) => _historyDialog.HistoryItemSelected += (x) =>
{ {
if(_allShipCallsControlDict.ContainsKey(x)) if(_allShipCallsControlDict.TryGetValue(x, out ShipcallControl? value))
_allShipCallsControlDict[x].BringIntoView(); value.BringIntoView();
}; };
_historyDialog.Show(); _historyDialog.Show();
} }
@ -509,14 +514,14 @@ namespace BreCalClient
SearchFilterModel? currentFilter = null; SearchFilterModel? currentFilter = null;
if (SearchFilterModel.filterMap != 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 else
{ {
SearchFilterModel.filterMap = new(); SearchFilterModel.filterMap = [];
} }
if (currentFilter == null) if (currentFilter == null)
{ {
@ -602,7 +607,7 @@ namespace BreCalClient
// load times for each shipcall // load times for each shipcall
List<Times> currentTimes = await _timesApi.TimesGetAsync(shipcall.Id); List<Times> currentTimes = await _timesApi.TimesGetAsync(shipcall.Id);
if (!_allShipcallsDict.ContainsKey(shipcall.Id)) if (!_allShipcallsDict.TryGetValue(shipcall.Id, out ShipcallControlModel? value))
{ {
// add entry // add entry
ShipcallControlModel scm = new() ShipcallControlModel scm = new()
@ -614,10 +619,9 @@ namespace BreCalClient
} }
else else
{ {
// update entry value.Shipcall = shipcall;
_allShipcallsDict[shipcall.Id].Shipcall = shipcall; value.Times = currentTimes;
_allShipcallsDict[shipcall.Id].Times = currentTimes; UpdateShipcall(value);
UpdateShipcall(_allShipcallsDict[shipcall.Id]);
} }
} }
@ -669,7 +673,7 @@ namespace BreCalClient
if (_loginResult?.NotifyPopup ?? false) if (_loginResult?.NotifyPopup ?? false)
{ {
List<Notification> notifications = await _staticApi.NotificationsGetAsync(); List<Notification> notifications = await _staticApi.NotificationsGetAsync();
AppNotification.UpdateNotifications(notifications, _allShipcallsDict, _vm); AppNotification.UpdateNotifications(notifications, _allShipcallsDict, _vm, _loginResult);
} }
} }
} }
@ -684,8 +688,8 @@ namespace BreCalClient
_allShipcallsDict[scm.Shipcall.Id] = scm; _allShipcallsDict[scm.Shipcall.Id] = scm;
Shipcall shipcall = scm.Shipcall; Shipcall shipcall = scm.Shipcall;
if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId)) if (BreCalLists.ShipLookupDict.TryGetValue(shipcall.ShipId, out ShipModel? value))
scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId].Ship; scm.Ship = value.Ship;
if (shipcall.Type == ShipcallType.Arrival) if (shipcall.Type == ShipcallType.Arrival)
{ {
@ -718,8 +722,8 @@ namespace BreCalClient
{ {
if(scm.Shipcall == null) return; if(scm.Shipcall == null) return;
Shipcall shipcall = scm.Shipcall; Shipcall shipcall = scm.Shipcall;
if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId)) if (BreCalLists.ShipLookupDict.TryGetValue(shipcall.ShipId, out ShipModel? value))
scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId].Ship; scm.Ship = value.Ship;
if (shipcall.Type == ShipcallType.Arrival) if (shipcall.Type == ShipcallType.Arrival)
{ {
@ -964,10 +968,10 @@ namespace BreCalClient
foreach (ShipcallControlModel visibleModel in this._visibleControlModels) foreach (ShipcallControlModel visibleModel in this._visibleControlModels)
{ {
if (visibleModel.Shipcall == null) continue; // should not happen 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(); value.RefreshData();
this.stackPanel.Children.Add(this._allShipCallsControlDict[visibleModel.Shipcall.Id]); this.stackPanel.Children.Add(value);
} }
} }
} }
@ -1073,6 +1077,13 @@ namespace BreCalClient
etc.Times.Id = apiResultId.VarId; etc.Times.Id = apiResultId.VarId;
obj.ShipcallControlModel?.Times.Add(etc.Times); obj.ShipcallControlModel?.Times.Add(etc.Times);
} }
// a pilot may have changed the tidal window so we update the shipcall too in this case
if(((Extensions.ParticipantType)etc.Times.ParticipantType) == ParticipantType.PILOT)
{
await _shipcallApi.ShipcallUpdateAsync(obj.ShipcallControlModel?.Shipcall);
}
_refreshImmediately = true; _refreshImmediately = true;
_tokenSource.Cancel(); _tokenSource.Cancel();
} }
@ -1110,8 +1121,8 @@ namespace BreCalClient
} }
else else
{ {
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;
} }
editControl.Times.ParticipantType = (int)ParticipantType.AGENCY; editControl.Times.ParticipantType = (int)ParticipantType.AGENCY;
if(editControl.ShowDialog() ?? false) if(editControl.ShowDialog() ?? false)
@ -1127,9 +1138,9 @@ namespace BreCalClient
} }
// always try to be the agent, even if we are BSMD // 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 else
{ {
@ -1178,7 +1189,7 @@ namespace BreCalClient
// (if the special-flag is enabled). Assigned Agency: ShipcallParticipantMap(id=628, shipcall_id=115, participant_id=10, // (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\"} // 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) if ((m != null) && m.Success)
{ {
try try
@ -1223,7 +1234,10 @@ namespace BreCalClient
e.Handled = true; e.Handled = true;
} }
[GeneratedRegex("\\{(.*)\\}")]
private static partial Regex ErrorRegex();
#endregion #endregion
} }
} }

View File

@ -4,8 +4,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
--> -->
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<ApplicationRevision>1</ApplicationRevision> <ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.7.0.5</ApplicationVersion> <ApplicationVersion>1.8.0.0</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled> <BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Debug</Configuration> <Configuration>Debug</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut> <CreateDesktopShortcut>True</CreateDesktopShortcut>
@ -21,7 +21,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<OpenBrowserOnPublish>False</OpenBrowserOnPublish> <OpenBrowserOnPublish>False</OpenBrowserOnPublish>
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<ProductName>Bremen calling development client</ProductName> <ProductName>Bremen calling development client</ProductName>
<PublishDir>bin\Debug\net6.0-windows\win-x64\app.publish\</PublishDir> <PublishDir>bin\Debug\net8.0-windows7.0\win-x64\app.publish\</PublishDir>
<PublishUrl>bin\publish.devel\</PublishUrl> <PublishUrl>bin\publish.devel\</PublishUrl>
<PublisherName>Informatikbüro Daniel Schick</PublisherName> <PublisherName>Informatikbüro Daniel Schick</PublisherName>
<PublishProtocol>ClickOnce</PublishProtocol> <PublishProtocol>ClickOnce</PublishProtocol>
@ -33,12 +33,12 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<SignManifests>False</SignManifests> <SignManifests>False</SignManifests>
<SuiteName>Bremen calling</SuiteName> <SuiteName>Bremen calling</SuiteName>
<SupportUrl>https://www.textbausteine.net/</SupportUrl> <SupportUrl>https://www.textbausteine.net/</SupportUrl>
<TargetFramework>net6.0-windows</TargetFramework> <TargetFramework>net8.0-windows7.0</TargetFramework>
<UpdateEnabled>True</UpdateEnabled> <UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode> <UpdateMode>Foreground</UpdateMode>
<UpdateRequired>True</UpdateRequired> <UpdateRequired>True</UpdateRequired>
<WebPageFileName>Publish.html</WebPageFileName> <WebPageFileName>Publish.html</WebPageFileName>
<MinimumRequiredVersion>1.7.0.5</MinimumRequiredVersion> <MinimumRequiredVersion>1.8.0.0</MinimumRequiredVersion>
<SkipPublishVerification>false</SkipPublishVerification> <SkipPublishVerification>false</SkipPublishVerification>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -12,7 +12,7 @@ namespace BreCalClient.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.12.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@ -34,7 +34,7 @@ namespace BreCalClient.Properties {
[global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("!!Bremen calling Testversion!!")] [global::System.Configuration.DefaultSettingValueAttribute("!!Bremen calling Entwicklungsversion!!")]
public string APP_TITLE { public string APP_TITLE {
get { get {
return ((string)(this["APP_TITLE"])); return ((string)(this["APP_TITLE"]));

View File

@ -6,7 +6,7 @@
<Value Profile="(Default)">#1D751F</Value> <Value Profile="(Default)">#1D751F</Value>
</Setting> </Setting>
<Setting Name="APP_TITLE" Type="System.String" Scope="Application"> <Setting Name="APP_TITLE" Type="System.String" Scope="Application">
<Value Profile="(Default)">!!Bremen calling Testversion!!</Value> <Value Profile="(Default)">!!Bremen calling Entwicklungsversion!!</Value>
</Setting> </Setting>
<Setting Name="LOGO_IMAGE_URL" Type="System.String" Scope="Application"> <Setting Name="LOGO_IMAGE_URL" Type="System.String" Scope="Application">
<Value Profile="(Default)">https://www.textbausteine.net/</Value> <Value Profile="(Default)">https://www.textbausteine.net/</Value>

View File

@ -948,6 +948,15 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Notify on.
/// </summary>
public static string textNotifyOn {
get {
return ResourceManager.GetString("textNotifyOn", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Notify by push notification in app. /// Looks up a localized string similar to Notify by push notification in app.
/// </summary> /// </summary>

View File

@ -595,4 +595,7 @@
<data name="textShipcallCancelled" xml:space="preserve"> <data name="textShipcallCancelled" xml:space="preserve">
<value>Der Anlauf wurde storniert</value> <value>Der Anlauf wurde storniert</value>
</data> </data>
<data name="textNotifyOn" xml:space="preserve">
<value>Benachrichtigung bei</value>
</data>
</root> </root>

View File

@ -646,4 +646,7 @@
<data name="textShipcallCancelled" xml:space="preserve"> <data name="textShipcallCancelled" xml:space="preserve">
<value>The shipcall was cancelled</value> <value>The shipcall was cancelled</value>
</data> </data>
<data name="textNotifyOn" xml:space="preserve">
<value>Notify on</value>
</data>
</root> </root>

View File

@ -6,8 +6,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<ApplicationIcon>Resources\lock_preferences.ico</ApplicationIcon> <ApplicationIcon>Resources\lock_preferences.ico</ApplicationIcon>
<FileVersion>1.7.0.5</FileVersion> <FileVersion>1.8.0.0</FileVersion>
<AssemblyVersion>1.7.0.5</AssemblyVersion> <AssemblyVersion>1.8.0.0</AssemblyVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,8 +2,7 @@ from flask import Blueprint, request
from ..schemas import model from ..schemas import model
from .. import impl from .. import impl
from ..services.auth_guard import auth_guard from ..services.auth_guard import auth_guard
import json
import logging
from marshmallow import ValidationError from marshmallow import ValidationError
from . import verify_if_request_is_json from . import verify_if_request_is_json
from BreCal.validators.validation_error import create_dynamic_exception_response, create_validation_error_response from BreCal.validators.validation_error import create_dynamic_exception_response, create_validation_error_response
@ -16,11 +15,11 @@ def PutUser():
try: try:
verify_if_request_is_json(request) verify_if_request_is_json(request)
content = request.get_json(force=True) content = request.get_json(force=True)
loadedModel = model.UserSchema().load(data=content, many=False, partial=True) loadedModel = model.UserSchema().load(data=content, many=False, partial=True)
return impl.user.PutUser(loadedModel) return impl.user.PutUser(loadedModel)
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400) return create_validation_error_response(ex=ex, status_code=400)

View File

@ -237,7 +237,7 @@ class SQLQuery():
@staticmethod @staticmethod
def get_user()->str: def get_user()->str:
query = "SELECT id, participant_id, first_name, last_name, user_name, user_email, user_phone, password_hash, " +\ 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?" "WHERE user_name = ?username? OR user_email = ?username?"
return query return query

View File

@ -8,6 +8,7 @@ from .. import local_db
from ..services import jwt_handler from ..services import jwt_handler
from BreCal.database.sql_queries import SQLQuery from BreCal.database.sql_queries import SQLQuery
def GetUser(options): def GetUser(options):
try: try:
@ -18,7 +19,7 @@ def GetUser(options):
# query = SQLQuery.get_user() # query = SQLQuery.get_user()
# data = commands.query(query, model=model.User, param={"username" : options["username"]}) # 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, " + 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?", "WHERE user_name = ?username? OR user_email = ?username?",
model=model.User, param={"username" : options["username"]}) model=model.User, param={"username" : options["username"]})
@ -35,7 +36,8 @@ def GetUser(options):
"notify_email": data[0].notify_email, "notify_email": data[0].notify_email,
"notify_whatsapp": data[0].notify_whatsapp, "notify_whatsapp": data[0].notify_whatsapp,
"notify_signal": data[0].notify_signal, "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 token = jwt_handler.generate_jwt(payload=result, lifetime=120) # generate token valid 60 mins
result["token"] = token # add token to user data result["token"] = token # add token to user data

View File

@ -35,7 +35,7 @@ def PutUser(schemaModel):
# should this be refactored? # should this be refactored?
# Also, what about the 'user_name'? # Also, what about the 'user_name'?
# 'participant_id' would also not trigger an update in isolation # '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 = SQLQuery.get_user_put(schemaModel)
query = "UPDATE user SET " query = "UPDATE user SET "
isNotFirst = False isNotFirst = False
@ -49,7 +49,14 @@ def PutUser(schemaModel):
if isNotFirst: if isNotFirst:
query += ", " query += ", "
isNotFirst = True 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?" query += "WHERE id = ?id?"
affected_rows = commands.execute(query, param=schemaModel) affected_rows = commands.execute(query, param=schemaModel)

View File

@ -10,11 +10,12 @@ from typing import List
import json import json
import re import re
import datetime import datetime
from BreCal.validators.time_logic import validate_time_is_in_not_too_distant_future 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.validators.validation_base_utils import check_if_string_has_special_characters
from BreCal.database.enums import ParticipantType, ParticipantFlag from BreCal.database.enums import ParticipantType, ParticipantFlag
# from BreCal. ... import check_if_user_is_bsmd_type
def obj_dict(obj): def obj_dict(obj):
if isinstance(obj, datetime.datetime): if isinstance(obj, datetime.datetime):
@ -84,6 +85,21 @@ class NotificationType(IntEnum):
def _missing_(cls, value): def _missing_(cls, value):
return cls.undefined 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): class ShipcallType(IntEnum):
undefined = 0 undefined = 0
arrival = 1 arrival = 1
@ -497,6 +513,7 @@ class UserSchema(Schema):
notify_whatsapp = fields.Bool(allow_none=True, required=False) notify_whatsapp = fields.Bool(allow_none=True, required=False)
notify_signal = 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_popup = fields.Bool(allow_none=True, required=False)
notify_on = fields.List(fields.Enum(NotificationType), required=False, allow_none=True)
@validates("user_phone") @validates("user_phone")
def validate_user_phone(self, value): def validate_user_phone(self, value):
@ -507,7 +524,7 @@ class UserSchema(Schema):
@validates("user_email") @validates("user_email")
def validate_user_email(self, value): 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"}) raise ValidationError({"user_email":f"invalid email address"})
@ -556,10 +573,16 @@ class User:
notify_popup: bool notify_popup: bool
created: datetime created: datetime
modified: datetime modified: datetime
ports: List[NotificationType] = field(default_factory=list)
notify_event: List[NotificationType] = field(default_factory=list)
def __hash__(self): def __hash__(self):
return hash(id) return hash(id)
def wants_notifications(self, notification_type: NotificationType):
events = bitflag_to_list(self.notify_event)
return notification_type in events
@dataclass @dataclass
class Ship: class Ship:
id: int id: int

View File

@ -229,14 +229,14 @@ def SendNotifications():
users = users_dict[notification.participant_id] users = users_dict[notification.participant_id]
for user in users: for user in users:
# send notification to user # send notification to user
if user.notify_email: if user.notify_email and user.wants_notifications(notification.type):
if user not in email_dict: if user not in email_dict:
email_dict[user] = [] email_dict[user] = []
email_dict[user].append(notification) email_dict[user].append(notification)
if user.notify_whatsapp: if user.notify_whatsapp and user.wants_notifications(notification.type):
# TBD # TBD
pass pass
if user.notify_signal: if user.notify_signal and user.wants_notifications(notification.type):
# TBD # TBD
pass pass

View File

@ -18,18 +18,19 @@ def get_user_simple():
created = datetime.datetime.now() created = datetime.datetime.now()
modified = created+datetime.timedelta(seconds=10) modified = created+datetime.timedelta(seconds=10)
notify_email = True notify_email = True
notify_whatsapp = True notify_whatsapp = True
notify_signal = True notify_signal = True
notify_popup = True notify_popup = True
user = User( user = User(
user_id, user_id,
participant_id, participant_id,
first_name, first_name,
last_name, last_name,
user_name, user_name,
user_email, user_email,
user_phone, user_phone,
password_hash, password_hash,

View File

@ -546,12 +546,18 @@ class InputValidationShipcall():
# query = 'SELECT * FROM shipcall_participant_map where (shipcall_id = ?shipcall_id? AND type=?participant_type?)' # query = 'SELECT * FROM shipcall_participant_map where (shipcall_id = ?shipcall_id? AND type=?participant_type?)'
# assigned_agency = execute_sql_query_standalone(query=query, model=ShipcallParticipantMap, param={"shipcall_id" : shipcall_id, "participant_type":int(ParticipantType.AGENCY)}) # assigned_agency = execute_sql_query_standalone(query=query, model=ShipcallParticipantMap, param={"shipcall_id" : shipcall_id, "participant_type":int(ParticipantType.AGENCY)})
assigned_agency = get_assigned_participant_of_type(shipcall_id, participant_type=ParticipantType.AGENCY) assigned_agency = get_assigned_participant_of_type(shipcall_id, participant_type=ParticipantType.AGENCY)
assigned_pilot = get_assigned_participant_of_type(shipcall_id, participant_type=ParticipantType.PILOT)
an_agency_is_assigned = True if assigned_agency is not None else False an_agency_is_assigned = True if assigned_agency is not None else False
a_pilot_is_assigned = True if assigned_pilot is not None else False
else: else:
# Agency assigned? User must belong to the assigned agency or be a BSMD user, in case the flag is set # Agency assigned? User must belong to the assigned agency or be a BSMD user, in case the flag is set
assigned_agency = [spm for spm in shipcall_participant_map if int(spm.type) == int(ParticipantType.AGENCY)] assigned_agency = [spm for spm in shipcall_participant_map if int(spm.type) == int(ParticipantType.AGENCY)]
assigned_pilot = [spm for spm in shipcall_participant_map if int(spm.type) == int(ParticipantType.PILOT)]
an_agency_is_assigned = len(assigned_agency)==1 an_agency_is_assigned = len(assigned_agency)==1
a_pilot_is_assigned = len(assigned_pilot)==1
if a_pilot_is_assigned:
assigned_pilot = assigned_pilot[0]
if len(assigned_agency)>1: if len(assigned_agency)>1:
raise ValidationError({"internal_error":f"Internal error? Found more than one assigned agency for the shipcall with ID {shipcall_id}. Found: {assigned_agency}"}) raise ValidationError({"internal_error":f"Internal error? Found more than one assigned agency for the shipcall with ID {shipcall_id}. Found: {assigned_agency}"})
@ -567,18 +573,19 @@ class InputValidationShipcall():
### USER authority ### ### USER authority ###
# determine, whether the user is a) the assigned agency or b) a BSMD participant # determine, whether the user is a) the assigned agency or b) a BSMD participant
user_is_assigned_agency = (user_participant_id == assigned_agency.id) user_is_assigned_agency = (user_participant_id == assigned_agency.id)
user_is_assigned_pilot = a_pilot_is_assigned and (user_participant_id == assigned_pilot.id)
# when the BSMD flag is set: the user must be either BSMD or the assigned agency # when the BSMD flag is set: the user must be either BSMD or the assigned agency
# when the BSMD flag is not set: the user must be the assigned agency # when the BSMD flag is not set: the user must be the assigned agency
user_is_authorized = (user_is_bsmd or user_is_assigned_agency) #if agency_has_bsmd_flag else user_is_assigned_agency user_is_authorized = (user_is_bsmd or user_is_assigned_agency or user_is_assigned_pilot) #if agency_has_bsmd_flag else user_is_assigned_agency
if not user_is_authorized: if not user_is_authorized:
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). Assigned Agency: {assigned_agency} with Flags: {assigned_agency.flags}") # Forbidden: 403 raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY / BSMD / PILOT (if the special-flag is enabled). Assigned Agency: {assigned_agency} with Flags: {assigned_agency.flags}") # Forbidden: 403
else: else:
# when there is no assigned agency, only BSMD users can update the shipcall # when there is no assigned agency, only BSMD users can update the shipcall
if not user_is_bsmd: if not user_is_bsmd:
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.") # part of a pytest.raises. Forbidden: 403 raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY / BSMD / PILOT users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.") # part of a pytest.raises. Forbidden: 403
return return

View File

@ -637,7 +637,7 @@ def test_shipcall_put_request_fails_when_different_participant_id_is_assigned(ge
# agency with different participant id is assigned # agency with different participant id is assigned
ivs = InputValidationShipcall() ivs = InputValidationShipcall()
with pytest.raises(werkzeug.exceptions.Forbidden, match=f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users"): with pytest.raises(werkzeug.exceptions.Forbidden, match=f"PUT Requests for shipcalls can only be issued by an assigned AGENCY BSMD PILOT users"):
ivs.check_user_is_authorized_for_put_request(user_data, loadedModel, content, spm_shipcall_data) ivs.check_user_is_authorized_for_put_request(user_data, loadedModel, content, spm_shipcall_data)
return return
@ -706,7 +706,7 @@ def test_shipcall_put_request_fails_when_no_agency_is_assigned(get_shipcall_id_a
# no agency assigned # no agency assigned
ivs = InputValidationShipcall() ivs = InputValidationShipcall()
with pytest.raises(werkzeug.exceptions.Forbidden, match=re.escape(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.")): with pytest.raises(werkzeug.exceptions.Forbidden, match=re.escape(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY BSMD PILOT users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.")):
ivs.check_user_is_authorized_for_put_request(user_data, loadedModel, content, spm_shipcall_data) ivs.check_user_is_authorized_for_put_request(user_data, loadedModel, content, spm_shipcall_data)
return return
@ -738,7 +738,7 @@ def test_shipcall_put_request_fails_when_user_is_not_authorized(get_shipcall_id_
# current user is not authorized # current user is not authorized
ivs = InputValidationShipcall() ivs = InputValidationShipcall()
with pytest.raises(werkzeug.exceptions.Forbidden, match=f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users"): with pytest.raises(werkzeug.exceptions.Forbidden, match=f"PUT Requests for shipcalls can only be issued by an assigned AGENCY BSMD PILOT users"):
ivs.check_user_is_authorized_for_put_request(user_data, loadedModel, content, spm_shipcall_data) ivs.check_user_is_authorized_for_put_request(user_data, loadedModel, content, spm_shipcall_data)
return return
@ -769,7 +769,7 @@ def test_shipcall_put_request_fails_when_user_tries_self_assignment(get_shipcall
# self-assignment. User is participant 6, and wants to assign participant 6. # self-assignment. User is participant 6, and wants to assign participant 6.
ivs = InputValidationShipcall() ivs = InputValidationShipcall()
with pytest.raises(werkzeug.exceptions.Forbidden, match=re.escape("PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.")): with pytest.raises(werkzeug.exceptions.Forbidden, match=re.escape("PUT Requests for shipcalls can only be issued by an assigned AGENCY BSMD PILOT users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.")):
# previous error message: An agency cannot self-register for a shipcall. The request is issued by an agency-user and tries to assign an AGENCY as the participant of the shipcall."" # previous error message: An agency cannot self-register for a shipcall. The request is issued by an agency-user and tries to assign an AGENCY as the participant of the shipcall.""
# however, self-assignment is no longer possible, because the SPM is verified beforehand. # however, self-assignment is no longer possible, because the SPM is verified beforehand.
ivs.check_user_is_authorized_for_put_request(user_data, loadedModel, content, spm_shipcall_data) ivs.check_user_is_authorized_for_put_request(user_data, loadedModel, content, spm_shipcall_data)
@ -846,7 +846,7 @@ def test_shipcall_put_request_fails_input_validation_shipcall_when_shipcall_is_c
assert user.participant_id == 4 assert user.participant_id == 4
#### verification should fail, because participant_id 4 is ParticipantType.PILOT (neither an assigned agency, nor bsmd) #### verification should fail, because participant_id 4 is ParticipantType.PILOT (neither an assigned agency, nor bsmd)
with pytest.raises(werkzeug.exceptions.Forbidden, match="PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD user"): with pytest.raises(werkzeug.exceptions.Forbidden, match="PUT Requests for shipcalls can only be issued by an assigned AGENCY BSMD PILOT user"):
InputValidationShipcall.evaluate_put_data(user_data, loadedModel, content) InputValidationShipcall.evaluate_put_data(user_data, loadedModel, content)
### PASSES: ### PASSES: