Files
focusflow_api/bin/server.dart
Oracle Public Cloud User 8958455a12 Initial scaffold: FocusFlow ADHD Task Manager backend
Dart Shelf API with modules: auth (JWT + PBKDF2), tasks (CRUD + dopamine scorer),
streaks (forgiveness + freeze), rewards (variable reward engine), time perception,
sync (offline-first push/pull), rooms (body doubling placeholder).
Includes DB migration (001_initial_schema.sql) and Docker Compose.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 15:53:40 +00:00

145 lines
6.6 KiB
Dart

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<void> 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<ProcessSignal> sigintSub;
late final StreamSubscription<ProcessSignal> sigtermSub;
Future<void> 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());
}