Files
LehrerApp/LehrerApp.Desktop/Services/WebUntisSettingsService.cs
T
adminandClaude Sonnet 5 33c15ffe3e WIP (unstable): Klassenlehrer-Feature auf Einstellungen/eigenen Bereich umgestellt
Nutzer-Feedback: Klassenlehrer ist eine Rolle für die ganze Klasse, unabhängig vom
eigenen Unterricht - die Anbindung an ein LearningGroup.IsClassTeacher-Flag mit Tab
in der Gruppendetailansicht war deshalb der falsche Ort. Ersetzt durch:

- Klassenauswahl in den WebUntis-Einstellungen (WebUntisSettingsService.HomeroomClassName),
  über den bestehenden WebUntis-Klassenpicker.
- Eigener Sidebar-Bereich "Klassenlehrer" statt Gruppen-Tab.
- Zwei Ebenen: kompakte Roster-Übersicht mit Ampel-Symbolen (Fehlzeiten heute,
  Klassenbuch-Badge) und Details (die bisherigen Rohdaten-Listen, jetzt gefiltert
  auf einzelne Schüler*innen anspringbar).

Noch nicht mit echtem WebUntis-Zugang gegengeprüft (siehe TODO.md).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 22:30:34 +02:00

139 lines
4.8 KiB
C#

using LehrerApp.Sync.Crypto;
using System.Text.Json;
namespace LehrerApp.Desktop.Services;
internal class WebUntisSettingsConfig
{
public bool Enabled { get; set; }
public string? EncryptedIcalUrl { get; set; }
public DateTime? LastSyncAt { get; set; }
public string LastSyncStatus { get; set; } = "";
public string? EncryptedApiCredentials { get; set; }
public int? TeacherUntisId { get; set; }
public int? HomeroomClassUntisId { get; set; }
public string? HomeroomClassName { get; set; }
}
public sealed record WebUntisCredentials(string School, string Host, string Username, string Password);
/// <summary>
/// Einstellungen für den WebUntis-iCal-Abgleich (Nutzer-Feedback, siehe TODO.md). Liegt wie
/// AiSettingsService/SyncSettingsService in LehrerApp.Desktop statt LehrerApp.Core, da die
/// Verschlüsselung über <see cref="SyncCrypto"/> aus LehrerApp.Sync läuft — Core bleibt bewusst
/// frei von Abhängigkeiten außerhalb von .NET selbst (siehe CLAUDE.md).
///
/// Die iCal-URL trägt ein eingebettetes Auth-Token und wird deshalb wie ein Passwort behandelt:
/// nie im Klartext persistiert, nur AES-256-GCM-verschlüsselt (gleicher Mechanismus wie beim
/// KI-Backend-Token) mit einem eigenen, dateirechte-geschützten Schlüssel.
/// </summary>
public class WebUntisSettingsService
{
private readonly string _configPath;
private readonly string _keyPath;
private readonly byte[] _urlKey;
private WebUntisSettingsConfig _config;
public bool Enabled => _config.Enabled;
public bool IsConfigured => _config.EncryptedIcalUrl is not null;
public bool ApiIsConfigured => _config.EncryptedApiCredentials is not null;
public int? TeacherUntisId => _config.TeacherUntisId;
public int? HomeroomClassUntisId => _config.HomeroomClassUntisId;
public string? HomeroomClassName => _config.HomeroomClassName;
public DateTime? LastSyncAt => _config.LastSyncAt;
public string LastSyncStatus => _config.LastSyncStatus;
public WebUntisSettingsService(string appDataPath)
{
_configPath = Path.Combine(appDataPath, "webuntis-settings.json");
_keyPath = Path.Combine(appDataPath, "webuntis-url.key");
_urlKey = SyncCrypto.LoadKey(_keyPath) ?? GenerateAndSaveKey();
_config = Load();
}
public void SetEnabled(bool enabled)
{
_config.Enabled = enabled;
Save();
}
public void SetIcalUrl(string url)
{
_config.EncryptedIcalUrl = SyncCrypto.EncryptObject(url, _urlKey);
Save();
}
public string? GetIcalUrl() =>
_config.EncryptedIcalUrl is null ? null : SyncCrypto.DecryptObject<string>(_config.EncryptedIcalUrl, _urlKey);
public void ClearIcalUrl()
{
_config.EncryptedIcalUrl = null;
_config.Enabled = false;
Save();
}
public void SetApiCredentials(WebUntisCredentials credentials)
{
_config.EncryptedApiCredentials = SyncCrypto.EncryptObject(credentials, _urlKey);
Save();
}
public WebUntisCredentials? GetApiCredentials() => _config.EncryptedApiCredentials is null
? null
: SyncCrypto.DecryptObject<WebUntisCredentials>(_config.EncryptedApiCredentials, _urlKey);
public void ClearApiCredentials()
{
_config.EncryptedApiCredentials = null;
_config.TeacherUntisId = null;
_config.HomeroomClassUntisId = null;
_config.HomeroomClassName = null;
Save();
}
public void SetTeacherUntisId(int? teacherUntisId)
{
_config.TeacherUntisId = teacherUntisId;
Save();
}
// Die Klasse, deren Klassenlehrer/-in man ist - unabhängig von jeder LearningGroup (siehe
// TODO.md "Klassenlehrer"-Feature): man ist es für die ganze Klasse, nicht für einen einzelnen
// Unterricht/Kurs. HomeroomClassName ist wie TeacherUntisId unverschlüsselt, da kein Geheimnis.
public void SetHomeroomClass(int? untisId, string? name)
{
_config.HomeroomClassUntisId = untisId;
_config.HomeroomClassName = name;
Save();
}
public void SetLastSync(DateTime at, string status)
{
_config.LastSyncAt = at;
_config.LastSyncStatus = status;
Save();
}
private byte[] GenerateAndSaveKey()
{
var key = SyncCrypto.GenerateKey();
SyncCrypto.SaveKey(key, _keyPath);
return key;
}
private void Save() => File.WriteAllText(_configPath, JsonSerializer.Serialize(_config));
private WebUntisSettingsConfig Load()
{
try
{
if (File.Exists(_configPath))
return JsonSerializer.Deserialize<WebUntisSettingsConfig>(File.ReadAllText(_configPath))
?? new WebUntisSettingsConfig();
}
catch { /* beschädigte Konfiguration -> Standardwert */ }
return new WebUntisSettingsConfig();
}
}