- 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>
63 lines
2.2 KiB
Python
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()
|
|
}
|