using System.Data; using System.Text; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using StickyBoard.Api.Common; using StickyBoard.Api.Common.Filters; using StickyBoard.Api.Middleware; using StickyBoard.Core.Auth; using StickyBoard.Core.Infrastructure.Db; using StickyBoard.Core.Repositories.Attachments; using StickyBoard.Core.Repositories.Attachments.Contracts; using StickyBoard.Core.Repositories.Automation.Jobs; using StickyBoard.Core.Repositories.BoardsAndCards; using StickyBoard.Core.Repositories.BoardsAndCards.Contracts; using StickyBoard.Core.Repositories.SocialAndMessaging; using StickyBoard.Core.Repositories.SocialAndMessaging.Contracts; using StickyBoard.Core.Repositories.UsersAndAuth; using StickyBoard.Core.Repositories.UsersAndAuth.Contracts; using StickyBoard.Core.Services.Attachments; using StickyBoard.Core.Services.Automation.Workers; using StickyBoard.Core.Services.BoardsAndCards; using StickyBoard.Core.Services.BoardsAndCards.Contracts; using StickyBoard.Core.Services.SocialAndMessaging; using StickyBoard.Core.Services.SocialAndMessaging.Contracts; using StickyBoard.Core.Services.UsersAndAuth; using StickyBoard.Core.Services.UsersAndAuth.Contracts; var builder = WebApplication.CreateBuilder(args); var configuration = builder.Configuration; // ========================================================== // 1. DATABASE CONNECTION (NpgsqlDataSource with Enum Mapping) // ========================================================== var connectionString = ConnectionStringBuilder.Build(configuration); var dataSource = DataSourceFactory.Create(connectionString); builder.Services.AddSingleton(dataSource); builder.Services.AddScoped(_ => dataSource.CreateConnection()); // ========================================================== // 2. REPOSITORIES & SERVICES // ========================================================== builder.Services.AddHttpClient(); // TODO: verify if added properly // ---------------------------------------------------------- // Attachments // ---------------------------------------------------------- builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // ---------------------------------------------------------- // Automation / Workers // ---------------------------------------------------------- builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // ---------------------------------------------------------- // Boards & Cards // ---------------------------------------------------------- builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // ---------------------------------------------------------- // Social & Messaging // ---------------------------------------------------------- builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // ---------------------------------------------------------- // Users & Auth (interfaces + implementations) // ---------------------------------------------------------- builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // ========================================================== // 3. AUTHENTICATION & AUTHORIZATION (JWT + API KEY) // ========================================================== var jwtSection = configuration.GetSection("Jwt"); var jwt = jwtSection.Get() ?? throw new Exception("JWT configuration missing."); builder.Services.Configure(jwtSection); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwt.Issuer, ValidAudience = jwt.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwt.SecretKey)), ClockSkew = TimeSpan.FromSeconds(30) }; }) .AddScheme("ApiKey", null); builder.Services.AddAuthorization(options => { options.AddPolicy("WorkerOrAdmin", policy => policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme, "ApiKey") .RequireAuthenticatedUser() .RequireRole("worker", "admin")); }); // ========================================================== // 4. CONTROLLERS + SWAGGER CONFIGURATION // ========================================================== builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "StickyBoard API", Version = "v1", Description = "REST API for StickyBoard cross-platform workspace (Academic Project)", Contact = new OpenApiContact { Name = "Alexandre Emond", Url = new Uri("https://aedev.pro") } }); // JWT var jwtSecurityScheme = new OpenApiSecurityScheme { Scheme = "bearer", BearerFormat = "JWT", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.Http, Description = "JWT Authorization (use: Bearer )", Reference = new OpenApiReference { Id = JwtBearerDefaults.AuthenticationScheme, Type = ReferenceType.SecurityScheme } }; options.AddSecurityDefinition(jwtSecurityScheme.Reference.Id, jwtSecurityScheme); // API Key var apiKeyScheme = new OpenApiSecurityScheme { Description = "Worker API Key (use: ApiKey )", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, Scheme = "ApiKey", Reference = new OpenApiReference { Id = "ApiKey", Type = ReferenceType.SecurityScheme } }; options.AddSecurityDefinition(apiKeyScheme.Reference.Id, apiKeyScheme); // Require both to appear in Swagger options.AddSecurityRequirement(new OpenApiSecurityRequirement { { jwtSecurityScheme, new string[] {} }, { apiKeyScheme, new string[] {} } }); options.OperationFilter(); options.OperationFilter(); options.OperationFilter(); }); // ========================================================== // 5. BUILD APP // ========================================================== var app = builder.Build(); // ========================================================== // 6. SWAGGER + MIDDLEWARE // ========================================================== app.UseMiddleware(); app.UseSwagger(); app.UseSwaggerUI(options => { string cacheBuster = app.Environment.IsDevelopment() ? DateTime.UtcNow.Ticks.ToString() : typeof(Program).Assembly.GetName().Version?.ToString() ?? "stable"; options.SwaggerEndpoint($"/swagger/v1/swagger.json?v={cacheBuster}", "StickyBoard API v1"); options.RoutePrefix = "api/swagger"; options.DocumentTitle = "StickyBoard API – Swagger Explorer"; options.DisplayRequestDuration(); }); // ========================================================== // 7. PIPELINE // ========================================================== app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); // ========================================================== // 8. LOGGING // ========================================================== var logger = app.Services.GetRequiredService>(); var workerKey = configuration["WORKER_API_KEY"]; if (string.IsNullOrEmpty(workerKey)) logger.LogWarning("WORKER_API_KEY is not set. Worker authentication will fail."); else logger.LogInformation("Worker key loaded successfully (length: {len})", workerKey.Length); app.Run();