- TypeScript 51.2%
- Vue 47.4%
- Shell 0.4%
- JavaScript 0.4%
- CSS 0.3%
- Other 0.3%
| e2e | Init | |
| marketplace | #1 Ernährungspräferenz im Marketplace: vegan, vegetarisch | |
| shopmenu | improve fix for runnning import in container | |
| .dockerignore | Init | |
| .env.example | add functionality for regular import of menue data from api | |
| .gitignore | Init | |
| .npmrc | Init | |
| dev-up.sh | Init | |
| docker-compose.coolify-demo.yml | improve fix for runnning import in container | |
| docker-compose.marketplace.yml | Init | |
| docker-compose.override.yml | Init | |
| docker-compose.shopmenu.yml | Init | |
| docker-entrypoint.sh | Init | |
| Dockerfile | Init | |
| Dockerfile.e2e | Init | |
| LICENSE.md | update license | |
| package.json | Init | |
| pnpm-lock.yaml | Init | |
| pnpm-workspace.yaml | Init | |
| README.md | Init | |
Menuverse
In English: Menuverse is a federated menu system for canteens and institutions, built on FEP-0837: Federated Marketplace, schema.org and Fedify/ActivityPub. It consists of two Nuxt 4 apps: ShopMenu publishes a canteen's daily dishes as federated
Proposalactivities (each canteen is its own ActivityPub actor), and Marketplace follows one or more ShopMenu instances, caches their proposals and lets users browse them filtered by day, allergen and full text. The rest of this README is in German. This project is still in heavy development and should probaly not use in unsupervised production.
Föderiertes Speiseplan-System für Kantinen und Einrichtungen auf Basis von FEP-0837: Federated Marketplace und schema.org. Zwei Nuxt 4 Apps, beide mit Fedify für ActivityPub und Prisma/Postgres:
- ShopMenu — publiziert Gerichte als
Proposal-Activities. Jede Kantine ist ein eigener ActivityPubPerson-Actor (/users/<canteen>). Ein Server kann mehrere Kantinen hosten. - Marketplace — folgt einer oder mehreren ShopMenu-Instanzen, cached deren Proposals, zeigt sie gefiltert nach Tag, Allergen und Volltext.
Datenmodell: langlebige MenuItem-Zeilen (Rezept mit Allergenen, Nährwerten — als schema.org-JSON-LD ausgespielt) und kurzlebige Dish-Zeilen (eine Portion an einem Tag mit Preis). Jeder Dish wird ein FEP-0837-Proposal.
graph LR
MP["🛒 Marketplace<br/><small>folgt & cached alle Proposals</small>"]
FED["🐘 Mastodon / Misskey / etc...<br/><small>beliebiger Fediverse-Account</small>"]
subgraph SA["🖥️ ShopMenu-Server A · a.example.com"]
A1["👤 @nord@a.example.com<br/><small>Kantine Nord</small>"]
A2["👤 @sued@a.example.com<br/><small>Cafeteria Süd</small>"]
end
subgraph SB["🖥️ ShopMenu-Server B · b.example.com"]
B1["👤 @zentral@b.example.com<br/><small>Mensa Zentral</small>"]
end
MP <--> A1
MP <--> A2
MP <--> B1
FED <--> A2
FED <--> B1
classDef market fill:#bfe3c0,stroke:#5fa463,color:#1f4d29;
classDef actor fill:#e9f6ea,stroke:#a9d4ad,color:#2f6b39;
classDef fed fill:#f2f7ec,stroke:#cbe0b4,color:#4e7136;
class MP market;
class FED fed;
class A1,A2,B1 actor;
style SA fill:#fafdf6,stroke:#cfe6cf,color:#3a5d3f;
style SB fill:#fafdf6,stroke:#cfe6cf,color:#3a5d3f;
linkStyle default stroke:#9ccaa0,stroke-width:1.5px;
Pfeil = Zwei Akteure die sich folgen.
Wichtig: Das Projekt ist immer nocht in der Entwicklung und es wird breaking changes geben. Es sollte nicht vermutlich nicht in einem überwachten Produktivsystem eingesetzt werden.
Was technisch geht
- Föderation via Fedify: HTTP-Signaturen, WebFinger, Followers/Inbox/Outbox, Proposal-Dereferencing
- Multi-Tenancy: ein ShopMenu-Server kann beliebig viele Kantinen-Actors hosten
- Allergen-Filter (EU-LMIV-Codes), Nährwerte, Wochenpläne, Tagesansicht
- Live-Daten: Seed-Set
studentenwerk-shzieht alle 18 Mensen/Cafeterien des Studierendenwerks Schleswig-Holstein aus inoffizieller API aus dem ProjektDraculente/mensa-api
Coolify Deploy
Drei Compose-Dateien, je nach Use-Case:
| Datei | Zweck |
|---|---|
docker-compose.shopmenu.yml |
Eine einzelne ShopMenu-Instanz |
docker-compose.marketplace.yml |
Ein einzelner Marketplace |
docker-compose.coolify-demo.yml |
Demo-Stack: 2 ShopMenus + 1 Marketplace in einem Coolify-Resource — zum Vorzeigen der Federation zwischen unabhängigen Instanzen |
Lokal entwickeln
pnpm install
cp .env.example .env # Dev-Defaults — Session-Secrets sind drin
./dev-up.sh # picks free ports, starts everything via compose
docker compose up -d postgres db-init # nur die DB starten
pnpm dev:shopmenu # http://localhost:3001
pnpm dev:marketplace # http://localhost:3002
Beispiel-Payload
FEP-0837-Proposal unter /users/shopmenu/proposals/2026-05-21-schnitzel-kartoffelsalat:
{
"@context": ["https://www.w3.org/ns/activitystreams",
{"vf": "https://w3id.org/valueflows/ont/vf#"}],
"type": "Proposal",
"attributedTo": "https://shopmenu.example.com/users/shopmenu",
"name": "Schnitzel mit Kartoffelsalat",
"startTime": "2026年05月21日T11:30:00+02:00",
"endTime": "2026年05月21日T14:30:00+02:00",
"purpose": "offer",
"publishes": {
"type": "Intent", "action": "transfer",
"resourceConformsTo": "https://shopmenu.example.com/users/shopmenu/menu-items/schnitzel-kartoffelsalat",
"resourceQuantity": {"type": "Measure", "unit": "portion", "numericalValue": 1},
"availableQuantity": {"type": "Measure", "unit": "portion", "numericalValue": 120}
},
"reciprocal": {
"type": "Intent", "action": "transfer",
"resourceQuantity": {"type": "Measure", "unit": "EUR", "numericalValue": 4.20}
}
}
Ein Consumer liest das Proposal, folgt publishes.resourceConformsTo und bekommt das MenuItem mit Allergenen/Nährwerten als schema.org-JSON-LD.
Spec & Quellen
- FEP-0837: https://codeberg.org/fediverse/fep/src/branch/main/fep/0837/fep-0837.md
- Fedify: https://fedify.dev
Nächste Schritte
Federation
- Richtige nutzung von
resourceConformsToin dieser Form - Konfiguration und Anzeige von Bildern bei Profilen
- Richtige nutzung von DIET_LABELS
- Richtige konforme nutzung von ID
Technisch
- Update von Proposal und berücksichtigung in Marketplace
- Löschen von Proposal und berücksichtigung in Marketplace
- Verbessern und ausweiten der e2e playwright tests
- Bessere Suchfunktionen für das hinterlegen von Adresse und OSM ID
- Korrekte auslieferung der Öffnungszeiten bei hinterlegten OSM Daten
- Testen und verbessern der Importe von Webseiten