init 1.0.0
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Sync.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace LehrerApp.Api;
|
||||
|
||||
public static class Endpoints
|
||||
{
|
||||
// ── Auth ──────────────────────────────────────────────────────────────────
|
||||
|
||||
public static void MapAuthEndpoints(this WebApplication app, string secret)
|
||||
{
|
||||
app.MapPost("/api/auth/login", (LoginRequest req) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(req.Username) || string.IsNullOrWhiteSpace(req.Password))
|
||||
return Results.Unauthorized();
|
||||
// TODO: Passwort gegen DB prüfen
|
||||
return Results.Ok(new { token = Jwt(req.Username, secret), userId = req.Username });
|
||||
});
|
||||
|
||||
app.MapPost("/api/auth/register", (RegisterRequest req) =>
|
||||
{
|
||||
if (req.Password.Length < 12)
|
||||
return Results.BadRequest("Passwort mind. 12 Zeichen.");
|
||||
// TODO: User anlegen, Passwort hashen (BCrypt)
|
||||
return Results.Ok(new { token = Jwt(req.Username, secret), userId = req.Username });
|
||||
});
|
||||
}
|
||||
|
||||
// ── Sync ──────────────────────────────────────────────────────────────────
|
||||
|
||||
public static void MapSyncEndpoints(this WebApplication app)
|
||||
{
|
||||
var g = app.MapGroup("/api/sync").RequireAuthorization();
|
||||
g.MapPost("/push", ([FromBody] List<SyncEvent> events,
|
||||
ClaimsPrincipal user, EventStore store) =>
|
||||
{
|
||||
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
return uid is null ? Results.Unauthorized() : Results.Ok(store.Push(uid, events));
|
||||
});
|
||||
g.MapGet("/pull", ([FromQuery] long since, [FromQuery] string deviceId,
|
||||
ClaimsPrincipal user, EventStore store) =>
|
||||
{
|
||||
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
return uid is null ? Results.Unauthorized() : Results.Ok(store.Pull(uid, since, deviceId));
|
||||
});
|
||||
g.MapGet("/status", (ClaimsPrincipal user) =>
|
||||
{
|
||||
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
return uid is null ? Results.Unauthorized() : Results.Ok(new { userId = uid, timestamp = DateTime.UtcNow });
|
||||
});
|
||||
}
|
||||
|
||||
// ── Snapshot (Device-Pairing) ─────────────────────────────────────────────
|
||||
|
||||
public static void MapSnapshotEndpoints(this WebApplication app)
|
||||
{
|
||||
var g = app.MapGroup("/api/snapshot").RequireAuthorization();
|
||||
g.MapPost("/upload", ([FromBody] SnapshotUploadRequest req,
|
||||
ClaimsPrincipal user, SnapshotStore store) =>
|
||||
{
|
||||
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
return uid is null ? Results.Unauthorized() : Results.Ok(store.Store(uid, req));
|
||||
});
|
||||
g.MapGet("/{code}", (string code, ClaimsPrincipal user, SnapshotStore store) =>
|
||||
{
|
||||
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (uid is null) return Results.Unauthorized();
|
||||
var result = store.Retrieve(uid, code);
|
||||
return result is null
|
||||
? Results.NotFound("Snapshot nicht gefunden, abgelaufen oder bereits verwendet.")
|
||||
: Results.Ok(result);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Readable Snapshot (WebApp) ────────────────────────────────────────────
|
||||
|
||||
public static void MapReadableSnapshotEndpoints(this WebApplication app)
|
||||
{
|
||||
var g = app.MapGroup("/api/snapshot/readable").RequireAuthorization();
|
||||
g.MapPost("/", ([FromBody] ReadableSnapshot snap,
|
||||
ClaimsPrincipal user, ReadableSnapshotStore store) =>
|
||||
{
|
||||
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (uid is null) return Results.Unauthorized();
|
||||
snap.ExportedAt = DateTime.UtcNow;
|
||||
store.Store(uid, snap);
|
||||
return Results.Ok(new { exportedAt = snap.ExportedAt,
|
||||
studentCount = snap.Meta.StudentCount });
|
||||
});
|
||||
g.MapGet("/", (ClaimsPrincipal user, ReadableSnapshotStore store) =>
|
||||
{
|
||||
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (uid is null) return Results.Unauthorized();
|
||||
var snap = store.Load(uid);
|
||||
return snap is null ? Results.NotFound("Kein Snapshot vorhanden.") : Results.Ok(snap);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Plain Sync (WebApp schreibt Events) ───────────────────────────────────
|
||||
|
||||
public static void MapPlainSyncEndpoints(this WebApplication app)
|
||||
{
|
||||
var g = app.MapGroup("/api/sync/plain").RequireAuthorization();
|
||||
g.MapPost("/push", ([FromBody] List<PlainSyncEvent> events,
|
||||
ClaimsPrincipal user, PlainEventStore store) =>
|
||||
{
|
||||
var uid = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
return uid is null ? Results.Unauthorized() : Results.Ok(store.Push(uid, events));
|
||||
});
|
||||
}
|
||||
|
||||
// ── JWT ───────────────────────────────────────────────────────────────────
|
||||
|
||||
private static string Jwt(string userId, string secret)
|
||||
{
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
var token = new JwtSecurityToken(
|
||||
claims: [new(ClaimTypes.NameIdentifier, userId),
|
||||
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())],
|
||||
expires: DateTime.UtcNow.AddDays(30),
|
||||
signingCredentials: creds);
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
public record LoginRequest(string Username, string Password);
|
||||
public record RegisterRequest(string Username, string Password, string DisplayName);
|
||||
Reference in New Issue
Block a user