Electronics Catalog: API, MCP server, frontend and deployment
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>
This commit is contained in:
63
backend/app/electronics/db/migrate.py
Normal file
63
backend/app/electronics/db/migrate.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user