using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; using API.Data; using API.DTOs.Email; using API.DTOs.KavitaPlus.Metadata; using API.DTOs.Settings; using API.Entities; using API.Entities.Enums; using API.Extensions; using API.Helpers.Converters; using API.Logging; using API.Services; using API.Services.Tasks.Scanner; using AutoMapper; using Cronos; using Hangfire; using Kavita.Common; using Kavita.Common.EnvironmentInfo; using Kavita.Common.Extensions; using Kavita.Common.Helpers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Swashbuckle.AspNetCore.Annotations; namespace API.Controllers; #nullable enable public class SettingsController : BaseApiController { private readonly ILogger _logger; private readonly IUnitOfWork _unitOfWork; private readonly IMapper _mapper; private readonly IEmailService _emailService; private readonly ILocalizationService _localizationService; private readonly ISettingsService _settingsService; public SettingsController(ILogger logger, IUnitOfWork unitOfWork, IMapper mapper, IEmailService emailService, ILocalizationService localizationService, ISettingsService settingsService) { _logger = logger; _unitOfWork = unitOfWork; _mapper = mapper; _emailService = emailService; _localizationService = localizationService; _settingsService = settingsService; } /// /// Returns the base url for this instance (if set) /// /// [HttpGet("base-url")] public async Task> GetBaseUrl() { var settingsDto = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); return Ok(settingsDto.BaseUrl); } /// /// Returns the server settings /// /// [Authorize(Policy = "RequireAdminRole")] [HttpGet] public async Task> GetSettings() { var settingsDto = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); return Ok(settingsDto); } [Authorize(Policy = "RequireAdminRole")] [HttpPost("reset")] public async Task> ResetSettings() { _logger.LogInformation("{UserName} is resetting Server Settings", User.GetUsername()); return await UpdateSettings(_mapper.Map(Seed.DefaultSettings)); } /// /// Resets the IP Addresses /// /// [Authorize(Policy = "RequireAdminRole")] [HttpPost("reset-ip-addresses")] public async Task> ResetIpAddressesSettings() { _logger.LogInformation("{UserName} is resetting IP Addresses Setting", User.GetUsername()); var ipAddresses = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.IpAddresses); ipAddresses.Value = Configuration.DefaultIpAddresses; _unitOfWork.SettingsRepository.Update(ipAddresses); if (!await _unitOfWork.CommitAsync()) { await _unitOfWork.RollbackAsync(); } return Ok(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()); } /// /// Resets the Base url /// /// [Authorize(Policy = "RequireAdminRole")] [HttpPost("reset-base-url")] public async Task> ResetBaseUrlSettings() { _logger.LogInformation("{UserName} is resetting Base Url Setting", User.GetUsername()); var baseUrl = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BaseUrl); baseUrl.Value = Configuration.DefaultBaseUrl; _unitOfWork.SettingsRepository.Update(baseUrl); if (!await _unitOfWork.CommitAsync()) { await _unitOfWork.RollbackAsync(); } Configuration.BaseUrl = baseUrl.Value; return Ok(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()); } /// /// Is the minimum information setup for Email to work /// /// [Authorize(Policy = "RequireAdminRole")] [HttpGet("is-email-setup")] public async Task> IsEmailSetup() { var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); return Ok(settings.IsEmailSetup()); } /// /// Update Server settings /// /// /// [Authorize(Policy = "RequireAdminRole")] [HttpPost] public async Task> UpdateSettings(ServerSettingDto updateSettingsDto) { _logger.LogInformation("{UserName} is updating Server Settings", User.GetUsername()); try { var d = await _settingsService.UpdateSettings(updateSettingsDto); return Ok(d); } catch (KavitaException ex) { return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message)); } catch (Exception ex) { _logger.LogError(ex, "There was an exception when updating server settings"); return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error")); } } /// /// All values allowed for Task Scheduling APIs. A custom cron job is not included. Disabled is not applicable for Cleanup. /// /// [Authorize(Policy = "RequireAdminRole")] [HttpGet("task-frequencies")] public ActionResult> GetTaskFrequencies() { return Ok(CronConverter.Options); } [Authorize(Policy = "RequireAdminRole")] [HttpGet("library-types")] public ActionResult> GetLibraryTypes() { return Ok(Enum.GetValues().Select(t => t.ToDescription())); } [Authorize(Policy = "RequireAdminRole")] [HttpGet("log-levels")] public ActionResult> GetLogLevels() { return Ok(new[] {"Trace", "Debug", "Information", "Warning", "Critical"}); } [HttpGet("opds-enabled")] public async Task> GetOpdsEnabled() { var settingsDto = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); return Ok(settingsDto.EnableOpds); } /// /// Is the cron expression valid for Kavita's scheduler /// /// /// [HttpGet("is-valid-cron")] public ActionResult IsValidCron(string cronExpression) { // NOTE: This must match Hangfire's underlying cron system. Hangfire is unique return Ok(CronHelper.IsValidCron(cronExpression)); } /// /// Sends a test email to see if email settings are hooked up correctly /// /// [Authorize(Policy = "RequireAdminRole")] [HttpPost("test-email-url")] public async Task> TestEmailServiceUrl() { var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId()); if (string.IsNullOrEmpty(user?.Email)) return BadRequest("Your account has no email on record. Cannot email."); return Ok(await _emailService.SendTestEmail(user!.Email)); } /// /// Get the metadata settings for Kavita+ users. /// /// [Authorize(Policy = "RequireAdminRole")] [HttpGet("metadata-settings")] public async Task> GetMetadataSettings() { return Ok(await _unitOfWork.SettingsRepository.GetMetadataSettingDto()); } /// /// Update the metadata settings for Kavita+ Metadata feature /// /// /// [Authorize(Policy = "RequireAdminRole")] [HttpPost("metadata-settings")] public async Task> UpdateMetadataSettings(MetadataSettingsDto dto) { try { return Ok(await _settingsService.UpdateMetadataSettings(dto)); } catch (Exception ex) { _logger.LogError(ex, "There was an issue when updating metadata settings"); return BadRequest(ex.Message); } } }