using System.Collections.Concurrent; using Microsoft.Extensions.Options; namespace LehrerApp.Api; public sealed record WebUntisConnectRequest( string School, string? Host, string Username, string Password); public sealed record WebUntisConnectionStatus(bool Connected, string? School, string? Username); /// /// Hält persönliche WebUntis-Verbindungen ausschließlich im Arbeitsspeicher des Servers. /// Passwörter werden weder protokolliert noch serverseitig persistiert. Der enthaltene Client /// verwaltet die eigentliche WebUntis-Session und meldet sie nach Inaktivität wieder ab. /// public sealed class WebUntisConnectionStore(IHttpClientFactory httpClientFactory) : IAsyncDisposable { private sealed record Entry(WebUntisClient Client, string School, string Username); private readonly ConcurrentDictionary _connections = new(); public WebUntisClient? GetClient(string userId) => _connections.TryGetValue(userId, out var entry) ? entry.Client : null; public WebUntisConnectionStatus GetStatus(string userId) => _connections.TryGetValue(userId, out var entry) ? new(true, entry.School, entry.Username) : new(false, null, null); public async Task ConnectAsync(string userId, WebUntisConnectRequest request, CancellationToken cancellationToken) { var options = new WebUntisOptions { School = request.School, Host = request.Host ?? "", Username = request.Username, Password = request.Password, Client = "LehrerApp", SessionIdleTimeoutMinutes = 10, }; var client = new WebUntisClient(httpClientFactory.CreateClient("webuntis"), Options.Create(options)); try { // Authentifiziert wirklich gegen WebUntis; ungültige Daten werden nicht gespeichert. await client.GetSchoolYearsAsync(cancellationToken); } catch { await client.DisposeAsync(); throw; } var entry = new Entry(client, request.School.Trim(), request.Username.Trim()); if (_connections.TryGetValue(userId, out var previous)) await previous.Client.DisposeAsync(); _connections[userId] = entry; return new(true, entry.School, entry.Username); } public async Task DisconnectAsync(string userId) { if (_connections.TryRemove(userId, out var entry)) await entry.Client.DisposeAsync(); } public async ValueTask DisposeAsync() { var entries = _connections.Values.ToList(); _connections.Clear(); foreach (var entry in entries) await entry.Client.DisposeAsync(); } }