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'; import 'v2/admin_users_api_v2.dart'; import 'v2/auth_api_v2.dart'; import 'v2/discounts_api_v2.dart'; import 'v2/games_api_v2.dart'; import 'v2/packs_api_v2.dart'; import 'v2/purchases_api_v2.dart'; 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'; import 'v2/authorize_v2.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(List args) async { final addressArg = args.indexOf('-a') == -1 ? null : args[args.indexOf('-a') + 1]; final portArg = args.indexOf('-p') == -1 ? null : args[args.indexOf('-p') + 1]; final address = addressArg ?? InternetAddress.anyIPv4.address; final port = int.tryParse(portArg ?? '') ?? 8443; final certs = args.contains('--certs') ? args[args.indexOf('--certs') + 1] : null; // null = HTTP without SSL final dualMode = args.contains('--dual'); // Запуск HTTP и HTTPS одновременно // V2 APIs (new RESTful API with OAuth2/JWT) final v2Routers = [ getIt.get().router, getIt.get().router, getIt.get().router, getIt.get().router, getIt.get().router, getIt.get().router, getIt.get().router, getIt.get().router, getIt.get().router, getIt.get().router, getIt.get().router, getIt.get().router, ]; final v2Router = v2Routers.fold( Router(), (router, child) => router..mount('/', child), ); // 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 // Поддерживаем как HTTP (разработка), так и HTTPS (продакшен) // Note: allowedOrigins is kept for reference but not used since we allow all origins // final allowedOrigins = [ // 'https://1592725-cf88967.twc1.net', // 'https://1592725-cf88967.twc1.net:443', // '5492281-cf88967.twc1.net', // '5492281-cf88967.twc1.net:8443' // 'http://localhost:*', // 'http://localhost:51717', // для разработки // 'http://localhost:3000', // для разработки // 'http://localhost:8000', // для разработки // 'http://127.0.0.1:8000', // для разработки // ]; 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', 'Access-Control-Expose-Headers': 'Authorization', 'Access-Control-Max-Age': '86400', }; // Добавляем HSTS только для HTTPS соединений if (certs != null) { corsConfig['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'; } // 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( authorizeV2(getIt.get(), getIt.get())) .addMiddleware(accessMiddleware( buildAccessService(getIt.get()), getIt.get())) .addMiddleware(sessionTrackingMiddleware(getIt.get())) .addHandler(v2Router); final rootRouter = Router() ..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; if (dualMode) { // Запуск HTTP и HTTPS серверов одновременно await _startDualServers(handler, address, port, certs); } else { // Обычный запуск одного сервера final server = await _createServer( handler, address, port, securityContext: certs != null ? getSecurityContext(certs) : null, poweredByHeader: null, shared: true, ); final protocol = certs != null ? 'https' : 'http'; print( 'Serving app at $protocol://${server.address.address}:${server.port}'); final url = '$protocol://${server.address.address}:${server.port}'; print('Server listening on $url'); if (certs != null) { print('HTTPS enabled with SSL certificates from: $certs'); print('Domain: 1592725-cf88967.twc1.net'); } } } Future _startDualServers( Handler handler, Object address, int port, String? certs) async { print('🚀 Starting DUAL mode: HTTP + HTTPS servers'); // HTTP сервер на указанном порту final httpServer = await _createServer( handler, address, port, securityContext: null, // HTTP без SSL poweredByHeader: null, shared: true, ); // HTTPS сервер на порту + 1 (например, 8443 -> 8081) final httpsPort = port + 1; final httpsServer = await _createServer( handler, address, httpsPort, securityContext: certs != null ? getSecurityContext(certs) : null, poweredByHeader: null, shared: true, ); print('✅ Both servers started successfully!'); print( '📡 HTTP server: http://${httpServer.address.address}:${httpServer.port}'); print( '🔒 HTTPS server: https://${httpsServer.address.address}:${httpsServer.port}'); if (certs != null) { print('🔐 HTTPS enabled with SSL certificates from: $certs'); print('🌐 Domain: 1592725-cf88967.twc1.net'); } } SecurityContext getSecurityContext(String path) { // Для Let's Encrypt используем fullchain.pem и privkey.pem // Для самоподписанных сертификатов используем server.crt и server.key String serverCert, key; if (path.contains('letsencrypt') || path.contains('/etc/letsencrypt/')) { // Let's Encrypt сертификаты serverCert = path + '/fullchain.pem'; key = path + '/privkey.pem'; } else { // Самоподписанные сертификаты serverCert = path.startsWith('/') ? path + '/server.crt' : Platform.script.resolve('$path/server.crt').toFilePath(); key = path.startsWith('/') ? path + '/server.key' : Platform.script.resolve('$path/server.key').toFilePath(); } return SecurityContext(withTrustedRoots: false) ..useCertificateChain(serverCert) ..usePrivateKey(key); } logger(String tag) => (String msg, bool isError) { if (isError) { print('[ERROR][$tag] $msg'); } else { print('[$tag] $msg'); } }; Future _createServer( Handler handler, Object address, int port, { SecurityContext? securityContext, int? backlog, bool shared = false, String? poweredByHeader = 'Dart with package:shelf', }) async { backlog ??= 0; var server = await (securityContext == null ? HttpServer.bind(address, port, backlog: backlog, shared: shared) : HttpServer.bindSecure( address, port, securityContext, backlog: backlog, shared: shared, requestClientCertificate: true, )); shelf_io.serveRequests(server, handler, poweredByHeader: poweredByHeader); return server; } }