137 lines
7.1 KiB
C#
137 lines
7.1 KiB
C#
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;
|
||
}
|
||
}
|