using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using ROLAC.API.DTOs.Expense; using ROLAC.API.Services; namespace ROLAC.API.Controllers; [ApiController] [Route("api/expenses")] [Authorize] public class ExpensesController : ControllerBase { private readonly IExpenseService _svc; public ExpensesController(IExpenseService svc) => _svc = svc; private bool IsFinance() => User.IsInRole("finance") || User.IsInRole("super_admin"); private bool CanViewAll() => IsFinance() || User.IsInRole("pastor"); // User id lives in the "sub" claim (NameClaimType="sub"); NameIdentifier is absent at runtime. private string CurrentUserId() => User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub") ?? ""; [HttpGet] public async Task GetPaged( [FromQuery] int page = 1, [FromQuery] int pageSize = 20, [FromQuery] string? search = null, [FromQuery] int? ministryId = null, [FromQuery] int? categoryGroupId = null, [FromQuery] string? status = null, [FromQuery] DateOnly? from = null, [FromQuery] DateOnly? to = null, [FromQuery] int? subCategoryId = null, [FromQuery] string? statuses = null) { if (!CanViewAll()) return Forbid(); return Ok(await _svc.GetPagedAsync(page, pageSize, search, ministryId, categoryGroupId, status, from, to, subCategoryId, statuses)); } [HttpGet("mine")] public async Task GetMine([FromQuery] string? status = null, [FromQuery] int page = 1, [FromQuery] int pageSize = 20) { return Ok(await _svc.GetMineAsync(CurrentUserId(), status, page, pageSize)); } [HttpGet("{id:int}")] public async Task GetById(int id) { var dto = await _svc.GetByIdAsync(id); if (dto is null) return NotFound(); if (!CanViewAll() && dto.SubmittedBy != CurrentUserId()) return Forbid(); return Ok(dto); } [HttpPost] public async Task Create([FromBody] CreateExpenseRequest r) { try { return Ok(new { id = await _svc.CreateAsync(r, IsFinance()) }); } catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); } } [HttpPut("{id:int}")] public async Task Update(int id, [FromBody] UpdateExpenseRequest r) { try { await _svc.UpdateAsync(id, r, IsFinance()); return NoContent(); } catch (KeyNotFoundException) { return NotFound(); } catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); } } [HttpDelete("{id:int}")] public async Task Delete(int id) { try { await _svc.DeleteAsync(id, IsFinance()); return NoContent(); } catch (KeyNotFoundException) { return NotFound(); } catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); } } [HttpPost("{id:int}/submit")] public async Task Submit(int id) { try { await _svc.SubmitAsync(id); return NoContent(); } catch (KeyNotFoundException) { return NotFound(); } catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); } } [HttpPost("{id:int}/approve")] [Authorize(Roles = "finance,super_admin")] public async Task Approve(int id) { try { await _svc.ApproveAsync(id); return NoContent(); } catch (KeyNotFoundException) { return NotFound(); } catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); } } [HttpPost("{id:int}/reject")] [Authorize(Roles = "finance,super_admin")] public async Task Reject(int id, [FromBody] RejectExpenseRequest r) { try { await _svc.RejectAsync(id, r.ReviewNotes); return NoContent(); } catch (KeyNotFoundException) { return NotFound(); } catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); } } [HttpPost("{id:int}/pay")] [Authorize(Roles = "finance,super_admin")] public async Task Pay(int id, [FromBody] PayExpenseRequest r) { try { await _svc.PayAsync(id, r.CheckNumber, r.PaidAt); return NoContent(); } catch (KeyNotFoundException) { return NotFound(); } catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); } } [HttpPost("{id:int}/receipt")] [RequestSizeLimit(10_485_760)] public async Task UploadReceipt(int id, IFormFile file) { if (file is null || file.Length == 0) return BadRequest(new { message = "No file." }); var allowed = new[] { "image/jpeg", "image/png", "image/webp", "application/pdf" }; if (!allowed.Contains(file.ContentType)) return BadRequest(new { message = "Unsupported file type." }); try { await using var stream = file.OpenReadStream(); await _svc.SaveReceiptAsync(id, stream, file.FileName, IsFinance()); return NoContent(); } catch (KeyNotFoundException) { return NotFound(); } catch (InvalidOperationException ex) { return Conflict(new { message = ex.Message }); } } [HttpGet("{id:int}/receipt")] public async Task GetReceipt(int id) { try { var result = await _svc.OpenReceiptAsync(id, IsFinance()); if (result is null) return NotFound(); return File(result.Value.stream, result.Value.contentType); } catch (KeyNotFoundException) { return NotFound(); } catch (InvalidOperationException) { return Forbid(); } } }