import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:logging/logging.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf_router/shelf_router.dart'; import 'package:focusflow_api/src/config/database.dart'; import 'package:focusflow_api/src/config/env.dart'; import 'package:focusflow_api/src/middleware/auth_middleware.dart'; import 'package:focusflow_api/src/middleware/cors_middleware.dart'; import 'package:focusflow_api/src/middleware/error_handler.dart'; import 'package:focusflow_api/src/middleware/logging_middleware.dart'; import 'package:focusflow_api/src/middleware/rate_limit_middleware.dart'; import 'package:focusflow_api/src/modules/auth/auth_routes.dart'; import 'package:focusflow_api/src/modules/auth/auth_service.dart'; import 'package:focusflow_api/src/modules/auth/password_hasher.dart'; import 'package:focusflow_api/src/modules/auth/token_service.dart'; import 'package:focusflow_api/src/modules/rewards/reward_engine.dart'; import 'package:focusflow_api/src/modules/rewards/reward_routes.dart'; import 'package:focusflow_api/src/modules/rewards/reward_service.dart'; import 'package:focusflow_api/src/modules/rooms/room_routes.dart'; import 'package:focusflow_api/src/modules/rooms/room_service.dart'; import 'package:focusflow_api/src/modules/streaks/streak_repository.dart'; import 'package:focusflow_api/src/modules/streaks/streak_routes.dart'; import 'package:focusflow_api/src/modules/streaks/streak_service.dart'; import 'package:focusflow_api/src/modules/sync/sync_routes.dart'; import 'package:focusflow_api/src/modules/sync/sync_service.dart'; import 'package:focusflow_api/src/modules/tasks/dopamine_scorer.dart'; import 'package:focusflow_api/src/modules/tasks/task_repository.dart'; import 'package:focusflow_api/src/modules/tasks/task_routes.dart'; import 'package:focusflow_api/src/modules/tasks/task_service.dart'; import 'package:focusflow_api/src/modules/time/time_routes.dart'; import 'package:focusflow_api/src/modules/time/time_service.dart'; final _log = Logger('Server'); Future main() async { // ── Logging ───────────────────────────────────────────────────────── Logger.root.level = Level.ALL; Logger.root.onRecord.listen((record) { // ignore: avoid_print print( '${record.time} [${record.level.name}] ${record.loggerName}: ' '${record.message}', ); }); // ── Environment ───────────────────────────────────────────────────── Env.init(); _log.info('Starting FocusFlow API (${Env.appEnv})'); // ── Database ──────────────────────────────────────────────────────── await Database.init(); _log.info('Database pool opened'); // ── Services (dependency injection) ───────────────────────────────── final tokenService = TokenService(); final passwordHasher = PasswordHasher(); final authService = AuthService( hasher: passwordHasher, tokenService: tokenService, ); final rewardEngine = RewardEngine(); final dopamineScorer = DopamineScorer(); final taskRepository = TaskRepository(); final taskService = TaskService( repository: taskRepository, scorer: dopamineScorer, rewardEngine: rewardEngine, ); final streakRepository = StreakRepository(); final streakService = StreakService(repository: streakRepository); final rewardService = RewardService(engine: rewardEngine); final timeService = TimeService(); final syncService = SyncService(); final roomService = RoomService(); // ── Route modules ─────────────────────────────────────────────────── final authRoutes = AuthRoutes(authService); final taskRoutes = TaskRoutes(taskService); final streakRoutes = StreakRoutes(streakService); final rewardRoutes = RewardRoutes(rewardService); final timeRoutes = TimeRoutes(timeService); final syncRoutes = SyncRoutes(syncService); final roomRoutes = RoomRoutes(roomService); // ── Router ────────────────────────────────────────────────────────── final app = Router(); // Health check (public) app.get('/health', (Request request) { return Response.ok( jsonEncode({'status': 'ok'}), headers: {'Content-Type': 'application/json'}, ); }); // Mount module routers app.mount('/api/v1/auth/', authRoutes.router.call); app.mount('/api/v1/tasks/', taskRoutes.router.call); app.mount('/api/v1/streaks/', streakRoutes.router.call); app.mount('/api/v1/rewards/', rewardRoutes.router.call); app.mount('/api/v1/time/', timeRoutes.router.call); app.mount('/api/v1/sync/', syncRoutes.router.call); app.mount('/api/v1/rooms/', roomRoutes.router.call); // ── Pipeline ──────────────────────────────────────────────────────── final handler = const Pipeline() .addMiddleware(corsMiddleware()) .addMiddleware(loggingMiddleware()) .addMiddleware(errorHandlerMiddleware()) .addMiddleware(rateLimitMiddleware()) .addMiddleware(authMiddleware(tokenService)) .addHandler(app.call); // ── Start server ──────────────────────────────────────────────────── final port = Env.port; final server = await shelf_io.serve(handler, InternetAddress.anyIPv4, port); _log.info('Listening on http://${server.address.host}:${server.port}'); // ── Graceful shutdown ─────────────────────────────────────────────── late final StreamSubscription sigintSub; late final StreamSubscription sigtermSub; Future shutdown() async { _log.info('Shutting down...'); await server.close(force: false); await Database.close(); _log.info('Server stopped'); await sigintSub.cancel(); await sigtermSub.cancel(); exit(0); } sigintSub = ProcessSignal.sigint.watch().listen((_) => shutdown()); sigtermSub = ProcessSignal.sigterm.watch().listen((_) => shutdown()); }