profileShare

rasmusjy / splitapp-backend-microservices

Read-only snapshot

No repository description.

main default branch 501 files Expires Sep 13, 2026, 9:06 AM
AdminUsersController.cs 5,201 bytes
1 using Asp.Versioning;
2 using Microsoft.AspNetCore.Authentication.JwtBearer;
3 using Microsoft.AspNetCore.Authorization;
4 using Microsoft.AspNetCore.Identity;
5 using Microsoft.AspNetCore.Mvc;
6 using SplitApp.Modules.Users.Api.Dto.v1.Admin;
7 using SplitApp.Modules.Users.Domain.Entities;
8 using SplitApp.Shared.Messaging;
9 using SplitApp.Shared.Messaging.Integration.Users;
10
11 namespace SplitApp.Modules.Users.Api.Controllers;
12
13 /// <summary>
14 /// Admin REST surface used by the monolith's Admin/UsersController. Protected by JWT bearer
15 /// + role=admin. User deletion publishes <see cref="UserDeletedEvent"/> on the bus so
16 /// downstream services can cascade-clean.
17 /// </summary>
18 [ApiVersion("1.0")]
19 [ApiController]
20 [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "admin")]
21 [Route("/api/v{version:apiVersion}/identity/admin/users")]
22 public class AdminUsersController : ControllerBase
23 {
24 private readonly UserManager<AppUser> _userManager;
25 private readonly RoleManager<AppRole> _roleManager;
26 private readonly IMessageBus _bus;
27
28 public AdminUsersController(
29 UserManager<AppUser> userManager,
30 RoleManager<AppRole> roleManager,
31 IMessageBus bus)
32 {
33 _userManager = userManager;
34 _roleManager = roleManager;
35 _bus = bus;
36 }
37
38 [HttpGet]
39 [Produces("application/json")]
40 public async Task<ActionResult<IEnumerable<AdminUserListItem>>> List()
41 {
42 var users = _userManager.Users.OrderBy(u => u.Email).ToList();
43 var items = new List<AdminUserListItem>(users.Count);
44 foreach (var u in users)
45 {
46 var roles = await _userManager.GetRolesAsync(u);
47 items.Add(new AdminUserListItem(u.Id, u.Email ?? "", u.FirstName, u.LastName, roles.ToArray()));
48 }
49 return Ok(items);
50 }
51
52 [HttpGet("{id:guid}")]
53 [Produces("application/json")]
54 public async Task<ActionResult<AdminUserDetails>> Get(Guid id)
55 {
56 var user = await _userManager.FindByIdAsync(id.ToString());
57 if (user is null) return NotFound();
58 var roles = await _userManager.GetRolesAsync(user);
59 return Ok(new AdminUserDetails(user.Id, user.Email ?? "", user.FirstName, user.LastName, roles.ToArray()));
60 }
61
62 [HttpPut("{id:guid}")]
63 [Consumes("application/json")]
64 [Produces("application/json")]
65 public async Task<ActionResult<AdminUserDetails>> Update(Guid id, [FromBody] AdminUserUpdateRequest req)
66 {
67 var user = await _userManager.FindByIdAsync(id.ToString());
68 if (user is null) return NotFound();
69
70 user.FirstName = req.FirstName;
71 user.LastName = req.LastName;
72
73 var result = await _userManager.UpdateAsync(user);
74 if (!result.Succeeded)
75 {
76 return BadRequest(new { errors = result.Errors.Select(e => e.Description) });
77 }
78
79 var roles = await _userManager.GetRolesAsync(user);
80 return Ok(new AdminUserDetails(user.Id, user.Email ?? "", user.FirstName, user.LastName, roles.ToArray()));
81 }
82
83 [HttpDelete("{id:guid}")]
84 public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
85 {
86 var user = await _userManager.FindByIdAsync(id.ToString());
87 if (user is null) return NotFound();
88
89 var result = await _userManager.DeleteAsync(user);
90 if (!result.Succeeded)
91 {
92 return BadRequest(new { errors = result.Errors.Select(e => e.Description) });
93 }
94
95 // Fan-out so monolith subscribers can clean cross-service references (Trips/Expenses).
96 await _bus.PublishEventAsync(new UserDeletedEvent(id), ct);
97 return NoContent();
98 }
99
100 [HttpGet("{id:guid}/roles")]
101 [Produces("application/json")]
102 public async Task<ActionResult<IEnumerable<string>>> GetRoles(Guid id)
103 {
104 var user = await _userManager.FindByIdAsync(id.ToString());
105 if (user is null) return NotFound();
106 var roles = await _userManager.GetRolesAsync(user);
107 return Ok(roles);
108 }
109
110 [HttpPut("{id:guid}/roles")]
111 [Consumes("application/json")]
112 public async Task<IActionResult> SetRoles(Guid id, [FromBody] AdminUserRolesUpdateRequest req)
113 {
114 var user = await _userManager.FindByIdAsync(id.ToString());
115 if (user is null) return NotFound();
116
117 var current = await _userManager.GetRolesAsync(user);
118 var desired = req.RoleNames ?? Array.Empty<string>();
119
120 foreach (var add in desired.Except(current, StringComparer.OrdinalIgnoreCase))
121 {
122 if (!await _roleManager.RoleExistsAsync(add)) continue;
123 await _userManager.AddToRoleAsync(user, add);
124 }
125 foreach (var remove in current.Except(desired, StringComparer.OrdinalIgnoreCase))
126 {
127 await _userManager.RemoveFromRoleAsync(user, remove);
128 }
129 return NoContent();
130 }
131
132 [HttpGet("/api/v{version:apiVersion}/identity/admin/roles")]
133 [Produces("application/json")]
134 public ActionResult<IEnumerable<AdminRoleInfo>> ListRoles()
135 {
136 var roles = _roleManager.Roles.OrderBy(r => r.Name).ToList();
137 return Ok(roles.Select(r => new AdminRoleInfo(r.Name ?? "")));
138 }
139 }
140