How a plan works
There are no accounts. A plan is reached by its link, and the link is the only key to it.
The data
Three tables, defined in prisma/schema.prisma:
- A plan has a title, an optional description, place and start time, the host's first name and the host's device id. Its id is also its slug: eight random characters followed by a shortened form of the title, so it cannot be guessed.
- An attendee is one device's answer on one plan: a first name and a status,
yes,maybeorno. A device has at most one answer per plan, so answering again updates it. - An item is something to bring: a label, an optional quantity and, once taken, the attendee who brings it.
Deleting a plan deletes its attendees and items. Deleting an attendee frees their items.
Who is who
Each browser gets a device id the first time it opens Duplan.
| Where | Key | What it holds |
|---|---|---|
| Browser storage | qqq.deviceId | The device id |
| Browser storage | qqq.name | The first name last used, to prefill forms |
| Cookie, one per plan | qqq_p_<slug> | The device id, so the server can recognise the device on that plan |
The host is the device whose id is stored on the plan. The host can assign any item to anyone and remove any item. Everyone else can take a free item, give up their own, and remove the items they added.
The server checks these rules in the .core.ts actions, from the device id the request carries. A device id is not a secret login: it identifies a browser, not a person.
Taking an item
Two people can try to take the same item at once. claimItemCore updates the item only if it is still free, in a single query, so exactly one of them succeeds and the other gets a conflict.
Protection against abuse
- Rate limits, per IP address: 5 new plans an hour, 20 answers an hour, and 30 item changes a minute for each kind of change. Without the Upstash variables, rate limiting is skipped.
- A hidden field in the create and answer forms: a submission that fills it is rejected.
- Size limits on every text field, in
lib/validation.ts.
Dates
A plan's date and time are stored as one timestamp, built on the server from what the host typed. The plan's header formats it on the server as well, so everyone sees the time the host entered, whatever their own time zone.