diff --git a/src/Koogle.Infrastructure/Security/BootstrapSeeder.cs b/src/Koogle.Infrastructure/Security/BootstrapSeeder.cs new file mode 100644 index 0000000..af81cc5 --- /dev/null +++ b/src/Koogle.Infrastructure/Security/BootstrapSeeder.cs @@ -0,0 +1,137 @@ +using Koogle.Domain.Entities; +using Koogle.Infrastructure.Identity; +using KoogleApp.Data; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace Koogle.Infrastructure.Security; + + +public static class BootstrapSeeder +{ + public static async Task SeedAsync(IServiceProvider services, IConfiguration config, IHostEnvironment env) + { + // Nur in Dev automatisch – Prod nur mit explizitem Flag + var allowSeed = env.IsDevelopment() || config.GetValue("Bootstrap:EnableSeeding"); + if (!allowSeed) return; + + using var scope = services.CreateScope(); + + var roleManager = scope.ServiceProvider.GetRequiredService>(); + var userManager = scope.ServiceProvider.GetRequiredService>(); + var appDb = scope.ServiceProvider.GetRequiredService(); + + // 1) Rollen + var roles = new[] { "SuperAdmin", "Admin", "Editor", "Viewer" }; + foreach (var r in roles) + { + if (!await roleManager.RoleExistsAsync(r)) + await roleManager.CreateAsync(new ApplicationRole { Name = r }); + } + + // 2) SuperAdmin User (Identity) + var adminEmail = config["Bootstrap:SuperAdmin:Email"]; + var adminUserName = config["Bootstrap:SuperAdmin:UserName"] ?? adminEmail; + var adminPassword = config["Bootstrap:SuperAdmin:Password"]; + + if (string.IsNullOrWhiteSpace(adminEmail) || string.IsNullOrWhiteSpace(adminPassword)) + throw new InvalidOperationException("Bootstrap SuperAdmin credentials missing. Set Bootstrap:SuperAdmin:Email and :Password."); + + var adminUser = await userManager.FindByEmailAsync(adminEmail); + if (adminUser == null) + { + adminUser = new ApplicationUser + { + UserName = adminUserName, + Email = adminEmail, + EmailConfirmed = env.IsDevelopment() // Dev: praktisch, Prod: lieber false + Confirm Flow + }; + + var createResult = await userManager.CreateAsync(adminUser, adminPassword); + if (!createResult.Succeeded) + throw new InvalidOperationException("Failed to create SuperAdmin: " + + string.Join("; ", createResult.Errors.Select(e => $"{e.Code}:{e.Description}"))); + } + + // 3) Rolle zuweisen + if (!await userManager.IsInRoleAsync(adminUser, "SuperAdmin")) + await userManager.AddToRoleAsync(adminUser, "SuperAdmin"); + + // 4) Domain UserProfile anlegen + var profile = await appDb.UserProfiles.SingleOrDefaultAsync(p => p.IdentityUserId == adminUser.Id); + if (profile == null) + { + profile = new UserProfile + { + IdentityUserId = adminUser.Id, + DisplayName = "Super Admin", + Locale = "de-DE", + TimeZone = "Europe/Berlin", + CreatedAt = DateTime.UtcNow + }; + appDb.UserProfiles.Add(profile); + await appDb.SaveChangesAsync(); + } + + // Optional: Default-Club erstellen + Membership + RoleAssignments (nur wenn gewünscht) + if (config.GetValue("Bootstrap:CreateDefaultClub")) + { + var clubName = config["Bootstrap:DefaultClub:Name"] ?? "Default Club"; + + var club = await appDb.Clubs.FirstOrDefaultAsync(c => c.Name == clubName); + if (club == null) + { + club = new Club { Name = clubName, CreatedAt = DateTime.UtcNow }; + appDb.Clubs.Add(club); + await appDb.SaveChangesAsync(); + } + + // Membership (UserProfileClub) – wenn du das nutzt + var membership = await appDb.UserProfileClubs + .FindAsync(profile.Id, club.Id); + + if (membership == null) + { + membership = new UserProfileClub + { + UserProfileId = profile.Id, + ClubId = club.Id, + IsDefault = true, + AssignedAt = DateTime.UtcNow, + AssignedById = adminUser.Id + }; + appDb.UserProfileClubs.Add(membership); + await appDb.SaveChangesAsync(); + } + + // Club-Rollen assignments (B2): Admin/Editor/Viewer o.ä. + // Hier: Admin im Default-Club + var roleExists = await appDb.UserProfileClubRoleAssignments.AnyAsync(a => + a.UserProfileId == profile.Id && a.ClubId == club.Id && a.RoleName == "Admin" && !a.IsDeleted); + + if (!roleExists) + { + appDb.UserProfileClubRoleAssignments.Add(new UserProfileClubRoleAssignment + { + UserProfileId = profile.Id, + ClubId = club.Id, + RoleId = Guid.Empty, // optional, wenn du RoleId nicht zwingend brauchst + RoleName = "Admin", + AssignedAt = DateTime.UtcNow, + AssignedById = adminUser.Id, + CreatedAt = DateTime.UtcNow + }); + + await appDb.SaveChangesAsync(); + } + } + } +} \ No newline at end of file diff --git a/src/Koogle.Web/Program.cs b/src/Koogle.Web/Program.cs index 5e8360d..5d075fd 100644 --- a/src/Koogle.Web/Program.cs +++ b/src/Koogle.Web/Program.cs @@ -51,7 +51,7 @@ using (var scope = app.Services.CreateScope()) { await IdentityRoleSeeder.SeedAsync(scope.ServiceProvider); } - +await BootstrapSeeder.SeedAsync(app.Services, app.Configuration, app.Environment); diff --git a/src/Koogle.Web/appsettings.Development.json b/src/Koogle.Web/appsettings.Development.json index 0c208ae..04de358 100644 --- a/src/Koogle.Web/appsettings.Development.json +++ b/src/Koogle.Web/appsettings.Development.json @@ -1,4 +1,18 @@ { + + "Bootstrap": { + "EnableSeeding": true, + "CreateDefaultClub": true, + "SuperAdmin": { + "Email": "ch@koogle.de", + "UserName": "ch@koogle.de", + "Password": "DEV_only3000!" + }, + "DefaultClub": { + "Name": "Koogle Demo Club" + } + }, + "Logging": { "LogLevel": { "Default": "Information",