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");
/// Папка импорта (--mode import, m06; подпапки — атлеты).
public string ImportDir { get; set; } =
Path.Combine(AppContext.BaseDirectory, "data", "import");
/// Папка обработанных файлов импорта.
public string ImportArchiveDir { get; set; } =
Path.Combine(AppContext.BaseDirectory, "data", "import_done");
/// Учётка Garmin Connect владельца (--mode fetch, m07).
public string GarminLogin { get; set; } = string.Empty;
public string GarminPassword { get; set; } = string.Empty;
/// Атлет, на которого пишутся факты fetch (изоляция 🛡1).
public string GarminAthleteName { get; set; } = string.Empty;
/// Папка скачанных .fit (кэш по activityId).
public string GarminDownloadDir { get; set; } =
Path.Combine(AppContext.BaseDirectory, "data", "fit");
/// Точки входа Garmin (замена в тестах/прокси).
public string GarminBaseUrl { get; set; } = "https://connect.garmin.com";
/// Пороги красных флагов (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;
if (db.TryGetProperty("ImportDir", out var idir) && idir.ValueKind == JsonValueKind.String)
result.ImportDir = idir.GetString() ?? result.ImportDir;
if (db.TryGetProperty("ImportArchiveDir", out var adir) && adir.ValueKind == JsonValueKind.String)
result.ImportArchiveDir = adir.GetString() ?? result.ImportArchiveDir;
if (db.TryGetProperty("Garmin", out var garmin) && garmin.ValueKind == JsonValueKind.Object)
{
if (garmin.TryGetProperty("Login", out var gl)) result.GarminLogin = gl.GetString() ?? "";
if (garmin.TryGetProperty("Password", out var gp)) result.GarminPassword = gp.GetString() ?? "";
if (garmin.TryGetProperty("AthleteName", out var ga)) result.GarminAthleteName = ga.GetString() ?? "";
if (garmin.TryGetProperty("DownloadDir", out var gd) && gd.ValueKind == JsonValueKind.String)
result.GarminDownloadDir = gd.GetString() ?? result.GarminDownloadDir;
if (garmin.TryGetProperty("BaseUrl", out var gb) && gb.ValueKind == JsonValueKind.String)
result.GarminBaseUrl = gb.GetString() ?? result.GarminBaseUrl;
}
}
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;
}
}