using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using WorkClub.Api.Auth; using WorkClub.Api.Endpoints.Clubs; using WorkClub.Api.Endpoints.Members; using WorkClub.Api.Endpoints.Shifts; using WorkClub.Api.Endpoints.Tasks; using WorkClub.Api.Middleware; using WorkClub.Api.Services; using WorkClub.Application.Interfaces; using WorkClub.Infrastructure.Data; using WorkClub.Infrastructure.Data.Interceptors; using WorkClub.Infrastructure.Services; using WorkClub.Infrastructure.Seed; var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); builder.Services.AddHttpContextAccessor(); 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.AddSingleton(); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = builder.Configuration["Keycloak:Authority"]; options.Audience = builder.Configuration["Keycloak:Audience"]; options.RequireHttpsMetadata = false; options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = false, // Disabled for local dev - external clients use localhost:8080, internal use keycloak:8080 ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true }; }); builder.Services.AddScoped(); builder.Services.AddAuthorizationBuilder() .AddPolicy("RequireAdmin", policy => policy.RequireRole("Admin")) .AddPolicy("RequireManager", policy => policy.RequireRole("Admin", "Manager")) .AddPolicy("RequireMember", policy => policy.RequireRole("Admin", "Manager", "Member")) .AddPolicy("RequireViewer", policy => policy.RequireAuthenticatedUser()); builder.Services.AddDbContext((sp, options) => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")) .AddInterceptors( sp.GetRequiredService(), sp.GetRequiredService())); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); if (!string.IsNullOrEmpty(connectionString)) { builder.Services.AddHealthChecks() .AddNpgSql(connectionString); } else { builder.Services.AddHealthChecks(); } var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.MapOpenApi(); using var scope = app.Services.CreateScope(); var seedService = scope.ServiceProvider.GetRequiredService(); await seedService.SeedAsync(); } app.UseHttpsRedirection(); app.UseAuthentication(); app.UseMiddleware(); app.UseAuthorization(); app.UseMiddleware(); app.MapHealthChecks("/health/live", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions { Predicate = _ => false }); app.MapHealthChecks("/health/ready"); app.MapHealthChecks("/health/startup"); var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; app.MapGet("/weatherforecast", () => { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast ( DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55), summaries[Random.Shared.Next(summaries.Length)] )) .ToArray(); return forecast; }) .WithName("GetWeatherForecast"); app.MapGet("/api/test", () => Results.Ok(new { message = "Test endpoint" })) .RequireAuthorization(); app.MapTaskEndpoints(); app.MapShiftEndpoints(); app.MapClubEndpoints(); app.MapMemberEndpoints(); app.Run(); record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) { public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); } public partial class Program { }