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:
root
2026-01-18 20:08:36 +00:00
commit a89c7894cf
132 changed files with 23178 additions and 0 deletions
@@ -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);
});