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 | |