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>
This commit is contained in:
Oracle Public Cloud User
2026-03-04 15:53:40 +00:00
commit 8958455a12
44 changed files with 4625 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
import 'dart:convert';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import '../../middleware/error_handler.dart';
import '../../shared/api_response.dart';
import '../../shared/pagination.dart';
import 'reward_service.dart';
/// Reward module route definitions.
class RewardRoutes {
final RewardService _service;
RewardRoutes(this._service);
Router get router {
final router = Router();
router.post('/generate', _generate);
router.get('/history', _history);
return router;
}
// ── Handlers ────────────────────────────────────────────────────────
Future<Response> _generate(Request request) async {
final userId = request.context['userId'] as String;
final body =
jsonDecode(await request.readAsString()) as Map<String, dynamic>;
final taskId = body['task_id'] as String?;
final taskEnergyLevel = body['task_energy_level'] as int?;
if (taskId == null || taskEnergyLevel == null) {
throw ApiException.badRequest(
'Missing required fields: task_id, task_energy_level',
);
}
final reward = await _service.generateReward(
userId: userId,
taskId: taskId,
taskEnergyLevel: taskEnergyLevel,
currentStreak: (body['current_streak'] as int?) ?? 0,
);
return ApiResponse.created(reward, message: 'Reward generated!');
}
Future<Response> _history(Request request) async {
final userId = request.context['userId'] as String;
final paging = Pagination.fromRequest(request);
final rewards = await _service.getHistory(
userId,
limit: paging.perPage,
offset: paging.offset,
);
return ApiResponse.success(rewards);
}
}