Files
vynl/backend/app/api/endpoints/recommendations.py
root 240032d972 Add Bandcamp/YouTube links on all recs, Fix My Playlist, configurable rec count
- Bandcamp: match artist+song, fall back to artist-only page
- YouTube fallback: if not on Bandcamp, link to YouTube music video search
- Fix My Playlist: AI analyzes playlist, finds outliers, suggests replacements
- User chooses recommendation count (5, 10, 15, 20)
2026-03-31 08:45:02 -05:00

95 lines
3.2 KiB
Python

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.security import get_current_user
from app.models.user import User
from app.models.recommendation import Recommendation
from app.schemas.recommendation import RecommendationRequest, RecommendationResponse, RecommendationItem
from app.services.recommender import generate_recommendations
router = APIRouter(prefix="/recommendations", tags=["recommendations"])
@router.post("/generate", response_model=RecommendationResponse)
async def generate(
data: RecommendationRequest,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
if not data.playlist_id and not data.query:
raise HTTPException(status_code=400, detail="Provide a playlist_id or query")
recs, remaining = await generate_recommendations(
db, user, playlist_id=data.playlist_id, query=data.query, bandcamp_mode=data.bandcamp_mode,
mode=data.mode, adventurousness=data.adventurousness, exclude=data.exclude, count=data.count,
)
if not recs and remaining == 0:
raise HTTPException(status_code=429, detail="Weekly recommendation limit reached. Upgrade to Premium for unlimited.")
return RecommendationResponse(
recommendations=[RecommendationItem.model_validate(r) for r in recs],
remaining_this_week=remaining,
)
@router.get("/history", response_model=list[RecommendationItem])
async def history(
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(
select(Recommendation)
.where(Recommendation.user_id == user.id)
.order_by(Recommendation.created_at.desc())
.limit(50)
)
return result.scalars().all()
@router.get("/saved", response_model=list[RecommendationItem])
async def saved(
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(
select(Recommendation)
.where(Recommendation.user_id == user.id, Recommendation.saved == True)
.order_by(Recommendation.created_at.desc())
)
return result.scalars().all()
@router.post("/{rec_id}/save")
async def save_recommendation(
rec_id: int,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(
select(Recommendation).where(Recommendation.id == rec_id, Recommendation.user_id == user.id)
)
rec = result.scalar_one_or_none()
if not rec:
raise HTTPException(status_code=404, detail="Recommendation not found")
rec.saved = not rec.saved
return {"saved": rec.saved}
@router.post("/{rec_id}/dislike")
async def dislike_recommendation(
rec_id: int,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(
select(Recommendation).where(Recommendation.id == rec_id, Recommendation.user_id == user.id)
)
rec = result.scalar_one_or_none()
if not rec:
raise HTTPException(status_code=404, detail="Recommendation not found")
rec.disliked = not rec.disliked
return {"disliked": rec.disliked}