Commit Graph

13 Commits

Author SHA1 Message Date
7d75ef44f7 fix: explicitly import Site in manager_account to resolve relationship
SQLAlchemy couldn't resolve the string 'Site' in ManagerAccount's
relationship() at query time because Site wasn't guaranteed to be
imported first. Adding the explicit import ensures the mapper is
configured correctly regardless of import order.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 00:57:02 +03:00
3598a929e0 fix: switch admin auth to HTTPBearer for consistent Swagger UI
Same fix as manager_auth — OAuth2PasswordBearer was showing an OAuth2
form in Swagger instead of a plain Bearer token input, causing
'Not authenticated' when testing via the docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 00:54:30 +03:00
f291234c14 fix: switch manager auth from OAuth2PasswordBearer to HTTPBearer
OAuth2PasswordBearer caused Swagger UI to show a username/password/
client_id form instead of a simple token input, making the docs
unusable for testing. HTTPBearer shows a plain 'Bearer token' field,
consistent with how the local_backend handles auth.

No runtime behaviour change — token extraction is identical.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 00:49:09 +03:00
6c5e35d537 fix: use AppData path for SQLite DB on Windows, absolute path on Linux
The default sqlite:////app/data/cloud.db path only works inside Docker.
On Windows, Controlled Folder Access blocks writes to arbitrary paths.

Now mirrors the same pattern as local_backend/config.py:
  - Windows: %LOCALAPPDATA%/xenia_cloud/cloud.db (always writable)
  - Linux/Docker: next to main.py (overridden by DATABASE_URL in .env)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 00:38:50 +03:00
12bd589bb7 feat(connect): Phase 6 — menu-app and manager-app frontend SPAs
Two Vite + React 18 + Tailwind apps under connect_frontend/.
Both use React Router v6, Axios, react-hot-toast, and Lucide icons.
Neither uses localStorage — tokens live in sessionStorage (manager-app)
or component state (menu-app cart).

── menu-app (public menu + ordering) ──────────────────────────────

Routing (basename /menu):
  /:siteSlug              → MenuPage
  /:siteSlug/order        → CartPage
  /:siteSlug/confirm/:ref → OrderConfirm

Pages:
  MenuPage      — fetches menu snapshot, category nav tabs, product
                  cards; floating CartButton; opens ProductModal
  ProductModal  — quantity picker, quick-option toggles, price/discount
                  display, "Add to order" with line total
  CartPage      — order type selector (dine_in/delivery), customer
                  details form, cart summary, submits to cloud API
  OrderConfirm  — polls GET /api/orders/status/:ref every 10s;
                  renders status icon + label; stops polling on
                  terminal states (delivered/rejected)

Components:
  CategoryNav   — horizontal scrollable pill tabs
  ProductCard   — image, name, description, price with discount badge;
                  greyed + "Out of stock" badge when unavailable
  CartButton    — fixed bottom bar with item count + total

── manager-app (remote dashboard) ─────────────────────────────────

Routing (basename /manage):
  /login              → LoginPage
  /                   → SiteSelectorPage (auto-navigates if one site)
  /:siteId            → DashboardPage
  /:siteId/orders     → IncomingOrdersPage
  /:siteId/orders/history → OrderHistoryPage
  /:siteId/orders/:id → OrderDetailPage
  RequireAuth wrapper redirects to /login if no sessionStorage token

Pages:
  LoginPage          — email/password → POST /api/manager/login;
                       stores JWT in sessionStorage
  SiteSelectorPage   — lists accessible venues; auto-selects if one
  DashboardPage      — polls snapshot + pending orders every 15s;
                       2×2 stat grid + pending order list
  IncomingOrdersPage — polls pending orders every 15s; order cards
  OrderDetailPage    — full order detail; Accept/Reject with optional
                       rejection reason; lifecycle progression buttons
                       (Preparing → Ready → Delivered etc.)
  OrderHistoryPage   — all orders with status filter tabs

Components:
  RequireAuth   — route guard using sessionStorage token
  StatCard      — labelled metric tile
  StatusBadge   — colour-coded status pill
  OrderCard     — summary card linking to OrderDetailPage
  (no OrderCard uses useParams to get siteId for nav — wired correctly)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 00:25:28 +03:00
abe7f4ff0d feat(connect): return site_numeric_id in heartbeat response
Local backend needs the cloud DB integer PK to call Connect API
endpoints (menu sync, order pending poll). Heartbeat is the only
authenticated channel available at startup, so we piggyback the id
there rather than adding a new endpoint.

Changes:
  - schemas/site.py: site_numeric_id: int | None added to HeartbeatResponse
  - routers/heartbeat.py: site_numeric_id=site.id included in response

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 00:11:48 +03:00
cd8a6c53cf feat(connect): Phase 3 — cloud API routes for Xenia Connect
Adds four new routers, three schema files, and one new model.
All new endpoints use new URL prefixes — no existing routes touched.

routers/menu.py
  GET  /api/menu/{site_slug}     — public menu snapshot (no auth)
  POST /api/menu/sync            — upsert snapshot (site API key)

routers/orders.py
  POST /api/orders/{site_slug}         — customer places order (public)
  GET  /api/orders/status/{public_ref} — customer tracks order (public)
  GET  /api/orders/pending/{site_id}   — local backend polls (site key)
  POST /api/orders/{id}/synced         — mark synced (site key)
  PATCH /api/orders/{id}/status        — update status with transition
                                         validation (site key)

routers/manager_auth.py
  POST /api/manager/register  — create manager account (admin JWT)
  POST /api/manager/login     — returns manager JWT
  POST /api/manager/refresh   — refreshes manager JWT
  Shared _get_current_manager dependency used by remote_dashboard

routers/remote_dashboard.py
  GET  /api/remote/sites                           — manager JWT
  GET  /api/remote/sites/{id}/snapshot             — manager JWT
  GET  /api/remote/sites/{id}/orders               — manager JWT
  GET  /api/remote/sites/{id}/orders/pending       — manager JWT
  PATCH /api/remote/sites/{id}/orders/{oid}/status — manager JWT
  POST /api/remote/snapshot                        — site API key
                                                     (stats push)

models/stats_snapshot.py (NEW)
  stats_snapshots table — one row per site, holds serialized
  operational stats; created by create_all on startup

config.py / .env.example
  MANAGER_JWT_SECRET and MANAGER_JWT_EXPIRE_HOURS (default 72h)
  added as separate settings from the admin SECRET_KEY

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 00:05:48 +03:00
835806f92d feat(connect): Phase 2 — cloud DB models for Xenia Connect
Adds three new tables created automatically by create_all on next
startup, plus an additive migration for an existing table:

models/menu_snapshot.py (NEW)
  - menu_snapshots: one row per site, holds the full serialized
    category+product JSON; upserted on each sync from local_backend

models/online_order.py (NEW)
  - online_orders: full order lifecycle from customer submission
    through delivery; tracks sync state so local_backend can poll
    for unprocessed orders (synced_to_local=0)

models/manager_account.py (NEW)
  - manager_accounts + manager_site_access (M2M join table): remote
    manager login accounts with per-site access control

models/site.py (MODIFIED)
  - order_counter column added; incremented atomically when each
    online order is created to generate "ORD-XXXX" public_ref values

main.py (MODIFIED)
  - imports all three new models so create_all registers their tables
  - _run_migrations() added (same pattern as local_backend) for the
    additive order_counter column on the existing sites table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 00:01:02 +03:00
2d1b927670 feat: add LATEST_VERSION config for client update detection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 15:24:43 +03:00
06b01533d4 feat: waiter domain + local IP tracking
- Site model: add waiter_domain and last_seen_local_ip columns
- HeartbeatRequest: accept optional local_ip field from local backend
- HeartbeatResponse: return waiter_domain to local backend
- heartbeat router: persist local_ip on each check-in
- SiteDetailPage: show Public IP / Local IP separately, add Waiter Domain
  card with inline edit modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:06:36 +03:00
bbbd421aec Update: Production ready with proper mapping and variables 2026-05-08 15:39:34 +03:00
dcf43d382c fix: changed to production with nginx serving the files 2026-05-08 14:21:17 +03:00
4cbf8986df Initial Commit. Split cloud service from the combined project 2026-05-08 13:20:23 +03:00