Files
xenia-pos-cloud/cloud_backend/main.py
bonamin a73f081ca1 fix: QR menu branding fields — display name, header image, empty-to-hide
- menu_display_name is now separate from the site's internal name (was
  incorrectly showing the sysadmin-only site name on the public menu).
- Header image now resolves against the cloud API origin instead of
  the menu-app's own origin, fixing the broken-image icon.
- Tagline/blurb/hours now genuinely hide when cleared: the sysadmin
  save handler was converting empty fields to null, which the PUT
  endpoint's exclude_none silently drops instead of clearing.
- Added a settable blurb (EN/GR) below the tagline, previously
  hardcoded ("Fresh seasonal plates, served with care.").
- Collapsed hours from two fields (EN/GR) to one — opening hours don't
  vary by language.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 10:44:28 +03:00

97 lines
3.6 KiB
Python

import os
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from config import settings
from database import engine, Base
from auth_utils import hash_password
import models.admin # noqa: F401
import models.site # noqa: F401
import models.menu_snapshot # noqa: F401
import models.online_order # noqa: F401
import models.manager_account # noqa: F401
import models.stats_snapshot # noqa: F401
from routers import auth, sites, heartbeat
from routers import menu as menu_router
from routers import orders as orders_router
from routers import manager_auth as manager_auth_router
from routers import remote_dashboard as remote_dashboard_router
def _seed_default_admin():
from sqlalchemy.orm import Session
from models.admin import Admin
with Session(engine) as db:
if not db.query(Admin).filter(Admin.username == settings.ADMIN_USERNAME).first():
db.add(Admin(
username=settings.ADMIN_USERNAME,
password_hash=hash_password(settings.ADMIN_PASSWORD),
role="sysadmin",
))
db.commit()
def _run_migrations():
"""Apply additive schema changes that create_all won't handle."""
from sqlalchemy import text
migrations = [
# Per-site order counter for public_ref generation (e.g. "ORD-0042")
"ALTER TABLE sites ADD COLUMN order_counter INTEGER NOT NULL DEFAULT 0",
# QR menu branding/config
"ALTER TABLE sites ADD COLUMN menu_mode VARCHAR NOT NULL DEFAULT 'order'",
"ALTER TABLE sites ADD COLUMN menu_tagline_en VARCHAR",
"ALTER TABLE sites ADD COLUMN menu_tagline_gr VARCHAR",
"ALTER TABLE sites ADD COLUMN menu_hours_en VARCHAR", # superseded by menu_hours, kept for history
"ALTER TABLE sites ADD COLUMN menu_hours_gr VARCHAR", # superseded by menu_hours, kept for history
"ALTER TABLE sites ADD COLUMN menu_header_image_url VARCHAR",
"ALTER TABLE sites ADD COLUMN menu_display_name VARCHAR",
"ALTER TABLE sites ADD COLUMN menu_blurb_en VARCHAR",
"ALTER TABLE sites ADD COLUMN menu_blurb_gr VARCHAR",
"ALTER TABLE sites ADD COLUMN menu_hours VARCHAR",
]
for sql in migrations:
try:
with engine.connect() as conn:
conn.execute(text(sql))
conn.commit()
except Exception:
pass
@asynccontextmanager
async def lifespan(app: FastAPI):
Base.metadata.create_all(bind=engine)
_run_migrations()
_seed_default_admin()
yield
app = FastAPI(title="POS Cloud Backend", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
app.include_router(sites.router, prefix="/api/sites", tags=["sites"])
app.include_router(heartbeat.router, prefix="/api/heartbeat", tags=["heartbeat"])
app.include_router(menu_router.router, prefix="/api/menu", tags=["menu"])
app.include_router(orders_router.router, prefix="/api/orders", tags=["orders"])
app.include_router(manager_auth_router.router, prefix="/api/manager", tags=["manager"])
app.include_router(remote_dashboard_router.router,prefix="/api/remote", tags=["remote"])
os.makedirs("/app/data/site_headers", exist_ok=True)
app.mount("/static/site_headers", StaticFiles(directory="/app/data/site_headers"), name="site_headers")
@app.get("/health")
def health():
return {"status": "ok"}