85 lines
3.3 KiB
Markdown
85 lines
3.3 KiB
Markdown
# mediator
|
|
|
|
A tiny self-hosted web app for scheduling get-togethers with your friend group.
|
|
You circle dates on a calendar, optionally add a time per day, and share one
|
|
link. Friends open the same calendar, circle the days that work for them, and
|
|
the best date floats to the top. No accounts, no sign-up — the link is the key.
|
|
|
|
Built with **Go standard library only**: no external dependencies, one static
|
|
binary, polls stored in a plain JSON file.
|
|
|
|
## Run it
|
|
|
|
Requires Go 1.22+.
|
|
|
|
```sh
|
|
go build -o mediator .
|
|
./mediator
|
|
```
|
|
|
|
Open http://localhost:8080 — that's the page for creating a poll.
|
|
þ
|
|
Flags:
|
|
|
|
| Flag | Default | Meaning |
|
|
|---------|---------|----------------------------------|
|
|
| `-addr` | `:8080` | Listen address |
|
|
| `-data` | `data` | Directory for `polls.json` |
|
|
|
|
To let friends reach it, run it on a machine they can access (a small VPS, a
|
|
Raspberry Pi behind a port forward, etc.) and put it behind HTTPS — e.g. a
|
|
Caddy or nginx reverse proxy. The share links use whatever host your friends
|
|
open the page on, so no configuration is needed.
|
|
|
|
## How it works
|
|
|
|
1. **Create a poll** — give it a title, click every date you want to offer on
|
|
the calendar (only today and future dates are clickable), and optionally
|
|
set a time for each date in the list below the calendar.
|
|
2. **Share** — after creating you get two links:
|
|
- **Share link** (`/p/<id>`) — send this to the group.
|
|
- **Admin link** (`/p/<id>?admin=<token>`) — keep this. It's the only way
|
|
to close or delete the poll.
|
|
3. **Friends answer** — they open the share link, see the same calendar with
|
|
the offered days highlighted, circle the ones that work, enter their name,
|
|
and send. Answering again with the same name (case-insensitive) replaces
|
|
the earlier answer.
|
|
4. **Close or delete** — polls can never be edited after creation. From the
|
|
admin link you can *close* the poll (answers freeze, results stay visible)
|
|
or *delete* it entirely.
|
|
|
|
The poll page refreshes results every 30 seconds while open.
|
|
|
|
## API (if you're curious)
|
|
|
|
| Method | Path | What it does |
|
|
|----------|------------------------|---------------------------------------|
|
|
| `POST` | `/api/polls` | Create a poll, returns id + admin token |
|
|
| `GET` | `/api/polls/{id}` | Poll data (admin token never included) |
|
|
| `POST` | `/api/polls/{id}/votes`| Submit / replace an answer |
|
|
| `POST` | `/api/polls/{id}/close`| Close (admin token required) |
|
|
| `DELETE` | `/api/polls/{id}` | Delete (admin token required) |
|
|
|
|
Admin token goes in the `X-Admin-Token` header or `?admin=` query parameter.
|
|
|
|
## Storage
|
|
|
|
Everything lives in `<data-dir>/polls.json`, written atomically on every
|
|
change. Back it up by copying that one file. Deleting a poll removes it from
|
|
the file immediately.
|
|
|
|
## Project layout
|
|
|
|
```
|
|
main.go server, storage, API handlers (stdlib only)
|
|
static/index.html create-a-poll page
|
|
static/poll.html voting + results page
|
|
static/calendar.js the shared calendar widget both pages use
|
|
static/create.js create-page logic
|
|
static/poll.js poll-page logic
|
|
static/style.css styles
|
|
```
|
|
|
|
Static files are embedded into the binary with `go:embed`, so the compiled
|
|
`mediator` binary is all you need to deploy.
|