using Castle.Core.Logging; using Microsoft.EntityFrameworkCore; using OwnChar.Data.Model.Base; using OwnChar.Server.Api; using OwnChar.Server.Api.Endpoint; using OwnChar.Server.Api.Endpoint.Implementations; using OwnChar.Server.Api.Plugins; using OwnChar.Server.Data; using Pilz.Configuration; using Pilz.Cryptography; using Pilz.Plugins.Advanced; using System.Net; namespace OwnChar.Server; internal class ServerContext(ISettings settings) : IServer { private readonly Dictionary users = []; private readonly HttpListener httpListener = new(); private readonly ApiBuilder apiBuilder = new(); public DbContext? Data { get; private set; } public ISettings Settings { get; } = settings; public ILogger Log { get; set; } = NullLogger.Instance; public void Start(string[] args) { Log.Info("Prepairing server"); // Load database Log.Debug("Loading database"); var settings = Settings.Get(); Data = new DatabaseContext(settings.DbServer, settings.DbUser, settings.DbPassword); // Built-in endpoints Log.Debug("Loading internal api endpoints"); var apibuilder = new ApiBuilder(); new LoginApi(this).Initialize(apibuilder); new UsersApi(this).Initialize(apibuilder); new GroupsApi(this).Initialize(apibuilder); new CharactersApi(this).Initialize(apibuilder); // Plugin endpoints Log.Debug("Loading plugin api endpoints"); var endpoints = PluginFeatureController.Instance.Features.Get(ApiEndpointFeature.FeatureType).OfType(); if (endpoints.Any()) { foreach (var endpoint in endpoints) endpoint.Initialize(apibuilder); } // Run server Log.Info("Starting webserver"); httpListener.Start(); Listen(); } private void Listen() { var apiUrl = Settings.Get().ApiUrl; if (string.IsNullOrWhiteSpace(apiUrl)) throw new NullReferenceException("ApiUrl is empty!"); while (httpListener.IsListening) { var context = httpListener.GetContext(); if (context.Request.HttpMethod != HttpMethod.Post.Method || context.Request.ContentType is not string contentType || contentType.Contains("application/json") || context.Request.AcceptTypes is null || context.Request.AcceptTypes.Contains("application/json")) { close(); continue; } // Parse url var path = context.Request.Url?.PathAndQuery.Replace(apiUrl, string.Empty); if (string.IsNullOrWhiteSpace(path) || context.Request.ContentLength64 <= 0) { close(); continue; } // Find mapped function and get target type if (!apiBuilder.Handlers.TryGetValue(new(path, context.Request.HttpMethod), out var postAction)) { close(); return; } // ... // Read input content using StreamReader input = new(context.Request.InputStream); var contentJson = input.ReadToEnd(); // Deserialize request if (JsonHelpers.DeserializeRequest(contentJson) is not T request) { close(); continue; } // Set response parameters context.Response.StatusCode = (int)args.ResponseStatusCode; context.Response.StatusDescription = args.ResponseStatusDescription; // Write response content if (args.ResponseContent != null) { context.Response.ContentType = ContentTypes.CONTENT_TYPE_JSON; using StreamWriter output = new(context.Response.OutputStream); output.Write(args.ResponseContent); } close(); void close() => context.Response.OutputStream.Close(); } } 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; } }