First commit. Include junk from template to remove

This commit is contained in:
Dominic Villemure
2024-03-09 20:25:30 -05:00
commit bbcefcf76f
140 changed files with 8151 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
using Hutopy.Application.Common.Exceptions;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
namespace Hutopy.Web.Infrastructure;
public class CustomExceptionHandler : IExceptionHandler
{
private readonly Dictionary<Type, Func<HttpContext, Exception, Task>> _exceptionHandlers;
public CustomExceptionHandler()
{
// Register known exception types and handlers.
_exceptionHandlers = new()
{
{ typeof(ValidationException), HandleValidationException },
{ typeof(NotFoundException), HandleNotFoundException },
{ typeof(UnauthorizedAccessException), HandleUnauthorizedAccessException },
{ typeof(ForbiddenAccessException), HandleForbiddenAccessException },
};
}
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
var exceptionType = exception.GetType();
if (_exceptionHandlers.ContainsKey(exceptionType))
{
await _exceptionHandlers[exceptionType].Invoke(httpContext, exception);
return true;
}
return false;
}
private async Task HandleValidationException(HttpContext httpContext, Exception ex)
{
var exception = (ValidationException)ex;
httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
await httpContext.Response.WriteAsJsonAsync(new ValidationProblemDetails(exception.Errors)
{
Status = StatusCodes.Status400BadRequest,
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
});
}
private async Task HandleNotFoundException(HttpContext httpContext, Exception ex)
{
var exception = (NotFoundException)ex;
httpContext.Response.StatusCode = StatusCodes.Status404NotFound;
await httpContext.Response.WriteAsJsonAsync(new ProblemDetails()
{
Status = StatusCodes.Status404NotFound,
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Title = "The specified resource was not found.",
Detail = exception.Message
});
}
private async Task HandleUnauthorizedAccessException(HttpContext httpContext, Exception ex)
{
httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
await httpContext.Response.WriteAsJsonAsync(new ProblemDetails
{
Status = StatusCodes.Status401Unauthorized,
Title = "Unauthorized",
Type = "https://tools.ietf.org/html/rfc7235#section-3.1"
});
}
private async Task HandleForbiddenAccessException(HttpContext httpContext, Exception ex)
{
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
await httpContext.Response.WriteAsJsonAsync(new ProblemDetails
{
Status = StatusCodes.Status403Forbidden,
Title = "Forbidden",
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.3"
});
}
}

View File

@@ -0,0 +1,6 @@
namespace Hutopy.Web.Infrastructure;
public abstract class EndpointGroupBase
{
public abstract void Map(WebApplication app);
}

View File

@@ -0,0 +1,46 @@
using System.Diagnostics.CodeAnalysis;
namespace Hutopy.Web.Infrastructure;
public static class IEndpointRouteBuilderExtensions
{
public static IEndpointRouteBuilder MapGet(this IEndpointRouteBuilder builder, Delegate handler, [StringSyntax("Route")] string pattern = "")
{
Guard.Against.AnonymousMethod(handler);
builder.MapGet(pattern, handler)
.WithName(handler.Method.Name);
return builder;
}
public static IEndpointRouteBuilder MapPost(this IEndpointRouteBuilder builder, Delegate handler, [StringSyntax("Route")] string pattern = "")
{
Guard.Against.AnonymousMethod(handler);
builder.MapPost(pattern, handler)
.WithName(handler.Method.Name);
return builder;
}
public static IEndpointRouteBuilder MapPut(this IEndpointRouteBuilder builder, Delegate handler, [StringSyntax("Route")] string pattern)
{
Guard.Against.AnonymousMethod(handler);
builder.MapPut(pattern, handler)
.WithName(handler.Method.Name);
return builder;
}
public static IEndpointRouteBuilder MapDelete(this IEndpointRouteBuilder builder, Delegate handler, [StringSyntax("Route")] string pattern)
{
Guard.Against.AnonymousMethod(handler);
builder.MapDelete(pattern, handler)
.WithName(handler.Method.Name);
return builder;
}
}

View File

@@ -0,0 +1,18 @@
using System.Reflection;
namespace Hutopy.Web.Infrastructure;
public static class MethodInfoExtensions
{
public static bool IsAnonymous(this MethodInfo method)
{
var invalidChars = new[] { '<', '>' };
return method.Name.Any(invalidChars.Contains);
}
public static void AnonymousMethod(this IGuardClause guardClause, Delegate input)
{
if (input.Method.IsAnonymous())
throw new ArgumentException("The endpoint name must be specified when using anonymous handlers.");
}
}

View File

@@ -0,0 +1,37 @@
using System.Reflection;
namespace Hutopy.Web.Infrastructure;
public static class WebApplicationExtensions
{
public static RouteGroupBuilder MapGroup(this WebApplication app, EndpointGroupBase group)
{
var groupName = group.GetType().Name;
return app
.MapGroup($"/api/{groupName}")
.WithGroupName(groupName)
.WithTags(groupName)
.WithOpenApi();
}
public static WebApplication MapEndpoints(this WebApplication app)
{
var endpointGroupType = typeof(EndpointGroupBase);
var assembly = Assembly.GetExecutingAssembly();
var endpointGroupTypes = assembly.GetExportedTypes()
.Where(t => t.IsSubclassOf(endpointGroupType));
foreach (var type in endpointGroupTypes)
{
if (Activator.CreateInstance(type) is EndpointGroupBase instance)
{
instance.Map(app);
}
}
return app;
}
}