feat(http): CMS pages + REST API for owners and customers
views.py (Jinja CMS pages, /restaurant/...):
- / restaurant list / dashboard
- /{slug} menu builder
- /{slug}/orders order monitor
- /{slug}/kds kitchen display
- /{slug}/settings restaurant + Nostr settings
views_api.py (REST under /restaurant/api/v1/):
Owner write-side (require_admin_key, ownership-checked):
- restaurants CRUD (publishes kind 0 metadata to Nostr on
create/update; signs with restaurant.nostr_pubkey override
or LNbits Account fallback)
- categories + subcategories CRUD
- menu_items CRUD (publishes/replaces kind 30402 NIP-99
listings on create/update; sends kind 5 NIP-09 deletion on
delete)
- modifier_groups + modifiers CRUD
- availability_windows CRUD
- orders status transitions (PUT /api/v1/orders/{id}/status/{new})
- print_jobs/{id}/ack
- settings (admin-only)
Customer-facing (no auth, customer pubkey optional):
- GET /api/v1/restaurants/{id} profile
- GET /api/v1/restaurants/{id}/menu full menu tree
(categories +
subcategories +
items + modifiers +
availability) in
one round trip
- POST /api/v1/orders/quote pre-flight balance
check; webapp calls
this *before* opening
any per-restaurant
invoice
- POST /api/v1/orders place an order on
one restaurant,
returns bolt11
KDS / order monitor (require_invoice_key, ownership-checked):
- GET /api/v1/restaurants/{id}/orders
- GET /api/v1/restaurants/{id}/print_jobs
crud.py: added get_print_job(job_id) helper used by the ack endpoint.
This commit is contained in:
parent
b155548036
commit
c37b17d474
3 changed files with 871 additions and 0 deletions
135
views.py
Normal file
135
views.py
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
"""
|
||||
Server-rendered CMS routes for restaurant owners.
|
||||
|
||||
Mounted at `/restaurant/...`. Customer-facing pages live in the AIO
|
||||
webapp (~/dev/webapp); this extension only renders the CMS.
|
||||
|
||||
Pages
|
||||
-----
|
||||
/restaurant/ dashboard (restaurant list)
|
||||
/restaurant/{slug} menu builder
|
||||
/restaurant/{slug}/orders order monitor
|
||||
/restaurant/{slug}/kds kitchen display
|
||||
/restaurant/{slug}/settings restaurant + Nostr settings
|
||||
|
||||
All pages require a logged-in LNbits user (check_user_exists).
|
||||
"""
|
||||
|
||||
import json
|
||||
from http import HTTPStatus
|
||||
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
from lnbits.core.models import User
|
||||
from lnbits.decorators import check_user_exists
|
||||
from lnbits.helpers import template_renderer
|
||||
|
||||
from .crud import get_restaurant_by_slug
|
||||
from .models import Restaurant
|
||||
|
||||
restaurant_generic_router = APIRouter()
|
||||
|
||||
|
||||
def restaurant_renderer():
|
||||
return template_renderer(["restaurant/templates"])
|
||||
|
||||
|
||||
def _restaurant_jsonable(restaurant: Restaurant) -> dict:
|
||||
"""
|
||||
Convert a Restaurant pydantic model to a plain JSON-serializable
|
||||
dict for Jinja's `tojson` filter.
|
||||
|
||||
`restaurant.dict()` returns a dict with a `datetime` on `time`,
|
||||
which Python's stdlib `JSONEncoder` (used by Jinja `tojson`) can't
|
||||
serialize — it errors out as
|
||||
TypeError: JSONEncoder.default() missing 1 required positional argument: 'o'
|
||||
Pydantic v1's `.json()` knows how to serialize datetime as
|
||||
ISO-8601, so we round-trip via JSON to get a clean dict.
|
||||
"""
|
||||
return json.loads(restaurant.json())
|
||||
|
||||
|
||||
@restaurant_generic_router.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||
return restaurant_renderer().TemplateResponse(
|
||||
"restaurant/index.html",
|
||||
{"request": request, "user": user.json()},
|
||||
)
|
||||
|
||||
|
||||
@restaurant_generic_router.get("/{slug}", response_class=HTMLResponse)
|
||||
async def menu_builder(
|
||||
request: Request, slug: str, user: User = Depends(check_user_exists)
|
||||
):
|
||||
restaurant = await get_restaurant_by_slug(slug)
|
||||
if not restaurant:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Restaurant not found."
|
||||
)
|
||||
return restaurant_renderer().TemplateResponse(
|
||||
"restaurant/menu.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": user.json(),
|
||||
"restaurant": _restaurant_jsonable(restaurant),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@restaurant_generic_router.get("/{slug}/orders", response_class=HTMLResponse)
|
||||
async def orders(
|
||||
request: Request, slug: str, user: User = Depends(check_user_exists)
|
||||
):
|
||||
restaurant = await get_restaurant_by_slug(slug)
|
||||
if not restaurant:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Restaurant not found."
|
||||
)
|
||||
return restaurant_renderer().TemplateResponse(
|
||||
"restaurant/orders.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": user.json(),
|
||||
"restaurant": _restaurant_jsonable(restaurant),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@restaurant_generic_router.get("/{slug}/kds", response_class=HTMLResponse)
|
||||
async def kds(
|
||||
request: Request, slug: str, user: User = Depends(check_user_exists)
|
||||
):
|
||||
restaurant = await get_restaurant_by_slug(slug)
|
||||
if not restaurant:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Restaurant not found."
|
||||
)
|
||||
return restaurant_renderer().TemplateResponse(
|
||||
"restaurant/kds.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": user.json(),
|
||||
"restaurant": _restaurant_jsonable(restaurant),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@restaurant_generic_router.get("/{slug}/settings", response_class=HTMLResponse)
|
||||
async def settings_page(
|
||||
request: Request, slug: str, user: User = Depends(check_user_exists)
|
||||
):
|
||||
restaurant = await get_restaurant_by_slug(slug)
|
||||
if not restaurant:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Restaurant not found."
|
||||
)
|
||||
return restaurant_renderer().TemplateResponse(
|
||||
"restaurant/settings.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": user.json(),
|
||||
"restaurant": _restaurant_jsonable(restaurant),
|
||||
},
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue