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 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) 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 }