femto-backend/Femto.Api/Program.cs
2025-07-19 14:10:01 +02:00

144 lines
4.4 KiB
C#

using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Channels;
using Femto.Api;
using Femto.Api.Auth;
using Femto.Api.Infrastructure;
using Femto.Common;
using Femto.Common.Domain;
using Femto.Common.Integration;
using Femto.Modules.Auth.Application;
using Femto.Modules.Blog.Application;
using Femto.Modules.Media.Application;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.WebUtilities;
const string CorsPolicyName = "DefaultCorsPolicy";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var loggerFactory = LoggerFactory.Create(b =>
{
b.SetMinimumLevel(LogLevel.Information)
.AddConfiguration(builder.Configuration.GetSection("Logging"))
.AddConsole()
.AddDebug();
});
builder.Services.AddSingleton(loggerFactory);
builder.Services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
var connectionString = builder.Configuration.GetConnectionString("Database");
if (connectionString is null)
throw new Exception("no database connection string found");
var blobStorageRoot = builder.Configuration.GetValue<string>("BlobStorageRoot");
if (blobStorageRoot is null)
throw new Exception("no blob storage root found");
var eventBus = new EventBus(Channel.CreateUnbounded<IEvent>());
builder.Services.AddHostedService(_ => eventBus);
builder.Services.InitializeBlogModule(connectionString, eventBus, loggerFactory);
builder.Services.InitializeMediaModule(connectionString, blobStorageRoot);
builder.Services.InitializeAuthenticationModule(connectionString, eventBus, loggerFactory, TimeProvider.System);
builder.Services.AddScoped<CurrentUserContext, CurrentUserContext>();
builder.Services.AddScoped<ICurrentUserContext>(s => s.GetRequiredService<CurrentUserContext>());
builder.Services.AddCors(options =>
{
options.AddPolicy(
CorsPolicyName,
b =>
{
b.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins(builder.Configuration.GetValue<string>("CorsOrigins")?.Split(';') ?? [])
.AllowCredentials();
}
);
});
builder
.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
});
builder.Services.Configure<CookieSettings>(
builder.Configuration.GetSection("Cookies"));
builder
.Services.AddAuthentication("SessionAuth")
.AddScheme<AuthenticationSchemeOptions, SessionAuthenticationHandler>(
"SessionAuth",
options => { }
);
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseCors(CorsPolicyName);
app.UseAuthentication();
app.UseAuthorization();
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
var exception = exceptionHandlerFeature?.Error;
var problemDetailsFactory =
errorApp.ApplicationServices.GetRequiredService<ProblemDetailsFactory>();
var statusCode = exception switch
{
DomainError => 400,
_ => 500,
};
var message = exception switch
{
DomainError domainError => domainError.Message,
{ } e => e.Message,
_ => ReasonPhrases.GetReasonPhrase(statusCode),
};
var problemDetails = problemDetailsFactory.CreateProblemDetails(
httpContext: context,
title: "An error occurred",
detail: message,
statusCode: statusCode
);
// problemDetails.Extensions["traceId"] = context.TraceIdentifier;
context.Response.StatusCode = statusCode;
context.Response.ContentType = "application/problem+json";
await context.Response.WriteAsJsonAsync(problemDetails);
});
});
// app.UseMiddleware<ExceptionMapperMiddleware>();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.MapControllers();
app.UseHttpsRedirection();
app.Run();