MireaBackend/Endpoint/Controllers/Configuration/SetupController.cs

351 lines
12 KiB
C#
Raw Normal View History

2024-05-29 03:46:16 +03:00
using Cronos;
2024-05-28 07:19:40 +03:00
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
2024-05-29 03:42:39 +03:00
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Caching.Memory;
2024-05-29 03:42:39 +03:00
using Mirea.Api.Dto.Requests;
using Mirea.Api.Dto.Requests.Configuration;
2024-05-28 07:19:40 +03:00
using Mirea.Api.Endpoint.Common.Attributes;
using Mirea.Api.Endpoint.Common.Exceptions;
using Mirea.Api.Endpoint.Common.Interfaces;
2024-06-01 08:19:26 +03:00
using Mirea.Api.Endpoint.Common.Model;
using Mirea.Api.Endpoint.Common.Services;
2024-05-28 07:19:40 +03:00
using Mirea.Api.Endpoint.Configuration.General;
using Mirea.Api.Endpoint.Configuration.General.Settings;
2024-06-01 06:29:16 +03:00
using Mirea.Api.Endpoint.Configuration.General.Validators;
2024-06-01 08:19:26 +03:00
using Mirea.Api.Security.Services;
2024-05-29 03:42:39 +03:00
using MySqlConnector;
using Npgsql;
2024-05-29 03:43:08 +03:00
using StackExchange.Redis;
2024-05-29 03:37:04 +03:00
using System;
2024-06-10 22:15:15 +03:00
using System.Collections.Generic;
2024-05-29 03:37:04 +03:00
using System.Data;
2024-05-29 03:46:16 +03:00
using System.IO;
2024-06-01 08:19:26 +03:00
using System.Net.Mail;
2024-05-29 03:46:16 +03:00
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text.Json;
2024-06-01 08:19:26 +03:00
using System.Text.RegularExpressions;
2024-05-28 07:19:40 +03:00
namespace Mirea.Api.Endpoint.Controllers.Configuration;
[ApiVersion("1.0")]
[ApiController]
[MaintenanceModeIgnore]
[ApiExplorerSettings(IgnoreApi = true)]
2024-06-01 08:20:27 +03:00
public class SetupController(
ISetupToken setupToken,
IMaintenanceModeNotConfigureService notConfigureService,
IMemoryCache cache,
PasswordHashService passwordHashService) : BaseController
2024-05-28 07:19:40 +03:00
{
2024-06-01 06:27:49 +03:00
private const string CacheGeneralKey = "config_general";
private const string CacheAdminKey = "config_admin";
private GeneralConfig GeneralConfig
2024-06-01 06:29:16 +03:00
{
get => cache.Get<GeneralConfig>(CacheGeneralKey) ?? new GeneralConfig();
set => cache.Set(CacheGeneralKey, value);
}
2024-05-28 07:19:40 +03:00
[HttpGet("GenerateToken")]
2024-05-29 03:30:26 +03:00
[Localhost]
2024-05-28 07:19:40 +03:00
public ActionResult<string> GenerateToken()
{
if (!notConfigureService.IsMaintenanceMode)
throw new ControllerArgumentException(
"The token cannot be generated because the server has been configured. " +
2024-05-28 07:20:21 +03:00
$"If you need to restart the configuration, then delete the \"{PathBuilder.Combine(GeneralConfig.FilePath)}\" file and restart the application.");
2024-05-28 07:19:40 +03:00
var token = new byte[32];
RandomNumberGenerator.Create().GetBytes(token);
setupToken.SetToken(token);
return Ok(Convert.ToBase64String(token));
}
[HttpGet("CheckToken")]
public ActionResult<bool> 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);
}
2024-05-29 03:37:04 +03:00
private ActionResult<bool> SetDatabase<TConnection, TException>(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;
2024-06-01 06:29:16 +03:00
return Ok(true);
}
2024-05-29 03:37:04 +03:00
catch (TException ex)
{
throw new ControllerArgumentException($"Error when connecting: {ex.Message}");
}
}
2024-05-29 03:38:21 +03:00
[HttpPost("SetPsql")]
[TokenAuthentication]
[BadRequestResponse]
public ActionResult<bool> 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<NpgsqlConnection, NpgsqlException>(connectionString, DbSettings.DatabaseEnum.PostgresSql);
}
[HttpPost("SetMysql")]
[TokenAuthentication]
[BadRequestResponse]
public ActionResult<bool> SetMysql([FromBody] DatabaseRequest request)
{
2024-06-01 06:28:13 +03:00
string connectionString = $"Server={request.Server}:{request.Port};Uid={request.User};Database={request.Database};";
2024-05-29 03:38:21 +03:00
if (request.Password != null)
2024-06-01 06:28:13 +03:00
connectionString += $"Pwd={request.Password};";
2024-05-29 03:38:21 +03:00
if (request.Ssl)
2024-06-01 06:28:13 +03:00
connectionString += "SslMode=Require;";
2024-05-29 03:38:21 +03:00
return SetDatabase<MySqlConnection, MySqlException>(connectionString, DbSettings.DatabaseEnum.Mysql);
}
[HttpPost("SetSqlite")]
[TokenAuthentication]
public ActionResult<bool> 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<SqliteConnection, SqliteException>(connectionString, DbSettings.DatabaseEnum.Sqlite);
}
2024-05-29 03:43:08 +03:00
[HttpPost("SetRedis")]
[TokenAuthentication]
[BadRequestResponse]
public ActionResult<bool> 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<bool> SetMemcached()
{
var general = GeneralConfig;
general.CacheSettings = new CacheSettings
{
ConnectionString = null,
TypeDatabase = CacheSettings.CacheEnum.Memcached
};
GeneralConfig = general;
return Ok(true);
}
2024-05-29 03:44:24 +03:00
2024-06-01 06:27:49 +03:00
[HttpPost("CreateAdmin")]
[TokenAuthentication]
[BadRequestResponse]
public ActionResult<string> CreateAdmin([FromBody] CreateUserRequest user)
{
2024-06-01 08:19:26 +03:00
if (user.Password.Length < 8 || !Regex.IsMatch(user.Password, "[A-Z]+") || !Regex.IsMatch(user.Password, "[!@#$%^&*]+"))
throw new ControllerArgumentException("The password must be at least 8 characters long and contain at least one uppercase letter and one special character.");
if (!MailAddress.TryCreate(user.Email, out _))
throw new ControllerArgumentException("The email address is incorrect.");
2024-06-01 08:20:27 +03:00
var (salt, hash) = passwordHashService.HashPassword(user.Password);
var admin = new Admin
{
Username = user.Username,
Email = user.Email,
PasswordHash = hash,
Salt = salt
};
cache.Set(CacheAdminKey, admin);
2024-06-01 06:27:49 +03:00
return Ok(true);
}
2024-05-29 03:44:24 +03:00
[HttpPost("SetLogging")]
[TokenAuthentication]
[BadRequestResponse]
public ActionResult<bool> SetLogging([FromBody] LoggingRequest? request = null)
2024-05-29 03:44:24 +03:00
{
var settings = (request == null) switch
{
true => new LogSettings
{
EnableLogToFile = true
2024-05-29 03:44:24 +03:00
},
false => new LogSettings
{
EnableLogToFile = request.EnableLogToFile,
LogFileName = request.LogFileName,
LogFilePath = request.LogFilePath
}
};
if (settings.EnableLogToFile)
{
if (string.IsNullOrEmpty(settings.LogFileName))
settings.LogFileName = "log-";
if (string.IsNullOrEmpty(settings.LogFilePath))
settings.LogFilePath = OperatingSystem.IsWindows() || PathBuilder.IsDefaultPath ?
PathBuilder.Combine("logs") :
"/var/log/mirea";
}
2024-05-29 03:44:24 +03:00
var general = GeneralConfig;
general.LogSettings = settings;
GeneralConfig = general;
return true;
}
2024-05-29 03:45:02 +03:00
[HttpPost("SetEmail")]
[TokenAuthentication]
[BadRequestResponse]
public ActionResult<bool> SetEmail([FromBody] EmailRequest? request = null)
2024-05-29 03:45:02 +03:00
{
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;
}
2024-05-29 03:46:16 +03:00
[HttpPost("SetSchedule")]
[TokenAuthentication]
[BadRequestResponse]
public ActionResult<bool> SetSchedule([FromBody] ScheduleConfigurationRequest request)
{
var general = GeneralConfig;
general.ScheduleSettings = new ScheduleSettings
{
// every 6 hours
CronUpdateSchedule = request.CronUpdateSchedule ?? "0 */6 * * *",
StartTerm = request.StartTerm,
2024-06-10 22:03:48 +03:00
PairPeriod = new Dictionary<int, ScheduleSettings.PairPeriodTime>
{
{1, new ScheduleSettings.PairPeriodTime(new TimeOnly(9, 0, 0), new TimeOnly(10, 30, 0))},
{2, new ScheduleSettings.PairPeriodTime(new TimeOnly(10, 40, 0), new TimeOnly(12, 10, 0))},
{3, new ScheduleSettings.PairPeriodTime(new TimeOnly(12, 40, 0), new TimeOnly(14, 10, 0))},
{4, new ScheduleSettings.PairPeriodTime(new TimeOnly(14, 20, 0), new TimeOnly(15, 50, 0))},
{5, new ScheduleSettings.PairPeriodTime(new TimeOnly(16, 20, 0), new TimeOnly(17, 50, 0))},
{6, new ScheduleSettings.PairPeriodTime(new TimeOnly(18, 0, 0), new TimeOnly(19, 30, 0))},
{7, new ScheduleSettings.PairPeriodTime(new TimeOnly(19, 40, 0), new TimeOnly(21, 10, 0))},
}
2024-05-29 03:46:16 +03:00
};
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;
2024-05-29 03:38:21 +03:00
}
2024-06-01 06:29:16 +03:00
[HttpPost("Submit")]
[TokenAuthentication]
[BadRequestResponse]
public ActionResult<bool> Submit()
{
if (!new SettingsRequiredValidator(GeneralConfig).AreSettingsValid())
throw new ControllerArgumentException("The necessary data has not been configured.");
2024-05-28 07:19:40 +03:00
2024-06-01 08:20:27 +03:00
if (!cache.TryGetValue(CacheAdminKey, out Admin? admin) || admin == null)
2024-06-01 06:29:16 +03:00
throw new ControllerArgumentException("The administrator's data was not set.");
if (System.IO.File.Exists(PathBuilder.Combine(GeneralConfig.FilePath)))
System.IO.File.Delete(PathBuilder.Combine(GeneralConfig.FilePath));
2024-06-01 08:20:27 +03:00
System.IO.File.WriteAllText(PathBuilder.Combine(Admin.PathToSave), JsonSerializer.Serialize(admin));
2024-06-01 06:29:16 +03:00
System.IO.File.WriteAllText(
PathBuilder.Combine(GeneralConfig.FilePath),
JsonSerializer.Serialize(GeneralConfig, new JsonSerializerOptions
{
WriteIndented = true
})
);
return true;
}
2024-05-28 07:19:40 +03:00
}