Files
AudiOhm/backend/LIBRARY_API_GUIDE.md
T
root 801e6a050b prod: UI Optimisée mise en production
- Documentation archivée et réorganisée
- Backend: Ajout tests, migrations, library service, rate limiting
- Frontend: Suppression Flutter, focus sur interface web HTML/JS
- Tailwind CSS ajouté pour le style
- Améliorations UX et corrections bugs

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-01-20 09:56:39 +00:00

12 KiB

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 <your_jwt_token>

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:

{
  "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):

{
  "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:

Future<void> 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):

[
  {
    "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:

Future<List<ListeningHistory>> 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<dynamic> 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):

{
  "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):

{
  "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:

Future<void> 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:

{
  "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):

{
  "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:

Future<void> 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):

[
  {
    "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):

{
  "is_liked": true
}

Exemple Flutter:

Future<bool> 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:

{
  "notes": "Nouvelles notes personnelles"
}

Response (200 OK):

{
  "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):

{
  "liked_tracks_count": 145,
  "total_plays": 2340,
  "plays_last_30_days": 320,
  "unique_tracks_played": 89
}

Exemple Flutter:

Future<LibraryStats> 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

// 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

// Charger plus d'entrées avec pagination
Future<void> loadMoreHistory() async {
  final newEntries = await getListeningHistory(
    limit: 50,
    offset: currentHistory.length,
  );

  setState(() {
    currentHistory.addAll(newEntries);
  });
}

3. Cache Local

// Mettre en cache les résultats pour éviter les requêtes inutiles
Map<String, bool> _likedCache = {};

Future<bool> 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

Future<void> safeApiCall(Future<void> 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

class AudioPlayerWithTracking {
  Timer? _trackingTimer;
  DateTime? _startTime;
  String? _currentTrackId;

  Future<void> playTrack(String trackId) async {
    // Logique de lecture audio...
    _startTime = DateTime.now();
    _currentTrackId = trackId;
  }

  Future<void> 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"

class LikedTracksScreen extends StatefulWidget {
  @override
  _LikedTracksScreenState createState() => _LikedTracksScreenState();
}

class _LikedTracksScreenState extends State<LikedTracksScreen> {
  List<LikedTrack> _likedTracks = [];
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    _loadLikedTracks();
  }

  Future<void> _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)