diff --git a/packages/backend/app/__pycache__/models.cpython-311.pyc b/packages/backend/app/__pycache__/models.cpython-311.pyc index 3ff203d..6003145 100644 Binary files a/packages/backend/app/__pycache__/models.cpython-311.pyc and b/packages/backend/app/__pycache__/models.cpython-311.pyc differ diff --git a/packages/backend/app/__pycache__/schemas.cpython-311.pyc b/packages/backend/app/__pycache__/schemas.cpython-311.pyc index 3c5bcb2..fc09b76 100644 Binary files a/packages/backend/app/__pycache__/schemas.cpython-311.pyc and b/packages/backend/app/__pycache__/schemas.cpython-311.pyc differ diff --git a/packages/backend/app/models.py b/packages/backend/app/models.py index d5ee26a..f774a7e 100644 --- a/packages/backend/app/models.py +++ b/packages/backend/app/models.py @@ -1,12 +1,15 @@ import enum from sqlalchemy import (Column, Integer, String, Date, Enum, Boolean, - DECIMAL, Text, TIMESTAMP, ForeignKey) + DECIMAL, Text, TIMESTAMP, ForeignKey, Table) from sqlalchemy.sql import func -from sqlalchemy.orm import relationship # Aggiungi questo import +from sqlalchemy.orm import relationship from .database import Base -# Definiamo gli Enum Python corrispondenti ai tipi custom di PostgreSQL +# ============================================================================= +# DEFINIZIONE DEGLI ENUM +# ============================================================================= + class GenderType(str, enum.Enum): male = "male" female = "female" @@ -25,20 +28,43 @@ class BraSystemType(str, enum.Enum): AU = "AU" IT = "IT" JP = "JP" + +# NUOVO ENUM: Tipo di intervento estetico +class SurgeryType(str, enum.Enum): + breast_reduction = "breast_reduction" + breast_augmentation = "breast_augmentation" + breast_lift = "breast_lift" + rhinoplasty = "rhinoplasty" + other = "other" -class Image(Base): - __tablename__ = "images" +# ============================================================================= +# TABELLE DI ASSOCIAZIONE (PER RELAZIONI MOLTI-A-MOLTI) +# ============================================================================= - id = Column(Integer, primary_key=True, index=True) - celebrity_id = Column(Integer, ForeignKey("celebrities.id"), nullable=False) - file_path = Column(String, nullable=False) - caption = Column(Text, nullable=True) - uploaded_at = Column(TIMESTAMP(timezone=True), server_default=func.now()) +celebrity_professions = Table( + "celebrity_professions", + Base.metadata, + Column("celebrity_id", Integer, ForeignKey("celebrities.id", ondelete="CASCADE"), primary_key=True), + Column("profession_id", Integer, ForeignKey("professions.id", ondelete="CASCADE"), primary_key=True), +) - # Relazione inversa: "questa immagine appartiene a UNA celebrità". - # Specifichiamo che deve usare la colonna `celebrity_id` di QUESTA tabella. - celebrity = relationship("Celebrity", back_populates="images", foreign_keys=[celebrity_id]) +celebrity_studios = Table( + "celebrity_studios", + Base.metadata, + Column("celebrity_id", Integer, ForeignKey("celebrities.id", ondelete="CASCADE"), primary_key=True), + Column("studio_id", Integer, ForeignKey("studios.id", ondelete="CASCADE"), primary_key=True), +) +video_performers = Table( + "video_performers", + Base.metadata, + Column("video_id", Integer, ForeignKey("videos.id", ondelete="CASCADE"), primary_key=True), + Column("celebrity_id", Integer, ForeignKey("celebrities.id", ondelete="CASCADE"), primary_key=True), +) + +# ============================================================================= +# MODELLI PRINCIPALI +# ============================================================================= class Celebrity(Base): __tablename__ = "celebrities" @@ -51,41 +77,140 @@ class Celebrity(Base): nationality = Column(String, nullable=True) ethnicity = Column(String, nullable=True) sexuality = Column(String, nullable=True) - hair_color = Column(String, nullable=True) eye_color = Column(String, nullable=True) height_cm = Column(Integer, nullable=True) weight_kg = Column(Integer, nullable=True) body_type = Column(String, nullable=True) - bust_cm = Column(Integer, nullable=True) waist_cm = Column(Integer, nullable=True) hips_cm = Column(Integer, nullable=True) - chest_circumference_cm = Column(Integer, nullable=True) - bra_band_size = Column(Integer, nullable=True) bra_cup_size = Column(String, nullable=True) bra_size_system = Column(Enum(BraSystemType), nullable=True) - boobs_are_natural = Column(Boolean, nullable=True) shoe_size = Column(DECIMAL(4, 1), nullable=True) shoe_size_system = Column(Enum(ShoeSystemType), nullable=True) - biography = Column(Text, nullable=True) official_website = Column(String, nullable=True) - # 1. Relazione "una celebrità ha MOLTE immagini". - # SQLAlchemy deve usare la FK che si trova nella tabella Image. - images = relationship("Image", back_populates="celebrity", foreign_keys="[Image.celebrity_id]") - # Chiave esterna per l'immagine del profilo profile_image_id = Column(Integer, ForeignKey("images.id", ondelete="SET NULL"), nullable=True) - # 2. Relazione "una celebrità ha UNA immagine del profilo". - # SQLAlchemy deve usare la FK che si trova in QUESTA tabella (celebrities). - profile_image = relationship("Image", foreign_keys=[profile_image_id]) - created_at = Column(TIMESTAMP(timezone=True), server_default=func.now()) updated_at = Column(TIMESTAMP(timezone=True), default=func.now(), onupdate=func.now()) -# Nota: le altre tabelle (images, tattoos, etc.) andrebbero modellate qui -# se si volessero gestire le relazioni in modo completo con SQLAlchemy. -# Per questo esempio, ci concentriamo solo sulla tabella `celebrities`. \ No newline at end of file + # RELAZIONI + profile_image = relationship("Image", foreign_keys=[profile_image_id]) + + # Relazioni Uno-a-Molti (Una celebrità ha molti...) + images = relationship("Image", back_populates="celebrity", foreign_keys="Image.celebrity_id", cascade="all, delete-orphan") + tattoos = relationship("Tattoo", back_populates="celebrity", cascade="all, delete-orphan") + aliases = relationship("CelebrityAlias", back_populates="celebrity", cascade="all, delete-orphan") + activity_periods = relationship("ActivityPeriod", back_populates="celebrity", cascade="all, delete-orphan") + cosmetic_surgeries = relationship("CosmeticSurgery", back_populates="celebrity", cascade="all, delete-orphan") + social_media = relationship("SocialMediaAccount", back_populates="celebrity", cascade="all, delete-orphan") + external_links = relationship("ExternalLink", back_populates="celebrity", cascade="all, delete-orphan") + + # Relazioni Molti-a-Molti + professions = relationship("Profession", secondary=celebrity_professions, back_populates="celebrities") + studios = relationship("Studio", secondary=celebrity_studios, back_populates="celebrities") + videos = relationship("Video", secondary=video_performers, back_populates="performers") + + +# ============================================================================= +# MODELLI "LOOKUP" (PROFESSIONI, STUDIOS) +# ============================================================================= + +class Profession(Base): + __tablename__ = "professions" + id = Column(Integer, primary_key=True) + name = Column(String, unique=True, nullable=False) + celebrities = relationship("Celebrity", secondary=celebrity_professions, back_populates="professions") + +class Studio(Base): + __tablename__ = "studios" + id = Column(Integer, primary_key=True) + name = Column(String, unique=True, nullable=False) + celebrities = relationship("Celebrity", secondary=celebrity_studios, back_populates="studios") + videos = relationship("Video", back_populates="studio") + +class Video(Base): + __tablename__ = "videos" + id = Column(Integer, primary_key=True) + title = Column(String, nullable=False) + release_date = Column(Date) + studio_id = Column(Integer, ForeignKey("studios.id")) + description = Column(Text) + url_preview = Column(String) + + studio = relationship("Studio", back_populates="videos") + performers = relationship("Celebrity", secondary=video_performers, back_populates="videos") + +# ============================================================================= +# MODELLI SATELLITE (LEGATI A CELEBRITY CON RELAZIONE 1-A-MOLTI) +# ============================================================================= + +class Image(Base): + __tablename__ = "images" + id = Column(Integer, primary_key=True, index=True) + celebrity_id = Column(Integer, ForeignKey("celebrities.id", ondelete="CASCADE"), nullable=False) + file_path = Column(String, nullable=False) + caption = Column(Text, nullable=True) + uploaded_at = Column(TIMESTAMP(timezone=True), server_default=func.now()) + celebrity = relationship("Celebrity", back_populates="images", foreign_keys=[celebrity_id]) + +class Tattoo(Base): + __tablename__ = "tattoos" + id = Column(Integer, primary_key=True) + celebrity_id = Column(Integer, ForeignKey("celebrities.id", ondelete="CASCADE"), nullable=False) + description = Column(String, nullable=False) + body_location = Column(String) + celebrity = relationship("Celebrity", back_populates="tattoos") + +class CelebrityAlias(Base): + __tablename__ = "celebrity_aliases" + id = Column(Integer, primary_key=True) + celebrity_id = Column(Integer, ForeignKey("celebrities.id", ondelete="CASCADE"), nullable=False) + alias_name = Column(String, nullable=False) + celebrity = relationship("Celebrity", back_populates="aliases") + +class ActivityPeriod(Base): + __tablename__ = "activity_periods" + id = Column(Integer, primary_key=True) + celebrity_id = Column(Integer, ForeignKey("celebrities.id", ondelete="CASCADE"), nullable=False) + start_year = Column(Integer, nullable=False) + end_year = Column(Integer, nullable=True) + notes = Column(Text) + celebrity = relationship("Celebrity", back_populates="activity_periods") + +class CosmeticSurgery(Base): + __tablename__ = "cosmetic_surgeries" + id = Column(Integer, primary_key=True) + celebrity_id = Column(Integer, ForeignKey("celebrities.id", ondelete="CASCADE"), nullable=False) + surgery_type = Column(Enum(SurgeryType), nullable=False) + surgery_date = Column(Date) + new_bra_band_size = Column(Integer) + new_bra_cup_size = Column(String) + new_bra_size_system = Column(Enum(BraSystemType)) + notes = Column(Text) + created_at = Column(TIMESTAMP(timezone=True), server_default=func.now()) + celebrity = relationship("Celebrity", back_populates="cosmetic_surgeries") + +class SocialMediaAccount(Base): + __tablename__ = "social_media_accounts" + id = Column(Integer, primary_key=True) + celebrity_id = Column(Integer, ForeignKey("celebrities.id", ondelete="CASCADE"), nullable=False) + platform = Column(String, nullable=False) + url = Column(String, unique=True, nullable=False) + follower_count = Column(Integer) + last_checked_date = Column(Date) + celebrity = relationship("Celebrity", back_populates="social_media") + +class ExternalLink(Base): + __tablename__ = "external_links" + id = Column(Integer, primary_key=True) + celebrity_id = Column(Integer, ForeignKey("celebrities.id", ondelete="CASCADE"), nullable=False) + url = Column(String, nullable=False) + title = Column(String) + source_website = Column(String) + publication_date = Column(Date) + celebrity = relationship("Celebrity", back_populates="external_links") \ No newline at end of file diff --git a/packages/backend/app/schemas.py b/packages/backend/app/schemas.py index eba01c6..6ab0dbc 100644 --- a/packages/backend/app/schemas.py +++ b/packages/backend/app/schemas.py @@ -1,7 +1,83 @@ from pydantic import BaseModel -from typing import Optional +from typing import Optional, List from datetime import date, datetime -from .models import GenderType, ShoeSystemType, BraSystemType +from .models import GenderType, ShoeSystemType, BraSystemType, SurgeryType + +# ============================================================================= +# SCHEMI PER I MODELLI CORRELATI (LOOKUP E SATELLITE) +# ============================================================================= + +# --- Profession --- +class ProfessionBase(BaseModel): + name: str +class ProfessionCreate(ProfessionBase): + pass +class Profession(ProfessionBase): + id: int + class Config: from_attributes = True + +# --- Studio --- +class StudioBase(BaseModel): + name: str +class StudioCreate(StudioBase): + pass +class Studio(StudioBase): + id: int + class Config: from_attributes = True + +# --- Tattoo --- +class TattooBase(BaseModel): + description: str + body_location: Optional[str] = None +class TattooCreate(TattooBase): + pass +class Tattoo(TattooBase): + id: int + celebrity_id: int + class Config: from_attributes = True + +# --- Alias --- +class CelebrityAliasBase(BaseModel): + alias_name: str +class CelebrityAliasCreate(CelebrityAliasBase): + pass +class CelebrityAlias(CelebrityAliasBase): + id: int + celebrity_id: int + class Config: from_attributes = True + +# --- Cosmetic Surgery --- +class CosmeticSurgeryBase(BaseModel): + surgery_type: SurgeryType + surgery_date: Optional[date] = None + new_bra_band_size: Optional[int] = None + new_bra_cup_size: Optional[str] = None + new_bra_size_system: Optional[BraSystemType] = None + notes: Optional[str] = None +class CosmeticSurgeryCreate(CosmeticSurgeryBase): + pass +class CosmeticSurgery(CosmeticSurgeryBase): + id: int + celebrity_id: int + created_at: datetime + class Config: from_attributes = True + +# --- Image --- +class ImageBase(BaseModel): + file_path: str + caption: Optional[str] = None +class ImageCreate(ImageBase): + pass +class Image(ImageBase): + id: int + celebrity_id: int + uploaded_at: datetime + class Config: from_attributes = True + + +# ============================================================================= +# SCHEMI PER CELEBRITY +# ============================================================================= # Schema di base con i campi comuni class CelebrityBase(BaseModel): @@ -63,11 +139,22 @@ class CelebrityUpdate(BaseModel): official_website: Optional[str] = None profile_image_id: Optional[int] = None -# Schema per la lettura dei dati (include campi generati dal DB) +# Schema per la lettura dei dati (include campi generati dal DB e le relazioni) class Celebrity(CelebrityBase): id: int created_at: datetime updated_at: datetime + + # Campi relazionali che verranno popolati automaticamente da SQLAlchemy + images: List[Image] = [] + tattoos: List[Tattoo] = [] + aliases: List[CelebrityAlias] = [] + + # Per semplicità, possiamo usare qui gli schemi di base, + # ma in un'app reale potresti volere schemi specifici per la "lettura". + professions: List[Profession] = [] + studios: List[Studio] = [] + cosmetic_surgeries: List[CosmeticSurgery] = [] class Config: from_attributes = True # Permette a Pydantic di leggere dati da un modello ORM \ No newline at end of file