profileShare

rasmusjy / splitapp-backend-modular-monolith

Read-only snapshot

No repository description.

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