using System; using System.IO; using Dapper; using DotEnv.Core; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Migrato.Core; using Migrato.Sqlite; using MiniWiki.Data; using MiniWiki.Data.Documents; using MiniWiki.Data.Sessions; using MiniWiki.Data.Settings; using MiniWiki.Data.Tags; using MiniWiki.Data.Users; using MiniWiki.Web.Auth; using MiniWiki.Web.Documents; using MiniWiki.Web.Settings; using MiniWiki.Web.Tags; using MiniWiki.Web.Users; // make dapper honor snake_case columns DefaultTypeMap.MatchNamesWithUnderscores = true; var builder = WebApplication.CreateBuilder(args); new EnvLoader().Load(); builder.Configuration.AddEnvironmentVariables(); builder.Services.AddRazorComponents(); builder.Services.AddControllers(); builder.Services.AddHttpContextAccessor(); builder.Logging.AddConsole(); builder.Logging.SetMinimumLevel(builder.Environment.IsDevelopment() ? LogLevel.Trace : LogLevel.Information); // Microsoft.AspNetCore logs are chatty AF. Don't allow INFO logs from MS in non-dev situations. builder.Logging.AddFilter("Microsoft.AspNetCore", level => level is LogLevel.Critical or LogLevel.Error or LogLevel.Warning); builder.WebHost.ConfigureKestrel(options => { var port = Environment.GetEnvironmentVariable("MW_PORT") ?? "8080"; options.ListenAnyIP(int.Parse(port)); }); var dbPath = Environment.GetEnvironmentVariable("MW_DB_PATH") ?? "/app/mw.db"; builder.Services.AddMemoryCache(); builder.Services.AddSingleton(new SqliteConnectionFactory($"Data Source={dbPath}")) .AddSingleton(sp => new SettingsCachingRepository(new SettingsDatabaseRepository(sp.GetRequiredService()), sp.GetRequiredService())) .AddSingleton(sp => new UsersCachingRepository(new UsersDatabaseRepository(sp.GetRequiredService()), sp.GetRequiredService())) .AddSingleton(sp => new SessionsCachingRepository(new SessionsDatabaseRepository(sp.GetRequiredService()), sp.GetRequiredService())) .AddSingleton() .AddSingleton(sp => new TagsCachingRepository(new TagsDatabaseRepository(sp.GetRequiredService()))); builder.Services.AddSingleton(); builder.Services.AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); builder.Services.AddSingleton(); // configure protection to persist keys var dataProtectionPath = Environment.GetEnvironmentVariable("MW_DATA_PROTECTION_PATH") ?? "/app/keys"; builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(dataProtectionPath)) .SetApplicationName("MiniWiki"); builder.Services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownIPNetworks.Clear(); options.KnownProxies.Clear(); }); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/auth/login"; options.LogoutPath = "/auth/logout"; }); builder.Services.AddAuthorization(); var app = builder.Build(); using (var scope = app.Services.CreateScope()) { var provider = scope.ServiceProvider.GetRequiredService(); using var connection = provider.CreateConnection(dbPath); var runner = new MigrationRunner(provider); var migrationsPath = Environment.GetEnvironmentVariable("MW_MIGRATIONS_PATH") ?? "/app/migrations"; var migrations = MigrationLoader.LoadFromDirectory(migrationsPath); var applied = runner.MigrateUp(connection, migrations); var logger = scope.ServiceProvider.GetRequiredService().CreateLogger("Migrations"); if (applied.Count == 0) { logger.LogInformation("no pending migrations"); } foreach (var migration in applied) { logger.LogInformation("applied {version}_{name}", migration.Version, migration.Name); } } // reverse proxy support app.UseForwardedHeaders(); app.UseStaticFiles(); app.UseMiddleware(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); using (var scope = app.Services.CreateScope()) { var tagRepo = scope.ServiceProvider.GetRequiredService(); if (tagRepo is TagsCachingRepository cachingRepo) await cachingRepo.PrimeCacheAsync(); } await app.RunAsync();