from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File from sqlalchemy.orm import Session from typing import List from datetime import datetime import os import uuid from pathlib import Path from PIL import Image from config.database import get_db from config.settings import settings from models.post import Post, PostMention, PostLike, PostComment from models.user import User from models.notification import Notification, NotificationType from utils.notification_service import NotificationService from schemas.post import PostCreate, PostUpdate, PostResponse, PostCommentCreate from utils.security import get_current_active_user from utils.settings_service import SettingsService router = APIRouter() @router.post("/", response_model=PostResponse) async def create_post( post_data: PostCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Create a new post.""" post = Post( author_id=current_user.id, content=post_data.content, image_url=post_data.image_url ) db.add(post) db.flush() # Get the post ID before creating mentions # Create mentions for user_id in post_data.mentioned_user_ids: mentioned_user = db.query(User).filter(User.id == user_id).first() if mentioned_user: mention = PostMention( post_id=post.id, mentioned_user_id=user_id ) db.add(mention) # Create notification for mentioned user NotificationService.create_mention_notification( db=db, mentioned_user_id=user_id, author=current_user, content_type="post", content_id=post.id ) db.commit() db.refresh(post) return format_post_response(post, db) @router.get("/", response_model=List[PostResponse]) async def get_posts( db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user), limit: int = 50, offset: int = 0 ): """Get all posts.""" posts = db.query(Post).order_by(Post.created_at.desc()).limit(limit).offset(offset).all() return [format_post_response(post, db) for post in posts] @router.get("/{post_id}", response_model=PostResponse) async def get_post( post_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Get a specific post.""" post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Post not found" ) return format_post_response(post, db) @router.put("/{post_id}", response_model=PostResponse) async def update_post( post_id: int, post_update: PostUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Update a post.""" post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Post not found" ) if post.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 post" ) for field, value in post_update.dict(exclude_unset=True).items(): setattr(post, field, value) post.updated_at = datetime.utcnow() db.commit() db.refresh(post) return format_post_response(post, db) @router.delete("/{post_id}") async def delete_post( post_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Delete a post.""" post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Post not found" ) if post.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 post" ) db.delete(post) db.commit() return {"message": "Post deleted successfully"} @router.post("/upload-image") async def upload_post_image( file: UploadFile = File(...), db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Upload an image for a post.""" # Validate file type if not SettingsService.is_file_type_allowed(db, file.content_type or "unknown"): allowed_types = SettingsService.get_setting(db, "allowed_image_types", ["image/jpeg", "image/png", "image/gif", "image/webp"]) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid file type. Allowed types: {', '.join(allowed_types)}" ) # Check file size contents = await file.read() file_size = len(contents) max_size = SettingsService.get_max_upload_size(db, file.content_type or "image/jpeg") if file_size > max_size: max_size_mb = max_size // (1024 * 1024) raise HTTPException( status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail=f"File too large. Maximum size: {max_size_mb}MB" ) # Create posts directory posts_dir = Path(settings.UPLOAD_PATH) / "posts" / str(current_user.id) posts_dir.mkdir(parents=True, exist_ok=True) # Generate unique filename file_extension = file.filename.split(".")[-1] filename = f"{uuid.uuid4()}.{file_extension}" file_path = posts_dir / filename # Save file with open(file_path, "wb") as f: f.write(contents) # Resize image if it's REALLY too large (4K support) try: img = Image.open(file_path) if img.size[0] > 3840 or img.size[1] > 2160: # 4K threshold img.thumbnail((3840, 2160), Image.Resampling.LANCZOS) img.save(file_path, quality=95, optimize=True) # High quality except Exception as e: print(f"Error processing image: {e}") # Return the image URL image_url = f"/posts/{current_user.id}/{filename}" return {"image_url": image_url} @router.post("/{post_id}/like") async def toggle_post_like( post_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Toggle like on a post.""" post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Post not found" ) # Check if user already liked the post existing_like = db.query(PostLike).filter( PostLike.post_id == post_id, PostLike.user_id == current_user.id ).first() if existing_like: # Unlike db.delete(existing_like) post.likes_count = max(0, post.likes_count - 1) message = "Like removed" is_liked = False else: # Like like = PostLike(post_id=post_id, user_id=current_user.id) db.add(like) post.likes_count += 1 message = "Post liked" is_liked = True db.commit() return {"message": message, "is_liked": is_liked, "likes_count": post.likes_count} @router.post("/{post_id}/comment") async def add_post_comment( post_id: int, comment_data: PostCommentCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Add a comment to a post.""" post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Post not found" ) comment = PostComment( post_id=post_id, author_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, "author_id": comment.author_id, "author_name": current_user.full_name, "author_avatar": current_user.avatar_url, "created_at": comment.created_at } } @router.delete("/{post_id}/comment/{comment_id}") async def delete_post_comment( post_id: int, comment_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Delete a comment from a post.""" comment = db.query(PostComment).filter( PostComment.id == comment_id, PostComment.post_id == post_id ).first() if not comment: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Comment not found" ) if comment.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 comment" ) db.delete(comment) db.commit() return {"message": "Comment deleted successfully"} def format_post_response(post: Post, db: Session) -> dict: """Format post response with author and mentions information.""" mentioned_users = [] for mention in post.mentions: user = mention.mentioned_user mentioned_users.append({ "id": user.id, "username": user.username, "full_name": user.full_name, "avatar_url": user.avatar_url }) # Get likes and comments likes = [] for like in post.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 }) comments = [] for comment in post.comments: user = db.query(User).filter(User.id == comment.author_id).first() if user: comments.append({ "id": comment.id, "content": comment.content, "author_id": user.id, "author_name": user.full_name, "author_avatar": user.avatar_url, "created_at": comment.created_at }) return { "id": post.id, "author_id": post.author_id, "content": post.content, "image_url": post.image_url, "likes_count": post.likes_count, "comments_count": post.comments_count, "created_at": post.created_at, "updated_at": post.updated_at, "author_name": post.author.full_name, "author_avatar": post.author.avatar_url, "mentioned_users": mentioned_users, "likes": likes, "comments": comments }