using System.Text.Json; using Server.WatchBody.Core.Metrics; using Server.WatchBody.Core.Programs; namespace Server.WatchBody.Web.Config; /// Атлет из конфига (Api.Athletes). public class ConfigAthlete { public string Name { get; set; } = string.Empty; public string ApiKey { get; set; } = string.Empty; } /// /// Конфигурация сервера (appsettings.json; tech-requirements 4.2). /// Ошибка валидации — фатальна: лог + stderr, код возврата 1. /// public class ServerConfig { public string ApiKey { get; set; } = string.Empty; public List Athletes { get; set; } = []; public string DbConnectionString { get; set; } = string.Empty; /// Каталог миграций (--mode migrate, З-36); по умолчанию — /// migrations рядом с исполняемым файлом. public string MigrationsDir { get; set; } = Path.Combine(AppContext.BaseDirectory, "migrations"); /// Пороги красных флагов (Safety.*, Р3-А; MetricsEngine). public MetricsConfig Safety { get; set; } = new(); /// Пороги правил прогрессии/делоада/перерывов (Progression.*, m05). public ProgressionConfig Progression { get; set; } = new(); /// Загрузка из json-файла (путь — из WATCHBODY_APPSETTINGS либо appsettings.json рядом). public static ServerConfig Load(string path) { var json = File.ReadAllText(path); using var doc = JsonDocument.Parse(json); var root = doc.RootElement; var result = new ServerConfig(); if (root.TryGetProperty("Api", out var api)) { if (api.TryGetProperty("Key", out var key)) result.ApiKey = key.GetString() ?? string.Empty; if (api.TryGetProperty("Athletes", out var athletes) && athletes.ValueKind == JsonValueKind.Array) { foreach (var a in athletes.EnumerateArray()) { result.Athletes.Add(new ConfigAthlete { Name = a.TryGetProperty("name", out var n) ? n.GetString() ?? string.Empty : string.Empty, ApiKey = a.TryGetProperty("apiKey", out var k) ? k.GetString() ?? string.Empty : string.Empty, }); } } } if (root.TryGetProperty("Db", out var db)) { if (db.TryGetProperty("ConnectionString", out var cs)) result.DbConnectionString = cs.GetString() ?? string.Empty; if (db.TryGetProperty("MigrationsDir", out var md) && md.ValueKind == JsonValueKind.String) result.MigrationsDir = md.GetString() ?? result.MigrationsDir; } result.Safety = MetricsConfig.Load(root); result.Progression = ProgressionConfig.Load(root); return result; } /// Список ошибок валидации (spec 01-config-bootstrap). public List Validate() { var errors = new List(); if (string.IsNullOrWhiteSpace(ApiKey)) errors.Add("Api.Key пуст"); var seen = new HashSet(StringComparer.Ordinal); for (var i = 0; i < Athletes.Count; i++) { var a = Athletes[i]; if (string.IsNullOrWhiteSpace(a.Name)) errors.Add($"Api.Athletes[{i}].name пуст"); if (string.IsNullOrWhiteSpace(a.ApiKey)) errors.Add($"Api.Athletes[{i}].apiKey пуст"); else if (a.ApiKey == ApiKey) errors.Add($"Api.Athletes[{i}].apiKey совпадает с Api.Key"); else if (!seen.Add(a.ApiKey)) errors.Add($"дубликат apiKey у Api.Athletes[{i}]"); } if (string.IsNullOrWhiteSpace(DbConnectionString)) errors.Add("Db.ConnectionString пуст"); errors.AddRange(Safety.Validate()); errors.AddRange(Progression.Validate()); return errors; } }