using API.Data; using API.GraphQL; using API.Services; using Microsoft.EntityFrameworkCore; using Model; using Npgsql; var builder = WebApplication.CreateBuilder(args); // Register HTTP Context Accessor and Token Service builder.Services.AddHttpContextAccessor(); builder.Services.AddSingleton(); // Configure CORS for Web / Blazor WebAssembly client builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { policy.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); }); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? builder.Configuration.GetConnectionString("Postgres") ?? Environment.GetEnvironmentVariable("POSTGRES_CONNECTION_STRING"); var envPassword = Environment.GetEnvironmentVariable("PostgreSQL_Password") ?? (OperatingSystem.IsWindows() ? Environment.GetEnvironmentVariable("PostgreSQL_Password", EnvironmentVariableTarget.User) : null) ?? (OperatingSystem.IsWindows() ? Environment.GetEnvironmentVariable("PostgreSQL_Password", EnvironmentVariableTarget.Machine) : null) ?? Environment.GetEnvironmentVariable("POSTGRESQL_PASSWORD") ?? Environment.GetEnvironmentVariable("POSTGRES_PASSWORD"); if (!string.IsNullOrEmpty(connectionString)) { if (!string.IsNullOrEmpty(envPassword)) try { var csb = new NpgsqlConnectionStringBuilder(connectionString) { Password = envPassword }; connectionString = csb.ConnectionString; } catch { } } else if (!string.IsNullOrEmpty(envPassword)) { var csb = new NpgsqlConnectionStringBuilder { Host = "localhost", Database = "chrono", Username = "postgres", Password = envPassword }; connectionString = csb.ConnectionString; } builder.Services.AddDbContext(options => { if (!string.IsNullOrEmpty(connectionString)) options.UseNpgsql(connectionString); else options.UseInMemoryDatabase("chrono"); }); // Register Hot Chocolate GraphQL Server with Nitro GraphQL IDE support builder.Services .AddGraphQLServer() .AddQueryType() .AddMutationType() .ModifyRequestOptions(opt => opt.IncludeExceptionDetails = builder.Environment.IsDevelopment()); var app = builder.Build(); using (var scope = app.Services.CreateScope()) { try { var db = scope.ServiceProvider.GetRequiredService(); if (!string.IsNullOrEmpty(connectionString) && db.Database.IsNpgsql()) db.Database.Migrate(); else db.Database.EnsureCreated(); // Seed default users if empty if (!db.Users.Any()) { db.Users.AddRange( new User { Username = "admin", Password = "password123", CreatedAt = DateTime.UtcNow }, new User { Username = "testuser", Password = "password123", CreatedAt = DateTime.UtcNow }, new User { Username = "player1", Password = "password123", CreatedAt = DateTime.UtcNow } ); db.SaveChanges(); } } catch (Exception ex) { Console.WriteLine($"[WARNING] Database initialization failed: {ex.Message}. Continuing without DB connection."); } } app.UseCors(); // Map Hot Chocolate GraphQL endpoint and Nitro GraphQL Debugger app.MapGraphQL(); app.MapGet("/", () => "Chrono CCG GraphQL API"); // REST Endpoints for Decks app.MapGet("/api/decks", async (AppDbContext db) => { var decks = await db.UserDecks.OrderByDescending(d => d.UpdatedAt).ToListAsync(); return Results.Ok(decks); }); app.MapGet("/api/decks/{id:guid}", async (AppDbContext db, Guid id) => { var deck = await db.UserDecks.FindAsync(id); return deck != null ? Results.Ok(deck) : Results.NotFound(); }); app.MapPost("/api/decks", async (AppDbContext db, UserDeck input) => { var existing = await db.UserDecks.FindAsync(input.Id); if (existing == null) { input.UpdatedAt = DateTime.UtcNow; db.UserDecks.Add(input); await db.SaveChangesAsync(); return Results.Ok(input); } existing.Name = input.Name; existing.Cards = input.Cards ?? []; existing.Divers = input.Divers ?? []; existing.Notes = input.Notes; existing.Season = input.Season; existing.UpdatedAt = DateTime.UtcNow; await db.SaveChangesAsync(); return Results.Ok(existing); }); app.MapDelete("/api/decks/{id:guid}", async (AppDbContext db, Guid id) => { var existing = await db.UserDecks.FindAsync(id); if (existing != null) { db.UserDecks.Remove(existing); await db.SaveChangesAsync(); return Results.Ok(new { success = true }); } return Results.NotFound(); }); // Authentication endpoints app.MapPost("/api/auth/dev-login", async (AppDbContext db, ITokenService tokenService, HttpContext httpContext) => { var user = await db.Users.FirstOrDefaultAsync(u => u.Username == "testuser"); if (user == null) { user = new User { Username = "testuser", Password = "password123", CreatedAt = DateTime.UtcNow }; db.Users.Add(user); await db.SaveChangesAsync(); } var token = tokenService.GenerateToken(user.Username); httpContext.Response.Cookies.Append("chrono_auth_token", token, new CookieOptions { Path = "/", SameSite = SameSiteMode.Lax }); httpContext.Response.Cookies.Append("chrono_auth_user", user.Username, new CookieOptions { Path = "/", SameSite = SameSiteMode.Lax }); return Results.Ok(new { success = true, token, username = user.Username }); }); app.MapDelete("/api/auth/credentials", () => Results.Ok(new { success = true })); app.Run();