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 ;
2024-05-29 03:35:52 +03:00
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-05-29 03:35:52 +03:00
using Mirea.Api.Endpoint.Common.Services ;
2024-07-04 23:54:17 +03:00
using Mirea.Api.Endpoint.Common.Settings ;
2024-05-29 03:35:52 +03:00
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 ;
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]
2024-06-01 07:33:08 +03:00
[ApiExplorerSettings(IgnoreApi = true)]
2024-07-04 23:39:12 +03:00
public partial class SetupController (
2024-06-01 08:20:27 +03:00
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" ;
2024-05-29 03:35:52 +03:00
private GeneralConfig GeneralConfig
2024-06-01 06:29:16 +03:00
{
2024-05-29 03:35:52 +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-07-04 23:45:33 +03:00
$"If you need to restart the configuration, then delete the \" { 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 ) ) ;
}
2024-07-08 01:56:14 +03:00
[HttpGet("IsConfigured")]
public ActionResult < bool > IsConfigured ( ) = >
! notConfigureService . IsMaintenanceMode ;
2024-05-28 07:19:40 +03:00
[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
{
2024-07-05 01:59:36 +03:00
Path = UrlHelper . GetSubPathWithoutFirstApiName + "api" ,
2024-07-05 01:35:19 +03:00
Domain = UrlHelper . CurrentDomain ( ControllerContext . HttpContext ) ,
Secure = true ,
HttpOnly = true
2024-05-28 07:19:40 +03:00
} ) ;
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-07-04 23:39:12 +03:00
if ( user . Password . Length < 8 | | ! PasswordExistUpperLetter ( ) . IsMatch ( user . Password ) | | ! PasswordExistSpecialSymbol ( ) . IsMatch ( user . Password ) )
2024-06-01 08:19:26 +03:00
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]
2024-06-01 07:26:22 +03:00
public ActionResult < bool > SetLogging ( [ FromBody ] LoggingRequest ? request = null )
2024-05-29 03:44:24 +03:00
{
var settings = ( request = = null ) switch
{
true = > new LogSettings
{
2024-06-10 22:04:29 +03:00
EnableLogToFile = true
2024-05-29 03:44:24 +03:00
} ,
false = > new LogSettings
{
EnableLogToFile = request . EnableLogToFile ,
LogFileName = request . LogFileName ,
LogFilePath = request . LogFilePath
}
} ;
2024-06-10 22:04:29 +03:00
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]
2024-06-01 07:26:22 +03:00
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." ) ;
2024-07-04 23:54:17 +03:00
admin . SaveSetting ( ) ;
2024-07-04 23:45:33 +03:00
GeneralConfig . SaveSetting ( ) ;
2024-06-01 06:29:16 +03:00
return true ;
}
2024-07-04 23:39:12 +03:00
[GeneratedRegex("[A-Z] + ")]
private static partial Regex PasswordExistUpperLetter ( ) ;
[GeneratedRegex("[!@#$%^&*] + ")]
private static partial Regex PasswordExistSpecialSymbol ( ) ;
2024-05-28 07:19:40 +03:00
}