diff --git a/OwnChar.Server/Api/Endpoint/ApiBuilder.cs b/OwnChar.Server/Api/Endpoint/ApiBuilder.cs index bf8cbfa..45abb20 100644 --- a/OwnChar.Server/Api/Endpoint/ApiBuilder.cs +++ b/OwnChar.Server/Api/Endpoint/ApiBuilder.cs @@ -1,25 +1,17 @@ -namespace OwnChar.ServerNew.Api.Endpoint; +using OwnChar.Api.Packets; + +namespace OwnChar.Server.Api.Endpoint; internal class ApiBuilder(WebApplication app) : IApiBuilder { - public void Map(string method, string pattern, Delegate action) + public void MapRequest(string pattern, Delegate action) { - if (method == ApiRequestMethods.Get) - app.MapGet(pattern, action); - else if (method == ApiRequestMethods.Post) - app.MapPost(pattern, action); - else if (method == ApiRequestMethods.Put) - app.MapPut(pattern, action); - else if (method == ApiRequestMethods.Patch) - app.MapPatch(pattern, action); - else if (method == ApiRequestMethods.Delete) - app.MapDelete(pattern, action); - else - throw new NotSupportedException(); + Map(pattern + "/{request}", action); } public void Map(string pattern, Delegate action) { - app.Map(pattern, action); + //app.Map(pattern, action); + app.MapPost(pattern, action); } } diff --git a/OwnChar.Server/Api/Endpoint/ApiRequestMethods.cs b/OwnChar.Server/Api/Endpoint/ApiRequestMethods.cs index 1166eb2..828e010 100644 --- a/OwnChar.Server/Api/Endpoint/ApiRequestMethods.cs +++ b/OwnChar.Server/Api/Endpoint/ApiRequestMethods.cs @@ -1,6 +1,6 @@ using System.Net; -namespace OwnChar.ServerNew.Api.Endpoint; +namespace OwnChar.Server.Api.Endpoint; public static class ApiRequestMethods { diff --git a/OwnChar.Server/Api/Endpoint/IApiBuilder.cs b/OwnChar.Server/Api/Endpoint/IApiBuilder.cs index b35f5fd..ec6c063 100644 --- a/OwnChar.Server/Api/Endpoint/IApiBuilder.cs +++ b/OwnChar.Server/Api/Endpoint/IApiBuilder.cs @@ -1,6 +1,9 @@ -namespace OwnChar.ServerNew.Api.Endpoint; +using OwnChar.Api.Packets; + +namespace OwnChar.Server.Api.Endpoint; public interface IApiBuilder { + void MapRequest(string pattern, Delegate action); void Map(string path, Delegate action); } diff --git a/OwnChar.Server/Api/Endpoint/IApiEndpoint.cs b/OwnChar.Server/Api/Endpoint/IApiEndpoint.cs index 50ea90b..5bcf7cd 100644 --- a/OwnChar.Server/Api/Endpoint/IApiEndpoint.cs +++ b/OwnChar.Server/Api/Endpoint/IApiEndpoint.cs @@ -1,4 +1,4 @@ -namespace OwnChar.ServerNew.Api.Endpoint; +namespace OwnChar.Server.Api.Endpoint; internal interface IApiEndpoint { diff --git a/OwnChar.Server/Api/Endpoint/Implementations/CharactersApi.cs b/OwnChar.Server/Api/Endpoint/Implementations/CharactersApi.cs index dd4ba0d..ab0f1a4 100644 --- a/OwnChar.Server/Api/Endpoint/Implementations/CharactersApi.cs +++ b/OwnChar.Server/Api/Endpoint/Implementations/CharactersApi.cs @@ -1,4 +1,7 @@ -namespace OwnChar.ServerNew.Api.Endpoint.Implementations; +using OwnChar.Server.Api; +using OwnChar.Server.Api.Endpoint; + +namespace OwnChar.Server.Api.Endpoint.Implementations; internal class CharactersApi(IServer server) : IApiEndpoint { diff --git a/OwnChar.Server/Api/Endpoint/Implementations/GroupsApi.cs b/OwnChar.Server/Api/Endpoint/Implementations/GroupsApi.cs index 3bc7501..b68266a 100644 --- a/OwnChar.Server/Api/Endpoint/Implementations/GroupsApi.cs +++ b/OwnChar.Server/Api/Endpoint/Implementations/GroupsApi.cs @@ -1,48 +1,188 @@ -namespace OwnChar.ServerNew.Api.Endpoint.Implementations; +using OwnChar.Api.Packets; +using OwnChar.Api.Packets.General; +using OwnChar.Api.Packets.Groups; +using OwnChar.Api.Updates; +using OwnChar.Data; +using OwnChar.Data.Model.Client; +using OwnChar.Server.Data.Model; +using OwnChar.Server.Extensions; + +namespace OwnChar.Server.Api.Endpoint.Implementations; internal class GroupsApi(IServer server) : IApiEndpoint { public void Initialize(IApiBuilder builder) { + builder.MapRequest("/groups/get/byid", GetById); + builder.MapRequest("/groups/get", Get); + builder.MapRequest("/groups/create", Create); + builder.MapRequest("/groups/update", Update); + builder.MapRequest("/groups/delete", Delete); + builder.MapRequest("/groups/members/get", GetMembers); + builder.MapRequest("/groups/members/add", AddMembers); + builder.MapRequest("/groups/members/update", UpdateMember); + builder.MapRequest("/groups/members/remove", RemoveMembers); } - private IResult GetGroups() + private IResult GetById(GetSinlgeObjectRequest request) { + if (!server.CheckLogin(request, UserType.Guest, out UserAccountDb? user)) + return TypedResults.Unauthorized(); + if (server.Data?.Set().FirstOrDefault(n => n.Id == request.ObjectId && n.Members.Any(m => m.User != null && m.User.Id == user.Id)) is not GroupDb group) + return TypedResults.Ok(new GetSingleObjectResponse(null).With(OwnCharResponseError.NotFound)); + + return TypedResults.Ok(new GetSingleObjectResponse(group.ToClient())); } - private IResult GetGroups(long characterId) + private IResult Get(GetGroupsRequest request) { + if (!server.CheckLogin(request, UserType.Guest, out UserAccountDb? user)) + return TypedResults.Unauthorized(); + IQueryable groups; + + if (request.UseProfileId && server.Data!.Set().FirstOrDefault(p => p.Id == request.ProfileId) is UserProfileDb profile) + groups = server.Data!.Set().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(); + else + groups = Array.Empty().AsQueryable(); // Currently not supported. + + return TypedResults.Ok(new GetGroupsResponse([.. groups.Select(g => g.ToClient())])); } - private IResult GetGroup(long groupId) + private IResult Create(CreateGroupRequest request) { + if (!server.CheckLogin(request, UserType.User, out UserAccountDb? user)) + return TypedResults.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 TypedResults.Ok(new CreateGroupResponse(group.ToClient())); } - private IResult CreateGroup(string name) + private IResult Update(UpdateRequest request) { + if (!server.CheckLogin(request, UserType.User, out UserAccountDb? user) + || server.Data?.Set().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 TypedResults.Unauthorized(); + if (request.Update is not GroupUpdate update) + return TypedResults.Ok(new UpdateResponse().With(OwnCharResponseError.Default)); + + group.Name = update.Name; + group.Fandom = update.Fandom; + + server.Data.Update(group); + server.Data.SaveChanges(); + + return TypedResults.Ok(new UpdateResponse()); } - private IResult UpdateGroup(int groupId, string name) + private IResult Delete(DeleteObjectRequest request) { + if (!server.CheckLogin(request, UserType.User, out UserAccountDb? user) + || server.Data?.Set().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 TypedResults.Unauthorized(); + server.Data.Remove(group); + server.Data.SaveChanges(); + + return TypedResults.Ok(new DeleteObjectResponse()); } - private IResult DeleteGroup(int groupId) + private IResult GetMembers(GetGroupMembersRequest request) { + if (!server.CheckLogin(request, UserType.User, out UserAccountDb? user) + || server.Data?.Set().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 TypedResults.Unauthorized(); + var members = group.Members.Select(n => n.ToClient()); + return TypedResults.Ok(new GetGroupMembersResponse(members.ToList())); } - private IResult AddMember(int groupId, long memberId) + private IResult AddMembers(GroupMemberAddRequest request) { + if (!server.CheckLogin(request, UserType.User, out UserAccountDb? user) + || server.Data?.Set().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 TypedResults.Unauthorized(); + var addedMembers = new List(); + + 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().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 TypedResults.Ok(new GroupMemberAddResponse(addedMembers.Select(m => m.ToClient()).ToList())); } - private IResult RemoveMember(int groupId, long memberId) + private IResult UpdateMember(UpdateRequest request) { + if (!server.CheckLogin(request, UserType.User, out UserAccountDb? user) + || server.Data?.Set().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 TypedResults.Unauthorized(); + if (request.Update is not MemberUpdate update) + return TypedResults.Ok(new UpdateResponse().With(OwnCharResponseError.Default)); + + member.Level = update.Level; + + server.Data.Update(member); + server.Data.SaveChanges(); + + return TypedResults.Ok(new UpdateResponse()); + } + + private IResult RemoveMembers(DeleteObjectRequest request) + { + if (!server.CheckLogin(request, UserType.User, out UserAccountDb? user) + || server.Data?.Set().FirstOrDefault(m => m.Id == request.ObjectId) is not MemberEntryDb member + || server.Data?.Set().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 TypedResults.Unauthorized(); + + group.Members.Remove(member); + server.Data.Remove(member); + server.Data.Update(group); + server.Data.SaveChanges(); + + return TypedResults.Ok(new DeleteObjectResponse()); } } diff --git a/OwnChar.Server/Api/Endpoint/Implementations/LoginApi.cs b/OwnChar.Server/Api/Endpoint/Implementations/LoginApi.cs index 06b0521..5f628df 100644 --- a/OwnChar.Server/Api/Endpoint/Implementations/LoginApi.cs +++ b/OwnChar.Server/Api/Endpoint/Implementations/LoginApi.cs @@ -1,8 +1,9 @@ -using OwnChar.Base.Data.Requests; -using OwnChar.Base.Data.Responses; +using OwnChar.Api.Packets; +using OwnChar.Api.Packets.General; using OwnChar.Server.Data.Model; +using OwnChar.Server.Extensions; -namespace OwnChar.ServerNew.Api.Endpoint.Implementations; +namespace OwnChar.Server.Api.Endpoint.Implementations; internal class LoginApi(ServerContext server) : IApiEndpoint { @@ -20,13 +21,13 @@ internal class LoginApi(ServerContext server) : IApiEndpoint && server.Data.Set()?.FirstOrDefault(n => n.Username == request.Username && n.Password == request.Password) is UserAccountDb acc && acc.Profile != null) { - result = new(new(acc), new(acc.Profile), server.Login(acc)); + result = new(acc.ToClient(), acc.Profile.ToClient(), server.Login(acc)); } else { result = new(null, null, null) { - ErrorCode = Base.Data.OwnCharResponseError.NotAuthorized + ErrorCode = OwnCharResponseError.NotFound, }; } @@ -36,6 +37,6 @@ internal class LoginApi(ServerContext server) : IApiEndpoint private IResult Logout(LogoutRequest request) { server.Logout(request.AuthSecret); - return TypedResults.Ok(); + return TypedResults.Ok(new LogoutResponse()); } } diff --git a/OwnChar.Server/Api/Endpoint/Implementations/UsersApi.cs b/OwnChar.Server/Api/Endpoint/Implementations/UsersApi.cs index ad46959..fbc4cab 100644 --- a/OwnChar.Server/Api/Endpoint/Implementations/UsersApi.cs +++ b/OwnChar.Server/Api/Endpoint/Implementations/UsersApi.cs @@ -1,4 +1,6 @@ -namespace OwnChar.ServerNew.Api.Endpoint.Implementations; +using OwnChar.Server.Api; + +namespace OwnChar.Server.Api.Endpoint.Implementations; internal class UsersApi(IServer server) : IApiEndpoint { diff --git a/OwnChar.Server/Api/IServer.cs b/OwnChar.Server/Api/IServer.cs index 08e25c9..b12711e 100644 --- a/OwnChar.Server/Api/IServer.cs +++ b/OwnChar.Server/Api/IServer.cs @@ -1,10 +1,10 @@ using Microsoft.EntityFrameworkCore; -using OwnChar.Base.Data.Model; +using OwnChar.Data.Model.Base; using Pilz.Configuration; using System.Diagnostics.CodeAnalysis; using ILogger = Castle.Core.Logging.ILogger; -namespace OwnChar.ServerNew.Api; +namespace OwnChar.Server.Api; public interface IServer { diff --git a/OwnChar.Server/Api/Plugins/ApiEndpointFeature.cs b/OwnChar.Server/Api/Plugins/ApiEndpointFeature.cs index e10257e..aa71c44 100644 --- a/OwnChar.Server/Api/Plugins/ApiEndpointFeature.cs +++ b/OwnChar.Server/Api/Plugins/ApiEndpointFeature.cs @@ -1,7 +1,7 @@ -using OwnChar.ServerNew.Api.Endpoint; +using OwnChar.Server.Api.Endpoint; using Pilz.Plugins.Advanced; -namespace OwnChar.ServerNew.Api.Plugins; +namespace OwnChar.Server.Api.Plugins; public abstract class ApiEndpointFeature(string identifier) : PluginFeature(FeatureType, identifier), IApiEndpoint { diff --git a/OwnChar.Server/Api/Plugins/OwnCharServerPluginInitParams.cs b/OwnChar.Server/Api/Plugins/OwnCharServerPluginInitParams.cs index 8943d19..72df5ad 100644 --- a/OwnChar.Server/Api/Plugins/OwnCharServerPluginInitParams.cs +++ b/OwnChar.Server/Api/Plugins/OwnCharServerPluginInitParams.cs @@ -1,6 +1,7 @@ using OwnChar.Plugins; +using OwnChar.Server.Api; -namespace OwnChar.ServerNew.Api.Plugins; +namespace OwnChar.Server.Api.Plugins; public class OwnCharServerPluginInitParams(IServer server) : OwnCharPluginInitParams { diff --git a/OwnChar.Server/Data/DatabaseContext.cs b/OwnChar.Server/Data/DatabaseContext.cs new file mode 100644 index 0000000..84f9273 --- /dev/null +++ b/OwnChar.Server/Data/DatabaseContext.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; + +namespace OwnChar.Server.Data; + +public class DatabaseContext(string? dbHost, string? dbUser, string? dbPassword) : DbContext +{ + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + } +} diff --git a/OwnChar.Server/Data/Model/CharacterDb.cs b/OwnChar.Server/Data/Model/CharacterDb.cs new file mode 100644 index 0000000..40ee166 --- /dev/null +++ b/OwnChar.Server/Data/Model/CharacterDb.cs @@ -0,0 +1,11 @@ +using OwnChar.Client.Data.Model; +using OwnChar.Data.Model.Base; + +namespace OwnChar.Server.Data.Model; + +public class CharacterDb : CharacterBase +{ + public UserProfileDb? Owner { get; set; } + public List PropCats { get; } = []; + public List Props { get; } = []; +} diff --git a/OwnChar.Server/Data/Model/GroupDb.cs b/OwnChar.Server/Data/Model/GroupDb.cs new file mode 100644 index 0000000..bfa84e4 --- /dev/null +++ b/OwnChar.Server/Data/Model/GroupDb.cs @@ -0,0 +1,10 @@ +using OwnChar.Api.Updates; +using OwnChar.Data.Model.Base; + +namespace OwnChar.Server.Data.Model; + +public class GroupDb : GroupBase +{ + public List Characters { get; } = []; + public List Members { get; } = []; +} diff --git a/OwnChar.Server/Data/Model/MemberEntryDb.cs b/OwnChar.Server/Data/Model/MemberEntryDb.cs new file mode 100644 index 0000000..41850af --- /dev/null +++ b/OwnChar.Server/Data/Model/MemberEntryDb.cs @@ -0,0 +1,8 @@ +using OwnChar.Data.Model.Base; + +namespace OwnChar.Server.Data.Model; + +public class MemberEntryDb() : MemberEntryBase +{ + public virtual UserProfileDb? User { get; set; } +} diff --git a/OwnChar.Server/Data/Model/PropertyCategoryDb.cs b/OwnChar.Server/Data/Model/PropertyCategoryDb.cs new file mode 100644 index 0000000..05f2ea4 --- /dev/null +++ b/OwnChar.Server/Data/Model/PropertyCategoryDb.cs @@ -0,0 +1,7 @@ +using OwnChar.Data.Model.Base; + +namespace OwnChar.Server.Data.Model; + +public class PropertyCategoryDb : PropertyCategoryBase +{ +} diff --git a/OwnChar.Server/Data/Model/PropertyDb.cs b/OwnChar.Server/Data/Model/PropertyDb.cs new file mode 100644 index 0000000..2c93c72 --- /dev/null +++ b/OwnChar.Server/Data/Model/PropertyDb.cs @@ -0,0 +1,7 @@ +using OwnChar.Data.Model.Base; + +namespace OwnChar.Server.Data.Model; + +public class PropertyDb : PropertyBase +{ +} diff --git a/OwnChar.Server/Data/Model/UserAccountDb.cs b/OwnChar.Server/Data/Model/UserAccountDb.cs new file mode 100644 index 0000000..7f27a74 --- /dev/null +++ b/OwnChar.Server/Data/Model/UserAccountDb.cs @@ -0,0 +1,9 @@ +using OwnChar.Data.Model.Base; + +namespace OwnChar.Server.Data.Model; + +public class UserAccountDb : UserAccountBase +{ + public virtual string? Password { get; set; } + public UserProfileDb? Profile { get; set; } +} diff --git a/OwnChar.Server/Data/Model/UserProfileDb.cs b/OwnChar.Server/Data/Model/UserProfileDb.cs new file mode 100644 index 0000000..be4d741 --- /dev/null +++ b/OwnChar.Server/Data/Model/UserProfileDb.cs @@ -0,0 +1,8 @@ +using OwnChar.Data.Model.Base; + +namespace OwnChar.Server.Data.Model; + +public class UserProfileDb : UserProfileBase +{ + public List Characters { get; } = []; +} diff --git a/OwnChar.Server/Extensions/GeneralExtensions.cs b/OwnChar.Server/Extensions/GeneralExtensions.cs new file mode 100644 index 0000000..4aabbe7 --- /dev/null +++ b/OwnChar.Server/Extensions/GeneralExtensions.cs @@ -0,0 +1,39 @@ +using OwnChar.Api.Packets; +using OwnChar.Data; +using OwnChar.Data.Model.Base; +using OwnChar.Server.Api; +using OwnChar.Server.Data.Model; +using System.Diagnostics.CodeAnalysis; + +namespace OwnChar.Server.Extensions; + +public static class GeneralExtensions +{ + public static bool CheckLogin(this IServer server, OwnCharRequest request, UserType userType) + { + if (server.Data is null + || string.IsNullOrWhiteSpace(request.Username) + || string.IsNullOrWhiteSpace(request.AuthSecret) + || !server.IsLoggedIn(request.AuthSecret) + || server.GetUser(request.AuthSecret) is not UserAccountBase usr + || usr.Type < userType) + return false; + return true; + } + + public static bool CheckLogin(this IServer server, OwnCharRequest request, UserType userType, [NotNullWhen(true)] out UserAccountDb? user) + { + if (server.Data is null + || string.IsNullOrWhiteSpace(request.Username) + || string.IsNullOrWhiteSpace(request.AuthSecret) + || !server.IsLoggedIn(request.AuthSecret) + || server.GetUser(request.AuthSecret) is not UserAccountDb usr + || usr.Type < userType) + { + user = null; + return false; + } + user = usr; + return true; + } +} diff --git a/OwnChar.Server/Extensions/ModelExtensions.cs b/OwnChar.Server/Extensions/ModelExtensions.cs new file mode 100644 index 0000000..0ccbaf3 --- /dev/null +++ b/OwnChar.Server/Extensions/ModelExtensions.cs @@ -0,0 +1,48 @@ +using OwnChar.Data.Model.Client; +using OwnChar.Server.Data.Model; + +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, + }; + } +} diff --git a/OwnChar.Server/Program.cs b/OwnChar.Server/Program.cs index cfd5ecf..6bbad8d 100644 --- a/OwnChar.Server/Program.cs +++ b/OwnChar.Server/Program.cs @@ -2,7 +2,7 @@ using OwnChar.Plugins; using OwnChar.ServerNew.Api.Plugins; using Pilz.Configuration; -namespace OwnChar.ServerNew; +namespace OwnChar.Server; internal class Program { diff --git a/OwnChar.Server/ServerContext.cs b/OwnChar.Server/ServerContext.cs index 48d7726..2c0bca9 100644 --- a/OwnChar.Server/ServerContext.cs +++ b/OwnChar.Server/ServerContext.cs @@ -1,20 +1,20 @@ using Castle.Core.Logging; using Microsoft.EntityFrameworkCore; -using OwnChar.Base.Data.Model; using OwnChar.Data; +using OwnChar.Data.Model.Base; using OwnChar.Model; -using OwnChar.Server; -using OwnChar.ServerNew.Api.Endpoint; +using OwnChar.Server.Api; +using OwnChar.Server.Api.Endpoint; +using OwnChar.Server.Api.Plugins; using OwnChar.ServerNew.Api.Endpoint.Implementations; -using OwnChar.ServerNew.Api.Plugins; using Pilz.Configuration; using Pilz.Cryptography; using Pilz.Plugins.Advanced; using ILogger = Castle.Core.Logging.ILogger; -namespace OwnChar.ServerNew; +namespace OwnChar.Server; -internal class ServerContext(ISettings settings) : Api.IServer +internal class ServerContext(ISettings settings) : IServer { private readonly Dictionary users = [];