using System; using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; using Dapper; namespace MiniWiki.Data.Settings; public class SettingsDatabaseRepository(IDbConnectionFactory connectionFactory) : ISettingsRepository { public async Task?> GetByKeyAsync(string key) { const string query = "SELECT key, value, created_at, updated_at, display_name, description FROM settings WHERE key = @key"; using var connection = connectionFactory.CreateConnection(); var setting = await connection.QuerySingleOrDefaultAsync(query, new { key }); if (setting is null) return null; return new Setting { Key = setting.Key, Value = ParseValue(setting.Value), DisplayName = setting.DisplayName, Description = setting.Description, CreatedAt = setting.CreatedAt, UpdatedAt = setting.UpdatedAt }; } public async Task UpdateAsync(string key, T value) { const string statement = """ UPDATE settings SET value = @value, updated_at = @updatedAt WHERE key = @key """; using var connection = connectionFactory.CreateConnection(); await connection.ExecuteAsync(statement, new { key, value = SerializeValue(value), updatedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }); } public async Task> GetAllSettingsAsync() { const string query = "SELECT key, value, created_at, updated_at, display_name, description FROM settings"; using var connection = connectionFactory.CreateConnection(); return await connection.QueryAsync(query); } private static T ParseValue(string value) { var targetType = typeof(T); // if T is nullable, we need to get the underlying type var conversionType = Nullable.GetUnderlyingType(targetType) ?? targetType; object parsedValue; if (conversionType == typeof(string)) { parsedValue = value; } else if (conversionType == typeof(bool)) { if (bool.TryParse(value, out var boolValue)) parsedValue = boolValue; else parsedValue = value switch { "1" => true, "0" => false, _ => throw new FormatException($"Could not parse '{value}' as a boolean") }; } else { parsedValue = Convert.ChangeType(value, conversionType, CultureInfo.InvariantCulture); } return (T)parsedValue; } private static string SerializeValue(T value) { if (value is null) return string.Empty; return value switch { bool boolValue => boolValue ? "true" : "false", IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture), _ => value.ToString() ?? string.Empty }; } }