using JobsJobsJobs.Shared; using JobsJobsJobs.Shared.Api; using NodaTime; using NodaTime.Serialization.SystemTextJson; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using System.Threading.Tasks; namespace JobsJobsJobs.Client { /// /// Functions used to access the API /// public static class ServerApi { /// /// System.Text.Json options configured for NodaTime /// private static readonly JsonSerializerOptions _serializerOptions; /// /// Static constructor /// static ServerApi() { var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; options.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); _serializerOptions = options; } /// /// Create an API URL /// /// The URL to append to the API base URL /// The full URL to be used in HTTP requests private static string ApiUrl(string url) => $"/api/{url}"; /// /// Create an HTTP request with an authorization header /// /// The current application state /// The URL for the request (will be appended to the API root) /// The request method (optional, defaults to GET) /// A request with the header attached, ready for further manipulation private static HttpRequestMessage WithHeader(AppState state, string url, HttpMethod? method = null) { var req = new HttpRequestMessage(method ?? HttpMethod.Get, ApiUrl(url)); req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", state.Jwt); return req; } /// /// Set the JSON Web Token (JWT) bearer header for the given HTTP client /// /// The HTTP client whose authentication header should be set /// The current application state public static void SetJwt(HttpClient http, AppState state) { if (state.User != null) { http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", state.Jwt); } } /// /// Log on a user with the authorization code received from No Agenda Social /// /// The HTTP client to use for server communication /// The authorization code received from NAS /// The log on details if successful, an error if not public static async Task> LogOn(HttpClient http, string authCode) { try { var logOn = await http.GetFromJsonAsync(ApiUrl($"citizen/log-on/{authCode}")); if (logOn == null) { return Result.AsError( "Unable to log on with No Agenda Social. This should never happen; contact @danieljsummers"); } return Result.AsOk(logOn); } catch (HttpRequestException ex) { return Result.AsError($"Unable to log on with No Agenda Social: {ex.Message}"); } } /// /// Retrieve a citizen's profile /// /// The HTTP client to use for server communication /// The current application state /// The citizen's profile, null if it is not found, or an error message if one occurs public static async Task> RetrieveProfile(HttpClient http, AppState state) { var req = WithHeader(state, "profile/"); var res = await http.SendAsync(req); return true switch { _ when res.StatusCode == HttpStatusCode.NoContent => Result.AsOk(null), _ when res.IsSuccessStatusCode => Result.AsOk( await res.Content.ReadFromJsonAsync(_serializerOptions)), _ => Result.AsError(await res.Content.ReadAsStringAsync()), }; } /// /// Retrieve all continents /// /// The HTTP client to use for server communication /// The current application state /// The continents, or an error message if one occurs public static async Task>> AllContinents(HttpClient http, AppState state) { var req = WithHeader(state, "continent/all"); var res = await http.SendAsync(req); if (res.IsSuccessStatusCode) { var continents = await res.Content.ReadFromJsonAsync>(); return Result>.AsOk(continents ?? Enumerable.Empty()); } return Result>.AsError(await res.Content.ReadAsStringAsync()); } /// /// Retrieve many items from the given URL /// /// The type of item expected /// The HTTP client to use for server communication /// The API URL to call /// A result with the items, or an error if one occurs /// The caller is responsible for setting the JWT on the HTTP client public static async Task>> RetrieveMany(HttpClient http, string url) { try { var results = await http.GetFromJsonAsync>($"/api/{url}", _serializerOptions); return Result>.AsOk(results ?? Enumerable.Empty()); } catch (HttpRequestException ex) { return Result>.AsError(ex.Message); } catch (JsonException ex) { return Result>.AsError($"Unable to parse result: {ex.Message}"); } } /// /// Retrieve one item from the given URL /// /// The type of item expected /// The HTTP client to use for server communication /// The API URL to call /// A result with the item (possibly null), or an error if one occurs /// The caller is responsible for setting the JWT on the HTTP client public static async Task> RetrieveOne(HttpClient http, string url) { try { return Result.AsOk(await http.GetFromJsonAsync($"/api/{url}", _serializerOptions)); } catch (HttpRequestException ex) { return Result.AsError(ex.Message); } catch (JsonException ex) { return Result.AsError($"Unable to parse result: {ex.Message}"); } } } }