begin new and last rework

This commit is contained in:
2024-07-18 14:44:29 +02:00
parent abfc997ac1
commit 1a976fc9ef
26 changed files with 444 additions and 101 deletions

View File

@@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OwnChar.Server", "OwnChar.S
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OwnChar", "OwnChar\OwnChar\OwnChar.csproj", "{BAD88A97-650D-493B-BAC3-3510B6F354B6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OwnChar", "OwnChar\OwnChar\OwnChar.csproj", "{BAD88A97-650D-493B-BAC3-3510B6F354B6}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OwnChar.Plugins", "OwnChar.Plugins\OwnChar.Plugins\OwnChar.Plugins.csproj", "{45BB6992-127F-4C4A-A7E3-B32F414784DE}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
{BAD88A97-650D-493B-BAC3-3510B6F354B6}.Debug|Any CPU.Build.0 = Debug|Any CPU {BAD88A97-650D-493B-BAC3-3510B6F354B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAD88A97-650D-493B-BAC3-3510B6F354B6}.Release|Any CPU.ActiveCfg = Release|Any CPU {BAD88A97-650D-493B-BAC3-3510B6F354B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAD88A97-650D-493B-BAC3-3510B6F354B6}.Release|Any CPU.Build.0 = Release|Any CPU {BAD88A97-650D-493B-BAC3-3510B6F354B6}.Release|Any CPU.Build.0 = Release|Any CPU
{45BB6992-127F-4C4A-A7E3-B32F414784DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{45BB6992-127F-4C4A-A7E3-B32F414784DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{45BB6992-127F-4C4A-A7E3-B32F414784DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45BB6992-127F-4C4A-A7E3-B32F414784DE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -0,0 +1,25 @@
namespace OwnChar.ServerNew.Api.Endpoint;
internal class ApiBuilder(WebApplication app) : IApiBuilder
{
public void Map(string method, 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();
}
public void Map(string pattern, Delegate action)
{
app.Map(pattern, action);
}
}

View File

@@ -0,0 +1,12 @@
using System.Net;
namespace OwnChar.ServerNew.Api.Endpoint;
public static class ApiRequestMethods
{
public static string Get => WebRequestMethods.Http.Get;
public static string Put => WebRequestMethods.Http.Put;
public static string Post => WebRequestMethods.Http.Post;
public static string Patch => "PATCH";
public static string Delete => "DELETE";
}

View File

@@ -0,0 +1,6 @@
namespace OwnChar.ServerNew.Api.Endpoint;
public interface IApiBuilder
{
void Map(string path, Delegate action);
}

View File

@@ -0,0 +1,6 @@
namespace OwnChar.ServerNew.Api.Endpoint;
internal interface IApiEndpoint
{
void Initialize(IApiBuilder builder);
}

View File

@@ -0,0 +1,34 @@
namespace OwnChar.ServerNew.Api.Endpoint.Implementations;
internal class CharactersApi(IServer server) : IApiEndpoint
{
public void Initialize(IApiBuilder builder)
{
throw new NotImplementedException();
}
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)
{
}
}

View File

@@ -0,0 +1,48 @@
namespace OwnChar.ServerNew.Api.Endpoint.Implementations;
internal class GroupsApi(IServer server) : IApiEndpoint
{
public void Initialize(IApiBuilder builder)
{
}
private IResult GetGroups()
{
}
private IResult GetGroups(long characterId)
{
}
private IResult GetGroup(long groupId)
{
}
private IResult CreateGroup(string name)
{
}
private IResult UpdateGroup(int groupId, string name)
{
}
private IResult DeleteGroup(int groupId)
{
}
private IResult AddMember(int groupId, long memberId)
{
}
private IResult RemoveMember(int groupId, long memberId)
{
}
}

View File

@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Mvc;
using OwnChar.Api.Responses;
using OwnChar.Model;
namespace OwnChar.ServerNew.Api.Endpoint.Implementations;
internal class LoginApi(ServerContext server) : IApiEndpoint
{
public void Initialize(IApiBuilder builder)
{
builder.Map("/auth/login/{username}", Login);
builder.Map("/auth/logout/{secret}", Logout);
}
private IResult Login(string username, [FromHeader(Name = "X-USER-PASSWORD")] string password)
{
if (server.Data != null && server.Data.GetAll<UserAccount>()?.FirstOrDefault(n => n.Username == username && n.Password == password) is UserAccount account)
return TypedResults.Ok(new LoginResponse
{
Secret = server.Login(account),
UserAccount = account,
});
return TypedResults.Unauthorized();
}
private IResult Logout([FromHeader(Name = "X-AUTH-SECRET")] string secret)
{
server.Logout(secret);
return TypedResults.Ok();
}
}

View File

@@ -0,0 +1,44 @@
namespace OwnChar.ServerNew.Api.Endpoint.Implementations;
internal class UsersApi(IServer server) : IApiEndpoint
{
public void Initialize(IApiBuilder builder)
{
throw new NotImplementedException();
}
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)
{
}
}

View File

@@ -0,0 +1,21 @@
using OwnChar.Data;
using OwnChar.Model;
using Pilz.Configuration;
using System.Diagnostics.CodeAnalysis;
namespace OwnChar.ServerNew.Api;
public interface IServer
{
ISettings Settings { get; }
IDataProvider? Data { get; }
[MemberNotNull(nameof(Data))]
void CheckLogin(string secret);
[MemberNotNullWhen(true, nameof(Data))]
bool IsLoggedIn(string secret);
[MemberNotNullWhen(true, nameof(Data))]
UserAccount? GetUser(string secret);
}

View File

@@ -0,0 +1,11 @@
using OwnChar.ServerNew.Api.Endpoint;
using Pilz.Plugins.Advanced;
namespace OwnChar.ServerNew.Api.Plugins;
public abstract class ApiEndpointFeature(string identifier) : PluginFeature(FeatureType, identifier), IApiEndpoint
{
public static string FeatureType => "ownchar.server.apiep";
public abstract void Initialize(IApiBuilder builder);
}

View File

@@ -0,0 +1,5 @@
namespace OwnChar.ServerNew.Api.Plugins;
public interface IPluginLoadContextServer
{
}

View File

@@ -0,0 +1,8 @@
using OwnChar.Plugins;
namespace OwnChar.ServerNew.Api.Plugins;
public class OwnCharServerPluginInitParams(IServer server) : OwnCharPluginInitParams
{
public IServer Server { get; } = server;
}

View File

@@ -1,17 +0,0 @@
using Pilz.Plugins.Advanced;
namespace OwnChar.Server.Commands
{
public class CmdSave() : PluginFunction(IServerCommand.FeatureCode, "ownchar.save"), IPluginFeatureProvider<CmdSave>, IServerCommand
{
public static CmdSave Instance { get; } = new();
public string Command => "save";
public string Description => "Saves the current state to disk.";
protected override object? ExecuteFunction(PluginFunctionParameter? @params)
{
return this;
}
}
}

View File

@@ -1,15 +0,0 @@
using Pilz.Plugins.Advanced;
namespace OwnChar.Server.Commands
{
public interface IServerCommand
{
// Shared
public const string FeatureCode = "ownchar.server.command";
public IEnumerable<IServerCommand> Commands => PluginFeatureController.Instance.Features.Get(FeatureCode).Cast<PluginFunction>().Select(f => (IServerCommand)f.Execute()!);
// Interface
public string Command { get; }
public string Description { get; }
}
}

View File

@@ -1,17 +0,0 @@
using Pilz.Plugins.Advanced;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OwnChar.Server.Data
{
public class ClientServerDataProvider() : PluginFunction(IServerDataProvider.FeatureCode, "ownchar.clientserver"), IServerDataProvider
{
protected override object? ExecuteFunction(PluginFunctionParameter? @params)
{
return this;
}
}
}

View File

@@ -1,19 +0,0 @@
using Pilz.Plugins.Advanced;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OwnChar.Server.Data
{
public interface IServerDataProvider
{
// Shared
public const string FeatureCode = "ownchar.server.dataprovider";
public IEnumerable<IServerDataProvider> DataProviders => PluginFeatureController.Instance.Features.Get(FeatureCode).Cast<PluginFunction>().Select(f => (IServerDataProvider)f.Execute()!);
// Interface
// ...
}
}

View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OwnChar.Server.Network
{
public class NetworkHandler
{
}
}

View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OwnChar.Server.Network
{
public class NetworkManager
{
}
}

View File

@@ -1,15 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Pilz.Plugins" Version="2.1.5" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.7" />
<PackageReference Include="Pilz.Plugins.Advanced" Version="2.7.3" /> <PackageReference Include="Pilz.Configuration" Version="3.1.2" />
<PackageReference Include="Pilz.Cryptography" Version="2.0.1" />
<PackageReference Include="Pilz.Plugins" Version="2.1.9" />
<PackageReference Include="Pilz.Plugins.Advanced" Version="2.10.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OwnChar.Plugins\OwnChar.Plugins\OwnChar.Plugins.csproj" />
<ProjectReference Include="..\OwnChar\OwnChar\OwnChar.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,6 @@
@OwnChar.ServerNew_HostAddress = http://localhost:5208
GET {{OwnChar.ServerNew_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -1,10 +1,69 @@
namespace OwnChar.Server using OwnChar.Data;
using OwnChar.Plugins;
using OwnChar.ServerNew.Api.Endpoint;
using OwnChar.ServerNew.Api.Endpoint.Implementations;
using OwnChar.ServerNew.Api.Plugins;
using Pilz.Configuration;
using Pilz.Plugins;
using Pilz.Plugins.Advanced;
namespace OwnChar.ServerNew;
internal class Program
{ {
internal class Program public static string? AppTempFolder { get; private set; }
public static ISettingsManager? SettingsManager { get; private set; }
public static void Main(string[] args)
{ {
static void Main(string[] args) // Load settings
AppTempFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "OwnChar", "Server");
Directory.CreateDirectory(AppTempFolder);
SettingsManager = new SettingsManager(Path.Combine(AppTempFolder, "Settings.json"), true);
// Create server context
var server = new ServerContext(SettingsManager.Instance);
// Load plugins
var pluginPath = Path.Combine(AppTempFolder, "Plugins");
Directory.CreateDirectory(pluginPath);
var pluginPaths = Directory.GetDirectories(pluginPath, "*", SearchOption.TopDirectoryOnly).Select(n => Path.Combine(n, n + ".dll")).ToArray();
OwnCharPlugins.Instance.LoadPlugins(pluginPaths, new OwnCharServerPluginInitParams(server));
// Add services to the container.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Build app
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{ {
Console.WriteLine("Hello, World!"); app.UseSwagger();
app.UseSwaggerUI();
} }
app.UseHttpsRedirection();
app.UseAuthorization();
// Built-in endpoints
var apibuilder = new ApiBuilder(app);
new LoginApi(server).Initialize(apibuilder);
new UsersApi(server).Initialize(apibuilder);
new GroupsApi(server).Initialize(apibuilder);
new CharactersApi(server).Initialize(apibuilder);
// Plugin endpoints
var endpoints = PluginFeatureController.Instance.Features.Get(ApiEndpointFeature.FeatureType).OfType<IApiEndpoint>();
if (endpoints.Any())
{
foreach (var endpoint in endpoints)
endpoint.Initialize(apibuilder);
}
// Run server
app.Run();
} }
} }

View File

@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:48428",
"sslPort": 44302
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5208",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7174;http://localhost:5208",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,47 @@
using OwnChar.Data;
using OwnChar.Model;
using OwnChar.ServerNew.Api;
using OwnChar.ServerNew.Api.Plugins;
using Pilz.Configuration;
using Pilz.Cryptography;
namespace OwnChar.ServerNew;
internal class ServerContext(ISettings settings) : IServer, IPluginLoadContextServer
{
private readonly Dictionary<string, UserAccount> users = [];
public IDataProvider? Data { get; private set; }
public ISettings Settings { get; } = settings;
public string Login(UserAccount account)
{
var secret = new UniquieID(UniquieIDGenerationMode.GenerateOnInit).ID;
users.Add(secret, account);
return secret;
}
public void Logout(string secret)
{
users.Remove(secret);
}
public bool IsLoggedIn(string secret)
{
return users.ContainsKey(secret);
}
public void CheckLogin(string secret)
{
if (!IsLoggedIn(secret))
throw new UnauthorizedAccessException();
}
public UserAccount? GetUser(string secret)
{
if (users.TryGetValue(secret, out UserAccount? value))
return value;
return null;
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}