initial commit - LeDiscord plateforme des copains
This commit is contained in:
347
backend/api/routers/posts.py
Normal file
347
backend/api/routers/posts.py
Normal file
@@ -0,0 +1,347 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user