VEU.WathBody/3-app/2-cli/Server.WatchBody.Web/Config/ServerConfig.cs

137 lines
7.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text.Json;
using Server.WatchBody.Core.Metrics;
using Server.WatchBody.Core.Programs;
namespace Server.WatchBody.Web.Config;
/// <summary>Атлет из конфига (Api.Athletes).</summary>
public class ConfigAthlete
{
public string Name { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
}
/// <summary>
/// Конфигурация сервера (appsettings.json; tech-requirements 4.2).
/// Ошибка валидации — фатальна: лог + stderr, код возврата 1.
/// </summary>
public class ServerConfig
{
public string ApiKey { get; set; } = string.Empty;
public List<ConfigAthlete> Athletes { get; set; } = [];
public string DbConnectionString { get; set; } = string.Empty;
/// <summary>Каталог миграций (--mode migrate, З-36); по умолчанию —
/// migrations рядом с исполняемым файлом.</summary>
public string MigrationsDir { get; set; } =
Path.Combine(AppContext.BaseDirectory, "migrations");
/// <summary>Папка импорта (--mode import, m06; подпапки — атлеты).</summary>
public string ImportDir { get; set; } =
Path.Combine(AppContext.BaseDirectory, "data", "import");
/// <summary>Папка обработанных файлов импорта.</summary>
public string ImportArchiveDir { get; set; } =
Path.Combine(AppContext.BaseDirectory, "data", "import_done");
/// <summary>Учётка владельца консоли (m08); Login пуст — консоль отключена.</summary>
public string AdminLogin { get; set; } = string.Empty;
public string AdminPasswordHash { get; set; } = string.Empty;
/// <summary>Учётка Garmin Connect владельца (--mode fetch, m07).</summary>
public string GarminLogin { get; set; } = string.Empty;
public string GarminPassword { get; set; } = string.Empty;
/// <summary>Атлет, на которого пишутся факты fetch (изоляция 🛡1).</summary>
public string GarminAthleteName { get; set; } = string.Empty;
/// <summary>Папка скачанных .fit (кэш по activityId).</summary>
public string GarminDownloadDir { get; set; } =
Path.Combine(AppContext.BaseDirectory, "data", "fit");
/// <summary>Точки входа Garmin (замена в тестах/прокси).</summary>
public string GarminBaseUrl { get; set; } = "https://connect.garmin.com";
/// <summary>Пороги красных флагов (Safety.*, Р3-А; MetricsEngine).</summary>
public MetricsConfig Safety { get; set; } = new();
/// <summary>Пороги правил прогрессии/делоада/перерывов (Progression.*, m05).</summary>
public ProgressionConfig Progression { get; set; } = new();
/// <summary>Загрузка из json-файла (путь — из WATCHBODY_APPSETTINGS либо appsettings.json рядом).</summary>
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("Admin", out var admin) && admin.ValueKind == JsonValueKind.Object)
{
if (admin.TryGetProperty("Login", out var al)) result.AdminLogin = al.GetString() ?? "";
if (admin.TryGetProperty("PasswordHash", out var aph)) result.AdminPasswordHash = aph.GetString() ?? "";
}
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;
}
/// <summary>Список ошибок валидации (spec 01-config-bootstrap).</summary>
public List<string> Validate()
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(ApiKey))
errors.Add("Api.Key пуст");
var seen = new HashSet<string>(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;
}
}