RECORDGOD/app/main.py

91 lines
4.2 KiB
Python

import os
import pathlib
# Load .env FIRST so auth/db/vault see their keys at import time.
_ENV = pathlib.Path(__file__).resolve().parent.parent / ".env"
if _ENV.exists():
for _ln in _ENV.read_text().splitlines():
_ln = _ln.strip()
if _ln and not _ln.startswith("#") and "=" in _ln:
_k, _v = _ln.split("=", 1)
os.environ.setdefault(_k.strip(), _v.strip())
from fastapi import FastAPI # noqa: E402
from fastapi.staticfiles import StaticFiles # noqa: E402
from fastapi.responses import FileResponse # noqa: E402
from . import __version__ # noqa: E402
from .wowplatter_v1 import router as wowplatter_v1_router # noqa: E402
from .virtual import router as virtual_router # noqa: E402
from .settings_routes import router as settings_router # noqa: E402
from .admin_routes import router as admin_router # noqa: E402
from .layout_routes import router as layout_router # noqa: E402
from .shop_routes import router as shop_router # noqa: E402
from .sales_routes import router as sales_router # noqa: E402
from .db import engine # noqa: E402
from sqlalchemy import text as _sqltext # noqa: E402
app = FastAPI(title="RECORDGOD", version=__version__)
app.include_router(wowplatter_v1_router)
app.include_router(virtual_router)
app.include_router(settings_router)
app.include_router(admin_router)
app.include_router(layout_router)
app.include_router(shop_router)
app.include_router(sales_router)
_STARTUP_DDL = [
"CREATE TABLE IF NOT EXISTS store_config (store_id int PRIMARY KEY, config jsonb NOT NULL, updated_at timestamptz NOT NULL DEFAULT now())",
# POS fields on the migrated sales tables
"ALTER TABLE sales ADD COLUMN IF NOT EXISTS subtotal numeric(10,2)",
"ALTER TABLE sales ADD COLUMN IF NOT EXISTS discount_amount numeric(10,2) DEFAULT 0",
"ALTER TABLE sales ADD COLUMN IF NOT EXISTS payment_status text DEFAULT 'paid'",
"ALTER TABLE sales ADD COLUMN IF NOT EXISTS amount_paid numeric(10,2) DEFAULT 0",
"ALTER TABLE sales ADD COLUMN IF NOT EXISTS hold_expires_at timestamptz",
"ALTER TABLE sales ADD COLUMN IF NOT EXISTS split_payments jsonb",
"ALTER TABLE sales ADD COLUMN IF NOT EXISTS trade_in_credit numeric(10,2) DEFAULT 0",
"ALTER TABLE sale_items ADD COLUMN IF NOT EXISTS discount_amount numeric(10,2) DEFAULT 0",
"ALTER TABLE sale_items ADD COLUMN IF NOT EXISTS original_price numeric(10,2)",
# auto-id for new POS rows (migrated tables came without sequences)
"CREATE SEQUENCE IF NOT EXISTS sales_id_seq",
"ALTER TABLE sales ALTER COLUMN id SET DEFAULT nextval('sales_id_seq')",
"SELECT setval('sales_id_seq', GREATEST(1, (SELECT coalesce(max(id),0) FROM sales)))",
"CREATE SEQUENCE IF NOT EXISTS sale_items_id_seq",
"ALTER TABLE sale_items ALTER COLUMN id SET DEFAULT nextval('sale_items_id_seq')",
"SELECT setval('sale_items_id_seq', GREATEST(1, (SELECT coalesce(max(id),0) FROM sale_items)))",
]
@app.on_event("startup")
async def _ensure_tables():
# lazy migrations on deploy (store_config + POS columns/sequences)
async with engine.begin() as conn:
for _stmt in _STARTUP_DDL:
await conn.execute(_sqltext(_stmt))
@app.get("/healthz")
async def healthz():
return {"ok": True, "service": "recordgod", "version": __version__}
_ROOT = pathlib.Path(__file__).resolve().parent.parent
# Pretty URLs: /builder /shop /admin /dash → the .html pages (registered before the "/" mount wins).
def _page(filename):
async def _serve():
return FileResponse(_ROOT / "site" / filename)
return _serve
for _stub in ("shop", "builder", "admin", "dash", "pos"):
app.add_api_route(f"/{_stub}", _page(_stub + ".html"), methods=["GET"], include_in_schema=False)
# Mounts come last so the API routes + /healthz match first; "/" serves the landing/dash site.
if (_ROOT / "webstore").is_dir():
app.mount("/store", StaticFiles(directory=str(_ROOT / "webstore"), html=True), name="store")
if (_ROOT / "site").is_dir():
app.mount("/", StaticFiles(directory=str(_ROOT / "site"), html=True), name="site")