Skip to content

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, maybe or no. 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.

WhereKeyWhat it holds
Browser storageqqq.deviceIdThe device id
Browser storageqqq.nameThe first name last used, to prefill forms
Cookie, one per planqqq_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.