diff --git a/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedOptions.cs b/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedOptions.cs deleted file mode 100644 index 98da4e89..00000000 --- a/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Socialize.Api.Infrastructure.Development; - -public record DevelopmentSeedOptions -{ - public const string SectionName = "DevelopmentSeed"; - - public bool Enabled { get; init; } = true; -} diff --git a/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs b/backend/src/Socialize.Api/Infrastructure/TestData/TestDataSeedExtensions.cs similarity index 96% rename from backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs rename to backend/src/Socialize.Api/Infrastructure/TestData/TestDataSeedExtensions.cs index 044fa14a..4e9d10d0 100644 --- a/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs +++ b/backend/src/Socialize.Api/Infrastructure/TestData/TestDataSeedExtensions.cs @@ -16,11 +16,10 @@ using Socialize.Api.Modules.Organizations.Data; using Socialize.Api.Modules.Organizations.Services; using Socialize.Api.Modules.Workspaces.Data; using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -namespace Socialize.Api.Infrastructure.Development; +namespace Socialize.Api.Infrastructure.TestData; -public static class DevelopmentSeedExtensions +public static class TestDataSeedExtensions { private static readonly Guid OrganizationId = Guid.Parse("99999999-9999-9999-9999-999999999999"); private static readonly Guid WorkspaceId = Guid.Parse("11111111-1111-1111-1111-111111111111"); @@ -39,23 +38,11 @@ public static class DevelopmentSeedExtensions private static readonly Guid ClientCommentId = Guid.Parse("77777777-7777-7777-7777-777777777777"); private static readonly Guid NotificationId = Guid.Parse("88888888-8888-8888-8888-888888888888"); - public static async Task UseDevelopmentSeedAsync( - this IApplicationBuilder app, + public static async Task SeedTestDataAsync( + this IServiceProvider services, CancellationToken cancellationToken = default) { - IHostEnvironment environment = app.ApplicationServices.GetRequiredService(); - if (!environment.IsDevelopment()) - { - return app; - } - - using IServiceScope scope = app.ApplicationServices.CreateScope(); - IOptions options = scope.ServiceProvider.GetRequiredService>(); - if (!options.Value.Enabled) - { - return app; - } - + using IServiceScope scope = services.CreateScope(); UserManager userManager = scope.ServiceProvider.GetRequiredService(); AppDbContext dbContext = scope.ServiceProvider.GetRequiredService(); @@ -64,7 +51,7 @@ public static class DevelopmentSeedExtensions id: Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"), username: "manager", email: "manager@socialize.local", - password: "manager", + password: "Manager1!", alias: "Northstar Manager", firstname: "Morgan", lastname: "Reid", @@ -80,7 +67,7 @@ public static class DevelopmentSeedExtensions id: Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"), username: "client", email: "client@socialize.local", - password: "client", + password: "Client1!", alias: "Sofia Martin", firstname: "Sofia", lastname: "Martin", @@ -97,7 +84,7 @@ public static class DevelopmentSeedExtensions id: Guid.Parse("cccccccc-cccc-cccc-cccc-cccccccccccc"), username: "provider", email: "provider@socialize.local", - password: "provider", + password: "Provider1!", alias: "Alex Studio", firstname: "Alex", lastname: "Studio", @@ -115,7 +102,7 @@ public static class DevelopmentSeedExtensions id: Guid.Parse("dddddddd-dddd-dddd-dddd-dddddddddddd"), username: "dev", email: "dev@socialize.local", - password: "dev", + password: "Developer1!", alias: "Socialize Dev", firstname: "Jo", lastname: "Bumble", @@ -138,7 +125,7 @@ public static class DevelopmentSeedExtensions dbContext, cancellationToken); - return app; + return services; } private static async Task EnsureUserAsync( @@ -175,7 +162,7 @@ public static class DevelopmentSeedExtensions if (!createResult.Succeeded) { throw new InvalidOperationException( - $"Failed to seed development user '{username}': {string.Join(", ", createResult.Errors.Select(error => error.Description))}"); + $"Failed to seed test user '{username}': {string.Join(", ", createResult.Errors.Select(error => error.Description))}"); } } @@ -195,7 +182,7 @@ public static class DevelopmentSeedExtensions if (!passwordResetResult.Succeeded) { throw new InvalidOperationException( - $"Failed to set development password for '{username}': {string.Join(", ", passwordResetResult.Errors.Select(error => error.Description))}"); + $"Failed to set test password for '{username}': {string.Join(", ", passwordResetResult.Errors.Select(error => error.Description))}"); } } diff --git a/backend/src/Socialize.Api/Modules/Workspaces/DependencyInjection.cs b/backend/src/Socialize.Api/Modules/Workspaces/DependencyInjection.cs index f74ea823..a2b0f64d 100644 --- a/backend/src/Socialize.Api/Modules/Workspaces/DependencyInjection.cs +++ b/backend/src/Socialize.Api/Modules/Workspaces/DependencyInjection.cs @@ -1,5 +1,4 @@ using Socialize.Api.Modules.Workspaces.Data; -using Socialize.Api.Infrastructure.Development; namespace Socialize.Api.Modules.Workspaces; @@ -8,9 +7,6 @@ public static class DependencyInjection public static WebApplicationBuilder AddWorkspaceModule( this WebApplicationBuilder builder) { - builder.Services.Configure( - builder.Configuration.GetSection(DevelopmentSeedOptions.SectionName)); - return builder; } } diff --git a/backend/src/Socialize.Api/Program.cs b/backend/src/Socialize.Api/Program.cs index 93188384..988c5115 100644 --- a/backend/src/Socialize.Api/Program.cs +++ b/backend/src/Socialize.Api/Program.cs @@ -6,7 +6,7 @@ using Socialize; using Socialize.Api.Infrastructure.BlobStorage.Configuration; using Socialize.Api.Infrastructure.BlobStorage.Services; using Socialize.Api.Infrastructure; -using Socialize.Api.Infrastructure.Development; +using Socialize.Api.Infrastructure.TestData; using Socialize.Api.Modules.Approvals; using Socialize.Api.Modules.Assets; using Socialize.Api.Modules.Channels; @@ -23,6 +23,7 @@ using Socialize.Api.Modules.Workspaces; var builder = WebApplication.CreateBuilder(args); +bool seedTestData = args.Any(arg => string.Equals(arg, "seed-testdata", StringComparison.OrdinalIgnoreCase)); string? vaultUri = Environment.GetEnvironmentVariable("VaultUri"); if (!string.IsNullOrWhiteSpace(vaultUri)) @@ -78,6 +79,25 @@ builder.AddCalendarIntegrationsModule(); var app = builder.Build(); +if (seedTestData) +{ + if (app.Environment.IsProduction() + && !string.Equals( + Environment.GetEnvironmentVariable("CONFIRM_PRODUCTION_SEED"), + "true", + StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Refusing to seed test data in Production without CONFIRM_PRODUCTION_SEED=true."); + } + + await app.UseAppDataAsync(); + await app.UseIdentityModuleAsync(); + await app.Services.SeedTestDataAsync(); + Console.WriteLine("Seeded test data."); + return; +} + app.UseForwardedHeaders( new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto } ); @@ -90,7 +110,6 @@ app.UseAuthorization(); // Initialize and seed the db. await app.UseAppDataAsync(); await app.UseIdentityModuleAsync(); -await app.UseDevelopmentSeedAsync(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) diff --git a/backend/src/Socialize.Api/appsettings.Development.json b/backend/src/Socialize.Api/appsettings.Development.json index 9af68feb..69c5b475 100644 --- a/backend/src/Socialize.Api/appsettings.Development.json +++ b/backend/src/Socialize.Api/appsettings.Development.json @@ -22,8 +22,5 @@ "Lifetime": "00:05:00", "RefreshTokenLifetime": "0.00:30:00" } - }, - "DevelopmentSeed": { - "Enabled": true } } diff --git a/backend/src/Socialize.Api/appsettings.Production.json b/backend/src/Socialize.Api/appsettings.Production.json index bd08cee1..5e842962 100644 --- a/backend/src/Socialize.Api/appsettings.Production.json +++ b/backend/src/Socialize.Api/appsettings.Production.json @@ -5,8 +5,5 @@ "LocalBlobStorage": { "RootPath": "App_Data/blob-storage", "RequestPath": "/api/storage" - }, - "DevelopmentSeed": { - "Enabled": false } } diff --git a/docs/TASKS/content/003-real-workspace-channels.md b/docs/TASKS/content/003-real-workspace-channels.md index b2d33d46..11ba4894 100644 --- a/docs/TASKS/content/003-real-workspace-channels.md +++ b/docs/TASKS/content/003-real-workspace-channels.md @@ -12,7 +12,7 @@ Replace frontend-derived fake channels with real workspace-owned channel records - Add a backend `Channels` module with a `Channel` table. - Add list and create endpoints for workspace channels. -- Seed development channels and align seeded content publication targets to those configured channel names. +- Seed test channels and align seeded content publication targets to those configured channel names. - Update the frontend channels store to load and create channels through the API. - Keep the Channels page UI shape intact. @@ -20,7 +20,7 @@ Replace frontend-derived fake channels with real workspace-owned channel records - `backend/src/Socialize.Api/Data/AppDbContext.cs` - `backend/src/Socialize.Api/Modules/Channels/` -- `backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs` +- `backend/src/Socialize.Api/Infrastructure/TestData/TestDataSeedExtensions.cs` - `frontend/src/features/channels/stores/channelsStore.js` - `docs/FEATURES/channels.md` diff --git a/docs/TASKS/development/001-script-testdata-seed.md b/docs/TASKS/development/001-script-testdata-seed.md new file mode 100644 index 00000000..7c2ce992 --- /dev/null +++ b/docs/TASKS/development/001-script-testdata-seed.md @@ -0,0 +1,20 @@ +# Task: Add explicit test data seed script + +## Goal + +Move demo/test data seeding out of API startup and into an explicit script command that can be run intentionally against any configured environment. + +## Relevant Files + +- `backend/src/Socialize.Api/Program.cs` +- `backend/src/Socialize.Api/Infrastructure/TestData/TestDataSeedExtensions.cs` +- `backend/src/Socialize.Api/appsettings.Development.json` +- `backend/src/Socialize.Api/appsettings.Production.json` +- `scripts/seed-testdata.sh` + +## Validation + +```bash +dotnet build backend/Socialize.slnx +./scripts/seed-testdata.sh +``` diff --git a/scripts/seed-testdata.sh b/scripts/seed-testdata.sh new file mode 100755 index 00000000..453e442b --- /dev/null +++ b/scripts/seed-testdata.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +cd "$REPO_ROOT" + +export ASPNETCORE_ENVIRONMENT="${ASPNETCORE_ENVIRONMENT:-Development}" + +if [[ "$ASPNETCORE_ENVIRONMENT" == "Production" && "${CONFIRM_PRODUCTION_SEED:-}" != "true" ]]; then + echo "Refusing to seed test data in Production without CONFIRM_PRODUCTION_SEED=true." >&2 + exit 2 +fi + +dotnet run \ + --no-launch-profile \ + --project backend/src/Socialize.Api/Socialize.Api.csproj \ + -- seed-testdata "$@"