Untis API Integration

This commit is contained in:
2026-08-24 21:52:00 +02:00
parent 5841d96c5b
commit 0f10f754d0
26 changed files with 1237 additions and 58 deletions
+75
View File
@@ -0,0 +1,75 @@
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);
/// <summary>
/// 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.
/// </summary>
public sealed class WebUntisConnectionStore(IHttpClientFactory httpClientFactory) : IAsyncDisposable
{
private sealed record Entry(WebUntisClient Client, string School, string Username);
private readonly ConcurrentDictionary<string, Entry> _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<WebUntisConnectionStatus> 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();
}
}