# Guide d'Utilisation de l'API Bibliothèque Ce guide présente comment utiliser les endpoints de l'API Bibliothèque d'AudiOhm depuis le frontend. ## Base URL Tous les endpoints sont préfixés par: `/api/v1` ## Authentication Tous les endpoints nécessitent une authentification via JWT token dans le header: ``` Authorization: Bearer ``` --- ## Endpoints d'Historique d'Écoute ### 1. Ajouter une entrée d'historique **Endpoint:** `POST /api/v1/library/history` **Description:** Enregistre une écoute de morceau dans l'historique de l'utilisateur. **Body:** ```json { "track_id": "uuid-du-morceau", "played_for": 180, "completed": true, "source": "library" } ``` **Champs:** - `track_id` (UUID, requis): ID du morceau écouté - `played_for` (int, requis): Durée écoutée en secondes - `completed` (bool, optionnel): Si le morceau a été écouté entièrement (défaut: false) - `source` (string, optionnel): Source de lecture (library, playlist, search, etc.) **Response (201 Created):** ```json { "id": "uuid-entrée", "user_id": "uuid-utilisateur", "track_id": "uuid-morceau", "played_for": 180, "completed": true, "source": "library", "played_at": "2026-01-19T10:30:00", "created_at": "2026-01-19T10:30:00", "track": { "id": "uuid-morceau", "title": "Nom du morceau", "duration": 240, "artist": { "id": "uuid-artiste", "name": "Nom de l'artiste" }, "album": { "id": "uuid-album", "name": "Nom de l'album" }, "image_url": "https://..." } } ``` **Exemple Flutter:** ```dart Future addToListeningHistory(String trackId, int playedFor) async { final response = await http.post( Uri.parse('$baseUrl/api/v1/library/history'), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, body: jsonEncode({ 'track_id': trackId, 'played_for': playedFor, 'completed': true, 'source': 'library', }), ); if (response.statusCode != 201) { throw Exception('Failed to add to history'); } } ``` --- ### 2. Lister l'historique **Endpoint:** `GET /api/v1/library/history` **Query Parameters:** - `limit` (1-100, défaut: 50): Nombre maximum de résultats - `offset` (défaut: 0): Pagination offset - `days` (optionnel): Filtrer les derniers N jours (1-365) **Response (200 OK):** ```json [ { "id": "uuid-entrée", "track_id": "uuid-morceau", "played_for": 180, "completed": true, "played_at": "2026-01-19T10:30:00", "track": { "id": "uuid-morceau", "title": "Nom du morceau", "duration": 240, "artist": {...}, "album": {...}, "image_url": "https://..." } } ] ``` **Exemple Flutter:** ```dart Future> getListeningHistory({ int limit = 50, int offset = 0, int? days, }) async { final queryParams = { 'limit': limit.toString(), 'offset': offset.toString(), if (days != null) 'days': days.toString(), }; final uri = Uri.parse('$baseUrl/api/v1/library/history') .replace(queryParameters: queryParams); final response = await http.get( uri, headers: {'Authorization': 'Bearer $token'}, ); if (response.statusCode == 200) { final List data = jsonDecode(response.body); return data.map((e) => ListeningHistory.fromJson(e)).toList(); } throw Exception('Failed to load history'); } ``` --- ### 3. Morceaux récemment écoutés **Endpoint:** `GET /api/v1/library/history/recent` **Query Parameters:** - `limit` (1-50, défaut: 20): Nombre maximum de résultats **Response (200 OK):** ```json { "tracks": [ { "id": "uuid-morceau", "title": "Nom du morceau", "duration": 240, "artist": {...}, "album": {...}, "image_url": "https://...", "play_count": 15 } ], "total": 20 } ``` --- ### 4. Morceaux les plus écoutés **Endpoint:** `GET /api/v1/library/history/most-played` **Query Parameters:** - `limit` (1-50, défaut: 20): Nombre maximum de résultats - `days` (optionnel): Filtrer les derniers N jours **Response (200 OK):** ```json { "tracks": [ { "track": { "id": "uuid-morceau", "title": "Nom du morceau", ... }, "play_count": 45 } ], "total": 20 } ``` --- ### 5. Effacer l'historique **Endpoint:** `DELETE /api/v1/library/history` **Query Parameters:** - `before_date` (optionnel, ISO 8601): Effacer avant cette date **Response (204 No Content)** **Exemple Flutter:** ```dart Future clearHistory({DateTime? beforeDate}) async { final queryParams = { if (beforeDate != null) 'before_date': beforeDate.toIso8601String(), }; final uri = Uri.parse('$baseUrl/api/v1/library/history') .replace(queryParameters: queryParams); final response = await http.delete( uri, headers: {'Authorization': 'Bearer $token'}, ); if (response.statusCode != 204) { throw Exception('Failed to clear history'); } } ``` --- ## Endpoints de Morceaux Likés ### 6. Liké un morceau **Endpoint:** `POST /api/v1/library/liked` **Body:** ```json { "track_id": "uuid-du-morceau", "notes": "Excellent morceau!" } ``` **Champs:** - `track_id` (UUID, requis): ID du morceau à liker - `notes` (string, optionnel, max 1000 caractères): Notes personnelles **Response (201 Created):** ```json { "id": "uuid-entrée", "user_id": "uuid-utilisateur", "track_id": "uuid-morceau", "notes": "Excellent morceau!", "created_at": "2026-01-19T10:30:00", "updated_at": "2026-01-19T10:30:00", "track": { "id": "uuid-morceau", "title": "Nom du morceau", ... } } ``` **Erreurs:** - `409 Conflict`: Le morceau est déjà liké --- ### 7. Unliké un morceau **Endpoint:** `DELETE /api/v1/library/liked/{track_id}` **Response (204 No Content)** **Exemple Flutter:** ```dart Future unlikeTrack(String trackId) async { final response = await http.delete( Uri.parse('$baseUrl/api/v1/library/liked/$trackId'), headers: {'Authorization': 'Bearer $token'}, ); if (response.statusCode != 204) { throw Exception('Failed to unlike track'); } } ``` --- ### 8. Lister les morceaux likés **Endpoint:** `GET /api/v1/library/liked` **Query Parameters:** - `limit` (1-100, défaut: 50) - `offset` (défaut: 0) **Response (200 OK):** ```json [ { "id": "uuid-entrée", "track_id": "uuid-morceau", "notes": "Excellent morceau!", "created_at": "2026-01-19T10:30:00", "track": { "id": "uuid-morceau", "title": "Nom du morceau", ... } } ] ``` --- ### 9. Vérifier si un morceau est liké **Endpoint:** `GET /api/v1/library/liked/check/{track_id}` **Response (200 OK):** ```json { "is_liked": true } ``` **Exemple Flutter:** ```dart Future isTrackLiked(String trackId) async { final response = await http.get( Uri.parse('$baseUrl/api/v1/library/liked/check/$trackId'), headers: {'Authorization': 'Bearer $token'}, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); return data['is_liked'] as bool; } return false; } ``` --- ### 10. Mettre à jour les notes **Endpoint:** `PUT /api/v1/library/liked/{track_id}/notes` **Body:** ```json { "notes": "Nouvelles notes personnelles" } ``` **Response (200 OK):** ```json { "id": "uuid-entrée", "track_id": "uuid-morceau", "notes": "Nouvelles notes personnelles", "created_at": "2026-01-19T10:30:00", "updated_at": "2026-01-19T11:00:00", "track": {...} } ``` --- ## Endpoint de Statistiques ### 11. Statistiques de la bibliothèque **Endpoint:** `GET /api/v1/library/stats` **Response (200 OK):** ```json { "liked_tracks_count": 145, "total_plays": 2340, "plays_last_30_days": 320, "unique_tracks_played": 89 } ``` **Exemple Flutter:** ```dart Future getLibraryStats() async { final response = await http.get( Uri.parse('$baseUrl/api/v1/library/stats'), headers: {'Authorization': 'Bearer $token'}, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); return LibraryStats.fromJson(data); } throw Exception('Failed to load stats'); } ``` --- ## Codes d'Erreur | Code | Description | |------|-------------| | 200 | Succès | | 201 | Ressource créée | | 204 | Succès sans contenu (DELETE) | | 400 | Requête invalide (ID invalide, etc.) | | 403 | Non autorisé | | 404 | Ressource non trouvée | | 409 | Conflit (déjà liké, etc.) | | 500 | Erreur serveur interne | --- ## Bonnes Pratiques ### 1. Tracking des Écoutes ```dart // Quand un utilisateur commence à écouter un morceau DateTime startTime = DateTime.now(); // Quand l'utilisateur arrête ou change de morceau void onTrackEnd(String trackId) { final playedFor = DateTime.now().difference(startTime).inSeconds; addToListeningHistory(trackId, playedFor).catchError((e) { // Gérer l'erreur silencieusement pour ne pas interrompre l'expérience print('Failed to track play: $e'); }); } ``` ### 2. Pagination ```dart // Charger plus d'entrées avec pagination Future loadMoreHistory() async { final newEntries = await getListeningHistory( limit: 50, offset: currentHistory.length, ); setState(() { currentHistory.addAll(newEntries); }); } ``` ### 3. Cache Local ```dart // Mettre en cache les résultats pour éviter les requêtes inutiles Map _likedCache = {}; Future isTrackLiked(String trackId) async { if (_likedCache.containsKey(trackId)) { return _likedCache[trackId]!; } final isLiked = await _fetchIsTrackLiked(trackId); _likedCache[trackId] = isLiked; return isLiked; } void toggleLike(String trackId, bool currentState) { _likedCache[trackId] = !currentState; // Effectuer la requête API... } ``` ### 4. Gestion des Erreurs ```dart Future safeApiCall(Future Function() apiCall) async { try { await apiCall(); } on HTTPException catch (e) { // Gérer les erreurs HTTP connues switch (e.statusCode) { case 401: // Rediriger vers login break; case 409: // Afficher message "déjà liké" break; default: // Afficher erreur générique } } catch (e) { // Gérer les erreurs inattendues } } ``` --- ## Exemples d'Intégration ### Player Audio avec Tracking ```dart class AudioPlayerWithTracking { Timer? _trackingTimer; DateTime? _startTime; String? _currentTrackId; Future playTrack(String trackId) async { // Logique de lecture audio... _startTime = DateTime.now(); _currentTrackId = trackId; } Future stopTrack() async { if (_startTime != null && _currentTrackId != null) { final playedFor = DateTime.now().difference(_startTime!).inSeconds; // Enregistrer dans l'historique await addToListeningHistory(_currentTrackId!, playedFor); } // Logique d'arrêt audio... _startTime = null; _currentTrackId = null; } } ``` ### Écran "Morceaux Likés" ```dart class LikedTracksScreen extends StatefulWidget { @override _LikedTracksScreenState createState() => _LikedTracksScreenState(); } class _LikedTracksScreenState extends State { List _likedTracks = []; bool _isLoading = false; @override void initState() { super.initState(); _loadLikedTracks(); } Future _loadLikedTracks() async { setState(() => _isLoading = true); try { final tracks = await getLikedTracks(limit: 50); setState(() { _likedTracks = tracks; _isLoading = false; }); } catch (e) { setState(() => _isLoading = false); // Afficher erreur } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Morceaux Likés')), body: _isLoading ? CircularProgressIndicator() : ListView.builder( itemCount: _likedTracks.length, itemBuilder: (context, index) { final track = _likedTracks[index]; return TrackTile(track: track.track); }, ), ); } } ``` --- ## Support Pour toute question ou problème, consultez: - Documentation technique: `LIBRARY_IMPLEMENTATION.md` - Tests: `test_library_features.py` - Schéma OpenAPI: `/api/docs` (when server is running)