Nach einem Login mit korrigierter Groß-/Kleinschreibung (siehe vorheriger Fix zur userId- Stabilität) bezog sich die lokale Versionsverfolgung je Entität (BasedOnServerSeq-Cache) und der Pull-Cursor weiterhin auf das alte Konto - ServerSeq-Werte sind aber nur innerhalb des Event-Logs EINES Kontos gültig. Der Push wurde zu Recht abgelehnt, der Server kannte die Entität unter der neuen userId aber gar nicht (404 beim Nachladen), und HandleRejectedAsync gab bei einem 404 bisher einfach auf, ohne den veralteten Cache-Eintrag zu bereinigen - derselbe Fehlschlag bei jedem weiteren Sync-Versuch. Dreiteiliger Fix: (1) ein 404 beim Nachladen löscht jetzt den stale Cache-Eintrag, sodass der nächste Push die Entität korrekt als neu behandelt und selbstheilend durchgeht; (2) der "Vollständigen Sync erzwingen"-Button setzt jetzt auch die Push-Versionsverfolgung zurück, nicht nur den Pull-Cursor; (3) SyncLogin erkennt einen echten Kontowechsel künftig proaktiv anhand der kanonischen userId aus der Server-Antwort und resettet automatisch, bevor der Folgefehler überhaupt auftreten kann. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
69 lines
3.1 KiB
C#
69 lines
3.1 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
|
|
namespace LehrerApp.Desktop.Services;
|
|
|
|
public class SyncAuthException(string userMessage) : Exception(userMessage);
|
|
|
|
public enum SyncConnectionTestResult { Ok, Unauthorized, Unreachable }
|
|
|
|
/// <summary>
|
|
/// Login/Verbindungstest gegen den eigenen Sync-Server (LehrerApp.Api) — getrennt vom bereits
|
|
/// bestehenden <see cref="AiPlanningService"/> (anderes Backend, anderer Wire-Vertrag). Nimmt die
|
|
/// Server-URL je Aufruf entgegen statt fest im Konstruktor, weil sie über die Einstellungen zur
|
|
/// Laufzeit geändert werden kann (anders als die feste KI-Backend-URL).
|
|
/// </summary>
|
|
public class SyncAuthService(HttpClient http)
|
|
{
|
|
/// <summary>
|
|
/// Liefert neben dem Token auch die kanonische userId aus der Server-Antwort - LiteDBs
|
|
/// case-insensitive Standard-Collation lässt einen Login mit abweichender
|
|
/// Groß-/Kleinschreibung des Benutzernamens erfolgreich durch (siehe TODO 10.2.5); die
|
|
/// zurückgegebene userId ist deshalb der einzige verlässliche Weg für den Aufrufer, einen
|
|
/// Kontowechsel von einer bloßen Schreibweisen-Abweichung zu unterscheiden (siehe TODO 10.3.5).
|
|
/// </summary>
|
|
public async Task<(string Token, string UserId)> LoginAsync(string serverUrl, string username, string password)
|
|
{
|
|
HttpResponseMessage resp;
|
|
try
|
|
{
|
|
resp = await http.PostAsJsonAsync(CombineUrl(serverUrl, "/api/auth/login"), new { username, password });
|
|
}
|
|
catch (HttpRequestException)
|
|
{
|
|
throw new SyncAuthException(
|
|
"Der Sync-Server ist nicht erreichbar. Bitte Adresse und Internetverbindung prüfen.");
|
|
}
|
|
|
|
if (resp.StatusCode == HttpStatusCode.Unauthorized)
|
|
throw new SyncAuthException("Benutzername oder Passwort ist falsch.");
|
|
if (!resp.IsSuccessStatusCode)
|
|
throw new SyncAuthException("Anmeldung fehlgeschlagen. Bitte später erneut versuchen.");
|
|
|
|
var result = await resp.Content.ReadFromJsonAsync<LoginResult>();
|
|
return result is null
|
|
? throw new SyncAuthException("Unerwartete Antwort des Sync-Servers.")
|
|
: (result.Token, result.UserId);
|
|
}
|
|
|
|
public async Task<SyncConnectionTestResult> TestConnectionAsync(string serverUrl, string? token)
|
|
{
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, CombineUrl(serverUrl, "/api/sync/status"));
|
|
if (token is not null)
|
|
req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
|
|
|
try
|
|
{
|
|
var resp = await http.SendAsync(req);
|
|
if (resp.StatusCode == HttpStatusCode.Unauthorized) return SyncConnectionTestResult.Unauthorized;
|
|
return resp.IsSuccessStatusCode ? SyncConnectionTestResult.Ok : SyncConnectionTestResult.Unreachable;
|
|
}
|
|
catch (HttpRequestException) { return SyncConnectionTestResult.Unreachable; }
|
|
}
|
|
|
|
private static string CombineUrl(string serverUrl, string path) =>
|
|
new Uri(new Uri(serverUrl), path).ToString();
|
|
|
|
private record LoginResult(string Token, string UserId);
|
|
}
|