namespace Health.WebApi.Endpoints; /// /// 饮食、用药、报告、问诊、运动、文件端点 /// public static class RemainingEndpoints { public static void MapDietEndpoints(this WebApplication app) { var group = app.MapGroup("/api/diet-records").RequireAuthorization(); group.MapGet("/", async (string? date, string? mealType, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var query = db.DietRecords.Include(d => d.FoodItems).Where(d => d.UserId == userId); if (DateOnly.TryParse(date, out var d)) query = query.Where(r => r.RecordedAt == d); if (Enum.TryParse(mealType, ignoreCase: true, out var mt)) query = query.Where(r => r.MealType == mt); var records = await query.OrderByDescending(r => r.RecordedAt).ToListAsync(ct); return Results.Ok(new { code = 0, data = records, message = (string?)null }); }); group.MapPost("/", async (CreateDietRequest req, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var record = new DietRecord { Id = Guid.NewGuid(), UserId = userId, MealType = req.MealType, TotalCalories = req.TotalCalories, HealthScore = req.HealthScore, RecordedAt = req.RecordedAt ?? DateOnly.FromDateTime(DateTime.Now), }; if (req.FoodItems != null) foreach (var fi in req.FoodItems) record.FoodItems.Add(new DietFoodItem { Id = Guid.NewGuid(), Name = fi.Name, Portion = fi.Portion, Calories = fi.Calories, ProteinGrams = fi.ProteinGrams, CarbsGrams = fi.CarbsGrams, FatGrams = fi.FatGrams, Warning = fi.Warning, SortOrder = fi.SortOrder }); db.DietRecords.Add(record); await db.SaveChangesAsync(ct); return Results.Ok(new { code = 0, data = new { record.Id }, message = (string?)null }); }); group.MapDelete("/{id:guid}", async (Guid id, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var record = await db.DietRecords.FirstOrDefaultAsync(r => r.Id == id && r.UserId == userId, ct); if (record != null) { db.DietRecords.Remove(record); await db.SaveChangesAsync(ct); } return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null }); }); } public static void MapMedicationEndpoints(this WebApplication app) { var group = app.MapGroup("/api/medications").RequireAuthorization(); group.MapGet("/", async (HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var meds = await db.Medications.Where(m => m.UserId == userId).OrderByDescending(m => m.CreatedAt).ToListAsync(ct); return Results.Ok(new { code = 0, data = meds, message = (string?)null }); }); group.MapPost("/", async (CreateMedicationRequest req, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var med = new Medication { Id = Guid.NewGuid(), UserId = userId, Name = req.Name, Dosage = req.Dosage, Frequency = req.Frequency, TimeOfDay = req.TimeOfDay ?? [], StartDate = req.StartDate, EndDate = req.EndDate, IsActive = true, Source = req.Source, }; db.Medications.Add(med); await db.SaveChangesAsync(ct); return Results.Ok(new { code = 0, data = new { med.Id }, message = (string?)null }); }); group.MapPut("/{id:guid}", async (Guid id, CreateMedicationRequest req, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var med = await db.Medications.FirstOrDefaultAsync(m => m.Id == id && m.UserId == userId, ct); if (med == null) return Results.Ok(new { code = 40004, message = "不存在" }); med.Name = req.Name; med.Dosage = req.Dosage; med.Frequency = req.Frequency; med.TimeOfDay = req.TimeOfDay ?? med.TimeOfDay; med.StartDate = req.StartDate; med.EndDate = req.EndDate; med.UpdatedAt = DateTime.UtcNow; await db.SaveChangesAsync(ct); return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null }); }); group.MapDelete("/{id:guid}", async (Guid id, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var med = await db.Medications.FirstOrDefaultAsync(m => m.Id == id && m.UserId == userId, ct); if (med != null) { db.Medications.Remove(med); await db.SaveChangesAsync(ct); } return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null }); }); group.MapPost("/{id:guid}/confirm", async (Guid id, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var log = new MedicationLog { Id = Guid.NewGuid(), MedicationId = id, UserId = userId, Status = MedicationLogStatus.Taken, ScheduledTime = TimeOnly.FromDateTime(DateTime.Now), ConfirmedAt = DateTime.UtcNow, }; db.MedicationLogs.Add(log); await db.SaveChangesAsync(ct); return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null }); }); } public static void MapReportEndpoints(this WebApplication app) { var group = app.MapGroup("/api/reports").RequireAuthorization(); group.MapGet("/", async (HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var reports = await db.Reports.Where(r => r.UserId == userId).OrderByDescending(r => r.CreatedAt).ToListAsync(ct); return Results.Ok(new { code = 0, data = reports, message = (string?)null }); }); group.MapGet("/{id:guid}", async (Guid id, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var report = await db.Reports.FirstOrDefaultAsync(r => r.Id == id && r.UserId == userId, ct); return report == null ? Results.Ok(new { code = 40004, message = "不存在" }) : Results.Ok(new { code = 0, data = report, message = (string?)null }); }); } public static void MapConsultationEndpoints(this WebApplication app) { var group = app.MapGroup("/api").RequireAuthorization(); group.MapGet("/doctors", async (AppDbContext db) => { var doctors = await db.Doctors.Where(d => d.IsActive).Select(d => new { d.Id, d.Name, d.Title, d.Department, d.Introduction }).ToListAsync(); return Results.Ok(new { code = 0, data = doctors, message = (string?)null }); }); group.MapGet("/consultations", async (HttpContext http, AppDbContext db) => { var userId = GetUserId(http); var consultations = await db.Consultations.Where(c => c.UserId == userId).OrderByDescending(c => c.CreatedAt).ToListAsync(); return Results.Ok(new { code = 0, data = consultations, message = (string?)null }); }); group.MapPost("/consultations", async (CreateConsultationRequest req, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var consultation = new Consultation { Id = Guid.NewGuid(), UserId = userId, DoctorId = req.DoctorId, Status = ConsultationStatus.AiTalking, Month = DateTime.UtcNow.Year * 100 + DateTime.UtcNow.Month, }; db.Consultations.Add(consultation); await db.SaveChangesAsync(ct); return Results.Ok(new { code = 0, data = new { consultation.Id }, message = (string?)null }); }); group.MapGet("/consultations/{id:guid}/messages", async (Guid id, string? after, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var query = db.ConsultationMessages.Where(m => m.ConsultationId == id && m.Consultation.UserId == userId); if (Guid.TryParse(after, out var afterId)) query = query.Where(m => m.Id.CompareTo(afterId) > 0); var messages = await query.OrderBy(m => m.CreatedAt).Take(50).Select(m => new { m.Id, SenderType = m.SenderType.ToString(), m.SenderName, m.Content, m.CreatedAt }).ToListAsync(ct); return Results.Ok(new { code = 0, data = messages, message = (string?)null }); }); group.MapPost("/consultations/{id:guid}/messages", async (Guid id, SendMessageRequest req, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var msg = new ConsultationMessage { Id = Guid.NewGuid(), ConsultationId = id, SenderType = ConsultationSenderType.User, Content = req.Content, SenderName = null, CreatedAt = DateTime.UtcNow }; db.ConsultationMessages.Add(msg); await db.SaveChangesAsync(ct); return Results.Ok(new { code = 0, data = new { msg.Id }, message = (string?)null }); }); group.MapGet("/user/consultation-quota", async (HttpContext http, AppDbContext db) => { var userId = GetUserId(http); var now = DateTime.UtcNow; // 用年月组合值避免跨年问题:202601=2026年1月 var currentPeriod = now.Year * 100 + now.Month; var used = await db.Consultations.CountAsync(c => c.UserId == userId && c.Month == currentPeriod); return Results.Ok(new { code = 0, data = new { total = 3, used, remaining = 3 - used }, message = (string?)null }); }); } public static void MapExerciseEndpoints(this WebApplication app) { var group = app.MapGroup("/api/exercise-plans").RequireAuthorization(); group.MapGet("/current", async (HttpContext http, AppDbContext db) => { var userId = GetUserId(http); var today = DateOnly.FromDateTime(DateTime.Now); var monday = today.AddDays(-(int)today.DayOfWeek + 1); var plan = await db.ExercisePlans.Include(p => p.Items).FirstOrDefaultAsync(p => p.UserId == userId && p.WeekStartDate == monday); if (plan == null) return Results.Ok(new { code = 0, data = (object?)null, message = (string?)null }); return Results.Ok(new { code = 0, data = new { plan.Id, plan.WeekStartDate, plan.CreatedAt, plan.UpdatedAt, items = plan.Items.Select(i => new { i.Id, i.DayOfWeek, i.ExerciseType, i.DurationMinutes, i.IsCompleted, i.CompletedAt, i.IsRestDay }) }, message = (string?)null }); }); group.MapPost("/", async (CreateExercisePlanRequest req, HttpContext http, AppDbContext db, CancellationToken ct) => { var userId = GetUserId(http); var plan = new ExercisePlan { Id = Guid.NewGuid(), UserId = userId, WeekStartDate = req.WeekStartDate }; if (req.Items != null) foreach (var item in req.Items) plan.Items.Add(new ExercisePlanItem { Id = Guid.NewGuid(), DayOfWeek = item.DayOfWeek, ExerciseType = item.ExerciseType, DurationMinutes = item.DurationMinutes, IsRestDay = item.IsRestDay }); db.ExercisePlans.Add(plan); await db.SaveChangesAsync(ct); return Results.Ok(new { code = 0, data = new { plan.Id }, message = (string?)null }); }); group.MapPost("/items/{itemId:guid}/checkin", async (Guid itemId, HttpContext http, AppDbContext db, CancellationToken ct) => { var item = await db.ExercisePlanItems.FindAsync([itemId], ct); if (item == null) return Results.Ok(new { code = 40004, message = "不存在" }); item.IsCompleted = true; item.CompletedAt = DateTime.UtcNow; await db.SaveChangesAsync(ct); return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null }); }); } public static void MapFileEndpoints(this WebApplication app) { var group = app.MapGroup("/api/files").RequireAuthorization(); group.MapPost("/upload", async (HttpRequest request) => { var form = await request.ReadFormAsync(); var files = form.Files; var results = new List(); var uploadsDir = Path.Combine(Directory.GetCurrentDirectory(), "uploads"); Directory.CreateDirectory(uploadsDir); foreach (var file in files) { var fileId = Guid.NewGuid().ToString(); var ext = Path.GetExtension(file.FileName); var filePath = Path.Combine(uploadsDir, $"{fileId}{ext}"); using var stream = new FileStream(filePath, FileMode.Create); await file.CopyToAsync(stream); results.Add(new { id = fileId, name = file.FileName, size = file.Length }); } return Results.Ok(new { code = 0, data = results, message = (string?)null }); }); } private static Guid GetUserId(HttpContext http) => Guid.TryParse(http.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value, out var id) ? id : Guid.Empty; } // ---- 请求 DTO ---- public sealed record CreateDietRequest(MealType MealType, int? TotalCalories, int? HealthScore, DateOnly? RecordedAt, List? FoodItems); public sealed record FoodItemDto(string Name, string? Portion, int? Calories, decimal? ProteinGrams, decimal? CarbsGrams, decimal? FatGrams, string? Warning, int SortOrder); public sealed record CreateMedicationRequest(string Name, string? Dosage, MedicationFrequency Frequency, List? TimeOfDay, DateOnly? StartDate, DateOnly? EndDate, MedicationSource Source); public sealed record CreateConsultationRequest(Guid DoctorId); public sealed record SendMessageRequest(string Content); public sealed class CreateExercisePlanRequest { public DateOnly WeekStartDate { get; init; } public List? Items { get; init; } } public sealed class ExerciseItemDto { public int DayOfWeek { get; init; } public string ExerciseType { get; init; } = ""; public int DurationMinutes { get; init; } public bool IsRestDay { get; init; } }