Files
OTSSignsOrchestrator/OTSSignsOrchestrator.Core/Services/SettingsService.cs
Matt Batchelder 45c94b6536
Some checks failed
Build and Publish Docker Image / build-and-push (push) Has been cancelled
feat: Add main application views and structure
- Implemented CreateInstanceView for creating new instances.
- Added HostsView for managing SSH hosts with CRUD operations.
- Created InstancesView for displaying and managing instances.
- Developed LogsView for viewing operation logs.
- Introduced SecretsView for managing secrets associated with hosts.
- Established SettingsView for configuring application settings.
- Created MainWindow as the main application window with navigation.
- Added app manifest and configuration files for logging and settings.
2026-02-18 10:43:27 -05:00

148 lines
6.0 KiB
C#

using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using OTSSignsOrchestrator.Core.Data;
using OTSSignsOrchestrator.Core.Models.Entities;
namespace OTSSignsOrchestrator.Core.Services;
/// <summary>
/// Reads and writes typed application settings from the AppSetting table.
/// Sensitive values are encrypted/decrypted transparently via DataProtection.
/// </summary>
public class SettingsService
{
private readonly XiboContext _db;
private readonly IDataProtector _protector;
private readonly ILogger<SettingsService> _logger;
// ── Category constants ─────────────────────────────────────────────────
public const string CatGit = "Git";
public const string CatMySql = "MySql";
public const string CatSmtp = "Smtp";
public const string CatPangolin = "Pangolin";
public const string CatCifs = "Cifs";
public const string CatDefaults = "Defaults";
// ── Key constants ──────────────────────────────────────────────────────
// Git
public const string GitRepoUrl = "Git.RepoUrl";
public const string GitRepoPat = "Git.RepoPat";
// MySQL Admin
public const string MySqlHost = "MySql.Host";
public const string MySqlPort = "MySql.Port";
public const string MySqlAdminUser = "MySql.AdminUser";
public const string MySqlAdminPassword = "MySql.AdminPassword";
// SMTP
public const string SmtpServer = "Smtp.Server";
public const string SmtpPort = "Smtp.Port";
public const string SmtpUsername = "Smtp.Username";
public const string SmtpPassword = "Smtp.Password";
public const string SmtpUseTls = "Smtp.UseTls";
public const string SmtpUseStartTls = "Smtp.UseStartTls";
public const string SmtpRewriteDomain = "Smtp.RewriteDomain";
public const string SmtpHostname = "Smtp.Hostname";
public const string SmtpFromLineOverride = "Smtp.FromLineOverride";
// Pangolin
public const string PangolinEndpoint = "Pangolin.Endpoint";
// CIFS
public const string CifsServer = "Cifs.Server";
public const string CifsShareBasePath = "Cifs.ShareBasePath";
public const string CifsUsername = "Cifs.Username";
public const string CifsPassword = "Cifs.Password";
public const string CifsOptions = "Cifs.Options";
// Instance Defaults
public const string DefaultCmsImage = "Defaults.CmsImage";
public const string DefaultNewtImage = "Defaults.NewtImage";
public const string DefaultMemcachedImage = "Defaults.MemcachedImage";
public const string DefaultQuickChartImage = "Defaults.QuickChartImage";
public const string DefaultCmsServerNameTemplate = "Defaults.CmsServerNameTemplate";
public const string DefaultThemeHostPath = "Defaults.ThemeHostPath";
public const string DefaultMySqlDbTemplate = "Defaults.MySqlDbTemplate";
public const string DefaultMySqlUserTemplate = "Defaults.MySqlUserTemplate";
public const string DefaultPhpPostMaxSize = "Defaults.PhpPostMaxSize";
public const string DefaultPhpUploadMaxFilesize = "Defaults.PhpUploadMaxFilesize";
public const string DefaultPhpMaxExecutionTime = "Defaults.PhpMaxExecutionTime";
public SettingsService(
XiboContext db,
IDataProtectionProvider dataProtection,
ILogger<SettingsService> logger)
{
_db = db;
_protector = dataProtection.CreateProtector("OTSSignsOrchestrator.Settings");
_logger = logger;
}
/// <summary>Get a single setting value, decrypting if sensitive.</summary>
public async Task<string?> GetAsync(string key)
{
var setting = await _db.AppSettings.FindAsync(key);
if (setting == null) return null;
return setting.IsSensitive && setting.Value != null
? Unprotect(setting.Value)
: setting.Value;
}
/// <summary>Get a setting with a fallback default.</summary>
public async Task<string> GetAsync(string key, string defaultValue)
=> await GetAsync(key) ?? defaultValue;
/// <summary>Set a single setting, encrypting if sensitive.</summary>
public async Task SetAsync(string key, string? value, string category, bool isSensitive = false)
{
var setting = await _db.AppSettings.FindAsync(key);
if (setting == null)
{
setting = new AppSetting { Key = key, Category = category, IsSensitive = isSensitive };
_db.AppSettings.Add(setting);
}
setting.Value = isSensitive && value != null ? _protector.Protect(value) : value;
setting.IsSensitive = isSensitive;
setting.Category = category;
setting.UpdatedAt = DateTime.UtcNow;
}
/// <summary>Save multiple settings in a single transaction.</summary>
public async Task SaveManyAsync(IEnumerable<(string Key, string? Value, string Category, bool IsSensitive)> settings)
{
foreach (var (key, value, category, isSensitive) in settings)
await SetAsync(key, value, category, isSensitive);
await _db.SaveChangesAsync();
_logger.LogInformation("Saved {Count} setting(s)",
settings is ICollection<(string, string?, string, bool)> c ? c.Count : -1);
}
/// <summary>Get all settings in a category (values decrypted).</summary>
public async Task<Dictionary<string, string?>> GetCategoryAsync(string category)
{
var settings = await _db.AppSettings
.Where(s => s.Category == category)
.ToListAsync();
return settings.ToDictionary(
s => s.Key,
s => s.IsSensitive && s.Value != null ? Unprotect(s.Value) : s.Value);
}
private string? Unprotect(string protectedValue)
{
try
{
return _protector.Unprotect(protectedValue);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to unprotect setting value — returning null");
return null;
}
}
}