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
This commit is contained in:
parent
fcfbbae94e
commit
5c088345b3
|
|
@ -0,0 +1,23 @@
|
|||
namespace Koogle.Domain.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Service for resetting the Demo club to initial state.
|
||||
/// </summary>
|
||||
public interface IDemoResetService
|
||||
{
|
||||
/// <summary>
|
||||
/// Resets the Demo club to initial state (hard delete + re-seed).
|
||||
/// </summary>
|
||||
/// <returns>True if reset was successful, false if Demo club not found or not enabled.</returns>
|
||||
Task<bool> ResetDemoClubAsync(CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given club ID is the Demo club.
|
||||
/// </summary>
|
||||
bool IsDemoClub(Guid clubId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Demo club ID if Demo mode is enabled.
|
||||
/// </summary>
|
||||
Guid? GetDemoClubId();
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Seeds demo data for the Demo club including user, persons, expenses, and sample days.
|
||||
/// </summary>
|
||||
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" };
|
||||
|
||||
/// <summary>
|
||||
/// Seeds demo user, club membership, persons, expenses, triggers and sample days.
|
||||
/// </summary>
|
||||
public static async Task SeedAsync(IServiceProvider services, IConfiguration config, IHostEnvironment env)
|
||||
{
|
||||
var demoEnabled = config.GetValue<bool>("Bootstrap:Demo:Enabled");
|
||||
if (!demoEnabled) return;
|
||||
|
||||
var allowSeed = env.IsDevelopment() || config.GetValue<bool>("Bootstrap:EnableSeeding");
|
||||
if (!allowSeed) return;
|
||||
|
||||
using var scope = services.CreateScope();
|
||||
|
||||
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||
var appDb = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
var triggerRepo = scope.ServiceProvider.GetRequiredService<ITriggerRepository>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the Demo club to initial state: hard delete all data, then re-seed.
|
||||
/// </summary>
|
||||
public static async Task ResetDemoClubAsync(IServiceProvider services, Guid clubId)
|
||||
{
|
||||
using var scope = services.CreateScope();
|
||||
var appDb = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
var triggerRepo = scope.ServiceProvider.GetRequiredService<ITriggerRepository>();
|
||||
|
||||
// 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<Person>();
|
||||
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<Expense>();
|
||||
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<Person> persons,
|
||||
List<Expense> 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<DayPerson>().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<GamePerson>().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<DayPerson>().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<GamePerson>().Add(new GamePerson { GameId = game2a.Id, ClubId = clubId, PersonId = p.Id });
|
||||
db.Set<GamePerson>().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<DayPerson>().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<GamePerson>().Add(new GamePerson { GameId = game3.Id, ClubId = clubId, PersonId = p.Id });
|
||||
}
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<IEmailService, StubEmailService>();
|
||||
services.AddScoped<IDemoResetService, DemoResetService>();
|
||||
|
||||
services.AddCascadingAuthenticationState();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Service for resetting the Demo club to initial state.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> ResetDemoClubAsync(CancellationToken ct = default)
|
||||
{
|
||||
var clubId = await GetDemoClubIdAsync(ct);
|
||||
if (!clubId.HasValue) return false;
|
||||
|
||||
await DemoSeeder.ResetDemoClubAsync(_services, clubId.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDemoClub(Guid clubId)
|
||||
{
|
||||
var demoClubId = GetDemoClubId();
|
||||
return demoClubId.HasValue && demoClubId.Value == clubId;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid? GetDemoClubId()
|
||||
{
|
||||
if (_demoClubId.HasValue) return _demoClubId;
|
||||
|
||||
var demoEnabled = _config.GetValue<bool>("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<Guid?> GetDemoClubIdAsync(CancellationToken ct)
|
||||
{
|
||||
if (_demoClubId.HasValue) return _demoClubId;
|
||||
|
||||
var demoEnabled = _config.GetValue<bool>("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;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
@inject NavigationManager NavigationManager
|
||||
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery
|
||||
@inject IHttpContextAccessor HttpContextAccessor
|
||||
@inject IConfiguration Configuration
|
||||
|
||||
|
||||
<form method="post" action="/auth/login">
|
||||
|
|
@ -28,6 +29,12 @@
|
|||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mb-3">@_error</MudAlert>
|
||||
}
|
||||
@if (_demoEnabled)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
||||
<strong>Demo-Zugang:</strong> @_demoEmail / @_demoPassword
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.h5" Class="mb-4">
|
||||
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<bool>("Bootstrap:Demo:Enabled");
|
||||
if (_demoEnabled)
|
||||
{
|
||||
_demoEmail = Configuration["Bootstrap:Demo:Email"] ?? "demo@koogle.de";
|
||||
_demoPassword = Configuration["Bootstrap:Demo:Password"] ?? "demo123";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> ClubState
|
||||
@inject IDispatcher Dispatcher
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
@inject IDemoResetService DemoResetService
|
||||
|
||||
<PageTitle>Clubs verwalten</PageTitle>
|
||||
|
||||
|
|
@ -66,6 +69,15 @@
|
|||
Size="Size.Small"
|
||||
OnClick="@(() => OpenEditDialog(context))"/>
|
||||
</MudTooltip>
|
||||
@if (DemoResetService.IsDemoClub(context.Id))
|
||||
{
|
||||
<MudTooltip Text="Demo zuruecksetzen">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.RestartAlt"
|
||||
Size="Size.Small"
|
||||
Color="Color.Warning"
|
||||
OnClick="@(() => ConfirmResetDemo(context))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
<MudTooltip Text="Löschen">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Size="Size.Small"
|
||||
|
|
@ -167,4 +179,23 @@
|
|||
Snackbar.Add("Club wird gelöscht...", Severity.Info);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConfirmResetDemo(ClubDto club)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ "ContentText", $"Möchtest du den Demo-Club \"{club.Name}\" auf den Ausgangszustand zurücksetzen? Alle Spieltage, Spiele und Kosten werden gelöscht." },
|
||||
{ "ButtonText", "Zurücksetzen" },
|
||||
{ "Color", Color.Warning }
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<ConfirmDialog>("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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
namespace Koogle.Web.Store.Demo;
|
||||
|
||||
/// <summary>
|
||||
/// Action to trigger Demo club reset.
|
||||
/// </summary>
|
||||
public record ResetDemoAction;
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when Demo reset succeeds.
|
||||
/// </summary>
|
||||
public record ResetDemoSuccessAction;
|
||||
|
||||
/// <summary>
|
||||
/// Action dispatched when Demo reset fails.
|
||||
/// </summary>
|
||||
public record ResetDemoFailureAction(string Error);
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue