- 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>
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 secondescompleted(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ésultatsoffset(défaut: 0): Pagination offsetdays(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ésultatsdays(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 à likernotes(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)