goon/app/models/performer.py
goon-foss ad0284585b Initial commit
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.
2026-05-20 10:10:22 +02:00

81 lines
3.1 KiB
Python

import enum
import uuid
from datetime import date, datetime
from sqlalchemy import Date, DateTime, Enum, Float, ForeignKey, Integer, String, UniqueConstraint, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin, UUIDPKMixin
class Gender(str, enum.Enum):
female = "female"
male = "male"
transgender_female = "transgender_female"
transgender_male = "transgender_male"
non_binary = "non_binary"
intersex = "intersex"
unknown = "unknown"
class Performer(UUIDPKMixin, TimestampMixin, Base):
__tablename__ = "performers"
canonical_name: Mapped[str] = mapped_column(String(256), nullable=False)
name_normalized: Mapped[str] = mapped_column(String(256), nullable=False, index=True)
slug: Mapped[str] = mapped_column(String(256), nullable=False, unique=True)
gender: Mapped[Gender | None] = mapped_column(Enum(Gender, name="performer_gender"))
birth_date: Mapped[date | None] = mapped_column(Date)
country: Mapped[str | None] = mapped_column(String(64))
# Continuous search worker: kiedy ostatni per-performer search across tubes.
# Queue: ORDER BY last_searched_at NULLS FIRST, search_run_count ASC. Po pełnym
# sweep cykliczne refresh najstarszych.
last_searched_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
search_run_count: Mapped[int] = mapped_column(
Integer, nullable=False, default=0, server_default="0"
)
class PerformerAlias(Base):
__tablename__ = "performer_aliases"
__table_args__ = (UniqueConstraint("performer_id", "alias_normalized"),)
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, server_default=func.gen_random_uuid()
)
performer_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("performers.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
alias: Mapped[str] = mapped_column(String(256), nullable=False)
alias_normalized: Mapped[str] = mapped_column(String(256), nullable=False, index=True)
source_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), ForeignKey("sources.id", ondelete="SET NULL")
)
class PerformerExternalRef(Base):
__tablename__ = "performer_external_refs"
source_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("sources.id", ondelete="CASCADE"), primary_key=True
)
external_id: Mapped[str] = mapped_column(String, primary_key=True)
performer_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("performers.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
confidence: Mapped[float] = mapped_column(Float, nullable=False, default=1.0, server_default="1.0")
first_seen: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False
)
last_seen: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False
)