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
+79 -34
View File
@@ -244,45 +244,79 @@ public static class Endpoints
});
}
// ── WebUntis (granulare, zustandslose Abrufe) ─────────────────────────────
// ── WebUntis (granulare Abrufe über benutzergebundene RAM-Sitzungen) ──────
public static void MapWebUntisEndpoints(this WebApplication app)
{
var group = app.MapGroup("/api/webuntis").RequireAuthorization();
group.MapGet("/schoolyears", (WebUntisClient client, CancellationToken cancellationToken) =>
WebUntisResult(() => client.GetSchoolYearsAsync(cancellationToken)));
group.MapGet("/connection", (ClaimsPrincipal user, WebUntisConnectionStore connections) =>
{
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
return uid is null ? Results.Unauthorized() : Results.Ok(connections.GetStatus(uid));
});
group.MapGet("/classes", ([FromQuery] int schoolyearId, WebUntisClient client,
group.MapPost("/connection", async (WebUntisConnectRequest request, ClaimsPrincipal user,
WebUntisConnectionStore connections, CancellationToken cancellationToken) =>
{
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (uid is null) return Results.Unauthorized();
if (string.IsNullOrWhiteSpace(request.School) || string.IsNullOrWhiteSpace(request.Username) ||
string.IsNullOrWhiteSpace(request.Password))
return Results.BadRequest("Schule, Benutzername und Passwort sind erforderlich.");
return await WebUntisResult(() => connections.ConnectAsync(uid, request, cancellationToken));
});
group.MapDelete("/connection", async (ClaimsPrincipal user, WebUntisConnectionStore connections) =>
{
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (uid is null) return Results.Unauthorized();
await connections.DisconnectAsync(uid);
return Results.NoContent();
});
group.MapGet("/schoolyears", (ClaimsPrincipal user, WebUntisConnectionStore connections,
WebUntisClient fallback, CancellationToken cancellationToken) =>
WithWebUntisClient(user, connections, fallback,
client => client.GetSchoolYearsAsync(cancellationToken)));
group.MapGet("/classes", ([FromQuery] int schoolyearId, ClaimsPrincipal user,
WebUntisConnectionStore connections, WebUntisClient fallback,
CancellationToken cancellationToken) => schoolyearId <= 0
? Task.FromResult<IResult>(Results.BadRequest("schoolyearId muss größer als 0 sein."))
: WebUntisResult(() => client.GetClassesAsync(schoolyearId, cancellationToken)));
: WithWebUntisClient(user, connections, fallback,
client => client.GetClassesAsync(schoolyearId, cancellationToken)));
group.MapGet("/teachers", (WebUntisClient client, CancellationToken cancellationToken) =>
WebUntisResult(() => client.GetTeachersAsync(cancellationToken)));
group.MapGet("/teachers", (ClaimsPrincipal user, WebUntisConnectionStore connections,
WebUntisClient fallback, CancellationToken cancellationToken) =>
WithWebUntisClient(user, connections, fallback, client => client.GetTeachersAsync(cancellationToken)));
group.MapGet("/student-report", ([FromQuery] string? className, WebUntisClient client,
CancellationToken cancellationToken) =>
WebUntisResult(() => client.GetStudentReportAsync(className, cancellationToken)));
group.MapGet("/student-report", ([FromQuery] string? className, ClaimsPrincipal user,
WebUntisConnectionStore connections, WebUntisClient fallback, CancellationToken cancellationToken) =>
WithWebUntisClient(user, connections, fallback,
client => client.GetStudentReportAsync(className, cancellationToken)));
group.MapGet("/holidays", (WebUntisClient client, CancellationToken cancellationToken) =>
WebUntisResult(() => client.GetHolidaysAsync(cancellationToken)));
group.MapGet("/holidays", (ClaimsPrincipal user, WebUntisConnectionStore connections,
WebUntisClient fallback, CancellationToken cancellationToken) =>
WithWebUntisClient(user, connections, fallback, client => client.GetHolidaysAsync(cancellationToken)));
group.MapGet("/timegrid", (WebUntisClient client, CancellationToken cancellationToken) =>
WebUntisResult(() => client.GetTimeGridAsync(cancellationToken)));
group.MapGet("/timegrid", (ClaimsPrincipal user, WebUntisConnectionStore connections,
WebUntisClient fallback, CancellationToken cancellationToken) =>
WithWebUntisClient(user, connections, fallback, client => client.GetTimeGridAsync(cancellationToken)));
group.MapGet("/substitutions", ([FromQuery] int startDate, [FromQuery] int endDate,
[FromQuery] int? departmentId, WebUntisClient client, CancellationToken cancellationToken) =>
[FromQuery] int? departmentId, ClaimsPrincipal user, WebUntisConnectionStore connections,
WebUntisClient fallback, CancellationToken cancellationToken) =>
{
if (!ValidDateRange(startDate, endDate, 31, out var error))
return Task.FromResult<IResult>(Results.BadRequest(error));
return WebUntisResult(() =>
client.GetSubstitutionsAsync(startDate, endDate, departmentId, cancellationToken));
return WithWebUntisClient(user, connections, fallback,
client => client.GetSubstitutionsAsync(startDate, endDate, departmentId, cancellationToken));
});
group.MapGet("/timetable", ([FromQuery] string elementType, [FromQuery] int elementId,
[FromQuery] int startDate, [FromQuery] int endDate, WebUntisClient client,
CancellationToken cancellationToken) =>
[FromQuery] int startDate, [FromQuery] int endDate, ClaimsPrincipal user,
WebUntisConnectionStore connections, WebUntisClient fallback, CancellationToken cancellationToken) =>
{
if (!Enum.TryParse<UntisTimetableElementType>(elementType, true, out var parsedType) ||
!Enum.IsDefined(parsedType))
@@ -292,40 +326,51 @@ public static class Endpoints
return Task.FromResult<IResult>(Results.BadRequest("elementId muss größer als 0 sein."));
if (!ValidDateRange(startDate, endDate, 62, out var error))
return Task.FromResult<IResult>(Results.BadRequest(error));
return WebUntisResult(() =>
client.GetTimetableAsync(parsedType, elementId, startDate, endDate, cancellationToken));
return WithWebUntisClient(user, connections, fallback,
client => client.GetTimetableAsync(parsedType, elementId, startDate, endDate, cancellationToken));
});
group.MapGet("/students/{studentKey:int}/absences", (int studentKey, [FromQuery] int startDate,
[FromQuery] int endDate, WebUntisClient client, CancellationToken cancellationToken) =>
[FromQuery] int endDate, ClaimsPrincipal user, WebUntisConnectionStore connections,
WebUntisClient fallback, CancellationToken cancellationToken) =>
{
if (studentKey <= 0)
return Task.FromResult<IResult>(Results.BadRequest("studentKey muss größer als 0 sein."));
if (!ValidDateRange(startDate, endDate, 400, out var error))
return Task.FromResult<IResult>(Results.BadRequest(error));
return WebUntisResult(() =>
client.GetStudentAbsencesAsync(studentKey, startDate, endDate, cancellationToken));
return WithWebUntisClient(user, connections, fallback,
client => client.GetStudentAbsencesAsync(studentKey, startDate, endDate, cancellationToken));
});
group.MapGet("/students/{studentId:int}/class-register-entries", (int studentId,
[FromQuery] int startDate, [FromQuery] int endDate, WebUntisClient client,
CancellationToken cancellationToken) =>
[FromQuery] int startDate, [FromQuery] int endDate, ClaimsPrincipal user,
WebUntisConnectionStore connections, WebUntisClient fallback, CancellationToken cancellationToken) =>
{
if (studentId <= 0)
return Task.FromResult<IResult>(Results.BadRequest("studentId muss größer als 0 sein."));
if (!ValidDateRange(startDate, endDate, 400, out var error))
return Task.FromResult<IResult>(Results.BadRequest(error));
return WebUntisResult(() =>
client.GetClassRegisterEntriesAsync(studentId, startDate, endDate, cancellationToken));
return WithWebUntisClient(user, connections, fallback,
client => client.GetClassRegisterEntriesAsync(studentId, startDate, endDate, cancellationToken));
});
group.MapGet("/class-register/categories", (WebUntisClient client,
CancellationToken cancellationToken) =>
WebUntisResult(() => client.GetClassRegisterCategoriesAsync(cancellationToken)));
group.MapGet("/class-register/categories", (ClaimsPrincipal user,
WebUntisConnectionStore connections, WebUntisClient fallback, CancellationToken cancellationToken) =>
WithWebUntisClient(user, connections, fallback,
client => client.GetClassRegisterCategoriesAsync(cancellationToken)));
group.MapGet("/class-register/category-groups", (WebUntisClient client,
CancellationToken cancellationToken) =>
WebUntisResult(() => client.GetClassRegisterCategoryGroupsAsync(cancellationToken)));
group.MapGet("/class-register/category-groups", (ClaimsPrincipal user,
WebUntisConnectionStore connections, WebUntisClient fallback, CancellationToken cancellationToken) =>
WithWebUntisClient(user, connections, fallback,
client => client.GetClassRegisterCategoryGroupsAsync(cancellationToken)));
}
private static Task<IResult> WithWebUntisClient<T>(ClaimsPrincipal user,
WebUntisConnectionStore connections, WebUntisClient fallback, Func<WebUntisClient, Task<T>> operation)
{
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (uid is null) return Task.FromResult<IResult>(Results.Unauthorized());
return WebUntisResult(() => operation(connections.GetClient(uid) ?? fallback));
}
private static async Task<IResult> WebUntisResult<T>(Func<Task<T>> operation)