using Health.Application.Medications; namespace Health.WebApi.Endpoints; public static class MedicationEndpoints { public static void MapMedicationEndpoints(this WebApplication app) { var group = app.MapGroup("/api/medications").RequireAuthorization(); group.MapGet("/", async (string? filter, HttpContext http, IMedicationService medications, CancellationToken ct) => { var userId = GetUserId(http); var meds = await medications.ListAsync(userId, filter, ct); return Results.Ok(new { code = 0, data = meds, message = (string?)null }); }); group.MapPost("/", async (HttpRequest request, HttpContext http, IMedicationService medications, CancellationToken ct) => { var userId = GetUserId(http); using var json = JsonDocument.Parse(await ReadBodyAsync(request, ct)); var medId = await medications.CreateAsync(userId, ReadCreateRequest(json.RootElement), ct); return Results.Ok(new { code = 0, data = new { id = medId }, message = (string?)null }); }); group.MapPut("/{id:guid}", async (Guid id, HttpRequest request, HttpContext http, IMedicationService medications, CancellationToken ct) => { var userId = GetUserId(http); using var json = JsonDocument.Parse(await ReadBodyAsync(request, ct)); var updated = await medications.UpdateAsync(userId, id, ReadPatchRequest(json.RootElement), ct); if (!updated) return Results.Ok(new { code = 40004, message = "不存在" }); return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null }); }); group.MapDelete("/{id:guid}", async (Guid id, HttpContext http, IMedicationService medications, CancellationToken ct) => { var userId = GetUserId(http); var deleted = await medications.DeleteAsync(userId, id, ct); return deleted ? Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null }) : Results.Ok(new { code = 40004, data = (object?)null, message = "用药记录不存在" }); }); group.MapPost("/{id:guid}/confirm", async (Guid id, HttpContext http, IMedicationService medications, CancellationToken ct) => { var userId = GetUserId(http); var taken = await medications.ToggleTodayTakenAsync(userId, id, ct); if (taken == null) return Results.Ok(new { code = 404, message = "药品不存在" }); return Results.Ok(new { code = 0, data = new { taken }, message = (string?)null }); }); group.MapGet("/reminders", async (HttpContext http, IMedicationService medications, CancellationToken ct) => { var userId = GetUserId(http); var reminders = await medications.GetRemindersAsync(userId, ct); return Results.Ok(new { code = 0, data = reminders, message = (string?)null }); }); group.MapPost("/{id:guid}/confirm-dose", async (Guid id, HttpContext http, IMedicationService medications, CancellationToken ct) => { var userId = GetUserId(http); using var json = JsonDocument.Parse(await ReadBodyAsync(http.Request, ct)); var timeStr = json.RootElement.GetProperty("scheduledTime").GetString()!; var statusStr = json.RootElement.TryGetProperty("status", out var st) ? st.GetString() : "taken"; var scheduledTime = TimeOnly.Parse(timeStr); var status = statusStr == "taken" ? MedicationLogStatus.Taken : MedicationLogStatus.Skipped; var result = await medications.ConfirmDoseAsync(userId, id, scheduledTime, status, ct); if (result == null) return Results.Ok(new { code = 404, message = "药品不存在" }); if (result == false) return Results.Ok(new { code = 0, data = new { success = false, message = "已打卡" }, message = (string?)null }); return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null }); }); group.MapDelete("/{id:guid}/confirm-dose/{time}", async (Guid id, string time, HttpContext http, IMedicationService medications, CancellationToken ct) => { var userId = GetUserId(http); var cancelled = await medications.CancelDoseAsync(userId, id, TimeOnly.Parse(time), ct); return cancelled ? Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null }) : Results.Ok(new { code = 40004, data = (object?)null, message = "未找到对应的服药打卡记录" }); }); } private static MedicationUpsertRequest ReadCreateRequest(JsonElement root) { var frequency = Enum.TryParse( root.TryGetProperty("frequency", out var f) ? f.GetString() : "Daily", out var freq) ? freq : MedicationFrequency.Daily; var source = Enum.TryParse( root.TryGetProperty("source", out var s) ? s.GetString() : "Manual", out var src) ? src : MedicationSource.Manual; return new MedicationUpsertRequest( root.GetProperty("name").GetString()!, root.TryGetProperty("dosage", out var dg) ? dg.GetString() : null, frequency, ReadTimes(root, "timeOfDay"), root.TryGetProperty("startDate", out var sd) && DateOnly.TryParse(sd.GetString(), out var startDate) ? startDate : null, root.TryGetProperty("endDate", out var ed) && DateOnly.TryParse(ed.GetString(), out var endDate) ? endDate : null, source, root.TryGetProperty("notes", out var nt) ? nt.GetString() : null); } private static MedicationPatchRequest ReadPatchRequest(JsonElement root) { MedicationFrequency? frequency = null; if (root.TryGetProperty("frequency", out var f) && Enum.TryParse(f.GetString(), out var freq)) frequency = freq; var hasEndDate = root.TryGetProperty("endDate", out var ed); var clearEndDate = hasEndDate && (ed.ValueKind == JsonValueKind.Null || string.IsNullOrWhiteSpace(ed.GetString())); return new MedicationPatchRequest( root.TryGetProperty("name", out var n) ? n.GetString() : null, root.TryGetProperty("dosage", out var dg) ? dg.GetString() : null, frequency, root.TryGetProperty("timeOfDay", out _) ? ReadTimes(root, "timeOfDay") : null, root.TryGetProperty("startDate", out var sd) && DateOnly.TryParse(sd.GetString(), out var startDate) ? startDate : null, hasEndDate && !clearEndDate && DateOnly.TryParse(ed.GetString(), out var endDate) ? endDate : null, root.TryGetProperty("notes", out var nt) ? nt.GetString() : null, clearEndDate); } private static List ReadTimes(JsonElement root, string propertyName) { if (!root.TryGetProperty(propertyName, out var tod) || tod.ValueKind != JsonValueKind.Array) return []; return tod.EnumerateArray() .Select(t => TimeOnly.TryParse(t.GetString(), out var parsed) ? parsed : (TimeOnly?)null) .Where(t => t.HasValue) .Select(t => t!.Value) .ToList(); } private static async Task ReadBodyAsync(HttpRequest request, CancellationToken ct) { request.EnableBuffering(); using var reader = new StreamReader(request.Body, leaveOpen: true); var body = await reader.ReadToEndAsync(ct); request.Body.Position = 0; return string.IsNullOrWhiteSpace(body) ? "{}" : body; } private static Guid GetUserId(HttpContext http) => Guid.TryParse(http.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value, out var id) ? id : Guid.Empty; }