From 4877fec1daf158febf21ec70e23cc0dbdc97cba0 Mon Sep 17 00:00:00 2001 From: Chris Chen Date: Thu, 25 Jun 2026 15:01:38 -0700 Subject: [PATCH] feat(expense-snapshot): REST controller + DI registration --- .../Controllers/ExpenseSnapshotsController.cs | 68 +++++++++++++++++++ API/ROLAC.API/Program.cs | 1 + 2 files changed, 69 insertions(+) create mode 100644 API/ROLAC.API/Controllers/ExpenseSnapshotsController.cs diff --git a/API/ROLAC.API/Controllers/ExpenseSnapshotsController.cs b/API/ROLAC.API/Controllers/ExpenseSnapshotsController.cs new file mode 100644 index 0000000..61b24e8 --- /dev/null +++ b/API/ROLAC.API/Controllers/ExpenseSnapshotsController.cs @@ -0,0 +1,68 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using ROLAC.API.Authorization; +using ROLAC.API.DTOs.Expense; +using ROLAC.API.Services; + +namespace ROLAC.API.Controllers; + +// Snapshots are reusable vendor-payment templates — a finance tool. Every action requires +// Expenses:Write (super_admin bypasses), matching who can create vendor payments. +[ApiController] +[Route("api/expense-snapshots")] +[Authorize] +public class ExpenseSnapshotsController : ControllerBase +{ + private readonly IExpenseSnapshotService _svc; + private readonly IPermissionService _perms; + public ExpenseSnapshotsController(IExpenseSnapshotService svc, IPermissionService perms) + { + _svc = svc; + _perms = perms; + } + + private List Roles() => User.FindAll("role").Select(claim => claim.Value).ToList(); + private bool IsSuperAdmin() => User.IsInRole(PermissionAuthorizationHandler.SuperAdminRole); + private async Task CanManageAsync() => + IsSuperAdmin() || await _perms.HasPermissionAsync(Roles(), Modules.Expenses, PermissionActions.Write); + + [HttpGet] + public async Task GetAll() + { + if (!await CanManageAsync()) return Forbid(); + return Ok(await _svc.GetAllAsync()); + } + + [HttpGet("{id:int}")] + public async Task GetById(int id) + { + if (!await CanManageAsync()) return Forbid(); + var dto = await _svc.GetByIdAsync(id); + return dto is null ? NotFound() : Ok(dto); + } + + [HttpPost] + public async Task Create([FromBody] CreateExpenseSnapshotRequest r) + { + if (!await CanManageAsync()) return Forbid(); + try { return Ok(new { id = await _svc.CreateAsync(r) }); } + catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); } + } + + [HttpPut("{id:int}")] + public async Task Update(int id, [FromBody] UpdateExpenseSnapshotRequest r) + { + if (!await CanManageAsync()) return Forbid(); + try { await _svc.UpdateAsync(id, r); return NoContent(); } + catch (KeyNotFoundException) { return NotFound(); } + catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); } + } + + [HttpDelete("{id:int}")] + public async Task Delete(int id) + { + if (!await CanManageAsync()) return Forbid(); + try { await _svc.DeleteAsync(id); return NoContent(); } + catch (KeyNotFoundException) { return NotFound(); } + } +} diff --git a/API/ROLAC.API/Program.cs b/API/ROLAC.API/Program.cs index ccc04c8..8f701c3 100644 --- a/API/ROLAC.API/Program.cs +++ b/API/ROLAC.API/Program.cs @@ -153,6 +153,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped();