From fd59af0e4ca8d9b10dbf9d37431a9d185556c9a1 Mon Sep 17 00:00:00 2001 From: Wyatt Hunter Date: Sat, 7 Feb 2026 10:24:35 -0600 Subject: [PATCH 1/2] FoundationCog --- CulinaryCommandApp/CulinaryCommand.csproj | 3 + CulinaryCommandApp/Program.cs | 193 +++++++++++++--------- 2 files changed, 117 insertions(+), 79 deletions(-) diff --git a/CulinaryCommandApp/CulinaryCommand.csproj b/CulinaryCommandApp/CulinaryCommand.csproj index 4e4019a..d8759c1 100644 --- a/CulinaryCommandApp/CulinaryCommand.csproj +++ b/CulinaryCommandApp/CulinaryCommand.csproj @@ -13,6 +13,8 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -22,6 +24,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + PreserveNewest diff --git a/CulinaryCommandApp/Program.cs b/CulinaryCommandApp/Program.cs index 0834f07..8fda080 100644 --- a/CulinaryCommandApp/Program.cs +++ b/CulinaryCommandApp/Program.cs @@ -1,86 +1,110 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; + using CulinaryCommand.Data; using CulinaryCommand.Services; using CulinaryCommand.Inventory.Services; using CulinaryCommand.PurchaseOrder.Services; using CulinaryCommand.Inventory; using CulinaryCommand.Inventory.Services.Interfaces; -using System; // for Version, TimeSpan -using System.Linq; -using CulinaryCommand.Components; // for args.Any -using Google.GenAI; +using CulinaryCommand.Components; using CulinaryCommandApp.AIDashboard.Services.Reporting; - +using Google.GenAI; +using System; var builder = WebApplication.CreateBuilder(args); - -// FORCE EF to load your config when running "dotnet ef" -// builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); -// builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true); - -// Add services to the container. +// +// ===================== +// UI +// ===================== builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); -// Register Google GenAI client and AIReportingService so they can be injected. -// The Client will pick up the GOOGLE_API_KEY from environment variables (set in deploy.yml). +// +// ===================== +// Cognito Authentication (MUST be before Build) +// ===================== +// ===== Cognito Auth (OIDC) ===== +builder.Services + .AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) + .AddCookie() + .AddOpenIdConnect(options => + { + var userPoolId = "us-east-2_SULe0c9vr"; + var region = "us-east-2"; + + options.Authority = $"https://cognito-idp.{region}.amazonaws.com/{userPoolId}"; + options.MetadataAddress = $"{options.Authority}/.well-known/openid-configuration"; + + options.ClientId = "55joip0viah9qtj7dndhvma2gt"; + options.ClientSecret = Environment.GetEnvironmentVariable("COGNITO_CLIENT_SECRET"); // don’t hardcode + + options.ResponseType = OpenIdConnectResponseType.Code; + options.SaveTokens = true; + + options.CallbackPath = "/signin-oidc"; + options.SignedOutCallbackPath = "/signout-callback-oidc"; + + options.RequireHttpsMetadata = true; // keep true + + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("email"); + options.Scope.Add("profile"); + + options.TokenValidationParameters.NameClaimType = "cognito:username"; + options.TokenValidationParameters.RoleClaimType = "cognito:groups"; + }); + +builder.Services.AddAuthorization(); + + +// +// ===================== +// AI Services +// ===================== builder.Services.AddSingleton(_ => new Client()); builder.Services.AddScoped(); -// DB hookup -// var conn = builder.Configuration.GetConnectionString("DefaultConnection"); -// if (string.IsNullOrWhiteSpace(conn)) -// { -// throw new InvalidOperationException("Missing connection string 'Default'. Set ConnectionStrings__Default via environment or config."); -// } -// Console.WriteLine("CONNECTION STRING FROM CONFIG:"); -// Console.WriteLine(conn); - - -// builder.Services.AddDbContext(opt => -// opt.UseMySql(conn, new MySqlServerVersion(new Version(8, 0, 36)), -// mySqlOptions => mySqlOptions.EnableRetryOnFailure() -// ) -// ); - -// DB hookup +// +// ===================== +// Database +// ===================== var conn = builder.Configuration.GetConnectionString("DefaultConnection"); if (string.IsNullOrWhiteSpace(conn)) { - throw new InvalidOperationException("Missing connection string 'DefaultConnection'. Set ConnectionStrings__DefaultConnection via environment or config."); + throw new InvalidOperationException( + "Missing connection string 'DefaultConnection'"); } -// Mask password for logs (primarily for debugging in the Lightsail instance) -string MaskPwd(string s) -{ - if (string.IsNullOrEmpty(s)) return s; - var parts = s.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - for (int i = 0; i < parts.Length; i++) - { - if (parts[i].StartsWith("Pwd=", StringComparison.OrdinalIgnoreCase)) - parts[i] = "Pwd=****"; - } - return string.Join(';', parts); -} -Console.WriteLine($"[Startup] Using MySQL connection: {MaskPwd(conn)}"); - builder.Services.AddDbContext(opt => - opt.UseMySql(conn, new MySqlServerVersion(new Version(8, 0, 36)), mySqlOpts => - { - // Enable transient retry for RDS connectivity hiccups - mySqlOpts.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10), errorNumbersToAdd: null); - })); - -// registers services with Scoped lifetime. + opt.UseMySql( + conn, + new MySqlServerVersion(new Version(8, 0, 36)), + mySqlOpts => mySqlOpts.EnableRetryOnFailure() + ) +); + +// +// ===================== +// Application Services +// ===================== 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(); @@ -90,50 +114,61 @@ string MaskPwd(string s) builder.Services.AddScoped(); builder.Services.AddSingleton(); +// +// ===================== +// Build App +// ===================== var app = builder.Build(); -// Determine whether the app should only run migrations and exit -var migrateOnly = (Environment.GetEnvironmentVariable("MIGRATE_ONLY")?.Equals("true", StringComparison.OrdinalIgnoreCase) == true) - || (args != null && args.Any(a => a.Equals("--migrate-only", StringComparison.OrdinalIgnoreCase))); - -// Apply pending EF Core migrations at startup +// +// ===================== +// Apply EF Migrations +// ===================== using (var scope = app.Services.CreateScope()) { try { - var database = scope.ServiceProvider.GetRequiredService(); - database.Database.Migrate(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); } catch (Exception ex) { - Console.WriteLine($"[Startup] Migration failed: {ex.GetType().Name} - {ex.Message}"); - // Optionally: keep running without schema update; remove this catch to fail hard instead + Console.WriteLine($"[Startup] Migration warning: {ex.Message}"); } } -if (migrateOnly) -{ - Console.WriteLine("[Startup] MIGRATE_ONLY set; exiting after applying migrations."); - return; -} - -// Configure the HTTP request pipeline. -if (!app.Environment.IsDevelopment()) -{ - app.UseExceptionHandler("/Error", createScopeForErrors: true); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); -} - -// Temporarily disable HTTPS redirect for development -//app.UseHttpsRedirection(); +// +// ===================== +// Middleware +// ===================== app.UseStaticFiles(); app.UseAntiforgery(); +app.UseAuthentication(); +app.UseAuthorization(); + +// +// ===================== +// Routes +// ===================== app.MapRazorComponents() .AddInteractiveServerRenderMode(); -// Simple health endpoint for load balancers/CI checks app.MapGet("/health", () => "OK"); +app.MapGet("/login", async (HttpContext ctx) => +{ + await ctx.ChallengeAsync( + OpenIdConnectDefaults.AuthenticationScheme, + new AuthenticationProperties { RedirectUri = "/" } + ); +}); + +app.MapGet("/logout", async (HttpContext ctx) => +{ + await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + await ctx.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, + new AuthenticationProperties { RedirectUri = "/" }); +}); + app.Run(); From 6bacd726df83364528776c6ee33c9d36a162c9a5 Mon Sep 17 00:00:00 2001 From: Wyatt Hunter Date: Tue, 17 Feb 2026 10:55:34 -0600 Subject: [PATCH 2/2] AI Placed Back --- .../Components/Pages/Dashboard.razor | 31 ++++++++-------- CulinaryCommandApp/Program.cs | 36 +++++++++---------- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/CulinaryCommandApp/Components/Pages/Dashboard.razor b/CulinaryCommandApp/Components/Pages/Dashboard.razor index 76a45d2..a50c2ca 100644 --- a/CulinaryCommandApp/Components/Pages/Dashboard.razor +++ b/CulinaryCommandApp/Components/Pages/Dashboard.razor @@ -54,7 +54,6 @@ else } else { -
@@ -169,20 +168,19 @@ else _aiLoading = true; StateHasChanged(); - var csvPath = Path.Combine(Env.ContentRootPath, "AIDashboard", "Services", "Reporting", "test_data.csv"); - _aiAnalysis = await ReportingService.AnalyzeCsvAsync(csvPath); - - @* if (!string.IsNullOrWhiteSpace(_aiAnalysis)) - { - var csvPath = Path.Combine(Env.ContentRootPath, "AIDashboard", "Services", "Reporting", "test_data.csv"); - _aiAnalysis = await ReportingService.AnalyzeCsvAsync(csvPath); - - // Try to deserialize the returned JSON into the DTO so we can render structured cards UI. - if (!string.IsNullOrWhiteSpace(_aiAnalysis)) + try{ + if (!string.IsNullOrWhiteSpace(_aiAnalysis)) { - var options = new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - _aiAnalysisObj = System.Text.Json.JsonSerializer.Deserialize(_aiAnalysis, options); - } + var csvPath = Path.Combine(Env.ContentRootPath, "AIDashboard", "Services", "Reporting", "test_data.csv"); + _aiAnalysis = await ReportingService.AnalyzeCsvAsync(csvPath); + + // Try to deserialize the returned JSON into the DTO so we can render structured cards UI. + if (!string.IsNullOrWhiteSpace(_aiAnalysis)) + { + var options = new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + _aiAnalysisObj = System.Text.Json.JsonSerializer.Deserialize(_aiAnalysis, options); + } + } } catch(Exception e) { @@ -195,15 +193,14 @@ else _aiLoading = false; StateHasChanged(); } - _aiLoading = false; *@ - _aiAnalysisObj = null; // Uncoomment above and delete curent like to activate AI + _aiLoading = false; + //_aiAnalysisObj = null; // Uncoomment above and delete curent like to activate AI StateHasChanged(); } } private void NavigateToSignIn() { - // This should go to your Cognito login page once that route is finalized. Nav.NavigateTo("/login", forceLoad: true); } } diff --git a/CulinaryCommandApp/Program.cs b/CulinaryCommandApp/Program.cs index 3562bda..4538d14 100644 --- a/CulinaryCommandApp/Program.cs +++ b/CulinaryCommandApp/Program.cs @@ -87,24 +87,24 @@ // ===================== // AI Services // ===================== -// builder.Services.AddSingleton(_ => new Client()); -// builder.Services.AddScoped(); - -var googleKey = - Environment.GetEnvironmentVariable("GOOGLE_API_KEY") - ?? builder.Configuration["Google:ApiKey"]; // optional appsettings slot - -if (!string.IsNullOrWhiteSpace(googleKey)) -{ - builder.Services.AddSingleton(_ => new Google.GenAI.Client(apiKey: googleKey)); - builder.Services.AddScoped(); - Console.WriteLine("✅ AI enabled (GOOGLE_API_KEY found)."); -} -else -{ - Console.WriteLine("⚠️ GOOGLE_API_KEY not set; AI features disabled."); - // Do NOT register AIReportingService at all. -} +builder.Services.AddSingleton(_ => new Client()); +builder.Services.AddScoped(); + +// var googleKey = +// Environment.GetEnvironmentVariable("GOOGLE_API_KEY") +// ?? builder.Configuration["Google:ApiKey"]; // optional appsettings slot + +// if (!string.IsNullOrWhiteSpace(googleKey)) +// { +// builder.Services.AddSingleton(_ => new Google.GenAI.Client(apiKey: googleKey)); +// builder.Services.AddScoped(); +// Console.WriteLine("✅ AI enabled (GOOGLE_API_KEY found)."); +// } +// else +// { +// Console.WriteLine("⚠️ GOOGLE_API_KEY not set; AI features disabled."); +// // Do NOT register AIReportingService at all. +// } //