using GerbilManagerWebAPI.Common; using GerbilManagerWebAPI.Dtos; using GerbilManagerWebAPI.Inbox; using GerbilManagerWebAPI.Models; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.EntityFrameworkCore; namespace GerbilManagerWebAPI.Endpoints { /// /// INBOX-0: Gmail request inbox. Sync imports mail into Request rows; the list/detail/triage /// endpoints drive the in-app triage (frontend INBOX-1). LAN-only like the rest of the API. /// public static class RequestEndpoints { public static IEndpointRouteBuilder MapRequestEndpoints(this IEndpointRouteBuilder app) { var api = app.MapGroup("/api").WithTags("Inbox"); // POST /api/requests/sync — fetch from Gmail + import (dedup on Message-Id) api.MapPost("/requests/sync", async (RequestSyncService sync) => TypedResults.Ok(await sync.SyncAsync())); // GET /api/requests?filter=status==New&orderBy=receivedAt desc (Gridify paged) api.MapGet("/requests", async ([Microsoft.AspNetCore.Http.AsParameters] GridifyParams query, ApplicationContext db) => TypedResults.Ok(await db.Requests.AsNoTracking() .ToPagedResultAsync(query, ToDto))); api.MapGet("/requests/{id:guid}", async Task, NotFound>> (Guid id, ApplicationContext db) => { var r = await db.Requests.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id); return r is null ? TypedResults.NotFound() : TypedResults.Ok(ToDto(r)); }); // PUT /api/requests/{id} — triage: status and/or assigned contact api.MapPut("/requests/{id:guid}", async Task>> ( Guid id, RequestTriageInput input, ApplicationContext db) => { var r = await db.Requests.FirstOrDefaultAsync(x => x.Id == id); if (r is null) return TypedResults.NotFound(); if (input.AssignedContactId is Guid cid && !await db.Contacts.AnyAsync(c => c.Id == cid)) return TypedResults.BadRequest("Assigned contact does not exist."); r.AssignedContactId = input.AssignedContactId; if (input.Status is RequestStatus s) { r.Status = s; if (s == RequestStatus.Answered) r.AnsweredAt ??= DateTimeOffset.UtcNow; } await db.SaveChangesAsync(); return TypedResults.NoContent(); }); // GET/PUT /api/mail-settings — App Password never leaves the server api.MapGet("/mail-settings", async (MailSettingsService svc) => { var s = await svc.GetAsync(); return TypedResults.Ok(new MailSettingsDto( s.GmailAddress, s.PollIntervalMinutes, s.Folder, s.BackgroundPollEnabled, svc.HasAppPassword(s))); }); api.MapPut("/mail-settings", async (MailSettingsInput input, MailSettingsService svc, ApplicationContext db) => { var s = await svc.GetAsync(); if (input.GmailAddress is not null) s.GmailAddress = string.IsNullOrWhiteSpace(input.GmailAddress) ? null : input.GmailAddress.Trim(); if (input.PollIntervalMinutes is int m) s.PollIntervalMinutes = m; if (input.Folder is not null) s.Folder = string.IsNullOrWhiteSpace(input.Folder) ? "INBOX" : input.Folder.Trim(); if (input.BackgroundPollEnabled is bool b) s.BackgroundPollEnabled = b; // AppPassword: null => leave unchanged; "" => clear; value => set (encrypted) if (input.AppPassword is not null) svc.SetPassword(s, input.AppPassword); await db.SaveChangesAsync(); return TypedResults.NoContent(); }); return app; } private static RequestDto ToDto(Request r) => new( r.Id, r.GmailMessageId, r.ThreadId, r.FromAddress, r.FromName, r.Subject, r.BodyText, r.ReceivedAt, r.Status, r.AssignedContactId, r.DraftReply, r.AnsweredAt); } }