profileShare

rasmusjy / splitapp-backend-modular-monolith

Read-only snapshot

No repository description.

main default branch 418 files Expires Sep 13, 2026, 9:06 AM
WishlistController.cs 9,011 bytes
1 using System.Security.Claims;
2 using Asp.Versioning;
3 using MediatR;
4 using Microsoft.AspNetCore.Authentication.JwtBearer;
5 using Microsoft.AspNetCore.Authorization;
6 using Microsoft.AspNetCore.Mvc;
7 using SplitApp.Modules.Trips.Api.Dto.v1;
8 using SplitApp.Modules.Trips.Application.Contracts;
9 using SplitApp.Modules.Trips.Domain.Entities;
10 using SplitApp.Modules.Trips.Domain.Enums;
11 using SplitApp.Shared.Contracts.Users.Queries;
12
13 namespace SplitApp.Modules.Trips.Api.Controllers;
14
15 [ApiVersion("1.0")]
16 [ApiController]
17 [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
18 [Route("api/v{version:apiVersion}/[controller]")]
19 public class WishlistController : ControllerBase
20 {
21 private readonly ITripsUnitOfWork _uow;
22 private readonly IMediator _mediator;
23
24 public WishlistController(ITripsUnitOfWork uow, IMediator mediator)
25 {
26 _uow = uow;
27 _mediator = mediator;
28 }
29
30 [HttpGet("trip/{tripId:guid}")]
31 public async Task<ActionResult<List<WishlistItemDto>>> GetForTrip(Guid tripId)
32 {
33 var userId = CurrentUserId();
34 if (userId == null) return Unauthorized();
35
36 if (!await IsParticipantAsync(tripId, userId.Value)) return Forbid();
37
38 var allItems = await _uow.WishlistItems.GetAllAsync();
39 var items = allItems.Where(i => i.TripId == tripId).OrderBy(i => i.DisplayOrder).ToList();
40 var allVotes = await _uow.WishlistVotes.GetAllAsync();
41
42 var addedByIds = items.Select(i => i.AddedByUserId).Distinct().ToList();
43 var users = addedByIds.Count == 0
44 ? new List<SplitApp.Shared.Contracts.Users.UserDto>()
45 : (await _mediator.Send(new GetUsersByIdsQuery(addedByIds))).ToList();
46 var nameLookup = users.ToDictionary(u => u.Id, u => u.DisplayName);
47
48 return Ok(items.Select(i => MapItem(i, allVotes, userId.Value, nameLookup)).ToList());
49 }
50
51 [HttpPost]
52 public async Task<ActionResult<WishlistItemDto>> Create([FromBody] WishlistItemCreateDto dto)
53 {
54 var userId = CurrentUserId();
55 if (userId == null) return Unauthorized();
56
57 if (!await IsParticipantAsync(dto.TripId, userId.Value)) return Forbid();
58 if (!Enum.TryParse<EWishlistCategory>(dto.Category, true, out var category))
59 return BadRequest($"Unknown category '{dto.Category}'.");
60 if (!Enum.TryParse<EWishlistPriority>(dto.Priority, true, out var priority))
61 return BadRequest($"Unknown priority '{dto.Priority}'.");
62
63 var entity = new TripWishlistItem
64 {
65 TripId = dto.TripId,
66 AddedByUserId = userId.Value,
67 Title = dto.Title,
68 Description = dto.Description,
69 Category = category,
70 Priority = priority,
71 EstimatedCost = dto.EstimatedCost,
72 Url = dto.Url,
73 Location = dto.Location,
74 IsCompleted = false,
75 DisplayOrder = 0,
76 };
77 _uow.WishlistItems.Add(entity);
78 await _uow.SaveChangesAsync();
79
80 var votes = await _uow.WishlistVotes.GetAllAsync();
81 return CreatedAtAction(null, new { id = entity.Id }, MapItem(entity, votes, userId.Value, null));
82 }
83
84 [HttpPut("{id:guid}")]
85 public async Task<IActionResult> Update(Guid id, [FromBody] WishlistItemCreateDto dto)
86 {
87 var userId = CurrentUserId();
88 if (userId == null) return Unauthorized();
89
90 var existing = await _uow.WishlistItems.GetByIdAsync(id);
91 if (existing == null) return NotFound();
92
93 var canEdit = existing.AddedByUserId == userId.Value
94 || await IsOrganizerAsync(existing.TripId, userId.Value);
95 if (!canEdit) return Forbid();
96
97 if (!Enum.TryParse<EWishlistCategory>(dto.Category, true, out var category))
98 return BadRequest($"Unknown category '{dto.Category}'.");
99 if (!Enum.TryParse<EWishlistPriority>(dto.Priority, true, out var priority))
100 return BadRequest($"Unknown priority '{dto.Priority}'.");
101
102 existing.Title = dto.Title;
103 existing.Description = dto.Description;
104 existing.Category = category;
105 existing.Priority = priority;
106 existing.EstimatedCost = dto.EstimatedCost;
107 existing.Url = dto.Url;
108 existing.Location = dto.Location;
109
110 _uow.WishlistItems.Update(existing);
111 await _uow.SaveChangesAsync();
112 return NoContent();
113 }
114
115 [HttpDelete("{id:guid}")]
116 public async Task<IActionResult> Delete(Guid id)
117 {
118 var userId = CurrentUserId();
119 if (userId == null) return Unauthorized();
120
121 var existing = await _uow.WishlistItems.GetByIdAsync(id);
122 if (existing == null) return NotFound();
123
124 var canDelete = existing.AddedByUserId == userId.Value
125 || await IsOrganizerAsync(existing.TripId, userId.Value);
126 if (!canDelete) return Forbid();
127
128 var allVotes = await _uow.WishlistVotes.GetAllAsync();
129 foreach (var v in allVotes.Where(v => v.WishlistItemId == id))
130 {
131 await _uow.WishlistVotes.RemoveAsync(v.Id);
132 }
133 await _uow.WishlistItems.RemoveAsync(id);
134 await _uow.SaveChangesAsync();
135 return NoContent();
136 }
137
138 [HttpPost("{id:guid}/vote")]
139 public async Task<IActionResult> ToggleVote(Guid id)
140 {
141 var userId = CurrentUserId();
142 if (userId == null) return Unauthorized();
143
144 var item = await _uow.WishlistItems.GetByIdAsync(id);
145 if (item == null) return NotFound();
146
147 if (!await IsParticipantAsync(item.TripId, userId.Value)) return Forbid();
148
149 var allVotes = await _uow.WishlistVotes.GetAllAsync();
150 var existing = allVotes.FirstOrDefault(v => v.WishlistItemId == id && v.UserId == userId.Value);
151 if (existing != null)
152 {
153 await _uow.WishlistVotes.RemoveAsync(existing.Id);
154 }
155 else
156 {
157 _uow.WishlistVotes.Add(new TripWishlistVote
158 {
159 WishlistItemId = id,
160 UserId = userId.Value,
161 IsInterested = true,
162 });
163 }
164
165 await _uow.SaveChangesAsync();
166 return Ok();
167 }
168
169 [HttpPost("{id:guid}/complete")]
170 public async Task<IActionResult> ToggleComplete(Guid id)
171 {
172 var userId = CurrentUserId();
173 if (userId == null) return Unauthorized();
174
175 var item = await _uow.WishlistItems.GetByIdAsync(id);
176 if (item == null) return NotFound();
177
178 var canToggle = item.AddedByUserId == userId.Value
179 || await IsOrganizerAsync(item.TripId, userId.Value);
180 if (!canToggle) return Forbid();
181
182 item.IsCompleted = !item.IsCompleted;
183 item.CompletedAt = item.IsCompleted ? DateTime.UtcNow : null;
184 _uow.WishlistItems.Update(item);
185 await _uow.SaveChangesAsync();
186 return Ok();
187 }
188
189 private Guid? CurrentUserId()
190 {
191 var raw = User.FindFirstValue(ClaimTypes.NameIdentifier);
192 return Guid.TryParse(raw, out var id) ? id : null;
193 }
194
195 private async Task<bool> IsParticipantAsync(Guid tripId, Guid userId)
196 {
197 var trip = await _uow.Trips.GetByIdAsync(tripId);
198 if (trip == null) return false;
199 if (trip.CreatedById == userId) return true;
200 var all = await _uow.Participants.GetAllAsync();
201 return all.Any(p => p.TripId == tripId && p.UserId == userId && p.IsActive);
202 }
203
204 private async Task<bool> IsOrganizerAsync(Guid tripId, Guid userId)
205 {
206 var trip = await _uow.Trips.GetByIdAsync(tripId);
207 if (trip == null) return false;
208 if (trip.CreatedById == userId) return true;
209 var all = await _uow.Participants.GetAllAsync();
210 return all.Any(p => p.TripId == tripId
211 && p.UserId == userId
212 && p.IsActive
213 && p.Role == EParticipantRole.Organizer);
214 }
215
216 private static WishlistItemDto MapItem(
217 TripWishlistItem item,
218 IEnumerable<TripWishlistVote> allVotes,
219 Guid currentUserId,
220 IDictionary<Guid, string>? userNames)
221 {
222 var votes = allVotes.Where(v => v.WishlistItemId == item.Id).ToList();
223 return new WishlistItemDto
224 {
225 Id = item.Id,
226 TripId = item.TripId,
227 AddedByUserId = item.AddedByUserId,
228 AddedByUserName = userNames != null && userNames.TryGetValue(item.AddedByUserId, out var name) ? name : null,
229 Title = item.Title,
230 Description = item.Description,
231 Category = item.Category.ToString(),
232 Priority = item.Priority.ToString(),
233 EstimatedCost = item.EstimatedCost,
234 Url = item.Url,
235 Location = item.Location,
236 IsCompleted = item.IsCompleted,
237 VoteCount = votes.Count,
238 UserHasVoted = votes.Any(v => v.UserId == currentUserId),
239 DisplayOrder = item.DisplayOrder,
240 };
241 }
242 }
243