profileShare

rasmusjy / splitapp-backend-microservices

Read-only snapshot

No repository description.

main default branch 501 files Expires Sep 13, 2026, 9:06 AM
PollService.cs 6,999 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.WebApp.Application.Contracts;
8
9 namespace SplitApp.WebApp.Application.Services;
10
11 public class PollService : IPollService
12 {
13 private readonly IAppUnitOfWork _uow;
14
15 public PollService(IAppUnitOfWork uow)
16 {
17 _uow = uow;
18 }
19
20 public async Task<TripPollBllDto> CreatePollWithOptionsAsync(TripPollBllDto poll, List<string> optionTexts)
21 {
22 var entity = PollBllDtoFactory.ToEntity(poll);
23 entity.Id = Guid.NewGuid();
24 _uow.TripPolls.Add(entity);
25
26 var optionRepo = _uow.GetRepository<TripPollOption>();
27 var order = 0;
28
29 foreach (var text in optionTexts.Where(t => !string.IsNullOrWhiteSpace(t)))
30 {
31 optionRepo.Add(new TripPollOption
32 {
33 Id = Guid.NewGuid(),
34 PollId = entity.Id,
35 Text = text,
36 DisplayOrder = order++
37 });
38 }
39
40 await _uow.SaveChangesAsync();
41
42 return PollBllDtoFactory.Create(entity);
43 }
44
45 public async Task ToggleVoteAsync(Guid pollId, Guid optionId, Guid userId)
46 {
47 var poll = await _uow.TripPolls.GetByIdWithDetailsAsync(pollId);
48 if (poll == null) return;
49
50 if (poll.ClosedAt != null) return;
51
52 var option = poll.Options?.FirstOrDefault(o => o.Id == optionId);
53 if (option == null) return;
54
55 var voteRepo = _uow.GetRepository<TripPollVote>();
56
57 if (!poll.AllowMultipleVotes)
58 {
59 if (poll.Options != null)
60 {
61 foreach (var opt in poll.Options)
62 {
63 if (opt.Votes != null)
64 {
65 foreach (var vote in opt.Votes.Where(v => v.UserId == userId).ToList())
66 {
67 await voteRepo.RemoveAsync(vote.Id);
68 }
69 }
70 }
71 }
72 }
73
74 var existingVote = option.Votes?.FirstOrDefault(v => v.UserId == userId);
75
76 if (existingVote != null)
77 {
78 await voteRepo.RemoveAsync(existingVote.Id);
79 }
80 else
81 {
82 voteRepo.Add(new TripPollVote
83 {
84 Id = Guid.NewGuid(),
85 PollOptionId = optionId,
86 UserId = userId
87 });
88 }
89
90 await _uow.SaveChangesAsync();
91 }
92
93 public async Task<List<TripPollBllDto>> GetByTripIdAsync(Guid tripId, Guid userId)
94 {
95 if (!await _uow.TripParticipants.IsParticipantAsync(tripId, userId))
96 return new List<TripPollBllDto>();
97 var polls = await _uow.TripPolls.GetByTripIdAsync(tripId);
98 return PollBllDtoFactory.CreateList(polls, includeOptions: true);
99 }
100
101 public async Task<TripPollBllDto?> GetByIdAsync(Guid pollId, Guid userId)
102 {
103 var poll = await _uow.TripPolls.GetByIdAsync(pollId);
104 if (poll == null) return null;
105 if (!await _uow.TripParticipants.IsParticipantAsync(poll.TripId, userId)) return null;
106 return PollBllDtoFactory.Create(poll);
107 }
108
109 public async Task<TripPollBllDto?> GetByIdWithDetailsAsync(Guid pollId, Guid userId)
110 {
111 var poll = await _uow.TripPolls.GetByIdWithDetailsAsync(pollId);
112 if (poll == null) return null;
113 if (!await _uow.TripParticipants.IsParticipantAsync(poll.TripId, userId)) return null;
114 return PollBllDtoFactory.Create(poll, includeOptions: true);
115 }
116
117 public async Task<(TripPollBllDto? poll, string? errorCode)> CreatePollGuardedAsync(TripPollBllDto poll, List<string> optionTexts, Guid userId)
118 {
119 if (!await _uow.TripParticipants.IsParticipantAsync(poll.TripId, userId))
120 return (null, "forbidden");
121 var created = await CreatePollWithOptionsAsync(poll, optionTexts);
122 return (created, null);
123 }
124
125 public async Task<(bool success, string? errorCode)> CastVoteAsync(Guid pollId, Guid optionId, Guid userId)
126 {
127 var poll = await _uow.TripPolls.GetByIdWithDetailsAsync(pollId);
128 if (poll == null) return (false, "notfound");
129
130 if (poll.ClosedAt != null) return (false, "closed");
131
132 if (!await _uow.TripParticipants.IsParticipantAsync(poll.TripId, userId))
133 return (false, "forbidden");
134
135 var option = poll.Options?.FirstOrDefault(o => o.Id == optionId);
136 if (option == null) return (false, "invalid-option");
137
138 await ToggleVoteAsync(pollId, optionId, userId);
139 return (true, null);
140 }
141
142 public async Task<(bool success, string? errorCode)> ClosePollAsync(Guid pollId, Guid userId, bool organizerAllowed)
143 {
144 var poll = await _uow.TripPolls.GetByIdAsync(pollId);
145 if (poll == null) return (false, "notfound");
146
147 if (!await _uow.TripParticipants.IsParticipantAsync(poll.TripId, userId))
148 return (false, "forbidden");
149
150 var isCreator = poll.CreatedByUserId == userId;
151 var isOrganizer = organizerAllowed && await _uow.TripParticipants.IsOrganizerAsync(poll.TripId, userId);
152
153 if (!isCreator && !isOrganizer) return (false, "forbidden");
154
155 poll.ClosedAt = DateTime.UtcNow;
156 _uow.TripPolls.Update(poll);
157 await _uow.SaveChangesAsync();
158 return (true, null);
159 }
160
161 public async Task<(bool success, string? errorCode)> DeletePollGuardedAsync(Guid pollId, Guid userId)
162 {
163 var poll = await _uow.TripPolls.GetByIdWithDetailsAsync(pollId);
164 if (poll == null) return (false, "notfound");
165
166 var isCreator = poll.CreatedByUserId == userId;
167 var isOrganizer = await _uow.TripParticipants.IsOrganizerAsync(poll.TripId, userId);
168
169 if (!isCreator && !isOrganizer) return (false, "forbidden");
170
171 await DeletePollCascadeAsync(pollId);
172 return (true, null);
173 }
174
175 public async Task DeletePollCascadeAsync(Guid pollId)
176 {
177 var poll = await _uow.TripPolls.GetByIdWithDetailsAsync(pollId);
178 if (poll == null) return;
179
180 var voteRepo = _uow.GetRepository<TripPollVote>();
181 var optionRepo = _uow.GetRepository<TripPollOption>();
182
183 if (poll.Options != null)
184 {
185 foreach (var option in poll.Options)
186 {
187 if (option.Votes != null)
188 {
189 foreach (var vote in option.Votes.ToList())
190 {
191 await voteRepo.RemoveAsync(vote.Id);
192 }
193 }
194 }
195
196 foreach (var option in poll.Options.ToList())
197 {
198 await optionRepo.RemoveAsync(option.Id);
199 }
200 }
201
202 await _uow.TripPolls.RemoveAsync(pollId);
203
204 await _uow.SaveChangesAsync();
205 }
206 }
207