profileShare

rasmusjy / splitapp-backend-microservices

Read-only snapshot

No repository description.

main default branch 501 files Expires Sep 13, 2026, 9:06 AM
CrossModuleNavigationLoader.cs 7,061 bytes
1 using Microsoft.EntityFrameworkCore;
2 using SplitApp.Modules.Expenses.Domain.Entities;
3 using SplitApp.Modules.Expenses.Infrastructure.Persistence;
4 using SplitApp.Modules.Trips.Domain.Entities;
5 using SplitApp.Modules.Trips.Infrastructure.Persistence;
6 using SplitApp.Shared.Contracts.Users;
7 using SplitApp.Shared.Messaging.Integration.Users;
8
9 namespace SplitApp.WebApp.Application.Persistence;
10
11 /// <summary>
12 /// Composition-root helper that hydrates the [NotMapped] cross-module nav properties
13 /// after entities have been loaded by their owning module's DbContext. EF can't follow
14 /// these because the FK crosses schemas (and now process boundaries) — the WebApp does
15 /// it manually via <see cref="IUserLookup"/> (RabbitMQ RPC) for users and the in-process
16 /// DbContexts for trips/currencies.
17 /// </summary>
18 public class CrossModuleNavigationLoader
19 {
20 private readonly IUserLookup _users;
21 private readonly TripsDbContext _trips;
22 private readonly ExpensesDbContext _expenses;
23
24 public CrossModuleNavigationLoader(IUserLookup users, TripsDbContext trips, ExpensesDbContext expenses)
25 {
26 _users = users;
27 _trips = trips;
28 _expenses = expenses;
29 }
30
31 private async Task<IReadOnlyDictionary<Guid, UserDto>> UsersByIdAsync(IEnumerable<Guid> ids)
32 => await _users.GetByIdsAsync(ids);
33
34 private async Task<Dictionary<Guid, Currency>> CurrenciesByIdAsync(IEnumerable<Guid> ids)
35 {
36 var idList = ids.Where(id => id != Guid.Empty).Distinct().ToList();
37 if (idList.Count == 0) return new Dictionary<Guid, Currency>();
38 return await _expenses.Currencies.Where(c => idList.Contains(c.Id)).ToDictionaryAsync(c => c.Id);
39 }
40
41 private async Task<Dictionary<Guid, Trip>> TripsByIdAsync(IEnumerable<Guid> ids)
42 {
43 var idList = ids.Where(id => id != Guid.Empty).Distinct().ToList();
44 if (idList.Count == 0) return new Dictionary<Guid, Trip>();
45 return await _trips.Trips.Where(t => idList.Contains(t.Id)).ToDictionaryAsync(t => t.Id);
46 }
47
48 public async Task PopulateAsync(Trip? trip)
49 {
50 if (trip == null) return;
51 var users = await UsersByIdAsync(new[] { trip.CreatedById });
52 var currencies = await CurrenciesByIdAsync(new[] { trip.DefaultCurrencyId });
53 if (users.TryGetValue(trip.CreatedById, out var creator)) trip.CreatedBy = creator;
54 if (currencies.TryGetValue(trip.DefaultCurrencyId, out var currency)) trip.DefaultCurrency = currency;
55 if (trip.Participants != null)
56 {
57 var pUsers = await UsersByIdAsync(trip.Participants.Select(p => p.UserId));
58 foreach (var p in trip.Participants)
59 if (pUsers.TryGetValue(p.UserId, out var u)) p.User = u;
60 }
61 if (trip.Invitations != null)
62 {
63 var iUsers = await UsersByIdAsync(trip.Invitations.Select(i => i.InvitedByUserId));
64 foreach (var i in trip.Invitations)
65 if (iUsers.TryGetValue(i.InvitedByUserId, out var u)) i.InvitedByUser = u;
66 }
67 }
68
69 public async Task PopulateAsync(IEnumerable<Trip> trips)
70 {
71 foreach (var t in trips) await PopulateAsync(t);
72 }
73
74 public async Task PopulateAsync(TripParticipant? p)
75 {
76 if (p == null) return;
77 var users = await UsersByIdAsync(new[] { p.UserId });
78 if (users.TryGetValue(p.UserId, out var u)) p.User = u;
79 }
80
81 public async Task PopulateAsync(IEnumerable<TripParticipant> ps)
82 {
83 var ids = ps.Select(p => p.UserId).ToList();
84 var users = await UsersByIdAsync(ids);
85 foreach (var p in ps) if (users.TryGetValue(p.UserId, out var u)) p.User = u;
86 }
87
88 public async Task PopulateAsync(Expense? e)
89 {
90 if (e == null) return;
91 var users = await UsersByIdAsync(new[] { e.PaidByUserId });
92 if (users.TryGetValue(e.PaidByUserId, out var u)) e.PaidByUser = u;
93 if (e.Splits != null)
94 {
95 var splitUsers = await UsersByIdAsync(e.Splits.Select(s => s.UserId));
96 foreach (var s in e.Splits)
97 if (splitUsers.TryGetValue(s.UserId, out var su)) s.User = su;
98 }
99 }
100
101 public async Task PopulateAsync(IEnumerable<Expense> es)
102 {
103 foreach (var e in es) await PopulateAsync(e);
104 }
105
106 public async Task PopulateAsync(SettlementPlan? plan)
107 {
108 if (plan == null) return;
109 var users = await UsersByIdAsync(
110 new[] { plan.CreatedByUserId }
111 .Concat(plan.Payments?.Select(p => p.FromUserId) ?? Enumerable.Empty<Guid>())
112 .Concat(plan.Payments?.Select(p => p.ToUserId) ?? Enumerable.Empty<Guid>()));
113 if (users.TryGetValue(plan.CreatedByUserId, out var creator)) plan.CreatedByUser = creator;
114 if (plan.Payments != null)
115 foreach (var p in plan.Payments)
116 {
117 if (users.TryGetValue(p.FromUserId, out var fu)) p.FromUser = fu;
118 if (users.TryGetValue(p.ToUserId, out var tu)) p.ToUser = tu;
119 }
120 }
121
122 public async Task PopulateAsync(SettlementPayment? p)
123 {
124 if (p == null) return;
125 var users = await UsersByIdAsync(new[] { p.FromUserId, p.ToUserId });
126 if (users.TryGetValue(p.FromUserId, out var fu)) p.FromUser = fu;
127 if (users.TryGetValue(p.ToUserId, out var tu)) p.ToUser = tu;
128 }
129
130 public async Task PopulateAsync(SplitPreset? sp)
131 {
132 if (sp == null) return;
133 var users = await UsersByIdAsync(
134 new[] { sp.CreatedById }
135 .Concat(sp.Members?.Select(m => m.UserId) ?? Enumerable.Empty<Guid>()));
136 if (users.TryGetValue(sp.CreatedById, out var creator)) sp.CreatedBy = creator;
137 if (sp.Members != null)
138 foreach (var m in sp.Members)
139 if (users.TryGetValue(m.UserId, out var u)) m.User = u;
140 }
141
142 public async Task PopulateAsync(IEnumerable<SplitPreset> sps)
143 {
144 foreach (var sp in sps) await PopulateAsync(sp);
145 }
146
147 public async Task PopulateAsync(TripPoll? poll)
148 {
149 if (poll == null) return;
150 var users = await UsersByIdAsync(new[] { poll.CreatedByUserId });
151 if (users.TryGetValue(poll.CreatedByUserId, out var u)) poll.CreatedByUser = u;
152 }
153
154 public async Task PopulateAsync(IEnumerable<TripPoll> polls)
155 {
156 foreach (var p in polls) await PopulateAsync(p);
157 }
158
159 public async Task PopulateAsync(TripWishlistItem? item)
160 {
161 if (item == null) return;
162 var users = await UsersByIdAsync(new[] { item.AddedByUserId });
163 if (users.TryGetValue(item.AddedByUserId, out var u)) item.AddedByUser = u;
164 }
165
166 public async Task PopulateAsync(IEnumerable<TripWishlistItem> items)
167 {
168 foreach (var i in items) await PopulateAsync(i);
169 }
170
171 public async Task PopulateAsync(TripInvitation? inv)
172 {
173 if (inv == null) return;
174 var users = await UsersByIdAsync(new[] { inv.InvitedByUserId });
175 if (users.TryGetValue(inv.InvitedByUserId, out var u)) inv.InvitedByUser = u;
176 }
177 }
178