using Cronos; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Caching.Memory; using Mirea.Api.Dto.Requests; using Mirea.Api.Dto.Requests.Configuration; using Mirea.Api.Endpoint.Common.Attributes; using Mirea.Api.Endpoint.Common.Exceptions; using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General.Settings; using MySqlConnector; using Npgsql; using StackExchange.Redis; using System; using System.Data; using System.IO; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text.Json; namespace Mirea.Api.Endpoint.Controllers.Configuration; [ApiVersion("1.0")] [ApiController] [MaintenanceModeIgnore] public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService, IMemoryCache cache) : BaseController { private const string CacheGeneralKey = "config_part"; private GeneralConfig GeneralConfig { get => cache.Get(CacheGeneralKey) ?? new GeneralConfig(); set => cache.Set(CacheGeneralKey, value); } [HttpGet("GenerateToken")] [Localhost] public ActionResult GenerateToken() { if (!notConfigureService.IsMaintenanceMode) throw new ControllerArgumentException( "The token cannot be generated because the server has been configured. " + $"If you need to restart the configuration, then delete the \"{PathBuilder.Combine(GeneralConfig.FilePath)}\" file and restart the application."); var token = new byte[32]; RandomNumberGenerator.Create().GetBytes(token); setupToken.SetToken(token); return Ok(Convert.ToBase64String(token)); } [HttpGet("CheckToken")] public ActionResult CheckToken([FromQuery] string token) { if (!setupToken.MatchToken(Convert.FromBase64String(token))) return Unauthorized("The token is not valid"); Response.Cookies.Append("AuthToken", token, new CookieOptions { HttpOnly = false, Secure = false, Path = "/" }); return Ok(true); } private ActionResult SetDatabase(string connectionString, DbSettings.DatabaseEnum databaseType) where TConnection : class, IDbConnection, new() where TException : Exception { try { using var connection = new TConnection(); connection.ConnectionString = connectionString; connection.Open(); connection.Close(); var general = GeneralConfig; general.DbSettings = new DbSettings { ConnectionStringSql = connectionString, TypeDatabase = databaseType }; GeneralConfig = general; return Ok(true); } catch (TException ex) { throw new ControllerArgumentException($"Error when connecting: {ex.Message}"); } } [HttpPost("SetPsql")] [TokenAuthentication] [BadRequestResponse] public ActionResult SetPsql([FromBody] DatabaseRequest request) { string connectionString = $"Host={request.Server}:{request.Port};Username={request.User};Database={request.Database}"; if (request.Password != null) connectionString += $";Password={request.Password}"; if (request.Ssl) connectionString += ";SSL Mode=Require;"; return SetDatabase(connectionString, DbSettings.DatabaseEnum.PostgresSql); } [HttpPost("SetMysql")] [TokenAuthentication] [BadRequestResponse] public ActionResult SetMysql([FromBody] DatabaseRequest request) { string connectionString = $"Server={request.Server}:{request.Port};Uid={request.User};Database={request.Database}"; if (request.Password != null) connectionString += $";Pwd={request.Password}"; if (request.Ssl) connectionString += ";SslMode=Require;"; return SetDatabase(connectionString, DbSettings.DatabaseEnum.Mysql); } [HttpPost("SetSqlite")] [TokenAuthentication] public ActionResult SetSqlite([FromQuery] string? path) { if (string.IsNullOrEmpty(path)) path = "database"; path = PathBuilder.Combine(path); if (!Directory.Exists(path)) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Directory.CreateDirectory(path); else Directory.CreateDirectory(path, UnixFileMode.UserRead | UnixFileMode.UserWrite); } else throw new ControllerArgumentException("Such a folder exists. Enter a different name"); string connectionString = $"Data Source={PathBuilder.Combine(path, "database.db3")}"; return SetDatabase(connectionString, DbSettings.DatabaseEnum.Sqlite); } [HttpPost("SetRedis")] [TokenAuthentication] [BadRequestResponse] public ActionResult SetRedis([FromBody] CacheRequest request) { string connectionString = $"{request.Server}:{request.Port},ssl=false"; if (request.Password != null) connectionString += $",password={request.Password}"; try { var redis = ConnectionMultiplexer.Connect(connectionString); redis.Close(); var general = GeneralConfig; general.CacheSettings = new CacheSettings { ConnectionString = connectionString, TypeDatabase = CacheSettings.CacheEnum.Redis }; GeneralConfig = general; return Ok(true); } catch (Exception ex) { throw new ControllerArgumentException("Error when connecting to Redis: " + ex.Message); } } [HttpPost("SetMemcached")] [TokenAuthentication] [BadRequestResponse] public ActionResult SetMemcached() { var general = GeneralConfig; general.CacheSettings = new CacheSettings { ConnectionString = null, TypeDatabase = CacheSettings.CacheEnum.Memcached }; GeneralConfig = general; return Ok(true); } [HttpPost("SetLogging")] [TokenAuthentication] [BadRequestResponse] public ActionResult SetLogging([FromBody] LoggingRequest? request) { var settings = (request == null) switch { true => new LogSettings { EnableLogToFile = true, LogFileName = "logging-", LogFilePath = "logs" }, false => new LogSettings { EnableLogToFile = request.EnableLogToFile, LogFileName = request.LogFileName, LogFilePath = request.LogFilePath } }; var general = GeneralConfig; general.LogSettings = settings; GeneralConfig = general; return true; } [HttpPost("SetEmail")] [TokenAuthentication] [BadRequestResponse] public ActionResult SetEmail([FromBody] EmailRequest? request) { var settings = (request == null) switch { true => new EmailSettings(), false => new EmailSettings { Server = request.Server, From = request.From, Password = request.Password, Port = request.Port, Ssl = request.Ssl, User = request.User } }; var general = GeneralConfig; general.EmailSettings = settings; GeneralConfig = general; return true; } [HttpPost("SetSchedule")] [TokenAuthentication] [BadRequestResponse] public ActionResult SetSchedule([FromBody] ScheduleConfigurationRequest request) { var general = GeneralConfig; general.ScheduleSettings = new ScheduleSettings { // every 6 hours CronUpdateSchedule = request.CronUpdateSchedule ?? "0 */6 * * *", StartTerm = request.StartTerm, PairPeriod = request.PairPeriod.ConvertFromDto() }; if (!CronExpression.TryParse(general.ScheduleSettings.CronUpdateSchedule, CronFormat.Standard, out _)) throw new ControllerArgumentException("The Cron task could not be parsed. Check the format of the entered data."); GeneralConfig = general; return true; } }