From d5b5ee8192323df80495e8a255ff3c74fe85489c Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Sun, 21 Dec 2025 02:38:40 -0800 Subject: [PATCH] feat(social): Add Reaction model, schema, and API endpoints --- backend/models.py | 10 ++++++ backend/routers/social.py | 72 +++++++++++++++++++++++++++++++++++++-- backend/schemas.py | 14 ++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/backend/models.py b/backend/models.py index 7e0bcf0..f7a3781 100644 --- a/backend/models.py +++ b/backend/models.py @@ -279,3 +279,13 @@ class Notification(SQLModel, table=True): created_at: datetime = Field(default_factory=datetime.utcnow) user: User = Relationship(back_populates="notifications") + +class Reaction(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + user_id: int = Field(foreign_key="user.id") + entity_type: str = Field(index=True) # "review", "comment" + entity_id: int = Field(index=True) + emoji: str # "❤️", "🔥", etc. + created_at: datetime = Field(default_factory=datetime.utcnow) + + user: User = Relationship() diff --git a/backend/routers/social.py b/backend/routers/social.py index b8daf68..955e37f 100644 --- a/backend/routers/social.py +++ b/backend/routers/social.py @@ -3,8 +3,8 @@ 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 -from schemas import CommentCreate, CommentRead, RatingCreate, RatingRead +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 @@ -121,3 +121,71 @@ def get_average_rating( 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() diff --git a/backend/schemas.py b/backend/schemas.py index a2af5ab..aefb2f2 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -346,3 +346,17 @@ class TagRead(TagBase): # Circular refs ShowRead.model_rebuild() PerformanceDetailRead.model_rebuild() + +# --- Reaction Schemas --- +class ReactionBase(SQLModel): + entity_type: str + entity_id: int + emoji: str + +class ReactionCreate(ReactionBase): + pass + +class ReactionRead(ReactionBase): + id: int + user_id: int + created_at: datetime