Files
yottob/models.py
Ryan Chen 4892bec986 Add SQLAlchemy ORM with Alembic migrations
- Added SQLAlchemy 2.0 and Alembic 1.13 dependencies
- Created models.py with Channel and VideoEntry ORM models
- Created database.py for database configuration and session management
- Initialized Alembic migration system with initial migration
- Updated feed_parser.py with save_to_db() method for persistence
- Updated main.py with database initialization and new API routes:
  - /api/feed now saves to database by default
  - /api/channels lists all tracked channels
  - /api/history/<channel_id> returns video history
- Updated .gitignore to exclude database files
- Updated CLAUDE.md with comprehensive ORM and migration documentation

Database uses SQLite (yottob.db) with upsert logic to avoid duplicates.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 13:58:10 -05:00

63 lines
2.2 KiB
Python

"""Database models for YouTube feed storage."""
from datetime import datetime
from typing import List
from sqlalchemy import String, DateTime, ForeignKey, Index
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
"""Base class for all database models."""
pass
class Channel(Base):
"""YouTube channel model."""
__tablename__ = "channels"
id: Mapped[int] = mapped_column(primary_key=True)
channel_id: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
title: Mapped[str] = mapped_column(String(200), nullable=False)
link: Mapped[str] = mapped_column(String(500), nullable=False)
last_fetched: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=datetime.utcnow)
# Relationship to video entries
videos: Mapped[List["VideoEntry"]] = relationship("VideoEntry", back_populates="channel", cascade="all, delete-orphan")
def __repr__(self) -> str:
return f"<Channel(id={self.id}, channel_id='{self.channel_id}', title='{self.title}')>"
class VideoEntry(Base):
"""YouTube video entry model."""
__tablename__ = "video_entries"
id: Mapped[int] = mapped_column(primary_key=True)
channel_id: Mapped[int] = mapped_column(ForeignKey("channels.id"), nullable=False)
title: Mapped[str] = mapped_column(String(500), nullable=False)
link: Mapped[str] = mapped_column(String(500), unique=True, nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=datetime.utcnow)
# Relationship to channel
channel: Mapped["Channel"] = relationship("Channel", back_populates="videos")
# Index for faster queries
__table_args__ = (
Index('idx_channel_created', 'channel_id', 'created_at'),
)
def __repr__(self) -> str:
return f"<VideoEntry(id={self.id}, title='{self.title}', link='{self.link}')>"
def to_dict(self) -> dict:
"""Convert to dictionary for API responses."""
return {
"id": self.id,
"title": self.title,
"link": self.link,
"created_at": self.created_at.isoformat()
}