- Discovery modes: Sonic Twin, Era Bridge, Deep Cuts, Rising Artists - Discovery dial (Safe to Adventurous slider) - Block genres/moods exclusion - Thumbs down/dislike on recommendations - My Taste page with Genre DNA breakdown, audio feature meters, listening personality - Updated pricing: Free (5/week), Premium ($6.99/mo), Family ($12.99/mo coming soon) - Weekly rate limiting instead of daily - Alembic migration for new fields
95 lines
3.2 KiB
Python
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,
|
|
)
|
|
|
|
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}
|