feat(account): enable native account management and register API Identity services
Some checks failed
Dotnet build and test / build (pull_request) Has been cancelled
Some checks failed
Dotnet build and test / build (pull_request) Has been cancelled
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
5e22b9a232
commit
f050ee4456
9 changed files with 1402 additions and 102 deletions
|
|
@ -104,7 +104,7 @@ public class HomePageProviderFlowTests
|
|||
};
|
||||
|
||||
Assert.NotNull(vm.SaveProfileCommand);
|
||||
Assert.True(vm.SaveProfileCommand.CanExecute(null));
|
||||
Assert.False(vm.SaveProfileCommand.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -123,7 +123,7 @@ public class HomePageProviderFlowTests
|
|||
|
||||
Assert.Equal("alice.new", payload.UserName);
|
||||
Assert.Equal("calendar-123", payload.GoogleCalendarId);
|
||||
Assert.Equal("FR14 2004 1010 0505 0001 3M02 606", payload.BankInfoSummary);
|
||||
Assert.Null(payload.BankInfoSummary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -141,7 +141,7 @@ public class HomePageProviderFlowTests
|
|||
var payload = vm.BuildProfileUpdateRequest();
|
||||
|
||||
Assert.Equal(string.Empty, payload.GoogleCalendarId);
|
||||
Assert.Equal(string.Empty, payload.BankInfoSummary);
|
||||
Assert.Null(payload.BankInfoSummary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
|
@ -11,13 +13,45 @@ namespace PostIt.ViewModels;
|
|||
public partial class UserProfilePageViewModel : ViewModelBase
|
||||
{
|
||||
private readonly YavscApiClient? _api;
|
||||
private readonly SessionStatusViewModel? _sessionStatus;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(SaveProfileCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SetMonthlyEmailPreferenceCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SetTwoFactorCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ChangePasswordCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(AddBankAccountCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(DeleteBankAccountCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(RemoveExternalLoginCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SendEmailConfirmationCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(LoadCalendarsCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ChooseCalendarCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(DeleteAccountCommand))]
|
||||
public partial bool IsBusy { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(SaveProfileCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SetMonthlyEmailPreferenceCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SetTwoFactorCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ChangePasswordCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(AddBankAccountCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(DeleteBankAccountCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(RemoveExternalLoginCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SendEmailConfirmationCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(LoadCalendarsCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ChooseCalendarCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(DeleteAccountCommand))]
|
||||
public partial bool IsProfileLoaded { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string UserName { get; set; } = "—";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string CurrentUserName { get; set; } = "—";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string UserId { get; set; } = "—";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string FullName { get; set; } = "—";
|
||||
|
||||
|
|
@ -42,15 +76,77 @@ public partial class UserProfilePageViewModel : ViewModelBase
|
|||
[ObservableProperty]
|
||||
public partial string StatusMessage { get; set; } = "Prêt.";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool EmailConfirmed { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool AllowMonthlyEmail { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool HasPassword { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool TwoFactorEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int ExternalLoginCount { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial long PostsCounter { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial long DiskUsage { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial long DiskQuota { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial decimal Credits { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string CurrentPassword { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string NewPassword { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string ConfirmPassword { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string BankIban { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string BankBic { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string BankCode { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string BankWicketCode { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string BankAccountNumber { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string UsernameConfirmation { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int BankedKey { get; set; }
|
||||
|
||||
public ObservableCollection<BankAccountInfo> BankAccounts { get; } = [];
|
||||
public ObservableCollection<LinkedExternalLogin> ExternalLogins { get; } = [];
|
||||
public ObservableCollection<CalendarOption> Calendars { get; } = [];
|
||||
|
||||
public override bool CanNavigateNext { get => false; protected set { _ = value; } }
|
||||
public override bool CanNavigatePrevious { get => true; protected set { _ = value; } }
|
||||
|
||||
public UserProfilePageViewModel(YavscApiClient? api)
|
||||
public UserProfilePageViewModel(YavscApiClient? api, SessionStatusViewModel? sessionStatus = null)
|
||||
{
|
||||
_api = api;
|
||||
_sessionStatus = sessionStatus;
|
||||
}
|
||||
|
||||
public UserProfilePageViewModel() : this(null) { }
|
||||
public UserProfilePageViewModel() : this(null, null) { }
|
||||
|
||||
public Task InitializeAsync() => RefreshAsync();
|
||||
|
||||
|
|
@ -59,27 +155,59 @@ public partial class UserProfilePageViewModel : ViewModelBase
|
|||
{
|
||||
if (_api is null)
|
||||
{
|
||||
IsProfileLoaded = false;
|
||||
StatusMessage = "Client API de profil indisponible.";
|
||||
return;
|
||||
}
|
||||
|
||||
IsBusy = true;
|
||||
IsProfileLoaded = false;
|
||||
try
|
||||
{
|
||||
var me = await _api.CallAsync<Me>(HttpMethod.Get, new Uri(new Uri(_api.Settings.ApiUrl.TrimEnd('/') + "/", UriKind.Absolute), "account/me").ToString()).ConfigureAwait(true);
|
||||
UserName = me?.UserName ?? "—";
|
||||
FullName = me?.FullName ?? "—";
|
||||
Email = me?.Email ?? "—";
|
||||
Address = me?.Address ?? "—";
|
||||
Avatar = string.IsNullOrWhiteSpace(me?.Avatar) ? "Aucun avatar" : me.Avatar;
|
||||
Roles = me?.Roles is { Length: > 0 } ? string.Join(", ", me.Roles) : "Aucun rôle";
|
||||
DedicatedGoogleCalendar = string.IsNullOrWhiteSpace(me?.DedicatedGoogleCalendar) ? "Non configuré" : me.DedicatedGoogleCalendar;
|
||||
BankInfoSummary = string.IsNullOrWhiteSpace(me?.BankInfoSummary) ? "Aucune information bancaire" : me.BankInfoSummary;
|
||||
if (me is null)
|
||||
{
|
||||
throw new InvalidOperationException("Le serveur a renvoyé un profil vide.");
|
||||
}
|
||||
|
||||
UserName = me.UserName ?? "—";
|
||||
CurrentUserName = UserName;
|
||||
UserId = me.UserId;
|
||||
FullName = me.FullName ?? "—";
|
||||
Email = me.Email ?? "—";
|
||||
Address = me.Address ?? "—";
|
||||
Avatar = string.IsNullOrWhiteSpace(me.Avatar) ? "Aucun avatar" : me.Avatar;
|
||||
Roles = me.Roles is { Length: > 0 } ? string.Join(", ", me.Roles) : "Aucun rôle";
|
||||
DedicatedGoogleCalendar = string.IsNullOrWhiteSpace(me.DedicatedGoogleCalendar) ? "Non configuré" : me.DedicatedGoogleCalendar;
|
||||
BankInfoSummary = string.IsNullOrWhiteSpace(me.BankInfoSummary) ? "Aucune information bancaire" : me.BankInfoSummary;
|
||||
EmailConfirmed = me.EmailConfirmed;
|
||||
AllowMonthlyEmail = me.AllowMonthlyEmail;
|
||||
HasPassword = me.HasPassword;
|
||||
TwoFactorEnabled = me.TwoFactorEnabled;
|
||||
ExternalLoginCount = me.ExternalLoginCount;
|
||||
PostsCounter = me.PostsCounter;
|
||||
DiskUsage = me.DiskUsage;
|
||||
DiskQuota = me.DiskQuota;
|
||||
Credits = me.Credits;
|
||||
BankAccounts.Clear();
|
||||
foreach (var account in me.BankAccounts)
|
||||
{
|
||||
BankAccounts.Add(account);
|
||||
}
|
||||
ExternalLogins.Clear();
|
||||
foreach (var login in me.ExternalLogins)
|
||||
{
|
||||
ExternalLogins.Add(login);
|
||||
}
|
||||
IsProfileLoaded = true;
|
||||
StatusMessage = "Profil chargé.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
IsProfileLoaded = false;
|
||||
UserName = "—";
|
||||
CurrentUserName = "—";
|
||||
UserId = "—";
|
||||
FullName = "—";
|
||||
Email = "—";
|
||||
Address = "—";
|
||||
|
|
@ -87,6 +215,18 @@ public partial class UserProfilePageViewModel : ViewModelBase
|
|||
Roles = "—";
|
||||
DedicatedGoogleCalendar = "Non configuré";
|
||||
BankInfoSummary = "Aucune information bancaire";
|
||||
EmailConfirmed = false;
|
||||
AllowMonthlyEmail = false;
|
||||
HasPassword = false;
|
||||
TwoFactorEnabled = false;
|
||||
ExternalLoginCount = 0;
|
||||
PostsCounter = 0;
|
||||
DiskUsage = 0;
|
||||
DiskQuota = 0;
|
||||
Credits = 0;
|
||||
BankAccounts.Clear();
|
||||
ExternalLogins.Clear();
|
||||
Calendars.Clear();
|
||||
StatusMessage = $"Impossible de charger le profil : {ex.Message}";
|
||||
}
|
||||
finally
|
||||
|
|
@ -115,12 +255,13 @@ public partial class UserProfilePageViewModel : ViewModelBase
|
|||
UserName = NormalizeField(UserName, "—"),
|
||||
FullName = NormalizeField(FullName, "—"),
|
||||
Address = NormalizeField(Address, "—"),
|
||||
GoogleCalendarId = NormalizeField(DedicatedGoogleCalendar, "Non configuré"),
|
||||
BankInfoSummary = NormalizeField(BankInfoSummary, "Aucune information bancaire")
|
||||
GoogleCalendarId = NormalizeField(DedicatedGoogleCalendar, "Non configuré")
|
||||
};
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private bool CanSaveProfile() => _api is not null && IsProfileLoaded && !IsBusy;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanSaveProfile))]
|
||||
public async Task SaveProfileAsync()
|
||||
{
|
||||
if (_api is null)
|
||||
|
|
@ -148,4 +289,324 @@ public partial class UserProfilePageViewModel : ViewModelBase
|
|||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanModifyAccount))]
|
||||
public async Task SetMonthlyEmailPreferenceAsync()
|
||||
{
|
||||
if (_api is null)
|
||||
{
|
||||
StatusMessage = "Client API de profil indisponible.";
|
||||
return;
|
||||
}
|
||||
|
||||
await UpdateBooleanPreferenceAsync(
|
||||
"account/preferences/monthly-email",
|
||||
new MonthlyEmailPreferenceRequest { Enabled = AllowMonthlyEmail },
|
||||
enabled => AllowMonthlyEmail = enabled,
|
||||
"préférence de courriel");
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanModifyAccount))]
|
||||
public async Task SetTwoFactorAsync()
|
||||
{
|
||||
if (_api is null)
|
||||
{
|
||||
StatusMessage = "Client API de profil indisponible.";
|
||||
return;
|
||||
}
|
||||
|
||||
await UpdateBooleanPreferenceAsync(
|
||||
"account/security/two-factor",
|
||||
new TwoFactorRequest { Enabled = TwoFactorEnabled },
|
||||
enabled => TwoFactorEnabled = enabled,
|
||||
"authentification à deux facteurs");
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanModifyAccount))]
|
||||
public async Task ChangePasswordAsync()
|
||||
{
|
||||
if (_api is null)
|
||||
{
|
||||
StatusMessage = "Client API de profil indisponible.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(NewPassword)
|
||||
|| !string.Equals(NewPassword, ConfirmPassword, StringComparison.Ordinal))
|
||||
{
|
||||
StatusMessage = "Le nouveau mot de passe et sa confirmation doivent être identiques.";
|
||||
return;
|
||||
}
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
var endpoint = BuildEndpoint("account/security/password");
|
||||
await _api.CallAsync(HttpMethod.Put, endpoint, new ChangePasswordRequest
|
||||
{
|
||||
CurrentPassword = CurrentPassword,
|
||||
NewPassword = NewPassword,
|
||||
ConfirmPassword = ConfirmPassword
|
||||
}).ConfigureAwait(true);
|
||||
CurrentPassword = string.Empty;
|
||||
NewPassword = string.Empty;
|
||||
ConfirmPassword = string.Empty;
|
||||
HasPassword = true;
|
||||
StatusMessage = "Mot de passe mis à jour.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Impossible de modifier le mot de passe : {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanModifyAccount))]
|
||||
public async Task AddBankAccountAsync()
|
||||
{
|
||||
if (_api is null)
|
||||
{
|
||||
StatusMessage = "Client API de profil indisponible.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(BankIban) && string.IsNullOrWhiteSpace(BankBic))
|
||||
{
|
||||
StatusMessage = "Renseignez un IBAN ou un BIC.";
|
||||
return;
|
||||
}
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
var endpoint = BuildEndpoint("account/bank-info");
|
||||
await _api.CallAsync<BankAccountInfo>(HttpMethod.Post, endpoint, new BankAccountInfoRequest
|
||||
{
|
||||
IBAN = BankIban.Trim(),
|
||||
BIC = BankBic.Trim(),
|
||||
BankCode = BankCode.Trim(),
|
||||
WicketCode = BankWicketCode.Trim(),
|
||||
AccountNumber = BankAccountNumber.Trim(),
|
||||
BankedKey = BankedKey
|
||||
}).ConfigureAwait(true);
|
||||
BankIban = string.Empty;
|
||||
BankBic = string.Empty;
|
||||
BankCode = string.Empty;
|
||||
BankWicketCode = string.Empty;
|
||||
BankAccountNumber = string.Empty;
|
||||
BankedKey = 0;
|
||||
await RefreshAsync().ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Impossible d’ajouter le compte bancaire : {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanModifyAccount))]
|
||||
public async Task DeleteBankAccountAsync(BankAccountInfo? account)
|
||||
{
|
||||
if (_api is null || account is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
await _api.CallAsync(HttpMethod.Delete, BuildEndpoint($"account/bank-info/{account.Id}")).ConfigureAwait(true);
|
||||
await RefreshAsync().ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Impossible de supprimer le compte bancaire : {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanModifyAccount))]
|
||||
public async Task RemoveExternalLoginAsync(LinkedExternalLogin? login)
|
||||
{
|
||||
if (_api is null || login is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
var query = $"provider={Uri.EscapeDataString(login.Provider)}&providerKey={Uri.EscapeDataString(login.ProviderKey)}";
|
||||
await _api.CallAsync(HttpMethod.Delete, BuildEndpoint($"account/security/external-logins?{query}")).ConfigureAwait(true);
|
||||
await RefreshAsync().ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Impossible de supprimer cette connexion externe : {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanModifyAccount))]
|
||||
public async Task SendEmailConfirmationAsync()
|
||||
{
|
||||
if (_api is null)
|
||||
{
|
||||
StatusMessage = "Client API de profil indisponible.";
|
||||
return;
|
||||
}
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
await _api.CallAsync<JsonElement>(HttpMethod.Post, BuildEndpoint("account/email/confirmation")).ConfigureAwait(true);
|
||||
StatusMessage = "E-mail de confirmation envoyé.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Impossible d’envoyer l’e-mail de confirmation : {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanModifyAccount))]
|
||||
public async Task LoadCalendarsAsync()
|
||||
{
|
||||
if (_api is null)
|
||||
{
|
||||
StatusMessage = "Client API de profil indisponible.";
|
||||
return;
|
||||
}
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
var calendars = await _api.CallAsync<CalendarOption[]>(
|
||||
HttpMethod.Get,
|
||||
BuildEndpoint("account/calendars")).ConfigureAwait(true);
|
||||
Calendars.Clear();
|
||||
foreach (var calendar in calendars ?? [])
|
||||
{
|
||||
Calendars.Add(calendar);
|
||||
}
|
||||
|
||||
StatusMessage = Calendars.Count == 0
|
||||
? "Aucun agenda Google dont vous êtes propriétaire n’est disponible."
|
||||
: "Agendas Google chargés.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Impossible de charger les agendas Google : {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanModifyAccount))]
|
||||
public async Task ChooseCalendarAsync(CalendarOption? calendar)
|
||||
{
|
||||
if (calendar is null)
|
||||
{
|
||||
DedicatedGoogleCalendar = "Non configuré";
|
||||
}
|
||||
else
|
||||
{
|
||||
DedicatedGoogleCalendar = calendar.Id;
|
||||
}
|
||||
|
||||
await SaveProfileAsync().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanModifyAccount))]
|
||||
public async Task DeleteAccountAsync()
|
||||
{
|
||||
if (_api is null)
|
||||
{
|
||||
StatusMessage = "Client API de profil indisponible.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.Equals(UsernameConfirmation, CurrentUserName, StringComparison.Ordinal))
|
||||
{
|
||||
StatusMessage = "Saisissez exactement votre nom d’utilisateur pour confirmer la suppression.";
|
||||
return;
|
||||
}
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
await _api.CallAsync(HttpMethod.Delete, BuildEndpoint("account/me"), new DeleteAccountRequest
|
||||
{
|
||||
UsernameConfirmation = UsernameConfirmation
|
||||
}).ConfigureAwait(true);
|
||||
UsernameConfirmation = string.Empty;
|
||||
StatusMessage = "Compte supprimé.";
|
||||
|
||||
if (_sessionStatus is not null)
|
||||
{
|
||||
await _sessionStatus.LogoutAsync().ConfigureAwait(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _api.LogoutAsync().ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Impossible de supprimer le compte : {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateBooleanPreferenceAsync<TRequest>(
|
||||
string path,
|
||||
TRequest request,
|
||||
Action<bool> applyValue,
|
||||
string preferenceName)
|
||||
where TRequest : class
|
||||
{
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
var endpoint = BuildEndpoint(path);
|
||||
var response = await _api!.CallAsync<System.Text.Json.JsonElement>(HttpMethod.Put, endpoint, request).ConfigureAwait(true);
|
||||
var enabled = response.GetProperty("enabled").GetBoolean();
|
||||
applyValue(enabled);
|
||||
StatusMessage = $"Préférence mise à jour : {preferenceName}.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await RefreshAsync().ConfigureAwait(true);
|
||||
StatusMessage = $"Impossible de mettre à jour {preferenceName} : {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildEndpoint(string path) =>
|
||||
new Uri(new Uri(_api!.Settings.ApiUrl.TrimEnd('/') + "/", UriKind.Absolute), path).ToString();
|
||||
|
||||
private bool CanModifyAccount() => _api is not null && IsProfileLoaded && !IsBusy;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@
|
|||
|
||||
<TextBlock Text="{Binding UserName, StringFormat='Nom d’utilisateur : {0}'}"/>
|
||||
<TextBox Text="{Binding ExerciseCountryCode, Mode=TwoWay}"
|
||||
Watermark="Code pays (fr, en...)"/>
|
||||
PlaceholderText="Code pays (fr, en...)"/>
|
||||
<TextBox Text="{Binding SIREN, Mode=TwoWay}"
|
||||
Watermark="SIREN"/>
|
||||
PlaceholderText="SIREN"/>
|
||||
<TextBox Text="{Binding Website, Mode=TwoWay}"
|
||||
Watermark="Site web"/>
|
||||
PlaceholderText="Site web"/>
|
||||
|
||||
<CheckBox Content="Disponible / actif"
|
||||
IsChecked="{Binding Active, Mode=TwoWay}"/>
|
||||
|
|
|
|||
|
|
@ -1,38 +1,298 @@
|
|||
<ContentPage xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:PostIt.ViewModels"
|
||||
xmlns:identity="using:Yavsc.Abstract.Identity"
|
||||
x:Class="PostIt.Views.UserProfilePage"
|
||||
x:DataType="vm:UserProfilePageViewModel"
|
||||
Header="Mon profil">
|
||||
Header="Mon compte">
|
||||
|
||||
<StackPanel Margin="16" Spacing="12">
|
||||
<TextBlock Text="Mon profil"
|
||||
FontSize="22"
|
||||
FontWeight="SemiBold"/>
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="16" Spacing="16">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Gérer votre compte"
|
||||
FontSize="22"
|
||||
FontWeight="SemiBold"/>
|
||||
<TextBlock Text="Paramètres de votre compte"
|
||||
Foreground="#4B5563"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1"
|
||||
Content="Actualiser"
|
||||
Command="{Binding RefreshCommand}"
|
||||
IsEnabled="{Binding !IsBusy}"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
|
||||
<Button Content="Actualiser"
|
||||
Command="{Binding RefreshCommand}"
|
||||
IsEnabled="{Binding !IsBusy}"/>
|
||||
<ProgressBar IsVisible="{Binding IsBusy}"
|
||||
IsIndeterminate="True"
|
||||
Height="8"/>
|
||||
|
||||
<ProgressBar IsVisible="{Binding IsBusy}"
|
||||
IsIndeterminate="True"
|
||||
Height="8"/>
|
||||
<Border Padding="16"
|
||||
BorderBrush="#D0D7DE"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Paramètres du compte"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"/>
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="*" RowSpacing="8">
|
||||
<TextBox Watermark="Nom d’utilisateur" Text="{Binding UserName}"/>
|
||||
<TextBox Grid.Row="1" Watermark="Nom complet" Text="{Binding FullName}"/>
|
||||
<TextBox Grid.Row="2" Watermark="Adresse" Text="{Binding Address}" AcceptsReturn="True" TextWrapping="Wrap"/>
|
||||
<TextBox Grid.Row="3" Watermark="Calendrier Google" Text="{Binding DedicatedGoogleCalendar}"/>
|
||||
<TextBox Grid.Row="4" Watermark="Détails bancaires" Text="{Binding BankInfoSummary}" AcceptsReturn="True" TextWrapping="Wrap"/>
|
||||
<Button Grid.Row="5" Content="Enregistrer" Command="{Binding SaveProfileCommand}" IsEnabled="{Binding !IsBusy}"/>
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="150,*"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"
|
||||
RowSpacing="12">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="Nom d’utilisateur"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Text="{Binding UserName, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Text="{Binding UserName, StringFormat='Nom d’utilisateur : {0}'}"/>
|
||||
<TextBlock Text="{Binding Email, StringFormat='Email : {0}'}"/>
|
||||
<TextBlock Text="{Binding Roles, StringFormat='Rôles : {0}'}"/>
|
||||
<TextBlock Text="{Binding DedicatedGoogleCalendar, StringFormat='Calendrier dédié : {0}'}"/>
|
||||
<TextBlock Text="{Binding BankInfoSummary, StringFormat='Informations bancaires : {0}'}" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="{Binding Avatar, StringFormat='Avatar : {0}'}" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="{Binding StatusMessage}" Foreground="#4B5563" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="Adresse e-mail"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Text="{Binding Email}"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"/>
|
||||
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="Nom complet"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Text="{Binding FullName, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="3"
|
||||
Text="Rôles"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Text="{Binding Roles}"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"/>
|
||||
|
||||
<TextBlock Grid.Row="4"
|
||||
Text="Adresse"
|
||||
VerticalAlignment="Top"/>
|
||||
<TextBox Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Text="{Binding Address, Mode=TwoWay}"
|
||||
AcceptsReturn="True"
|
||||
TextWrapping="Wrap"
|
||||
MinHeight="64"/>
|
||||
|
||||
<TextBlock Grid.Row="5"
|
||||
Text="Avatar"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Text="{Binding Avatar}"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"/>
|
||||
|
||||
<TextBlock Grid.Row="6"
|
||||
Text="Calendrier Google"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="6"
|
||||
Grid.Column="1"
|
||||
Text="{Binding DedicatedGoogleCalendar, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="7"
|
||||
Text="Courriel mensuel"
|
||||
VerticalAlignment="Center"/>
|
||||
<StackPanel Grid.Row="7"
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<CheckBox Content="Recevoir les nouvelles mensuelles"
|
||||
IsChecked="{Binding AllowMonthlyEmail, Mode=TwoWay}"/>
|
||||
<Button Content="Enregistrer"
|
||||
Command="{Binding SetMonthlyEmailPreferenceCommand}"
|
||||
IsEnabled="{Binding !IsBusy}"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="8"
|
||||
Text="E-mail confirmé"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="8"
|
||||
Grid.Column="1"
|
||||
Text="{Binding EmailConfirmed}"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"/>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right">
|
||||
<Button x:Name="ChooseAvatarButton"
|
||||
Content="Choisir un avatar"
|
||||
Click="ChooseAvatar_Click"
|
||||
IsEnabled="{Binding !IsBusy}"/>
|
||||
<Button Content="Enregistrer le profil"
|
||||
Command="{Binding SaveProfileCommand}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Padding="16"
|
||||
BorderBrush="#D0D7DE"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="Sécurité et connexions"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding Email, StringFormat='E-mail : {0}'}"/>
|
||||
<TextBlock Text="{Binding EmailConfirmed, StringFormat='Adresse confirmée : {0}'}"/>
|
||||
<Button Content="Renvoyer le courriel de confirmation"
|
||||
Command="{Binding SendEmailConfirmationCommand}"
|
||||
IsVisible="{Binding !EmailConfirmed}"/>
|
||||
<TextBlock Text="{Binding ExternalLoginCount, StringFormat='Connexions externes : {0}'}"/>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding ExternalLogins}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="identity:LinkedExternalLogin">
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="0,4">
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button Grid.Column="1"
|
||||
Content="Dissocier"
|
||||
Command="{Binding $parent[ContentPage].((vm:UserProfilePageViewModel)DataContext).RemoveExternalLoginCommand}"
|
||||
CommandParameter="{Binding}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<CheckBox Content="Activer l’authentification à deux facteurs"
|
||||
IsChecked="{Binding TwoFactorEnabled, Mode=TwoWay}"/>
|
||||
<Button Content="Enregistrer le réglage de sécurité"
|
||||
Command="{Binding SetTwoFactorCommand}"
|
||||
IsEnabled="{Binding !IsBusy}"
|
||||
HorizontalAlignment="Right"/>
|
||||
<TextBlock Text="{Binding HasPassword, StringFormat='Mot de passe local configuré : {0}'}"/>
|
||||
<TextBox PlaceholderText="Mot de passe actuel"
|
||||
PasswordChar="●"
|
||||
Text="{Binding CurrentPassword, Mode=TwoWay}"
|
||||
IsVisible="{Binding HasPassword}"/>
|
||||
<TextBox PlaceholderText="Nouveau mot de passe"
|
||||
PasswordChar="●"
|
||||
Text="{Binding NewPassword, Mode=TwoWay}"/>
|
||||
<TextBox PlaceholderText="Confirmer le nouveau mot de passe"
|
||||
PasswordChar="●"
|
||||
Text="{Binding ConfirmPassword, Mode=TwoWay}"/>
|
||||
<Button Content="Modifier le mot de passe"
|
||||
Command="{Binding ChangePasswordCommand}"
|
||||
IsEnabled="{Binding !IsBusy}"
|
||||
HorizontalAlignment="Right"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Padding="16"
|
||||
BorderBrush="#D0D7DE"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="Informations bancaires"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"/>
|
||||
<ItemsControl ItemsSource="{Binding BankAccounts}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="identity:BankAccountInfo">
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="0,4">
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Text="{Binding IBAN, StringFormat='IBAN : {0}'}"/>
|
||||
<TextBlock Text="{Binding BIC, StringFormat='BIC : {0}'}"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1"
|
||||
Content="Supprimer"
|
||||
Command="{Binding $parent[ContentPage].((vm:UserProfilePageViewModel)DataContext).DeleteBankAccountCommand}"
|
||||
CommandParameter="{Binding}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock Text="Ajouter un compte bancaire"
|
||||
FontWeight="SemiBold"/>
|
||||
<TextBox PlaceholderText="IBAN" Text="{Binding BankIban, Mode=TwoWay}"/>
|
||||
<TextBox PlaceholderText="BIC" Text="{Binding BankBic, Mode=TwoWay}"/>
|
||||
<Grid ColumnDefinitions="*,*" ColumnSpacing="8">
|
||||
<TextBox PlaceholderText="Code banque" Text="{Binding BankCode, Mode=TwoWay}"/>
|
||||
<TextBox Grid.Column="1" PlaceholderText="Code guichet" Text="{Binding BankWicketCode, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="8">
|
||||
<TextBox PlaceholderText="Numéro de compte" Text="{Binding BankAccountNumber, Mode=TwoWay}"/>
|
||||
<NumericUpDown Grid.Column="1"
|
||||
Width="120"
|
||||
Minimum="0"
|
||||
Maximum="99"
|
||||
Value="{Binding BankedKey, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
<Button Content="Ajouter les informations bancaires"
|
||||
Command="{Binding AddBankAccountCommand}"
|
||||
IsEnabled="{Binding !IsBusy}"
|
||||
HorizontalAlignment="Right"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Padding="16"
|
||||
BorderBrush="#D0D7DE"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="Autres paramètres du compte"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding PostsCounter, StringFormat='Articles publiés : {0}'}"/>
|
||||
<TextBlock Text="{Binding DiskUsage, StringFormat='Espace utilisé (octets) : {0}'}"/>
|
||||
<TextBlock Text="{Binding DiskQuota, StringFormat='Quota disque (octets) : {0}'}"/>
|
||||
<TextBlock Text="{Binding Credits, StringFormat='Crédits : {0}'}"/>
|
||||
<TextBlock Text="{Binding UserId, StringFormat='Identifiant utilisateur : {0}'}"/>
|
||||
<TextBlock Text="Calendrier Google"/>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button Content="Charger les calendriers"
|
||||
Command="{Binding LoadCalendarsCommand}"/>
|
||||
<Button Content="Désactiver le calendrier"
|
||||
Command="{Binding ChooseCalendarCommand}"
|
||||
CommandParameter="{x:Null}"/>
|
||||
</StackPanel>
|
||||
<ItemsControl ItemsSource="{Binding Calendars}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="identity:CalendarOption">
|
||||
<Button Content="{Binding Name}"
|
||||
Command="{Binding $parent[ContentPage].((vm:UserProfilePageViewModel)DataContext).ChooseCalendarCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
HorizontalAlignment="Left"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button Content="Gérer mes cercles" Click="OpenCircles_Click"/>
|
||||
<Button Content="Gérer mon activité professionnelle" Click="OpenPerformerConfiguration_Click"/>
|
||||
<Button Content="Mes publications" Click="OpenBlogs_Click"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Padding="16"
|
||||
BorderBrush="#D0D7DE"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Supprimer le compte"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"/>
|
||||
<TextBlock Text="Cette action est définitive. Saisissez votre nom d’utilisateur actuel pour confirmer."
|
||||
TextWrapping="Wrap"/>
|
||||
<TextBox PlaceholderText="{Binding CurrentUserName, StringFormat='Saisir {0} pour confirmer'}"
|
||||
Text="{Binding UsernameConfirmation, Mode=TwoWay}"/>
|
||||
<Button Content="Supprimer définitivement mon compte"
|
||||
Command="{Binding DeleteAccountCommand}"
|
||||
HorizontalAlignment="Right"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="{Binding StatusMessage}"
|
||||
Foreground="#4B5563"
|
||||
TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentPage>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PostIt.Services;
|
||||
using PostIt.ViewModels;
|
||||
|
||||
namespace PostIt.Views;
|
||||
|
||||
|
|
@ -8,4 +16,124 @@ public partial class UserProfilePage : ContentPage
|
|||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void ChooseAvatar_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not UserProfilePageViewModel viewModel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
if (topLevel is null)
|
||||
{
|
||||
viewModel.StatusMessage = "Impossible d’accéder au sélecteur de fichiers.";
|
||||
return;
|
||||
}
|
||||
|
||||
viewModel.IsBusy = true;
|
||||
try
|
||||
{
|
||||
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Choisir un avatar",
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter =
|
||||
[
|
||||
new FilePickerFileType("Images")
|
||||
{
|
||||
Patterns = ["*.png", "*.jpg", "*.jpeg", "*.webp", "*.gif", "*.bmp", "*.tif", "*.tiff"]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
var file = files.FirstOrDefault();
|
||||
if (file is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var properties = await file.GetBasicPropertiesAsync();
|
||||
if (properties.Size is > 2 * 1024 * 1024)
|
||||
{
|
||||
viewModel.StatusMessage = "L’avatar ne doit pas dépasser 2 Mo.";
|
||||
return;
|
||||
}
|
||||
|
||||
var app = (App?)Application.Current;
|
||||
var api = app?.ServiceProvider?.GetRequiredService<YavscApiClient>();
|
||||
if (api is null)
|
||||
{
|
||||
viewModel.StatusMessage = "Client API indisponible.";
|
||||
return;
|
||||
}
|
||||
|
||||
await using var stream = await file.OpenReadAsync();
|
||||
var message = await api.SetAvatarAsync(stream, file.Name, GetMimeType(file.Name));
|
||||
await viewModel.RefreshAsync();
|
||||
if (viewModel.IsProfileLoaded)
|
||||
{
|
||||
viewModel.StatusMessage = message;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
viewModel.StatusMessage = $"Impossible d’envoyer l’avatar : {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
viewModel.IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async void OpenCircles_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
var app = (App?)Application.Current;
|
||||
var viewModel = app?.ServiceProvider?.GetRequiredService<CirclesPageViewModel>();
|
||||
if (app is null || viewModel is null)
|
||||
{
|
||||
if (DataContext is UserProfilePageViewModel profile)
|
||||
{
|
||||
profile.StatusMessage = "La page des cercles est indisponible.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await viewModel.RefreshAsync();
|
||||
await app.PushPageAsync(viewModel);
|
||||
}
|
||||
|
||||
private async void OpenPerformerConfiguration_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
var app = (App?)Application.Current;
|
||||
var viewModel = app?.ServiceProvider?.GetRequiredService<PerformerConfigurationPageViewModel>();
|
||||
if (app is null || viewModel is null)
|
||||
{
|
||||
if (DataContext is UserProfilePageViewModel profile)
|
||||
{
|
||||
profile.StatusMessage = "La configuration professionnelle est indisponible.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await viewModel.InitializeAsync();
|
||||
await app.PushPageAsync(viewModel);
|
||||
}
|
||||
|
||||
private async void OpenBlogs_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
await App.PushBlogsPageAsync();
|
||||
}
|
||||
|
||||
private static string GetMimeType(string fileName) =>
|
||||
Path.GetExtension(fileName).ToLowerInvariant() switch
|
||||
{
|
||||
".png" => "image/png",
|
||||
".jpg" or ".jpeg" => "image/jpeg",
|
||||
".webp" => "image/webp",
|
||||
".gif" => "image/gif",
|
||||
".bmp" => "image/bmp",
|
||||
".tif" or ".tiff" => "image/tiff",
|
||||
_ => "application/octet-stream"
|
||||
};
|
||||
}
|
||||
|
|
|
|||
59
src/Yavsc.Abstract/Identity/AccountSecurityRequests.cs
Normal file
59
src/Yavsc.Abstract/Identity/AccountSecurityRequests.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#nullable enable
|
||||
|
||||
namespace Yavsc.Abstract.Identity;
|
||||
|
||||
public sealed class ChangePasswordRequest
|
||||
{
|
||||
public string CurrentPassword { get; set; } = string.Empty;
|
||||
public string NewPassword { get; set; } = string.Empty;
|
||||
public string ConfirmPassword { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class TwoFactorRequest
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
public sealed class MonthlyEmailPreferenceRequest
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
public sealed class DeleteAccountRequest
|
||||
{
|
||||
public string UsernameConfirmation { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class LinkedExternalLogin
|
||||
{
|
||||
public string Provider { get; set; } = string.Empty;
|
||||
public string ProviderKey { get; set; } = string.Empty;
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class BankAccountInfo
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string IBAN { get; set; } = string.Empty;
|
||||
public string BIC { get; set; } = string.Empty;
|
||||
public string BankCode { get; set; } = string.Empty;
|
||||
public string WicketCode { get; set; } = string.Empty;
|
||||
public string AccountNumber { get; set; } = string.Empty;
|
||||
public int BankedKey { get; set; }
|
||||
}
|
||||
|
||||
public sealed class BankAccountInfoRequest
|
||||
{
|
||||
public string IBAN { get; set; } = string.Empty;
|
||||
public string BIC { get; set; } = string.Empty;
|
||||
public string BankCode { get; set; } = string.Empty;
|
||||
public string WicketCode { get; set; } = string.Empty;
|
||||
public string AccountNumber { get; set; } = string.Empty;
|
||||
public int BankedKey { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CalendarOption
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -3,6 +3,18 @@
|
|||
namespace Yavsc.Abstract.Identity {
|
||||
public class Me : UserInfo
|
||||
{
|
||||
public bool EmailConfirmed { get; set; }
|
||||
public bool AllowMonthlyEmail { get; set; }
|
||||
public bool HasPassword { get; set; }
|
||||
public bool TwoFactorEnabled { get; set; }
|
||||
public int ExternalLoginCount { get; set; }
|
||||
public long PostsCounter { get; set; }
|
||||
public long DiskUsage { get; set; }
|
||||
public long DiskQuota { get; set; }
|
||||
public decimal Credits { get; set; }
|
||||
public BankAccountInfo[] BankAccounts { get; set; } = [];
|
||||
public LinkedExternalLogin[] ExternalLogins { get; set; } = [];
|
||||
|
||||
public AuthToken? Token {
|
||||
get;
|
||||
set;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
#nullable enable
|
||||
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
|
@ -11,6 +13,8 @@ using System.Diagnostics;
|
|||
using Microsoft.Extensions.Options;
|
||||
using Yavsc.Abstract.Identity;
|
||||
using Yavsc.Abstract.Workflow;
|
||||
using Yavsc.Interface;
|
||||
using Yavsc.Services;
|
||||
|
||||
namespace Yavsc.WebApi.Controllers
|
||||
{
|
||||
|
|
@ -33,40 +37,99 @@ namespace Yavsc.WebApi.Controllers
|
|||
private readonly SiteSettings siteSettings;
|
||||
private readonly ILogger _logger;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly ITrueEmailSender _emailSender;
|
||||
private readonly ICalendarManager _calendarManager;
|
||||
|
||||
public ApiAccountController(
|
||||
ILoggerFactory loggerFactory, ApplicationDbContext dbContext,
|
||||
IOptions<SiteSettings> siteSettings, UserManager<ApplicationUser> userManager)
|
||||
IOptions<SiteSettings> siteSettings, UserManager<ApplicationUser> userManager,
|
||||
ITrueEmailSender emailSender, ICalendarManager calendarManager)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger(nameof(ApiAccountController));
|
||||
_dbContext = dbContext;
|
||||
this.siteSettings = siteSettings.Value;
|
||||
_userManager = userManager;
|
||||
_emailSender = emailSender;
|
||||
_calendarManager = calendarManager;
|
||||
}
|
||||
|
||||
[HttpGet("me")]
|
||||
public async Task<IActionResult> Me()
|
||||
{
|
||||
if (User == null)
|
||||
return new BadRequestObjectResult(
|
||||
new { error = "user not found" });
|
||||
if (User?.Identity?.IsAuthenticated != true)
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
var uid = User.GetUserId();
|
||||
Debug.Assert(uid != null, "uid is null");
|
||||
var userData = await GetUserData(uid);
|
||||
Debug.Assert(userData != null, "userData is null");
|
||||
var user = new Yavsc.Models.Auth.Me(userData.Id, userData.UserName, userData.Email,
|
||||
userData.Avatar,
|
||||
userData.PostalAddress, userData.DedicatedGoogleCalendar, userData.FullName);
|
||||
user.Address = userData.PostalAddress?.Address ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
var userData = await _dbContext.Users
|
||||
.Include(u => u.PostalAddress)
|
||||
.Include(u => u.AccountBalance)
|
||||
.Include(u => u.BankInfo)
|
||||
.FirstOrDefaultAsync(u => u.Id == uid);
|
||||
|
||||
if (userData is null)
|
||||
{
|
||||
return NotFound(new { error = "user not found" });
|
||||
}
|
||||
|
||||
var user = new Yavsc.Abstract.Identity.Me
|
||||
{
|
||||
UserId = userData.Id,
|
||||
UserName = userData.UserName,
|
||||
Email = userData.Email,
|
||||
Avatar = userData.Avatar,
|
||||
FullName = userData.FullName,
|
||||
Address = userData.PostalAddress?.Address ?? string.Empty,
|
||||
DedicatedGoogleCalendar = userData.DedicatedGoogleCalendar
|
||||
};
|
||||
user.BankInfoSummary = userData.BankInfo is { Count: > 0 }
|
||||
? string.Join(" | ", userData.BankInfo.Select(b => string.IsNullOrWhiteSpace(b.IBAN) ? b.BIC : $"{b.IBAN} / {b.BIC}"))
|
||||
: string.Empty;
|
||||
|
||||
var userRoles = _dbContext.UserRoles.Where(u => u.UserId == uid).Select(r => r.RoleId).ToArray();
|
||||
var userRoles = await _dbContext.UserRoles
|
||||
.Where(u => u.UserId == uid)
|
||||
.Select(r => r.RoleId)
|
||||
.ToArrayAsync();
|
||||
|
||||
IdentityRole[] roles = _dbContext.Roles.Where(r => userRoles.Contains(r.Id)).ToArray();
|
||||
var roles = await _dbContext.Roles
|
||||
.Where(r => userRoles.Contains(r.Id))
|
||||
.ToArrayAsync();
|
||||
|
||||
user.Roles = roles.Select(r => r.Name).ToArray();
|
||||
user.Roles = roles.Select(r => r.Name).OfType<string>().ToArray();
|
||||
user.EmailConfirmed = await _userManager.IsEmailConfirmedAsync(userData);
|
||||
user.AllowMonthlyEmail = userData.AllowMonthlyEmail;
|
||||
user.HasPassword = await _userManager.HasPasswordAsync(userData);
|
||||
user.TwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(userData);
|
||||
var externalLogins = await _userManager.GetLoginsAsync(userData);
|
||||
user.ExternalLoginCount = externalLogins.Count;
|
||||
user.ExternalLogins = externalLogins.Select(login => new LinkedExternalLogin
|
||||
{
|
||||
Provider = login.LoginProvider,
|
||||
ProviderKey = login.ProviderKey,
|
||||
DisplayName = login.ProviderDisplayName ?? login.LoginProvider
|
||||
}).ToArray();
|
||||
user.PostsCounter = await _dbContext.BlogSpot.LongCountAsync(x => x.AuthorId == uid);
|
||||
user.DiskUsage = userData.DiskUsage;
|
||||
user.DiskQuota = userData.DiskQuota;
|
||||
user.Credits = userData.AccountBalance?.Credits ?? 0;
|
||||
user.BankAccounts = (userData.BankInfo ?? [])
|
||||
.Select(bank => new BankAccountInfo
|
||||
{
|
||||
Id = bank.Id,
|
||||
IBAN = bank.IBAN,
|
||||
BIC = bank.BIC,
|
||||
BankCode = bank.BankCode,
|
||||
WicketCode = bank.WicketCode,
|
||||
AccountNumber = bank.AccountNumber,
|
||||
BankedKey = bank.BankedKey
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return Ok(user);
|
||||
}
|
||||
|
|
@ -76,6 +139,7 @@ namespace Yavsc.WebApi.Controllers
|
|||
return await _dbContext.Users
|
||||
.Include(u => u.PostalAddress)
|
||||
.Include(u => u.AccountBalance)
|
||||
.Include(u => u.BankInfo)
|
||||
.FirstAsync(u => u.Id == uid);
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +151,11 @@ namespace Yavsc.WebApi.Controllers
|
|||
return BadRequest(new { error = "Payload is required." });
|
||||
}
|
||||
|
||||
if (request.BankInfoSummary is not null)
|
||||
{
|
||||
return BadRequest(new { error = "Bank information must be managed through the bank-info endpoint." });
|
||||
}
|
||||
|
||||
var uid = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
|
|
@ -143,48 +212,348 @@ namespace Yavsc.WebApi.Controllers
|
|||
user.DedicatedGoogleCalendar = request.GoogleCalendarId.Trim();
|
||||
}
|
||||
|
||||
if (request.BankInfoSummary is not null)
|
||||
{
|
||||
var bankSummary = request.BankInfoSummary.Trim();
|
||||
if (string.IsNullOrWhiteSpace(bankSummary))
|
||||
{
|
||||
if (user.BankInfo is not null)
|
||||
{
|
||||
user.BankInfo.Clear();
|
||||
}
|
||||
}
|
||||
else if (user.BankInfo is null || user.BankInfo.Count == 0)
|
||||
{
|
||||
var bank = new Yavsc.Models.Bank.BankIdentity
|
||||
{
|
||||
UserId = uid,
|
||||
User = user,
|
||||
IBAN = bankSummary,
|
||||
BIC = string.Empty,
|
||||
AccountNumber = string.Empty,
|
||||
BankCode = string.Empty,
|
||||
WicketCode = string.Empty,
|
||||
BankedKey = 0
|
||||
};
|
||||
|
||||
user.BankInfo ??= [];
|
||||
user.BankInfo.Add(bank);
|
||||
}
|
||||
else
|
||||
{
|
||||
var firstBank = user.BankInfo.First();
|
||||
firstBank.IBAN = bankSummary;
|
||||
firstBank.BIC = string.Empty;
|
||||
firstBank.AccountNumber = string.Empty;
|
||||
firstBank.BankCode = string.Empty;
|
||||
firstBank.WicketCode = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync(uid);
|
||||
return Ok(new { status = "saved", fullName = user.FullName, address = user.PostalAddress?.Address, googleCalendarId = user.DedicatedGoogleCalendar });
|
||||
}
|
||||
|
||||
[HttpPut("preferences/monthly-email")]
|
||||
public async Task<IActionResult> UpdateMonthlyEmailPreference([FromBody] MonthlyEmailPreferenceRequest request)
|
||||
{
|
||||
var uid = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByIdAsync(uid);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
user.AllowMonthlyEmail = request.Enabled;
|
||||
var result = await _userManager.UpdateAsync(user);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return BadRequest(new { error = "Unable to update email preferences.", details = result.Errors.Select(e => e.Description) });
|
||||
}
|
||||
|
||||
return Ok(new { enabled = user.AllowMonthlyEmail });
|
||||
}
|
||||
|
||||
[HttpPut("security/two-factor")]
|
||||
public async Task<IActionResult> UpdateTwoFactor([FromBody] TwoFactorRequest request)
|
||||
{
|
||||
var uid = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByIdAsync(uid);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var result = await _userManager.SetTwoFactorEnabledAsync(user, request.Enabled);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return BadRequest(new { error = "Unable to update two-factor authentication.", details = result.Errors.Select(e => e.Description) });
|
||||
}
|
||||
|
||||
return Ok(new { enabled = user.TwoFactorEnabled });
|
||||
}
|
||||
|
||||
[HttpPut("security/password")]
|
||||
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
|
||||
{
|
||||
if (request is null
|
||||
|| string.IsNullOrWhiteSpace(request.NewPassword)
|
||||
|| !string.Equals(request.NewPassword, request.ConfirmPassword, StringComparison.Ordinal))
|
||||
{
|
||||
return BadRequest(new { error = "A matching new password and confirmation are required." });
|
||||
}
|
||||
|
||||
var uid = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByIdAsync(uid);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var result = await _userManager.HasPasswordAsync(user)
|
||||
? await _userManager.ChangePasswordAsync(user, request.CurrentPassword, request.NewPassword)
|
||||
: await _userManager.AddPasswordAsync(user, request.NewPassword);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return BadRequest(new { error = "Unable to change the password.", details = result.Errors.Select(e => e.Description) });
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("security/external-logins")]
|
||||
public async Task<IActionResult> RemoveExternalLogin([FromQuery] string provider, [FromQuery] string providerKey)
|
||||
{
|
||||
var uid = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(provider) || string.IsNullOrWhiteSpace(providerKey))
|
||||
{
|
||||
return BadRequest(new { error = "A provider and provider key are required." });
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByIdAsync(uid);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (await _userManager.GetLoginsAsync(user) is { Count: <= 1 }
|
||||
&& !await _userManager.HasPasswordAsync(user))
|
||||
{
|
||||
return Conflict(new { error = "Add a password before removing the only sign-in method." });
|
||||
}
|
||||
|
||||
var result = await _userManager.RemoveLoginAsync(user, provider, providerKey);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return BadRequest(new { error = "Unable to remove the external login.", details = result.Errors.Select(e => e.Description) });
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("me")]
|
||||
public async Task<IActionResult> DeleteMyAccount([FromBody] DeleteAccountRequest request)
|
||||
{
|
||||
var uid = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByIdAsync(uid);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!string.Equals(request?.UsernameConfirmation, user.UserName, StringComparison.Ordinal))
|
||||
{
|
||||
return BadRequest(new { error = "Type your exact username to confirm account deletion." });
|
||||
}
|
||||
|
||||
_dbContext.DeviceDeclaration.RemoveRange(
|
||||
_dbContext.DeviceDeclaration.Where(device => device.DeviceOwnerId == uid));
|
||||
var result = await _userManager.DeleteAsync(user);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return BadRequest(new { error = "Unable to delete this account.", details = result.Errors.Select(e => e.Description) });
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("email/confirmation")]
|
||||
public async Task<IActionResult> SendEmailConfirmation()
|
||||
{
|
||||
var uid = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByIdAsync(uid);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (user.EmailConfirmed)
|
||||
{
|
||||
return Conflict(new { error = "This email address is already confirmed." });
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(user.Email))
|
||||
{
|
||||
return BadRequest(new { error = "No email address is associated with this account." });
|
||||
}
|
||||
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var confirmationUrl = new Uri(
|
||||
new Uri(siteSettings.ExternalUrl.TrimEnd('/') + "/", UriKind.Absolute),
|
||||
"Account/ConfirmEmail").ToString();
|
||||
confirmationUrl = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(
|
||||
confirmationUrl,
|
||||
new Dictionary<string, string?> { ["userId"] = user.Id, ["code"] = code });
|
||||
|
||||
await _emailSender.SendEmailAsync(
|
||||
user.UserName ?? user.Email,
|
||||
user.Email,
|
||||
$"[{siteSettings.Title}] Confirmation de votre adresse e-mail",
|
||||
$"<p>Confirmez votre adresse e-mail en suivant ce lien :</p><p><a href=\"{System.Text.Encodings.Web.HtmlEncoder.Default.Encode(confirmationUrl)}\">Confirmer mon adresse</a></p>");
|
||||
|
||||
return Ok(new { message = "Un e-mail de confirmation a été envoyé." });
|
||||
}
|
||||
|
||||
[HttpGet("calendars")]
|
||||
public async Task<IActionResult> GetCalendars([FromQuery] string? pageToken = null)
|
||||
{
|
||||
var calendars = await _calendarManager.GetCalendarsAsync(pageToken ?? string.Empty);
|
||||
var options = (calendars.Items ?? [])
|
||||
.Where(calendar => string.Equals(calendar.AccessRole, "owner", StringComparison.OrdinalIgnoreCase))
|
||||
.Select(calendar => new CalendarOption
|
||||
{
|
||||
Id = calendar.Id,
|
||||
Name = calendar.Summary ?? calendar.Id
|
||||
})
|
||||
.ToArray();
|
||||
return Ok(options);
|
||||
}
|
||||
|
||||
[HttpGet("bank-info")]
|
||||
public async Task<IActionResult> GetBankInfo()
|
||||
{
|
||||
var uid = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
var accounts = await _dbContext.BankIdentity
|
||||
.AsNoTracking()
|
||||
.Where(bank => bank.UserId == uid)
|
||||
.Select(bank => new BankAccountInfo
|
||||
{
|
||||
Id = bank.Id,
|
||||
IBAN = bank.IBAN,
|
||||
BIC = bank.BIC,
|
||||
BankCode = bank.BankCode,
|
||||
WicketCode = bank.WicketCode,
|
||||
AccountNumber = bank.AccountNumber,
|
||||
BankedKey = bank.BankedKey
|
||||
})
|
||||
.ToArrayAsync();
|
||||
return Ok(accounts);
|
||||
}
|
||||
|
||||
[HttpPost("bank-info")]
|
||||
public async Task<IActionResult> AddBankInfo([FromBody] BankAccountInfoRequest request)
|
||||
{
|
||||
var uid = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
if (!IsValidBankAccountRequest(request))
|
||||
{
|
||||
return BadRequest(new { error = "Provide an IBAN or BIC and ensure each bank field fits its allowed length." });
|
||||
}
|
||||
|
||||
var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == uid);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var bank = new Yavsc.Models.Bank.BankIdentity
|
||||
{
|
||||
UserId = uid,
|
||||
User = user,
|
||||
IBAN = request.IBAN?.Trim() ?? string.Empty,
|
||||
BIC = request.BIC?.Trim() ?? string.Empty,
|
||||
BankCode = request.BankCode?.Trim() ?? string.Empty,
|
||||
WicketCode = request.WicketCode?.Trim() ?? string.Empty,
|
||||
AccountNumber = request.AccountNumber?.Trim() ?? string.Empty,
|
||||
BankedKey = request.BankedKey
|
||||
};
|
||||
_dbContext.BankIdentity.Add(bank);
|
||||
await _dbContext.SaveChangesAsync(uid);
|
||||
return Ok(new BankAccountInfo
|
||||
{
|
||||
Id = bank.Id,
|
||||
IBAN = bank.IBAN,
|
||||
BIC = bank.BIC,
|
||||
BankCode = bank.BankCode,
|
||||
WicketCode = bank.WicketCode,
|
||||
AccountNumber = bank.AccountNumber,
|
||||
BankedKey = bank.BankedKey
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPut("bank-info/{id:long}")]
|
||||
public async Task<IActionResult> UpdateBankInfo(long id, [FromBody] BankAccountInfoRequest request)
|
||||
{
|
||||
var uid = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
if (!IsValidBankAccountRequest(request))
|
||||
{
|
||||
return BadRequest(new { error = "Provide an IBAN or BIC and ensure each bank field fits its allowed length." });
|
||||
}
|
||||
|
||||
var bank = await _dbContext.BankIdentity
|
||||
.FirstOrDefaultAsync(item => item.Id == id && item.UserId == uid);
|
||||
if (bank is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
bank.IBAN = request.IBAN?.Trim() ?? string.Empty;
|
||||
bank.BIC = request.BIC?.Trim() ?? string.Empty;
|
||||
bank.BankCode = request.BankCode?.Trim() ?? string.Empty;
|
||||
bank.WicketCode = request.WicketCode?.Trim() ?? string.Empty;
|
||||
bank.AccountNumber = request.AccountNumber?.Trim() ?? string.Empty;
|
||||
bank.BankedKey = request.BankedKey;
|
||||
await _dbContext.SaveChangesAsync(uid);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private static bool IsValidBankAccountRequest(BankAccountInfoRequest? request) =>
|
||||
request is not null
|
||||
&& (!string.IsNullOrWhiteSpace(request.IBAN) || !string.IsNullOrWhiteSpace(request.BIC))
|
||||
&& (request.IBAN?.Length ?? 0) <= 33
|
||||
&& (request.BIC?.Length ?? 0) <= 15
|
||||
&& (request.BankCode?.Length ?? 0) <= 5
|
||||
&& (request.WicketCode?.Length ?? 0) <= 5
|
||||
&& (request.AccountNumber?.Length ?? 0) <= 15
|
||||
&& request.BankedKey >= 0;
|
||||
|
||||
[HttpDelete("bank-info/{id:long}")]
|
||||
public async Task<IActionResult> DeleteBankInfo(long id)
|
||||
{
|
||||
var uid = User.GetUserId();
|
||||
if (string.IsNullOrWhiteSpace(uid))
|
||||
{
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
var bank = await _dbContext.BankIdentity
|
||||
.FirstOrDefaultAsync(item => item.Id == id && item.UserId == uid);
|
||||
if (bank is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_dbContext.BankIdentity.Remove(bank);
|
||||
await _dbContext.SaveChangesAsync(uid);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("performer-profile")]
|
||||
public async Task<IActionResult> GetPerformerProfile()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
using Anthropic.SDK;
|
||||
using IdentityModel;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Yavsc.Abstract.Interfaces;
|
||||
|
|
@ -86,6 +87,16 @@ internal class Program
|
|||
options.UseNpgsql(builder.Configuration.GetConnectionString(
|
||||
Yavsc.Constants.YavscConnectionStringName)));
|
||||
|
||||
services.AddIdentityCore<ApplicationUser>(options =>
|
||||
{
|
||||
options.ClaimsIdentity.UserNameClaimType = JwtClaimTypes.PreferredUserName;
|
||||
options.ClaimsIdentity.RoleClaimType = Constants.RoleClaimType;
|
||||
})
|
||||
.AddRoles<IdentityRole>()
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders()
|
||||
.AddSignInManager();
|
||||
|
||||
services.AddLocalization(options =>
|
||||
{
|
||||
options.ResourcesPath = "Resources";
|
||||
|
|
|
|||
Loading…
Reference in a new issue