using System.Security.Claims; using foodmarket.Api.Infrastructure.Tenancy; using foodmarket.Api.Seed; using foodmarket.Application.Common.Tenancy; using foodmarket.Infrastructure.Identity; using foodmarket.Infrastructure.Persistence; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using OpenIddict.Validation.AspNetCore; using Serilog; using static OpenIddict.Abstractions.OpenIddictConstants; Log.Logger = new LoggerConfiguration() .WriteTo.Console() .CreateBootstrapLogger(); try { var builder = WebApplication.CreateBuilder(args); builder.Host.UseSerilog((ctx, services, cfg) => cfg.ReadFrom.Configuration(ctx.Configuration).ReadFrom.Services(services)); const string CorsPolicy = "food-market-cors"; builder.Services.AddCors(opts => opts.AddPolicy(CorsPolicy, p => { var origins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get() ?? ["http://localhost:5173"]; p.WithOrigins(origins).AllowAnyHeader().AllowAnyMethod().AllowCredentials(); })); builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddDbContext(opts => { opts.UseNpgsql(builder.Configuration.GetConnectionString("Default"), npg => npg.MigrationsAssembly(typeof(AppDbContext).Assembly.GetName().Name)); opts.UseOpenIddict(); }); builder.Services.AddIdentity(opts => { opts.Password.RequireDigit = true; opts.Password.RequiredLength = 8; opts.Password.RequireNonAlphanumeric = false; opts.Password.RequireUppercase = false; opts.User.RequireUniqueEmail = true; }) .AddEntityFrameworkStores() .AddDefaultTokenProviders(); builder.Services.Configure(opts => { opts.ClaimsIdentity.UserNameClaimType = Claims.Name; opts.ClaimsIdentity.UserIdClaimType = Claims.Subject; opts.ClaimsIdentity.RoleClaimType = Claims.Role; }); builder.Services.AddOpenIddict() .AddCore(opts => opts.UseEntityFrameworkCore().UseDbContext()) .AddServer(opts => { opts.SetTokenEndpointUris("/connect/token"); opts.AllowPasswordFlow().AllowRefreshTokenFlow(); opts.AcceptAnonymousClients(); opts.RegisterScopes(Scopes.OpenId, Scopes.Profile, Scopes.Email, Scopes.Roles, "api"); if (builder.Environment.IsDevelopment()) { opts.AddEphemeralEncryptionKey().AddEphemeralSigningKey(); opts.DisableAccessTokenEncryption(); } opts.UseAspNetCore() .EnableTokenEndpointPassthrough() .DisableTransportSecurityRequirement(); opts.SetAccessTokenLifetime(TimeSpan.Parse( builder.Configuration["OpenIddict:AccessTokenLifetime"] ?? "01:00:00")); opts.SetRefreshTokenLifetime(TimeSpan.Parse( builder.Configuration["OpenIddict:RefreshTokenLifetime"] ?? "30.00:00:00")); }) .AddValidation(opts => { opts.UseLocalServer(); opts.UseAspNetCore(); }); builder.Services.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); builder.Services.AddAuthorization(); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); var app = builder.Build(); app.UseSerilogRequestLogging(); app.UseCors(CorsPolicy); app.UseAuthentication(); app.UseAuthorization(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.MapControllers(); app.MapGet("/health", () => Results.Ok(new { status = "ok", time = DateTime.UtcNow })); app.MapGet("/api/me", (HttpContext ctx) => { var user = ctx.User; return Results.Ok(new { sub = user.FindFirst(Claims.Subject)?.Value, name = user.Identity?.Name, email = user.FindFirst(Claims.Email)?.Value, roles = user.FindAll(Claims.Role).Select(c => c.Value), orgId = user.FindFirst(HttpContextTenantContext.OrganizationClaim)?.Value, }); }).RequireAuthorization(); if (app.Environment.IsDevelopment()) { using var scope = app.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); db.Database.Migrate(); } app.Run(); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }