Update settings for production deployment

This commit is contained in:
2025-12-05 14:28:23 -05:00
parent 826eac1372
commit 56dbd549fd
5 changed files with 126 additions and 47 deletions
+109 -21
View File
@@ -1,13 +1,59 @@
using Data;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
using MudBlazor.Services;
using Serilog;
using System.Text.Json;
using WebApp;
using WebApp.Authentication;
using WebApp.Components;
var builder = WebApplication.CreateBuilder(args);
// Load optional appsettings from Data directory (overrides defaults)
var dataAppSettingsPath = Path.Combine(builder.Environment.ContentRootPath, "Data", "appsettings.json");
if (!File.Exists(dataAppSettingsPath))
{
Console.WriteLine($"appsettings.json not found at {dataAppSettingsPath}. Creating with template values...");
var templateSettings = new
{
ChapterSettings = new
{
Name = "Your Chapter Name",
ShortName = "YCN",
NationalId = "0000",
StateId = "00000",
RegionalId = "00000",
CompetitionYear = "2025"
}
};
var json = JsonSerializer.Serialize(templateSettings, new JsonSerializerOptions
{
WriteIndented = true
});
try
{
// Ensure Data directory exists
Directory.CreateDirectory(Path.Combine(builder.Environment.ContentRootPath, "Data"));
File.WriteAllText(dataAppSettingsPath, json);
Console.WriteLine("appsettings.json created in Data directory. Please update ChapterSettings with your chapter information.");
}
catch (Exception ex)
{
Console.WriteLine($"WARNING: Unable to create appsettings.json: {ex.Message}");
}
}
// Add Data/appsettings.json as additional configuration source
if (File.Exists(dataAppSettingsPath))
{
builder.Configuration.AddJsonFile(dataAppSettingsPath, optional: true, reloadOnChange: true);
Console.WriteLine($"Loaded configuration from {dataAppSettingsPath}");
}
// Configure Serilog
builder.Host.UseSerilog((context, configuration) =>
configuration
@@ -24,8 +70,8 @@ builder.Host.UseSerilog((context, configuration) =>
// Configure authentication secrets for production (Docker, etc.)
if (builder.Environment.IsProduction())
{
// Option 1: Load from volume-mounted secrets file
var secretsPath = "/app/secrets/auth-secrets.json";
// Option 1: Load from volume-mounted secrets file in Data directory
var secretsPath = Path.Combine(builder.Environment.ContentRootPath, "Data", "auth-secrets.json");
if (File.Exists(secretsPath))
{
builder.Configuration.AddJsonFile(secretsPath, optional: false, reloadOnChange: true);
@@ -42,9 +88,52 @@ builder.Services.AddRazorComponents()
builder.Services.AddMudServices();
// Configure SQLite
var connectionString = builder.Configuration.GetConnectionString("SQLiteDefault");
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlite(connectionString));
// Configure Data Protection to persist keys across container restarts
var keysPath = Path.Combine(builder.Environment.ContentRootPath, "DataProtectionKeys");
// Try to create the directory if it doesn't exist
try
{
if (!Directory.Exists(keysPath))
{
Directory.CreateDirectory(keysPath);
Console.WriteLine($"Created DataProtectionKeys directory at: {keysPath}");
}
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"WARNING: Unable to create DataProtectionKeys directory at {keysPath}");
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine("This may cause issues with antiforgery tokens and authentication cookies.");
Console.WriteLine("Ensure the volume mount has correct permissions for the container user.");
}
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keysPath))
.SetApplicationName("TSA-Chapter-Organizer");
// Ensure Data directory exists
var dataPath = Path.Combine(builder.Environment.ContentRootPath, "Data");
try
{
if (!Directory.Exists(dataPath))
{
Directory.CreateDirectory(dataPath);
Console.WriteLine($"Created Data directory at: {dataPath}");
}
}
catch (Exception ex)
{
Console.WriteLine($"WARNING: Unable to create Data directory at {dataPath}: {ex.Message}");
}
// Configure SQLite with hardcoded connection string
var connectionString = "Data Source=Data/app.db";
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlite(connectionString,
sqliteOptions => sqliteOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
});
builder.Services.AddQuickGridEntityFrameworkAdapter();
@@ -71,7 +160,9 @@ builder.Services.AddAuthentication("Auth")
// Enhanced security settings
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SecurePolicy = builder.Environment.IsDevelopment()
? CookieSecurePolicy.SameAsRequest
: CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.Name = "TSA.Auth";
});
@@ -89,25 +180,19 @@ using (var scope = app.Services.CreateScope())
{
var context = services.GetRequiredService<AppDbContext>();
if (app.Environment.IsDevelopment())
// Check for pending migrations
var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
if (pendingMigrations.Any())
{
// Auto-apply migrations in development
logger.LogInformation("Applying database migrations (Development)...");
logger.LogInformation(
"Applying database migrations: {Migrations}",
string.Join(", ", pendingMigrations));
await context.Database.MigrateAsync();
logger.LogInformation("Database migrations applied successfully");
}
else
{
// Check if migrations are needed in production
var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
if (pendingMigrations.Any())
{
logger.LogError(
"Database has pending migrations: {Migrations}",
string.Join(", ", pendingMigrations));
throw new InvalidOperationException(
"Database migrations are pending. Run migrations before starting the application.");
}
logger.LogInformation("Database is up to date");
}
}
@@ -126,8 +211,11 @@ if (!app.Environment.IsDevelopment())
app.UseHsts();
app.UseMigrationsEndPoint();
}
app.UseHttpsRedirection();
else
{
// Only redirect to HTTPS in development (production should use reverse proxy)
app.UseHttpsRedirection();
}
app.UseRouting();
app.UseAuthentication();