Verified catalogue of mobiles and laptops sold in India, collected from real retail listings (FastAPI backend, React frontend, Postgres/pgvector). - REST API under /api/elec (read-only catalogue; admin endpoints need login) - MCP server (FastMCP) at /mcp/ with list_categories, search_products, get_product and price_history tools - Real ratings and reviews read from product pages and search results - Production Dockerfile (requirements-api.txt, no PyTorch) and .env.production.example; remote database only via an explicit ELEC_ALLOW_REMOTE_DB host/name allowlist - docs/API.md: endpoint and MCP reference with live examples Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
64 lines
2.1 KiB
Python
64 lines
2.1 KiB
Python
"""Tiny migration runner for the numbered SQL files in ./migrations.
|
|
|
|
Each file runs once, in its own transaction, and is recorded in
|
|
elec.schema_migrations with a checksum. Editing an applied file is refused
|
|
rather than silently ignored - add a new numbered file instead.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
from app.electronics.db.connection import connect
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
MIGRATIONS_DIR = Path(__file__).resolve().parent / "migrations"
|
|
|
|
_BOOTSTRAP = """
|
|
CREATE SCHEMA IF NOT EXISTS elec;
|
|
CREATE TABLE IF NOT EXISTS elec.schema_migrations (
|
|
version TEXT PRIMARY KEY,
|
|
checksum TEXT NOT NULL,
|
|
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
"""
|
|
|
|
|
|
def _files() -> List[Path]:
|
|
return sorted(MIGRATIONS_DIR.glob("[0-9][0-9][0-9][0-9]_*.sql"))
|
|
|
|
|
|
def run_migrations() -> List[str]:
|
|
"""Apply pending migrations. Returns the versions applied by this call."""
|
|
applied_now: List[str] = []
|
|
with connect() as conn:
|
|
conn.execute(_BOOTSTRAP)
|
|
conn.commit()
|
|
done = {
|
|
r["version"]: r["checksum"]
|
|
for r in conn.execute("SELECT version, checksum FROM elec.schema_migrations")
|
|
}
|
|
for path in _files():
|
|
sql = path.read_text(encoding="utf-8")
|
|
checksum = hashlib.sha256(sql.encode("utf-8")).hexdigest()
|
|
version = path.stem
|
|
if version in done:
|
|
if done[version] != checksum:
|
|
raise RuntimeError(
|
|
f"Migration {version} was edited after it was applied. "
|
|
f"Revert the edit and add a new numbered migration instead."
|
|
)
|
|
continue
|
|
logger.info("Applying migration %s", version)
|
|
with conn.transaction():
|
|
conn.execute(sql)
|
|
conn.execute(
|
|
"INSERT INTO elec.schema_migrations (version, checksum) VALUES (%s, %s)",
|
|
(version, checksum),
|
|
)
|
|
applied_now.append(version)
|
|
return applied_now
|