from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form from sqlalchemy.orm import Session from typing import List import os import uuid from pathlib import Path from config.database import get_db from config.settings import settings from models.vlog import Vlog, VlogLike, VlogComment, VlogView from models.user import User from models.notification import Notification, NotificationType from schemas.vlog import VlogCreate, VlogUpdate, VlogResponse, VlogCommentCreate from utils.security import get_current_active_user from utils.video_utils import generate_video_thumbnail, get_video_duration from utils.settings_service import SettingsService router = APIRouter() @router.post("/", response_model=VlogResponse) async def create_vlog( vlog_data: VlogCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Create a new vlog.""" vlog = Vlog( author_id=current_user.id, **vlog_data.dict() ) db.add(vlog) db.commit() db.refresh(vlog) return format_vlog_response(vlog, db, current_user.id) @router.get("/", response_model=List[VlogResponse]) async def get_vlogs( db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user), limit: int = 20, offset: int = 0 ): """Get all vlogs.""" vlogs = db.query(Vlog).order_by(Vlog.created_at.desc()).limit(limit).offset(offset).all() return [format_vlog_response(vlog, db, current_user.id) for vlog in vlogs] @router.get("/{vlog_id}", response_model=VlogResponse) async def get_vlog( vlog_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Get a specific vlog.""" vlog = db.query(Vlog).filter(Vlog.id == vlog_id).first() if not vlog: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Vlog not found" ) # Manage views and replays view = db.query(VlogView).filter( VlogView.vlog_id == vlog_id, VlogView.user_id == current_user.id ).first() if view: # User has already viewed this vlog -> Count as replay vlog.replays_count = (vlog.replays_count or 0) + 1 else: # First time viewing -> Count as unique view new_view = VlogView(vlog_id=vlog_id, user_id=current_user.id) db.add(new_view) vlog.views_count += 1 db.commit() return format_vlog_response(vlog, db, current_user.id) @router.put("/{vlog_id}", response_model=VlogResponse) async def update_vlog( vlog_id: int, vlog_update: VlogUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Update a vlog.""" vlog = db.query(Vlog).filter(Vlog.id == vlog_id).first() if not vlog: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Vlog not found" ) if vlog.author_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to update this vlog" ) for field, value in vlog_update.dict(exclude_unset=True).items(): setattr(vlog, field, value) db.commit() db.refresh(vlog) return format_vlog_response(vlog, db, current_user.id) @router.delete("/{vlog_id}") async def delete_vlog( vlog_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Delete a vlog.""" vlog = db.query(Vlog).filter(Vlog.id == vlog_id).first() if not vlog: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Vlog not found" ) if vlog.author_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to delete this vlog" ) # Delete video files try: if vlog.video_url: os.remove(settings.UPLOAD_PATH + vlog.video_url) if vlog.thumbnail_url: os.remove(settings.UPLOAD_PATH + vlog.thumbnail_url) except: pass db.delete(vlog) db.commit() return {"message": "Vlog deleted successfully"} @router.post("/{vlog_id}/like") async def toggle_vlog_like( vlog_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Toggle like on a vlog.""" vlog = db.query(Vlog).filter(Vlog.id == vlog_id).first() if not vlog: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Vlog not found" ) existing_like = db.query(VlogLike).filter( VlogLike.vlog_id == vlog_id, VlogLike.user_id == current_user.id ).first() if existing_like: # Unlike db.delete(existing_like) vlog.likes_count = max(0, vlog.likes_count - 1) message = "Like removed" else: # Like try: like = VlogLike(vlog_id=vlog_id, user_id=current_user.id) db.add(like) vlog.likes_count += 1 message = "Vlog liked" except Exception: # Handle potential race condition or constraint violation db.rollback() return {"message": "Already liked", "likes_count": vlog.likes_count} db.commit() return {"message": message, "likes_count": vlog.likes_count} @router.post("/{vlog_id}/comment") async def add_vlog_comment( vlog_id: int, comment_data: VlogCommentCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Add a comment to a vlog.""" vlog = db.query(Vlog).filter(Vlog.id == vlog_id).first() if not vlog: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Vlog not found" ) comment = VlogComment( vlog_id=vlog_id, user_id=current_user.id, content=comment_data.content ) db.add(comment) db.commit() db.refresh(comment) return { "message": "Comment added successfully", "comment": { "id": comment.id, "content": comment.content, "user_id": comment.user_id, "username": current_user.username, "full_name": current_user.full_name, "avatar_url": current_user.avatar_url, "created_at": comment.created_at } } @router.delete("/{vlog_id}/comment/{comment_id}") async def delete_vlog_comment( vlog_id: int, comment_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Delete a comment from a vlog.""" comment = db.query(VlogComment).filter( VlogComment.id == comment_id, VlogComment.vlog_id == vlog_id ).first() if not comment: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Comment not found" ) if comment.user_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to delete this comment" ) db.delete(comment) db.commit() return {"message": "Comment deleted successfully"} @router.post("/upload") async def upload_vlog_video( title: str = Form(...), description: str = Form(None), video: UploadFile = File(...), thumbnail: UploadFile = File(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Upload a vlog video.""" # Validate video file if not SettingsService.is_file_type_allowed(db, video.content_type or "unknown"): allowed_types = SettingsService.get_setting(db, "allowed_video_types", ["video/mp4", "video/mpeg", "video/quicktime", "video/webm"]) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid video type. Allowed types: {', '.join(allowed_types)}" ) # Check file size video_content = await video.read() max_size = SettingsService.get_max_upload_size(db, video.content_type or "video/mp4", is_vlog=True) if len(video_content) > max_size: max_size_mb = max_size // (1024 * 1024) raise HTTPException( status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail=f"Video file too large. Maximum size: {max_size_mb}MB" ) # Create vlog directory vlog_dir = Path(settings.UPLOAD_PATH) / "vlogs" / str(current_user.id) vlog_dir.mkdir(parents=True, exist_ok=True) # Save video video_extension = video.filename.split(".")[-1] video_filename = f"{uuid.uuid4()}.{video_extension}" video_path = vlog_dir / video_filename with open(video_path, "wb") as f: f.write(video_content) # Process thumbnail thumbnail_url = None if thumbnail: if thumbnail.content_type not in settings.ALLOWED_IMAGE_TYPES: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid thumbnail type" ) thumbnail_content = await thumbnail.read() thumbnail_extension = thumbnail.filename.split(".")[-1] thumbnail_filename = f"thumb_{uuid.uuid4()}.{thumbnail_extension}" thumbnail_path = vlog_dir / thumbnail_filename with open(thumbnail_path, "wb") as f: f.write(thumbnail_content) thumbnail_url = f"/vlogs/{current_user.id}/{thumbnail_filename}" else: # Generate automatic thumbnail from video try: thumbnail_filename = f"auto_thumb_{uuid.uuid4()}.jpg" thumbnail_path = vlog_dir / thumbnail_filename if generate_video_thumbnail(str(video_path), str(thumbnail_path)): thumbnail_url = f"/vlogs/{current_user.id}/{thumbnail_filename}" except Exception as e: print(f"Error generating automatic thumbnail: {e}") # Continue without thumbnail if generation fails # Get video duration duration = None try: duration = int(get_video_duration(str(video_path))) except Exception as e: print(f"Error getting video duration: {e}") # Create vlog record vlog = Vlog( author_id=current_user.id, title=title, description=description, video_url=f"/vlogs/{current_user.id}/{video_filename}", thumbnail_url=thumbnail_url, duration=duration ) db.add(vlog) db.commit() db.refresh(vlog) # Create notifications for all active users (except the creator) users = db.query(User).filter(User.is_active == True).all() for user in users: if user.id != current_user.id: notification = Notification( user_id=user.id, type=NotificationType.NEW_VLOG, title="Nouveau vlog", message=f"{current_user.full_name} a publiƩ un nouveau vlog : {vlog.title}", link=f"/vlogs/{vlog.id}", is_read=False ) db.add(notification) db.commit() return format_vlog_response(vlog, db, current_user.id) def format_vlog_response(vlog: Vlog, db: Session, current_user_id: int) -> dict: """Format vlog response with author information, likes, and comments.""" # Check if current user liked this vlog is_liked = db.query(VlogLike).filter( VlogLike.vlog_id == vlog.id, VlogLike.user_id == current_user_id ).first() is not None # Format likes likes = [] for like in vlog.likes: user = db.query(User).filter(User.id == like.user_id).first() if user: likes.append({ "id": like.id, "user_id": user.id, "username": user.username, "full_name": user.full_name, "avatar_url": user.avatar_url, "created_at": like.created_at }) # Format comments comments = [] for comment in vlog.comments: user = db.query(User).filter(User.id == comment.user_id).first() if user: comments.append({ "id": comment.id, "user_id": user.id, "username": user.username, "full_name": user.full_name, "avatar_url": user.avatar_url, "content": comment.content, "created_at": comment.created_at, "updated_at": comment.updated_at }) return { "id": vlog.id, "author_id": vlog.author_id, "title": vlog.title, "description": vlog.description, "video_url": vlog.video_url, "thumbnail_url": vlog.thumbnail_url, "duration": vlog.duration, "views_count": vlog.views_count, "replays_count": vlog.replays_count, "likes_count": vlog.likes_count, "created_at": vlog.created_at, "updated_at": vlog.updated_at, "author_name": vlog.author.full_name, "author_avatar": vlog.author.avatar_url, "is_liked": is_liked, "likes": likes, "comments": comments }