9c504d2c3d
Features: - Frontend Flutter avec thème néon cyberpunk - Backend FastAPI avec streaming YouTube - Base de données PostgreSQL + Redis - Authentification JWT complète - Recherche multi-source (DB + YouTube) - Playlists CRUD avec drag & drop - Queue management - Settings avec audio quality - Interface adaptative (Desktop + Mobile) Tech Stack: - Frontend: Flutter 3.2+, Riverpod - Backend: Python 3.11+, FastAPI - Database: PostgreSQL 15+ - Cache: Redis 7+ - Streaming: yt-dlp + FFmpeg 🚀 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
77 lines
2.2 KiB
Dart
77 lines
2.2 KiB
Dart
/// API Service - Main HTTP client using Dio
|
|
library;
|
|
|
|
import 'package:dio/dio.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
|
|
|
import '../../../core/constants/api_constants.dart';
|
|
import '../../providers/auth_provider.dart';
|
|
|
|
/// API Service provider
|
|
final apiServiceProvider = Provider<Dio>((ref) {
|
|
final authState = ref.watch(authProvider);
|
|
final token = authState?.accessToken;
|
|
|
|
final options = BaseOptions(
|
|
baseUrl: ApiConstants.baseUrl,
|
|
connectTimeout: const Duration(milliseconds: ApiConstants.connectionTimeoutMs),
|
|
receiveTimeout: const Duration(milliseconds: ApiConstants.receiveTimeoutMs),
|
|
sendTimeout: const Duration(milliseconds: ApiConstants.sendTimeoutMs),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
if (token != null) 'Authorization': 'Bearer $token',
|
|
},
|
|
);
|
|
|
|
final dio = Dio(options);
|
|
|
|
// Add logger in debug mode
|
|
dio.interceptors.add(
|
|
PrettyDioLogger(
|
|
requestHeader: true,
|
|
requestBody: true,
|
|
responseBody: true,
|
|
responseHeader: false,
|
|
error: true,
|
|
compact: true,
|
|
),
|
|
);
|
|
|
|
// Add token refresh interceptor
|
|
dio.interceptors.add(
|
|
InterceptorsWrapper(
|
|
onError: (error, handler) async {
|
|
if (error.response?.statusCode == 401) {
|
|
// Try to refresh token
|
|
try {
|
|
final newToken = await ref.read(authProvider.notifier).refreshToken();
|
|
if (newToken != null) {
|
|
// Retry original request with new token
|
|
final opts = options.copyWith(
|
|
headers: {
|
|
...options.headers,
|
|
'Authorization': 'Bearer $newToken',
|
|
},
|
|
);
|
|
final clonedReq = await dio.fetch(opts..path = error.requestOptions.path);
|
|
return handler.resolve(clonedReq);
|
|
}
|
|
} catch (e) {
|
|
// Refresh failed, logout user
|
|
ref.read(authProvider.notifier).logout();
|
|
}
|
|
}
|
|
return handler.next(error);
|
|
},
|
|
),
|
|
);
|
|
|
|
return dio;
|
|
});
|
|
|
|
/// Get API client
|
|
Dio getDio(Ref ref) {
|
|
return ref.read(apiServiceProvider);
|
|
}
|