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>
104 lines
3.2 KiB
Dart
104 lines
3.2 KiB
Dart
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 'auth_service.dart';
|
|
|
|
/// Auth module routes: register, login, refresh, logout, delete account.
|
|
class AuthRoutes {
|
|
final AuthService _authService;
|
|
|
|
AuthRoutes(this._authService);
|
|
|
|
Router get router {
|
|
final router = Router();
|
|
|
|
router.post('/register', _register);
|
|
router.post('/login', _login);
|
|
router.post('/refresh', _refresh);
|
|
router.post('/logout', _logout);
|
|
router.delete('/account', _deleteAccount);
|
|
|
|
return router;
|
|
}
|
|
|
|
// ── Handlers ────────────────────────────────────────────────────────
|
|
|
|
Future<Response> _register(Request request) async {
|
|
final body = jsonDecode(await request.readAsString()) as Map<String, dynamic>;
|
|
|
|
final email = body['email'] as String?;
|
|
final password = body['password'] as String?;
|
|
final displayName = body['display_name'] as String?;
|
|
|
|
if (email == null || password == null || displayName == null) {
|
|
throw ApiException.badRequest(
|
|
'Missing required fields: email, password, display_name',
|
|
);
|
|
}
|
|
|
|
if (password.length < 8) {
|
|
throw ApiException.badRequest('Password must be at least 8 characters');
|
|
}
|
|
|
|
final tokens = await _authService.register(
|
|
email: email,
|
|
password: password,
|
|
displayName: displayName,
|
|
);
|
|
|
|
return ApiResponse.created(tokens, message: 'Account created');
|
|
}
|
|
|
|
Future<Response> _login(Request request) async {
|
|
final body = jsonDecode(await request.readAsString()) as Map<String, dynamic>;
|
|
|
|
final email = body['email'] as String?;
|
|
final password = body['password'] as String?;
|
|
|
|
if (email == null || password == null) {
|
|
throw ApiException.badRequest('Missing required fields: email, password');
|
|
}
|
|
|
|
final tokens = await _authService.login(
|
|
email: email,
|
|
password: password,
|
|
);
|
|
|
|
return ApiResponse.success(tokens, message: 'Login successful');
|
|
}
|
|
|
|
Future<Response> _refresh(Request request) async {
|
|
final body = jsonDecode(await request.readAsString()) as Map<String, dynamic>;
|
|
|
|
final refreshToken = body['refresh_token'] as String?;
|
|
if (refreshToken == null) {
|
|
throw ApiException.badRequest('Missing required field: refresh_token');
|
|
}
|
|
|
|
final tokens = await _authService.refresh(refreshToken);
|
|
return ApiResponse.success(tokens);
|
|
}
|
|
|
|
Future<Response> _logout(Request request) async {
|
|
final body = jsonDecode(await request.readAsString()) as Map<String, dynamic>;
|
|
|
|
final refreshToken = body['refresh_token'] as String?;
|
|
if (refreshToken == null) {
|
|
throw ApiException.badRequest('Missing required field: refresh_token');
|
|
}
|
|
|
|
await _authService.logout(refreshToken);
|
|
return ApiResponse.success(null, message: 'Logged out successfully');
|
|
}
|
|
|
|
Future<Response> _deleteAccount(Request request) async {
|
|
final userId = request.context['userId'] as String;
|
|
await _authService.deleteAccount(userId);
|
|
return ApiResponse.success(null, message: 'Account deleted');
|
|
}
|
|
}
|