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>
186 lines
4.8 KiB
Dart
186 lines
4.8 KiB
Dart
/// Auth API Service
|
|
library;
|
|
|
|
import 'package:dio/dio.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../../core/constants/api_constants.dart';
|
|
import '../../../domain/entities/user.dart';
|
|
import 'api_service.dart';
|
|
|
|
/// Auth API response models
|
|
class LoginResponse {
|
|
final String accessToken;
|
|
final String refreshToken;
|
|
final int expiresIn;
|
|
final User user;
|
|
|
|
LoginResponse({
|
|
required this.accessToken,
|
|
required this.refreshToken,
|
|
required this.expiresIn,
|
|
required this.user,
|
|
});
|
|
|
|
factory LoginResponse.fromJson(Map<String, dynamic> json) {
|
|
return LoginResponse(
|
|
accessToken: json['access_token'] as String,
|
|
refreshToken: json['refresh_token'] as String,
|
|
expiresIn: json['expires_in'] as int,
|
|
user: User.fromJson(json['user'] as Map<String, dynamic>),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Extension on User for JSON serialization
|
|
extension UserJson on User {
|
|
static User fromJson(Map<String, dynamic> json) {
|
|
return User(
|
|
id: json['id'] as String,
|
|
email: json['email'] as String,
|
|
username: json['username'] as String,
|
|
displayName: json['display_name'] as String?,
|
|
avatarUrl: json['avatar_url'] as String?,
|
|
isPremium: json['is_premium'] as bool? ?? false,
|
|
createdAt: DateTime.parse(json['created_at'] as String),
|
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'id': id,
|
|
'email': email,
|
|
'username': username,
|
|
if (displayName != null) 'display_name': displayName,
|
|
if (avatarUrl != null) 'avatar_url': avatarUrl,
|
|
'is_premium': isPremium,
|
|
'created_at': createdAt.toIso8601String(),
|
|
'updated_at': updatedAt.toIso8601String(),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Auth API Service
|
|
class AuthApiService {
|
|
AuthApiService(this._dio);
|
|
|
|
final Dio _dio;
|
|
|
|
/// Login with email and password
|
|
Future<LoginResponse> login(String email, String password) async {
|
|
try {
|
|
final response = await _dio.post(
|
|
ApiConstants.login,
|
|
data: {
|
|
'email': email,
|
|
'password': password,
|
|
},
|
|
);
|
|
|
|
return LoginResponse.fromJson(response.data);
|
|
} on DioException catch (e) {
|
|
throw _handleDioError(e);
|
|
}
|
|
}
|
|
|
|
/// Register a new user
|
|
Future<LoginResponse> register({
|
|
required String email,
|
|
required String username,
|
|
required String password,
|
|
String? displayName,
|
|
}) async {
|
|
try {
|
|
final response = await _dio.post(
|
|
ApiConstants.register,
|
|
data: {
|
|
'email': email,
|
|
'username': username,
|
|
'password': password,
|
|
if (displayName != null) 'display_name': displayName,
|
|
},
|
|
);
|
|
|
|
return LoginResponse.fromJson(response.data);
|
|
} on DioException catch (e) {
|
|
throw _handleDioError(e);
|
|
}
|
|
}
|
|
|
|
/// Refresh access token
|
|
Future<Map<String, dynamic>> refreshToken(String refreshToken) async {
|
|
try {
|
|
final response = await _dio.post(
|
|
ApiConstants.refresh,
|
|
data: {'refresh_token': refreshToken},
|
|
);
|
|
|
|
return response.data;
|
|
} on DioException catch (e) {
|
|
throw _handleDioError(e);
|
|
}
|
|
}
|
|
|
|
/// Get current user profile
|
|
Future<User> getCurrentUser() async {
|
|
try {
|
|
final response = await _dio.get(ApiConstants.me);
|
|
return UserJson.fromJson(response.data);
|
|
} on DioException catch (e) {
|
|
throw _handleDioError(e);
|
|
}
|
|
}
|
|
|
|
/// Update user profile
|
|
Future<User> updateProfile({
|
|
String? displayName,
|
|
String? avatarUrl,
|
|
}) async {
|
|
try {
|
|
final response = await _dio.put(
|
|
ApiConstants.me,
|
|
data: {
|
|
if (displayName != null) 'display_name': displayName,
|
|
if (avatarUrl != null) 'avatar_url': avatarUrl,
|
|
},
|
|
);
|
|
|
|
return UserJson.fromJson(response.data);
|
|
} on DioException catch (e) {
|
|
throw _handleDioError(e);
|
|
}
|
|
}
|
|
|
|
/// Logout
|
|
Future<void> logout() async {
|
|
try {
|
|
await _dio.post(ApiConstants.logout);
|
|
} on DioException catch (e) {
|
|
throw _handleDioError(e);
|
|
}
|
|
}
|
|
|
|
Exception _handleDioError(DioException error) {
|
|
if (error.response != null) {
|
|
final statusCode = error.response!.statusCode;
|
|
final message = error.response!.data['detail'] as String? ??
|
|
'An error occurred';
|
|
|
|
return Exception('$statusCode: $message');
|
|
} else if (error.type == DioExceptionType.connectionTimeout) {
|
|
return const Exception('Connection timeout');
|
|
} else if (error.type == DioExceptionType.receiveTimeout) {
|
|
return const Exception('Receive timeout');
|
|
} else {
|
|
return Exception('Network error: ${error.message}');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Provider for Auth API Service
|
|
final authApiServiceProvider = Provider<AuthApiService>((ref) {
|
|
final dio = ref.watch(apiServiceProvider);
|
|
return AuthApiService(dio);
|
|
});
|