Compare commits
4 Commits
b08c7619a4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32a8a3ec38 | ||
|
|
f268058cdc | ||
| 6b5445ccdf | |||
| 24708eb798 |
@@ -1,5 +1,5 @@
|
||||
namespace OwnChar.Api.Clients;
|
||||
|
||||
public class CharactersApiClient(OwnCharApiClient client)
|
||||
internal class CharactersApiClient(OwnCharApiClient client) : ICharactersApiClient
|
||||
{
|
||||
}
|
||||
|
||||
@@ -4,40 +4,40 @@ using OwnChar.Data.Model.Client;
|
||||
|
||||
namespace OwnChar.Api.Clients;
|
||||
|
||||
public class GroupsApiClient(OwnCharApiClient client)
|
||||
internal class GroupsApiClient(IOwnCharApiClient client) : IGroupsApiClient
|
||||
{
|
||||
public async Task<Group?> GetGroup(long id)
|
||||
{
|
||||
var result = await client.MakeRequest<GetSinlgeObjectRequest, GetSingleObjectResponse<Group>>("/groups/get/byid", new(id));
|
||||
result.EnsureSuccess();
|
||||
return result.Result;
|
||||
var result = await client.SendRequest<GetSingleObjectResponse<Group>>("/groups/get/byid", new GetSinlgeObjectRequest(id));
|
||||
result.EnsureOk();
|
||||
return result.Message.Result;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Group>> GetGroupsForProfile(long userProfileId)
|
||||
{
|
||||
var result = await client.MakeRequest<GetGroupsRequest, GetGroupsResponse>("/groups/get", new()
|
||||
var result = await client.SendRequest<GetGroupsResponse>("/groups/get", new GetGroupsRequest
|
||||
{
|
||||
ProfileId = userProfileId,
|
||||
UseProfileId = true,
|
||||
});
|
||||
result.EnsureSuccess();
|
||||
return result.Groups;
|
||||
result.EnsureOk();
|
||||
return result.Message.Groups;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Group>> GetPublicGroups()
|
||||
{
|
||||
var result = await client.MakeRequest<GetGroupsRequest, GetGroupsResponse>("/groups/get", new());
|
||||
result.EnsureSuccess();
|
||||
return result.Groups;
|
||||
var result = await client.SendRequest<GetGroupsResponse>("/groups/get", new GetGroupsRequest());
|
||||
result.EnsureOk();
|
||||
return result.Message.Groups;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Group>> GetAllGroups()
|
||||
{
|
||||
var result = await client.MakeRequest<GetGroupsRequest, GetGroupsResponse>("/groups/get", new()
|
||||
var result = await client.SendRequest<GetGroupsResponse>("/groups/get", new GetGroupsRequest
|
||||
{
|
||||
IncludeNonPublic = true,
|
||||
});
|
||||
result.EnsureSuccess();
|
||||
return result.Groups;
|
||||
result.EnsureOk();
|
||||
return result.Message.Groups;
|
||||
}
|
||||
}
|
||||
|
||||
5
OwnChar/Api/Clients/ICharactersApiClient.cs
Normal file
5
OwnChar/Api/Clients/ICharactersApiClient.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace OwnChar.Api.Clients;
|
||||
|
||||
public interface ICharactersApiClient
|
||||
{
|
||||
}
|
||||
10
OwnChar/Api/Clients/IGroupsApiClient.cs
Normal file
10
OwnChar/Api/Clients/IGroupsApiClient.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using OwnChar.Data.Model.Client;
|
||||
|
||||
namespace OwnChar.Api.Clients;
|
||||
public interface IGroupsApiClient
|
||||
{
|
||||
Task<IEnumerable<Group>> GetAllGroups();
|
||||
Task<Group?> GetGroup(long id);
|
||||
Task<IEnumerable<Group>> GetGroupsForProfile(long userProfileId);
|
||||
Task<IEnumerable<Group>> GetPublicGroups();
|
||||
}
|
||||
9
OwnChar/Api/Clients/ILoginApiClient.cs
Normal file
9
OwnChar/Api/Clients/ILoginApiClient.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using OwnChar.Data.Model.Client;
|
||||
using Pilz.Cryptography;
|
||||
|
||||
namespace OwnChar.Api.Clients;
|
||||
public interface ILoginApiClient
|
||||
{
|
||||
Task<UserProfile?> Login(string username, SecureString password);
|
||||
Task<bool> Logout();
|
||||
}
|
||||
11
OwnChar/Api/Clients/IUsersApiClient.cs
Normal file
11
OwnChar/Api/Clients/IUsersApiClient.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OwnChar.Api.Clients;
|
||||
|
||||
public interface IUsersApiClient
|
||||
{
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
using OwnChar.Api.Packets;
|
||||
using OwnChar.Api.Packets.General;
|
||||
using OwnChar.Api.Packets.General;
|
||||
using OwnChar.Data.Model.Client;
|
||||
using Pilz.Cryptography;
|
||||
|
||||
namespace OwnChar.Api.Clients;
|
||||
|
||||
public class LoginApiClient(OwnCharApiClient client)
|
||||
internal class LoginApiClient(OwnCharApiClient client) : ILoginApiClient
|
||||
{
|
||||
public async Task<UserProfile?> Login(string username, SecureString password)
|
||||
{
|
||||
var result = await client.MakeRequest<LoginRequest, LoginResponse>("/auth/login", new(username, password));
|
||||
var result = await client.SendRequest<LoginResponse>("/auth/login", new LoginRequest(username, password));
|
||||
result.EnsureOk();
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Secret) && result.Profile != null && result.Account != null)
|
||||
if (!string.IsNullOrEmpty(result.Message.Secret) && result.Message.Profile != null && result.Message.Account != null)
|
||||
{
|
||||
client.AuthSecret = result.Secret;
|
||||
return result.Profile;
|
||||
client.AuthKey = result.Message.Secret;
|
||||
client.CurrentUser = result.Message.Account;
|
||||
return result.Message.Profile;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -22,8 +23,8 @@ public class LoginApiClient(OwnCharApiClient client)
|
||||
|
||||
public async Task<bool> Logout()
|
||||
{
|
||||
await client.MakeRequest<LogoutRequest, OwnCharResponse>("/auth/logout", new());
|
||||
client.AuthSecret = null;
|
||||
return true;
|
||||
var res = await client.SendRequest("/auth/logout");
|
||||
client.AuthKey = null;
|
||||
return res.IsOk;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace OwnChar.Api.Clients;
|
||||
|
||||
public class UsersApiClient(OwnCharApiClient client)
|
||||
internal class UsersApiClient(IOwnCharApiClient client) : IUsersApiClient
|
||||
{
|
||||
}
|
||||
|
||||
29
OwnChar/Api/Endpoint/CharactersApi.cs
Normal file
29
OwnChar/Api/Endpoint/CharactersApi.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace OwnChar.Api.Endpoint;
|
||||
|
||||
internal class CharactersApi(IOwnCharApiServer server)
|
||||
{
|
||||
private IResult GetCharacter(long characterId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private IResult CreateGroupCharacter(string name, long groupId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private IResult CreateUserCharacter(string name, long userId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private IResult UpdateCharacter(long characterId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private IResult DeleteCharacter(long characterId)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
184
OwnChar/Api/Endpoint/GroupsApi.cs
Normal file
184
OwnChar/Api/Endpoint/GroupsApi.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using OwnChar.Api.Packets.General;
|
||||
using OwnChar.Api.Packets.Groups;
|
||||
using OwnChar.Api.Updates;
|
||||
using OwnChar.Data;
|
||||
using OwnChar.Data.Model.Client;
|
||||
using OwnChar.Data.Model.Server;
|
||||
using OwnChar.Server.Extensions;
|
||||
using Pilz.Net.Api;
|
||||
|
||||
namespace OwnChar.Api.Endpoint;
|
||||
|
||||
internal class GroupsApi(IOwnCharApiServer server)
|
||||
{
|
||||
[MessageHandler("/group/get/byid")]
|
||||
public ApiResult GetById(GetSinlgeObjectRequest request, ApiRequestInfo info)
|
||||
{
|
||||
if (!server.CheckLogin(info, UserType.Guest, out UserAccountDb? user))
|
||||
return ApiResult.Unauthorized();
|
||||
|
||||
if (server.Data?.Set<GroupDb>().FirstOrDefault(n => n.Id == request.ObjectId && n.Members.Any(m => m.User != null && m.User.Id == user.Id)) is not GroupDb group)
|
||||
return ApiResult.NotFound();
|
||||
|
||||
return ApiResult.Ok(new GetSingleObjectResponse<Group>(group.ToClient()));
|
||||
}
|
||||
|
||||
[MessageHandler("/group/get", RequiesAuth = true)]
|
||||
public ApiResult Get(GetGroupsRequest request, ApiRequestInfo info)
|
||||
{
|
||||
if (!server.CheckLogin(info, UserType.Guest, out UserAccountDb? user))
|
||||
return ApiResult.Unauthorized();
|
||||
|
||||
IQueryable<GroupDb> groups;
|
||||
|
||||
if (request.UseProfileId && server.Data!.Set<UserProfileDb>().FirstOrDefault(p => p.Id == request.ProfileId) is UserProfileDb profile)
|
||||
groups = server.Data!.Set<GroupDb>().Where(group => group.Members.Any(m => m.User != null && m.User.Id == profile.Id));
|
||||
else if (request.IncludeNonPublic && user.Is(UserType.Admin))
|
||||
groups = server.Data!.Set<GroupDb>();
|
||||
else
|
||||
groups = Array.Empty<GroupDb>().AsQueryable(); // Currently not supported.
|
||||
|
||||
return ApiResult.Ok(new GetGroupsResponse([.. groups.Select(g => g.ToClient())]));
|
||||
}
|
||||
|
||||
[MessageHandler("/group/create", RequiesAuth = true)]
|
||||
public ApiResult Create(CreateGroupRequest request, ApiRequestInfo info)
|
||||
{
|
||||
if (!server.CheckLogin(info, UserType.User, out UserAccountDb? user))
|
||||
return ApiResult.Unauthorized();
|
||||
|
||||
var group = new GroupDb();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Name))
|
||||
group.Name = request.Name;
|
||||
|
||||
group.Members.Add(new()
|
||||
{
|
||||
User = user.Profile,
|
||||
Level = MemberLevel.Owner,
|
||||
});
|
||||
|
||||
server.Data!.Update(group);
|
||||
server.Data.SaveChanges();
|
||||
|
||||
return ApiResult.Ok(new CreateGroupResponse(group.ToClient()));
|
||||
}
|
||||
|
||||
[MessageHandler("/group/update", RequiesAuth = true)]
|
||||
public ApiResult Update(UpdateRequest request, ApiRequestInfo info)
|
||||
{
|
||||
if (!server.CheckLogin(info, UserType.User, out UserAccountDb? user)
|
||||
|| server.Data?.Set<GroupDb>().FirstOrDefault(n => n.Id == request.Update.Id) is not GroupDb group
|
||||
|| !group.Members.Any(m => m.Id == user.Profile!.Id && m.Level >= MemberLevel.Admin
|
||||
|| user.IsNot(UserType.Admin)))
|
||||
return ApiResult.Unauthorized();
|
||||
|
||||
if (request.Update is not GroupUpdate update)
|
||||
return ApiResult.NotFound();
|
||||
|
||||
group.Name = update.Name;
|
||||
group.Fandom = update.Fandom;
|
||||
|
||||
server.Data.Update(group);
|
||||
server.Data.SaveChanges();
|
||||
|
||||
return ApiResult.Ok();
|
||||
}
|
||||
|
||||
[MessageHandler("/group/delete", RequiesAuth = true)]
|
||||
public ApiResult Delete(DeleteObjectRequest request, ApiRequestInfo info)
|
||||
{
|
||||
if (!server.CheckLogin(info, UserType.User, out UserAccountDb? user)
|
||||
|| server.Data?.Set<GroupDb>().FirstOrDefault(n => n.Id == request.ObjectId) is not GroupDb group
|
||||
|| !group.Members.Any(m => m.Id == user.Profile!.Id && m.Level >= MemberLevel.Owner)
|
||||
|| user.IsNot(UserType.Admin))
|
||||
return ApiResult.Unauthorized();
|
||||
|
||||
server.Data.Remove(group);
|
||||
server.Data.SaveChanges();
|
||||
|
||||
return ApiResult.Ok();
|
||||
}
|
||||
|
||||
[MessageHandler("/group/members/get", RequiesAuth = true)]
|
||||
public ApiResult GetMembers(GetGroupMembersRequest request, ApiRequestInfo info)
|
||||
{
|
||||
if (!server.CheckLogin(info, UserType.User, out UserAccountDb? user)
|
||||
|| server.Data?.Set<GroupDb>().FirstOrDefault(n => n.Id == request.GroupId) is not GroupDb group
|
||||
|| !group.Members.Any(m => m.Id == user.Profile!.Id && m.Level >= MemberLevel.Member)
|
||||
|| user.IsNot(UserType.Admin))
|
||||
return ApiResult.Unauthorized();
|
||||
|
||||
var members = group.Members.Select(n => n.ToClient());
|
||||
return ApiResult.Ok(new GetGroupMembersResponse(members.ToList()));
|
||||
}
|
||||
|
||||
[MessageHandler("/group/members/add", RequiesAuth = true)]
|
||||
public ApiResult AddMembers(GroupMemberAddRequest request, ApiRequestInfo info)
|
||||
{
|
||||
if (!server.CheckLogin(info, UserType.User, out UserAccountDb? user)
|
||||
|| server.Data?.Set<GroupDb>().FirstOrDefault(n => n.Id == request.GroupId) is not GroupDb group
|
||||
|| !group.Members.Any(m => m.Id == user.Profile!.Id && m.Level >= MemberLevel.Admin)
|
||||
|| user.IsNot(UserType.Admin))
|
||||
return ApiResult.Unauthorized();
|
||||
|
||||
var addedMembers = new List<MemberEntryDb>();
|
||||
|
||||
foreach (var kvp in request.Members)
|
||||
{
|
||||
if (group.Members.FirstOrDefault(m => m.User != null && m.User.Id == kvp.Key) is not MemberEntryDb member
|
||||
&& server.Data.Set<UserProfileDb>().FirstOrDefault(u => u.Id == kvp.Key) is UserProfileDb mu)
|
||||
{
|
||||
member = new()
|
||||
{
|
||||
User = mu,
|
||||
Level = kvp.Value,
|
||||
};
|
||||
server.Data.Update(member);
|
||||
server.Data.Update(group);
|
||||
server.Data.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResult.Ok(new GroupMemberAddResponse(addedMembers.Select(m => m.ToClient()).ToList()));
|
||||
}
|
||||
|
||||
[MessageHandler("/group/members/update", RequiesAuth = true)]
|
||||
public ApiResult UpdateMember(UpdateRequest request, ApiRequestInfo info)
|
||||
{
|
||||
if (!server.CheckLogin(info, UserType.User, out UserAccountDb? user)
|
||||
|| server.Data?.Set<GroupDb>().FirstOrDefault(n => n.Id == request.Update.Id) is not GroupDb group
|
||||
|| group.Members.FirstOrDefault(m => m.Id == request.Update.Id) is not MemberEntryDb member
|
||||
|| !group.Members.Any(m => m.Id == user.Profile!.Id && m.Level >= MemberLevel.Admin
|
||||
|| user.IsNot(UserType.Admin)))
|
||||
return ApiResult.Unauthorized();
|
||||
|
||||
if (request.Update is not MemberUpdate update)
|
||||
return ApiResult.NotFound();
|
||||
|
||||
member.Level = update.Level;
|
||||
|
||||
server.Data.Update(member);
|
||||
server.Data.SaveChanges();
|
||||
|
||||
return ApiResult.Ok();
|
||||
}
|
||||
|
||||
[MessageHandler("/group/members/remove", RequiesAuth = true)]
|
||||
public ApiResult RemoveMembers(DeleteObjectRequest request, ApiRequestInfo info)
|
||||
{
|
||||
if (!server.CheckLogin(info, UserType.User, out UserAccountDb? user)
|
||||
|| server.Data?.Set<MemberEntryDb>().FirstOrDefault(m => m.Id == request.ObjectId) is not MemberEntryDb member
|
||||
|| server.Data?.Set<GroupDb>().FirstOrDefault(n => n.Members.Contains(member)) is not GroupDb group
|
||||
|| !group.Members.Any(m => m.Id == user.Profile!.Id && m.Level >= MemberLevel.Admin
|
||||
|| user.IsNot(UserType.Admin)))
|
||||
return ApiResult.Unauthorized();
|
||||
|
||||
group.Members.Remove(member);
|
||||
server.Data.Remove(member);
|
||||
server.Data.Update(group);
|
||||
server.Data.SaveChanges();
|
||||
|
||||
return ApiResult.Ok();
|
||||
}
|
||||
}
|
||||
30
OwnChar/Api/Endpoint/LoginApi.cs
Normal file
30
OwnChar/Api/Endpoint/LoginApi.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using OwnChar.Api.Packets;
|
||||
using OwnChar.Api.Packets.General;
|
||||
using OwnChar.Data.Model.Server;
|
||||
using OwnChar.Server.Extensions;
|
||||
using Pilz.Net.Api;
|
||||
|
||||
namespace OwnChar.Api.Endpoint;
|
||||
|
||||
internal class LoginApi(OwnCharApiServer server)
|
||||
{
|
||||
[MessageHandler("/auth/login")]
|
||||
public ApiResult Login(LoginRequest request, ApiRequestInfo info)
|
||||
{
|
||||
if (server.Data?.Set<UserAccountDb>()?.FirstOrDefault(n => n.Username == request.Username && n.Password == request.Password) is UserAccountDb acc && acc.Profile != null)
|
||||
{
|
||||
if (info.IsAuthenticated)
|
||||
server.Logout(info.AuthKey);
|
||||
return ApiResult.Ok(new LoginResponse(acc.ToClient(), acc.Profile.ToClient(), server.Login(acc)));
|
||||
}
|
||||
return ApiResult.NotFound();
|
||||
}
|
||||
|
||||
[MessageHandler("/auth/logout")]
|
||||
public ApiResult Logout(ApiRequestInfo info)
|
||||
{
|
||||
if (info.IsAuthenticated)
|
||||
server.Logout(info.AuthKey);
|
||||
return ApiResult.Ok();
|
||||
}
|
||||
}
|
||||
39
OwnChar/Api/Endpoint/UsersApi.cs
Normal file
39
OwnChar/Api/Endpoint/UsersApi.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
namespace OwnChar.Api.Endpoint;
|
||||
|
||||
internal class UsersApi(IOwnCharApiServer server)
|
||||
{
|
||||
private IResult GetUsers()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private IResult GetUser(long userId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private IResult GetUserProfile(long userId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private IResult CreateUser(string username, string password)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private IResult DeleteUser(long userId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private IResult UpdateUserPassword(long userId, string username, string password)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private IResult UpdateUserProfile(long profileId, string displayName)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
21
OwnChar/Api/IOwnCharApiClient.cs
Normal file
21
OwnChar/Api/IOwnCharApiClient.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using OwnChar.Api.Clients;
|
||||
using OwnChar.Data.Model.Client;
|
||||
using Pilz.Net.Api;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace OwnChar.Api;
|
||||
public interface IOwnCharApiClient : IApiClient
|
||||
{
|
||||
UserAccount? CurrentUser { get; }
|
||||
|
||||
[MemberNotNullWhen(true, nameof(AuthKey), nameof(CurrentUser))]
|
||||
bool IsLoggedIn { get; }
|
||||
|
||||
ILoginApiClient Auth { get; }
|
||||
|
||||
IGroupsApiClient Groups { get; }
|
||||
|
||||
ICharactersApiClient Characters { get; }
|
||||
|
||||
IUsersApiClient Users { get; }
|
||||
}
|
||||
20
OwnChar/Api/IOwnCharApiServer.cs
Normal file
20
OwnChar/Api/IOwnCharApiServer.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OwnChar.Data.Model.Base;
|
||||
using Pilz.Net.Api;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace OwnChar.Api;
|
||||
|
||||
public interface IOwnCharApiServer : IApiServer
|
||||
{
|
||||
DbContext? Data { get; }
|
||||
|
||||
[MemberNotNull(nameof(Data))]
|
||||
void CheckLogin(string secret);
|
||||
|
||||
[MemberNotNullWhen(true, nameof(Data))]
|
||||
bool IsLoggedIn(string secret);
|
||||
|
||||
[MemberNotNullWhen(true, nameof(Data))]
|
||||
UserAccountBase? GetUser(string secret);
|
||||
}
|
||||
@@ -1,66 +1,38 @@
|
||||
using Newtonsoft.Json;
|
||||
using OwnChar.Api.Clients;
|
||||
using OwnChar.Api.Exceptions;
|
||||
using OwnChar.Api.Packets;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using OwnChar.Api.Clients;
|
||||
using OwnChar.Data.Model.Client;
|
||||
using Pilz.Cryptography;
|
||||
using Pilz.Net.Api;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace OwnChar.Api;
|
||||
|
||||
public class OwnCharApiClient
|
||||
internal class OwnCharApiClient : ApiClient, IOwnCharApiClient
|
||||
{
|
||||
private readonly HttpClient httpClient = new();
|
||||
private readonly Dictionary<Type, object> clients = [];
|
||||
public UserAccount? CurrentUser { get; internal set; }
|
||||
|
||||
internal string? AuthSecret { get; set; } = null;
|
||||
public bool IsLoggedIn => AuthSecret != null;
|
||||
[MemberNotNullWhen(true, nameof(AuthKey), nameof(CurrentUser))]
|
||||
public bool IsLoggedIn => CurrentUser != null && !string.IsNullOrWhiteSpace(AuthKey);
|
||||
|
||||
public LoginApiClient Auth { get; }
|
||||
public UsersApiClient Users { get; }
|
||||
public CharactersApiClient Characters { get; }
|
||||
public GroupsApiClient Groups { get; }
|
||||
public ILoginApiClient Auth { get; }
|
||||
|
||||
public OwnCharApiClient()
|
||||
public IGroupsApiClient Groups { get; }
|
||||
|
||||
public ICharactersApiClient Characters { get; }
|
||||
|
||||
public IUsersApiClient Users { get; }
|
||||
|
||||
public OwnCharApiClient(string apiUrl) : base(apiUrl)
|
||||
{
|
||||
Auth = GetClient<LoginApiClient>();
|
||||
Users = GetClient<UsersApiClient>();
|
||||
Characters = GetClient<CharactersApiClient>();
|
||||
Groups = GetClient<GroupsApiClient>();
|
||||
Auth = new LoginApiClient(this);
|
||||
Groups = new GroupsApiClient(this);
|
||||
Characters = new CharactersApiClient(this);
|
||||
Users = new UsersApiClient(this);
|
||||
}
|
||||
|
||||
public T GetClient<T>() where T : class
|
||||
protected override string? EncodeAuthKey()
|
||||
{
|
||||
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 content = new StringContent(JsonHelpers.SerializeRequest(request)!, MediaTypeHeaderValue.Parse("application/json"));
|
||||
var res = await httpClient.PostAsync(requestUrl, content);
|
||||
|
||||
if (res.IsSuccessStatusCode)
|
||||
return JsonHelpers.DeserializeResponse<TResponse>(await res.Content.ReadAsStringAsync());
|
||||
|
||||
return null;
|
||||
if (IsLoggedIn && !string.IsNullOrWhiteSpace(CurrentUser.Username))
|
||||
return new SecureString($"{CurrentUser}:{AuthKey}", false).EncryptedValue;
|
||||
return AuthKey;
|
||||
}
|
||||
}
|
||||
|
||||
89
OwnChar/Api/OwnCharApiServer.cs
Normal file
89
OwnChar/Api/OwnCharApiServer.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OwnChar.Api;
|
||||
using OwnChar.Api.Endpoint;
|
||||
using OwnChar.Data.Model.Base;
|
||||
using OwnChar.Server.Data;
|
||||
using Pilz.Cryptography;
|
||||
using Pilz.Extensions.Collections;
|
||||
using Pilz.Net.Api;
|
||||
|
||||
namespace OwnChar.Api;
|
||||
|
||||
internal class OwnCharApiServer(string apiUrl, string dbServer, string dbDatabase, string dbUser, SecureString dbPassword) : ApiServer(apiUrl), IOwnCharApiServer
|
||||
{
|
||||
private readonly Dictionary<string, UserAccountBase> users = [];
|
||||
|
||||
public DbContext? Data { get; private set; }
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
Log.Info("Prepairing server");
|
||||
|
||||
// Load database
|
||||
Log.Debug("Loading database");
|
||||
Data = new DatabaseContext(dbServer, dbDatabase, dbUser, dbPassword);
|
||||
|
||||
// Built-in endpoints
|
||||
Log.Debug("Loading internal api endpoints");
|
||||
RegisterHandler(new LoginApi(this));
|
||||
RegisterHandler(new UsersApi(this));
|
||||
RegisterHandler(new GroupsApi(this));
|
||||
RegisterHandler(new CharactersApi(this));
|
||||
|
||||
// Run server
|
||||
Log.Info("Starting webserver");
|
||||
base.Start();
|
||||
}
|
||||
|
||||
protected override string? DecodeAuthKey(string authKey)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(authKey))
|
||||
return new SecureString(authKey, true).Value;
|
||||
return authKey;
|
||||
}
|
||||
|
||||
protected override bool CheckAuthentication(string authKey)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(authKey)
|
||||
|| authKey.Split(":") is not string[] authKeyParts
|
||||
|| authKey.Length != 2
|
||||
|| string.IsNullOrWhiteSpace(authKeyParts[0])
|
||||
|| string.IsNullOrWhiteSpace(authKeyParts[1])
|
||||
|| !IsLoggedIn(authKeyParts[1]);
|
||||
}
|
||||
|
||||
public string Login(UserAccountBase account)
|
||||
{
|
||||
var secret = new UniquieID(UniquieIDGenerationMode.GenerateOnInit).ID;
|
||||
users.Add(secret, account);
|
||||
Log.DebugFormat("Logged-in out user with secret {0}", secret);
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void Logout(string secret)
|
||||
{
|
||||
users.Remove(secret);
|
||||
Log.DebugFormat("Logged-out user with secret {0}", secret);
|
||||
}
|
||||
|
||||
public bool IsLoggedIn(string secret)
|
||||
{
|
||||
Log.DebugFormat("Deleting user with secret {0}", secret);
|
||||
return users.ContainsKey(secret);
|
||||
}
|
||||
|
||||
public void CheckLogin(string secret)
|
||||
{
|
||||
Log.DebugFormat("Checking login for user with secret {0}", secret);
|
||||
if (!IsLoggedIn(secret))
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
public UserAccountBase? GetUser(string secret)
|
||||
{
|
||||
Log.DebugFormat("Getting user with secret {0}", secret);
|
||||
if (users.TryGetValue(secret, out UserAccountBase? value))
|
||||
return value;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
using OwnChar.Api.Packets;
|
||||
|
||||
namespace OwnChar.Api.Packets.General;
|
||||
|
||||
public class DeleteObjectResponse : OwnCharResponse
|
||||
{
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
using OwnChar.Api.Packets;
|
||||
|
||||
namespace OwnChar.Api.Packets.General;
|
||||
|
||||
public class LogoutRequest : OwnCharRequest
|
||||
{
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
using OwnChar.Api.Packets;
|
||||
|
||||
namespace OwnChar.Api.Packets.General;
|
||||
|
||||
public class LogoutResponse : OwnCharResponse
|
||||
{
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace OwnChar.Api.Packets.General;
|
||||
|
||||
public class SetOwnerResponse : OwnCharResponse
|
||||
{
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
using OwnChar.Api.Packets;
|
||||
|
||||
namespace OwnChar.Api.Packets.General;
|
||||
|
||||
public class UpdateResponse : OwnCharResponse
|
||||
{
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
using Pilz.Cryptography;
|
||||
using Pilz.Net.Api;
|
||||
|
||||
namespace OwnChar.Api.Packets;
|
||||
|
||||
public class OwnCharRequest
|
||||
public class OwnCharRequest : ApiMessage
|
||||
{
|
||||
public string? Username { get; set; } = null;
|
||||
public SecureString? AuthSecret { get; set; } = null;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Pilz.Net.Api;
|
||||
|
||||
namespace OwnChar.Api.Packets;
|
||||
|
||||
public class OwnCharResponse
|
||||
public class OwnCharResponse : ApiMessage
|
||||
{
|
||||
public OwnCharResponseError ErrorCode { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsSuccess => ErrorCode != OwnCharResponseError.None;
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace OwnChar.Api.Packets;
|
||||
|
||||
public enum OwnCharResponseError
|
||||
{
|
||||
None,
|
||||
Default,
|
||||
NotFound,
|
||||
StillInUse,
|
||||
}
|
||||
10
OwnChar/Data/Model.Server/CharacterDb.cs
Normal file
10
OwnChar/Data/Model.Server/CharacterDb.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using OwnChar.Data.Model.Base;
|
||||
|
||||
namespace OwnChar.Data.Model.Server;
|
||||
|
||||
public class CharacterDb : CharacterBase
|
||||
{
|
||||
public UserProfileDb? Owner { get; set; }
|
||||
public List<PropertyCategoryDb> PropCats { get; } = [];
|
||||
public List<PropertyDb> Props { get; } = [];
|
||||
}
|
||||
17
OwnChar/Data/Model.Server/DatabaseContext.cs
Normal file
17
OwnChar/Data/Model.Server/DatabaseContext.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pilz.Cryptography;
|
||||
|
||||
namespace OwnChar.Server.Data;
|
||||
|
||||
public class DatabaseContext(string dbHost, string dbDatabase, string dbUser, SecureString dbPassword) : DbContext
|
||||
{
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
9
OwnChar/Data/Model.Server/GroupDb.cs
Normal file
9
OwnChar/Data/Model.Server/GroupDb.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using OwnChar.Data.Model.Base;
|
||||
|
||||
namespace OwnChar.Data.Model.Server;
|
||||
|
||||
public class GroupDb : GroupBase
|
||||
{
|
||||
public List<CharacterDb> Characters { get; } = [];
|
||||
public List<MemberEntryDb> Members { get; } = [];
|
||||
}
|
||||
8
OwnChar/Data/Model.Server/MemberEntryDb.cs
Normal file
8
OwnChar/Data/Model.Server/MemberEntryDb.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using OwnChar.Data.Model.Base;
|
||||
|
||||
namespace OwnChar.Data.Model.Server;
|
||||
|
||||
public class MemberEntryDb() : MemberEntryBase
|
||||
{
|
||||
public virtual UserProfileDb? User { get; set; }
|
||||
}
|
||||
7
OwnChar/Data/Model.Server/PropertyCategoryDb.cs
Normal file
7
OwnChar/Data/Model.Server/PropertyCategoryDb.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using OwnChar.Data.Model.Base;
|
||||
|
||||
namespace OwnChar.Data.Model.Server;
|
||||
|
||||
public class PropertyCategoryDb : PropertyCategoryBase
|
||||
{
|
||||
}
|
||||
7
OwnChar/Data/Model.Server/PropertyDb.cs
Normal file
7
OwnChar/Data/Model.Server/PropertyDb.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using OwnChar.Data.Model.Base;
|
||||
|
||||
namespace OwnChar.Data.Model.Server;
|
||||
|
||||
public class PropertyDb : PropertyBase
|
||||
{
|
||||
}
|
||||
9
OwnChar/Data/Model.Server/UserAccountDb.cs
Normal file
9
OwnChar/Data/Model.Server/UserAccountDb.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using OwnChar.Data.Model.Base;
|
||||
|
||||
namespace OwnChar.Data.Model.Server;
|
||||
|
||||
public class UserAccountDb : UserAccountBase
|
||||
{
|
||||
public virtual string? Password { get; set; }
|
||||
public UserProfileDb? Profile { get; set; }
|
||||
}
|
||||
8
OwnChar/Data/Model.Server/UserProfileDb.cs
Normal file
8
OwnChar/Data/Model.Server/UserProfileDb.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using OwnChar.Data.Model.Base;
|
||||
|
||||
namespace OwnChar.Data.Model.Server;
|
||||
|
||||
public class UserProfileDb : UserProfileBase
|
||||
{
|
||||
public List<CharacterDb> Characters { get; } = [];
|
||||
}
|
||||
11
OwnChar/Extensions/ApiRequestInfoExtensions.cs
Normal file
11
OwnChar/Extensions/ApiRequestInfoExtensions.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Pilz.Net.Api;
|
||||
|
||||
namespace OwnChar.Extensions;
|
||||
|
||||
public static class ApiRequestInfoExtensions
|
||||
{
|
||||
public static string? GetUsername(this ApiRequestInfo @this)
|
||||
{
|
||||
return @this.AuthKey?.Split(':').ElementAtOrDefault(0);
|
||||
}
|
||||
}
|
||||
48
OwnChar/Extensions/ModelExtensions.cs
Normal file
48
OwnChar/Extensions/ModelExtensions.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using OwnChar.Data.Model.Client;
|
||||
using OwnChar.Data.Model.Server;
|
||||
|
||||
namespace OwnChar.Server.Extensions;
|
||||
|
||||
public static class ModelExtensions
|
||||
{
|
||||
public static Group ToClient(this GroupDb @this)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = @this.Id,
|
||||
Name = @this.Name,
|
||||
Fandom = @this.Fandom,
|
||||
IsInternal = @this.IsInternal,
|
||||
};
|
||||
}
|
||||
|
||||
public static UserAccount ToClient(this UserAccountDb @this)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = @this.Id,
|
||||
Email = @this.Email,
|
||||
Type = @this.Type,
|
||||
Username = @this.Username,
|
||||
};
|
||||
}
|
||||
|
||||
public static UserProfile ToClient(this UserProfileDb @this)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = @this.Id,
|
||||
Name = @this.Name,
|
||||
};
|
||||
}
|
||||
|
||||
public static MemberEntry ToClient(this MemberEntryDb @this)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = @this.Id,
|
||||
UserProfileId = @this.User!.Id,
|
||||
Level = @this.Level,
|
||||
};
|
||||
}
|
||||
}
|
||||
47
OwnChar/Extensions/OwnCharApiServerExtensions.cs
Normal file
47
OwnChar/Extensions/OwnCharApiServerExtensions.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using OwnChar.Api;
|
||||
using OwnChar.Data;
|
||||
using OwnChar.Data.Model.Base;
|
||||
using OwnChar.Data.Model.Server;
|
||||
using Pilz.Net.Api;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace OwnChar.Server.Extensions;
|
||||
|
||||
public static class OwnCharApiServerExtensions
|
||||
{
|
||||
public static bool CheckLogin(this IOwnCharApiServer server, ApiRequestInfo request, UserType userType)
|
||||
{
|
||||
if (server.Data is null
|
||||
|| !request.IsAuthenticated
|
||||
|| request.AuthKey.Split(":") is not string[] authKey
|
||||
|| authKey.ElementAtOrDefault(0) is not string username
|
||||
|| authKey.ElementAtOrDefault(1) is not string secret
|
||||
|| string.IsNullOrWhiteSpace(username)
|
||||
|| string.IsNullOrWhiteSpace(secret)
|
||||
|| !server.IsLoggedIn(secret)
|
||||
|| server.GetUser(secret) is not UserAccountBase usr
|
||||
|| usr.Type < userType)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool CheckLogin(this IOwnCharApiServer server, ApiRequestInfo request, UserType userType, [NotNullWhen(true)] out UserAccountDb? user)
|
||||
{
|
||||
if (server.Data is null
|
||||
|| !request.IsAuthenticated
|
||||
|| request.AuthKey.Split(":") is not string[] authKey
|
||||
|| authKey.ElementAtOrDefault(0) is not string username
|
||||
|| authKey.ElementAtOrDefault(1) is not string secret
|
||||
|| string.IsNullOrWhiteSpace(username)
|
||||
|| string.IsNullOrWhiteSpace(secret)
|
||||
|| !server.IsLoggedIn(secret)
|
||||
|| server.GetUser(secret) is not UserAccountDb usr
|
||||
|| usr.Type < userType)
|
||||
{
|
||||
user = null;
|
||||
return false;
|
||||
}
|
||||
user = usr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -8,17 +8,16 @@ namespace OwnChar;
|
||||
public interface IOwnCharManager
|
||||
{
|
||||
bool IsLoggedIn { get; }
|
||||
OwnCharApiClient? Api { get; }
|
||||
|
||||
IOwnCharApiClient? Api { get; }
|
||||
|
||||
IUserManager Users { get; }
|
||||
|
||||
IGroupsManager Groups { get; }
|
||||
|
||||
ICharacterManager Characters { get; }
|
||||
|
||||
Task<UserProfile?> Login(string? username, SecureString? password);
|
||||
Task<bool> Logout();
|
||||
|
||||
public static IOwnCharManager CreateDefault()
|
||||
{
|
||||
return new OwnCharManager();
|
||||
}
|
||||
Task<bool> Logout();
|
||||
}
|
||||
@@ -7,11 +7,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.7" />
|
||||
<PackageReference Include="Pilz.Cryptography" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
|
||||
<PackageReference Include="Pilz.Cryptography" Version="2.1.1" />
|
||||
<PackageReference Include="Pilz.Net" Version="2.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -7,17 +7,17 @@ using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace OwnChar;
|
||||
|
||||
internal class OwnCharManager : IOwnCharManager
|
||||
public class OwnCharManager : IOwnCharManager
|
||||
{
|
||||
// User
|
||||
[MemberNotNullWhen(true, nameof(Api))]
|
||||
public bool IsLoggedIn => Api is not null && Api.IsLoggedIn;
|
||||
|
||||
// Data Provider
|
||||
public OwnCharApiClient? Api { get; private set; }
|
||||
public IOwnCharApiClient? Api { get; private set; }
|
||||
|
||||
// Manager
|
||||
public IUserManager Users { get; }
|
||||
|
||||
public IGroupsManager Groups { get; }
|
||||
|
||||
public ICharacterManager Characters { get; }
|
||||
|
||||
public OwnCharManager()
|
||||
|
||||
Reference in New Issue
Block a user