profileShare

rasmusjy / splitapp-backend-modular-monolith

Read-only snapshot

No repository description.

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