141 lines
8.4 KiB
C#
141 lines
8.4 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
|
|
namespace LehrerApp.Desktop.Services;
|
|
|
|
public sealed class WebUntisIntegrationException(string message) : Exception(message);
|
|
|
|
public sealed record UntisSchoolYearDto(int UntisId, string Name, int StartDate, int EndDate);
|
|
public sealed record UntisClassDto(int UntisId, string Name, string? LongName);
|
|
public sealed record UntisTeacherDto(int UntisId, string Name, string? ForeName, string? LongName, string? Title,
|
|
bool Active, IReadOnlyList<int> DepartmentUntisIds)
|
|
{
|
|
public string DisplayName => string.IsNullOrWhiteSpace(LongName)
|
|
? Name
|
|
: $"{ForeName} {LongName} ({Name})".Trim();
|
|
}
|
|
public sealed record UntisEntityDto(int Id, string Name, int? OriginalId, string? OriginalName, string? ExternalKey);
|
|
public sealed record UntisTimeUnitDto(string Name, int StartTime, int EndTime);
|
|
public sealed record UntisTimeGridDayDto(int Day, IReadOnlyList<UntisTimeUnitDto> TimeUnits);
|
|
public sealed record UntisTimetablePeriodDto(int Id, int Date, int StartTime, int EndTime, string? Code,
|
|
string? ActivityType, string? Info, string? LessonText, string? SubstitutionText, string? StudentGroup,
|
|
IReadOnlyList<UntisEntityDto> Classes, IReadOnlyList<UntisEntityDto> Teachers,
|
|
IReadOnlyList<UntisEntityDto> Subjects, IReadOnlyList<UntisEntityDto> Rooms);
|
|
public sealed record UntisStudentAddressDto(string? Email, string? Mobile, string? Phone, string? City,
|
|
string? PostCode, string? Street);
|
|
public sealed record UntisStudentDto(int UntisId, int ExternKey, string ClassName, string? Name, string? LongName,
|
|
string? ForeName, string DisplayName, string? Gender, int? BirthDate, string? BirthDateRaw, int? EntryDate,
|
|
string? EntryDateRaw, int? ExitDate, string? ExitDateRaw, string? Text, string? MedicalReportDuty,
|
|
string? Schulpflicht, string? Majority, UntisStudentAddressDto Address, string? AttributeIL);
|
|
public sealed record UntisStudentReportDto(int Count, string? ClassNameFilter, IReadOnlyList<UntisStudentDto> Students);
|
|
public sealed record UntisStudentAbsenceDto(int StudentKey, int Date, int StartTime, int EndTime, int AbsentMinutes,
|
|
bool Checked, string? AbsenceReason, string? ExcuseStatus, int? SubjectId, IReadOnlyList<int> TeacherIds,
|
|
string? StudentGroup);
|
|
public sealed record UntisStudentAbsenceReportDto(int StudentKey, int StartDate, int EndDate, int EntryCount,
|
|
int AbsentMinutes, IReadOnlyList<UntisStudentAbsenceDto> Absences);
|
|
|
|
/// <summary>Authentifizierter Desktop-Client für die LehrerApp-API. Falls der Server neu gestartet
|
|
/// wurde, baut er die nur im Server-RAM gehaltene WebUntis-Verbindung automatisch erneut auf.</summary>
|
|
public sealed class WebUntisIntegrationService(HttpClient http, SyncSettingsService syncSettings,
|
|
WebUntisSettingsService settings)
|
|
{
|
|
public bool IsAvailable => syncSettings.IsLoggedIn && !string.IsNullOrWhiteSpace(syncSettings.ServerUrl)
|
|
&& settings.ApiIsConfigured;
|
|
|
|
public async Task ConnectAsync(WebUntisCredentials credentials, CancellationToken token = default)
|
|
{
|
|
using var response = await SendAsync(HttpMethod.Post, "/api/webuntis/connection", new
|
|
{
|
|
credentials.School,
|
|
Host = string.IsNullOrWhiteSpace(credentials.Host) ? null : credentials.Host,
|
|
credentials.Username,
|
|
credentials.Password,
|
|
}, token);
|
|
await EnsureSuccessAsync(response);
|
|
}
|
|
|
|
public async Task DisconnectAsync(CancellationToken token = default)
|
|
{
|
|
using var response = await SendAsync(HttpMethod.Delete, "/api/webuntis/connection", null, token);
|
|
await EnsureSuccessAsync(response);
|
|
}
|
|
|
|
public Task<IReadOnlyList<UntisSchoolYearDto>> GetSchoolYearsAsync(CancellationToken token = default) =>
|
|
GetAsync<IReadOnlyList<UntisSchoolYearDto>>("/api/webuntis/schoolyears", token);
|
|
|
|
public Task<IReadOnlyList<UntisClassDto>> GetClassesAsync(int schoolYearId, CancellationToken token = default) =>
|
|
GetAsync<IReadOnlyList<UntisClassDto>>($"/api/webuntis/classes?schoolyearId={schoolYearId}", token);
|
|
|
|
public Task<IReadOnlyList<UntisTeacherDto>> GetTeachersAsync(CancellationToken token = default) =>
|
|
GetAsync<IReadOnlyList<UntisTeacherDto>>("/api/webuntis/teachers", token);
|
|
|
|
public Task<IReadOnlyList<UntisTimeGridDayDto>> GetTimeGridAsync(CancellationToken token = default) =>
|
|
GetAsync<IReadOnlyList<UntisTimeGridDayDto>>("/api/webuntis/timegrid", token);
|
|
|
|
public Task<IReadOnlyList<UntisTimetablePeriodDto>> GetTimetableAsync(int teacherId, DateOnly start,
|
|
DateOnly end, CancellationToken token = default) => GetAsync<IReadOnlyList<UntisTimetablePeriodDto>>(
|
|
$"/api/webuntis/timetable?elementType=teacher&elementId={teacherId}&startDate={Date(start)}&endDate={Date(end)}", token);
|
|
|
|
public Task<UntisStudentReportDto> GetStudentsAsync(string className, CancellationToken token = default) =>
|
|
GetAsync<UntisStudentReportDto>($"/api/webuntis/student-report?className={Uri.EscapeDataString(className)}", token);
|
|
|
|
public Task<UntisStudentAbsenceReportDto> GetAbsencesAsync(int studentKey, DateOnly start, DateOnly end,
|
|
CancellationToken token = default) => GetAsync<UntisStudentAbsenceReportDto>(
|
|
$"/api/webuntis/students/{studentKey}/absences?startDate={Date(start)}&endDate={Date(end)}", token);
|
|
|
|
private async Task<T> GetAsync<T>(string path, CancellationToken token)
|
|
{
|
|
await EnsureConnectedAsync(token);
|
|
using var response = await SendAsync(HttpMethod.Get, path, null, token);
|
|
await EnsureSuccessAsync(response);
|
|
return await response.Content.ReadFromJsonAsync<T>(cancellationToken: token)
|
|
?? throw new WebUntisIntegrationException("Der Server hat keine WebUntis-Daten zurückgegeben.");
|
|
}
|
|
|
|
private async Task EnsureConnectedAsync(CancellationToken token)
|
|
{
|
|
var credentials = settings.GetApiCredentials()
|
|
?? throw new WebUntisIntegrationException("Bitte zuerst die WebUntis-Anmeldedaten einrichten.");
|
|
using var status = await SendAsync(HttpMethod.Get, "/api/webuntis/connection", null, token);
|
|
await EnsureSuccessAsync(status);
|
|
using var json = JsonDocument.Parse(await status.Content.ReadAsStringAsync(token));
|
|
if (!json.RootElement.GetProperty("connected").GetBoolean()) await ConnectAsync(credentials, token);
|
|
}
|
|
|
|
private async Task<HttpResponseMessage> SendAsync(HttpMethod method, string path, object? body,
|
|
CancellationToken token)
|
|
{
|
|
if (!syncSettings.IsLoggedIn || string.IsNullOrWhiteSpace(syncSettings.ServerUrl))
|
|
throw new WebUntisIntegrationException("Bitte zuerst unter „Synchronisation“ am LehrerApp-Server anmelden.");
|
|
using var request = new HttpRequestMessage(method, new Uri(new Uri(syncSettings.ServerUrl), path));
|
|
request.Headers.Authorization = new("Bearer", syncSettings.GetToken());
|
|
if (body is not null) request.Content = JsonContent.Create(body);
|
|
try { return await http.SendAsync(request, token); }
|
|
catch (HttpRequestException) { throw new WebUntisIntegrationException("Der LehrerApp-Server ist nicht erreichbar."); }
|
|
}
|
|
|
|
private static async Task EnsureSuccessAsync(HttpResponseMessage response)
|
|
{
|
|
if (response.IsSuccessStatusCode) return;
|
|
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
|
throw new WebUntisIntegrationException("Die Server-Anmeldung ist abgelaufen. Bitte erneut anmelden.");
|
|
if (response.StatusCode == HttpStatusCode.NotFound &&
|
|
response.RequestMessage?.RequestUri?.AbsolutePath.StartsWith("/api/webuntis",
|
|
StringComparison.OrdinalIgnoreCase) == true)
|
|
throw new WebUntisIntegrationException(
|
|
"Der konfigurierte LehrerApp-Server enthält die WebUntis-Integration noch nicht. " +
|
|
"Bitte zuerst die aktuelle LehrerApp.Api auf dem Server bereitstellen.");
|
|
try
|
|
{
|
|
using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
|
if (json.RootElement.TryGetProperty("detail", out var detail) && !string.IsNullOrWhiteSpace(detail.GetString()))
|
|
throw new WebUntisIntegrationException(detail.GetString()!);
|
|
}
|
|
catch (JsonException) { }
|
|
throw new WebUntisIntegrationException("Der WebUntis-Abruf ist fehlgeschlagen.");
|
|
}
|
|
|
|
private static int Date(DateOnly date) => date.Year * 10000 + date.Month * 100 + date.Day;
|
|
}
|