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:
70
lib/src/middleware/error_handler.dart
Normal file
70
lib/src/middleware/error_handler.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
|
||||
final _log = Logger('ErrorHandler');
|
||||
|
||||
// ── ApiException ──────────────────────────────────────────────────────
|
||||
|
||||
/// Typed exception that maps directly to an HTTP status code.
|
||||
class ApiException implements Exception {
|
||||
final int statusCode;
|
||||
final String message;
|
||||
final dynamic errors;
|
||||
|
||||
const ApiException(this.statusCode, this.message, {this.errors});
|
||||
|
||||
// Factory constructors for common HTTP errors
|
||||
factory ApiException.badRequest(String message, {dynamic errors}) =>
|
||||
ApiException(400, message, errors: errors);
|
||||
|
||||
factory ApiException.unauthorized([String message = 'Unauthorized']) =>
|
||||
ApiException(401, message);
|
||||
|
||||
factory ApiException.forbidden([String message = 'Forbidden']) =>
|
||||
ApiException(403, message);
|
||||
|
||||
factory ApiException.notFound([String message = 'Resource not found']) =>
|
||||
ApiException(404, message);
|
||||
|
||||
factory ApiException.conflict([String message = 'Conflict']) =>
|
||||
ApiException(409, message);
|
||||
|
||||
factory ApiException.tooManyRequests(
|
||||
[String message = 'Too many requests']) =>
|
||||
ApiException(429, message);
|
||||
|
||||
@override
|
||||
String toString() => 'ApiException($statusCode): $message';
|
||||
}
|
||||
|
||||
// ── Middleware ─────────────────────────────────────────────────────────
|
||||
|
||||
/// Catches all exceptions and returns a consistent JSON error response.
|
||||
Middleware errorHandlerMiddleware() {
|
||||
return (Handler innerHandler) {
|
||||
return (Request request) async {
|
||||
try {
|
||||
return await innerHandler(request);
|
||||
} on ApiException catch (e) {
|
||||
return _jsonError(e.statusCode, e.message, errors: e.errors);
|
||||
} catch (e, st) {
|
||||
_log.severe('Unhandled exception', e, st);
|
||||
return _jsonError(500, 'Internal server error');
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Response _jsonError(int statusCode, String message, {dynamic errors}) {
|
||||
return Response(
|
||||
statusCode,
|
||||
body: jsonEncode({
|
||||
'success': false,
|
||||
'message': message,
|
||||
if (errors != null) 'errors': errors,
|
||||
}),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user