Goon — self-hosted aggregator for adult-content scene metadata. Indexes scenes from TPDB, StashDB, and 30+ public adult tube sites. Cross-source deduplication via perceptual hash + Levenshtein distance. FastAPI backend + APScheduler worker + React Native (Expo) mobile client. FOSS, ad-free, donation-funded. See README for details.
116 lines
3.8 KiB
Python
116 lines
3.8 KiB
Python
"""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.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)],
|
|
) -> BlacklistOut:
|
|
perfs = session.execute(
|
|
select(BlacklistedPerformer.performer_id, Performer.canonical_name, Performer.slug)
|
|
.join(Performer, Performer.id == BlacklistedPerformer.performer_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)
|
|
.order_by(Studio.name)
|
|
).all()
|
|
tags = session.execute(
|
|
select(BlacklistedTag.tag_id, Tag.name, Tag.slug)
|
|
.join(Tag, Tag.id == BlacklistedTag.tag_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)],
|
|
) -> 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, entity_id) is not None:
|
|
return {"kind": kind, "id": str(entity_id), "created": False}
|
|
session.add(bl_model(**{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)],
|
|
) -> None:
|
|
bl_model, _, _ = _kind_to_entity(kind)
|
|
row = session.get(bl_model, entity_id)
|
|
if row is None:
|
|
return # idempotent
|
|
session.delete(row)
|
|
session.commit()
|