Tidy up of the API service
This commit is contained in:
parent
cc779051bf
commit
909038ff79
5 changed files with 135 additions and 189 deletions
|
@ -1,10 +1,12 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Neighbourhood.omg.lol.Models;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
|
||||
namespace Neighbourhood.omg.lol
|
||||
{
|
||||
|
@ -19,13 +21,23 @@ namespace Neighbourhood.omg.lol
|
|||
_client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(App.Name, App.Version));
|
||||
_serializerOptions = new JsonSerializerOptions {
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
#if DEBUG
|
||||
WriteIndented = true
|
||||
#else
|
||||
WriteIndented = false
|
||||
#endif
|
||||
};
|
||||
AddToken(token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize json convenience function with default serializer options
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to deserialize</typeparam>
|
||||
/// <param name="str">The string to deserialize</param>
|
||||
/// <returns>The deserialized object if successful, otherwise default</returns>
|
||||
public T? Deserialize<T>(string str) {
|
||||
T? responseObj = default(T);
|
||||
T? responseObj = default;
|
||||
try {
|
||||
responseObj = JsonSerializer.Deserialize<T>(str, _serializerOptions);
|
||||
}
|
||||
|
@ -36,166 +48,31 @@ namespace Neighbourhood.omg.lol
|
|||
return responseObj;
|
||||
}
|
||||
|
||||
public void AddToken(string? token = null) {
|
||||
if (token == null) token = Task.Run(() => SecureStorage.GetAsync("accounttoken")).GetAwaiter().GetResult();
|
||||
if (token != null) _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
#region Base Requests
|
||||
|
||||
public void RemoveToken() {
|
||||
_client.DefaultRequestHeaders.Remove("Authorization");
|
||||
}
|
||||
|
||||
private async Task<T?> Get<T>(string uri, CancellationToken cancellationToken = default) where T : IOmgLolResponseData {
|
||||
T? responseData = default(T);
|
||||
try {
|
||||
HttpResponseMessage response = await _client.GetAsync(uri, cancellationToken: cancellationToken);
|
||||
if (response.IsSuccessStatusCode) {
|
||||
string str = await response.Content.ReadAsStringAsync();
|
||||
try {
|
||||
OmgLolResponse<T>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<T>>(_serializerOptions, cancellationToken: cancellationToken);
|
||||
if (responseObj?.Request == null || (responseObj?.Request?.Success ?? false)) {
|
||||
responseData = responseObj!.Response;
|
||||
}
|
||||
}
|
||||
catch (JsonException ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
Debug.WriteLine(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
private async Task<TResponse?> Post<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
|
||||
TResponse? responseData = default(TResponse);
|
||||
try {
|
||||
HttpResponseMessage response = await _client.PostAsJsonAsync(uri, data, _serializerOptions, cancellationToken: cancellationToken);
|
||||
string str = await response.Content.ReadAsStringAsync();
|
||||
if (response.IsSuccessStatusCode) {
|
||||
OmgLolResponse<TResponse>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<TResponse>>(_serializerOptions, cancellationToken: cancellationToken);
|
||||
if (responseObj?.Request?.Success ?? false) {
|
||||
responseData = responseObj.Response;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
private async Task<TResponse?> PostBinary<TResponse, TData>(string uri, FileResult? fileResult = null, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
|
||||
TResponse? responseData = default(TResponse);
|
||||
try {
|
||||
if (fileResult != null) using (var fileStream = await fileResult.OpenReadAsync()) {
|
||||
Uri url = new Uri(_client.BaseAddress?.AbsoluteUri + uri);
|
||||
if (string.IsNullOrEmpty(url.Query)) uri += "?binary";
|
||||
else if (!url.Query.Contains("binary")) uri += "&binary";
|
||||
|
||||
HttpContent fileStreamContent = new StreamContent(fileStream);
|
||||
fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(fileResult.ContentType ?? "application/octet-stream");
|
||||
fileStreamContent.Headers.ContentLength = fileStream.Length;
|
||||
HttpResponseMessage response = await _client.PostAsync(uri, fileStreamContent, cancellationToken: cancellationToken);
|
||||
string str = await response.Content.ReadAsStringAsync();
|
||||
if (response.IsSuccessStatusCode) {
|
||||
OmgLolResponse<TResponse>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<TResponse>>(_serializerOptions, cancellationToken: cancellationToken);
|
||||
if (responseObj?.Request?.Success ?? false) {
|
||||
responseData = responseObj.Response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
private async Task<TResponse?> PostMultipart<TResponse, TData>(string uri, TData? data = null, FileResult? fileResult = null, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData where TData : class
|
||||
{
|
||||
if(fileResult != null) {
|
||||
using (var fileStream = await fileResult.OpenReadAsync())
|
||||
return await PostMultipart<TResponse, TData>(uri, data: data, fileStream: fileStream, fileName: fileResult.FileName, contentType: fileResult.ContentType);
|
||||
}
|
||||
else return await PostMultipart<TResponse, TData>(uri, data, fileStream: null);
|
||||
}
|
||||
|
||||
private async Task<TResponse?> PostMultipart<TResponse, TData>(string uri, TData? data = null, Stream? fileStream = null, string? fileName = null, string? contentType = null, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData where TData : class
|
||||
/// <summary>
|
||||
/// Decode the response from an API call
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">The type of response object we are trying to get</typeparam>
|
||||
/// <param name="response">The raw Http Response Message</param>
|
||||
/// <param name="cancellationToken">A cancellation token to cancel the operation</param>
|
||||
/// <returns>The decoded object if successfull, otherwise default</returns>
|
||||
private async Task<TResponse?> DecodeResponse<TResponse>(HttpResponseMessage response, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
{
|
||||
TResponse? responseData = default;
|
||||
try {
|
||||
using (MultipartFormDataContent formData = new MultipartFormDataContent()) {
|
||||
if(fileStream != null) {
|
||||
HttpContent fileStreamContent = new StreamContent(fileStream);
|
||||
fileStreamContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data") {
|
||||
Name = "\"file\"",
|
||||
FileName = $"\"{fileName}\"" ?? "\"unknown\""
|
||||
};
|
||||
fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType ?? "application/octet-stream");
|
||||
formData.Add(fileStreamContent);
|
||||
}
|
||||
if (data != null) {
|
||||
HttpContent jsonContent = JsonContent.Create(data, options: _serializerOptions);
|
||||
formData.Add(jsonContent);
|
||||
}
|
||||
|
||||
HttpResponseMessage response = await _client.PostAsync(uri, formData, cancellationToken: cancellationToken);
|
||||
string str = await response.Content.ReadAsStringAsync();
|
||||
if (response.IsSuccessStatusCode) {
|
||||
OmgLolResponse<TResponse>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<TResponse>>(_serializerOptions, cancellationToken: cancellationToken);
|
||||
if (responseObj?.Request?.Success ?? false) {
|
||||
responseData = responseObj.Response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
private async Task<TResponse?> Put<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
|
||||
TResponse? responseData = default(TResponse);
|
||||
try {
|
||||
string json = JsonSerializer.Serialize(data, _serializerOptions);
|
||||
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, uri);
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
HttpResponseMessage response = await _client.SendAsync(request, cancellationToken: cancellationToken);
|
||||
if (response.IsSuccessStatusCode) {
|
||||
OmgLolResponse<TResponse>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<TResponse>>(_serializerOptions, cancellationToken: cancellationToken);
|
||||
if (responseObj?.Request?.Success ?? false) {
|
||||
responseData = responseObj.Response;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
private async Task<TResponse?> Patch<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
|
||||
TResponse? responseData = default(TResponse);
|
||||
try {
|
||||
HttpResponseMessage response = await _client.PatchAsJsonAsync(uri, data, _serializerOptions, cancellationToken: cancellationToken);
|
||||
string str = await response.Content.ReadAsStringAsync();
|
||||
if (response.IsSuccessStatusCode) {
|
||||
OmgLolResponse<TResponse>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<TResponse>>(_serializerOptions, cancellationToken: cancellationToken);
|
||||
if (responseObj?.Request?.Success ?? false) {
|
||||
responseData = responseObj.Response;
|
||||
OmgLolResponse<TResponse>? responseObj = Deserialize<OmgLolResponse<TResponse>>(str);
|
||||
if (responseObj?.Request == null || (responseObj?.Request?.Success ?? false)) {
|
||||
responseData = responseObj!.Response;
|
||||
}
|
||||
}
|
||||
else {
|
||||
OmgLolResponse<TResponse>? responseObj = Deserialize<OmgLolResponse<TResponse>>(str);
|
||||
throw responseObj == null ? new OmgLolApiException<TResponse>(str) : new OmgLolApiException<TResponse>(responseObj);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
|
@ -204,23 +81,46 @@ namespace Neighbourhood.omg.lol
|
|||
return responseData;
|
||||
}
|
||||
|
||||
private async Task<T?> Delete<T>(string uri, CancellationToken cancellationToken = default) where T : IOmgLolResponseData {
|
||||
T? responseData = default(T);
|
||||
/// <summary>
|
||||
/// Performs a request for the supplied uri, with the supplied Http Method,
|
||||
/// with the supplied data in the body (if present)
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">The type of response we are expecting</typeparam>
|
||||
/// <typeparam name="TData">The type of data we are sending</typeparam>
|
||||
/// <param name="uri">The uri to request</param>
|
||||
/// <param name="method">The Http Method to use for the request</param>
|
||||
/// <param name="data">The data to send in the body of the request</param>
|
||||
/// <param name="file">A FileResult for the file to send in the body of the request as binary data</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>The returned data if successful, otherwise default</returns>
|
||||
private async Task<TResponse?> Request<TResponse, TData>(string uri, HttpMethod method, TData? data = default, FileResult? file = null, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
{
|
||||
TResponse? responseData = default;
|
||||
try {
|
||||
HttpResponseMessage response = await _client.DeleteAsync(uri, cancellationToken: cancellationToken);
|
||||
if (response.IsSuccessStatusCode) {
|
||||
string str = await response.Content.ReadAsStringAsync();
|
||||
try {
|
||||
OmgLolResponse<T>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<T>>(_serializerOptions, cancellationToken: cancellationToken);
|
||||
if (responseObj?.Request?.Success ?? false) {
|
||||
responseData = responseObj.Response;
|
||||
}
|
||||
}
|
||||
catch (JsonException ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
Debug.WriteLine(str);
|
||||
}
|
||||
HttpRequestMessage request = new HttpRequestMessage(method, uri);
|
||||
Stream? fileStream = null;
|
||||
if (file != null) {
|
||||
// append "binary" query parameter (if not already present)
|
||||
Uri url = new Uri(_client.BaseAddress?.AbsoluteUri + uri);
|
||||
if (string.IsNullOrEmpty(url.Query)) uri += "?binary";
|
||||
else if (!url.Query.Contains("binary")) uri += "&binary";
|
||||
request = new HttpRequestMessage(method, uri);
|
||||
|
||||
fileStream = await file.OpenReadAsync();
|
||||
HttpContent fileStreamContent = new StreamContent(fileStream);
|
||||
fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(file.ContentType ?? "application/octet-stream");
|
||||
fileStreamContent.Headers.ContentLength = fileStream.Length;
|
||||
request.Content = fileStreamContent;
|
||||
}
|
||||
else if (data != null) {
|
||||
string json = JsonSerializer.Serialize(data, _serializerOptions);
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
HttpResponseMessage response = await _client.SendAsync(request, cancellationToken: cancellationToken);
|
||||
responseData = await DecodeResponse<TResponse>(response, cancellationToken);
|
||||
|
||||
fileStream?.Dispose();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
|
@ -229,6 +129,38 @@ namespace Neighbourhood.omg.lol
|
|||
return responseData;
|
||||
}
|
||||
|
||||
// GET request
|
||||
private async Task<TResponse?> Get<TResponse>(string uri, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> await Request<TResponse, object>(uri, HttpMethod.Get, cancellationToken: cancellationToken);
|
||||
|
||||
// POST request
|
||||
private async Task<TResponse?> Post<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> await Request<TResponse, TData>(uri, HttpMethod.Post, data: data, cancellationToken: cancellationToken);
|
||||
|
||||
// POST request, but with a file as binary data
|
||||
private async Task<TResponse?> PostBinary<TResponse>(string uri, FileResult? fileResult = null, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> await Request<TResponse, object>(uri, HttpMethod.Post, file: fileResult, cancellationToken: cancellationToken);
|
||||
|
||||
// PUT request
|
||||
private async Task<TResponse?> Put<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> await Request<TResponse, TData>(uri, HttpMethod.Put, data: data, cancellationToken: cancellationToken);
|
||||
|
||||
// PATCH request
|
||||
private async Task<TResponse?> Patch<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> await Request<TResponse, TData>(uri, HttpMethod.Patch, data: data, cancellationToken: cancellationToken);
|
||||
|
||||
// Delete request
|
||||
private async Task<TResponse?> Delete<TResponse>(string uri, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> await Request<TResponse, object>(uri, HttpMethod.Delete, cancellationToken: cancellationToken);
|
||||
#endregion
|
||||
|
||||
#region Specific Requests
|
||||
public async Task<List<Status>> StatuslogLatest() =>
|
||||
(await Get<StatusResponseData>("/statuslog/latest"))?.Statuses ?? new List<Status>();
|
||||
|
||||
|
@ -257,25 +189,6 @@ namespace Neighbourhood.omg.lol
|
|||
|
||||
public async Task<PutPicResponseData?> PutPic(string address, string base64Image) =>
|
||||
(await Put<PutPicResponseData, PutPic>($"/address/{address}/pics/upload", new PutPic { Pic = base64Image }));
|
||||
public async Task<PutPicResponseData?> PutPic(string address, IBrowserFile file) {
|
||||
byte[] bytes;
|
||||
using (var memoryStream = new MemoryStream()) {
|
||||
await file.OpenReadStream().CopyToAsync(memoryStream);
|
||||
bytes = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
return await PutPic(address, bytes);
|
||||
}
|
||||
|
||||
public async Task<PutPicResponseData?> PutPic(string address, FileResult file) {
|
||||
byte[] bytes;
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var fileStream = await file.OpenReadAsync();
|
||||
await fileStream.CopyToAsync(memoryStream);
|
||||
bytes = memoryStream.ToArray();
|
||||
|
||||
return await PutPic(address, bytes);
|
||||
}
|
||||
|
||||
public async Task<PutPicResponseData?> PutPic(string address, byte[] bytes) =>
|
||||
await PutPic(address, Convert.ToBase64String(bytes));
|
||||
|
@ -324,7 +237,26 @@ namespace Neighbourhood.omg.lol
|
|||
(MarkupString)((await Get<ThemePreviewResponseData>($"/theme/{theme}/preview"))?.Html ?? string.Empty);
|
||||
|
||||
public async Task<BasicResponseData?> PostProfilePic(string address, FileResult image) =>
|
||||
await PostBinary<BasicResponseData, object>($"/address/{address}/pfp", fileResult: image);
|
||||
await PostBinary<BasicResponseData>($"/address/{address}/pfp", fileResult: image);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Auth
|
||||
/// <summary>
|
||||
/// Add the api token into the default headers
|
||||
/// </summary>
|
||||
/// <param name="token">The api token</param>
|
||||
public void AddToken(string? token = null) {
|
||||
if (token == null) token = Task.Run(() => SecureStorage.GetAsync("accounttoken")).GetAwaiter().GetResult();
|
||||
if (token != null) _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the api token from the default headers
|
||||
/// </summary>
|
||||
public void RemoveToken() {
|
||||
_client.DefaultRequestHeaders.Remove("Authorization");
|
||||
}
|
||||
|
||||
public async Task<string?> OAuth(string code, string client_id, string client_secret, string redirect_uri) {
|
||||
string? token = null;
|
||||
|
@ -343,6 +275,7 @@ namespace Neighbourhood.omg.lol
|
|||
}
|
||||
return token;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<MarkupString?> GetHtml(string url) {
|
||||
string? raw = null;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class AddressResponseList : List<AddressResponseData>, IOmgLolResponseList<AddressResponseData> {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public interface IOmgLolResponseData {
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
11
Models/API/OmgLolApiException.cs
Normal file
11
Models/API/OmgLolApiException.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class OmgLolApiException<T> : Exception where T : IOmgLolResponseData {
|
||||
public OmgLolResponse<T>? Response { get; set; }
|
||||
|
||||
public OmgLolApiException(OmgLolResponse<T>? response) : base(response?.Response?.Message) {
|
||||
Response = response;
|
||||
}
|
||||
|
||||
public OmgLolApiException(string? response) : base(response) { }
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class StatusPostResponseData : IOmgLolResponseData {
|
||||
public string? Message { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public string? Id { get; set; }
|
||||
public string? Url { get; set; }
|
||||
public string? ExternalUrl { get; set; }
|
||||
|
|
Loading…
Reference in a new issue