using Server.WatchBody.Web; using Server.WatchBody.Web.Config; using Server.WatchBody.Web.Db; using Server.WatchBody.Fetch; using Server.WatchBody.Import; using Server.WatchBody.Web.Services; // Точка входа сервера (m02; режимы — requirements 2.3, З-36): // --mode serve REST API (по умолчанию) // --mode migrate применение 2-db\migrations\ по журналу src.migration // --config путь к appsettings (иначе WATCHBODY_APPSETTINGS либо // appsettings.json рядом с исполняемым файлом) // Валидация конфига — фатальна: лог + stderr, код возврата 1; // migrate: успех — 0, сбой — 1. string? mode = null, configArg = null; for (var i = 0; i < args.Length - 1; i++) { switch (args[i]) { case "--mode": mode = args[i + 1]; break; case "--config": configArg = args[i + 1]; break; } } mode ??= "serve"; var configPath = configArg ?? Environment.GetEnvironmentVariable("WATCHBODY_APPSETTINGS") ?? Path.Combine(AppContext.BaseDirectory, "appsettings.json"); ServerConfig config; try { config = ServerConfig.Load(configPath); } catch (Exception e) { Console.Error.WriteLine($"конфиг {configPath} не читается: {e.Message}"); return 1; } if (mode == "migrate") { try { var applied = new MigrationRunner( new PostgresConnectionString(config.DbConnectionString)).Apply(config.MigrationsDir); Console.WriteLine($"migrate: применено миграций — {applied}"); return 0; } catch (Exception e) { Console.Error.WriteLine($"migrate: сбой — {e.Message}"); return 1; } } if (mode == "import") { try { var connectionStringImport = new PostgresConnectionString(config.DbConnectionString); Directory.CreateDirectory(config.ImportDir); Directory.CreateDirectory(config.ImportArchiveDir); var (processed, failed) = new ImportService(connectionStringImport).Run( config.ImportDir, config.ImportArchiveDir); Console.WriteLine($"import: обработано {processed}, сбоев {failed}"); return failed == 0 ? 0 : 2; } catch (Exception e) { Console.Error.WriteLine($"import: авария конфигурации — {e.Message}"); return 1; } } if (mode == "fetch") { if (config.GarminLogin.Length == 0 || config.GarminPassword.Length == 0 || config.GarminAthleteName.Length == 0) { Console.Error.WriteLine( "fetch: требуются Garmin.Login, Garmin.Password и Garmin.AthleteName"); return 1; } try { Directory.CreateDirectory(config.GarminDownloadDir); var client = new GarminClient( config.GarminLogin, config.GarminPassword, config.GarminBaseUrl); var (downloaded, skipped, failed) = new FitFetcher( new PostgresConnectionString(config.DbConnectionString), client, config.GarminDownloadDir, config.GarminAthleteName ).FetchNew(); Console.WriteLine($"fetch: скачано {downloaded}, пропущено {skipped}, сбоев {failed}"); return failed == 0 ? 0 : 2; } catch (GarminAuthException e) { Console.Error.WriteLine( $"fetch: логин Garmin не удался — {e.Message}; " + "fallback: папка импорта (--mode import)"); return 2; } catch (FetchException e) { Console.Error.WriteLine($"fetch: {e.Message}"); return 1; } catch (Exception e) { Console.Error.WriteLine($"fetch: авария — {e.Message}"); return 1; } } if (mode != "serve") { Console.Error.WriteLine($"неизвестный режим '{mode}' (serve|import|fetch|migrate)"); return 1; } var errors = config.Validate(); if (errors.Count > 0) { foreach (var error in errors) Console.Error.WriteLine($"конфиг: {error}"); return 1; } var connectionString = new PostgresConnectionString(config.DbConnectionString); try { WebAppStartup.Initialize(connectionString, config); } catch (Exception e) { Console.Error.WriteLine($"инициализация не удалась: {e.Message}"); return 1; } var builder = WebApplication.CreateBuilder(args); var app = WebAppStartup.Build(builder, config, connectionString); app.Run(); return 0; public partial class Program;