from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.core.deps import get_current_active_user, get_db from app.models.favorite import Favorite from app.models.spot import Spot from app.models.user import User from app.schemas.common import PageResponse from app.schemas.spot import SpotBrief router = APIRouter() def _spot_to_brief(spot: Spot) -> SpotBrief: cover = next((img for img in spot.images if img.is_cover), None) if cover is None and spot.images: cover = spot.images[0] return SpotBrief( id=spot.id, title=spot.title, city=spot.city, longitude=spot.longitude, latitude=spot.latitude, cover_image_url=cover.image_url if cover else None, audit_status=spot.audit_status, created_at=spot.created_at, ) @router.post("/{spot_id}", status_code=status.HTTP_201_CREATED) async def add_favorite( spot_id: int, current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(Spot).where(Spot.id == spot_id)) if not result.scalar_one_or_none(): raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Spot not found") existing = await db.execute( select(Favorite).where( Favorite.user_id == current_user.id, Favorite.spot_id == spot_id ) ) if existing.scalar_one_or_none(): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Already favorited" ) fav = Favorite(user_id=current_user.id, spot_id=spot_id) db.add(fav) await db.execute( select(Spot).where(Spot.id == spot_id) ) spot_obj = (await db.execute(select(Spot).where(Spot.id == spot_id))).scalar_one() spot_obj.favorite_count = (spot_obj.favorite_count or 0) + 1 await db.commit() return {"code": 0, "message": "success"} @router.delete("/{spot_id}", status_code=status.HTTP_204_NO_CONTENT) async def remove_favorite( spot_id: int, current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db), ): result = await db.execute( select(Favorite).where( Favorite.user_id == current_user.id, Favorite.spot_id == spot_id ) ) fav = result.scalar_one_or_none() if not fav: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Favorite not found" ) spot_obj = (await db.execute(select(Spot).where(Spot.id == spot_id))).scalar_one_or_none() if spot_obj: spot_obj.favorite_count = max((spot_obj.favorite_count or 0) - 1, 0) await db.delete(fav) await db.commit() @router.get("/", response_model=PageResponse[SpotBrief]) async def list_favorites( page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db), ): count_query = select(func.count(Favorite.id)).where( Favorite.user_id == current_user.id ) total_result = await db.execute(count_query) total = total_result.scalar() or 0 offset = (page - 1) * page_size query = ( select(Favorite) .where(Favorite.user_id == current_user.id) .order_by(Favorite.created_at.desc()) .offset(offset) .limit(page_size) ) result = await db.execute(query) favorites = result.scalars().all() return PageResponse( total=total, items=[_spot_to_brief(f.spot) for f in favorites], )