bit more work on api client

This commit is contained in:
Schedel Pascal
2024-07-31 11:25:19 +02:00
parent 38dc09ab12
commit db5191b0b8
18 changed files with 145 additions and 42 deletions

View File

@@ -0,0 +1,5 @@
namespace OwnChar.Api.Exceptions;
public class ApiException(string message) : Exception(message)
{
}

3
OwnChar/AssemblyInfo.cs Normal file
View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("OwnChar.Server")]

View File

@@ -0,0 +1,8 @@
using Pilz.Cryptography;
namespace OwnChar.Base.Data;
public class OwnCharRequest
{
public SecureString? AuthSecret { get; set; } = null;
}

View File

@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
namespace OwnChar.Base.Data;
public class OwnCharResponse
{
[JsonIgnore]
public bool IsSuccess => ErrorCode != OwnCharResponseError.None;
public OwnCharResponseError ErrorCode { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace OwnChar.Base.Data;
public enum OwnCharResponseError
{
None,
Default,
NotAuthorized,
}

View File

@@ -1,8 +1,6 @@
using OwnChar.Client.Data.Model;
namespace OwnChar.Base.Data.Requests;
namespace OwnChar.Base.Data.Requests;
public class GroupMemberAddRequest
public class GroupMemberAddRequest : OwnCharRequest
{
public Dictionary<long, MemberLevel> Members { get; } = [];
}

View File

@@ -2,7 +2,7 @@
namespace OwnChar.Base.Data.Requests;
public class GroupMemberRemoveRequest
public class GroupMemberRemoveRequest : OwnCharRequest
{
public List<long> Members { get; } = [];
}

View File

@@ -2,7 +2,7 @@
namespace OwnChar.Base.Data.Requests;
public class LoginRequest(string username, SecureString password)
public class LoginRequest(string username, SecureString password) : OwnCharRequest
{
public string Username { get; } = username;
public SecureString Password { get; } = password;

View File

@@ -1,5 +1,5 @@
namespace OwnChar.Base.Data.Requests;
public class LogoutRequest
public class LogoutRequest : OwnCharRequest
{
}

View File

@@ -2,7 +2,7 @@
namespace OwnChar.Base.Data.Requests;
public class UpdateRequest(OwnCharObjectUpdate update)
public class UpdateRequest(OwnCharObjectUpdate update) : OwnCharRequest
{
public OwnCharObjectUpdate Update { get; } = update;
}

View File

@@ -2,9 +2,9 @@
namespace OwnChar.Base.Data.Responses;
public class LoginResponse(UserAccount account, UserProfile profile, string secret)
public class LoginResponse(UserAccount? account, UserProfile? profile, string? secret) : OwnCharResponse
{
public UserAccount Account { get; } = account;
public UserProfile Profile { get; } = profile;
public UserAccount? Account { get; } = account;
public UserProfile? Profile { get; } = profile;
public string? Secret { get; } = secret;
}

View File

@@ -1,5 +1,5 @@
namespace OwnChar.Client.Data.Clients;
public class CharactersApiClient
public class CharactersApiClient(OwnCharApiClient client)
{
}

View File

@@ -1,5 +1,5 @@
namespace OwnChar.Client.Data.Clients;
public class GroupsApiClient
public class GroupsApiClient(OwnCharApiClient client)
{
}

View File

@@ -1,14 +1,30 @@
namespace OwnChar.Client.Data.Clients;
using OwnChar.Base.Data;
using OwnChar.Base.Data.Requests;
using OwnChar.Base.Data.Responses;
using OwnChar.Client.Data.Model;
using Pilz.Cryptography;
public class LoginApiClient
namespace OwnChar.Client.Data.Clients;
public class LoginApiClient(OwnCharApiClient client)
{
public string Login(string username, string password)
public async Task<UserProfile?> Login(string username, SecureString password)
{
var result = await client.MakeRequest<LoginRequest, LoginResponse>("/auth/login", new(username, password));
if (!string.IsNullOrEmpty(result.Secret) && result.Profile != null && result.Account != null)
{
client.AuthSecret = result.Secret;
return result.Profile;
}
return null;
}
public string Logout()
public async Task<bool> Logout()
{
await client.MakeRequest<LogoutRequest, OwnCharResponse>("/auth/logout", new());
client.AuthSecret = null;
return true;
}
}

View File

@@ -1,5 +1,5 @@
namespace OwnChar.Client.Data.Clients;
public class UsersApiClient
public class UsersApiClient(OwnCharApiClient client)
{
}

View File

@@ -1,6 +1,63 @@
namespace OwnChar.Client.Data;
using OwnChar.Api.Exceptions;
using OwnChar.Base.Data;
using OwnChar.Client.Data.Clients;
using System.Net.Http.Json;
namespace OwnChar.Client.Data;
public class OwnCharApiClient
{
private readonly HttpClient httpClient = new();
private readonly Dictionary<Type, object> clients = [];
internal string? AuthSecret { get; set; } = null;
public bool IsLoggedIn => AuthSecret != null;
public LoginApiClient Auth { get; }
public UsersApiClient Users { get; }
public CharactersApiClient Characters { get; }
public GroupsApiClient Groups { get; }
public OwnCharApiClient()
{
Auth = GetClient<LoginApiClient>();
Users = GetClient<UsersApiClient>();
Characters = GetClient<CharactersApiClient>();
Groups = GetClient<GroupsApiClient>();
}
public T GetClient<T>() where T : class
{
var t = typeof(T);
if (clients.TryGetValue(t, out var client) && client is T clientT1)
return clientT1;
if (Activator.CreateInstance(t, this) is T clientT2)
{
clients.Add(t, clientT2);
return clientT2;
}
throw new Exception("Client could not be created!");
}
public async Task<TResponse> MakeRequest<TRequest, TResponse>(string requestUrl, TRequest request) where TRequest : OwnCharRequest where TResponse : OwnCharResponse
{
if (await TryMakeRequest<TRequest, TResponse>(requestUrl, request) is TResponse response)
return response;
throw new ApiException(string.Format("The api request to \"{0}\" failed!", requestUrl));
}
public async Task<TResponse?> TryMakeRequest<TRequest, TResponse>(string requestUrl, TRequest request) where TRequest : OwnCharRequest where TResponse : OwnCharResponse
{
request.AuthSecret = AuthSecret;
var res = await httpClient.PostAsJsonAsync(requestUrl, request);
if (res.IsSuccessStatusCode)
return await res.Content.ReadFromJsonAsync<TResponse>();
return null;
}
}

View File

@@ -1,22 +1,21 @@
using OwnChar.Client.Managers;
using OwnChar.Data;
using OwnChar.Model;
using OwnChar.Client.Data;
using OwnChar.Client.Data.Model;
using OwnChar.Client.Managers;
using Pilz.Cryptography;
namespace OwnChar.Client;
public interface IOwnCharManager
{
IDataManager? DataManager { get; set; }
UserAccountBase? CurrentUser { get; }
bool IsLoggedIn { get; }
OwnCharApiClient? Api { get; }
IUserManager Users { get; }
IGroupsManager Groups { get; }
ICharacterManager Characters { get; }
bool Login(IDataManager? proxy, string? username, SecureString? password);
bool Logout();
Task<UserProfile?> Login(string? username, SecureString? password);
Task<bool> Logout();
public static IOwnCharManager CreateDefault()
{

View File

@@ -1,8 +1,7 @@
using OwnChar.Api.Exceptions;
using OwnChar.Client.Data;
using OwnChar.Client.Data.Model;
using OwnChar.Client.Managers;
using OwnChar.Data;
using OwnChar.Manager.Modules;
using OwnChar.Model;
using Pilz.Cryptography;
using System.Diagnostics.CodeAnalysis;
@@ -11,11 +10,10 @@ namespace OwnChar.Client;
internal class OwnCharManager : IOwnCharManager
{
// User
public bool IsLoggedIn => CurrentUser != null;
public UserAccountBase? CurrentUser { get; private set; }
public bool IsLoggedIn => Api is not null && Api.IsLoggedIn;
// Data Provider
public IDataManager? DataManager { get; set; }
public OwnCharApiClient? Api { get; private set; }
// Manager
public IUserManager Users { get; }
@@ -29,10 +27,10 @@ internal class OwnCharManager : IOwnCharManager
Characters = new CharacterManager(this);
}
[MemberNotNull(nameof(CurrentUser), nameof(DataManager))]
[MemberNotNull(nameof(Api))]
internal protected void CheckLogin()
{
if (!IsLoggedIn || DataManager == null)
if (!IsLoggedIn)
throw new LoginException("You are already logged in!");
}
@@ -40,25 +38,25 @@ internal class OwnCharManager : IOwnCharManager
/// Tries to login on the given data provider.
/// </summary>
/// <returns>Returns <see cref="true"/> if the login was successfull and <see cref="false"/> if not.</returns>
public bool Login(IDataManager? proxy, string? username, SecureString? password)
public Task<UserProfile?> Login(string? username, SecureString? password)
{
ArgumentNullException.ThrowIfNull(proxy, nameof(proxy));
ArgumentNullException.ThrowIfNull(Api, nameof(Api));
ArgumentException.ThrowIfNullOrWhiteSpace(username, nameof(username));
ArgumentException.ThrowIfNullOrWhiteSpace(password, nameof(password));
username = username.Trim().ToLower();
CurrentUser = proxy.Login(username, OwnCharUtils.HashPassword(username, password));
DataManager = proxy;
return IsLoggedIn;
return Api.Auth.Login(username, OwnCharUtils.HashPassword(username, password));
}
/// <summary>
/// Closes the session on the current data provider.
/// </summary>
/// <returns></returns>
public bool Logout()
public async Task<bool> Logout()
{
return DataManager?.Logout(CurrentUser) ?? true;
if (Api is null)
return true;
return await Api.Auth.Logout();
}
}