using System.Text; using FlatRender.StudioSvc.Application.Services; using FlatRender.StudioSvc.Domain.Enums; using FlatRender.StudioSvc.Infrastructure.Data; using FlatRender.StudioSvc.Middleware; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Npgsql; var builder = WebApplication.CreateBuilder(args); // ── Database ───────────────────────────────────────────────────────────────── // Native PostgreSQL enums are mapped on the EF provider so Npgsql can read/write them // at runtime (HasPostgresEnum in the model alone is not enough on Npgsql 8+). builder.Services.AddDbContext(opt => opt.UseNpgsql( builder.Configuration.GetConnectionString("Default"), npgsql => npgsql.MapEnum("saved_project_type", "studio", PreserveCaseNameTranslator.Instance)) .UseSnakeCaseNamingConvention()); // ── Application services ────────────────────────────────────────────────────── builder.Services.AddScoped(); // ── Auth ────────────────────────────────────────────────────────────────────── var jwtKey = builder.Configuration["Jwt:Key"] ?? throw new InvalidOperationException("Jwt:Key is required"); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(opt => { opt.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)), ValidateIssuer = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidateAudience = true, ValidAudience = builder.Configuration["Jwt:Audience"], ValidateLifetime = true, ClockSkew = TimeSpan.FromSeconds(30), }; }); builder.Services.AddAuthorization(); // ── Controllers ─────────────────────────────────────────────────────────────── builder.Services.AddRouting(opts => { opts.LowercaseUrls = true; opts.AppendTrailingSlash = false; }); builder.Services.AddControllers(); // ── Swagger ─────────────────────────────────────────────────────────────────── builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "FlatRender Studio Service", Version = "v1" }); c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Name = "Authorization", Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", In = ParameterLocation.Header }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, [] } }); }); // ── CORS ────────────────────────────────────────────────────────────────────── builder.Services.AddCors(opt => opt.AddDefaultPolicy(p => p .WithOrigins( builder.Configuration.GetSection("Cors:Origins").Get() ?? ["http://localhost:3000"]) .AllowAnyHeader() .AllowAnyMethod())); // ── Health check ────────────────────────────────────────────────────────────── builder.Services.AddHealthChecks(); // ───────────────────────────────────────────────────────────────────────────── var app = builder.Build(); // ── Auto-migrate in Development ─────────────────────────────────────────────── if (app.Environment.IsDevelopment()) { using var scope = app.Services.CreateScope(); await scope.ServiceProvider.GetRequiredService() .Database.MigrateAsync(); } // ── Middleware pipeline ─────────────────────────────────────────────────────── app.UseMiddleware(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Studio v1")); } app.UseCors(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapHealthChecks("/health"); app.Run();