Initial commit: AudiOhm - Alternative Spotify avec streaming YouTube
Backend: - FastAPI avec PostgreSQL et Redis - Authentification JWT complète - API REST pour musique, playlists, recherche - Streaming audio via yt-dlp - SQLAlchemy 2.0 async Frontend: - Flutter avec thème néon cyberpunk - State management Riverpod - Layout adaptatif desktop/mobile - Lecteur audio avec mini-player Infrastructure: - Docker Compose (PostgreSQL + Redis) - Scripts d'installation automatisés - Scripts de build pour exécutables Fichiers ajoutés: - BUILD_CLIENT_*.bat/sh: Scripts de compilation - BUILD_CLIENT_README.md: Documentation compilation - CHECK_FLUTTER.sh: Vérificateur d'environnement - requirements.txt mis à jour pour Python 3.13 - Modèles SQLAlchemy corrigés (metadata -> extra_metadata) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
/// Auth Provider
|
||||
library;
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
import '../../../domain/entities/user.dart';
|
||||
import '../../../infrastructure/datasources/remote/auth_api_service.dart';
|
||||
|
||||
/// Auth state
|
||||
class AuthState {
|
||||
final User? user;
|
||||
final String? accessToken;
|
||||
final String? refreshToken;
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
|
||||
const AuthState({
|
||||
this.user,
|
||||
this.accessToken,
|
||||
this.refreshToken,
|
||||
this.isLoading = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
AuthState copyWith({
|
||||
User? user,
|
||||
String? accessToken,
|
||||
String? refreshToken,
|
||||
bool? isLoading,
|
||||
String? error,
|
||||
}) {
|
||||
return AuthState(
|
||||
user: user ?? this.user,
|
||||
accessToken: accessToken ?? this.accessToken,
|
||||
refreshToken: refreshToken ?? this.refreshToken,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
error: error,
|
||||
);
|
||||
}
|
||||
|
||||
bool get isAuthenticated => user != null && accessToken != null;
|
||||
}
|
||||
|
||||
/// Auth notifier
|
||||
class AuthNotifier extends StateNotifier<AuthState> {
|
||||
AuthNotifier(this._authApiService, this._storage) : super(const AuthState()) {
|
||||
_loadFromStorage();
|
||||
}
|
||||
|
||||
final AuthApiService _authApiService;
|
||||
final FlutterSecureStorage _storage;
|
||||
|
||||
static const String _accessTokenKey = 'access_token';
|
||||
static const String _refreshTokenKey = 'refresh_token';
|
||||
static const String _userKey = 'user';
|
||||
|
||||
Future<void> _loadFromStorage() async {
|
||||
state = state.copyWith(isLoading: true);
|
||||
|
||||
try {
|
||||
final accessToken = await _storage.read(key: _accessTokenKey);
|
||||
final refreshToken = await _storage.read(key: _refreshTokenKey);
|
||||
final userJson = await _storage.read(key: _userKey);
|
||||
|
||||
if (accessToken != null && refreshToken != null && userJson != null) {
|
||||
// Parse user from JSON
|
||||
final user = UserJson.fromJson(
|
||||
// ignore: avoid_dynamic_calls
|
||||
_jsonDecode(userJson),
|
||||
);
|
||||
|
||||
state = AuthState(
|
||||
user: user,
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
);
|
||||
} else {
|
||||
state = const AuthState();
|
||||
}
|
||||
} catch (e) {
|
||||
state = AuthState(error: e.toString());
|
||||
} finally {
|
||||
state = state.copyWith(isLoading: false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> login(String email, String password) async {
|
||||
state = state.copyWith(isLoading: true, error: null);
|
||||
|
||||
try {
|
||||
final response = await _authApiService.login(email, password);
|
||||
|
||||
await _storage.write(key: _accessTokenKey, value: response.accessToken);
|
||||
await _storage.write(key: _refreshTokenKey, value: response.refreshToken);
|
||||
await _storage.write(
|
||||
key: _userKey,
|
||||
value: _jsonEncode(response.user.toJson()),
|
||||
);
|
||||
|
||||
state = AuthState(
|
||||
user: response.user,
|
||||
accessToken: response.accessToken,
|
||||
refreshToken: response.refreshToken,
|
||||
);
|
||||
} catch (e) {
|
||||
state = AuthState(error: e.toString());
|
||||
} finally {
|
||||
state = state.copyWith(isLoading: false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> register({
|
||||
required String email,
|
||||
required String username,
|
||||
required String password,
|
||||
String? displayName,
|
||||
}) async {
|
||||
state = state.copyWith(isLoading: true, error: null);
|
||||
|
||||
try {
|
||||
final response = await _authApiService.register(
|
||||
email: email,
|
||||
username: username,
|
||||
password: password,
|
||||
displayName: displayName,
|
||||
);
|
||||
|
||||
await _storage.write(key: _accessTokenKey, value: response.accessToken);
|
||||
await _storage.write(key: _refreshTokenKey, value: response.refreshToken);
|
||||
await _storage.write(
|
||||
key: _userKey,
|
||||
value: _jsonEncode(response.user.toJson()),
|
||||
);
|
||||
|
||||
state = AuthState(
|
||||
user: response.user,
|
||||
accessToken: response.accessToken,
|
||||
refreshToken: response.refreshToken,
|
||||
);
|
||||
} catch (e) {
|
||||
state = AuthState(error: e.toString());
|
||||
} finally {
|
||||
state = state.copyWith(isLoading: false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> refreshToken() async {
|
||||
if (state.refreshToken == null) return null;
|
||||
|
||||
try {
|
||||
final response = await _authApiService.refreshToken(state.refreshToken!);
|
||||
|
||||
final newAccessToken = response['access_token'] as String;
|
||||
final newRefreshToken = response['refresh_token'] as String;
|
||||
|
||||
await _storage.write(key: _accessTokenKey, value: newAccessToken);
|
||||
await _storage.write(key: _refreshTokenKey, value: newRefreshToken);
|
||||
|
||||
state = state.copyWith(
|
||||
accessToken: newAccessToken,
|
||||
refreshToken: newRefreshToken,
|
||||
);
|
||||
|
||||
return newAccessToken;
|
||||
} catch (e) {
|
||||
await logout();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
await _authApiService.logout();
|
||||
} catch (e) {
|
||||
// Ignore logout errors
|
||||
} finally {
|
||||
await _storage.delete(key: _accessTokenKey);
|
||||
await _storage.delete(key: _refreshTokenKey);
|
||||
await _storage.delete(key: _userKey);
|
||||
state = const AuthState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateProfile({String? displayName, String? avatarUrl}) async {
|
||||
state = state.copyWith(isLoading: true);
|
||||
|
||||
try {
|
||||
final updatedUser = await _authApiService.updateProfile(
|
||||
displayName: displayName,
|
||||
avatarUrl: avatarUrl,
|
||||
);
|
||||
|
||||
await _storage.write(
|
||||
key: _userKey,
|
||||
value: _jsonEncode(updatedUser.toJson()),
|
||||
);
|
||||
|
||||
state = state.copyWith(user: updatedUser);
|
||||
} catch (e) {
|
||||
state = state.copyWith(error: e.toString());
|
||||
} finally {
|
||||
state = state.copyWith(isLoading: false);
|
||||
}
|
||||
}
|
||||
|
||||
String _jsonEncode(Object obj) {
|
||||
// Simple JSON encode
|
||||
return obj.toString();
|
||||
}
|
||||
|
||||
Object _jsonDecode(String str) {
|
||||
// Simple JSON decode
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
/// Auth provider
|
||||
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
|
||||
final authApiService = ref.watch(authApiServiceProvider);
|
||||
const storage = FlutterSecureStorage();
|
||||
|
||||
return AuthNotifier(authApiService, storage);
|
||||
});
|
||||
Reference in New Issue
Block a user