ac3dcc2f31
Addresses production CPU spike incident. Key changes: - Guard OTel exporter behind OTEL_EXPORTER_OTLP_ENDPOINT env var; filter tracing to /api paths only — unconditional export was primary suspect - Remove /healthz endpoint entirely (unauthenticated, hit DB on every call) - Replace KnownUserMiddleware with POST /api/users/me called once on login from TokenSync — eliminates unconditional DB write on every request - Add DB indexes: (BudgetId, IsDeleted) on Incomes/Outgos, OwnerUserId on Budgets, SharedWithUserId and (IsPending, SharedWithEmail) on BudgetShares - Move UseRateLimiter() before UseStaticFiles() so all requests are throttled - Replace full-array reorder with move-by-position (id + newIndex) — bounded input, fewer DB writes, better API design - Lock ForwardedHeaders to 172.20.0.0/16 subnet; fixes KnownNetworks deprecation warning (0 warnings in build now) - Add AsNoTracking() to all read-only queries in Summary/Incomes/OutgosController - FrequencyCalculator returns 0 for unknown enum values instead of throwing - Thread.Sleep → await Task.Delay in OIDC startup loop - AllowedHosts locked to budget.stwaddle.com Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
50 lines
1.4 KiB
C#
50 lines
1.4 KiB
C#
using Budget.Core.Models;
|
|
using Budget.Infrastructure.Data;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Budget.Api.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/users")]
|
|
[Authorize(Roles = "admin,user")]
|
|
public class UsersController(AppDbContext db) : ControllerBase
|
|
{
|
|
[HttpPost("me")]
|
|
public async Task<IActionResult> RegisterMe()
|
|
{
|
|
var sub = User.FindFirst("sub")?.Value;
|
|
var email = User.FindFirst("email")?.Value;
|
|
var name = User.FindFirst("name")?.Value;
|
|
|
|
if (sub is null || email is null || name is null)
|
|
return Unauthorized();
|
|
|
|
var known = await db.KnownUsers.FindAsync(sub);
|
|
if (known is null)
|
|
{
|
|
db.KnownUsers.Add(new KnownUser { Id = sub, Email = email, Name = name, LastSeenAt = DateTimeOffset.UtcNow });
|
|
}
|
|
else
|
|
{
|
|
known.Email = email;
|
|
known.Name = name;
|
|
known.LastSeenAt = DateTimeOffset.UtcNow;
|
|
}
|
|
|
|
var pending = await db.BudgetShares
|
|
.Where(s => s.IsPending && s.SharedWithEmail == email)
|
|
.ToListAsync();
|
|
|
|
foreach (var share in pending)
|
|
{
|
|
share.SharedWithUserId = sub;
|
|
share.IsPending = false;
|
|
}
|
|
|
|
await db.SaveChangesAsync();
|
|
return NoContent();
|
|
}
|
|
}
|