profileShare

rasmusjy / splitapp-backend-modular-monolith

Read-only snapshot

No repository description.

main default branch 418 files Expires Sep 13, 2026, 9:06 AM
AdminStatsService.cs 8,583 bytes
1 using SplitApp.WebApp.Application.DTO;
2 using SplitApp.WebApp.Application.Mappers;
3 using SplitApp.Modules.Trips.Domain.Entities;
4 using SplitApp.Modules.Trips.Domain.Enums;
5 using SplitApp.Modules.Expenses.Domain.Entities;
6 using SplitApp.Modules.Expenses.Domain.Enums;
7 using SplitApp.Modules.Users.Domain.Entities;
8 using SplitApp.WebApp.Application.Contracts;
9
10 namespace SplitApp.WebApp.Application.Services.Admin;
11
12 public class AdminStatsService : IAdminStatsService
13 {
14 private readonly IAppUnitOfWork _uow;
15
16 public AdminStatsService(IAppUnitOfWork uow)
17 {
18 _uow = uow;
19 }
20
21 public async Task<AdminDashboardData> GetDashboardStatsAsync()
22 {
23 var trips = (await _uow.Trips.GetAllAsync()).ToList();
24 var expenses = (await _uow.Expenses.GetAllAsync()).ToList();
25 var budgetCategories = (await _uow.BudgetCategories.GetAllAsync()).ToList();
26 var settlementPlans = (await _uow.SettlementPlans.GetAllAsync()).ToList();
27 var wishlistItems = (await _uow.TripWishlistItems.GetAllAsync()).ToList();
28 var polls = (await _uow.TripPolls.GetAllAsync()).ToList();
29 var invitations = (await _uow.TripInvitations.GetAllAsync()).ToList();
30 var currencies = (await _uow.GetRepository<Currency>().GetAllAsync()).ToList();
31 var participants = (await _uow.TripParticipants.GetAllAsync()).ToList();
32 var settlementPayments = (await _uow.SettlementPayments.GetAllAsync()).ToList();
33 var allUsers = (await _uow.Users.GetAllAsync()).ToList();
34
35 // === Counts ===
36 var tripCount = trips.Count;
37 var userCount = allUsers.Count;
38 var expenseCount = expenses.Count;
39
40 // === Trip status breakdown ===
41 var activeTrips = trips.Count(t => t.Status == ETripStatus.Active);
42 var settledTrips = trips.Count(t => t.Status == ETripStatus.Settled);
43 var archivedTrips = trips.Count(t => t.Status == ETripStatus.Archived);
44
45 var totalExpenseAmount = expenses.Sum(e => e.Amount);
46
47 var pendingSettlements = settlementPlans.Count(s => s.Status == ESettlementStatus.Pending);
48 var inProgressSettlements = settlementPlans.Count(s => s.Status == ESettlementStatus.InProgress);
49 var completedSettlements = settlementPlans.Count(s => s.Status == ESettlementStatus.Completed);
50
51 var recentTrips = TripBllDtoFactory.CreateList(trips.OrderByDescending(t => t.CreatedAt).Take(5));
52 var recentExpenses = ExpenseBllDtoFactory.CreateList(expenses.OrderByDescending(e => e.ExpenseDate).Take(8));
53 var recentUsers = (await _uow.Users.GetRecentAsync(5)).Select(u => new AppUserBllDto
54 {
55 Id = u.Id,
56 FirstName = u.FirstName,
57 LastName = u.LastName,
58 Email = u.Email
59 }).ToList();
60
61 var pendingInvitations = invitations.Count(i => i.Status == EInvitationStatus.Pending);
62 var pendingPayments = settlementPayments.Count(p => p.Status == EPaymentStatus.Pending);
63 var markedPaidPayments = settlementPayments.Count(p => p.Status == EPaymentStatus.MarkedPaid);
64
65 // === Top Active Trips ===
66 var topActiveTrips = trips
67 .Where(t => t.Status == ETripStatus.Active)
68 .Select(t =>
69 {
70 var tripExpenses = expenses.Where(e => e.TripId == t.Id).ToList();
71 return new AdminTopActiveTripItem
72 {
73 Trip = TripBllDtoFactory.Create(t),
74 ParticipantCount = participants.Count(p => p.TripId == t.Id),
75 ExpenseSum = tripExpenses.Sum(e => e.Amount),
76 ExpenseCount = tripExpenses.Count
77 };
78 })
79 .OrderByDescending(x => x.ExpenseSum)
80 .ThenByDescending(x => x.ParticipantCount)
81 .Take(5)
82 .ToList();
83
84 // === Top 10 biggest expenses ===
85 var biggestExpensesEntities = expenses
86 .OrderByDescending(e => e.Amount)
87 .Take(10)
88 .ToList();
89 foreach (var e in biggestExpensesEntities)
90 {
91 e.Trip ??= trips.FirstOrDefault(t => t.Id == e.TripId);
92 e.PaidByUser ??= allUsers.FirstOrDefault(u => u.Id == e.PaidByUserId);
93 }
94 var biggestExpenses = ExpenseBllDtoFactory.CreateList(biggestExpensesEntities);
95
96 // === Active users in last N days ===
97 var now = DateTime.UtcNow;
98 var cutoff7 = now.AddDays(-7);
99 var cutoff30 = now.AddDays(-30);
100
101 var activeUserIds7 = expenses.Where(e => e.CreatedAt >= cutoff7)
102 .Select(e => e.PaidByUserId).Distinct().Count();
103 var activeUserIds30 = expenses.Where(e => e.CreatedAt >= cutoff30)
104 .Select(e => e.PaidByUserId).Distinct().Count();
105
106 // === Top 5 most active users by expense count ===
107 var topActiveUsers = expenses
108 .GroupBy(e => e.PaidByUserId)
109 .Select(g =>
110 {
111 var user = allUsers.FirstOrDefault(u => u.Id == g.Key);
112 return new AdminTopActiveUserItem
113 {
114 UserId = g.Key,
115 Email = user?.Email ?? "—",
116 FullName = user != null ? $"{user.FirstName} {user.LastName}".Trim() : "—",
117 ExpenseCount = g.Count(),
118 TotalAmount = g.Sum(e => e.Amount)
119 };
120 })
121 .OrderByDescending(x => x.ExpenseCount)
122 .Take(5)
123 .ToList();
124
125 // === Activity feed (chronological) ===
126 var feed = new List<AdminActivityFeedItem>();
127 foreach (var t in trips)
128 {
129 feed.Add(new AdminActivityFeedItem
130 {
131 Type = "trip",
132 Date = t.CreatedAt,
133 MessageKey = "Trip \"{0}\" created",
134 MessageArgs = new object[] { t.Name },
135 IconCssClass = "bi-suitcase-lg",
136 BadgeCssClass = "bg-primary"
137 });
138 }
139 foreach (var e in expenses.OrderByDescending(x => x.CreatedAt).Take(30))
140 {
141 var tripName = trips.FirstOrDefault(t => t.Id == e.TripId)?.Name ?? "?";
142 feed.Add(new AdminActivityFeedItem
143 {
144 Type = "expense",
145 Date = e.CreatedAt,
146 MessageKey = "Expense {0:0.00} added to \"{1}\"",
147 MessageArgs = new object[] { e.Amount, tripName },
148 IconCssClass = "bi-cash-coin",
149 BadgeCssClass = "bg-success"
150 });
151 }
152 foreach (var s in settlementPlans)
153 {
154 var tripName = trips.FirstOrDefault(t => t.Id == s.TripId)?.Name ?? "?";
155 feed.Add(new AdminActivityFeedItem
156 {
157 Type = "settlement",
158 Date = s.CreatedAt,
159 MessageKey = "Settlement plan for \"{0}\" ({1})",
160 MessageArgs = new object[] { tripName, s.Status },
161 IconCssClass = "bi-diagram-3",
162 BadgeCssClass = "bg-info"
163 });
164 }
165 var activityFeed = feed.OrderByDescending(f => f.Date).Take(15).ToList();
166
167 return new AdminDashboardData
168 {
169 TripCount = tripCount,
170 UserCount = userCount,
171 ExpenseCount = expenseCount,
172 CategoryCount = budgetCategories.Count,
173 SettlementCount = settlementPlans.Count,
174 WishlistCount = wishlistItems.Count,
175 PollCount = polls.Count,
176 InvitationCount = invitations.Count,
177 CurrencyCount = currencies.Count,
178 ParticipantCount = participants.Count,
179
180 ActiveTrips = activeTrips,
181 SettledTrips = settledTrips,
182 ArchivedTrips = archivedTrips,
183 TotalExpenseAmount = totalExpenseAmount,
184 PendingSettlements = pendingSettlements,
185 InProgressSettlements = inProgressSettlements,
186 CompletedSettlements = completedSettlements,
187 PendingInvitations = pendingInvitations,
188 PendingPayments = pendingPayments,
189 MarkedPaidPayments = markedPaidPayments,
190
191 RecentTrips = recentTrips,
192 RecentExpenses = recentExpenses,
193 RecentUsers = recentUsers,
194
195 TopActiveTrips = topActiveTrips,
196 BiggestExpenses = biggestExpenses,
197 NewUsersLast7Days = activeUserIds7,
198 NewUsersLast30Days = activeUserIds30,
199 TopActiveUsers = topActiveUsers,
200 ActivityFeed = activityFeed
201 };
202 }
203 }
204