Documentation
- Vue d'ensemble
- Authentification
- Schéma BD (MCD)
- Profils
- Endpoints
- Paiements E-Billing
- Sports Live
- Modèles
- Exemples
🆕 Migration mai 2026 : MySQL → Supabase. L'auth bcrypt+JWT custom est remplacée par Supabase Auth (JWT). Voir
docs/SUPABASE_MIGRATION.md.🗄️ Modèle de données : MCD complet dans
docs/DATABASE_SCHEMA.md (21 tables, vue d'ensemble + 5 sous-domaines).🔐 Base URL en local :
http://localhost:5000. Toutes les routes protégées attendent Authorization: Bearer <supabase_access_token>.
Authentification & Système de Profils
L'API utilise Supabase Auth (JWT). Les sessions sont créées via signInWithPassword et le serveur vérifie le token via supabase.auth.getUser().
Chaque utilisateur peut créer plusieurs profils (limite définie par le plan :
maxProfiles).Chaque connexion enregistre l'appareil utilisé. Au-delà de
plan.maxDevices appareils connectés simultanément, le login renvoie 403 MAX_DEVICES_REACHED.
/api/users/login
Authentifie l'utilisateur via Supabase Auth. Vérifie la limite d'appareils du plan actif puis retourne accessToken, refreshToken et les détails du plan.
Paramètres
| Nom | Type | Description | Requis |
|---|---|---|---|
| String | Email de l'utilisateur | Oui | |
| password | String | Mot de passe | Oui |
| deviceId | String | UUID unique généré côté client (persistant) | Recommandé |
| deviceName | String | Nom lisible : "Mon iPhone Pro", "Smart TV Salon" | Non |
| deviceType | String | Desktop, Mobile, Smart TV, Tablet… | Non |
| operatingSystem | String | iOS 18, Windows 11, Android 14… | Non |
| appVersion | String | Version de l'app cliente, ex: 1.0.3 | Non |
Réponse
{
"success": true,
"message": "Connexion réussie",
"data": {
"userId": "0fe28a3c-…-uuid",
"username": "alice",
"email": "alice@example.com",
"role": "USER",
"session": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"refreshToken": "v1.MR…",
"expiresIn": 3600,
"expiresAt": 1715450000
},
"plan": {
"id": 4,
"name": "Premium",
"source": "subscription",
"maxProfiles": 5,
"maxDevices": 4,
"maxConcurrentStreams": 2,
"features": {
"hasMovies": true,
"hasShows": true,
"hasIPTV": true,
"allowDownloads": true,
"hasAds": false
}
},
"profiles": {
"count": 3,
"data": [ { "id": 1, "profileName": "Alice", "profileType": "adult", "isDefault": true }, … ]
}
}
}
Erreur 403 — Limite d'appareils
{
"success": false,
"message": "Limite d'appareils connectés atteinte (4 max pour le plan Premium). Déconnectez un appareil pour continuer.",
"code": "MAX_DEVICES_REACHED",
"limit": 4,
"current": 4
}
/api/users/register
Enregistre un nouvel utilisateur dans le système.
Paramètres
| Nom | Type | Description | Requis |
|---|---|---|---|
| username | String | Nom d'utilisateur | Oui |
| String | Email de l'utilisateur | Oui | |
| password | String | Mot de passe (min. 8 caractères) | Oui |
| phoneNumber | String | Numéro de téléphone | Non |
/api/users/verify
Vérifie le compte d'un utilisateur à l'aide d'un token de vérification.
Paramètres
| Nom | Type | Description | Requis |
|---|---|---|---|
| token | String | Token de vérification | Oui |
Exemple d'utilisation du token Supabase
// JavaScript - Login puis requête authentifiée
const login = async () => {
const res = await fetch('http://localhost:5000/api/users/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'alice@example.com',
password: 'Password123!',
deviceId: crypto.randomUUID(), // à persister côté client
deviceName: 'Chrome Desktop',
deviceType: 'Desktop',
operatingSystem: navigator.platform,
appVersion: '1.0.0'
})
});
const { data } = await res.json();
localStorage.setItem('accessToken', data.session.accessToken);
localStorage.setItem('refreshToken', data.session.refreshToken);
return data;
};
const fetchMovies = async () => {
const token = localStorage.getItem('accessToken');
const res = await fetch('http://localhost:5000/api/movies', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
return res.json();
};
// Refresh quand le token expire
const refresh = async () => {
const refreshToken = localStorage.getItem('refreshToken');
const res = await fetch('http://localhost:5000/api/users/refresh-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken })
});
const { data } = await res.json();
localStorage.setItem('accessToken', data.accessToken);
localStorage.setItem('refreshToken', data.refreshToken);
};
Schéma de la base de données — MCD
Modèle Conceptuel de Données de Supabase Postgres. Bascule entre les vues pour explorer les 21 tables et leurs relations.
erDiagram
Users ||--o{ Profiles : "1:N"
Users ||--o{ UserDevices : "1:N"
Users ||--o{ OTP : "1:N"
Users ||--o| Admin : "optionnel"
Users ||--o{ Subscriptions : "1:N"
Users ||--o{ Payments : "1:N"
Plans ||--o{ Subscriptions : "1:N"
Payments ||--o| Subscriptions : "optionnel"
Users ||--o{ MovieWatchHistory : "1:N"
Users ||--o{ EpisodeWatchHistory : "1:N"
Profiles ||--o{ MovieWatchHistory : "1:N"
Profiles ||--o{ EpisodeWatchHistory : "1:N"
Users ||--o{ FavoritesMovies : "1:N"
Users ||--o{ FavoritesShows : "1:N"
Users ||--o{ FavoritesChannels : "1:N"
Users ||--o{ MovieComments : "1:N"
Users ||--o{ ShowComments : "1:N"
TVShows ||--o{ Seasons : "1:N"
Seasons ||--o{ Episodes : "1:N"
Movies ||--o{ MovieWatchHistory : "1:N"
Movies ||--o{ FavoritesMovies : "1:N"
Movies ||--o{ MovieComments : "1:N"
TVShows ||--o{ FavoritesShows : "1:N"
TVShows ||--o{ ShowComments : "1:N"
Episodes ||--o{ EpisodeWatchHistory : "1:N"
IPTVChannels ||--o{ FavoritesChannels : "1:N"
Users {
uuid id PK
string username UK
string email UK
string phoneNumber UK
text profilePictureUrl
timestamptz trialStartDate
timestamptz trialEndDate
timestamptz lastLoginDate
string lastLoginIp
string status
string role
}
Profiles {
bigint id PK
uuid userID FK
string profileName
text profileAvatarUrl
string profileType
jsonb preferences
bool isDefault
bool isActive
}
UserDevices {
bigint id PK
uuid userId FK
string deviceId
string deviceName
string deviceType
string operatingSystem
string appVersion
string ipAddress
bool isConnected
timestamptz lastActive
}
OTP {
bigint id PK
uuid userId FK
string code
string purpose
timestamptz expiresAt
bool isUsed
}
Admin {
bigint id PK
uuid userId FK,UK
string role
}
Plans {
bigint id PK
string name UK
text description
numeric price
int duration
int maxProfiles
int maxDevices
int maxConcurrentStreams
bool hasMovies
bool hasShows
bool hasIPTV
bool allowDownloads
bool hasAds
bool isActive
}
Subscriptions {
bigint id PK
uuid userId FK
bigint planId FK
bigint paymentId FK
bool autoRenew
string status
timestamptz startDate
timestamptz endDate
}
Payments {
bigint id PK
uuid userId FK
numeric amount
string status
string paymentMethod
text transactionId
timestamptz transactionDate
}
Movies {
bigint id PK
string tmdbId UK
string title
text overview
text posterPath
int year
numeric rating
string quality
text videoPath
bool isAvailable
}
TVShows {
bigint id PK
string tmdbId UK
string title
text overview
text posterPath
int year
numeric rating
bool isAvailable
}
Seasons {
bigint id PK
bigint showId FK
int seasonNumber
string title
text overview
}
Episodes {
bigint id PK
bigint seasonId FK
int episodeNumber
string title
text videoPath
bool isAvailable
}
MovieWatchHistory {
bigint id PK
uuid userId FK
bigint profileId FK
bigint movieId FK
int lastWatchedPosition
bool isCompleted
timestamptz lastWatchedAt
}
EpisodeWatchHistory {
bigint id PK
uuid userId FK
bigint profileId FK
bigint episodeId FK
int lastWatchedPosition
bool isCompleted
timestamptz lastWatchedAt
}
FavoritesMovies {
bigint id PK
uuid userId FK
bigint movieId FK
}
FavoritesShows {
bigint id PK
uuid userId FK
bigint showId FK
}
FavoritesChannels {
bigint id PK
uuid userId FK
bigint channelId FK
}
MovieComments {
bigint id PK
uuid userId FK
bigint movieId FK
text comment
smallint rating
}
ShowComments {
bigint id PK
uuid userId FK
bigint showId FK
text comment
smallint rating
}
IPTVChannels {
bigint id PK
string channelName
text streamUrl
text logoUrl
text epgUrl
bool isActive
}
LiveEvents {
bigint id PK
string title
string category
timestamptz eventTime
jsonb streamLinks
bool isLive
string quality
}
erDiagram
auth_users ||--|| Users : "trigger SQL"
Users ||--o{ Profiles : "possède (1:N)"
Users ||--o{ UserDevices : "utilise (1:N)"
Users ||--o{ OTP : "reçoit (1:N)"
Users ||--o| Admin : "joue rôle (optionnel)"
auth_users {
uuid id PK
string email
jsonb raw_user_meta_data
}
Users {
uuid id PK,FK
string username UK
string email UK
string phoneNumber UK
timestamptz trialStartDate
timestamptz trialEndDate
timestamptz lastLoginDate
string lastLoginIp
string status
string role
}
Profiles {
bigint id PK
uuid userID FK
string profileName
string profileType
jsonb preferences
bool isDefault
bool isActive
}
UserDevices {
bigint id PK
uuid userId FK
string deviceId
string deviceName
string deviceType
string ipAddress
bool isConnected
timestamptz lastActive
}
OTP {
bigint id PK
uuid userId FK
string code
string purpose
timestamptz expiresAt
bool isUsed
}
Admin {
bigint id PK
uuid userId FK,UK
string role
}
erDiagram
Users ||--o{ Subscriptions : "souscrit (1:N)"
Users ||--o{ Payments : "paie (1:N)"
Plans ||--o{ Subscriptions : "définit (1:N)"
Payments ||--o| Subscriptions : "finance (optionnel)"
Users {
uuid id PK
string username
string email
}
Plans {
bigint id PK
string name UK
numeric price
int duration
int maxProfiles
int maxDevices
int maxConcurrentStreams
bool hasMovies
bool hasIPTV
bool allowDownloads
bool hasAds
}
Subscriptions {
bigint id PK
uuid userId FK
bigint planId FK
bigint paymentId FK
bool autoRenew
string status
timestamptz startDate
timestamptz endDate
}
Payments {
bigint id PK
uuid userId FK
numeric amount
string status
string paymentMethod
text transactionId
timestamptz transactionDate
}
erDiagram
TVShows ||--o{ Seasons : "contient (1:N)"
Seasons ||--o{ Episodes : "compose (1:N)"
Movies {
bigint id PK
string tmdbId UK
string title
text overview
text posterPath
text backdropPath
int year
numeric rating
string quality
text videoPath
bool isAvailable
}
TVShows {
bigint id PK
string tmdbId UK
string title
text overview
text posterPath
text backdropPath
int year
numeric rating
bool isAvailable
}
Seasons {
bigint id PK
bigint showId FK
int seasonNumber
string title
text overview
text posterPath
}
Episodes {
bigint id PK
bigint seasonId FK
int episodeNumber
string title
text overview
text stillPath
text videoPath
bool isAvailable
}
erDiagram
Users ||--o{ MovieWatchHistory : "regarde N:N"
Users ||--o{ EpisodeWatchHistory : "regarde N:N"
Profiles ||--o{ MovieWatchHistory : "par profil"
Profiles ||--o{ EpisodeWatchHistory : "par profil"
Movies ||--o{ MovieWatchHistory : ""
Episodes ||--o{ EpisodeWatchHistory : ""
Users ||--o{ FavoritesMovies : "favoris N:N"
Movies ||--o{ FavoritesMovies : ""
Users ||--o{ FavoritesShows : "favoris N:N"
TVShows ||--o{ FavoritesShows : ""
Users ||--o{ FavoritesChannels : "favoris N:N"
IPTVChannels ||--o{ FavoritesChannels : ""
Users ||--o{ MovieComments : "commente N:N"
Movies ||--o{ MovieComments : ""
Users ||--o{ ShowComments : "commente N:N"
TVShows ||--o{ ShowComments : ""
MovieWatchHistory {
bigint id PK
uuid userId FK
bigint profileId FK
bigint movieId FK
int lastWatchedPosition
bool isCompleted
timestamptz lastWatchedAt
}
EpisodeWatchHistory {
bigint id PK
uuid userId FK
bigint profileId FK
bigint episodeId FK
int lastWatchedPosition
bool isCompleted
timestamptz lastWatchedAt
}
FavoritesMovies {
bigint id PK
uuid userId FK
bigint movieId FK
}
FavoritesShows {
bigint id PK
uuid userId FK
bigint showId FK
}
FavoritesChannels {
bigint id PK
uuid userId FK
bigint channelId FK
}
MovieComments {
bigint id PK
uuid userId FK
bigint movieId FK
text comment
smallint rating
}
ShowComments {
bigint id PK
uuid userId FK
bigint showId FK
text comment
smallint rating
}
flowchart LR
U[Users]
PR[Profiles]
DEV[UserDevices]
PL[Plans]
SUB[Subscriptions]
PAY[Payments]
MO[Movies]
TV[TVShows]
SE[Seasons]
EP[Episodes]
IPTV[IPTVChannels]
A1{possède}
A2{utilise}
A3{souscrit}
A4{définit}
A5{paie}
A6{contient}
A7{compose}
A8{regarde film}
A9{regarde épisode}
A10{favori film}
A11{favori série}
A12{favori chaîne}
U ---|"(1,1)"| A1
A1 ---|"(0,N)"| PR
U ---|"(1,1)"| A2
A2 ---|"(0,N)"| DEV
U ---|"(1,1)"| A3
A3 ---|"(0,N)"| SUB
PL ---|"(1,1)"| A4
A4 ---|"(0,N)"| SUB
U ---|"(1,1)"| A5
A5 ---|"(0,N)"| PAY
TV ---|"(1,1)"| A6
A6 ---|"(0,N)"| SE
SE ---|"(1,1)"| A7
A7 ---|"(0,N)"| EP
U ---|"(0,N)"| A8
A8 ---|"(0,N)"| MO
U ---|"(0,N)"| A9
A9 ---|"(0,N)"| EP
U ---|"(0,N)"| A10
A10 ---|"(0,N)"| MO
U ---|"(0,N)"| A11
A11 ---|"(0,N)"| TV
U ---|"(0,N)"| A12
A12 ---|"(0,N)"| IPTV
classDef entity fill:#1f1f1f,stroke:#e50914,color:#fff,stroke-width:2px
classDef assoc fill:#c0504d,stroke:#8c3a37,color:#fff
class U,PR,DEV,PL,SUB,PAY,MO,TV,SE,EP,IPTV entity
class A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12 assoc
docs/DATABASE_SCHEMA.md contient la version complète du MCD Merise (entités · associations · cardinalités min,max) ainsi que le tableau récap des contraintes UNIQUE et ON DELETE.
Système de Profils
Plusieurs profils par compte, chacun avec ses préférences et son historique. Le nombre maximum dépend du plan d'abonnement (maxProfiles : 1 pour Basic, 2 pour Trial, 3 pour Standard, 5 pour Premium/Family).
Fonctionnalités
Création Automatique
Un profil par défaut est créé automatiquement à l'inscription (trigger SQL on_auth_user_created côté Supabase + creation du Profile par défaut côté API).
Types de Profils
Deux types disponibles : adult et child. Le type child peut activer un contrôle parental via la clé preferences.matureContent: false.
Préférences Personnalisées
Chaque profil peut avoir ses propres préférences : langue, qualité vidéo, sous-titres, etc.
Profil par Défaut
Un seul profil peut être marqué comme défaut par utilisateur. Le profil par défaut est utilisé pour les nouvelles sessions.
Exemple d'utilisation du système de profils
Connexion et récupération des profils
// 1. Connexion utilisateur
const loginResponse = await fetch('/api/users/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
password: 'password123'
})
});
const loginData = await loginResponse.json();
// 2. Récupération des profils dans la réponse
const { token, profiles } = loginData.data;
console.log('Token JWT:', token);
console.log('Nombre de profils:', profiles.count);
console.log('Profils disponibles:', profiles.data);
// 3. Affichage des profils pour sélection
profiles.data.forEach(profile => {
console.log(`- ${profile.profileName} (${profile.profileType})`);
if (profile.isDefault) console.log(' → Profil par défaut');
});
// 4. Stockage du profil sélectionné
localStorage.setItem('selectedProfileId', profiles.data[0].id);
localStorage.setItem('jwt_token', token);
Gestion des profils (CRUD)
// Configuration de base
const API_BASE = 'http://localhost:5000/api';
const token = localStorage.getItem('jwt_token');
// Récupérer tous les profils
const getProfiles = async () => {
const response = await fetch(`${API_BASE}/profiles`, {
headers: { 'Authorization': `Bearer ${token}` }
});
return response.json();
};
// Créer un nouveau profil
const createProfile = async (profileData) => {
const response = await fetch(`${API_BASE}/profiles`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
profileName: 'Kids Profile',
profileType: 'child',
preferences: {
contentRating: 'G',
language: 'fr',
autoplay: false
}
})
});
return response.json();
};
// Mettre à jour un profil
const updateProfile = async (profileId, updates) => {
const response = await fetch(`${API_BASE}/profiles/${profileId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
});
return response.json();
};
// Définir le profil par défaut
const setDefaultProfile = async (profileId) => {
const response = await fetch(`${API_BASE}/profiles/${profileId}/default`, {
method: 'PATCH',
headers: { 'Authorization': `Bearer ${token}` }
});
return response.json();
};
// Supprimer un profil
const deleteProfile = async (profileId) => {
const response = await fetch(`${API_BASE}/profiles/${profileId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
return response.json();
};
// Exemple d'utilisation
async function manageProfiles() {
try {
// Récupérer les profils existants
const profiles = await getProfiles();
console.log('Profils existants:', profiles);
// Créer un nouveau profil enfant
const newProfile = await createProfile({
profileName: 'Kids',
profileType: 'child'
});
console.log('Nouveau profil créé:', newProfile);
// Mettre à jour les préférences
await updateProfile(newProfile.id, {
preferences: {
contentRating: 'G',
language: 'fr',
autoplay: false,
subtitles: true
}
});
} catch (error) {
console.error('Erreur:', error);
}
}
Endpoints API
L'API DayWatch expose 198 endpoints répartis en 29 groupes. Toutes les réponses sont en JSON. Authentification via header Authorization: Bearer <jwt>.
Films (9)
/api/movies
Liste paginée de tous les films disponibles.
/api/movies/:id
Détails d'un film par son ID (Radarr ou tmdb_XXXXX).
/api/movies/coming-soon
Films bientôt disponibles.
/api/movies/popular
Films populaires (basé sur TMDB).
/api/movies/recent
Films récemment ajoutés à la médiathèque.
/api/movies/recent-additions
Films récemment importés (version étendue).
/api/movies/recent-additions/essentials
Recent additions — version allégée (champs essentiels).
/api/movies/top-recommendations
Top recommandations personnalisées.
/api/movies/top-recommendations/essentials
Top recommandations — version allégée.
Radarr (films détaillés, acteurs, plateformes) (26)
/api/radarr/actors/:id
Détail d'un acteur.
/api/radarr/actors/:id/credits
Filmographie d'un acteur.
/api/radarr/actors/popular
Acteurs populaires.
/api/radarr/actors/popular/essentials
Acteurs populaires — champs essentiels.
/api/radarr/movies
Tous les films via Radarr.
/api/radarr/movies/:id
Détail d'un film Radarr.
/api/radarr/movies/:id/credits
Casting & équipe.
/api/radarr/movies/:id/file
Métadonnées du fichier vidéo (codec, taille).
/api/radarr/movies/:id/images
Affiches, fonds, logos.
/api/radarr/movies/:id/stream
Stream HLS du film.
/api/radarr/movies/:id/subtitle/:lang
Sous-titres par langue (.srt/.vtt).
/api/radarr/movies/:id/subtitle/embedded/:subIndex
Sous-titres embedded (index dans le MKV).
/api/radarr/movies/:id/subtitles
Sous-titres disponibles (FS local + Bazarr).
/api/radarr/movies/:id/tracks
Pistes audio disponibles.
/api/radarr/movies/:id/videos
Bandes-annonces & extraits.
/api/radarr/movies/boxoffice
Films actuellement au box-office.
/api/radarr/movies/boxoffice/essentials
Box-office — champs essentiels.
/api/radarr/movies/essentials
Liste films — champs essentiels (perf).
/api/radarr/movies/popular
Films populaires (Radarr).
/api/radarr/movies/popular/essentials
Films populaires — champs essentiels.
/api/radarr/movies/recent
Films récents (Radarr).
/api/radarr/movies/recent/essentials
Films récents — champs essentiels.
/api/radarr/movies/stats
Statistiques globales films.
/api/radarr/movies/upcoming
Films à venir.
/api/radarr/platforms
Plateformes streaming disponibles.
/api/radarr/platforms/:platform/movies
Films par plateforme.
Séries (18)
/api/series
Liste paginée de toutes les séries.
/api/series/:id
Détail d'une série.
/api/series/:id/credits
Casting de la série.
/api/series/:id/episodes
Tous les épisodes.
/api/series/:id/episodes-with-files
Épisodes avec leurs fichiers vidéo.
/api/series/:id/images
Affiches et images.
/api/series/:id/seasons
Liste des saisons.
/api/series/:id/seasons/:seasonNumber
Détail d'une saison.
/api/series/:id/videos
Trailers de la série.
/api/series/anime
Animés japonais.
/api/series/anime/upcoming
Animés à venir.
/api/series/k-drama
Séries coréennes (K-Drama).
/api/series/platforms/:platform
Séries par plateforme (Netflix, Disney+, etc.).
/api/series/popular
Séries populaires.
/api/series/recent
Séries récemment ajoutées.
/api/series/recommended
Séries recommandées.
/api/series/stats
Statistiques séries.
/api/series/upcoming
Séries à venir.
Sonarr (séries détaillées, épisodes) (18)
/api/sonarr/series
Liste séries (Sonarr).
/api/sonarr/series/:id
Détail série Sonarr.
/api/sonarr/series/:id/episodes
Épisodes d'une série.
/api/sonarr/series/:id/episodes-with-files
Épisodes + fichiers.
/api/sonarr/series/:id/seasons/:seasonNumber/episodes/:episodeNumber/stream
Stream HLS de l'épisode.
/api/sonarr/series/:id/seasons/:seasonNumber/episodes/:episodeNumber/subtitle/:lang
Sous-titres par langue.
/api/sonarr/series/:id/seasons/:seasonNumber/episodes/:episodeNumber/subtitle/embedded/:subIndex
Sous-titres embedded.
/api/sonarr/series/:id/seasons/:seasonNumber/episodes/:episodeNumber/subtitles
Sous-titres d'un épisode.
/api/sonarr/series/:id/seasons/:seasonNumber/episodes/:episodeNumber/timestamps
Timestamps intro/credits (Jellyfin).
/api/sonarr/series/:id/seasons/:seasonNumber/episodes/:episodeNumber/tracks
Pistes audio d'un épisode.
/api/sonarr/series/essentials
Liste séries — champs essentiels.
/api/sonarr/series/popular
Séries populaires (Sonarr).
/api/sonarr/series/popular/essentials
Séries populaires — champs essentiels.
/api/sonarr/series/recent
Séries récentes.
/api/sonarr/series/recent/essentials
Séries récentes — champs essentiels.
/api/sonarr/series/recommended/essentials
Recommandations — champs essentiels.
/api/sonarr/series/stats
Stats Sonarr.
/api/sonarr/series/upcoming
Séries à venir.
Bandes-annonces (2)
/api/trailers/recent
Bandes-annonces récentes.
/api/trailers/upcoming
Bandes-annonces des films à venir.
Acteurs (2)
/api/actors
Liste générique des acteurs.
/api/actors/:actorId
Détail acteur.
Plateformes de streaming (2)
/api/platforms
Plateformes streaming.
/api/platforms/:id
Détail plateforme.
Recherche universelle (1)
/api/search
Recherche universelle (films, séries, acteurs).
Sports — Big games (3)
/api/sports/big-games/search
Recherche big game.
/api/sports/big-games/today
Big games du jour.
/api/sports/big-games/upcoming/:sports
Big games à venir par sport.
Sports — Live & diffuseurs (6)
/api/sports-live/admin/unavailable-channels
Chaînes non-dispos (admin).
/api/sports-live/admin/unavailable-channels/:id/resolve
Marquer chaîne comme résolue.
/api/sports-live/events
Événements live.
/api/sports-live/events/:id
Détail événement.
/api/sports-live/events/:id/watch
Stream du match.
/api/sports-live/sync
Sync sports depuis APIs externes.
IPTV — Chaînes locales (7)
/api/iptv/channels
Liste des chaînes IPTV.
/api/iptv/channels
Ajouter chaîne.
/api/iptv/channels/:id
Supprimer chaîne.
/api/iptv/channels/:id
Modifier chaîne.
/api/iptv/channelsFilters
Filtres dispo (pays, genres).
/api/iptv/channelsPages/:page/:limit
Chaînes paginées.
/api/iptv/sync
Sync depuis source M3U.
IPTV-Org — Catalogue mondial (11)
/api/iptv-org/channels/all
Toutes les chaînes mondiales.
/api/iptv-org/channels/all/with-streams
Toutes les chaînes + streams.
/api/iptv-org/channels/french
Chaînes françaises.
/api/iptv-org/channels/french/:channelId
Détail chaîne FR.
/api/iptv-org/channels/french/categories
Catégories chaînes FR.
/api/iptv-org/channels/french/categories/:category
FR — chaînes par catégorie.
/api/iptv-org/channels/french/only-with-streams
FR — uniquement avec streams.
/api/iptv-org/channels/french/search
Recherche dans FR.
/api/iptv-org/channels/french/stats
Stats chaînes FR.
/api/iptv-org/channels/french/streams
Chaînes FR + streams.
/api/iptv-org/test
Test connexion source.
CanalSport — Événements & streams (11)
/api/canalsport/categories
Liste catégories.
/api/canalsport/database/events
Événements en DB.
/api/canalsport/database/stats
Stats DB.
/api/canalsport/events
Événements scrapés.
/api/canalsport/events/category/:category
Par catégorie.
/api/canalsport/events/live
Événements live.
/api/canalsport/scrape-and-save
Scrape + persistance.
/api/canalsport/search
Recherche événement.
/api/canalsport/stats
Stats scraping.
/api/canalsport/stream
Obtenir le stream.
/api/canalsport/test
Test scraping.
Utilisateurs (16)
/api/users/account
Suppression compte.
/api/users/forgot-password
Demande de reset password.
/api/users/login
Connexion (JWT).
/api/users/logout
Déconnexion.
/api/users/password
Changer le mot de passe.
/api/users/phone
Changer le téléphone.
/api/users/profile
Profil de l'utilisateur courant.
/api/users/profile
Mise à jour du profil.
/api/users/refresh-token
Refresh du token.
/api/users/register
Inscription utilisateur.
/api/users/resend-otp
Renvoyer le code OTP.
/api/users/resend-verification
Renvoyer le mail de vérification.
/api/users/reset-password
Reset effectif avec token.
/api/users/subscription-status
Statut abonnement de l'user.
/api/users/users
Liste tous les users (admin).
/api/users/verify-email
Vérification email.
Authentification — OTP & vérification (6)
/api/auth/reset-password
Reset password via OTP.
/api/auth/send-email-otp
Envoyer OTP par email.
/api/auth/send-phone-otp
Envoyer OTP par SMS.
/api/auth/status
Statut vérification user.
/api/auth/verify-email-otp
Vérifier OTP email.
/api/auth/verify-phone-otp
Vérifier OTP SMS.
Profils (11)
/api/profiles
Créer un profil.
/api/profiles/:profileId
Supprimer profil.
/api/profiles/:profileId
Détail profil.
/api/profiles/:profileId
Mettre à jour profil.
/api/profiles/:profileId/default
Définir profil par défaut.
/api/profiles/:profileId/pin
Supprimer le PIN.
/api/profiles/:profileId/pin
Activer/changer le PIN.
/api/profiles/:profileId/verify-pin
Vérifier le PIN.
/api/profiles/me
Profil actif courant.
/api/profiles/user
Tous les profils du user courant.
/api/profiles/user/:userId
Profils d'un user spécifique.
Devices (sessions multi-écran) (4)
/api/devices
Mes devices.
/api/devices/:deviceId
/api/devices/disconnect-others
/api/devices/heartbeat
Plans (abonnements) (5)
/api/plans
Liste plans dispo.
/api/plans
Créer plan (admin).
/api/plans/:id
Supprimer plan.
/api/plans/:id
Détail plan.
/api/plans/:id
Modifier plan.
Abonnements utilisateurs (5)
/api/subscriptions
Toutes les subs (admin).
/api/subscriptions
Créer sub.
/api/subscriptions/:id
Supprimer sub.
/api/subscriptions/:id
Détail sub.
/api/subscriptions/:id
Modifier sub.
Paiements — E-Billing Mobile Money (5)
/api/payments/:id/status
Statut d'un paiement.
/api/payments/:id/verify
Vérifier paiement manuellement.
/api/payments/init
Initier un paiement.
/api/payments/me
Mes paiements.
/api/payments/webhook/ebilling
Webhook E-Billing (Futursowax).
Favoris (films & séries) (6)
/api/favorites/movies
Retirer film des favoris.
/api/favorites/movies
Ajouter film en favori.
/api/favorites/movies/:userID
Films favoris d'un user.
/api/favorites/shows
Retirer série des favoris.
/api/favorites/shows
Ajouter série en favori.
/api/favorites/shows/:userID
Séries favorites d'un user.
Favoris (chaînes IPTV) (3)
/api/favorite-channels/favorites/channels
Retirer chaîne des favoris.
/api/favorite-channels/favorites/channels
Ajouter chaîne en favori.
/api/favorite-channels/favorites/channels/:userID
Chaînes favorites.
Historique de visionnage (4)
/api/history/episodes
Marquer épisode vu.
/api/history/episodes/:userID
Historique épisodes.
/api/history/movies
Marquer film vu.
/api/history/movies/:userID
Historique films.
Commentaires (9)
/api/comments
Liste tous les commentaires.
/api/comments
Ajouter commentaire.
/api/comments/:id
Supprimer commentaire.
/api/comments/movies
Supprimer comment film.
/api/comments/movies
Comment sur film.
/api/comments/movies/:movieID
Comments d'un film.
/api/comments/shows
Supprimer comment série.
/api/comments/shows
Comment sur série.
/api/comments/shows/:showID
Comments d'une série.
Demandes de contenu (1)
/api/content/request
Demander un contenu manquant.
Notifications (1)
/api/notifications
Mes notifications.
Stockage (1)
/api/storage/init
Administration (2)
/api/admin/create
Créer compte admin.
/api/admin/login
Login admin.
Admin — Timestamps intro/credits (3)
/api/admin/timestamps/:tvdbId
/api/admin/timestamps/:tvdbId/:season/:episode
/api/admin/timestamps/:tvdbId/:season/:episode
Paiements — E-Billing (Mobile Money)
Intégration Futursowax E-Billing pour encaisser les abonnements via Airtel Money, Moov Money et carte bancaire (XAF / FCFA, Gabon). Architecture hybride : le backend orchestre, le frontend redirige vers le portail E-Billing.
- Front →
POST /api/payments/initavec planId + phoneNumber - Backend crée Payment
PENDINGen BD + appelle E-Billing - Front redirige
window.location = portalUrl→ portail E-Billing - User paie → E-Billing déclenche
POST /api/payments/webhook/ebilling - Webhook : update status
SUCCESS+ créeSubscription ACTIVE - Front poll
GET /:id/statustoutes les 3s → affiche succès
/api/payments/init
Crée un paiement PENDING et retourne l'URL du portail E-Billing vers laquelle rediriger l'utilisateur.
Auth Bearer requise.
Body
| Nom | Type | Description | Requis |
|---|---|---|---|
| planId | int | ID du plan souscrit (non-Trial) | Oui |
| phoneNumber | String | 9 chiffres format Gabon (ex: 074000000) | Recommandé |
| paymentMethod | String | MOBILE_MONEY (défaut), CARD, PAYPAL... | Non |
Réponse 201
{
"success": true,
"data": {
"paymentId": 42,
"reference": "DW_1746999999999_A1B2C3D4E5F6",
"portalUrl": "https://futursowax.com/paiement/portal.php?bill=BILL_42&...",
"billId": "BILL_42",
"amount": 9990,
"plan": { "id": 4, "name": "Premium" }
}
}
/api/payments/webhook/ebilling?secret=...
Public (sans Bearer). Reçoit les notifications server-to-server d'E-Billing.
Vérifie le secret partagé, double-check via check_status.php, puis active la Subscription si SUCCESS.
Idempotent : retraite-le sans risque.
Body (JSON ou form-urlencoded)
| Nom | Type | Description |
|---|---|---|
| ref | String | Référence DayWatch (envoyée à E-Billing au init) |
| order | String | billId E-Billing |
| amount | Int | Montant payé (FCFA) |
| status | String | completed ou failed |
/api/payments/:id/status
Renvoie le statut courant du paiement (depuis la BD locale). Utilisé par le front en polling toutes les 3s après le retour du portail.
Réponse
{
"success": true,
"data": {
"id": 42,
"status": "SUCCESS",
"reference": "DW_...",
"amount": "9990.00",
"gateway": "EBILLING",
"planId": 4,
"transactionDate": "2026-05-12T14:32:11Z"
}
}
/api/payments/:id/verify
Force une re-vérification auprès d'E-Billing si le webhook a été raté (fallback manuel via bouton "Vérifier mon paiement").
Si confirmé SUCCESS, crée la Subscription.
/api/payments/me
Historique des paiements de l'utilisateur connecté.
Exemple complet (front)
// 1. Initier le paiement
const initPayment = async (planId, phoneNumber) => {
const res = await fetch('/api/payments/init', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ planId, phoneNumber })
});
const { data } = await res.json();
sessionStorage.setItem('currentPaymentId', data.paymentId);
window.location.href = data.portalUrl; // → E-Billing
};
// 2. Sur la page "payment/result" : polling
const pollPaymentStatus = async () => {
const paymentId = sessionStorage.getItem('currentPaymentId');
for (let i = 0; i < 100; i++) { // max 5min
const res = await fetch(`/api/payments/${paymentId}/status`, {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
const { data } = await res.json();
if (data.status === 'SUCCESS') {
showSuccess('Paiement confirmé ! Abonnement actif.');
return;
}
if (data.status === 'FAILED' || data.status === 'EXPIRED') {
showError('Paiement échoué : ' + (data.failureReason || ''));
return;
}
await new Promise(r => setTimeout(r, 3000)); // poll 3s
}
};
docs/PAYMENTS.md contient les détails (sécurité webhook, edge cases, multi-PSP futur, rollback).
Sports Live — Matchs & Diffuseurs
Agrégation des événements sportifs (foot, basket, MMA, boxe) depuis TheSportsDB enrichis via API-Football (free tier 100 req/jour), avec matching automatique broadcaster → chaîne IPTV et signalement si chaîne manquante.
GET /api/sports-live/events/:id/watch →
fuzzy-match les broadcasters (ex: "beIN Sports 1") avec les chaînes IPTV en BD,
retourne le streamUrl si trouvé, sinon enregistre un signalement et notifie l'admin par email après 5 reports.
/api/sports-live/events
Liste les événements sportifs à venir. Public.
Query params
| Nom | Type | Description |
|---|---|---|
| sport | String | Soccer, Basketball, Fighting... |
| region | String | FR, INT (défaut : tous) |
| limit | Int | Max 50 par défaut |
Réponse
{
"success": true,
"count": 3,
"data": [
{
"id": 12,
"title": "PSG vs OM",
"sport": "Soccer",
"competition": "Ligue 1",
"homeTeam": "PSG",
"awayTeam": "OM",
"eventTime": "2026-05-15T19:00:00Z",
"broadcasters": ["Canal+ Sport", "Prime Video"],
"region": "FR"
}
]
}
/api/sports-live/events/:id/watch
⭐ Cœur de la feature. Trouve le streamUrl en faisant le fuzzy-match broadcaster → chaîne IPTV. Si aucune chaîne dispo, enregistre un signalement et notifie l'admin. Auth Bearer requise.
Réponse 200 — Stream trouvé
{
"success": true,
"data": {
"eventId": 12,
"eventTitle": "PSG vs OM",
"streamUrl": "https://iptv.example.com/canalplus_sport.m3u8",
"channel": {
"id": 156,
"name": "Canal+ Sport HD",
"logoUrl": "https://...",
"matchedFrom": "Canal+ Sport",
"matchType": "fuzzy"
},
"alternatives": [
{ "channelId": 234, "name": "Prime Video France", "streamUrl": "..." }
]
}
}
Réponse 404 — Chaîne indisponible (signalement créé)
{
"success": false,
"code": "CHANNEL_UNAVAILABLE",
"message": "Aucune chaîne disponible ne diffuse cet event",
"eventId": 12,
"eventTitle": "PSG vs OM",
"missingChannels": ["Canal+ Sport", "Prime Video"],
"adminNotified": ["Canal+ Sport"] // si seuil de 5 atteint
}
/api/sports-live/sync
Déclenche le sync depuis TheSportsDB + enrichissement API-Football.
Aussi disponible en CLI : npm run sports:sync.
À mettre en cron toutes les 4h.
Body (optionnel)
| Nom | Type | Défaut |
|---|---|---|
| sports | String[] | ['football','basketball','fighting'] |
| days | Int | 1 (J + J+1) |
/api/sports-live/admin/unavailable-channels
Admin. Liste des chaînes IPTV demandées mais absentes du catalogue,
triées par reportCount décroissant. Permet d'identifier les chaînes prioritaires à acquérir.
/api/sports-live/admin/unavailable-channels/:id/resolve
Admin. Marque un signalement comme résolu (ex: après ajout de la chaîne au catalogue IPTV).
• TheSportsDB (gratuit, multi-sport) — key publique
3 ou Patreon ~3€/mois• API-Football (free 100 req/jour) — enrichit les broadcasters foot quand TheSportsDB n'en a pas
Configurer
THESPORTSDB_API_KEY et API_FOOTBALL_KEY dans .env.
iptvMatcherService.js normalise les noms
(lowercase, sans HD/SD/4K, sans ponctuation) puis utilise la distance de Levenshtein ≤ 2.
Exemples reconnus comme une même chaîne :"beIN Sports 1 HD" · "BEIN SPORT 1" · "beIN Sport 1"
Modèles de données
Structures de données utilisées par l'API avec exemples de réponses JSON.
Utilisateur (User)
| Champ | Type | Description |
|---|---|---|
| id | INT | Identifiant unique de l'utilisateur |
| username | VARCHAR(255) | Nom d'utilisateur |
| VARCHAR(255) | Adresse email (unique) | |
| phoneNumber | VARCHAR(20) | Numéro de téléphone |
| status | ENUM | Statut du compte (PENDING, ACTIVE, SUSPENDED) |
| role | ENUM | Rôle de l'utilisateur (USER, ADMIN) |
| trialStartDate | DATETIME | Date de début de la période d'essai |
| trialEndDate | DATETIME | Date de fin de la période d'essai |
| createdAt | TIMESTAMP | Date de création du compte |
| updatedAt | TIMESTAMP | Date de dernière mise à jour |
{
"id": 42,
"username": "johndoe",
"email": "john.doe@example.com",
"phoneNumber": "+33612345678",
"status": "ACTIVE",
"role": "USER",
"trialStartDate": "2023-04-15T14:30:45Z",
"trialEndDate": "2023-05-15T14:30:45Z",
"createdAt": "2023-04-15T14:30:45Z",
"updatedAt": "2023-09-22T09:15:33Z",
"profiles": {
"count": 2,
"data": [
{
"id": 1,
"profileName": "johndoe",
"profileType": "adult",
"isDefault": true
},
{
"id": 2,
"profileName": "Kids",
"profileType": "child",
"isDefault": false
}
]
}
}
Profil (Profile)
| Champ | Type | Description |
|---|---|---|
| id | INT | Identifiant unique du profil |
| userID | INT | Référence à l'utilisateur propriétaire |
| profileName | VARCHAR(255) | Nom du profil |
| profileAvatarUrl | VARCHAR(500) | URL de l'avatar du profil |
| profileType | ENUM | Type de profil (adult, child, teen) |
| preferences | JSON | Préférences personnalisées du profil |
| isDefault | BOOLEAN | Indique si c'est le profil par défaut |
| isActive | BOOLEAN | Indique si le profil est actif |
| createdAt | TIMESTAMP | Date de création du profil |
| updatedAt | TIMESTAMP | Date de dernière mise à jour |
{
"id": 1,
"userID": 42,
"profileName": "johndoe",
"profileAvatarUrl": "/avatars/default.png",
"profileType": "adult",
"preferences": {
"language": "fr",
"contentRating": "R",
"autoplay": true,
"subtitles": true,
"audioQuality": "5.1",
"videoQuality": "4K"
},
"isDefault": true,
"isActive": true,
"createdAt": "2023-04-15T14:30:45Z",
"updatedAt": "2023-09-22T09:15:33Z",
"watchHistory": {
"count": 45,
"lastWatched": "2023-09-22T08:30:00Z"
},
"favorites": {
"count": 12,
"movies": 8,
"series": 4
}
}
Film (Movie)
| Champ | Type | Description |
|---|---|---|
| id | INT | Identifiant unique du film |
| tmdbId | VARCHAR(255) | Identifiant TMDB (unique) |
| title | VARCHAR(255) | Titre du film |
| overview | TEXT | Synopsis du film |
| posterPath | VARCHAR(255) | Chemin vers l'affiche |
| backdropPath | VARCHAR(255) | Chemin vers l'image de fond |
| year | INT | Année de sortie |
| rating | DECIMAL(3,1) | Note moyenne (sur 10) |
| videoUrl | VARCHAR(255) | URL du fichier vidéo |
| isPopular | BOOLEAN | Indique si le film est populaire |
{
"id": 1287,
"tmdbId": "tt1234567",
"title": "Exemple de Film",
"overview": "Un film passionnant qui raconte l'histoire d'un développeur qui crée une API de streaming vidéo.",
"posterPath": "/images/posters/example-movie.jpg",
"backdropPath": "/images/backdrops/example-movie.jpg",
"year": 2023,
"rating": 8.4,
"videoUrl": "/videos/example-movie.mp4",
"isPopular": true,
"genres": ["Action", "Drame", "Science-Fiction"],
"duration": 126,
"director": "Jane Smith",
"cast": [
{"name": "Actor One", "character": "Developer"},
{"name": "Actor Two", "character": "Designer"},
{"name": "Actor Three", "character": "Project Manager"}
],
"relatedMovies": [
{"id": 1288, "title": "Exemple de Film 2"},
{"id": 1289, "title": "Exemple de Film 3"}
]
}
Abonnement (Subscription)
| Champ | Type | Description |
|---|---|---|
| id | INT | Identifiant unique de l'abonnement |
| userId | INT | Référence à l'utilisateur |
| planId | INT | Référence au plan d'abonnement |
| status | ENUM | Statut de l'abonnement (PENDING, ACTIVE, EXPIRED) |
| startDate | DATETIME | Date de début de l'abonnement |
| endDate | DATETIME | Date de fin de l'abonnement |
{
"id": 789,
"userId": 42,
"planId": 2,
"plan": {
"name": "Premium",
"description": "Accès à tout le contenu en 4K",
"price": 14.99,
"intervalType": "MONTH"
},
"status": "ACTIVE",
"autoRenew": true,
"startDate": "2023-04-15T00:00:00Z",
"endDate": "2024-04-15T23:59:59Z",
"payment": {
"method": "CARD",
"lastFour": "1234",
"nextBillingDate": "2024-04-15T00:00:00Z"
}
}
Exemples de Code
Exemples pratiques pour interagir avec l'API DayWatch depuis différents environnements de développement.
Classe utilitaire pour l'API
// apiService.js - Classe utilitaire pour interagir avec l'API
class DayWatchAPI {
constructor(baseUrl = 'http://api.daywatch.local') {
this.baseUrl = baseUrl;
this.token = localStorage.getItem('daywatch_token');
}
setToken(token) {
this.token = token;
localStorage.setItem('daywatch_token', token);
}
clearToken() {
this.token = null;
localStorage.removeItem('daywatch_token');
}
async request(endpoint, method = 'GET', data = null) {
const url = `${this.baseUrl}${endpoint}`;
const headers = {
'Content-Type': 'application/json'
};
if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`;
}
const options = {
method,
headers
};
if (data && (method === 'POST' || method === 'PUT')) {
options.body = JSON.stringify(data);
}
const response = await fetch(url, options);
// Si la réponse est 401, le token est probablement expiré
if (response.status === 401) {
this.clearToken();
throw new Error('Session expirée. Veuillez vous reconnecter.');
}
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || 'Une erreur est survenue');
}
return result;
}
// Authentification
async login(email, password) {
const data = await this.request('/api/users/login', 'POST', { email, password });
this.setToken(data.token);
return data.user;
}
// Films
getMovies(page = 1, limit = 20) {
return this.request(`/api/movies?page=${page}&limit=${limit}`);
}
getPopularMovies() {
return this.request('/api/movies/popular');
}
getMovieDetails(id) {
return this.request(`/api/movies/${id}`);
}
// Utilisateurs
getUserProfile() {
return this.request('/api/users/profile');
}
updateUserProfile(userData) {
return this.request('/api/users/profile', 'PUT', userData);
}
// Favoris
getFavorites() {
return this.request('/api/favorites');
}
addToFavorites(mediaId, mediaType = 'movie') {
return this.request('/api/favorites', 'POST', { mediaId, mediaType });
}
removeFromFavorites(id) {
return this.request(`/api/favorites/${id}`, 'DELETE');
}
}
// Exemple d'utilisation
const api = new DayWatchAPI();
// Authentification
async function authenticateUser() {
try {
const user = await api.login('dev@daywatch.local', 'password123');
console.log('Connecté:', user);
return user;
} catch (error) {
console.error('Erreur d\'authentification:', error);
}
}
// Récupérer et afficher les films populaires
async function displayPopularMovies() {
try {
const movies = await api.getPopularMovies();
console.log('Films populaires:', movies);
// Exemple d'affichage dans le DOM
const container = document.getElementById('movies-container');
movies.forEach(movie => {
const movieEl = document.createElement('div');
movieEl.innerHTML = `
${movie.title}
${movie.overview.substring(0, 100)}...
`;
container.appendChild(movieEl);
});
} catch (error) {
console.error('Erreur:', error);
}
}
Classe PHP pour l'API
baseUrl = $baseUrl;
$this->token = isset($_SESSION['daywatch_token']) ? $_SESSION['daywatch_token'] : null;
}
public function setToken($token) {
$this->token = $token;
$_SESSION['daywatch_token'] = $token;
}
public function clearToken() {
$this->token = null;
unset($_SESSION['daywatch_token']);
}
public function request($endpoint, $method = 'GET', $data = null) {
$url = $this->baseUrl . $endpoint;
$ch = curl_init($url);
$headers = [
'Content-Type: application/json',
'Accept: application/json'
];
if ($this->token) {
$headers[] = 'Authorization: Bearer ' . $this->token;
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if ($method === 'POST' || $method === 'PUT') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if ($data) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
} else if ($method === 'DELETE') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
$result = json_decode($response, true);
// Si la réponse est 401, le token est probablement expiré
if ($httpCode === 401) {
$this->clearToken();
throw new Exception('Session expirée. Veuillez vous reconnecter.');
}
if ($httpCode >= 400) {
throw new Exception($result['error'] ?? 'Une erreur est survenue (' . $httpCode . ')');
}
return $result;
}
// Authentification
public function login($email, $password) {
$data = $this->request('/api/users/login', 'POST', [
'email' => $email,
'password' => $password
]);
$this->setToken($data['token']);
return $data['user'];
}
// Films
public function getMovies($page = 1, $limit = 20) {
return $this->request("/api/movies?page=$page&limit=$limit");
}
public function getPopularMovies() {
return $this->request('/api/movies/popular');
}
public function getMovieDetails($id) {
return $this->request("/api/movies/$id");
}
// Utilisateurs
public function getUserProfile() {
return $this->request('/api/users/profile');
}
}
// Exemple d'utilisation:
session_start();
$api = new DayWatchAPI();
try {
// Connexion
$user = $api->login('dev@daywatch.local', 'password123');
echo "Connecté en tant que: " . $user['username'] . "\n";
// Récupérer les films populaires
$movies = $api->getPopularMovies();
echo "Films populaires:\n";
foreach ($movies as $movie) {
echo " - " . $movie['title'] . " (" . $movie['year'] . ")\n";
}
} catch (Exception $e) {
echo "Erreur: " . $e->getMessage() . "\n";
}
Classe Python pour l'API
# daywatch_api.py
import requests
import json
from typing import Dict, List, Optional, Any, Union
class DayWatchAPI:
def __init__(self, base_url: str = "http://api.daywatch.local"):
self.base_url = base_url
self.token = None
self.session = requests.Session()
def set_token(self, token: str) -> None:
"""Définit le token JWT pour l'authentification"""
self.token = token
self.session.headers.update({"Authorization": f"Bearer {token}"})
def request(self, endpoint: str, method: str = "GET",
data: Optional[Dict] = None) -> Union[Dict, List]:
"""
Effectue une requête à l'API DayWatch
Args:
endpoint: Point de terminaison de l'API (débute par /)
method: Méthode HTTP (GET, POST, PUT, DELETE)
data: Données à envoyer (pour POST et PUT)
Returns:
Données JSON désérialisées de la réponse
"""
url = f"{self.base_url}{endpoint}"
headers = {"Content-Type": "application/json"}
if self.token:
headers["Authorization"] = f"Bearer {self.token}"
if method == "GET":
response = self.session.get(url, headers=headers)
elif method == "POST":
response = self.session.post(url, headers=headers,
data=json.dumps(data) if data else None)
elif method == "PUT":
response = self.session.put(url, headers=headers,
data=json.dumps(data) if data else None)
elif method == "DELETE":
response = self.session.delete(url, headers=headers)
else:
raise ValueError(f"Méthode HTTP non supportée: {method}")
# Vérifier les erreurs
if response.status_code == 401:
self.token = None
raise Exception("Session expirée. Veuillez vous reconnecter.")
try:
result = response.json()
except json.JSONDecodeError:
raise Exception(f"Réponse non-JSON: {response.text}")
if not response.ok:
raise Exception(result.get("error", f"Erreur API: {response.status_code}"))
return result
# Méthodes d'authentification
def login(self, email: str, password: str) -> Dict:
"""Authentifie un utilisateur et récupère un token JWT"""
data = self.request("/api/users/login", "POST",
{"email": email, "password": password})
self.set_token(data["token"])
return data["user"]
# Méthodes pour les films
def get_movies(self, page: int = 1, limit: int = 20) -> Dict:
"""Récupère une liste de films paginée"""
return self.request(f"/api/movies?page={page}&limit={limit}")
def get_popular_movies(self) -> List[Dict]:
"""Récupère la liste des films populaires"""
return self.request("/api/movies/popular")
def get_movie_details(self, movie_id: int) -> Dict:
"""Récupère les détails d'un film spécifique"""
return self.request(f"/api/movies/{movie_id}")
# Méthodes pour les utilisateurs
def get_user_profile(self) -> Dict:
"""Récupère le profil de l'utilisateur connecté"""
return self.request("/api/users/profile")
# Exemple d'utilisation
def main():
api = DayWatchAPI()
try:
# Connexion
user = api.login("dev@daywatch.local", "password123")
print(f"Connecté en tant que: {user['username']}")
# Récupérer les films populaires
movies = api.get_popular_movies()
print("\nFilms populaires:")
for movie in movies:
print(f" - {movie['title']} ({movie['year']}) - Note: {movie['rating']}/10")
# Récupérer les détails d'un film
if movies:
movie_id = movies[0]["id"]
details = api.get_movie_details(movie_id)
print(f"\nDétails du film '{details['title']}': {details['overview'][:100]}...")
except Exception as e:
print(f"Erreur: {e}")
if __name__ == "__main__":
main()