neiam
/fieri-forever
the flavortown lunch engine
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
Log in to mark this repository.
Outings: vote on a lunch together, live over SSE
7def6a5 · Gabriel Morell · 2026-10-28 18:07
Message
{commit_body(@commit)}
Files changed
modified
Cargo.toml
+1
−0
| 22 | 22 | axum-htmx = { version = "0.7.0", features = ["tokio"] } |
| 23 | 23 | tower-http = { version = "0.6", features = ["fs", "trace"] } |
| 24 | 24 | tokio = { version = "1", features = ["full"] } |
| 25 | +tokio-stream = { version = "0.1", features = ["sync"] } | |
| 25 | 26 | |
| 26 | 27 | # Templating |
| 27 | 28 | minijinja = { version = "2.10", features = ["loader", "builtins", "json", "urlencode"] } |
modified
README.md
+25
−2
| 67 | 67 | anyone with a valid invite token self-serve, or keep it off and hand out links |
| 68 | 68 | from the in-app **Invites** page. |
| 69 | 69 | |
| 70 | +## Deciding together: outings | |
| 71 | + | |
| 72 | +"Start a vote" on an area's planner opens an **outing**: a ballot of a few | |
| 73 | +options (3 by default) for today. Share the link; people join, and vote for | |
| 74 | +**every** option they'd happily go to (approval voting). The most-voted option | |
| 75 | +is marked as leading, and anyone can lock one in with "Go here", which logs | |
| 76 | +the lunch for everyone going. | |
| 77 | + | |
| 78 | +**Re-roll keeps every option that has a vote** and swaps the rest for places | |
| 79 | +this outing hasn't shown yet, re-drawn for whoever is going now. Only when an | |
| 80 | +area runs out of fresh places does it reach back for ones rolled away earlier. | |
| 81 | +If someone who joined after a draw ranked one of the options "never", it's | |
| 82 | +flagged rather than silently swapped out. | |
| 83 | + | |
| 84 | +Outing pages update live over SSE. Each change runs `pg_notify('outing_changed', | |
| 85 | +<id>)`; every replica `LISTEN`s and passes the id to its open streams, and the | |
| 86 | +page re-fetches its panel when it gets the event. Because it goes through | |
| 87 | +Postgres, this works across any number of pods. A 30 s poll covers a dropped | |
| 88 | +stream. If the app sits behind ingress-nginx, the `X-Accel-Buffering: no` | |
| 89 | +header the stream sends stops events being buffered; long-lived connections | |
| 90 | +may also need `proxy-read-timeout` raised past the 15 s keep-alive. | |
| 91 | + | |
| 70 | 92 | ## How suggestions work |
| 71 | 93 | |
| 72 | 94 | All in `src/suggest.rs`, pure functions with unit tests. For the people going: |
| 139 | 161 | bin/ webservice, registration-token |
| 140 | 162 | config.rs env-driven configuration |
| 141 | 163 | lib.rs AppState + minijinja environment |
| 142 | − entities/ sea-orm models (auth, areas, restaurants, rankings, lunches) | |
| 164 | + entities/ sea-orm models (auth, areas, restaurants, rankings, lunches, outings) | |
| 143 | 165 | auth/ password hashing, sessions, invite tokens, user ops |
| 144 | 166 | middleware/ cookie-session auth layers |
| 145 | − handlers/ axum handlers (auth, areas, restaurants, places, lunch) | |
| 167 | + handlers/ axum handlers (auth, areas, restaurants, places, lunch, outings) | |
| 146 | 168 | osm.rs Photon + POI client |
| 169 | + live.rs LISTEN/NOTIFY → SSE relay for outings | |
| 147 | 170 | suggest.rs scoring and planning |
| 148 | 171 | migration/ sea-orm migrations |
| 149 | 172 | templates/ minijinja templates (excluded from generation templating) |
added
assets/f/js/htmx-global.js
+7
−0
| 1 | +// htmx extensions (htmx-ext-sse) look for a global `htmx` when they load, and | |
| 2 | +// ES imports run before any statement in the importing module. Setting the | |
| 3 | +// global in its own module, imported first, is what orders it ahead of them. | |
| 4 | +import htmx from "htmx.org"; | |
| 5 | + | |
| 6 | +window.htmx = htmx; | |
| 7 | +export default htmx; |
modified
assets/f/js/src.js
+3
−3
| 1 | 1 | // Bundled to f/js/app.js by esbuild (see the Justfile). Importing htmx here |
| 2 | 2 | // registers it globally and processes hx-* attributes on load, which is what |
| 3 | 3 | // powers hx-boost navigation and the partial updates (ranks, plans, lookups). |
| 4 | −import htmx from "htmx.org"; | |
| 4 | +// The SSE extension drives live outing updates (see templates/outings/). | |
| 5 | +import "./htmx-global.js"; | |
| 6 | +import "htmx-ext-sse"; | |
| 5 | 7 | import { installMaps } from "./map.js"; |
| 6 | 8 | |
| 7 | −// map.js and inline handlers use the global; esbuild's bundle doesn't set it. | |
| 8 | −window.htmx = htmx; | |
| 9 | 9 | installMaps(); |
| 10 | 10 | |
| 11 | 11 | // Small toast helper available as window.toast(msg, {kind}) for ad-hoc use. |
modified
assets/package-lock.json
+6
−0
| 10 | 10 | "@fortawesome/fontawesome-free": "^6.6.0", |
| 11 | 11 | "@tailwindcss/forms": "^0.5.10", |
| 12 | 12 | "daisyui": "^4.12.24", |
| 13 | + "htmx-ext-sse": "^2.2.2", | |
| 13 | 14 | "htmx.org": "^2.0.4", |
| 14 | 15 | "leaflet": "^1.9.4", |
| 15 | 16 | "tailwindcss": "^3.4.17" |
| 881 | 882 | "node": ">= 0.4" |
| 882 | 883 | } |
| 883 | 884 | }, |
| 885 | + "node_modules/htmx-ext-sse": { | |
| 886 | + "version": "2.2.2", | |
| 887 | + "resolved": "https://registry.npmjs.org/htmx-ext-sse/-/htmx-ext-sse-2.2.2.tgz", | |
| 888 | + "integrity": "sha512-MTnKkBzA2t4sI8gOXrRiPaceTlkUbrw3+3qOy1BfuBNIPBalsJiT4qxUGd6W48ggOkfe2akOnB8uxICJKw+Dsg==" | |
| 889 | + }, | |
| 884 | 890 | "node_modules/htmx.org": { |
| 885 | 891 | "version": "2.0.10", |
| 886 | 892 | "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.10.tgz", |
modified
assets/package.json
+1
−0
Click to load diff…
modified
migration/src/lib.rs
+2
−0
Click to load diff…
added
migration/src/m20261028_000002_create_outings.rs
+240
−0
Click to load diff…
modified
src/bin/webservice.rs
+16
−0
Click to load diff…
modified
src/entities/mod.rs
+6
−1
Click to load diff…
added
src/entities/outing_attendees.rs
+16
−0
Click to load diff…
added
src/entities/outing_options.rs
+23
−0
Click to load diff…
added
src/entities/outing_votes.rs
+19
−0
Click to load diff…
added
src/entities/outings.rs
+52
−0
Click to load diff…
modified
src/entities/prelude.rs
+4
−0
Click to load diff…
modified
src/handlers/areas.rs
+3
−0
Click to load diff…
modified
src/handlers/lunch.rs
+84
−73
Click to load diff…
modified
src/handlers/mod.rs
+19
−0
Click to load diff…
added
src/handlers/outings.rs
+551
−0
Click to load diff…
modified
src/lib.rs
+2
−0
Click to load diff…
added
src/live.rs
+86
−0
Click to load diff…
modified
src/suggest.rs
+40
−0
Click to load diff…
modified
templates/areas/show.html
+11
−0
Click to load diff…
modified
templates/lunch/plan.html
+16
−3
Click to load diff…
added
templates/outings/_panel.html
+105
−0
Click to load diff…
added
templates/outings/show.html
+25
−0
Click to load diff…
modified
tests/templates.rs
+61
−1
Click to load diff…
Parents: 25c4237