58 lines
1.6 KiB
Python
58 lines
1.6 KiB
Python
|
|
"""news tables: scraped articles + LLM article_summaries
|
||
|
|
|
||
|
|
Revision ID: 003_news
|
||
|
|
Revises: 002_cameras
|
||
|
|
Create Date: 2026-08-24
|
||
|
|
"""
|
||
|
|
|
||
|
|
from alembic import op
|
||
|
|
import sqlalchemy as sa # noqa: F401
|
||
|
|
|
||
|
|
# revision identifiers, used by Alembic.
|
||
|
|
revision = '003_news'
|
||
|
|
down_revision = '002_cameras'
|
||
|
|
branch_labels = None
|
||
|
|
depends_on = None
|
||
|
|
|
||
|
|
|
||
|
|
def upgrade() -> None:
|
||
|
|
# Idempotent DDL: the news scraper's Scrapy pipeline also issues
|
||
|
|
# `CREATE TABLE IF NOT EXISTS articles`, so either the scraper or the app
|
||
|
|
# may create these first depending on container startup order. IF NOT
|
||
|
|
# EXISTS makes both orders safe — whichever runs first wins, the other
|
||
|
|
# no-ops. Same table shapes as the upstream newsPipeline services.
|
||
|
|
op.execute(
|
||
|
|
"""
|
||
|
|
CREATE TABLE IF NOT EXISTS articles (
|
||
|
|
id SERIAL PRIMARY KEY,
|
||
|
|
title TEXT,
|
||
|
|
url TEXT UNIQUE,
|
||
|
|
content TEXT,
|
||
|
|
domain TEXT,
|
||
|
|
timestamp TIMESTAMPTZ
|
||
|
|
)
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
op.execute(
|
||
|
|
"CREATE INDEX IF NOT EXISTS ix_articles_timestamp ON articles (timestamp)"
|
||
|
|
)
|
||
|
|
|
||
|
|
op.execute(
|
||
|
|
"""
|
||
|
|
CREATE TABLE IF NOT EXISTS article_summaries (
|
||
|
|
id SERIAL PRIMARY KEY,
|
||
|
|
summary_text TEXT NOT NULL,
|
||
|
|
batch_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
|
|
)
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
op.execute(
|
||
|
|
"CREATE INDEX IF NOT EXISTS ix_article_summaries_batch_timestamp "
|
||
|
|
"ON article_summaries (batch_timestamp)"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def downgrade() -> None:
|
||
|
|
op.execute("DROP TABLE IF EXISTS article_summaries")
|
||
|
|
op.execute("DROP TABLE IF EXISTS articles")
|