from typing import List, Union from fastapi import APIRouter, Depends, Query from sqlmodel import Session, select, desc from database import get_session from models import Review, Attendance, GroupPost, User, Profile, Performance, Show, Song from schemas import ReviewRead, AttendanceRead, GroupPostRead from datetime import datetime from auth import get_current_user_optional router = APIRouter(prefix="/feed", tags=["feed"]) # We need a unified schema for the feed from pydantic import BaseModel class FeedItem(BaseModel): type: str # review, attendance, post timestamp: datetime data: Union[ReviewRead, AttendanceRead, GroupPostRead, dict] user: dict # Basic user info entity: dict | None = None # Linked entity info def get_user_display(session: Session, user_id: int) -> dict: """Get consistent user display info using Profile username""" user = session.get(User, user_id) if not user: return {"id": 0, "username": "Deleted User", "avatar_bg_color": "#666", "avatar_text": None} profile = session.exec( select(Profile).where(Profile.user_id == user_id) ).first() return { "id": user.id, "username": profile.username if profile else f"User {user_id}", "display_name": profile.display_name if profile else None, "avatar_bg_color": user.avatar_bg_color or "#0F4C81", "avatar_text": user.avatar_text, } def get_entity_info(session: Session, review: Review) -> dict | None: """Get entity link info for a review""" if review.performance_id: perf = session.get(Performance, review.performance_id) if perf: song = session.get(Song, perf.song_id) show = session.get(Show, perf.show_id) return { "type": "performance", "slug": perf.slug, "title": song.title if song else "Unknown Song", "date": show.date.isoformat() if show and show.date else None, } elif review.show_id: show = session.get(Show, review.show_id) if show: return { "type": "show", "slug": show.slug, "title": show.date.strftime("%Y-%m-%d") if show.date else "Unknown Date", } elif review.song_id: song = session.get(Song, review.song_id) if song: return { "type": "song", "slug": song.slug, "title": song.title, } return None @router.get("/", response_model=List[FeedItem]) def get_global_feed( limit: int = 20, session: Session = Depends(get_session) ): # Fetch latest reviews reviews = session.exec( select(Review).order_by(desc(Review.created_at)).limit(limit) ).all() # Fetch latest attendance attendance = session.exec( select(Attendance).order_by(desc(Attendance.created_at)).limit(limit) ).all() # Fetch latest group posts posts = session.exec( select(GroupPost).order_by(desc(GroupPost.created_at)).limit(limit) ).all() feed_items = [] for r in reviews: feed_items.append(FeedItem( type="review", timestamp=r.created_at or datetime.utcnow(), data=r, user=get_user_display(session, r.user_id), entity=get_entity_info(session, r) )) for a in attendance: show = session.get(Show, a.show_id) if a.show_id else None entity_info = None if show: entity_info = { "type": "show", "slug": show.slug, "title": show.date.strftime("%Y-%m-%d") if show.date else "Unknown", } feed_items.append(FeedItem( type="attendance", timestamp=a.created_at, data=a, user=get_user_display(session, a.user_id), entity=entity_info )) for p in posts: feed_items.append(FeedItem( type="post", timestamp=p.created_at, data=p, user=get_user_display(session, p.user_id), entity=None )) # Sort by timestamp desc feed_items.sort(key=lambda x: x.timestamp, reverse=True) return feed_items[:limit] @router.get("/me", response_model=List[FeedItem]) def get_personalized_feed( limit: int = 20, session: Session = Depends(get_session), current_user: User = Depends(get_current_user_optional) ): """Get feed filtered by user's band preferences (or all bands if not logged in)""" from models import UserVerticalPreference, Vertical # Get user's preferred vertical IDs preferred_vertical_ids = None if current_user: prefs = session.exec( select(UserVerticalPreference) .where(UserVerticalPreference.user_id == current_user.id) .where(UserVerticalPreference.display_mode != "hidden") ).all() if prefs: preferred_vertical_ids = [p.vertical_id for p in prefs] # Fetch reviews (filtered by vertical if user has preferences) review_query = select(Review).order_by(desc(Review.created_at)).limit(limit * 2) reviews = session.exec(review_query).all() # Fetch attendance (filtered by show's vertical) attendance_query = select(Attendance).order_by(desc(Attendance.created_at)).limit(limit * 2) attendance = session.exec(attendance_query).all() feed_items = [] for r in reviews: # Filter by vertical if user has preferences vertical_id = None if r.performance_id: perf = session.get(Performance, r.performance_id) if perf: show = session.get(Show, perf.show_id) if show: vertical_id = show.vertical_id elif r.show_id: show = session.get(Show, r.show_id) if show: vertical_id = show.vertical_id elif r.song_id: song = session.get(Song, r.song_id) if song: vertical_id = song.vertical_id if preferred_vertical_ids and vertical_id and vertical_id not in preferred_vertical_ids: continue feed_items.append(FeedItem( type="review", timestamp=r.created_at or datetime.utcnow(), data=r, user=get_user_display(session, r.user_id), entity=get_entity_info(session, r) )) for a in attendance: show = session.get(Show, a.show_id) if a.show_id else None # Filter by vertical if preferred_vertical_ids and show and show.vertical_id not in preferred_vertical_ids: continue entity_info = None if show: entity_info = { "type": "show", "slug": show.slug, "title": show.date.strftime("%Y-%m-%d") if show.date else "Unknown", } feed_items.append(FeedItem( type="attendance", timestamp=a.created_at, data=a, user=get_user_display(session, a.user_id), entity=entity_info )) # Sort by timestamp desc feed_items.sort(key=lambda x: x.timestamp, reverse=True) return feed_items[:limit]