using Budget.Api.Services; using Budget.Core.DTOs; using Budget.Core.Models; using Budget.Infrastructure.Data; using Budget.Infrastructure.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; using Microsoft.EntityFrameworkCore; using BudgetEntity = Budget.Core.Models.Budget; namespace Budget.Api.Controllers; [ApiController] [Route("api/[controller]")] [Authorize] public class BudgetsController(AppDbContext db, BudgetAuthorizationService authz) : ControllerBase { private IActionResult? TryGetUserId(out string userId) { var id = User.GetUserId(); if (id is null) { userId = string.Empty; return Unauthorized(); } userId = id; return null; } [HttpGet] public async Task List() { if (TryGetUserId(out var userId) is { } err) return err; var budgets = await db.Budgets .Where(b => b.OwnerUserId == userId || b.Shares.Any(s => s.SharedWithUserId == userId && !s.IsPending)) .Select(b => new BudgetDto(b.Id, b.Name, b.EffectiveTaxRate, b.CreatedAt, b.UpdatedAt)) .ToListAsync(); return Ok(budgets); } [HttpPost] [EnableRateLimiting("writes")] public async Task Create([FromBody] CreateBudgetRequest req) { if (TryGetUserId(out var userId) is { } err) return err; var budget = new BudgetEntity { Id = Guid.NewGuid(), Name = req.Name, OwnerUserId = userId, CreatedAt = DateTimeOffset.UtcNow, UpdatedAt = DateTimeOffset.UtcNow, }; db.Budgets.Add(budget); await db.SaveChangesAsync(); return CreatedAtAction(nameof(Get), new { id = budget.Id }, new BudgetDto(budget.Id, budget.Name, budget.EffectiveTaxRate, budget.CreatedAt, budget.UpdatedAt)); } [HttpGet("{id:guid}")] public async Task Get(Guid id) { if (TryGetUserId(out var userId) is { } err) return err; if (!await authz.CanReadAsync(id, userId)) return Forbid(); var b = await db.Budgets.FindAsync(id); if (b is null) return NotFound(); return Ok(new BudgetDto(b.Id, b.Name, b.EffectiveTaxRate, b.CreatedAt, b.UpdatedAt)); } [HttpPut("{id:guid}")] [EnableRateLimiting("writes")] public async Task Update(Guid id, [FromBody] UpdateBudgetRequest req) { if (TryGetUserId(out var userId) is { } err) return err; if (!await authz.CanWriteAsync(id, userId)) return Forbid(); var b = await db.Budgets.FindAsync(id); if (b is null) return NotFound(); b.Name = req.Name; b.UpdatedAt = DateTimeOffset.UtcNow; await db.SaveChangesAsync(); return Ok(new BudgetDto(b.Id, b.Name, b.EffectiveTaxRate, b.CreatedAt, b.UpdatedAt)); } [HttpDelete("{id:guid}")] [EnableRateLimiting("writes")] public async Task Delete(Guid id) { if (TryGetUserId(out var userId) is { } err) return err; if (!await authz.IsOwnerAsync(id, userId)) return Forbid(); var b = await db.Budgets.FindAsync(id); if (b is null) return NotFound(); db.Budgets.Remove(b); await db.SaveChangesAsync(); return NoContent(); } }