Refactor LoginPage to MVVM

This commit is contained in:
2025-12-17 17:25:42 +01:00
parent bb5aac2944
commit c11b361655
11 changed files with 442 additions and 134 deletions

View File

@@ -0,0 +1,94 @@
using System.Net.Http;
using System.Text;
using Jugenddienst_Stunden.Infrastructure;
using Jugenddienst_Stunden.Interfaces;
using Jugenddienst_Stunden.Models;
using Jugenddienst_Stunden.Types;
namespace Jugenddienst_Stunden.Services;
internal sealed class AuthService : IAuthService {
private readonly IApiClient _api;
private readonly IAppSettings _settings;
private readonly IAlertService _alerts;
public AuthService(IApiClient api, IAppSettings settings, IAlertService alerts) {
_api = api;
_settings = settings;
_alerts = alerts;
}
public async Task<User> LoginWithCredentials(string username, string password, string serverUrl, CancellationToken ct = default) {
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
throw new Exception("Benutzername und Passwort werden benötigt.");
var apiBase = NormalizeApiUrl(serverUrl);
_settings.ApiUrl = apiBase; // BaseAddress für IApiClient setzen
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("user", username),
new KeyValuePair<string, string>("pass", password)
});
// POST ohne Pfad die API erwartet /appapi
// Wichtig: Basis-URL hat garantiert einen abschließenden Slash (…/appapi/),
// sodass ein leerer Pfad nicht zu Redirects führt (die den POST in GET verwandeln könnten).
var res = await _api.SendAsync<BaseResponse>(HttpMethod.Post, string.Empty, content, null, ct).ConfigureAwait(false);
if (res.user is null)
throw new Exception(res.message ?? "Ungültige Antwort vom Server.");
ApplyUser(res.user, apiBase);
return res.user;
}
public async Task<User> LoginWithToken(string token, CancellationToken ct = default) {
if (string.IsNullOrWhiteSpace(token)) throw new Exception("Kein Token erkannt.");
// QR-Token enthält die URL extrahiere sie
var td = new TokenData(token);
// URL aus dem Token ebenfalls normalisieren, damit sie auf "/appapi/" endet
_settings.ApiUrl = NormalizeApiUrl(td.Url);
_settings.ApiKey = token;
var res = await _api.GetAsync<BaseResponse>(string.Empty, null, ct).ConfigureAwait(false);
if (res.user is null)
throw new Exception(res.message ?? "Ungültige Antwort vom Server.");
ApplyUser(res.user, td.Url);
return res.user;
}
private void ApplyUser(User user, string apiBase) {
_settings.ApiUrl = apiBase;
// Wenn der Server keinen Token im User zurückliefert (QR-Login-Fall), bestehenden Token beibehalten
var tokenToUse = string.IsNullOrWhiteSpace(user.Token) ? _settings.ApiKey : user.Token;
_settings.ApiKey = tokenToUse;
_settings.EmployeeId = user.Id;
_settings.Name = user.Name;
_settings.Surname = user.Surname;
}
private static string NormalizeApiUrl(string input) {
if (string.IsNullOrWhiteSpace(input)) throw new Exception("Server-URL wird benötigt.");
var url = input.Trim();
if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) {
url = "https://" + url;
}
// Sicherstellen, dass der Pfad auf "/appapi" endet
if (!url.EndsWith("/appapi", StringComparison.OrdinalIgnoreCase)) {
url = url.TrimEnd('/') + "/appapi";
}
// WICHTIG: Einen abschließenden Slash erzwingen, damit relative Pfade korrekt angehängt werden
// und damit POST auf Basis-URL (leerem Pfad) nicht zu einem 301/302-Redirect führt,
// der den Body (user/pass) verlieren könnte.
//if (!url.EndsWith("/", StringComparison.Ordinal)) {
// url += "/";
//}
if (url.EndsWith("/", StringComparison.Ordinal)) {
url = url.Remove(url.Length - 1, 1);
}
return url;
}
}