"""Blacklists — globalnie ukryte performerki/studia/tagi. Sceny które MAJĄ blacklisted entity wypadają z każdego /scenes (pełna lista, search, performer scenes, tag scenes). Auto-apply w `app/api/scenes.py`. Endpointy: GET /blacklist — wszystkie 3 listy w jednym response POST /blacklist/{kind}/{entity_id} — dodaj (idempotent) DELETE /blacklist/{kind}/{entity_id} — usuń `kind` ∈ {performer, studio, tag}. """ from __future__ import annotations import uuid from typing import Annotated, Literal from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel from sqlalchemy import select from sqlalchemy.orm import Session from app.api.device import get_device_id from app.auth import require_api_key from app.db import get_session from app.models.blacklist import ( BlacklistedPerformer, BlacklistedStudio, BlacklistedTag, ) from app.models.performer import Performer from app.models.studio import Studio from app.models.tag import Tag router = APIRouter( prefix="/blacklist", tags=["blacklist"], dependencies=[Depends(require_api_key)] ) Kind = Literal["performer", "studio", "tag"] class BlacklistEntry(BaseModel): id: uuid.UUID name: str # canonical_name (performer) / name (studio/tag) slug: str | None = None class BlacklistOut(BaseModel): performers: list[BlacklistEntry] studios: list[BlacklistEntry] tags: list[BlacklistEntry] @router.get("", response_model=BlacklistOut) def list_blacklist( session: Annotated[Session, Depends(get_session)], device_id: Annotated[str, Depends(get_device_id)], ) -> BlacklistOut: perfs = session.execute( select(BlacklistedPerformer.performer_id, Performer.canonical_name, Performer.slug) .join(Performer, Performer.id == BlacklistedPerformer.performer_id) .where(BlacklistedPerformer.device_id == device_id) .order_by(Performer.canonical_name) ).all() studios = session.execute( select(BlacklistedStudio.studio_id, Studio.name, Studio.slug) .join(Studio, Studio.id == BlacklistedStudio.studio_id) .where(BlacklistedStudio.device_id == device_id) .order_by(Studio.name) ).all() tags = session.execute( select(BlacklistedTag.tag_id, Tag.name, Tag.slug) .join(Tag, Tag.id == BlacklistedTag.tag_id) .where(BlacklistedTag.device_id == device_id) .order_by(Tag.name) ).all() return BlacklistOut( performers=[BlacklistEntry(id=r[0], name=r[1], slug=r[2]) for r in perfs], studios=[BlacklistEntry(id=r[0], name=r[1], slug=r[2]) for r in studios], tags=[BlacklistEntry(id=r[0], name=r[1], slug=r[2]) for r in tags], ) def _kind_to_entity(kind: Kind): if kind == "performer": return BlacklistedPerformer, Performer, "performer_id" if kind == "studio": return BlacklistedStudio, Studio, "studio_id" if kind == "tag": return BlacklistedTag, Tag, "tag_id" raise HTTPException(status_code=400, detail="kind must be performer|studio|tag") @router.post("/{kind}/{entity_id}", status_code=status.HTTP_200_OK) def add_blacklist( kind: Kind, entity_id: uuid.UUID, session: Annotated[Session, Depends(get_session)], device_id: Annotated[str, Depends(get_device_id)], ) -> dict: bl_model, parent_model, fk = _kind_to_entity(kind) if session.get(parent_model, entity_id) is None: raise HTTPException(status_code=404, detail=f"{kind} not found") if session.get(bl_model, (device_id, entity_id)) is not None: return {"kind": kind, "id": str(entity_id), "created": False} session.add(bl_model(**{"device_id": device_id, fk: entity_id})) session.commit() return {"kind": kind, "id": str(entity_id), "created": True} @router.delete("/{kind}/{entity_id}", status_code=status.HTTP_204_NO_CONTENT) def remove_blacklist( kind: Kind, entity_id: uuid.UUID, session: Annotated[Session, Depends(get_session)], device_id: Annotated[str, Depends(get_device_id)], ) -> None: bl_model, _, _ = _kind_to_entity(kind) row = session.get(bl_model, (device_id, entity_id)) if row is None: return # idempotent session.delete(row) session.commit()