feat: add a return of the data that has been configured

This commit is contained in:
Polianin Nikita 2024-12-18 07:39:17 +03:00
parent fd578aa61e
commit e9ff1cabe8
13 changed files with 362 additions and 48 deletions

View File

@ -0,0 +1,17 @@
namespace Mirea.Api.Dto.Common;
/// <summary>
/// Specifies the types of caching mechanisms available.
/// </summary>
public enum CacheType
{
/// <summary>
/// Memcached caching type.
/// </summary>
Memcached,
/// <summary>
/// Redis caching type.
/// </summary>
Redis
}

View File

@ -0,0 +1,22 @@
namespace Mirea.Api.Dto.Common;
/// <summary>
/// Specifies the types of databases supported.
/// </summary>
public enum DatabaseType
{
/// <summary>
/// MySQL database type.
/// </summary>
Mysql,
/// <summary>
/// SQLite database type.
/// </summary>
Sqlite,
/// <summary>
/// PostgreSQL database type.
/// </summary>
PostgresSql
}

View File

@ -10,27 +10,18 @@ public class CreateUserRequest
/// <summary> /// <summary>
/// Gets or sets the email address of the user. /// Gets or sets the email address of the user.
/// </summary> /// </summary>
/// <remarks>
/// The email address is a required field.
/// </remarks>
[Required] [Required]
public required string Email { get; set; } public required string Email { get; set; }
/// <summary> /// <summary>
/// Gets or sets the username of the user. /// Gets or sets the username of the user.
/// </summary> /// </summary>
/// <remarks>
/// The username is a required field.
/// </remarks>
[Required] [Required]
public required string Username { get; set; } public required string Username { get; set; }
/// <summary> /// <summary>
/// Gets or sets the password of the user. /// Gets or sets the password of the user.
/// </summary> /// </summary>
/// <remarks>
/// The password is a required field.
/// </remarks>
[Required] [Required]
public required string Password { get; set; } public required string Password { get; set; }
} }

View File

@ -1,16 +1,17 @@
using Mirea.Api.Dto.Common; using Mirea.Api.Dto.Common;
using System; using System.ComponentModel.DataAnnotations;
namespace Mirea.Api.Dto.Responses; namespace Mirea.Api.Dto.Responses;
/// <summary> /// <summary>
/// Represents the response containing information about available OAuth providers. /// Represents the response containing information about available OAuth providers.
/// </summary> /// </summary>
public class AvailableProvidersResponse public class AvailableOAuthProvidersResponse
{ {
/// <summary> /// <summary>
/// Gets or sets the name of the OAuth provider. /// Gets or sets the name of the OAuth provider.
/// </summary> /// </summary>
[Required]
public required string ProviderName { get; set; } public required string ProviderName { get; set; }
/// <summary> /// <summary>
@ -19,7 +20,8 @@ public class AvailableProvidersResponse
public OAuthProvider Provider { get; set; } public OAuthProvider Provider { get; set; }
/// <summary> /// <summary>
/// Gets or sets the redirect URI for the OAuth provider. /// Gets or sets the redirect URL for the OAuth provider's authorization process.
/// </summary> /// </summary>
public required Uri Redirect { get; set; } [Required]
public required string Redirect { get; set; }
} }

View File

@ -0,0 +1,29 @@
using Mirea.Api.Dto.Common;
namespace Mirea.Api.Dto.Responses.Configuration;
/// <summary>
/// Represents a response containing cache configuration details.
/// </summary>
public class CacheResponse
{
/// <summary>
/// Gets or sets the type of cache database.
/// </summary>
public CacheType Type { get; set; }
/// <summary>
/// Gets or sets the server address.
/// </summary>
public string? Server { get; set; }
/// <summary>
/// Gets or sets the port number.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Gets or sets the password.
/// </summary>
public string? Password { get; set; }
}

View File

@ -0,0 +1,49 @@
using Mirea.Api.Dto.Common;
namespace Mirea.Api.Dto.Responses.Configuration;
/// <summary>
/// Represents a response containing database configuration details.
/// </summary>
public class DatabaseResponse
{
/// <summary>
/// Gets or sets the type of database.
/// </summary>
public DatabaseType Type { get; set; }
/// <summary>
/// Gets or sets the server address.
/// </summary>
public string? Server { get; set; }
/// <summary>
/// Gets or sets the port number.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Gets or sets the database name.
/// </summary>
public string? Database { get; set; }
/// <summary>
/// Gets or sets the username.
/// </summary>
public string? User { get; set; }
/// <summary>
/// Gets or sets a value indicating whether SSL is enabled.
/// </summary>
public bool Ssl { get; set; }
/// <summary>
/// Gets or sets the password.
/// </summary>
public string? Password { get; set; }
/// <summary>
/// Gets or sets the path to database. Only for Sqlite
/// </summary>
public string? PathToDatabase { get; set; }
}

View File

@ -0,0 +1,17 @@
namespace Mirea.Api.Dto.Responses;
/// <summary>
/// Represents the response containing the TOTP (Time-Based One-Time Password) key details.
/// </summary>
public class TotpKeyResponse
{
/// <summary>
/// Gets or sets the secret key used for TOTP generation.
/// </summary>
public required string Secret { get; set; }
/// <summary>
/// Gets or sets the image (QR code) representing the TOTP key.
/// </summary>
public required string Image { get; set; }
}

View File

@ -0,0 +1,35 @@
using Mirea.Api.Dto.Common;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Mirea.Api.Dto.Responses;
/// <summary>
/// Represents a response containing user information.
/// </summary>
public class UserResponse
{
/// <summary>
/// Gets or sets the email address of the user.
/// </summary>
[Required]
public required string Email { get; set; }
/// <summary>
/// Gets or sets the username of the user.
/// </summary>
[Required]
public required string Username { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the user has two-factor authentication enabled.
/// </summary>
[Required]
public bool TwoFactorAuthenticatorEnabled { get; set; }
/// <summary>
/// Gets or sets a collection of OAuth providers used by the user.
/// </summary>
[Required]
public required IEnumerable<OAuthProvider> UsedOAuthProviders { get; set; }
}

View File

@ -1,7 +1,7 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Mirea.Api.Dto.Common;
using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
namespace Mirea.Api.Endpoint.Configuration.Core.Startup; namespace Mirea.Api.Endpoint.Configuration.Core.Startup;
@ -10,7 +10,7 @@ public static class CacheConfiguration
public static IServiceCollection AddCustomRedis(this IServiceCollection services, IConfiguration configuration, IHealthChecksBuilder? healthChecksBuilder = null) public static IServiceCollection AddCustomRedis(this IServiceCollection services, IConfiguration configuration, IHealthChecksBuilder? healthChecksBuilder = null)
{ {
var cache = configuration.Get<GeneralConfig>()?.CacheSettings; var cache = configuration.Get<GeneralConfig>()?.CacheSettings;
if (cache?.TypeDatabase != CacheSettings.CacheEnum.Redis) if (cache?.TypeDatabase != CacheType.Redis)
return services; return services;
services.AddStackExchangeRedisCache(options => services.AddStackExchangeRedisCache(options =>

View File

@ -1,8 +1,8 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Mirea.Api.Dto.Common;
using Mirea.Api.Endpoint.Common.Services.Security; using Mirea.Api.Endpoint.Common.Services.Security;
using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
using Mirea.Api.Security; using Mirea.Api.Security;
using Mirea.Api.Security.Common.Interfaces; using Mirea.Api.Security.Common.Interfaces;
@ -16,7 +16,7 @@ public static class SecureConfiguration
services.AddSingleton<IRevokedToken, MemoryRevokedTokenService>(); services.AddSingleton<IRevokedToken, MemoryRevokedTokenService>();
if (configuration.Get<GeneralConfig>()?.CacheSettings?.TypeDatabase == CacheSettings.CacheEnum.Redis) if (configuration.Get<GeneralConfig>()?.CacheSettings?.TypeDatabase == CacheType.Redis)
services.AddSingleton<ICacheService, DistributedCacheService>(); services.AddSingleton<ICacheService, DistributedCacheService>();
else else
services.AddSingleton<ICacheService, MemoryCacheService>(); services.AddSingleton<ICacheService, MemoryCacheService>();

View File

@ -1,4 +1,5 @@
using Mirea.Api.Endpoint.Configuration.Validation.Attributes; using Mirea.Api.Dto.Common;
using Mirea.Api.Endpoint.Configuration.Validation.Attributes;
using Mirea.Api.Endpoint.Configuration.Validation.Interfaces; using Mirea.Api.Endpoint.Configuration.Validation.Interfaces;
namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
@ -6,18 +7,12 @@ namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
[RequiredSettings] [RequiredSettings]
public class CacheSettings : IIsConfigured public class CacheSettings : IIsConfigured
{ {
public enum CacheEnum public CacheType TypeDatabase { get; set; }
{
Memcached,
Redis
}
public CacheEnum TypeDatabase { get; set; }
public string? ConnectionString { get; set; } public string? ConnectionString { get; set; }
public bool IsConfigured() public bool IsConfigured()
{ {
return TypeDatabase == CacheEnum.Memcached || return TypeDatabase == CacheType.Memcached ||
!string.IsNullOrEmpty(ConnectionString); !string.IsNullOrEmpty(ConnectionString);
} }
} }

View File

@ -1,4 +1,5 @@
using Mirea.Api.DataAccess.Persistence.Common; using Mirea.Api.DataAccess.Persistence.Common;
using Mirea.Api.Dto.Common;
using Mirea.Api.Endpoint.Configuration.Validation.Attributes; using Mirea.Api.Endpoint.Configuration.Validation.Attributes;
using Mirea.Api.Endpoint.Configuration.Validation.Interfaces; using Mirea.Api.Endpoint.Configuration.Validation.Interfaces;
using System; using System;
@ -9,22 +10,16 @@ namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
[RequiredSettings] [RequiredSettings]
public class DbSettings : IIsConfigured public class DbSettings : IIsConfigured
{ {
public enum DatabaseEnum public DatabaseType TypeDatabase { get; set; }
{
Mysql,
Sqlite,
PostgresSql
}
public DatabaseEnum TypeDatabase { get; set; }
public required string ConnectionStringSql { get; set; } public required string ConnectionStringSql { get; set; }
[JsonIgnore] [JsonIgnore]
public DatabaseProvider DatabaseProvider => public DatabaseProvider DatabaseProvider =>
TypeDatabase switch TypeDatabase switch
{ {
DatabaseEnum.PostgresSql => DatabaseProvider.Postgresql, DatabaseType.PostgresSql => DatabaseProvider.Postgresql,
DatabaseEnum.Mysql => DatabaseProvider.Mysql, DatabaseType.Mysql => DatabaseProvider.Mysql,
DatabaseEnum.Sqlite => DatabaseProvider.Sqlite, DatabaseType.Sqlite => DatabaseProvider.Sqlite,
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException()
}; };

View File

@ -4,15 +4,21 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Mirea.Api.Dto.Common;
using Mirea.Api.Dto.Requests; using Mirea.Api.Dto.Requests;
using Mirea.Api.Dto.Requests.Configuration; using Mirea.Api.Dto.Requests.Configuration;
using Mirea.Api.Dto.Responses;
using Mirea.Api.Dto.Responses.Configuration;
using Mirea.Api.Endpoint.Common.Attributes; using Mirea.Api.Endpoint.Common.Attributes;
using Mirea.Api.Endpoint.Common.Exceptions; using Mirea.Api.Endpoint.Common.Exceptions;
using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Common.Interfaces;
using Mirea.Api.Endpoint.Common.MapperDto;
using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Common.Services;
using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
using Mirea.Api.Endpoint.Configuration.Validation.Validators; using Mirea.Api.Endpoint.Configuration.Validation.Validators;
using Mirea.Api.Security.Common.Domain;
using Mirea.Api.Security.Services; using Mirea.Api.Security.Services;
using MySqlConnector; using MySqlConnector;
using Npgsql; using Npgsql;
@ -36,7 +42,8 @@ public class SetupController(
ISetupToken setupToken, ISetupToken setupToken,
IMaintenanceModeNotConfigureService notConfigureService, IMaintenanceModeNotConfigureService notConfigureService,
IMemoryCache cache, IMemoryCache cache,
PasswordHashService passwordHashService) : BaseController PasswordHashService passwordHashService,
IOptionsSnapshot<Admin> user) : BaseController
{ {
private const string CacheGeneralKey = "config_general"; private const string CacheGeneralKey = "config_general";
private const string CacheAdminKey = "config_admin"; private const string CacheAdminKey = "config_admin";
@ -95,7 +102,12 @@ public class SetupController(
return Ok(true); return Ok(true);
} }
private ActionResult<bool> SetDatabase<TConnection, TException>(string connectionString, DbSettings.DatabaseEnum databaseType) [HttpGet("IsConfiguredToken")]
[TokenAuthentication]
public ActionResult<bool> IsConfiguredToken() =>
Ok(true);
private void SetDatabase<TConnection, TException>(string connectionString, DatabaseType databaseType)
where TConnection : class, IDbConnection, new() where TConnection : class, IDbConnection, new()
where TException : Exception where TException : Exception
{ {
@ -118,8 +130,6 @@ public class SetupController(
TypeDatabase = databaseType TypeDatabase = databaseType
}; };
GeneralConfig = general; GeneralConfig = general;
return Ok(true);
} }
catch (TException ex) catch (TException ex)
{ {
@ -138,7 +148,20 @@ public class SetupController(
if (request.Ssl) if (request.Ssl)
connectionString += ";SSL Mode=Require;"; connectionString += ";SSL Mode=Require;";
return SetDatabase<NpgsqlConnection, NpgsqlException>(connectionString, DbSettings.DatabaseEnum.PostgresSql);
SetDatabase<NpgsqlConnection, NpgsqlException>(connectionString, DatabaseType.PostgresSql);
cache.Set("database", new DatabaseResponse
{
Type = DatabaseType.PostgresSql,
Database = request.Database,
Password = request.Password,
Ssl = request.Ssl,
Port = request.Port,
Server = request.Server,
User = request.User
});
return Ok(true);
} }
[HttpPost("SetMysql")] [HttpPost("SetMysql")]
@ -152,7 +175,19 @@ public class SetupController(
if (request.Ssl) if (request.Ssl)
connectionString += "SslMode=Require;"; connectionString += "SslMode=Require;";
return SetDatabase<MySqlConnection, MySqlException>(connectionString, DbSettings.DatabaseEnum.Mysql); SetDatabase<MySqlConnection, MySqlException>(connectionString, DatabaseType.Mysql);
cache.Set("database", new DatabaseResponse
{
Type = DatabaseType.Mysql,
Database = request.Database,
Password = request.Password,
Ssl = request.Ssl,
Port = request.Port,
Server = request.Server,
User = request.User
});
return Ok(true);
} }
[HttpPost("SetSqlite")] [HttpPost("SetSqlite")]
@ -179,14 +214,26 @@ public class SetupController(
var filePath = Path.Combine(path, "database.db3"); var filePath = Path.Combine(path, "database.db3");
var connectionString = $"Data Source={filePath}"; var connectionString = $"Data Source={filePath}";
var result = SetDatabase<SqliteConnection, SqliteException>(connectionString, DbSettings.DatabaseEnum.Sqlite); SetDatabase<SqliteConnection, SqliteException>(connectionString, DatabaseType.Sqlite);
foreach (var file in Directory.GetFiles(path)) foreach (var file in Directory.GetFiles(path))
System.IO.File.Delete(file); System.IO.File.Delete(file);
return result; cache.Set("database", new DatabaseResponse
{
Type = DatabaseType.Sqlite,
PathToDatabase = path
});
return Ok(true);
} }
[HttpGet("DatabaseConfiguration")]
[TokenAuthentication]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult<DatabaseRequest> DatabaseConfiguration() =>
cache.TryGetValue<DatabaseResponse>("database", out var response) ? Ok(response) : NoContent();
[HttpPost("SetRedis")] [HttpPost("SetRedis")]
[TokenAuthentication] [TokenAuthentication]
[BadRequestResponse] [BadRequestResponse]
@ -205,10 +252,17 @@ public class SetupController(
general.CacheSettings = new CacheSettings general.CacheSettings = new CacheSettings
{ {
ConnectionString = connectionString, ConnectionString = connectionString,
TypeDatabase = CacheSettings.CacheEnum.Redis TypeDatabase = CacheType.Redis
}; };
GeneralConfig = general; GeneralConfig = general;
cache.Set("cache", new CacheResponse
{
Type = CacheType.Redis,
Server = request.Server,
Password = request.Password,
Port = request.Port
});
return Ok(true); return Ok(true);
} }
catch (Exception ex) catch (Exception ex)
@ -226,20 +280,29 @@ public class SetupController(
general.CacheSettings = new CacheSettings general.CacheSettings = new CacheSettings
{ {
ConnectionString = null, ConnectionString = null,
TypeDatabase = CacheSettings.CacheEnum.Memcached TypeDatabase = CacheType.Memcached
}; };
GeneralConfig = general; GeneralConfig = general;
cache.Set("cache", new CacheResponse
{
Type = CacheType.Memcached
});
return Ok(true); return Ok(true);
} }
[HttpGet("CacheConfiguration")]
[TokenAuthentication]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult<CacheResponse> CacheConfiguration() =>
cache.TryGetValue<CacheResponse>("cache", out var response) ? Ok(response) : NoContent();
[HttpPost("CreateAdmin")] [HttpPost("CreateAdmin")]
[TokenAuthentication] [TokenAuthentication]
[BadRequestResponse] [BadRequestResponse]
public ActionResult<string> CreateAdmin([FromBody] CreateUserRequest user) public ActionResult<string> CreateAdmin([FromBody] CreateUserRequest user)
{ {
if (!PasswordHashService.HasPasswordInPolicySecurity(user.Password)) new PasswordPolicyService(GeneralConfig.PasswordPolicy).ValidatePasswordOrThrow(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 _)) if (!MailAddress.TryCreate(user.Email, out _))
throw new ControllerArgumentException("The email address is incorrect."); throw new ControllerArgumentException("The email address is incorrect.");
@ -258,6 +321,73 @@ public class SetupController(
return Ok(true); return Ok(true);
} }
[HttpGet("UpdateAdminConfiguration")]
[TokenAuthentication]
public ActionResult UpdateAdminConfiguration()
{
if (string.IsNullOrEmpty(user.Value.Email))
return Ok();
if (!cache.TryGetValue<Admin>(CacheAdminKey, out var admin))
{
admin = user.Value;
cache.Set(CacheAdminKey, admin);
return Ok();
}
admin!.OAuthProviders = user.Value.OAuthProviders;
if (string.IsNullOrEmpty(admin.Email))
admin.Email = user.Value.Email;
if (string.IsNullOrEmpty(admin.Username))
admin.Username = user.Value.Username;
cache.Set(CacheAdminKey, admin);
return Ok();
}
[HttpGet("AdminConfiguration")]
[TokenAuthentication]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult<UserResponse> AdminConfiguration() =>
cache.TryGetValue<Admin>(CacheAdminKey, out var admin) ? Ok(new UserResponse()
{
Email = admin!.Email,
Username = admin.Username,
TwoFactorAuthenticatorEnabled = admin.TwoFactorAuthenticator != TwoFactorAuthenticator.None,
UsedOAuthProviders = admin.OAuthProviders == null ? [] : admin.OAuthProviders.Keys.Select(x => x.ConvertToDto())
}) : NoContent();
[HttpGet("GenerateTotpKey")]
[TokenAuthentication]
public ActionResult<string> GenerateTotpKey()
{
if (cache.TryGetValue<string>("totpSecret", out var secret))
return secret!;
secret = GeneratorKey.GenerateAlphaNumericBase32Compatible(16);
cache.Set("totpSecret", secret);
return secret;
}
[HttpGet("VerifyTotp")]
[TokenAuthentication]
public ActionResult<bool> VerifyTotp([FromQuery] string code)
{
var isCorrect = cache.TryGetValue<string>("totpSecret", out var secret) &&
new TotpService(secret!).VerifyToken(code);
if (!isCorrect || !cache.TryGetValue<Admin>(CacheAdminKey, out var admin))
return false;
admin!.Secret = secret;
admin.TwoFactorAuthenticator = TwoFactorAuthenticator.Totp;
cache.Set(CacheAdminKey, admin);
return true;
}
[HttpPost("SetLogging")] [HttpPost("SetLogging")]
[TokenAuthentication] [TokenAuthentication]
[BadRequestResponse] [BadRequestResponse]
@ -292,9 +422,22 @@ public class SetupController(
general.LogSettings = settings; general.LogSettings = settings;
GeneralConfig = general; GeneralConfig = general;
cache.Set("logging", new LoggingRequest
{
EnableLogToFile = settings.EnableLogToFile,
LogFileName = settings.LogFileName,
LogFilePath = settings.LogFilePath
});
return true; return true;
} }
[HttpGet("LoggingConfiguration")]
[TokenAuthentication]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult<LoggingRequest> LoggingConfiguration() =>
cache.TryGetValue<LoggingRequest>("logging", out var data) ? Ok(data) : NoContent();
[HttpPost("SetEmail")] [HttpPost("SetEmail")]
[TokenAuthentication] [TokenAuthentication]
[BadRequestResponse] [BadRequestResponse]
@ -318,9 +461,16 @@ public class SetupController(
general.EmailSettings = settings; general.EmailSettings = settings;
GeneralConfig = general; GeneralConfig = general;
cache.Set("email", settings);
return true; return true;
} }
[HttpGet("EmailConfiguration")]
[TokenAuthentication]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult<EmailRequest> EmailConfiguration() =>
cache.TryGetValue<EmailRequest>("email", out var data) ? Ok(data) : NoContent();
[HttpPost("SetSchedule")] [HttpPost("SetSchedule")]
[TokenAuthentication] [TokenAuthentication]
[BadRequestResponse] [BadRequestResponse]
@ -349,8 +499,20 @@ public class SetupController(
GeneralConfig = general; GeneralConfig = general;
cache.Set("schedule", new ScheduleConfigurationRequest()
{
StartTerm = general.ScheduleSettings.StartTerm,
CronUpdateSchedule = general.ScheduleSettings.CronUpdateSchedule
});
return true; return true;
} }
[HttpGet("ScheduleConfiguration")]
[TokenAuthentication]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult<ScheduleConfigurationRequest> ScheduleConfiguration() =>
cache.TryGetValue<ScheduleConfigurationRequest>("schedule", out var data) ? Ok(data) : NoContent();
[HttpPost("SetPasswordPolicy")] [HttpPost("SetPasswordPolicy")]
[TokenAuthentication] [TokenAuthentication]
public ActionResult<bool> SetPasswordPolicy([FromBody] PasswordPolicy? policy = null) public ActionResult<bool> SetPasswordPolicy([FromBody] PasswordPolicy? policy = null)