Arkkitehtuuri
Tuutti on single-binary monoliitti — yksi Deno-prosessi tarjoilee sekä API:n että staattisen SPA-frontin. Tähän lisäksi yksi worker- prosessi taustatöille (SMS, kuitit, tilitykset, kuvien mirroroinnit).
Komponentit
| Mitä | Tekniikka | Sijainti repossa |
|---|---|---|
| API + SSR-fallback | Deno + Hono | server/ |
| Asiakas + admin PWA | React 18 + Vite 6 + Tailwind v3 | web/ |
| Tietokanta | PlanetScale Postgres | database/migrations/ |
| Reaktiivinen sync | Postgres LISTEN/NOTIFY → in-proc pub/sub → SSE | server/lib/events.ts |
| Taustatyöt | Graphile Worker | server/jobs.ts, server/worker.ts |
| Tiedostot | Tigris (S3-yhteensopiva) | server/lib/tigris.ts |
| Kuva-optimointi | imgproxy → Tigris (variantit) | server/lib/imgproxy.ts |
| Maksut | Stripe Connect (direct charges) | server/lib/stripe.ts |
| SMS | GatewayAPI | server/lib/sms.ts |
| Sähköposti | SES via SMTP | server/lib/mailer.ts |
| Tekoälykuvat | Anthropic Claude (vision) | server/lib/vision.ts |
| i18n | Lingui v6 | web/src/locales/ |
Datavirta tilauksessa
Asiakas selaa <slug>.tuutti.io
↓ GET /api/menu (palvelin)
↓ asiakas valitsee tuotteet, lisää koriin
↓ POST /api/orders (palvelin)
↓ palvelin avaa Stripe PaymentIntent
↓ asiakas maksaa kortilla / Apple Pay / Google Pay
↓ Stripe lähettää webhookin → POST /api/stripe/webhook
↓ tilauksen tila päivittyy queued / pre_queue → SSE
↓ henkilökunta näkee tilauksen jonosivulla (reaaliajassa)
↓ henkilökunta klikkaa "Aloita" → "Valmis"
↓ Tuutti lähettää asiakkaalle ilmoituksen
↓ asiakas hakee tilauksen tiskiltä
↓ henkilökunta klikkaa "Noudettu"
Multi-tenant rakenne
Yksi Postgres-tietokanta hallitsee kaikkia kioskeja:
stores— yksi rivi per kioski (slug, nimi, brändi, asetukset)companies— yksi rivi per Y-tunnus (sama yritys voi pyörittää useaa kioskia)products,categories,modifier_groups,modifier_options— kaikki per-storeorders,order_items,order_item_modifiers,order_item_companions— per-storestaff_memberships— kytkee käyttäjän kioskiin (admin / staff)
Multi-tenancy ratkaistu slug-pohjaisella reititykellä: asiakassovellus
toimii osoitteessa <slug>.tuutti.io, hallinta osoitteessa
app.tuutti.io/admin/<polku>?store=<slug>.
Hosting
| Mitä | Missä |
|---|---|
| API + Worker | Fly.io (kaksi appia: tuutti + tuutti-worker) |
| Tietokanta | PlanetScale (Postgres) — Eurooppa-alueella |
| Tiedostot + Kuvat | Tigris (Fly:n hallinnoima S3) |
| imgproxy | sama Fly.io-koneella sivutyö |
| SMS | GatewayAPI (Tanska) |
| Sähköposti | AWS SES (Frankfurt) |
| LLM | Anthropic API (Yhdysvallat) |
| Maksut | Stripe (Euroopan toiminta-alue) |
Konfiguraatio (env-vars) hallitaan Dopplerilla ja synkronoidaan Fly:hyn
deployissa. Lokaalin kehityksen .env.local on dev-konfiguraation kopio.
Deploy
GitHub Actions pyörittää release-prosessin:
- Push merkityllä tagilla (
v0.1.X) tai erillinen GH Release - Workflow rakentaa kaksi Docker-imagea (tuutti + tuutti-worker)
- Migraatiot ajetaan yhden kertaa containerissa
tuuttideployataan bluegreen-strategialla (no-downtime)tuutti-workerrolling-deployataan- Healthcheck
/healthvahvistaa onnistumisen
Kokonaisaika gh release create v0.1.X -komennosta tuotantoon on
noin 3–4 minuuttia.
Reaktiivinen sync (kuinka tilaus näkyy tiskillä reaaliajassa)
Tuutti käyttää PostgreSQL:n natiivin LISTEN/NOTIFY-mekanismia kahden prosessin välisenä viestintävälineenä. Asiakassovellus avaa EventSource-yhteyden palvelimelle:
GET /api/events?store=kulmakioski
Palvelin pitää yhden LISTENing-yhteyden Postgresiin per prosessi.
Kun joku osa järjestelmästä haluaa lähettää tapahtuman
(“queue_changed”, “menu_changed”), se kutsuu NOTIFY tuutti_events, {...} — Postgres broadcastaa tämän kaikille listenenders. Palvelin
välittää tapahtuman SSE-streamiin oikealle storen tilaajille.
Tämä on redisettön ja cross-instance turvallinen — useita Fly:n koneita voi pyöriä samanaikaisesti ja Postgresin LISTEN levittää viestit oikein kaikille.