profileShare

rasmusjy / splitapp-backend-microservices

Read-only snapshot

No repository description.

main default branch 501 files Expires Sep 13, 2026, 9:06 AM
MembersController.cs 5,853 bytes
1 using System.Security.Claims;
2 using SplitApp.WebApp.Application.Services;
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 Microsoft.AspNetCore.Authorization;
8 using Microsoft.AspNetCore.Identity;
9 using Microsoft.AspNetCore.Mvc;
10
11 namespace SplitApp.WebApp.Controllers;
12
13 [Authorize]
14 public class MembersController : Controller
15 {
16 private readonly ITripService _tripService;
17 private readonly IInvitationService _invitationService;
18
19 public MembersController(
20 ITripService tripService,
21 IInvitationService invitationService)
22 {
23 _tripService = tripService;
24 _invitationService = invitationService;
25 }
26
27 private Guid GetUserId() => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
28
29 // GET: Members?tripId=xxx
30 public async Task<IActionResult> Index(Guid tripId)
31 {
32 var userId = GetUserId();
33 if (!await _tripService.IsParticipantAsync(tripId, userId)) return Forbid();
34
35 var trip = await _tripService.GetByIdAsync(tripId, userId);
36 if (trip == null) return NotFound();
37
38 var participants = await _tripService.GetParticipantsAsync(tripId, userId);
39
40 var isOrganizer = participants.Any(p => p.UserId == userId && p.Role == EParticipantRole.Organizer);
41
42 var pendingInvitations = await _invitationService.GetPendingByTripIdAsync(tripId, userId);
43
44 ViewData["TripId"] = tripId;
45 ViewData["TripName"] = trip.Name;
46 ViewData["CurrentUserId"] = userId;
47 ViewData["IsOrganizer"] = isOrganizer;
48 ViewData["PendingInvitations"] = pendingInvitations;
49
50 return View(participants);
51 }
52
53 // GET: Members/Invite?tripId=xxx
54 public async Task<IActionResult> Invite(Guid tripId)
55 {
56 var userId = GetUserId();
57 if (!await _tripService.IsParticipantAsync(tripId, userId)) return Forbid();
58
59 var trip = await _tripService.GetByIdAsync(tripId, userId);
60 if (trip == null) return NotFound();
61
62 ViewData["TripId"] = tripId;
63 ViewData["TripName"] = trip.Name;
64
65 return View();
66 }
67
68 // POST: Members/Invite
69 [HttpPost]
70 [ValidateAntiForgeryToken]
71 public async Task<IActionResult> Invite(Guid tripId, int _)
72 {
73 var userId = GetUserId();
74 if (!await _tripService.IsParticipantAsync(tripId, userId)) return Forbid();
75
76 var trip = await _tripService.GetByIdAsync(tripId, userId);
77 if (trip == null) return NotFound();
78
79 var invitation = await _invitationService.CreateInvitationAsync(tripId, userId);
80
81 var inviteUrl = Url.Action("AcceptInvitation", "Members", new { token = invitation.Token }, Request.Scheme);
82
83 ViewData["TripId"] = tripId;
84 ViewData["TripName"] = trip.Name;
85 ViewData["InviteUrl"] = inviteUrl;
86 ViewData["Token"] = invitation.Token;
87
88 return View("InviteGenerated");
89 }
90
91 // GET: Members/AcceptInvitation?token=xxx
92 [AllowAnonymous]
93 public async Task<IActionResult> AcceptInvitation(string token)
94 {
95 var invitation = await _invitationService.GetByTokenAsync(token);
96
97 if (invitation == null) return NotFound();
98
99 if (invitation.Status != EInvitationStatus.Pending || invitation.ExpiresAt < DateTime.UtcNow)
100 {
101 ViewData["Error"] = "This invitation has expired or is no longer valid.";
102 return View("InvitationInvalid");
103 }
104
105 // Load trip for the view
106 invitation.Trip = await _tripService.GetRawByIdAsync(invitation.TripId);
107
108 ViewData["Token"] = token;
109 return View(invitation);
110 }
111
112 // POST: Members/AcceptInvitation
113 [HttpPost]
114 [ValidateAntiForgeryToken]
115 public async Task<IActionResult> AcceptInvitation(string token, int _)
116 {
117 var userId = GetUserId();
118
119 var invitation = await _invitationService.GetByTokenAsync(token);
120 if (invitation == null) return NotFound();
121
122 var success = await _invitationService.AcceptInvitationAsync(token, userId);
123
124 if (!success)
125 {
126 ViewData["Error"] = "This invitation has expired or is no longer valid.";
127 return View("InvitationInvalid");
128 }
129
130 return RedirectToAction("Details", "Trips", new { id = invitation.TripId });
131 }
132
133 // POST: Members/Remove
134 [HttpPost]
135 [ValidateAntiForgeryToken]
136 public async Task<IActionResult> Remove(Guid tripId, Guid participantId)
137 {
138 var userId = GetUserId();
139
140 if (!await _tripService.IsOrganizerAsync(tripId, userId)) return Forbid();
141
142 var participant = await _tripService.GetParticipantByIdAsync(participantId);
143 if (participant == null || participant.TripId != tripId) return NotFound();
144
145 // Cannot remove yourself
146 if (participant.UserId == userId)
147 {
148 TempData["Error"] = "You cannot remove yourself from the trip.";
149 return RedirectToAction(nameof(Index), new { tripId });
150 }
151
152 await _tripService.RemoveParticipantByIdAsync(tripId, participantId, userId);
153
154 return RedirectToAction(nameof(Index), new { tripId });
155 }
156
157 // POST: Members/RevokeInvitation
158 [HttpPost]
159 [ValidateAntiForgeryToken]
160 public async Task<IActionResult> RevokeInvitation(Guid id, Guid tripId)
161 {
162 var userId = GetUserId();
163
164 var (ok, errorCode) = await _invitationService.RevokeInvitationAsync(id, tripId, userId);
165 if (!ok)
166 {
167 return errorCode switch
168 {
169 "forbidden" => Forbid(),
170 "notfound" => NotFound(),
171 _ => NotFound()
172 };
173 }
174
175 return RedirectToAction(nameof(Index), new { tripId });
176 }
177 }
178