using System.Text.Json; using Microsoft.Extensions.Configuration; using UtopiaCanteenSystem.Data; using UtopiaCanteenSystem.Models; using UtopiaCanteenSystem.Services; namespace UtopiaCanteen.BackendService; /// /// Backend service configuration: appsettings.json + persisted JSON under LocalApplicationData. /// public sealed class BackendHostConfigService : IConfigService { private readonly IConfiguration _configuration; private readonly string _persistedPath; private PersistedConfig _persisted; public BackendHostConfigService(IConfiguration configuration) { _configuration = configuration; _persistedPath = Path.Combine(DatabasePath.GetAppDataFolder(), "backend-settings.json"); _persisted = LoadPersisted(); MergeFromAppSettingsIfNeeded(); } public string GetListenUrls() => _configuration["ListenUrls"] ?? "http://0.0.0.0:5000"; public string GetSyncApiEndpoint() => _configuration["SyncApiEndpoint"] ?? _persisted.SyncApiEndpoint ?? string.Empty; public void SetSyncApiEndpoint(string endpoint) { _persisted.SyncApiEndpoint = endpoint ?? string.Empty; SavePersisted(); } public bool GetSyncServiceEnabled() => _configuration.GetValue("SyncServiceEnabled", _persisted.SyncServiceEnabled); public void SetSyncServiceEnabled(bool enabled) { _persisted.SyncServiceEnabled = enabled; SavePersisted(); } public bool GetScannerConnected() => false; public void SetScannerConnected(bool connected) { } public int GetScanIntervalDays() => 0; public void SetScanIntervalDays(int value) { } public int GetScanIntervalHours() => 0; public void SetScanIntervalHours(int value) { } public int GetScanIntervalMinutes() => 1; public void SetScanIntervalMinutes(int value) { } public int GetScanIntervalSeconds() => 0; public void SetScanIntervalSeconds(int value) { } public TimeSpan GetScanInterval() => TimeSpan.FromMinutes(1); public string GetAdminCardId() => "ADMIN"; public void SetAdminCardId(string cardId) { } public string GetSiteId() => _persisted.SiteId ?? "02"; public void SetSiteId(string siteId) { _persisted.SiteId = siteId ?? string.Empty; SavePersisted(); } public void ApplyLocationSiteIdFromAuth(string? locationSiteId) { } public string GetDeviceId() { if (!string.IsNullOrWhiteSpace(_persisted.DeviceId)) return _persisted.DeviceId.Trim(); _persisted.DeviceId = ResolveMachineDeviceId(); SavePersisted(); return _persisted.DeviceId; } public void SetDeviceId(string deviceId) { _persisted.DeviceId = string.IsNullOrWhiteSpace(deviceId) ? ResolveMachineDeviceId() : deviceId.Trim(); SavePersisted(); } public bool GetRememberAdminCredentials() => false; public void SetRememberAdminCredentials(bool remember) { } public string GetSavedAdminUsername() => string.Empty; public void SetSavedAdminUsername(string username) { } public string GetSavedAdminPassword() => string.Empty; public void SetSavedAdminPassword(string password) { } public string GetMySqlConnectionString() => FirstNonEmpty(_configuration["MySqlConnectionString"], _persisted.MySqlConnectionString); public void SetMySqlConnectionString(string connectionString) { _persisted.MySqlConnectionString = connectionString ?? string.Empty; SavePersisted(); } public string GetHrmsLookupConnectionString() => ConfigConnectionHelper.GetHrmsOrProductionConnectionString( FirstNonEmpty(_configuration["HrmsLookupConnectionString"], _persisted.HrmsLookupConnectionString), GetMySqlConnectionString()); public void SetHrmsLookupConnectionString(string connectionString) { _persisted.HrmsLookupConnectionString = connectionString ?? string.Empty; SavePersisted(); } public DateTime? GetLastEmployeeRfidCacheSyncUtc() => _persisted.LastEmployeeRfidCacheSyncUtc; public void SetLastEmployeeRfidCacheSyncUtc(DateTime utc) { _persisted.LastEmployeeRfidCacheSyncUtc = utc; SavePersisted(); } public DateTime? GetLastMealMenuCacheSyncUtc() => _persisted.LastMealMenuCacheSyncUtc; public void SetLastMealMenuCacheSyncUtc(DateTime utc) { _persisted.LastMealMenuCacheSyncUtc = utc; SavePersisted(); } public AppMode GetAppMode() => AppMode.Server; public void SetAppMode(AppMode mode) { } public string GetCentralServerBaseUrl() => string.Empty; public void SetCentralServerBaseUrl(string url) { } public string GetBackendBaseUrl() => DeriveLocalhostApiBaseUrl(GetListenUrls()); public string GetLocalServerListenUrls() => GetListenUrls(); public void SetLocalServerListenUrls(string urls) { } private static string ResolveMachineDeviceId() { var machineName = Environment.MachineName; return string.IsNullOrWhiteSpace(machineName) ? Guid.NewGuid().ToString("N") : machineName.Trim(); } private void MergeFromAppSettingsIfNeeded() { var mysql = _configuration["MySqlConnectionString"]; if (!string.IsNullOrWhiteSpace(mysql) && string.IsNullOrWhiteSpace(_persisted.MySqlConnectionString)) _persisted.MySqlConnectionString = mysql; var hrms = _configuration["HrmsLookupConnectionString"]; if (!string.IsNullOrWhiteSpace(hrms) && string.IsNullOrWhiteSpace(_persisted.HrmsLookupConnectionString)) _persisted.HrmsLookupConnectionString = hrms; SavePersisted(); } private static string FirstNonEmpty(params string?[] values) { foreach (var v in values) { if (!string.IsNullOrWhiteSpace(v)) return v!; } return string.Empty; } private static string DeriveLocalhostApiBaseUrl(string listenUrls) { var first = listenUrls .Split(new[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .FirstOrDefault() ?? "http://0.0.0.0:5000"; first = first.TrimEnd('/'); if (first.Contains("0.0.0.0", StringComparison.Ordinal)) first = first.Replace("0.0.0.0", "localhost", StringComparison.Ordinal); if (first.Contains('+')) first = first.Replace("+", "localhost", StringComparison.Ordinal); return first; } private PersistedConfig LoadPersisted() { try { if (!File.Exists(_persistedPath)) return new PersistedConfig(); var json = File.ReadAllText(_persistedPath); return JsonSerializer.Deserialize(json) ?? new PersistedConfig(); } catch { return new PersistedConfig(); } } private void SavePersisted() { try { var json = JsonSerializer.Serialize(_persisted, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(_persistedPath, json); } catch (Exception ex) { Logger.Log(ex, "BackendHostConfigService.SavePersisted"); } } private sealed class PersistedConfig { public string? SyncApiEndpoint { get; set; } public bool SyncServiceEnabled { get; set; } = true; public string? MySqlConnectionString { get; set; } public string? HrmsLookupConnectionString { get; set; } public DateTime? LastEmployeeRfidCacheSyncUtc { get; set; } public DateTime? LastMealMenuCacheSyncUtc { get; set; } public string? SiteId { get; set; } public string? DeviceId { get; set; } } }