Private
Public Access
1
0

Fix Reports 400 errors: normalize DateTime to UTC and restore camelCase JSON

Query-bound DateTimes have Kind=Unspecified which Npgsql rejects for
timestamptz columns, causing InvalidOperationException caught as 400.
Also add explicit CamelCase naming policy lost when AddJsonOptions was
introduced for the enum converter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-08 20:35:57 -05:00
parent d49ad4aca7
commit 9497812bf5
2 changed files with 8 additions and 4 deletions
@@ -18,17 +18,20 @@ public class ReportsController : ControllerBase
[HttpGet("spending-by-category")] [HttpGet("spending-by-category")]
public async Task<ActionResult<SpendingByCategoryReport>> SpendingByCategory([FromQuery] DateTime startDate, [FromQuery] DateTime endDate, [FromQuery] Guid? accountId) public async Task<ActionResult<SpendingByCategoryReport>> SpendingByCategory([FromQuery] DateTime startDate, [FromQuery] DateTime endDate, [FromQuery] Guid? accountId)
=> Ok(await _reportService.GetSpendingByCategoryAsync(UserId, startDate, endDate, accountId)); => Ok(await _reportService.GetSpendingByCategoryAsync(UserId, Utc(startDate), Utc(endDate), accountId));
[HttpGet("income-vs-expense")] [HttpGet("income-vs-expense")]
public async Task<ActionResult<IncomeVsExpenseReport>> IncomeVsExpense([FromQuery] DateTime startDate, [FromQuery] DateTime endDate) public async Task<ActionResult<IncomeVsExpenseReport>> IncomeVsExpense([FromQuery] DateTime startDate, [FromQuery] DateTime endDate)
=> Ok(await _reportService.GetIncomeVsExpenseAsync(UserId, startDate, endDate)); => Ok(await _reportService.GetIncomeVsExpenseAsync(UserId, Utc(startDate), Utc(endDate)));
[HttpGet("net-worth")] [HttpGet("net-worth")]
public async Task<ActionResult<NetWorthReport>> NetWorth([FromQuery] DateTime startDate, [FromQuery] DateTime endDate) public async Task<ActionResult<NetWorthReport>> NetWorth([FromQuery] DateTime startDate, [FromQuery] DateTime endDate)
=> Ok(await _reportService.GetNetWorthAsync(UserId, startDate, endDate)); => Ok(await _reportService.GetNetWorthAsync(UserId, Utc(startDate), Utc(endDate)));
[HttpGet("cash-flow")] [HttpGet("cash-flow")]
public async Task<ActionResult<CashFlowReport>> CashFlow([FromQuery] DateTime startDate, [FromQuery] DateTime endDate) public async Task<ActionResult<CashFlowReport>> CashFlow([FromQuery] DateTime startDate, [FromQuery] DateTime endDate)
=> Ok(await _reportService.GetCashFlowAsync(UserId, startDate, endDate)); => Ok(await _reportService.GetCashFlowAsync(UserId, Utc(startDate), Utc(endDate)));
private static DateTime Utc(DateTime dt) =>
dt.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(dt, DateTimeKind.Utc) : dt;
} }
+1
View File
@@ -105,6 +105,7 @@ builder.Services.AddSignalR();
builder.Services.AddControllers() builder.Services.AddControllers()
.AddJsonOptions(options => .AddJsonOptions(options =>
{ {
options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
}); });
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();