AgendaTasks/lib/features/tasks/data/datasources/task_remote_datasource.dart
m3mo d8164be49a Add user authentication to Flutter frontend
- Create auth feature with Clean Architecture (domain/data/presentation)
- Add login and register pages with form validation
- Implement secure token storage with flutter_secure_storage
- Create AuthenticatedClient for automatic Bearer token headers
- Add AuthViewModel for global auth state management
- Update router with auth guards (redirect to login if not authenticated)
- Add logout option to settings page
- Update TaskRemoteDataSource to use authenticated client
- Add auth-related localization strings (EN/DE)
2026-02-02 22:58:07 +01:00

221 lines
6.3 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import '../../../../core/errors/exceptions.dart';
import '../../../../core/logging/app_logger.dart';
import '../models/task_model.dart';
abstract class TaskRemoteDataSource {
Future<List<TaskModel>> getTasksByDate(String date, {String? status});
Future<TaskModel> getTaskById(String id);
Future<TaskModel> createTask(TaskModel task);
Future<TaskModel> updateTask(TaskModel task);
Future<void> deleteTask(String id);
Future<TaskModel> toggleTaskStatus(String id);
Future<TaskModel> rescheduleTask(String id, String targetDate);
}
class TaskRemoteDataSourceImpl implements TaskRemoteDataSource {
final AppLogger logger;
final http.Client _client;
static const String _baseUrl = String.fromEnvironment(
'API_URL',
defaultValue: 'http://localhost:8000',
);
TaskRemoteDataSourceImpl({
required this.logger,
required http.Client client,
}) : _client = client;
Map<String, String> get _headers => {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
@override
Future<List<TaskModel>> getTasksByDate(String date, {String? status}) async {
final queryParams = {'date': date};
if (status != null && status != 'all') {
queryParams['status'] = status;
}
final uri = Uri.parse('$_baseUrl/tasks').replace(queryParameters: queryParams);
logger.info('GET $uri');
try {
final response = await _client.get(uri, headers: _headers);
logger.debug('Response: ${response.statusCode}');
if (response.statusCode == 200) {
final List<dynamic> jsonList = json.decode(response.body);
return jsonList.map((json) => TaskModel.fromJson(json)).toList();
} else {
throw ServerException(
message: 'Failed to load tasks',
statusCode: response.statusCode,
);
}
} on SocketException {
throw const NetworkException();
}
}
@override
Future<TaskModel> getTaskById(String id) async {
final uri = Uri.parse('$_baseUrl/tasks/$id');
logger.info('GET $uri');
try {
final response = await _client.get(uri, headers: _headers);
if (response.statusCode == 200) {
return TaskModel.fromJson(json.decode(response.body));
} else if (response.statusCode == 404) {
throw const NotFoundException(message: 'Task not found');
} else {
throw ServerException(
message: 'Failed to load task',
statusCode: response.statusCode,
);
}
} on SocketException {
throw const NetworkException();
}
}
@override
Future<TaskModel> createTask(TaskModel task) async {
final uri = Uri.parse('$_baseUrl/tasks');
logger.info('POST $uri');
try {
final response = await _client.post(
uri,
headers: _headers,
body: json.encode(task.toCreateJson()),
);
logger.debug('Response: ${response.statusCode}');
if (response.statusCode == 200 || response.statusCode == 201) {
return TaskModel.fromJson(json.decode(response.body));
} else if (response.statusCode == 400) {
throw ValidationException(
message: 'Invalid task data',
errors: json.decode(response.body),
);
} else {
throw ServerException(
message: 'Failed to create task',
statusCode: response.statusCode,
);
}
} on SocketException {
throw const NetworkException();
}
}
@override
Future<TaskModel> updateTask(TaskModel task) async {
final uri = Uri.parse('$_baseUrl/tasks/${task.id}');
logger.info('PUT $uri');
try {
final response = await _client.put(
uri,
headers: _headers,
body: json.encode(task.toJson()),
);
if (response.statusCode == 200) {
return TaskModel.fromJson(json.decode(response.body));
} else if (response.statusCode == 404) {
throw const NotFoundException(message: 'Task not found');
} else {
throw ServerException(
message: 'Failed to update task',
statusCode: response.statusCode,
);
}
} on SocketException {
throw const NetworkException();
}
}
@override
Future<void> deleteTask(String id) async {
final uri = Uri.parse('$_baseUrl/tasks/$id');
logger.info('DELETE $uri');
try {
final response = await _client.delete(uri, headers: _headers);
if (response.statusCode == 204 || response.statusCode == 200) {
return;
} else if (response.statusCode == 404) {
throw const NotFoundException(message: 'Task not found');
} else {
throw ServerException(
message: 'Failed to delete task',
statusCode: response.statusCode,
);
}
} on SocketException {
throw const NetworkException();
}
}
@override
Future<TaskModel> toggleTaskStatus(String id) async {
final uri = Uri.parse('$_baseUrl/tasks/$id/toggle');
logger.info('PATCH $uri');
try {
final response = await _client.patch(uri, headers: _headers);
if (response.statusCode == 200) {
return TaskModel.fromJson(json.decode(response.body));
} else if (response.statusCode == 404) {
throw const NotFoundException(message: 'Task not found');
} else {
throw ServerException(
message: 'Failed to toggle task status',
statusCode: response.statusCode,
);
}
} on SocketException {
throw const NetworkException();
}
}
@override
Future<TaskModel> rescheduleTask(String id, String targetDate) async {
final uri = Uri.parse('$_baseUrl/tasks/$id/reschedule');
logger.info('POST $uri');
try {
final response = await _client.post(
uri,
headers: _headers,
body: json.encode({'target_date': targetDate}),
);
if (response.statusCode == 200) {
return TaskModel.fromJson(json.decode(response.body));
} else if (response.statusCode == 404) {
throw const NotFoundException(message: 'Task not found');
} else {
throw ServerException(
message: 'Failed to reschedule task',
statusCode: response.statusCode,
);
}
} on SocketException {
throw const NetworkException();
}
}
}