Initial import of Politrack, a self-hosted service that tracks Austrian politicians from federal to municipal level - roles, votes, money flows, statements, meetings, social media and legal proceedings - with every claim linked to its sources. - backend: FastAPI + Postgres/pgvector; adapter framework (fetch -> parse -> load -> enrich) with robots.txt/TDM-aware fetching, a raw-response archive for reparsing, and a claim/evidence model with corroboration - sources: Parliament, Wikidata, EU Parliament/Commission, Council of Ministers, Rechnungshof party reports and donations, procurement, RTR media transparency, political ad libraries, WKStA, news feeds, Wikipedia edits, Bluesky, Mastodon, public Telegram channels, YouTube and X (official API) - web: Next.js frontend in German and English with typed API client - mcp: MCP server for AI agents and RAG - docs: architecture, data model, sources, legal notes, Unraid deployment and contributor guides; Docker Compose setup for Unraid Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
36 lines
1.2 KiB
Python
36 lines
1.2 KiB
Python
"""Minimal forward-only SQL migration runner (files in backend/migrations)."""
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
import psycopg
|
|
from psycopg import sql
|
|
|
|
from politrack.config import get_settings
|
|
|
|
log = logging.getLogger(__name__)
|
|
MIGRATIONS_DIR = Path(__file__).resolve().parent.parent / "migrations"
|
|
|
|
|
|
def migrate() -> None:
|
|
settings = get_settings()
|
|
with psycopg.connect(settings.database_url, autocommit=False) as conn:
|
|
conn.execute(
|
|
"CREATE TABLE IF NOT EXISTS schema_migrations ("
|
|
" name TEXT PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT now())"
|
|
)
|
|
applied = {r[0] for r in conn.execute("SELECT name FROM schema_migrations")}
|
|
for path in sorted(MIGRATIONS_DIR.glob("*.sql")):
|
|
if path.name in applied:
|
|
continue
|
|
log.info("applying %s", path.name)
|
|
conn.execute(path.read_text(encoding="utf-8"))
|
|
conn.execute("INSERT INTO schema_migrations (name) VALUES (%s)", (path.name,))
|
|
conn.commit()
|
|
|
|
if settings.reader_password:
|
|
conn.execute(
|
|
sql.SQL("ALTER ROLE politrack_reader PASSWORD {}").format(sql.Literal(settings.reader_password))
|
|
)
|
|
conn.commit()
|