using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using ROLAC.API.DTOs.Giving; using ROLAC.API.DTOs.Members; using ROLAC.API.Hubs; using ROLAC.API.Services; namespace ROLAC.API.Controllers; /// /// Anonymous endpoints powering the mobile Sunday offering-entry page. The page /// has no login yet, so it cannot reach the auth-gated members/categories/ /// offering-sessions APIs — these expose just what it needs (active categories, /// a name-only member typeahead, and append-one-line). /// [ApiController] [Route("api/offering-entry")] [AllowAnonymous] public class OfferingEntryController : ControllerBase { private readonly IOfferingSessionService _sessions; private readonly IGivingCategoryService _categories; private readonly IMemberService _members; private readonly IHubContext _hub; public OfferingEntryController( IOfferingSessionService sessions, IGivingCategoryService categories, IMemberService members, IHubContext hub) { _sessions = sessions; _categories = categories; _members = members; _hub = hub; } // Seed the page in one round-trip: active categories + today's session state. [HttpGet("bootstrap")] public async Task Bootstrap([FromQuery] DateOnly date) => Ok(new OfferingEntryBootstrapDto { SessionDate = date.ToString("yyyy-MM-dd"), Categories = await _categories.GetAllAsync(false), Summary = await _sessions.GetEntrySummaryAsync(date), }); // Name-only member suggestions for the giver typeahead. [HttpGet("members")] public async Task SearchMembers([FromQuery] string? search, [FromQuery] int take = 10) => Ok(await _sessions.SearchMembersForEntryAsync(search, Math.Clamp(take, 1, 25))); // Quick-add a giver who isn't on file yet (created as a Visitor). Reuses the // member service directly — role checks live on MembersController, so this // anonymous path is the intended public entry point for the mobile page. [HttpPost("members")] public async Task QuickAddMember([FromBody] QuickAddMemberRequest request) { var id = await _members.CreateAsync(new CreateMemberRequest { FirstName_en = request.FirstName_en, LastName_en = request.LastName_en, NickName = request.NickName, FirstName_zh = request.FirstName_zh, LastName_zh = request.LastName_zh, PhoneCell = request.PhoneCell, Status = "Visitor", Country = "USA", LanguagePreference = "en", }); return Ok(new MemberTypeaheadDto { Id = id, NickName = request.NickName, FirstName_en = request.FirstName_en, LastName_en = request.LastName_en, }); } // Append one offering line to the date's session (find-or-create), then // broadcast it to everyone viewing that date. [HttpPost("lines")] public async Task AppendLine([FromBody] AppendOfferingLineRequest request) { var result = await _sessions.AppendLineAsync(request.Date, request.Line); await _hub.Clients.Group(result.SessionDate).SendAsync("LineAdded", result); return Ok(result); } }