From e9011011ed4007bc07d9e91efc4a8adcd53d5b47 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 28 Dec 2025 16:00:01 -0800 Subject: [PATCH] Add v5 UI settings endpoints --- .../Settings/SettingsController.cs | 49 ++++++++++++++++++ .../Settings/UiSettingsController.cs | 50 +++++++++++++++++++ .../Settings/UiSettingsResource.cs | 44 ++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 src/Sonarr.Api.V5/Settings/SettingsController.cs create mode 100644 src/Sonarr.Api.V5/Settings/UiSettingsController.cs create mode 100644 src/Sonarr.Api.V5/Settings/UiSettingsResource.cs diff --git a/src/Sonarr.Api.V5/Settings/SettingsController.cs b/src/Sonarr.Api.V5/Settings/SettingsController.cs new file mode 100644 index 000000000..f3e7f0d43 --- /dev/null +++ b/src/Sonarr.Api.V5/Settings/SettingsController.cs @@ -0,0 +1,49 @@ +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using NzbDrone.Core.Configuration; +using Sonarr.Http.REST; +using Sonarr.Http.REST.Attributes; + +namespace Sonarr.Api.V5.Config +{ + public abstract class ConfigController : RestController + where TResource : RestResource, new() + { + protected readonly IConfigService _configService; + + protected ConfigController(IConfigService configService) + { + _configService = configService; + } + + protected override TResource GetResourceById(int id) + { + return GetConfig(); + } + + [HttpGet] + [Produces("application/json")] + public TResource GetConfig() + { + var resource = ToResource(_configService); + resource.Id = 1; + + return resource; + } + + [RestPutById] + [Consumes("application/json")] + public virtual ActionResult SaveConfig([FromBody] TResource resource) + { + var dictionary = resource.GetType() + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null)); + + _configService.SaveConfigDictionary(dictionary); + + return Accepted(resource.Id); + } + + protected abstract TResource ToResource(IConfigService model); + } +} diff --git a/src/Sonarr.Api.V5/Settings/UiSettingsController.cs b/src/Sonarr.Api.V5/Settings/UiSettingsController.cs new file mode 100644 index 000000000..39288b972 --- /dev/null +++ b/src/Sonarr.Api.V5/Settings/UiSettingsController.cs @@ -0,0 +1,50 @@ +using System.Reflection; +using FluentValidation; +using Microsoft.AspNetCore.Mvc; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Languages; +using Sonarr.Http; +using Sonarr.Http.REST.Attributes; + +namespace Sonarr.Api.V5.Config; + +[V5ApiController("settings/ui")] +public class UiSettingsController : ConfigController +{ + private readonly IConfigFileProvider _configFileProvider; + + public UiSettingsController(IConfigFileProvider configFileProvider, IConfigService configService) + : base(configService) + { + _configFileProvider = configFileProvider; + SharedValidator.RuleFor(c => c.UiLanguage).Custom((value, context) => + { + if (!Language.All.Any(o => o.Id == value)) + { + context.AddFailure("Invalid UI Language value"); + } + }); + + SharedValidator.RuleFor(c => c.UiLanguage) + .GreaterThanOrEqualTo(1) + .WithMessage("The UI Language value cannot be less than 1"); + } + + [RestPutById] + public override ActionResult SaveConfig([FromBody] UiSettingsResource resource) + { + var dictionary = resource.GetType() + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null)); + + _configFileProvider.SaveConfigDictionary(dictionary); + _configService.SaveConfigDictionary(dictionary); + + return Accepted(resource.Id); + } + + protected override UiSettingsResource ToResource(IConfigService model) + { + return UiSettingsResourceMapper.ToResource(_configFileProvider, model); + } +} diff --git a/src/Sonarr.Api.V5/Settings/UiSettingsResource.cs b/src/Sonarr.Api.V5/Settings/UiSettingsResource.cs new file mode 100644 index 000000000..5abeb2fff --- /dev/null +++ b/src/Sonarr.Api.V5/Settings/UiSettingsResource.cs @@ -0,0 +1,44 @@ +using NzbDrone.Core.Configuration; +using Sonarr.Http.REST; + +namespace Sonarr.Api.V5.Config; + +public class UiSettingsResource : RestResource +{ + // Calendar + public int FirstDayOfWeek { get; set; } + public string? CalendarWeekColumnHeader { get; set; } + + // Dates + public string? ShortDateFormat { get; set; } + public string? LongDateFormat { get; set; } + public string? TimeFormat { get; set; } + public string? TimeZone { get; set; } + public bool ShowRelativeDates { get; set; } + + public bool EnableColorImpairedMode { get; set; } + public string? Theme { get; set; } + public int UiLanguage { get; set; } +} + +public static class UiSettingsResourceMapper +{ + public static UiSettingsResource ToResource(IConfigFileProvider config, IConfigService model) + { + return new UiSettingsResource + { + FirstDayOfWeek = model.FirstDayOfWeek, + CalendarWeekColumnHeader = model.CalendarWeekColumnHeader, + + ShortDateFormat = model.ShortDateFormat, + LongDateFormat = model.LongDateFormat, + TimeFormat = model.TimeFormat, + TimeZone = model.TimeZone, + ShowRelativeDates = model.ShowRelativeDates, + + EnableColorImpairedMode = model.EnableColorImpairedMode, + Theme = config.Theme, + UiLanguage = model.UILanguage + }; + } +}