AppDataInit.cs
6,697 bytes
| 1 | using SplitApp.Modules.Expenses.Domain.Entities; |
|---|---|
| 2 | using SplitApp.Modules.Expenses.Domain.Enums; |
| 3 | using SplitApp.Modules.Expenses.Infrastructure.Persistence; |
| 4 | using SplitApp.Modules.Trips.Domain.Entities; |
| 5 | using SplitApp.Modules.Trips.Domain.Enums; |
| 6 | using SplitApp.Modules.Trips.Infrastructure.Persistence; |
| 7 | using SplitApp.Shared.Kernel.Localization; |
| 8 | using SplitApp.WebApp.Application.UsersService; |
| 9 | |
| 10 | namespace SplitApp.WebApp.Hosting; |
| 11 | |
| 12 | public static class AppDataInit |
| 13 | { |
| 14 | public static void SeedExampleData(IServiceProvider services) |
| 15 | { |
| 16 | using var scope = services.CreateScope(); |
| 17 | var usersClient = scope.ServiceProvider.GetRequiredService<IUsersServiceClient>(); |
| 18 | var trips = scope.ServiceProvider.GetRequiredService<TripsDbContext>(); |
| 19 | var expenses = scope.ServiceProvider.GetRequiredService<ExpensesDbContext>(); |
| 20 | |
| 21 | if (trips.Trips.Any()) return; |
| 22 | |
| 23 | // Users live in the Users service now — fetch them via HTTP. The compose healthcheck |
| 24 | // gates webapp on users-service:service_healthy, so the seed users should already be |
| 25 | // present here. If the call times out, just skip example seeding (operator can rerun). |
| 26 | List<SplitApp.WebApp.Application.UsersService.Dtos.AdminUserListItem> allUsers; |
| 27 | try |
| 28 | { |
| 29 | allUsers = usersClient.ListUsersAsync().GetAwaiter().GetResult().ToList(); |
| 30 | } |
| 31 | catch |
| 32 | { |
| 33 | return; |
| 34 | } |
| 35 | |
| 36 | Guid IdOf(string email) => allUsers.FirstOrDefault(u => |
| 37 | string.Equals(u.Email, email, StringComparison.OrdinalIgnoreCase))?.Id ?? Guid.Empty; |
| 38 | |
| 39 | var admin = IdOf("admin@example.com"); |
| 40 | var testUser = IdOf("user@example.com"); |
| 41 | var alice = IdOf("alice@example.com"); |
| 42 | var bob = IdOf("bob@example.com"); |
| 43 | var charlie = IdOf("charlie@example.com"); |
| 44 | var diana = IdOf("diana@example.com"); |
| 45 | |
| 46 | if (admin == Guid.Empty || testUser == Guid.Empty || alice == Guid.Empty |
| 47 | || bob == Guid.Empty || charlie == Guid.Empty || diana == Guid.Empty) |
| 48 | { |
| 49 | return; |
| 50 | } |
| 51 | |
| 52 | var eur = expenses.Currencies.First(c => c.Code == "EUR").Id; |
| 53 | var usd = expenses.Currencies.First(c => c.Code == "USD").Id; |
| 54 | var gbp = expenses.Currencies.First(c => c.Code == "GBP").Id; |
| 55 | |
| 56 | var now = DateTime.UtcNow; |
| 57 | |
| 58 | var trip1 = new Trip |
| 59 | { |
| 60 | Name = "Barcelona Weekend", |
| 61 | Description = "A long weekend exploring Barcelona with friends", |
| 62 | Destination = "Barcelona, Spain", |
| 63 | StartDate = new DateTime(2026, 4, 10, 0, 0, 0, DateTimeKind.Utc), |
| 64 | EndDate = new DateTime(2026, 4, 13, 0, 0, 0, DateTimeKind.Utc), |
| 65 | Status = ETripStatus.Active, |
| 66 | DefaultCurrencyId = eur, |
| 67 | CreatedById = admin, |
| 68 | }; |
| 69 | trips.Trips.Add(trip1); |
| 70 | |
| 71 | trips.TripParticipants.AddRange( |
| 72 | new TripParticipant { TripId = trip1.Id, UserId = admin, Role = EParticipantRole.Organizer, JoinedAt = now.AddDays(-10), IsActive = true }, |
| 73 | new TripParticipant { TripId = trip1.Id, UserId = alice, Role = EParticipantRole.Participant, JoinedAt = now.AddDays(-9), IsActive = true }, |
| 74 | new TripParticipant { TripId = trip1.Id, UserId = bob, Role = EParticipantRole.Participant, JoinedAt = now.AddDays(-8), IsActive = true }, |
| 75 | new TripParticipant { TripId = trip1.Id, UserId = charlie, Role = EParticipantRole.Participant, JoinedAt = now.AddDays(-7), IsActive = true }); |
| 76 | |
| 77 | var cat1Food = new BudgetCategory { TripId = trip1.Id, Name = Lang("Food & Drinks", "Toit ja joogid"), IconName = "cup-hot", PlannedAmount = 400m, DisplayOrder = 0 }; |
| 78 | var cat1Transport = new BudgetCategory { TripId = trip1.Id, Name = Lang("Transport", "Transport"), IconName = "bus-front", PlannedAmount = 150m, DisplayOrder = 1 }; |
| 79 | var cat1Activities = new BudgetCategory { TripId = trip1.Id, Name = Lang("Activities", "Tegevused"), IconName = "binoculars", PlannedAmount = 200m, DisplayOrder = 2 }; |
| 80 | var cat1Accommodation = new BudgetCategory { TripId = trip1.Id, Name = Lang("Accommodation", "Majutus"), IconName = "house", PlannedAmount = 500m, DisplayOrder = 3 }; |
| 81 | trips.BudgetCategories.AddRange(cat1Food, cat1Transport, cat1Activities, cat1Accommodation); |
| 82 | |
| 83 | trips.SaveChanges(); |
| 84 | |
| 85 | // Slimmed seed: keep one trip with example expenses so the UX has data on first boot. |
| 86 | // The full phase-3 fixture is preserved in source history; truncated here to keep the |
| 87 | // post-extraction monolith boot path simple. |
| 88 | var trip1Members = new[] { admin, alice, bob, charlie }; |
| 89 | var trip1Expenses = new (string desc, decimal amount, Guid paidBy, Guid? catId, DateTime date, ESplitMethod split)[] |
| 90 | { |
| 91 | ("Airbnb apartment (3 nights)", 480.00m, admin, cat1Accommodation.Id, now.AddDays(-5), ESplitMethod.EqualAll), |
| 92 | ("Airport taxi", 35.00m, alice, cat1Transport.Id, now.AddDays(-5), ESplitMethod.EqualAll), |
| 93 | ("Grocery shopping", 62.50m, bob, cat1Food.Id, now.AddDays(-4), ESplitMethod.EqualAll), |
| 94 | ("Sagrada Familia tickets", 104.00m, charlie, cat1Activities.Id, now.AddDays(-3), ESplitMethod.EqualAll), |
| 95 | }; |
| 96 | |
| 97 | foreach (var (desc, amount, paidBy, catId, date, split) in trip1Expenses) |
| 98 | { |
| 99 | var ex = new Expense |
| 100 | { |
| 101 | TripId = trip1.Id, PaidByUserId = paidBy, Amount = amount, |
| 102 | Description = desc, ExpenseDate = date, BudgetCategoryId = catId, |
| 103 | CurrencyId = eur, SplitMethod = split, |
| 104 | }; |
| 105 | expenses.Expenses.Add(ex); |
| 106 | AddEqualSplits(expenses, ex, trip1Members); |
| 107 | } |
| 108 | |
| 109 | expenses.SaveChanges(); |
| 110 | |
| 111 | _ = testUser; _ = diana; _ = usd; _ = gbp; |
| 112 | } |
| 113 | |
| 114 | private static LangStr Lang(string en, string et) |
| 115 | { |
| 116 | var s = new LangStr(en, "en"); |
| 117 | s["et"] = et; |
| 118 | return s; |
| 119 | } |
| 120 | |
| 121 | private static void AddEqualSplits(ExpensesDbContext db, Expense expense, Guid[] memberIds) |
| 122 | { |
| 123 | var count = memberIds.Length; |
| 124 | var baseAmt = Math.Floor(expense.Amount / count * 100m) / 100m; |
| 125 | var remainderCents = (int)Math.Round((expense.Amount - baseAmt * count) * 100m); |
| 126 | |
| 127 | for (var i = 0; i < count; i++) |
| 128 | { |
| 129 | var splitAmt = baseAmt + (i < remainderCents ? 0.01m : 0m); |
| 130 | db.ExpenseSplits.Add(new ExpenseSplit |
| 131 | { |
| 132 | ExpenseId = expense.Id, |
| 133 | UserId = memberIds[i], |
| 134 | Amount = splitAmt, |
| 135 | }); |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | |