Files
spotify_vibe/app/db/models.py
2026-02-26 19:33:05 +00:00

116 lines
5.6 KiB
Python

from __future__ import annotations
from datetime import date, datetime
from sqlalchemy import Boolean, Date, DateTime, Float, ForeignKey, Integer, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base, TimestampMixin
class User(Base, TimestampMixin):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
telegram_chat_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
telegram_username: Mapped[str | None] = mapped_column(String(128), nullable=True)
spotify_user_id: Mapped[str | None] = mapped_column(String(128), unique=True, nullable=True, index=True)
spotify_access_token: Mapped[str | None] = mapped_column(Text, nullable=True)
spotify_refresh_token: Mapped[str | None] = mapped_column(Text, nullable=True)
spotify_token_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
spotify_scopes: Mapped[str | None] = mapped_column(Text, nullable=True)
timezone: Mapped[str] = mapped_column(String(64), default="UTC")
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
playlist_size: Mapped[int] = mapped_column(Integer, default=30)
min_new_ratio: Mapped[float] = mapped_column(Float, default=0.8)
last_generated_date: Mapped[date | None] = mapped_column(Date, nullable=True)
latest_playlist_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
latest_playlist_url: Mapped[str | None] = mapped_column(Text, nullable=True)
saved_tracks: Mapped[list["SavedTrack"]] = relationship(back_populates="user", cascade="all, delete-orphan")
playlist_runs: Mapped[list["PlaylistRun"]] = relationship(back_populates="user", cascade="all, delete-orphan")
rec_history: Mapped[list["RecommendationHistory"]] = relationship(
back_populates="user", cascade="all, delete-orphan"
)
class AuthState(Base):
__tablename__ = "auth_states"
id: Mapped[int] = mapped_column(primary_key=True)
state: Mapped[str] = mapped_column(String(128), unique=True, index=True)
telegram_chat_id: Mapped[str] = mapped_column(String(64), index=True)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
class SavedTrack(Base):
__tablename__ = "saved_tracks"
__table_args__ = (UniqueConstraint("user_id", "spotify_track_id", name="uq_saved_track_user_track"),)
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
spotify_track_id: Mapped[str] = mapped_column(String(128), index=True)
name: Mapped[str] = mapped_column(Text)
artist_names: Mapped[str] = mapped_column(Text)
artist_ids_csv: Mapped[str] = mapped_column(Text)
album_name: Mapped[str | None] = mapped_column(Text, nullable=True)
added_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
popularity: Mapped[int | None] = mapped_column(Integer, nullable=True)
user: Mapped[User] = relationship(back_populates="saved_tracks")
class RecommendationHistory(Base):
__tablename__ = "recommendation_history"
__table_args__ = (UniqueConstraint("user_id", "spotify_track_id", name="uq_rec_history_user_track"),)
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
spotify_track_id: Mapped[str] = mapped_column(String(128), index=True)
first_recommended_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
last_recommended_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
times_recommended: Mapped[int] = mapped_column(Integer, default=1)
user: Mapped[User] = relationship(back_populates="rec_history")
class PlaylistRun(Base, TimestampMixin):
__tablename__ = "playlist_runs"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
run_date: Mapped[date] = mapped_column(Date)
status: Mapped[str] = mapped_column(String(32), default="pending")
playlist_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
playlist_name: Mapped[str | None] = mapped_column(Text, nullable=True)
playlist_url: Mapped[str | None] = mapped_column(Text, nullable=True)
total_tracks: Mapped[int] = mapped_column(Integer, default=0)
new_tracks: Mapped[int] = mapped_column(Integer, default=0)
reused_tracks: Mapped[int] = mapped_column(Integer, default=0)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
user: Mapped[User] = relationship(back_populates="playlist_runs")
tracks: Mapped[list["PlaylistRunTrack"]] = relationship(back_populates="run", cascade="all, delete-orphan")
class PlaylistRunTrack(Base):
__tablename__ = "playlist_run_tracks"
__table_args__ = (
UniqueConstraint("run_id", "spotify_track_id", name="uq_run_track"),
UniqueConstraint("run_id", "position", name="uq_run_position"),
)
id: Mapped[int] = mapped_column(primary_key=True)
run_id: Mapped[int] = mapped_column(ForeignKey("playlist_runs.id", ondelete="CASCADE"), index=True)
spotify_track_id: Mapped[str] = mapped_column(String(128), index=True)
name: Mapped[str] = mapped_column(Text)
artist_names: Mapped[str] = mapped_column(Text)
source: Mapped[str] = mapped_column(String(64))
position: Mapped[int] = mapped_column(Integer)
is_new_to_bot: Mapped[bool] = mapped_column(Boolean, default=True)
run: Mapped[PlaylistRun] = relationship(back_populates="tracks")