profileShare

rasmusjy / splitapp-backend-microservices

Read-only snapshot

No repository description.

main default branch 501 files Expires Sep 13, 2026, 9:06 AM
UsersServiceClient.cs 5,613 bytes
1 using System.Net;
2 using System.Net.Http.Json;
3 using SplitApp.WebApp.Application.UsersService.Dtos;
4
5 namespace SplitApp.WebApp.Application.UsersService;
6
7 public class UsersServiceClient : IUsersServiceClient
8 {
9 private readonly HttpClient _http;
10
11 public UsersServiceClient(HttpClient http) => _http = http;
12
13 public async Task<UsersServiceResult<JwtResponsePayload>> LoginAsync(
14 string email, string password, CancellationToken ct = default)
15 {
16 var resp = await _http.PostAsJsonAsync(
17 "api/v1/identity/account/login",
18 new LoginRequestPayload { Email = email, Password = password },
19 ct);
20 return await ParseJwtResponseAsync(resp, ct);
21 }
22
23 public async Task<UsersServiceResult<JwtResponsePayload>> RegisterAsync(
24 string email, string password, string firstName, string lastName, CancellationToken ct = default)
25 {
26 var resp = await _http.PostAsJsonAsync(
27 "api/v1/identity/account/register",
28 new RegisterRequestPayload
29 {
30 Email = email,
31 Password = password,
32 Firstname = firstName,
33 Lastname = lastName,
34 },
35 ct);
36 return await ParseJwtResponseAsync(resp, ct);
37 }
38
39 public async Task<UsersServiceResult<JwtResponsePayload>> RefreshAsync(
40 string jwt, string refreshToken, CancellationToken ct = default)
41 {
42 var resp = await _http.PostAsJsonAsync(
43 "api/v1/identity/account/refreshtokendata",
44 new RefreshRequestPayload { Jwt = jwt, RefreshToken = refreshToken },
45 ct);
46 return await ParseJwtResponseAsync(resp, ct);
47 }
48
49 public async Task LogoutAsync(string refreshToken, CancellationToken ct = default)
50 {
51 var resp = await _http.PostAsJsonAsync(
52 "api/v1/identity/account/logout",
53 new LogoutRequestPayload { RefreshToken = refreshToken },
54 ct);
55 // Logout is best-effort: even if the service is unreachable, the MVC layer will still
56 // clear local cookies. So we swallow non-2xx here.
57 _ = resp;
58 }
59
60 public async Task<IReadOnlyList<AdminUserListItem>> ListUsersAsync(CancellationToken ct = default)
61 {
62 var list = await _http.GetFromJsonAsync<List<AdminUserListItem>>("api/v1/identity/admin/users", ct);
63 return list ?? new List<AdminUserListItem>();
64 }
65
66 public async Task<AdminUserDetails?> GetUserAsync(Guid id, CancellationToken ct = default)
67 {
68 var resp = await _http.GetAsync($"api/v1/identity/admin/users/{id}", ct);
69 if (resp.StatusCode == HttpStatusCode.NotFound) return null;
70 resp.EnsureSuccessStatusCode();
71 return await resp.Content.ReadFromJsonAsync<AdminUserDetails>(cancellationToken: ct);
72 }
73
74 public async Task<AdminUserDetails?> UpdateUserAsync(
75 Guid id, string firstName, string lastName, CancellationToken ct = default)
76 {
77 var resp = await _http.PutAsJsonAsync(
78 $"api/v1/identity/admin/users/{id}",
79 new AdminUserUpdatePayload { FirstName = firstName, LastName = lastName },
80 ct);
81 if (resp.StatusCode == HttpStatusCode.NotFound) return null;
82 resp.EnsureSuccessStatusCode();
83 return await resp.Content.ReadFromJsonAsync<AdminUserDetails>(cancellationToken: ct);
84 }
85
86 public async Task<bool> DeleteUserAsync(Guid id, CancellationToken ct = default)
87 {
88 var resp = await _http.DeleteAsync($"api/v1/identity/admin/users/{id}", ct);
89 if (resp.StatusCode == HttpStatusCode.NotFound) return false;
90 resp.EnsureSuccessStatusCode();
91 return true;
92 }
93
94 public async Task<IReadOnlyList<string>> GetUserRolesAsync(Guid id, CancellationToken ct = default)
95 {
96 var roles = await _http.GetFromJsonAsync<List<string>>(
97 $"api/v1/identity/admin/users/{id}/roles", ct);
98 return roles ?? new List<string>();
99 }
100
101 public async Task SetUserRolesAsync(
102 Guid id, IReadOnlyCollection<string> roleNames, CancellationToken ct = default)
103 {
104 var resp = await _http.PutAsJsonAsync(
105 $"api/v1/identity/admin/users/{id}/roles",
106 new AdminUserRolesUpdatePayload { RoleNames = roleNames.ToArray() },
107 ct);
108 resp.EnsureSuccessStatusCode();
109 }
110
111 public async Task<IReadOnlyList<AdminRoleInfo>> ListRolesAsync(CancellationToken ct = default)
112 {
113 var roles = await _http.GetFromJsonAsync<List<AdminRoleInfo>>(
114 "api/v1/identity/admin/roles", ct);
115 return roles ?? new List<AdminRoleInfo>();
116 }
117
118 private static async Task<UsersServiceResult<JwtResponsePayload>> ParseJwtResponseAsync(
119 HttpResponseMessage resp, CancellationToken ct)
120 {
121 if (resp.IsSuccessStatusCode)
122 {
123 var payload = await resp.Content.ReadFromJsonAsync<JwtResponsePayload>(cancellationToken: ct);
124 return payload is null
125 ? UsersServiceResult<JwtResponsePayload>.Fail("Empty response from users service")
126 : UsersServiceResult<JwtResponsePayload>.Ok(payload);
127 }
128
129 string? err = null;
130 try
131 {
132 var body = await resp.Content.ReadFromJsonAsync<ServiceErrorPayload>(cancellationToken: ct);
133 err = body?.Error ?? body?.Errors?.FirstOrDefault();
134 }
135 catch
136 {
137 // body wasn't JSON; fall through
138 }
139 return UsersServiceResult<JwtResponsePayload>.Fail(err ?? $"Users service returned {(int)resp.StatusCode}");
140 }
141 }
142