- Split database.py into a runtime engine (scoped `kds` DB role, used for all request handling) and a migration engine (privileged `kitchen` role, used only at startup to create KDS's own tables and ALTER kitchen_settings) — KDS previously shared kitchen's full-access DB credential wholesale - Dispose both engines on shutdown (main.py) - Fix theme colour clash: KDS was accidentally seeded with kitchen's teal (#0d9488) instead of its own colour — now #ea580c (orange), regenerated PWA icons to match Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
61 lines
2 KiB
Python
61 lines
2 KiB
Python
import os
|
|
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
|
from sqlalchemy.orm import DeclarativeBase
|
|
|
|
|
|
def _to_asyncpg(url: str) -> str:
|
|
if url.startswith("postgresql://"):
|
|
return url.replace("postgresql://", "postgresql+asyncpg://", 1)
|
|
return url
|
|
|
|
|
|
# Runtime connection — the scoped `kds` DB role, used for every request via
|
|
# get_db(). Only has SELECT/INSERT/UPDATE/DELETE on kds_tickets,
|
|
# kds_course_bumps and the specific kitchen_settings columns KDS needs (see
|
|
# deploy_kds grants) — it cannot read the rest of kitchen_db.
|
|
DATABASE_URL = _to_asyncpg(os.getenv(
|
|
"DATABASE_URL",
|
|
"postgresql://kitchen:kitchen_secret@localhost:5432/kitchen_gp"
|
|
))
|
|
|
|
# Migration connection — the privileged `kitchen` DB role. Used ONLY at
|
|
# startup to create KDS's own tables and ALTER kitchen_settings (adding KDS
|
|
# columns). Never used for request handling. Falls back to DATABASE_URL for
|
|
# local/dev convenience, but production must set this separately once the
|
|
# `kds` role is scoped down (see port log E17/task: KDS DB role split).
|
|
MIGRATION_DATABASE_URL = _to_asyncpg(
|
|
os.getenv("MIGRATION_DATABASE_URL") or os.getenv(
|
|
"DATABASE_URL", "postgresql://kitchen:kitchen_secret@localhost:5432/kitchen_gp"
|
|
)
|
|
)
|
|
|
|
# `engine` stays the name existing migration files (`from database import
|
|
# engine`) already import — no changes needed there. It's bound to the
|
|
# privileged connection.
|
|
engine = create_async_engine(MIGRATION_DATABASE_URL, echo=False)
|
|
|
|
runtime_engine = create_async_engine(
|
|
DATABASE_URL,
|
|
echo=False,
|
|
pool_size=10, # Default is 5
|
|
max_overflow=20, # Default is 10 - allows burst to 30 connections
|
|
pool_pre_ping=True # Verify connections are alive before use
|
|
)
|
|
|
|
AsyncSessionLocal = async_sessionmaker(
|
|
runtime_engine,
|
|
class_=AsyncSession,
|
|
expire_on_commit=False
|
|
)
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
pass
|
|
|
|
|
|
async def get_db():
|
|
async with AsyncSessionLocal() as session:
|
|
try:
|
|
yield session
|
|
finally:
|
|
await session.close()
|