import re from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, Query from sqlmodel import Session, select, func from database import get_session from models import Comment, Rating, User, Profile, Reaction from schemas import CommentCreate, CommentRead, RatingCreate, RatingRead, ReactionCreate, ReactionRead from auth import get_current_user from helpers import create_notification from services.gamification import award_xp, check_and_award_badges, update_streak, XP_REWARDS router = APIRouter(prefix="/social", tags=["social"]) # --- Comments --- @router.post("/comments", response_model=CommentRead) def create_comment( comment: CommentCreate, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): db_comment = Comment.model_validate(comment) db_comment.user_id = current_user.id session.add(db_comment) session.commit() session.refresh(db_comment) # Notify parent author if reply if db_comment.parent_id: parent_comment = session.get(Comment, db_comment.parent_id) if parent_comment and parent_comment.user_id != current_user.id: create_notification( session, user_id=parent_comment.user_id, title="New Reply", message=f"Someone replied to your comment.", type="reply", link=f"/activity" ) # Handle Mentions mention_pattern = r"@(\w+)" mentions = re.findall(mention_pattern, db_comment.content) if mentions: # Find users with these profile usernames mentioned_profiles = session.exec(select(Profile).where(Profile.username.in_(mentions))).all() for profile in mentioned_profiles: if profile.user_id != current_user.id: create_notification( session, user_id=profile.user_id, title="You were mentioned!", message=f"Someone mentioned you in a comment.", type="mention", link=f"/activity" # Generic link for now ) return db_comment @router.get("/comments", response_model=List[CommentRead]) def read_comments( show_id: Optional[int] = None, venue_id: Optional[int] = None, song_id: Optional[int] = None, offset: int = 0, limit: int = Query(default=50, le=100), session: Session = Depends(get_session) ): query = select(Comment) if show_id: query = query.where(Comment.show_id == show_id) if venue_id: query = query.where(Comment.venue_id == venue_id) if song_id: query = query.where(Comment.song_id == song_id) query = query.order_by(Comment.created_at.desc()).offset(offset).limit(limit) comments = session.exec(query).all() return comments # --- Ratings --- @router.post("/ratings", response_model=RatingRead) def create_rating( rating: RatingCreate, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): # Check if user already rated this entity query = select(Rating).where(Rating.user_id == current_user.id) if rating.show_id: query = query.where(Rating.show_id == rating.show_id) elif rating.song_id: query = query.where(Rating.song_id == rating.song_id) elif rating.performance_id: query = query.where(Rating.performance_id == rating.performance_id) elif rating.venue_id: query = query.where(Rating.venue_id == rating.venue_id) elif rating.tour_id: query = query.where(Rating.tour_id == rating.tour_id) else: raise HTTPException(status_code=400, detail="Must rate a show, song, performance, venue, or tour") existing_rating = session.exec(query).first() if existing_rating: # Update existing (no XP for updating) existing_rating.score = rating.score session.add(existing_rating) session.commit() session.refresh(existing_rating) return existing_rating db_rating = Rating.model_validate(rating) db_rating.user_id = current_user.id session.add(db_rating) # Award XP for new rating # Check if first rating for bonus rating_count = session.exec( select(func.count(Rating.id)).where(Rating.user_id == current_user.id) ).one() or 0 xp_amount = XP_REWARDS["rating_submit"] if rating_count == 0: xp_amount += XP_REWARDS["first_rating"] # Bonus for first rating award_xp(session, current_user, xp_amount, "rating") update_streak(session, current_user) check_and_award_badges(session, current_user) session.commit() session.refresh(db_rating) return db_rating @router.get("/ratings/average", response_model=float) def get_average_rating( show_id: Optional[int] = None, song_id: Optional[int] = None, performance_id: Optional[int] = None, venue_id: Optional[int] = None, tour_id: Optional[int] = None, session: Session = Depends(get_session) ): query = select(func.avg(Rating.score)) if show_id: query = query.where(Rating.show_id == show_id) elif song_id: query = query.where(Rating.song_id == song_id) elif performance_id: query = query.where(Rating.performance_id == performance_id) elif venue_id: query = query.where(Rating.venue_id == venue_id) elif tour_id: query = query.where(Rating.tour_id == tour_id) else: # Return 0 if no entity specified instead of error (graceful degradation) return 0.0 avg = session.exec(query).first() return float(avg) if avg else 0.0 # --- Reactions --- @router.post("/reactions", response_model=ReactionRead) def toggle_reaction( reaction: ReactionCreate, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): query = select(Reaction).where( Reaction.user_id == current_user.id, Reaction.entity_type == reaction.entity_type, Reaction.entity_id == reaction.entity_id ) existing = session.exec(query).first() if existing: if existing.emoji == reaction.emoji: # Toggle off session.delete(existing) session.commit() return existing else: # Change emoji existing.emoji = reaction.emoji session.add(existing) session.commit() session.refresh(existing) return existing # Create new db_reaction = Reaction.model_validate(reaction) db_reaction.user_id = current_user.id session.add(db_reaction) session.commit() session.refresh(db_reaction) return db_reaction @router.get("/reactions/counts") def get_reaction_counts( entity_type: str, entity_id: int, session: Session = Depends(get_session) ): # Group by emoji and count query = select(Reaction.emoji, func.count(Reaction.id)).where( Reaction.entity_type == entity_type, Reaction.entity_id == entity_id ).group_by(Reaction.emoji) results = session.exec(query).all() # returns list of (emoji, count) return {emoji: count for emoji, count in results} @router.get("/reactions/me") def get_my_reaction( entity_type: str, entity_id: int, current_user: User = Depends(get_current_user), session: Session = Depends(get_session) ): query = select(Reaction).where( Reaction.user_id == current_user.id, Reaction.entity_type == entity_type, Reaction.entity_id == entity_id ) return session.exec(query).first()