import 'dart:io'; import 'package:injectable/injectable.dart'; import 'package:mnemo_cards_backend/api/di/injector.dart'; import 'package:mnemo_cards_backend/user/user_manager.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf_router/shelf_router.dart'; // import 'v2/ads_api_v2.dart'; // disabled import 'v2/admin_analytics_api_v2.dart'; import 'v2/admin_auth_api_v2.dart'; import 'v2/admin_cards_api_v2.dart'; import 'v2/admin_packs_api_v2.dart'; // import 'v2/admin_users_api_v2.dart'; // disabled import 'v2/auth_api_v2.dart'; import 'v2/discounts_api_v2.dart'; // import 'v2/games_api_v2.dart'; // disabled // import 'v2/packs_api_v2.dart'; // disabled // import 'v2/purchases_api_v2.dart'; // disabled import 'v2/promocodes_api_v2.dart'; import 'v2/subscriptions_api_v2.dart'; import 'v2/tasks_api_v2.dart'; import 'v2/tests_api_v2.dart'; // import 'v2/users_api_v2.dart'; // disabled // import 'v2/telegram_bot_api_v2.dart'; // disabled import 'v2/authorize_v2.dart'; import 'v2/telegram_bot_auth_middleware.dart'; import 'v2/jwt_service.dart'; import 'authorize/access_middleware.dart'; import 'package:mnemo_cards_backend/packs/pack_manager.dart'; import 'package:mnemo_cards_backend/statistics/session_tracking_middleware.dart'; import 'package:mnemo_cards_backend/statistics/session_tracker.dart'; @lazySingleton class MnemoShelf { final UserManager userManager; MnemoShelf(this.userManager); Future initV2() async { // Чтение переменных окружения вместо CLI аргументов final addressArg = Platform.environment['SERVER_ADDRESS']; final portArg = Platform.environment['PORT']; // Используем InternetAddress напрямую для правильной работы с bind final address = addressArg != null && addressArg != '0.0.0.0' ? InternetAddress(addressArg) : InternetAddress.anyIPv4; final port = int.tryParse(portArg ?? '') ?? 3000; // V2 APIs (new RESTful API with OAuth2/JWT) final v2Router = Router(); // Add routers individually to avoid type issues v2Router.mount('/', getIt.get().router); v2Router.mount('/', getIt.get().router); v2Router.mount('/', getIt.get().router); v2Router.mount('/', getIt.get().router); v2Router.mount('/', getIt.get().router); // v2Router.mount('/', getIt.get().router); // disabled v2Router.mount('/', getIt.get().router); // v2Router.mount('/', getIt.get().router); // disabled // v2Router.mount('/', getIt.get().router); // disabled v2Router.mount('/', getIt.get().router); v2Router.mount('/', getIt.get().router); v2Router.mount('/', getIt.get().router); v2Router.mount('/', getIt.get().handler); // Note: rootRouter is not used because we manually route to v1/v2 handlers // for different authentication middleware. Keeping for reference. // final rootRouter = Router() // ..mount('/', v1Router) // Legacy v1 APIs // ..mount('/api/v2', v2Router); // New v2 APIs // ..mount( // '/swagger', // SwaggerUI('public/open_api.yaml', title: 'Api'), // ) // ..mount('/', createStaticHandler('public')); // Configure a pipeline with proper CORS configuration final corsConfig = { 'Access-Control-Allow-Origin': '*', // Разрешаем все домены для гибкости 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS', 'Access-Control-Allow-Headers': 'Origin, Content-Type, Accept, Authorization, user_token, request_token, app_version, X-API-Key', 'Access-Control-Expose-Headers': 'Authorization', 'Access-Control-Max-Age': '86400', }; // CORS middleware that handles preflight requests Middleware corsMiddleware() { return (Handler innerHandler) { return (Request request) async { // Handle preflight requests if (request.method == 'OPTIONS') { return Response.ok('', headers: corsConfig); } // Process the request and add CORS headers to response final response = await innerHandler(request); return response.change(headers: { ...response.headers, ...corsConfig, }); }; }; } final v2Handler = Pipeline() .addMiddleware(corsMiddleware()) .addMiddleware(logRequests(logger: logger('v2'))) .addMiddleware(telegramBotAuthMiddleware()) .addMiddleware( authorizeV2(getIt.get(), getIt.get())) .addMiddleware(accessMiddleware( buildAccessService(getIt.get()), getIt.get())) .addMiddleware(sessionTrackingMiddleware(getIt.get())) .addHandler(v2Router); final rootRouter = Router() ..get('/health', (Request request) { // Простой эндпоинт для healthcheck - всегда отвечает OK // Не используем async для максимальной простоты и скорости return Response.ok( 'OK', headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsConfig, }, ); }) ..mount('/api/v2', v2Handler) ..all('/', (Request request) async { if (request.method == 'OPTIONS') { return Response.ok('', headers: corsConfig); } return Response.notFound('Not Found', headers: corsConfig); }); final handler = rootRouter.call; // Запуск HTTP сервера (HTTPS обрабатывается на уровне reverse proxy в Coolify) final server = await _createServer( handler, address, port, poweredByHeader: null, shared: true, ); print('Serving app at http://${server.address.address}:${server.port}'); print('Server listening on http://${server.address.address}:${server.port}'); } logger(String tag) => (String msg, bool isError) { if (isError) { print('[ERROR][$tag] $msg'); } else { print('[$tag] $msg'); } }; Future _createServer( Handler handler, InternetAddress address, int port, { int? backlog, bool shared = false, String? poweredByHeader = 'Dart with package:shelf', }) async { backlog ??= 0; final server = await HttpServer.bind( address, port, backlog: backlog, shared: shared, ); // Запускаем обработку запросов (неблокирующая операция) // serveRequests запускает обработку запросов в фоне и возвращает управление сразу shelf_io.serveRequests(server, handler, poweredByHeader: poweredByHeader); // Даем серверу небольшое время на инициализацию обработчика запросов await Future.delayed(const Duration(milliseconds: 100)); print('✅ Server bound and ready to accept connections on ${server.address.address}:${server.port}'); return server; } }