Files
SlipItIn/SlipItIn/ViewModels/LoginViewModel.cs
Tim Krampitz af6528ee68
Some checks failed
Build / build (pull_request) Has been cancelled
Tech-Debt-Cleanup: Doku, Security-Hardening, Server-Validierung, Client-Stabilitaet
Doku/Repo:
- README.md mit Setup, Run-Anleitung, Konfiguration und Sprachpolicy neu geschrieben
- appsettings.json: committed JWT-Key und irreführende LocalDB-Connection-String entfernt
- appsettings.Development.json: expliziter Dev-Only JWT-Key hinterlegt
- build.yml: Build-CI für master/PRs hinzugefügt
- slnx/aspire.config.json: Pfad-Casing korrigiert (Linux-kompatibel)
- .gitignore: Kommentar zu den Legacy-SQLite-Einträgen

Server:
- Jwt:Key ist in Production Pflicht (Fail-fast statt Fallback-Secret)
- Passwort-Mindestlänge 8 Zeichen bei Registrierung
- StartGame: nur aus Lobby, mindestens 2 Spieler
- SetPlayerReady: gameId/Status geprüft
- SubmitSlip: Game-/Round-Status, Round-Membership, IsUsed validiert
- CreateChallenge: Round-Bindung, kein Self-Challenge, nur gespielte Karten, keine Doppel-Challenges
- ResolveChallenge: Pending-Guard, Scoring (Score/SuccessfulSlips/FailedSlips), Approved-Transfer gefixt
- GetPlayerHand: nur Karten der aktiven Runde
- RoundTimeRemaining: StartedAt wird bei Round-Aktivierung gesetzt
- Lobby-Code: crypto-random mit Kollisions-Retry
- EF: explizite NpgsqlDataSource-Auflösung aus DI
- Hub: Reconnect-Gruppenbeitritt, ConnectionId-Cleanup beim Disconnect, ChallengingPlayerName gesetzt, SlipChallenged/ChallengeResolved konsistent

Client:
- Offline-Start löscht Session nicht mehr (NetworkError vs. Invalid unterschieden)
- ResolveChallenge geht durch die Offline-Queue (kein Crash mehr bei Disconnect)
- Queue: Poison-Entries werden nach 3 Retries verworfen statt die Queue ewig zu blockieren
- SignalR: Verbindung wird sauber disposed/rebuildet, Token immer zur Laufzeit gelesen, Connect-Lock gegen parallele Starts
- ViewModels: Thread-Marshaling in allen Receive-Handlern, kein async void mehr
- LobbyViewModel: Logout deaktiviert den Singleton (kein stale-Navigation mehr), Reaktivierung via Activate()
- GameBoard: lokaler 1s-Timer für RoundTimeRemaining
- Overlay: x:DataType für kompilierte Bindings, Frame→Border
- csproj: Preview-Logging-Package auf 10.0.10, Template-Cruft entfernt
2026-08-22 19:37:54 +02:00

82 lines
2.5 KiB
C#

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using SlipItIn.Services.Interfaces;
using SlipItIn.Shared.DTOs;
using SlipItIn.Views;
namespace SlipItIn.ViewModels;
public partial class LoginViewModel : BaseViewModel
{
private readonly IApiService _apiService;
private readonly IAuthSessionService _authSession;
private readonly ISignalRService _signalR;
private readonly IGameStateService _gameStateService;
[ObservableProperty]
private string email = string.Empty;
[ObservableProperty]
private string password = string.Empty;
public LoginViewModel(IApiService apiService, IAuthSessionService authSession, ISignalRService signalR, IGameStateService gameStateService)
{
_apiService = apiService;
_authSession = authSession;
_signalR = signalR;
_gameStateService = gameStateService;
}
[RelayCommand]
private Task GoToRegisterAsync() => Shell.Current.GoToAsync(nameof(RegisterPage));
[RelayCommand]
private async Task LoginAsync()
{
await RunSafeAsync(async () =>
{
var response = await _apiService.LoginAsync(new LoginRequestDto
{
Email = Email,
Password = Password
});
await _authSession.SetSessionAsync(response);
var connected = await _signalR.ConnectAsync(response.Token);
if (!connected)
throw new InvalidOperationException("Verbindung zum Spielserver fehlgeschlagen.");
await _gameStateService.InitializeAsync();
await Shell.Current.GoToAsync(nameof(LobbyPage));
}, "Anmeldung läuft...");
}
[RelayCommand]
public async Task InitializeAsync()
{
if (!_authSession.IsAuthenticated)
return;
var sessionResult = await _apiService.ValidateSessionAsync();
if (sessionResult == SessionValidationResult.NetworkError)
return;
if (sessionResult == SessionValidationResult.Invalid)
{
await _authSession.ClearSessionAsync();
await _gameStateService.ClearLocalGameDataAsync();
await _signalR.DisconnectAsync();
return;
}
var token = _authSession.AccessToken;
if (string.IsNullOrWhiteSpace(token))
return;
var connected = await _signalR.ConnectAsync(token);
if (connected)
await Shell.Current.GoToAsync(nameof(LobbyPage));
}
}