feat(api): public GET /restaurants/by-slug/{slug}

Prerequisite for the customer webapp module (aiolabs/webapp,
branch feat/restaurant-bundle): the webapp's /r/:slug route needs
to resolve a slug to a Restaurant payload without an admin key.

crud.get_restaurant_by_slug already exists (used by the server-
rendered CMS routes in views.py); just expose it as a public REST
endpoint. Mirrors api_get_restaurant by id and is declared before
the bare-id route so the static prefix wins FastAPI's path match.

Verified live against seeded 'Big Jay's Bustaurant':
  GET /restaurant/api/v1/restaurants/by-slug/big-jays-bustaurant
  -> 200 with the Restaurant payload.
This commit is contained in:
Padreug 2026-05-11 09:03:10 +02:00
commit 6dae57f3f4
3 changed files with 27 additions and 1 deletions

View file

@ -15,6 +15,7 @@ the catalog.
| Method | Path | Notes | | Method | Path | Notes |
|---|---|---| |---|---|---|
| `GET` | `/restaurants/{id}` | Restaurant profile | | `GET` | `/restaurants/{id}` | Restaurant profile |
| `GET` | `/restaurants/by-slug/{slug}` | Restaurant profile by URL slug — used by webapps that route on `/r/:slug` and need to resolve to an `id` before any other lookup. 404 when no match |
| `GET` | `/restaurants/{id}/menu` | `{restaurant, tree, items}` — the canonical [[menu-tree|menu tree]] (hydrated children + items per node) plus a flat enriched items list with modifier groups + availability windows pre-joined | | `GET` | `/restaurants/{id}/menu` | `{restaurant, tree, items}` — the canonical [[menu-tree|menu tree]] (hydrated children + items per node) plus a flat enriched items list with modifier groups + availability windows pre-joined |
| `GET` | `/menu_items/{id}` | Single item | | `GET` | `/menu_items/{id}` | Single item |
| `GET` | `/menu_nodes/{id}` | Single node row | | `GET` | `/menu_nodes/{id}` | Single node row |

View file

@ -9,7 +9,17 @@ of restaurants, especially the multi-restaurant cart pattern.
A webapp can either talk to one restaurant directly or aggregate A webapp can either talk to one restaurant directly or aggregate
many. There's no central directory inside this extension — grouping many. There's no central directory inside this extension — grouping
("festival", "collective space", "food court") is **emergent** via ("festival", "collective space", "food court") is **emergent** via
NIP-51 list events curated by whoever runs the venue: NIP-51 list events curated by whoever runs the venue.
For the **single-venue** case, a webapp that routes on a URL slug
(`/r/big-jays-bustaurant`) resolves the slug → restaurant via the
public `GET /restaurants/by-slug/{slug}` endpoint
([[api-reference]]) and proceeds with that one `id` for menu reads
and order placement. Slug is just a URL nicety — internally
everything continues to key on the restaurant `id`.
For the **aggregator** case (multiple restaurants in one cart), the
webapp consumes a curated NIP-51 list event:
``` ```
{ {

View file

@ -59,6 +59,7 @@ from .crud import (
get_print_job, get_print_job,
get_print_jobs, get_print_jobs,
get_restaurant, get_restaurant,
get_restaurant_by_slug,
get_restaurants, get_restaurants,
get_settings, get_settings,
move_menu_node, move_menu_node,
@ -248,6 +249,20 @@ async def api_list_restaurants(
return await get_restaurants(wallet_ids) return await get_restaurants(wallet_ids)
@restaurant_api_router.get("/api/v1/restaurants/by-slug/{slug}")
async def api_get_restaurant_by_slug(slug: str) -> Restaurant:
"""Public — used by the customer webapp to resolve a URL slug
(e.g. /r/big-jays-bustaurant) to a restaurant. Mirrors
api_get_restaurant; declared *before* the bare-id route so the
static prefix wins the path match in FastAPI's router."""
restaurant = await get_restaurant_by_slug(slug)
if not restaurant:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Restaurant not found."
)
return restaurant
@restaurant_api_router.get("/api/v1/restaurants/{restaurant_id}") @restaurant_api_router.get("/api/v1/restaurants/{restaurant_id}")
async def api_get_restaurant(restaurant_id: str) -> Restaurant: async def api_get_restaurant(restaurant_id: str) -> Restaurant:
"""Public — used by the webapp to fetch profile metadata.""" """Public — used by the webapp to fetch profile metadata."""