Sube carpetas API, API.Tests, Application y Domain del Backend

This commit is contained in:
FrancoRu
2026-09-18 13:35:59 -03:00
parent bbb09554bb
commit 2eb4eeef03
572 changed files with 56405 additions and 0 deletions
@@ -0,0 +1,37 @@
using Application.Interfaces.Backup;
using Microsoft.AspNetCore.Http;
using System;
using System.Threading.Tasks;
namespace API.Utils.Middlewares;
/// <summary>
/// Returns 503 for every request outside the allow-listed paths while maintenance mode is active.
/// </summary>
public class MaintenanceModeMiddleware(RequestDelegate next, IMaintenanceModeState maintenanceModeState)
{
private static readonly string[] AllowedPaths =
[
"/health",
"/api/maintenance",
];
public async Task InvokeAsync(HttpContext context)
{
if (maintenanceModeState.IsActive && !Array.Exists(AllowedPaths, p => context.Request.Path.StartsWithSegments(p)))
{
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
context.Response.Headers.RetryAfter = "30";
await context.Response.WriteAsJsonAsync(new
{
error = "The application is temporarily in maintenance mode.",
reason = maintenanceModeState.Reason,
});
return;
}
await next(context);
}
}
@@ -0,0 +1,39 @@
using Application.Utils.Constants.Auth;
using Microsoft.AspNetCore.Http;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
namespace API.Utils.Middlewares;
/// <summary>
/// Rejects every request except the user-update and logout endpoints with 403 once CustomClaimTypes.MustChangePassword is set on the caller's token, until the password is changed.
/// </summary>
public class MustChangePasswordMiddleware(RequestDelegate next)
{
private static readonly string[] AllowedPaths =
[
"/api/users/",
"/api/auth/logout",
];
public async Task InvokeAsync(HttpContext context)
{
bool mustChange = context.User.FindFirstValue(CustomClaimTypes.MustChangePassword) == "true";
if (mustChange && !Array.Exists(AllowedPaths, p => context.Request.Path.StartsWithSegments(p)))
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
await context.Response.WriteAsJsonAsync(new
{
error = "Password change required.",
action = "PUT /api/users/{your-id}/password"
});
return;
}
await next(context);
}
}
@@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Threading.Tasks;
namespace API.Utils.Middlewares;
/// <summary>
/// Logs one compact, structured line per HTTP request: method, path, status code, elapsed time, and correlation id.
/// </summary>
public class RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
private const string LogTemplate =
"HTTP {Method} {PathAndQuery} responded {StatusCode} in {ElapsedMs} ms [{CorrelationId}]";
private const string HttpMethodOptions = "OPTIONS";
/// <summary>
/// Times the request, invokes the pipeline, and logs the outcome at a level matched to the response status code.
/// </summary>
/// <param name="context">The current HTTP context.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task InvokeAsync(HttpContext context)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
bool isPreflight = context.Request.Method == HttpMethodOptions;
int statusCode = context.Response.StatusCode;
LogLevel level = statusCode switch
{
_ when isPreflight => LogLevel.Debug,
>= 500 => LogLevel.Error,
>= 400 => LogLevel.Warning,
_ => LogLevel.Information,
};
logger.Log(
level,
LogTemplate,
context.Request.Method,
context.Request.Path + context.Request.QueryString,
statusCode,
stopwatch.ElapsedMilliseconds,
context.TraceIdentifier);
}
}