from datetime import datetime from sqlalchemy import ( Boolean, Column, DateTime, Float, ForeignKey, Integer, String, Text, UniqueConstraint, ) from .database import Base class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String, unique=True, nullable=False, index=True) email = Column(String, unique=True, nullable=False, index=True) hashed_password = Column(String, nullable=False) is_admin = Column(Boolean, default=False) created_at = Column(DateTime, default=datetime.utcnow) class Channel(Base): __tablename__ = "channels" id = Column(Integer, primary_key=True, index=True) youtube_channel_id = Column(String, unique=True, nullable=False, index=True) name = Column(String, nullable=False) description = Column(Text) thumbnail_url = Column(String) banner_url = Column(String) crawled_at = Column(DateTime) subscriber_count = Column(Integer) class UserChannel(Base): __tablename__ = "user_channels" __table_args__ = (UniqueConstraint("user_id", "channel_id"),) id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) channel_id = Column(Integer, ForeignKey("channels.id", ondelete="CASCADE"), nullable=False) status = Column(String, default="followed") # followed / dismissed / pending added_at = Column(DateTime, default=datetime.utcnow) auto_download = Column(Boolean, default=None) # None = use global, True/False = override last_seen_at = Column(DateTime, default=None) muted_until = Column(DateTime, default=None) notes = Column(Text, default="") class Video(Base): __tablename__ = "videos" id = Column(Integer, primary_key=True, index=True) youtube_video_id = Column(String, unique=True, nullable=False, index=True) channel_id = Column(Integer, ForeignKey("channels.id", ondelete="SET NULL"), nullable=True) title = Column(String, nullable=False) description = Column(Text) thumbnail_url = Column(String) duration_seconds = Column(Integer) published_at = Column(DateTime) indexed_at = Column(DateTime, default=datetime.utcnow) tags = Column(Text) # JSON array string category = Column(String) chapters = Column(Text) # JSON array of {start_time, end_time, title} class UserVideo(Base): __tablename__ = "user_videos" __table_args__ = (UniqueConstraint("user_id", "video_id"),) id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) video_id = Column(Integer, ForeignKey("videos.id", ondelete="CASCADE"), nullable=False) watched = Column(Boolean, default=False) watch_progress_seconds = Column(Integer, default=0) completion_percent = Column(Float, default=None) # 0–100, set when video ends/navigates away rewatch_count = Column(Integer, default=0) # incremented each time a completed video is replayed queued = Column(Boolean, default=False) downloaded = Column(Boolean, default=False) liked = Column(Boolean, default=False) rating = Column(Integer, default=None) # NULL=unrated, 1=thumbs up, -1=thumbs down downloaded_at = Column(DateTime) liked_at = Column(DateTime) last_watched_at = Column(DateTime) class Download(Base): __tablename__ = "downloads" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) video_id = Column(Integer, ForeignKey("videos.id", ondelete="CASCADE"), nullable=False) status = Column(String, default="pending") # pending / downloading / complete / failed progress_percent = Column(Float, default=0.0) file_path = Column(String) resolution = Column(String) # e.g. "1080p", "720p" created_at = Column(DateTime, default=datetime.utcnow) completed_at = Column(DateTime) error_message = Column(Text) pending_delete_at = Column(DateTime, default=None) class UserSettings(Base): __tablename__ = "user_settings" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True) preferred_quality = Column(String, default="best") # best / 1080p / 720p / 480p / 360p max_concurrent_downloads = Column(Integer, default=3) # 1–5 hide_watched_from_feed = Column(Boolean, default=False) mark_watched_at_percent = Column(Integer, default=90) # 50–100 auto_download_on_sync = Column(Boolean, default=False) cookies_browser = Column(String, default="") # chrome / firefox / etc., "" = disabled theater_mode = Column(Boolean, default=False) discovery_regions = Column(String, default="US,SE") # comma-separated ISO country codes calm_mode = Column(Boolean, default=False) hide_subscriber_counts = Column(Boolean, default=False) autoplay_enabled = Column(Boolean, default=False) feed_weight_recency = Column(Float, default=5.0) # 0–10 feed_weight_affinity = Column(Float, default=5.0) # 0–10 feed_weight_channel = Column(Float, default=5.0) # 0–10 class DiscoveryQueue(Base): __tablename__ = "discovery_queue" __table_args__ = (UniqueConstraint("user_id", "channel_id"),) id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) channel_id = Column(Integer, ForeignKey("channels.id", ondelete="CASCADE"), nullable=False) score = Column(Float, default=0.0) source = Column(String) # search / community / category / liked seen = Column(Boolean, default=False) preview_json = Column(Text) # JSON: [{thumbnail_url, title}, ...] created_at = Column(DateTime, default=datetime.utcnow) class ChannelGroup(Base): __tablename__ = "channel_groups" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) name = Column(String, nullable=False) created_at = Column(DateTime, default=datetime.utcnow) class ChannelGroupMember(Base): __tablename__ = "channel_group_members" __table_args__ = (UniqueConstraint("group_id", "channel_id"),) id = Column(Integer, primary_key=True, index=True) group_id = Column(Integer, ForeignKey("channel_groups.id", ondelete="CASCADE"), nullable=False) channel_id = Column(Integer, ForeignKey("channels.id", ondelete="CASCADE"), nullable=False) class UserTagAffinity(Base): """Per-user taste signal: how much the user engages with a given tag or category.""" __tablename__ = "user_tag_affinity" __table_args__ = (UniqueConstraint("user_id", "tag"),) id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) tag = Column(String, nullable=False, index=True) score = Column(Float, default=0.0) updated_at = Column(DateTime, default=datetime.utcnow) class Collection(Base): __tablename__ = "collections" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) name = Column(String, nullable=False) created_at = Column(DateTime, default=datetime.utcnow) class CollectionItem(Base): __tablename__ = "collection_items" __table_args__ = (UniqueConstraint("collection_id", "video_id"),) id = Column(Integer, primary_key=True, index=True) collection_id = Column(Integer, ForeignKey("collections.id", ondelete="CASCADE"), nullable=False) video_id = Column(Integer, ForeignKey("videos.id", ondelete="CASCADE"), nullable=False) added_at = Column(DateTime, default=datetime.utcnow) class GraphEdge(Base): __tablename__ = "graph_edges" __table_args__ = (UniqueConstraint("from_channel_id", "to_channel_id"),) id = Column(Integer, primary_key=True, index=True) from_channel_id = Column(Integer, ForeignKey("channels.id", ondelete="CASCADE"), nullable=False) to_channel_id = Column(Integer, ForeignKey("channels.id", ondelete="CASCADE"), nullable=False) mention_count = Column(Integer, default=1) last_seen = Column(DateTime, default=datetime.utcnow) class SystemConfig(Base): __tablename__ = "system_config" key = Column(String, primary_key=True) value = Column(String, nullable=False) class SearchHistory(Base): __tablename__ = "search_history" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) query = Column(String, nullable=False) searched_at = Column(DateTime, default=datetime.utcnow) class VideoBookmark(Base): __tablename__ = "video_bookmarks" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) video_id = Column(Integer, ForeignKey("videos.id", ondelete="CASCADE"), nullable=False) timestamp_seconds = Column(Integer, nullable=False) note = Column(Text, default="") source = Column(String, default="manual") # manual | auto created_at = Column(DateTime, default=datetime.utcnow)