feat(api): add CategoriesController with list, mappings, and add-mapping endpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MoneyMap.Data;
|
||||||
|
using MoneyMap.Models;
|
||||||
|
using MoneyMap.Services;
|
||||||
|
|
||||||
|
namespace MoneyMap.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class CategoriesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly MoneyMapContext _db;
|
||||||
|
private readonly ITransactionCategorizer _categorizer;
|
||||||
|
private readonly IMerchantService _merchantService;
|
||||||
|
|
||||||
|
public CategoriesController(MoneyMapContext db, ITransactionCategorizer categorizer, IMerchantService merchantService)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_categorizer = categorizer;
|
||||||
|
_merchantService = merchantService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> List()
|
||||||
|
{
|
||||||
|
var categories = await _db.Transactions
|
||||||
|
.Where(t => t.Category != null && t.Category != "")
|
||||||
|
.GroupBy(t => t.Category!)
|
||||||
|
.Select(g => new { Category = g.Key, Count = g.Count(), TotalSpent = g.Where(t => t.Amount < 0).Sum(t => Math.Abs(t.Amount)) })
|
||||||
|
.OrderByDescending(x => x.Count)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var uncategorized = await _db.Transactions
|
||||||
|
.CountAsync(t => t.Category == null || t.Category == "");
|
||||||
|
|
||||||
|
return Ok(new { Categories = categories, UncategorizedCount = uncategorized });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("mappings")]
|
||||||
|
public async Task<IActionResult> GetMappings([FromQuery] string? category = null)
|
||||||
|
{
|
||||||
|
var mappings = await _categorizer.GetAllMappingsAsync();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(category))
|
||||||
|
mappings = mappings.Where(m => m.Category.Equals(category, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||||
|
|
||||||
|
var result = mappings.Select(m => new
|
||||||
|
{
|
||||||
|
m.Id,
|
||||||
|
m.Pattern,
|
||||||
|
m.Category,
|
||||||
|
m.MerchantId,
|
||||||
|
m.Priority
|
||||||
|
}).OrderBy(m => m.Category).ThenByDescending(m => m.Priority).ToList();
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("mappings")]
|
||||||
|
public async Task<IActionResult> AddMapping([FromBody] CreateCategoryMappingRequest request)
|
||||||
|
{
|
||||||
|
int? merchantId = null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.MerchantName))
|
||||||
|
merchantId = await _merchantService.GetOrCreateIdAsync(request.MerchantName);
|
||||||
|
|
||||||
|
var mapping = new CategoryMapping
|
||||||
|
{
|
||||||
|
Pattern = request.Pattern,
|
||||||
|
Category = request.Category,
|
||||||
|
MerchantId = merchantId,
|
||||||
|
Priority = request.Priority
|
||||||
|
};
|
||||||
|
|
||||||
|
_db.CategoryMappings.Add(mapping);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(new { Created = true, mapping.Id, mapping.Pattern, mapping.Category, Merchant = request.MerchantName, mapping.Priority });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CreateCategoryMappingRequest
|
||||||
|
{
|
||||||
|
public string Pattern { get; set; } = "";
|
||||||
|
public string Category { get; set; } = "";
|
||||||
|
public string? MerchantName { get; set; }
|
||||||
|
public int Priority { get; set; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user