profileShare

rasmusjy / splitapp-backend-microservices

Read-only snapshot

No repository description.

main default branch 501 files Expires Sep 13, 2026, 9:06 AM
AdminStatsService.cs 8,607 bytes
1 using SplitApp.Modules.Expenses.Domain.Entities;
2 using SplitApp.Modules.Expenses.Domain.Enums;
3 using SplitApp.Modules.Trips.Domain.Enums;
4 using SplitApp.Shared.Contracts.Users;
5 using SplitApp.WebApp.Application.Contracts;
6 using SplitApp.WebApp.Application.DTO;
7 using SplitApp.WebApp.Application.Mappers;
8 using SplitApp.WebApp.Application.UsersService;
9
10 namespace SplitApp.WebApp.Application.Services.Admin;
11
12 public class AdminStatsService : IAdminStatsService
13 {
14 private readonly IAppUnitOfWork _uow;
15 private readonly IUsersServiceClient _usersService;
16
17 public AdminStatsService(IAppUnitOfWork uow, IUsersServiceClient usersService)
18 {
19 _uow = uow;
20 _usersService = usersService;
21 }
22
23 public async Task<AdminDashboardData> GetDashboardStatsAsync()
24 {
25 var trips = (await _uow.Trips.GetAllAsync()).ToList();
26 var expenses = (await _uow.Expenses.GetAllAsync()).ToList();
27 var budgetCategories = (await _uow.BudgetCategories.GetAllAsync()).ToList();
28 var settlementPlans = (await _uow.SettlementPlans.GetAllAsync()).ToList();
29 var wishlistItems = (await _uow.TripWishlistItems.GetAllAsync()).ToList();
30 var polls = (await _uow.TripPolls.GetAllAsync()).ToList();
31 var invitations = (await _uow.TripInvitations.GetAllAsync()).ToList();
32 var currencies = (await _uow.GetRepository<Currency>().GetAllAsync()).ToList();
33 var participants = (await _uow.TripParticipants.GetAllAsync()).ToList();
34 var settlementPayments = (await _uow.SettlementPayments.GetAllAsync()).ToList();
35 var allUsers = (await _usersService.ListUsersAsync()).ToList();
36
37 var tripCount = trips.Count;
38 var userCount = allUsers.Count;
39 var expenseCount = expenses.Count;
40
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 = allUsers.OrderByDescending(u => u.Email).Take(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 var topActiveTrips = trips
66 .Where(t => t.Status == ETripStatus.Active)
67 .Select(t =>
68 {
69 var tripExpenses = expenses.Where(e => e.TripId == t.Id).ToList();
70 return new AdminTopActiveTripItem
71 {
72 Trip = TripBllDtoFactory.Create(t),
73 ParticipantCount = participants.Count(p => p.TripId == t.Id),
74 ExpenseSum = tripExpenses.Sum(e => e.Amount),
75 ExpenseCount = tripExpenses.Count,
76 };
77 })
78 .OrderByDescending(x => x.ExpenseSum)
79 .ThenByDescending(x => x.ParticipantCount)
80 .Take(5)
81 .ToList();
82
83 var biggestExpensesEntities = expenses
84 .OrderByDescending(e => e.Amount)
85 .Take(10)
86 .ToList();
87 var usersById = allUsers.ToDictionary(u => u.Id);
88 foreach (var e in biggestExpensesEntities)
89 {
90 e.Trip ??= trips.FirstOrDefault(t => t.Id == e.TripId);
91 if (e.PaidByUser is null && usersById.TryGetValue(e.PaidByUserId, out var u))
92 {
93 e.PaidByUser = new UserDto(u.Id, $"{u.FirstName} {u.LastName}".Trim(), u.Email);
94 }
95 }
96 var biggestExpenses = ExpenseBllDtoFactory.CreateList(biggestExpensesEntities);
97
98 var now = DateTime.UtcNow;
99 var cutoff7 = now.AddDays(-7);
100 var cutoff30 = now.AddDays(-30);
101
102 var activeUserIds7 = expenses.Where(e => e.CreatedAt >= cutoff7)
103 .Select(e => e.PaidByUserId).Distinct().Count();
104 var activeUserIds30 = expenses.Where(e => e.CreatedAt >= cutoff30)
105 .Select(e => e.PaidByUserId).Distinct().Count();
106
107 var topActiveUsers = expenses
108 .GroupBy(e => e.PaidByUserId)
109 .Select(g =>
110 {
111 usersById.TryGetValue(g.Key, out var user);
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 var feed = new List<AdminActivityFeedItem>();
126 foreach (var t in trips)
127 {
128 feed.Add(new AdminActivityFeedItem
129 {
130 Type = "trip",
131 Date = t.CreatedAt,
132 MessageKey = "Trip \"{0}\" created",
133 MessageArgs = new object[] { t.Name },
134 IconCssClass = "bi-suitcase-lg",
135 BadgeCssClass = "bg-primary",
136 });
137 }
138 foreach (var e in expenses.OrderByDescending(x => x.CreatedAt).Take(30))
139 {
140 var tripName = trips.FirstOrDefault(t => t.Id == e.TripId)?.Name ?? "?";
141 feed.Add(new AdminActivityFeedItem
142 {
143 Type = "expense",
144 Date = e.CreatedAt,
145 MessageKey = "Expense {0:0.00} added to \"{1}\"",
146 MessageArgs = new object[] { e.Amount, tripName },
147 IconCssClass = "bi-cash-coin",
148 BadgeCssClass = "bg-success",
149 });
150 }
151 foreach (var s in settlementPlans)
152 {
153 var tripName = trips.FirstOrDefault(t => t.Id == s.TripId)?.Name ?? "?";
154 feed.Add(new AdminActivityFeedItem
155 {
156 Type = "settlement",
157 Date = s.CreatedAt,
158 MessageKey = "Settlement plan for \"{0}\" ({1})",
159 MessageArgs = new object[] { tripName, s.Status },
160 IconCssClass = "bi-diagram-3",
161 BadgeCssClass = "bg-info",
162 });
163 }
164 var activityFeed = feed.OrderByDescending(f => f.Date).Take(15).ToList();
165
166 return new AdminDashboardData
167 {
168 TripCount = tripCount,
169 UserCount = userCount,
170 ExpenseCount = expenseCount,
171 CategoryCount = budgetCategories.Count,
172 SettlementCount = settlementPlans.Count,
173 WishlistCount = wishlistItems.Count,
174 PollCount = polls.Count,
175 InvitationCount = invitations.Count,
176 CurrencyCount = currencies.Count,
177 ParticipantCount = participants.Count,
178
179 ActiveTrips = activeTrips,
180 SettledTrips = settledTrips,
181 ArchivedTrips = archivedTrips,
182 TotalExpenseAmount = totalExpenseAmount,
183 PendingSettlements = pendingSettlements,
184 InProgressSettlements = inProgressSettlements,
185 CompletedSettlements = completedSettlements,
186 PendingInvitations = pendingInvitations,
187 PendingPayments = pendingPayments,
188 MarkedPaidPayments = markedPaidPayments,
189
190 RecentTrips = recentTrips,
191 RecentExpenses = recentExpenses,
192 RecentUsers = recentUsers,
193
194 TopActiveTrips = topActiveTrips,
195 BiggestExpenses = biggestExpenses,
196 NewUsersLast7Days = activeUserIds7,
197 NewUsersLast30Days = activeUserIds30,
198 TopActiveUsers = topActiveUsers,
199 ActivityFeed = activityFeed,
200 };
201 }
202 }
203