profileShare

rasmusjy / splitapp-backend-microservices

Read-only snapshot

No repository description.

main default branch 501 files Expires Sep 13, 2026, 9:06 AM
InvitationService.cs 7,099 bytes
1 using System.Security.Cryptography;
2 using SplitApp.WebApp.Application.DTO;
3 using SplitApp.WebApp.Application.Mappers;
4 using SplitApp.Modules.Trips.Domain.Entities;
5 using SplitApp.Modules.Trips.Domain.Enums;
6 using SplitApp.Modules.Expenses.Domain.Entities;
7 using SplitApp.Modules.Expenses.Domain.Enums;
8 using SplitApp.WebApp.Application.Contracts;
9
10 namespace SplitApp.WebApp.Application.Services;
11
12 public class InvitationService : IInvitationService
13 {
14 private readonly IAppUnitOfWork _uow;
15
16 public InvitationService(IAppUnitOfWork uow)
17 {
18 _uow = uow;
19 }
20
21 public async Task<TripInvitationBllDto> CreateInvitationAsync(Guid tripId, Guid userId)
22 {
23 var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32))
24 .Replace("+", "-").Replace("/", "_").TrimEnd('=');
25
26 var invitation = new TripInvitation
27 {
28 Id = Guid.NewGuid(),
29 TripId = tripId,
30 InvitedByUserId = userId,
31 Token = token,
32 Status = EInvitationStatus.Pending,
33 ExpiresAt = DateTime.UtcNow.AddDays(7)
34 };
35
36 _uow.TripInvitations.Add(invitation);
37 await _uow.SaveChangesAsync();
38
39 return InvitationBllDtoFactory.Create(invitation);
40 }
41
42 public async Task<bool> AcceptInvitationAsync(string token, Guid userId)
43 {
44 var invitation = await _uow.TripInvitations.GetByTokenAsync(token);
45 if (invitation == null) return false;
46
47 if (invitation.Status != EInvitationStatus.Pending || invitation.ExpiresAt < DateTime.UtcNow)
48 return false;
49
50 // Check if already a participant
51 var participants = (await _uow.TripParticipants.GetByTripIdAsync(invitation.TripId)).ToList();
52 var existingParticipant = participants.FirstOrDefault(tp => tp.UserId == userId);
53
54 var allParticipants = await _uow.TripParticipants.GetAllAsync();
55 var inactiveParticipant = allParticipants
56 .FirstOrDefault(tp => tp.TripId == invitation.TripId && tp.UserId == userId && !tp.IsActive);
57
58 if (existingParticipant != null)
59 {
60 // Already an active participant
61 }
62 else if (inactiveParticipant != null)
63 {
64 inactiveParticipant.IsActive = true;
65 inactiveParticipant.LeftAt = null;
66 _uow.TripParticipants.Update(inactiveParticipant);
67 }
68 else
69 {
70 var participant = new TripParticipant
71 {
72 Id = Guid.NewGuid(),
73 TripId = invitation.TripId,
74 UserId = userId,
75 Role = EParticipantRole.Participant,
76 JoinedAt = DateTime.UtcNow,
77 IsActive = true
78 };
79 _uow.TripParticipants.Add(participant);
80 }
81
82 invitation.Status = EInvitationStatus.Accepted;
83 invitation.RespondedAt = DateTime.UtcNow;
84 _uow.TripInvitations.Update(invitation);
85
86 await _uow.SaveChangesAsync();
87
88 return true;
89 }
90
91 public async Task<(TripInvitationBllDto? invitation, string? errorCode)> CreateInvitationGuardedAsync(Guid tripId, Guid userId)
92 {
93 if (!await _uow.TripParticipants.IsOrganizerAsync(tripId, userId))
94 return (null, "forbidden");
95 var invitation = await CreateInvitationAsync(tripId, userId);
96 return (invitation, null);
97 }
98
99 public async Task<TripInvitationBllDto?> GetByIdAsync(Guid id)
100 {
101 var entity = await _uow.TripInvitations.GetByIdAsync(id);
102 return entity == null ? null : InvitationBllDtoFactory.Create(entity);
103 }
104
105 public async Task<TripInvitationBllDto?> GetByTokenAsync(string token)
106 {
107 var entity = await _uow.TripInvitations.GetByTokenAsync(token);
108 return entity == null ? null : InvitationBllDtoFactory.Create(entity);
109 }
110
111 public async Task<List<TripInvitationBllDto>> GetPendingByTripIdAsync(Guid tripId, Guid userId)
112 {
113 if (!await _uow.TripParticipants.IsParticipantAsync(tripId, userId))
114 return new List<TripInvitationBllDto>();
115 var invitations = await _uow.TripInvitations.GetPendingByTripIdAsync(tripId);
116 return InvitationBllDtoFactory.CreateList(invitations);
117 }
118
119 public async Task<(bool success, string? errorCode)> AcceptInvitationGuardedAsync(string token, Guid userId)
120 {
121 var invitation = await _uow.TripInvitations.GetByTokenAsync(token);
122 if (invitation == null) return (false, "notfound");
123
124 if (invitation.Status != EInvitationStatus.Pending)
125 return (false, "not-pending");
126
127 if (invitation.ExpiresAt < DateTime.UtcNow)
128 {
129 invitation.Status = EInvitationStatus.Expired;
130 _uow.TripInvitations.Update(invitation);
131 await _uow.SaveChangesAsync();
132 return (false, "expired");
133 }
134
135 if (await _uow.TripParticipants.IsParticipantAsync(invitation.TripId, userId))
136 return (false, "already-participant");
137
138 var accepted = await AcceptInvitationAsync(token, userId);
139 if (!accepted) return (false, "failed");
140
141 return (true, null);
142 }
143
144 public async Task<(bool success, string? errorCode)> RevokeInvitationAsync(Guid invitationId, Guid tripId, Guid userId)
145 {
146 if (!await _uow.TripParticipants.IsOrganizerAsync(tripId, userId))
147 return (false, "forbidden");
148
149 var invitation = await _uow.TripInvitations.GetByIdAsync(invitationId);
150 if (invitation == null || invitation.TripId != tripId) return (false, "notfound");
151
152 if (invitation.Status == EInvitationStatus.Pending)
153 {
154 invitation.Status = EInvitationStatus.Revoked;
155 invitation.RespondedAt = DateTime.UtcNow;
156 _uow.TripInvitations.Update(invitation);
157 await _uow.SaveChangesAsync();
158 }
159
160 return (true, null);
161 }
162
163 public async Task<(bool success, string? errorCode)> RevokeInvitationByTokenAsync(string token, Guid userId)
164 {
165 var invitation = await _uow.TripInvitations.GetByTokenAsync(token);
166 if (invitation == null) return (false, "notfound");
167
168 if (!await _uow.TripParticipants.IsOrganizerAsync(invitation.TripId, userId))
169 return (false, "forbidden");
170
171 invitation.Status = EInvitationStatus.Revoked;
172 invitation.RespondedAt = DateTime.UtcNow;
173 _uow.TripInvitations.Update(invitation);
174 await _uow.SaveChangesAsync();
175 return (true, null);
176 }
177
178 public async Task<(bool success, string? errorCode)> DeclineInvitationAsync(string token)
179 {
180 var invitation = await _uow.TripInvitations.GetByTokenAsync(token);
181 if (invitation == null) return (false, "notfound");
182
183 if (invitation.Status != EInvitationStatus.Pending)
184 return (false, "not-pending");
185
186 invitation.Status = EInvitationStatus.Declined;
187 invitation.RespondedAt = DateTime.UtcNow;
188 _uow.TripInvitations.Update(invitation);
189 await _uow.SaveChangesAsync();
190 return (true, null);
191 }
192 }
193