From 5c088345b399b137965084420fd13873d28eb68c Mon Sep 17 00:00:00 2001 From: beo3000 Date: Sun, 28 Dec 2025 12:10:15 +0100 Subject: [PATCH] add demo-club: Neue Dateien (5) | Datei | Zweck | |--------------------------------------------------------|----------------------| | src/Koogle.Infrastructure/Data/DemoSeeder.cs | Seeder + Reset-Logik | | src/Koogle.Domain/Interfaces/IDemoResetService.cs | Interface | | src/Koogle.Infrastructure/Services/DemoResetService.cs | Service | | src/Koogle.Web/Store/Demo/DemoActions.cs | Fluxor Actions | | src/Koogle.Web/Store/Demo/DemoEffects.cs | Fluxor Effects | Geaenderte Dateien (6) | Datei | Aenderung | |------------------------------|-------------------------------| | appsettings.Development.json | Demo-Config hinzugefuegt | | Program.cs | DemoSeeder.SeedAsync() Aufruf | | DependencyInjection.cs | IDemoResetService registriert | | Login.razor | Demo-Hinweis-Box | | Clubs.razor | Reset-Button + Confirm-Dialog | Demo-Daten - User: demo@koogle.de / demo123 (ClubAdmin, kein SuperAdmin) - Club: "Demo" - 8 Mitglieder: Hans Maier, Klaus Schmidt, Werner Braun, Dieter Fischer, Juergen Weber, Heinz Mueller, Rolf Schneider, Karl Hoffmann - 2 Gaeste: Stefan Gast, Thomas Besucher - 10 Expenses mit Trigger-Zuordnung (Gosse 0.50, Pudel 0.30, ... Abwesenheit 5.00) - 3 Spieltage mit Games --- .../Interfaces/IDemoResetService.cs | 23 + src/Koogle.Infrastructure/Data/DemoSeeder.cs | 407 ++++++++++++++++++ .../DependencyInjection.cs | 2 + .../Services/DemoResetService.cs | 78 ++++ .../Components/Pages/Account/Login.razor | 18 + .../Components/Pages/Admin/Clubs.razor | 31 ++ src/Koogle.Web/Program.cs | 1 + src/Koogle.Web/Store/Demo/DemoActions.cs | 16 + src/Koogle.Web/Store/Demo/DemoEffects.cs | 35 ++ src/Koogle.Web/appsettings.Development.json | 7 +- 10 files changed, 617 insertions(+), 1 deletion(-) create mode 100644 src/Koogle.Domain/Interfaces/IDemoResetService.cs create mode 100644 src/Koogle.Infrastructure/Data/DemoSeeder.cs create mode 100644 src/Koogle.Infrastructure/Services/DemoResetService.cs create mode 100644 src/Koogle.Web/Store/Demo/DemoActions.cs create mode 100644 src/Koogle.Web/Store/Demo/DemoEffects.cs diff --git a/src/Koogle.Domain/Interfaces/IDemoResetService.cs b/src/Koogle.Domain/Interfaces/IDemoResetService.cs new file mode 100644 index 0000000..c33749d --- /dev/null +++ b/src/Koogle.Domain/Interfaces/IDemoResetService.cs @@ -0,0 +1,23 @@ +namespace Koogle.Domain.Interfaces; + +/// +/// Service for resetting the Demo club to initial state. +/// +public interface IDemoResetService +{ + /// + /// Resets the Demo club to initial state (hard delete + re-seed). + /// + /// True if reset was successful, false if Demo club not found or not enabled. + Task ResetDemoClubAsync(CancellationToken ct = default); + + /// + /// Checks if the given club ID is the Demo club. + /// + bool IsDemoClub(Guid clubId); + + /// + /// Gets the Demo club ID if Demo mode is enabled. + /// + Guid? GetDemoClubId(); +} diff --git a/src/Koogle.Infrastructure/Data/DemoSeeder.cs b/src/Koogle.Infrastructure/Data/DemoSeeder.cs new file mode 100644 index 0000000..6432661 --- /dev/null +++ b/src/Koogle.Infrastructure/Data/DemoSeeder.cs @@ -0,0 +1,407 @@ +using Koogle.Domain.Entities; +using Koogle.Domain.Enums; +using Koogle.Domain.Interfaces; +using Koogle.Infrastructure.Identity; +using KoogleApp.Data; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Koogle.Infrastructure.Data; + +/// +/// Seeds demo data for the Demo club including user, persons, expenses, and sample days. +/// +public static class DemoSeeder +{ + private static readonly string[] MemberNames = + { + "Hans Maier", "Klaus Schmidt", "Werner Braun", "Dieter Fischer", + "Juergen Weber", "Heinz Mueller", "Rolf Schneider", "Karl Hoffmann" + }; + + private static readonly string[] GuestNames = { "Stefan Gast", "Thomas Besucher" }; + + /// + /// Seeds demo user, club membership, persons, expenses, triggers and sample days. + /// + public static async Task SeedAsync(IServiceProvider services, IConfiguration config, IHostEnvironment env) + { + var demoEnabled = config.GetValue("Bootstrap:Demo:Enabled"); + if (!demoEnabled) return; + + var allowSeed = env.IsDevelopment() || config.GetValue("Bootstrap:EnableSeeding"); + if (!allowSeed) return; + + using var scope = services.CreateScope(); + + var userManager = scope.ServiceProvider.GetRequiredService>(); + var appDb = scope.ServiceProvider.GetRequiredService(); + var triggerRepo = scope.ServiceProvider.GetRequiredService(); + + var demoEmail = config["Bootstrap:Demo:Email"] ?? "demo@koogle.de"; + var demoPassword = config["Bootstrap:Demo:Password"] ?? "demo123"; + var demoClubName = config["Bootstrap:Demo:ClubName"] ?? "Demo"; + + // 1) Find or create Demo Club (should exist from BootstrapSeeder if DefaultClub.Name == "Demo") + var club = await appDb.Clubs.FirstOrDefaultAsync(c => c.Name == demoClubName && !c.IsDeleted); + if (club == null) + { + club = new Club { Name = demoClubName, CreatedAt = DateTime.UtcNow }; + appDb.Clubs.Add(club); + await appDb.SaveChangesAsync(); + } + + // 2) Create Demo User (NOT SuperAdmin) + var demoUser = await userManager.FindByEmailAsync(demoEmail); + if (demoUser == null) + { + demoUser = new ApplicationUser + { + UserName = demoEmail, + Email = demoEmail, + EmailConfirmed = true + }; + + var result = await userManager.CreateAsync(demoUser, demoPassword); + if (!result.Succeeded) + { + throw new InvalidOperationException("Failed to create Demo user: " + + string.Join("; ", result.Errors.Select(e => $"{e.Code}:{e.Description}"))); + } + } + + // 3) Create UserProfile for Demo user + var profile = await appDb.UserProfiles.FirstOrDefaultAsync(p => p.IdentityUserId == demoUser.Id); + if (profile == null) + { + profile = new UserProfile + { + IdentityUserId = demoUser.Id, + DisplayName = "Demo Benutzer", + Locale = "de-DE", + TimeZone = "Europe/Berlin", + CreatedAt = DateTime.UtcNow + }; + appDb.UserProfiles.Add(profile); + await appDb.SaveChangesAsync(); + } + + // 4) Add Demo user to Demo club + 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 = demoUser.Id + }; + appDb.UserProfileClubs.Add(membership); + await appDb.SaveChangesAsync(); + } + + // 5) Assign ClubAdmin role to Demo user (NOT SuperAdmin!) + 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, + RoleName = "Admin", + AssignedAt = DateTime.UtcNow, + AssignedById = demoUser.Id, + CreatedAt = DateTime.UtcNow + }); + await appDb.SaveChangesAsync(); + } + + // 6) Seed demo data if not already present + var hasPersons = await appDb.Persons.AnyAsync(p => p.ClubId == club.Id && !p.IsDeleted); + if (!hasPersons) + { + await SeedDemoDataAsync(appDb, triggerRepo, club.Id, demoUser.Id); + } + } + + /// + /// Resets the Demo club to initial state: hard delete all data, then re-seed. + /// + public static async Task ResetDemoClubAsync(IServiceProvider services, Guid clubId) + { + using var scope = services.CreateScope(); + var appDb = scope.ServiceProvider.GetRequiredService(); + var triggerRepo = scope.ServiceProvider.GetRequiredService(); + + // Find creator ID for seeding + var club = await appDb.Clubs.FindAsync(clubId); + if (club == null) return; + + var membership = await appDb.UserProfileClubs.FirstOrDefaultAsync(m => m.ClubId == clubId); + var creatorId = membership?.AssignedById ?? Guid.Empty; + + // Hard delete all club data + await HardDeleteClubDataAsync(appDb, clubId); + + // Re-seed demo data + await SeedDemoDataAsync(appDb, triggerRepo, clubId, creatorId); + } + + private static async Task HardDeleteClubDataAsync(AppDbContext db, Guid clubId) + { + // Order matters due to FK constraints - delete children first + await db.Database.ExecuteSqlAsync($"DELETE FROM app.PersonExpenses WHERE ClubId = {clubId}"); + await db.Database.ExecuteSqlAsync($"DELETE FROM app.GamePersons WHERE ClubId = {clubId}"); + await db.Database.ExecuteSqlAsync($"DELETE FROM app.Games WHERE ClubId = {clubId}"); + await db.Database.ExecuteSqlAsync($"DELETE FROM app.DayPersons WHERE ClubId = {clubId}"); + await db.Database.ExecuteSqlAsync($"DELETE FROM app.Days WHERE ClubId = {clubId}"); + await db.Database.ExecuteSqlAsync($"DELETE FROM app.ExpenseTriggers WHERE ClubId = {clubId}"); + await db.Database.ExecuteSqlAsync($"DELETE FROM app.Expenses WHERE ClubId = {clubId}"); + await db.Database.ExecuteSqlAsync($"DELETE FROM app.Persons WHERE ClubId = {clubId}"); + } + + private static async Task SeedDemoDataAsync(AppDbContext db, ITriggerRepository triggerRepo, Guid clubId, Guid creatorId) + { + var now = DateTime.UtcNow; + + // 1) Seed Persons (8 Members + 2 Guests) + var persons = new List(); + foreach (var name in MemberNames) + { + var person = new Person + { + Name = name, + PersonStatus = PersonStatus.Member, + ClubId = clubId, + CreatedAt = now, + CreatedById = creatorId + }; + db.Persons.Add(person); + persons.Add(person); + } + + foreach (var name in GuestNames) + { + var person = new Person + { + Name = name, + PersonStatus = PersonStatus.Guest, + ClubId = clubId, + CreatedAt = now, + CreatedById = creatorId + }; + db.Persons.Add(person); + persons.Add(person); + } + await db.SaveChangesAsync(); + + // 2) Seed Expenses with Trigger mappings + var expenseData = new (string Name, decimal Price, ExpenseTriggerType TriggerType, bool IsInverse)[] + { + ("Gosse", 0.50m, ExpenseTriggerType.Gutter, false), + ("Pudel", 0.30m, ExpenseTriggerType.NoWood, false), + ("Klingel", 0.20m, ExpenseTriggerType.Bell, false), + ("Anwurffehler", 0.40m, ExpenseTriggerType.FirstThrowFail, false), + ("Kranz", 0.50m, ExpenseTriggerType.Circle, true), + ("Alle Neune", 1.00m, ExpenseTriggerType.Strike, true), + ("Gosse Anwurf", 1.00m, ExpenseTriggerType.FullGutter, false), + ("Ausgeschieden", 2.00m, ExpenseTriggerType.Eliminated, false), + ("Abwesenheit", 5.00m, ExpenseTriggerType.Absent, false), + ("Strafpunkt", 0.10m, ExpenseTriggerType.ExpensePoint, false) + }; + + var expenses = new List(); + foreach (var (name, price, triggerType, isInverse) in expenseData) + { + var expense = new Expense + { + ClubId = clubId, + Name = name, + Description = $"Strafe fuer {name}", + Price = price, + IsOneClick = true, + IsInverse = isInverse, + IsVariable = false, + ExpenseType = ExpenseType.Monetary, + CreatedAt = now, + CreatedById = creatorId + }; + db.Expenses.Add(expense); + expenses.Add(expense); + } + await db.SaveChangesAsync(); + + // 3) Create ExpenseTrigger mappings + for (int i = 0; i < expenses.Count; i++) + { + var expense = expenses[i]; + var triggerType = expenseData[i].TriggerType; + var trigger = await triggerRepo.GetByTriggerTypeAsync(triggerType); + + if (trigger != null) + { + var expenseTrigger = new ExpenseTrigger + { + ClubId = clubId, + ExpenseId = expense.Id, + TriggerId = trigger.Id, + AssignedAt = now, + AssignedById = creatorId + }; + db.ExpenseTriggers.Add(expenseTrigger); + } + } + await db.SaveChangesAsync(); + + // 4) Seed Sample Days with Games + await SeedSampleDaysAsync(db, clubId, creatorId, persons, expenses, now); + } + + private static async Task SeedSampleDaysAsync( + AppDbContext db, + Guid clubId, + Guid creatorId, + List persons, + List expenses, + DateTime now) + { + var members = persons.Where(p => p.PersonStatus == PersonStatus.Member).ToList(); + var gosseExpense = expenses.First(e => e.Name == "Gosse"); + var pudelExpense = expenses.First(e => e.Name == "Pudel"); + var klingelExpense = expenses.First(e => e.Name == "Klingel"); + + // Day 1: 3 participants, 1 game, some expenses + var day1 = new Day + { + PostDate = now.AddDays(-14), + Status = DayStatus.Closed, + ClubId = clubId, + CreatedAt = now, + CreatedById = creatorId + }; + db.Days.Add(day1); + await db.SaveChangesAsync(); + + var day1Participants = members.Take(3).ToList(); + foreach (var p in day1Participants) + { + db.Set().Add(new DayPerson { DayId = day1.Id, ClubId = clubId, PersonId = p.Id }); + } + + var game1 = new Game + { + DayId = day1.Id, + ClubId = clubId, + GameType = "Training", + Status = GameStatus.Completed, + StartedAt = day1.PostDate, + CompletedAt = day1.PostDate.AddHours(2), + CreatedAt = now, + CreatedById = creatorId + }; + db.Games.Add(game1); + await db.SaveChangesAsync(); + + foreach (var p in day1Participants) + { + db.Set().Add(new GamePerson { GameId = game1.Id, ClubId = clubId, PersonId = p.Id }); + } + await db.SaveChangesAsync(); + + // Day 2: 5 participants, 2 games, diverse expenses + var day2 = new Day + { + PostDate = now.AddDays(-7), + Status = DayStatus.Closed, + ClubId = clubId, + CreatedAt = now, + CreatedById = creatorId + }; + db.Days.Add(day2); + await db.SaveChangesAsync(); + + var day2Participants = members.Take(5).ToList(); + foreach (var p in day2Participants) + { + db.Set().Add(new DayPerson { DayId = day2.Id, ClubId = clubId, PersonId = p.Id }); + } + + var game2a = new Game + { + DayId = day2.Id, + ClubId = clubId, + GameType = "Training", + Status = GameStatus.Completed, + StartedAt = day2.PostDate, + CompletedAt = day2.PostDate.AddHours(1), + CreatedAt = now, + CreatedById = creatorId + }; + var game2b = new Game + { + DayId = day2.Id, + ClubId = clubId, + GameType = "Totenkiste", + Status = GameStatus.Completed, + StartedAt = day2.PostDate.AddHours(1), + CompletedAt = day2.PostDate.AddHours(2), + CreatedAt = now, + CreatedById = creatorId + }; + db.Games.Add(game2a); + db.Games.Add(game2b); + await db.SaveChangesAsync(); + + foreach (var p in day2Participants) + { + db.Set().Add(new GamePerson { GameId = game2a.Id, ClubId = clubId, PersonId = p.Id }); + db.Set().Add(new GamePerson { GameId = game2b.Id, ClubId = clubId, PersonId = p.Id }); + } + await db.SaveChangesAsync(); + + // Day 3: 4 participants, 1 game (recent, still open) + var day3 = new Day + { + PostDate = now.AddDays(-1), + Status = DayStatus.Started, + ClubId = clubId, + CreatedAt = now, + CreatedById = creatorId + }; + db.Days.Add(day3); + await db.SaveChangesAsync(); + + var day3Participants = members.Take(4).ToList(); + foreach (var p in day3Participants) + { + db.Set().Add(new DayPerson { DayId = day3.Id, ClubId = clubId, PersonId = p.Id }); + } + + var game3 = new Game + { + DayId = day3.Id, + ClubId = clubId, + GameType = "Training", + Status = GameStatus.Active, + StartedAt = day3.PostDate, + CreatedAt = now, + CreatedById = creatorId + }; + db.Games.Add(game3); + await db.SaveChangesAsync(); + + foreach (var p in day3Participants) + { + db.Set().Add(new GamePerson { GameId = game3.Id, ClubId = clubId, PersonId = p.Id }); + } + await db.SaveChangesAsync(); + } +} diff --git a/src/Koogle.Infrastructure/DependencyInjection.cs b/src/Koogle.Infrastructure/DependencyInjection.cs index 38419cb..9178384 100644 --- a/src/Koogle.Infrastructure/DependencyInjection.cs +++ b/src/Koogle.Infrastructure/DependencyInjection.cs @@ -4,6 +4,7 @@ using Koogle.Infrastructure.Data; using Koogle.Infrastructure.Identity; using Koogle.Infrastructure.Repositories; using Koogle.Infrastructure.Security; +using Koogle.Infrastructure.Services; using KoogleApp.Data; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -84,6 +85,7 @@ public static class DependencyInjection // Services //services.AddScoped(); + services.AddScoped(); services.AddCascadingAuthenticationState(); diff --git a/src/Koogle.Infrastructure/Services/DemoResetService.cs b/src/Koogle.Infrastructure/Services/DemoResetService.cs new file mode 100644 index 0000000..bde69cf --- /dev/null +++ b/src/Koogle.Infrastructure/Services/DemoResetService.cs @@ -0,0 +1,78 @@ +using Koogle.Domain.Interfaces; +using Koogle.Infrastructure.Data; +using KoogleApp.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +namespace Koogle.Infrastructure.Services; + +/// +/// Service for resetting the Demo club to initial state. +/// +public class DemoResetService : IDemoResetService +{ + private readonly IServiceProvider _services; + private readonly IConfiguration _config; + private readonly AppDbContext _db; + private Guid? _demoClubId; + + public DemoResetService( + IServiceProvider services, + IConfiguration config, + AppDbContext db) + { + _services = services; + _config = config; + _db = db; + } + + /// + public async Task ResetDemoClubAsync(CancellationToken ct = default) + { + var clubId = await GetDemoClubIdAsync(ct); + if (!clubId.HasValue) return false; + + await DemoSeeder.ResetDemoClubAsync(_services, clubId.Value); + return true; + } + + /// + public bool IsDemoClub(Guid clubId) + { + var demoClubId = GetDemoClubId(); + return demoClubId.HasValue && demoClubId.Value == clubId; + } + + /// + public Guid? GetDemoClubId() + { + if (_demoClubId.HasValue) return _demoClubId; + + var demoEnabled = _config.GetValue("Bootstrap:Demo:Enabled"); + if (!demoEnabled) return null; + + var demoClubName = _config["Bootstrap:Demo:ClubName"] ?? "Demo"; + var club = _db.Clubs + .AsNoTracking() + .FirstOrDefault(c => c.Name == demoClubName && !c.IsDeleted); + + _demoClubId = club?.Id; + return _demoClubId; + } + + private async Task GetDemoClubIdAsync(CancellationToken ct) + { + if (_demoClubId.HasValue) return _demoClubId; + + var demoEnabled = _config.GetValue("Bootstrap:Demo:Enabled"); + if (!demoEnabled) return null; + + var demoClubName = _config["Bootstrap:Demo:ClubName"] ?? "Demo"; + var club = await _db.Clubs + .AsNoTracking() + .FirstOrDefaultAsync(c => c.Name == demoClubName && !c.IsDeleted, ct); + + _demoClubId = club?.Id; + return _demoClubId; + } +} diff --git a/src/Koogle.Web/Components/Pages/Account/Login.razor b/src/Koogle.Web/Components/Pages/Account/Login.razor index e8f9b12..b942917 100644 --- a/src/Koogle.Web/Components/Pages/Account/Login.razor +++ b/src/Koogle.Web/Components/Pages/Account/Login.razor @@ -6,6 +6,7 @@ @inject NavigationManager NavigationManager @inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery @inject IHttpContextAccessor HttpContextAccessor +@inject IConfiguration Configuration
@@ -28,6 +29,12 @@ { @_error } + @if (_demoEnabled) + { + + Demo-Zugang: @_demoEmail / @_demoPassword + + } Anmeldung @@ -93,6 +100,9 @@ private string _antiToken = ""; private bool _registered; private string? _inviteToken; + private bool _demoEnabled; + private string _demoEmail = ""; + private string _demoPassword = ""; protected override void OnInitialized() { @@ -119,6 +129,14 @@ var http = HttpContextAccessor.HttpContext!; var tokens = Antiforgery.GetAndStoreTokens(http); _antiToken = tokens.RequestToken!; + + // Demo credentials from configuration + _demoEnabled = Configuration.GetValue("Bootstrap:Demo:Enabled"); + if (_demoEnabled) + { + _demoEmail = Configuration["Bootstrap:Demo:Email"] ?? "demo@koogle.de"; + _demoPassword = Configuration["Bootstrap:Demo:Password"] ?? "demo123"; + } } } diff --git a/src/Koogle.Web/Components/Pages/Admin/Clubs.razor b/src/Koogle.Web/Components/Pages/Admin/Clubs.razor index 52c8f7a..d720273 100644 --- a/src/Koogle.Web/Components/Pages/Admin/Clubs.razor +++ b/src/Koogle.Web/Components/Pages/Admin/Clubs.razor @@ -5,14 +5,17 @@ @using Fluxor @using Koogle.Application.DTOs +@using Koogle.Domain.Interfaces @using Koogle.Domain.Enums @using Koogle.Web.Store.ClubState +@using Koogle.Web.Store.Demo @using Microsoft.AspNetCore.Authorization @inject IState ClubState @inject IDispatcher Dispatcher @inject ISnackbar Snackbar @inject IDialogService DialogService +@inject IDemoResetService DemoResetService Clubs verwalten @@ -66,6 +69,15 @@ Size="Size.Small" OnClick="@(() => OpenEditDialog(context))"/> + @if (DemoResetService.IsDemoClub(context.Id)) + { + + + + } ("Demo zurücksetzen", parameters); + var result = await dialog.Result; + + if (result != null && !result.Canceled) + { + Dispatcher.Dispatch(new ResetDemoAction()); + Snackbar.Add("Demo-Club wird zurückgesetzt...", Severity.Info); + } + } } diff --git a/src/Koogle.Web/Program.cs b/src/Koogle.Web/Program.cs index cbb6ae2..d46a632 100644 --- a/src/Koogle.Web/Program.cs +++ b/src/Koogle.Web/Program.cs @@ -107,6 +107,7 @@ using (var scope = app.Services.CreateScope()) } await BootstrapSeeder.SeedAsync(app.Services, app.Configuration, app.Environment); await BootstrapSeeder.SeedTriggersAsync(app.Services); +await DemoSeeder.SeedAsync(app.Services, app.Configuration, app.Environment); app.Run(); diff --git a/src/Koogle.Web/Store/Demo/DemoActions.cs b/src/Koogle.Web/Store/Demo/DemoActions.cs new file mode 100644 index 0000000..9cc0f29 --- /dev/null +++ b/src/Koogle.Web/Store/Demo/DemoActions.cs @@ -0,0 +1,16 @@ +namespace Koogle.Web.Store.Demo; + +/// +/// Action to trigger Demo club reset. +/// +public record ResetDemoAction; + +/// +/// Action dispatched when Demo reset succeeds. +/// +public record ResetDemoSuccessAction; + +/// +/// Action dispatched when Demo reset fails. +/// +public record ResetDemoFailureAction(string Error); diff --git a/src/Koogle.Web/Store/Demo/DemoEffects.cs b/src/Koogle.Web/Store/Demo/DemoEffects.cs new file mode 100644 index 0000000..dfdf414 --- /dev/null +++ b/src/Koogle.Web/Store/Demo/DemoEffects.cs @@ -0,0 +1,35 @@ +using Fluxor; +using Koogle.Domain.Interfaces; + +namespace Koogle.Web.Store.Demo; + +public class DemoEffects +{ + private readonly IDemoResetService _demoResetService; + + public DemoEffects(IDemoResetService demoResetService) + { + _demoResetService = demoResetService; + } + + [EffectMethod] + public async Task HandleResetDemo(ResetDemoAction action, IDispatcher dispatcher) + { + try + { + var success = await _demoResetService.ResetDemoClubAsync(); + if (success) + { + dispatcher.Dispatch(new ResetDemoSuccessAction()); + } + else + { + dispatcher.Dispatch(new ResetDemoFailureAction("Demo-Club nicht gefunden oder Demo-Modus nicht aktiviert.")); + } + } + catch (Exception ex) + { + dispatcher.Dispatch(new ResetDemoFailureAction(ex.Message)); + } + } +} diff --git a/src/Koogle.Web/appsettings.Development.json b/src/Koogle.Web/appsettings.Development.json index 81d7e0a..6dd18a0 100644 --- a/src/Koogle.Web/appsettings.Development.json +++ b/src/Koogle.Web/appsettings.Development.json @@ -1,5 +1,4 @@ { - "Bootstrap": { "EnableSeeding": true, "CreateDefaultClub": true, @@ -10,6 +9,12 @@ }, "DefaultClub": { "Name": "Demo" + }, + "Demo": { + "Enabled": true, + "Email": "demo@koogle.de", + "Password": "Demo123!", + "ClubName": "Demo" } }, "DetailedErrors": true,