89 lines
3.5 KiB
C#
89 lines
3.5 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
[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<OfferingEntryHub> _hub;
|
|
|
|
public OfferingEntryController(
|
|
IOfferingSessionService sessions,
|
|
IGivingCategoryService categories,
|
|
IMemberService members,
|
|
IHubContext<OfferingEntryHub> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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);
|
|
}
|
|
}
|