2nzi commited on
Commit
923cd30
·
verified ·
1 Parent(s): c7da95d

update backend with video upload on HF

Browse files
app/__init__.py ADDED
File without changes
app/api/__init__.py ADDED
File without changes
app/api/endpoints/__init__.py ADDED
File without changes
app/api/endpoints/videos.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, File, UploadFile, Depends, HTTPException, Form
2
+ from firebase_admin import firestore
3
+ import hashlib
4
+ import uuid
5
+ from ...core.auth import require_role, get_user
6
+ from typing import List, Optional, Dict
7
+ from app.services.youtube_downloader import download_youtube_video, parse_urls
8
+ from ...services.processor import process_video
9
+ from typing import Dict
10
+ from ...core.firebase import db # Modifier cette ligne
11
+
12
+
13
+ router = APIRouter()
14
+
15
+
16
+
17
+ @router.get("/videos")
18
+ async def get_videos(user_info=Depends(get_user)):
19
+ try:
20
+ # Récupération des vidéos globales
21
+ videos_ref = db.collection('videos')
22
+ videos = []
23
+
24
+ # Récupération du statut utilisateur
25
+ user_status_ref = db.collection('user_video_status').document(user_info['uid'])
26
+ user_status_doc = user_status_ref.get()
27
+ user_statuses = user_status_doc.to_dict() if user_status_doc.exists else {"video_status": []}
28
+
29
+ # Fusion des données
30
+ for doc in videos_ref.stream():
31
+ video_data = doc.to_dict()
32
+ video_data['id'] = doc.id
33
+
34
+ # Ajout du statut spécifique à l'utilisateur
35
+ user_status = next(
36
+ (status for status in user_status_doc.get('video_status', [])
37
+ if status['uuid'] == doc.id),
38
+ None
39
+ )
40
+ video_data['user_status'] = user_status['status'] if user_status else 'ready'
41
+
42
+ videos.append(video_data)
43
+
44
+ return {"videos": videos}
45
+ except Exception as e:
46
+ raise HTTPException(status_code=500, detail=str(e))
47
+
48
+
49
+
50
+
51
+ @router.post("/videos/upload")
52
+ async def upload_video(
53
+ sport_id: str = Form(...),
54
+ file: Optional[UploadFile] = File(None),
55
+ youtube_url: Optional[str] = Form(None),
56
+ user_info=Depends(require_role(["admin", "user_intern"]))
57
+ ):
58
+ try:
59
+ responses = []
60
+ urls_to_process = []
61
+
62
+ # Collecter toutes les URLs à traiter
63
+ if youtube_url:
64
+ # Cas URL YouTube directe - peut contenir plusieurs URLs séparées par des points-virgules
65
+ urls_to_process.extend(parse_urls(youtube_url))
66
+ elif file and file.content_type in ['text/plain', 'text/csv']:
67
+ # Cas fichier texte avec URLs
68
+ text_content = (await file.read()).decode('utf-8')
69
+ urls_to_process.extend(parse_urls(text_content))
70
+ elif file:
71
+ # Cas upload direct de vidéo
72
+ content = await file.read()
73
+ return await process_single_video(content, sport_id, user_info, file.filename)
74
+
75
+ if not urls_to_process and not file:
76
+ raise HTTPException(status_code=400, detail="Aucune URL YouTube valide trouvée")
77
+
78
+ # Traiter chaque URL
79
+ for url in urls_to_process:
80
+ try:
81
+ print(f"[DEBUG] Processing YouTube URL: {url}")
82
+ content = await download_youtube_video(url)
83
+ result = await process_single_video(content, sport_id, user_info, f"YouTube: {url}")
84
+ responses.append({
85
+ "url": url,
86
+ "status": "success",
87
+ "video_id": result["video_id"]
88
+ })
89
+ except Exception as e:
90
+ print(f"[ERROR] Failed to process URL {url}: {str(e)}")
91
+ responses.append({
92
+ "url": url,
93
+ "status": "error",
94
+ "error": str(e)
95
+ })
96
+
97
+ return {
98
+ "message": f"Traitement terminé pour {len(responses)} vidéos",
99
+ "results": responses
100
+ }
101
+
102
+ except Exception as e:
103
+ print(f"[ERROR] Upload failed: {str(e)}")
104
+ raise HTTPException(status_code=500, detail=str(e))
105
+
106
+ async def process_single_video(content: bytes, sport_id: str, user_info: dict, title: str):
107
+ """Traite une seule vidéo et retourne son ID"""
108
+ video_uuid = str(uuid.uuid4())
109
+
110
+ # Calcul du hash MD5
111
+ md5_hash = hashlib.md5()
112
+ md5_hash.update(content)
113
+ file_hash = md5_hash.hexdigest()
114
+
115
+ # Vérification des doublons
116
+ existing_videos = db.collection('videos').where('md5_hash', '==', file_hash).get()
117
+ if len(existing_videos) > 0:
118
+ return {"message": "Cette vidéo existe déjà", "video_id": existing_videos[0].id}
119
+
120
+ # Création de l'entrée Firestore
121
+ video_data = {
122
+ "uuid": video_uuid,
123
+ "sport_id": sport_id,
124
+ "upload_date": firestore.SERVER_TIMESTAMP,
125
+ "uploaded_by": user_info['uid'],
126
+ "title": title,
127
+ "status": "downloading",
128
+ "md5_hash": file_hash,
129
+ "paths": {
130
+ "raw": f"{sport_id}/raw/{video_uuid}_raw.mp4",
131
+ "compressed": f"{sport_id}/compressed/{video_uuid}_compressed.mp4",
132
+ "reduced_videos": []
133
+ },
134
+ "scenes": [],
135
+ "reduced_scenes": []
136
+ }
137
+
138
+ db.collection('videos').document(video_uuid).set(video_data)
139
+ await process_video(video_uuid, content)
140
+
141
+ return {"message": "Upload initié", "video_id": video_uuid}
142
+
143
+
144
+
145
+ @router.put("/videos/{video_id}/status")
146
+ async def update_video_status(
147
+ video_id: str,
148
+ status: str,
149
+ user_info=Depends(get_user)
150
+ ):
151
+ try:
152
+ # Vérifier si la vidéo existe
153
+ video_ref = db.collection('videos').document(video_id)
154
+ video_doc = video_ref.get()
155
+
156
+ if not video_doc.exists:
157
+ raise HTTPException(status_code=404, detail="Vidéo non trouvée")
158
+
159
+ # Mettre à jour ou créer le statut utilisateur
160
+ user_status_ref = db.collection('user_video_status').document(user_info['uid'])
161
+ user_status_doc = user_status_ref.get()
162
+
163
+ if user_status_doc.exists:
164
+ statuses = user_status_doc.get('video_status', [])
165
+ # Mettre à jour le statut existant ou ajouter un nouveau
166
+ status_updated = False
167
+ for s in statuses:
168
+ if s['uuid'] == video_id:
169
+ s['status'] = status
170
+ status_updated = True
171
+ break
172
+
173
+ if not status_updated:
174
+ statuses.append({
175
+ 'uuid': video_id,
176
+ 'status': status
177
+ })
178
+
179
+ user_status_ref.update({'video_status': statuses})
180
+ else:
181
+ # Créer un nouveau document avec le statut
182
+ user_status_ref.set({
183
+ 'video_status': [{
184
+ 'uuid': video_id,
185
+ 'status': status
186
+ }]
187
+ })
188
+
189
+ return {"message": "Statut mis à jour avec succès"}
190
+
191
+ except Exception as e:
192
+ raise HTTPException(status_code=500, detail=str(e))
app/core/__init__.py ADDED
File without changes
app/core/auth.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import Depends, HTTPException, status
2
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3
+ from firebase_admin import auth
4
+ from .firebase import db
5
+
6
+ security = HTTPBearer()
7
+
8
+ def get_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
9
+ try:
10
+ token = credentials.credentials
11
+ decoded_token = auth.verify_id_token(token)
12
+
13
+ # Récupérer le rôle depuis Firestore
14
+ user_id = decoded_token['uid']
15
+ user_doc = db.collection('users').document(user_id).get()
16
+
17
+ if not user_doc.exists:
18
+ raise HTTPException(
19
+ status_code=status.HTTP_401_UNAUTHORIZED,
20
+ detail="User not found in Firestore"
21
+ )
22
+
23
+ # Ajouter le rôle aux informations du token
24
+ user_data = user_doc.to_dict()
25
+ decoded_token['role'] = user_data.get('role', 'user_extern')
26
+
27
+ return decoded_token
28
+ except Exception as e:
29
+ raise HTTPException(
30
+ status_code=status.HTTP_401_UNAUTHORIZED,
31
+ detail=f"Invalid authentication credentials: {str(e)}"
32
+ )
33
+
34
+ def require_role(allowed_roles):
35
+ def role_checker(user_info=Depends(get_user)):
36
+ if user_info['role'] not in allowed_roles:
37
+ raise HTTPException(
38
+ status_code=status.HTTP_403_FORBIDDEN,
39
+ detail="Insufficient permissions"
40
+ )
41
+ return user_info
42
+ return role_checker
app/core/config.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic_settings import BaseSettings
2
+
3
+ class Settings(BaseSettings):
4
+ PROJECT_NAME: str = "JunsenAI Backend"
5
+ API_V1_STR: str = "/api"
6
+ FIREBASE_CREDENTIALS: str = "serviceAccountKey.json"
7
+ STORAGE_PATH: str = "media_storage"
8
+ STORAGE_BUCKET: str = "create-user-e4e2c.appspot.com" # Ajoutez cette ligne
9
+
10
+ class Config:
11
+ case_sensitive = True
12
+
13
+ settings = Settings()
app/core/firebase.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import firebase_admin
2
+ from firebase_admin import credentials, firestore, storage
3
+ import os
4
+ import json
5
+ from dotenv import load_dotenv
6
+ from .config import settings
7
+
8
+ # Charger les variables d'environnement
9
+ load_dotenv()
10
+
11
+ def get_firebase_app():
12
+ if not firebase_admin._apps:
13
+ try:
14
+ firebase_credentials = os.getenv("FIREBASE_CREDENTIALS")
15
+ if not firebase_credentials:
16
+ raise ValueError("FIREBASE_CREDENTIALS environment variable is not set")
17
+
18
+ cred_dict = json.loads(firebase_credentials)
19
+ cred = credentials.Certificate(cred_dict)
20
+ return firebase_admin.initialize_app(cred, {
21
+ 'storageBucket': settings.STORAGE_BUCKET
22
+ })
23
+ except Exception as e:
24
+ print(f"Firebase initialization error: {str(e)}")
25
+ raise
26
+ return firebase_admin.get_app()
27
+
28
+ def get_firestore_db():
29
+ get_firebase_app()
30
+ return firestore.client()
31
+
32
+ def get_storage_bucket():
33
+ get_firebase_app()
34
+ return storage.bucket()
35
+
36
+ # Créer des instances uniques
37
+ db = get_firestore_db()
38
+ bucket = get_storage_bucket()
app/models/__init__.py ADDED
File without changes
app/models/video.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import List, Optional
3
+ from datetime import datetime
4
+
5
+ class Scene(BaseModel):
6
+ start: str
7
+ end: str
8
+ recognized_sport: str
9
+ confidence: float
10
+
11
+ class ReducedScene(BaseModel):
12
+ clip_id: str
13
+ scenes: List[Scene]
14
+
15
+ class VideoPaths(BaseModel):
16
+ raw: str
17
+ compressed: str
18
+ reduced_videos: List[str]
19
+
20
+ class Video(BaseModel):
21
+ uuid: str
22
+ sport_id: str
23
+ upload_date: datetime
24
+ uploaded_by: str
25
+ title: str
26
+ status: str
27
+ md5_hash: Optional[str]
28
+ paths: VideoPaths
29
+ scenes: List[Scene] = []
30
+ reduced_scenes: List[ReducedScene] = []
app/services/__init__.py ADDED
File without changes
app/services/processor.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from firebase_admin import firestore
2
+ import os
3
+ from ..core.config import settings
4
+ from ..core.firebase import db
5
+ from huggingface_hub import HfApi, create_repo
6
+ import tempfile
7
+ from .video_processing.hf_upload import HFUploader
8
+ from .video_processing.compression import compress_video
9
+
10
+ async def process_video(video_uuid: str, content: bytes):
11
+ temp_files = []
12
+ try:
13
+ video_ref = db.collection('videos').document(video_uuid)
14
+ video_data = video_ref.get().to_dict()
15
+
16
+ hf_uploader = HFUploader()
17
+ sport_id = video_data['sport_id']
18
+
19
+ # Ensure folder structure exists
20
+ hf_uploader.ensure_folder_structure(sport_id)
21
+
22
+ # Create temp files
23
+ temp_raw_file = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False)
24
+ temp_compressed_file = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False)
25
+ temp_files.extend([temp_raw_file.name, temp_compressed_file.name])
26
+
27
+ # Write raw video and close file
28
+ temp_raw_file.write(content)
29
+ temp_raw_file.close()
30
+
31
+ # Compress video
32
+ compress_video(temp_raw_file.name, temp_compressed_file.name)
33
+ temp_compressed_file.close()
34
+
35
+ # Upload both versions
36
+ raw_url = hf_uploader.upload_video(
37
+ temp_raw_file.name,
38
+ f"{sport_id}/raw/{video_uuid}.mp4"
39
+ )
40
+
41
+ compressed_url = hf_uploader.upload_video(
42
+ temp_compressed_file.name,
43
+ f"{sport_id}/compressed/{video_uuid}.mp4"
44
+ )
45
+
46
+ # Update Firestore
47
+ video_ref.update({
48
+ "raw_video_url": raw_url,
49
+ "compressed_video_url": compressed_url,
50
+ "status": "ready"
51
+ })
52
+
53
+ except Exception as e:
54
+ print(f"Erreur lors du traitement de la vidéo {video_uuid}: {str(e)}")
55
+ video_ref.update({"status": "error", "error": str(e)})
56
+
57
+ finally:
58
+ # Clean up temp files
59
+ for temp_file in temp_files:
60
+ try:
61
+ if os.path.exists(temp_file):
62
+ os.unlink(temp_file)
63
+ except Exception as e:
64
+ print(f"Erreur lors de la suppression du fichier temporaire {temp_file}: {str(e)}")
app/services/video_processing/__init__.py ADDED
File without changes
app/services/video_processing/compression.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ from fastapi import HTTPException
3
+
4
+ def compress_video(input_path: str, output_path: str, resolution: str = "192x144"):
5
+ """Compress video using FFmpeg for maximum reduction, with audio removed."""
6
+ command = [
7
+ "ffmpeg",
8
+ "-y", # Force overwrite without asking
9
+ "-i", input_path,
10
+ "-vf", f"scale={resolution}",
11
+ "-an", # Remove audio
12
+ "-c:v", "libx265",
13
+ "-preset", "medium", # Changed from veryslow for better speed/compression balance
14
+ "-crf", "32",
15
+ "-maxrate", "150k",
16
+ "-bufsize", "200k",
17
+ "-r", "10",
18
+ output_path
19
+ ]
20
+
21
+ try:
22
+ subprocess.run(command, check=True, capture_output=True, text=True)
23
+ except subprocess.CalledProcessError as e:
24
+ print(f"Compression error: {e.stderr}")
25
+ raise HTTPException(status_code=500, detail="Video compression failed")
app/services/video_processing/hf_upload.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from huggingface_hub import HfApi, create_repo
2
+ import os
3
+
4
+ class HFUploader:
5
+ def __init__(self):
6
+ self.hf_api = HfApi()
7
+ self.repo_id = "HUGGINGFACE_REPO_ID"
8
+ self._ensure_repo_exists()
9
+
10
+ def _ensure_repo_exists(self):
11
+ try:
12
+ self.hf_api.repo_info(repo_id=self.repo_id, repo_type="dataset")
13
+ except Exception:
14
+ create_repo(self.repo_id, private=False, repo_type="dataset",
15
+ token=os.getenv("HUGGINGFACE_TOKEN"))
16
+
17
+ def ensure_folder_structure(self, sport_id: str):
18
+ paths = [
19
+ f"{sport_id}/raw",
20
+ f"{sport_id}/compressed"
21
+ ]
22
+ for path in paths:
23
+ try:
24
+ self.hf_api.upload_file(
25
+ path_or_fileobj="",
26
+ path_in_repo=f"{path}/.gitkeep",
27
+ repo_id=self.repo_id,
28
+ repo_type="dataset",
29
+ token=os.getenv("HUGGINGFACE_TOKEN")
30
+ )
31
+ except Exception:
32
+ pass
33
+
34
+ def upload_video(self, file_path: str, destination_path: str):
35
+ self.hf_api.upload_file(
36
+ path_or_fileobj=file_path,
37
+ path_in_repo=destination_path,
38
+ repo_id=self.repo_id,
39
+ repo_type="dataset",
40
+ token=os.getenv("HUGGINGFACE_TOKEN")
41
+ )
42
+ return f"https://huggingface.co/datasets/{self.repo_id}/raw/main/{destination_path}"
app/services/youtube_downloader.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yt_dlp
2
+ import io
3
+ import asyncio
4
+ from typing import List
5
+ import os
6
+
7
+ async def download_youtube_video(url: str) -> bytes:
8
+ try:
9
+ print(f"[DEBUG] Téléchargement de {url}")
10
+
11
+ # Créer un dossier temporaire pour le téléchargement
12
+ temp_dir = "temp_downloads"
13
+ os.makedirs(temp_dir, exist_ok=True)
14
+
15
+ # Configuration de yt-dlp
16
+ ydl_opts = {
17
+ 'format': 'bestvideo[ext=mp4][vcodec^=avc1]+bestaudio[ext=m4a]/mp4',
18
+ 'merge_output_format': 'mp4',
19
+ 'quiet': True,
20
+ 'no_warnings': True,
21
+ 'outtmpl': os.path.join(temp_dir, '%(title)s.%(ext)s'),
22
+ }
23
+
24
+ def custom_download(d):
25
+ if d['status'] == 'finished':
26
+ print(f"[DEBUG] Téléchargement terminé pour {url}")
27
+
28
+ ydl_opts['progress_hooks'] = [custom_download]
29
+
30
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
31
+ # Télécharger la vidéo
32
+ info = ydl.extract_info(url, download=True)
33
+ video_path = ydl.prepare_filename(info)
34
+
35
+ # Lire le fichier en bytes
36
+ with open(video_path, 'rb') as f:
37
+ content = f.read()
38
+
39
+ # Nettoyer le fichier temporaire
40
+ os.remove(video_path)
41
+
42
+ return content
43
+
44
+ except Exception as e:
45
+ print(f"[ERROR] Erreur téléchargement YouTube: {str(e)}")
46
+ raise Exception(f"Erreur téléchargement YouTube: {str(e)}")
47
+ finally:
48
+ # Nettoyer le dossier temporaire si vide
49
+ try:
50
+ os.rmdir(temp_dir)
51
+ except:
52
+ pass
53
+
54
+ def parse_urls(content: str) -> List[str]:
55
+ urls = [url.strip() for url in content.replace(';', '\n').split('\n')]
56
+ return [url for url in urls if url and ('youtube.com' in url or 'youtu.be' in url)]
57
+
58
+ async def test_download():
59
+ test_url = "https://www.youtube.com/watch?v=ksIfusO_DgA" # URL de test de surf
60
+ try:
61
+ content = await download_youtube_video(test_url)
62
+ print(f"[SUCCESS] Vidéo téléchargée avec succès! Taille: {len(content)} bytes")
63
+ return True
64
+ except Exception as e:
65
+ print(f"[ERROR] Test échoué: {str(e)}")
66
+ return False
67
+
68
+ def test_parse_urls():
69
+ test_content = """
70
+ https://www.youtube.com/watch?v=ksIfusO_DgA&ab_channel=NobodySurf%3ASurfingVideos
71
+ https://www.youtube.com/watch?v=GHDLZwccg8E&ab_channel=RIPITUP
72
+ """
73
+ urls = parse_urls(test_content)
74
+ print(f"[DEBUG] URLs trouvées: {urls}")
75
+ return len(urls) == 2
76
+
77
+ if __name__ == "__main__":
78
+ # Test de parse_urls
79
+ print("\nTest de parse_urls:")
80
+ if test_parse_urls():
81
+ print("[SUCCESS] Test parse_urls réussi!")
82
+ else:
83
+ print("[ERROR] Test parse_urls échoué")
84
+
85
+ # Test de download_youtube_video
86
+ print("\nTest de download_youtube_video:")
87
+ asyncio.run(test_download())
main.py CHANGED
@@ -1,100 +1,77 @@
1
-
2
- from fastapi import FastAPI, Depends, HTTPException, status, Response
3
- from fastapi.middleware.cors import CORSMiddleware
4
- from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
5
- from firebase_admin import auth, credentials, firestore
6
- import firebase_admin
7
- import os
8
- from dotenv import load_dotenv
9
-
10
- # Charger les variables d'environnement depuis le fichier .env
11
- load_dotenv()
12
-
13
- # Récupérer les variables d'environnement pour initialiser Firebase
14
- firebase_credentials = os.getenv("FIREBASE_CREDENTIALS")
15
-
16
- # Vérification de la présence de la clé
17
- if not firebase_credentials:
18
- raise ValueError("La variable d'environnement FIREBASE_CREDENTIALS n'est pas définie.")
19
-
20
- # Charger les informations de la clé Firebase
21
- import json
22
- try:
23
- firebase_credentials_dict = json.loads(firebase_credentials)
24
- if not isinstance(firebase_credentials_dict, dict):
25
- raise ValueError("FIREBASE_CREDENTIALS n'est pas un JSON valide.")
26
- except json.JSONDecodeError as e:
27
- raise ValueError("FIREBASE_CREDENTIALS n'est pas un JSON valide.") from e
28
-
29
- # Initialisation Firebase Admin
30
- cred = credentials.Certificate(firebase_credentials_dict)
31
- firebase_admin.initialize_app(cred)
32
- db = firestore.client()
33
-
34
- app = FastAPI()
35
-
36
- # Configuration CORS
37
- allowed_origins = [
38
- "*"
39
- ]
40
- app.add_middleware(
41
- CORSMiddleware,
42
- allow_origins=allowed_origins,
43
- allow_credentials=True,
44
- allow_methods=["POST", "GET"],
45
- allow_headers=["*"]
46
- )
47
-
48
- # Fonction pour vérifier le token Firebase et récupérer le rôle depuis Firestore
49
- def get_user(res: Response,
50
- cred: HTTPAuthorizationCredentials = Depends(HTTPBearer(auto_error=False))):
51
- if cred is None:
52
- raise HTTPException(
53
- status_code=status.HTTP_401_UNAUTHORIZED,
54
- detail="Bearer authentication required",
55
- headers={'WWW-Authenticate': 'Bearer realm="auth_required"'},
56
- )
57
- try:
58
- # Vérification et décodage du token Firebase
59
- decoded_token = auth.verify_id_token(cred.credentials)
60
- user_id = decoded_token['uid']
61
-
62
- # Récupération du rôle de l'utilisateur depuis Firestore
63
- user_doc = db.collection('users').document(user_id).get()
64
- if not user_doc.exists:
65
- raise HTTPException(status_code=401, detail="Utilisateur non trouvé dans Firestore")
66
-
67
- # Extraction du rôle et ajout aux informations utilisateur
68
- user_data = user_doc.to_dict()
69
- user_role = user_data.get('role', 'user_extern') # Par défaut à 'user_extern' si le rôle n'existe pas
70
- decoded_token['role'] = user_role
71
- res.headers['WWW-Authenticate'] = 'Bearer realm="auth_required"'
72
-
73
- return decoded_token
74
- except Exception as err:
75
- raise HTTPException(
76
- status_code=status.HTTP_401_UNAUTHORIZED,
77
- detail=f"Invalid authentication credentials. {err}",
78
- headers={'WWW-Authenticate': 'Bearer error="invalid_token"'},
79
- )
80
-
81
- # Fonction de dépendance pour vérifier le rôle
82
- def require_role(allowed_roles):
83
- def role_checker(user_info=Depends(get_user)):
84
- if user_info['role'] not in allowed_roles:
85
- raise HTTPException(status_code=403, detail="Accès non autorisé")
86
- return user_info
87
- return role_checker
88
-
89
- # Route publique
90
- @app.get("/")
91
- async def root():
92
- return {"message": "This is the root."}
93
-
94
- @app.get("/api/protected/user")
95
- async def protected_user_route(user_info=Depends(get_user)):
96
- return {"message": "Protected user route accessed successfully", "user_info": user_info}
97
-
98
- @app.get("/api/protected/admin")
99
- async def protected_admin_route(user_info=Depends(require_role(["admin"]))):
100
- return {"message": "Protected admin route accessed successfully", "user_info": user_info}
 
1
+ from fastapi import FastAPI, Depends, HTTPException, status, Response
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
4
+ from firebase_admin import auth, credentials, firestore
5
+ from firebase_admin import auth
6
+ from app.core.firebase import db, get_firebase_app # Modifier cette ligne
7
+ from app.api.endpoints.videos import router as videos_router
8
+
9
+
10
+ get_firebase_app()
11
+
12
+ app = FastAPI()
13
+
14
+ # Configuration CORS
15
+ app.add_middleware(
16
+ CORSMiddleware,
17
+ allow_origins=["*"],
18
+ allow_credentials=True,
19
+ allow_methods=["POST", "GET"],
20
+ allow_headers=["*"]
21
+ )
22
+
23
+ # Configuration CORS
24
+ app.add_middleware(
25
+ CORSMiddleware,
26
+ allow_origins=["*"],
27
+ allow_credentials=True,
28
+ allow_methods=["POST", "GET"],
29
+ allow_headers=["*"]
30
+ )
31
+
32
+ def get_user(res: Response,
33
+ cred: HTTPAuthorizationCredentials = Depends(HTTPBearer(auto_error=False))):
34
+ if cred is None:
35
+ raise HTTPException(
36
+ status_code=status.HTTP_401_UNAUTHORIZED,
37
+ detail="Bearer authentication required",
38
+ headers={'WWW-Authenticate': 'Bearer realm="auth_required"'},
39
+ )
40
+ try:
41
+ decoded_token = auth.verify_id_token(
42
+ cred.credentials,
43
+ check_revoked=True,
44
+ clock_skew_seconds=1800
45
+ )
46
+ user_id = decoded_token['uid']
47
+
48
+ user_doc = db.collection('users').document(user_id).get()
49
+ if not user_doc.exists:
50
+ raise HTTPException(status_code=401, detail="Utilisateur non trouvé dans Firestore")
51
+
52
+ user_data = user_doc.to_dict()
53
+ user_role = user_data.get('role', 'user_extern')
54
+ decoded_token['role'] = user_role
55
+ res.headers['WWW-Authenticate'] = 'Bearer realm="auth_required"'
56
+
57
+ return decoded_token
58
+ except Exception as err:
59
+ raise HTTPException(
60
+ status_code=status.HTTP_401_UNAUTHORIZED,
61
+ detail=f"Invalid authentication credentials. {err}",
62
+ headers={'WWW-Authenticate': 'Bearer error="invalid_token"'},
63
+ )
64
+
65
+ def require_role(allowed_roles):
66
+ def role_checker(user_info=Depends(get_user)):
67
+ if user_info['role'] not in allowed_roles:
68
+ raise HTTPException(status_code=403, detail="Accès non autorisé")
69
+ return user_info
70
+ return role_checker
71
+
72
+ # Inclure le router videos
73
+ app.include_router(videos_router, prefix="/api")
74
+
75
+ @app.get("/")
76
+ async def root():
77
+ return {"message": "API is running"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,5 +1,20 @@
1
  fastapi
2
  uvicorn
3
- python-dotenv
4
  firebase-admin
5
- python-multipart
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  fastapi
2
  uvicorn
3
+ python-multipart
4
  firebase-admin
5
+ python-dotenv
6
+ huggingface-hub
7
+ yt-dlp
8
+ ffmpeg-python
9
+ pydantic
10
+
11
+
12
+ # fastapi==0.110.0
13
+ # uvicorn==0.27.1
14
+ # python-multipart==0.0.9
15
+ # firebase-admin==6.4.0
16
+ # python-dotenv==1.0.1
17
+ # huggingface-hub==0.21.4
18
+ # yt-dlp==2024.3.10
19
+ # ffmpeg-python==0.2.0
20
+ # pydantic==2.6.3