From d061cac4895c65fd716467925900fa485d58af50 Mon Sep 17 00:00:00 2001 From: john Date: Sun, 18 May 2025 17:18:34 +0200 Subject: [PATCH] some stuff --- Femto.Api/Dockerfile | 34 ++++++++++++++++- Femto.Database/Femto.Database.csproj | 5 ++- Femto.Database/Program.cs | 56 +++++++++++++++++++++------- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/Femto.Api/Dockerfile b/Femto.Api/Dockerfile index 131e508..097b28f 100644 --- a/Femto.Api/Dockerfile +++ b/Femto.Api/Dockerfile @@ -20,24 +20,54 @@ USER appuser FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src + +# Copy csproj files for all projects COPY ["Femto.Api/Femto.Api.csproj", "Femto.Api/"] COPY ["Femto.Modules.Blog/Femto.Modules.Blog.csproj", "Femto.Modules.Blog/"] COPY ["Femto.Common/Femto.Common.csproj", "Femto.Common/"] COPY ["Femto.Modules.Auth.Contracts/Femto.Modules.Auth.Contracts.csproj", "Femto.Modules.Auth.Contracts/"] COPY ["Femto.Modules.Auth/Femto.Modules.Auth.csproj", "Femto.Modules.Auth/"] COPY ["Femto.Modules.Media/Femto.Modules.Media.csproj", "Femto.Modules.Media/"] +COPY ["Femto.Database/Femto.Database.csproj", "Femto.Database/"] + +# Restore all dependencies RUN dotnet restore "Femto.Api/Femto.Api.csproj" + +# Copy everything COPY . . + +# Build the API WORKDIR "/src/Femto.Api" RUN dotnet build "Femto.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build +# Build and publish both API and Database CLI FROM build AS publish ARG BUILD_CONFIGURATION=Release + +# Publish API RUN dotnet publish "Femto.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false +# Publish Database CLI +WORKDIR "/src/Femto.Database" +RUN dotnet publish "Femto.Database.csproj" -c $BUILD_CONFIGURATION -o /app/femto-db /p:UseAppHost=false + +# Final runtime image FROM base AS final WORKDIR /app -COPY --from=publish /app/publish . -# Entrypoint +# Copy published API and DB CLI +COPY --from=publish /app/publish . +COPY --from=publish /app/femto-db /app/femto-db + +# Add a wrapper script to launch the DB CLI +RUN mkdir -p /app/scripts && \ + echo '#!/bin/sh\nexec dotnet /app/femto-db/Femto.Database.dll "$@"' > /app/scripts/femto-db && \ + chmod +x /app/scripts/femto-db + +# (Optional) Add script dir to PATH for easier access +ENV PATH="/app/scripts:${PATH}" + + +# Entrypoint for the API ENTRYPOINT ["dotnet", "Femto.Api.dll"] + diff --git a/Femto.Database/Femto.Database.csproj b/Femto.Database/Femto.Database.csproj index acb7f32..d8d3289 100644 --- a/Femto.Database/Femto.Database.csproj +++ b/Femto.Database/Femto.Database.csproj @@ -14,7 +14,10 @@ - + + + PreserveNewest + diff --git a/Femto.Database/Program.cs b/Femto.Database/Program.cs index 3af67fb..ba182d5 100644 --- a/Femto.Database/Program.cs +++ b/Femto.Database/Program.cs @@ -6,7 +6,7 @@ using Npgsql; var nameArg = new Argument("name", "the name of the migration"); var migrationsDirectoryOption = new Option( ["--migrations-directory"], - () => "./Migrations", + () => System.Environment.GetEnvironmentVariable("MigrationsDirectory") ?? "./Migrations", "the directory where the migrations are stored" ); @@ -15,6 +15,7 @@ var newCommand = new Command("new", "creates a new migrations") nameArg, migrationsDirectoryOption, }; + newCommand.SetHandler(MakeNewMigration, nameArg, migrationsDirectoryOption); var connectionStringArg = new Argument( @@ -22,30 +23,53 @@ var connectionStringArg = new Argument( "the connection string to the database" ); +var connectionStringOption = new Option( + ["-c", "--connection-string"], + () => System.Environment.GetEnvironmentVariable("ConnectionStrings__Database") ?? null, + "the connection string to the database" +); + var upCommand = new Command("up", "update the database to the most current migration") { migrationsDirectoryOption, connectionStringArg, }; -upCommand.SetHandler(MigrateUp, migrationsDirectoryOption, connectionStringArg); + +upCommand.SetHandler( + async (directory, connectionString) => + { + if (connectionString is null) + { + throw new ArgumentException("Connection string is required."); + } + + await MigrateUp(directory, connectionString); + }, + migrationsDirectoryOption, + connectionStringOption +); var seedCommand = new Command("seed", "seed the database with test data") { connectionStringArg }; seedCommand.SetHandler(Seed, connectionStringArg); // Add these near the top with other command definitions -var yesOption = new Option( - ["-y", "--yes"], - "Skip confirmation prompt" -); +var yesOption = new Option(["-y", "--yes"], "Skip confirmation prompt"); -var resetCommand = new Command("reset", "drops the existing database, runs migrations, and seeds test data") +var resetCommand = new Command( + "reset", + "drops the existing database, runs migrations, and seeds test data" +) { connectionStringArg, migrationsDirectoryOption, - yesOption + yesOption, }; -resetCommand.SetHandler(CreateTestDatabase, connectionStringArg, migrationsDirectoryOption, yesOption); - +resetCommand.SetHandler( + CreateTestDatabase, + connectionStringArg, + migrationsDirectoryOption, + yesOption +); var rootCommand = new RootCommand("migrator") { newCommand, upCommand, seedCommand, resetCommand }; @@ -85,19 +109,23 @@ static async Task Seed(string connectionString) await TestDataSeeder.Seed(dataSource); } -static async Task CreateTestDatabase(string connectionString, string migrationsDirectory, bool skipConfirmation) +static async Task CreateTestDatabase( + string connectionString, + string migrationsDirectory, + bool skipConfirmation +) { var builder = new NpgsqlConnectionStringBuilder(connectionString); if (!skipConfirmation) { builder.Database = "postgres"; - + try { await using var dataSource = NpgsqlDataSource.Create(builder.ConnectionString); await using var conn = await dataSource.OpenConnectionAsync(); await using var cmd = conn.CreateCommand(); - + cmd.CommandText = $"SELECT 1 FROM pg_database WHERE datname = '{builder.Database}'"; var exists = await cmd.ExecuteScalarAsync(); if (exists is true) @@ -128,7 +156,7 @@ static async Task CreateTestDatabase(string connectionString, string migrationsD await using var dataSource = NpgsqlDataSource.Create(builder.ConnectionString); await using var conn = await dataSource.OpenConnectionAsync(); await using var cmd = conn.CreateCommand(); - + // Drop database if it exists cmd.CommandText = $"DROP DATABASE IF EXISTS {databaseName} WITH (FORCE)"; await cmd.ExecuteNonQueryAsync();