A REST API and a real data export - #14
Merged
Merged
Conversation
Two things the research kept surfacing. Donetick ships a REST API, webhooks and long-lived tokens, and self-hosters ask for data portability specifically and repeatedly. This also gives a native client something to talk to. Endpoints under /api/v1: agenda, balance, complete-or-skip an occurrence, and export. Responses are flattened shapes rather than raw table rows, so the schema can move without breaking somebody's script. Two ways to authenticate. A bearer token for scripts, wall tablets and native clients, or the ordinary session cookie so the browser needs no second credential. Tokens are stored only as a SHA-256 hash, like sessions - a leaked backup must not hand over working credentials - and the plaintext is shown exactly once, which is stated plainly rather than left to be discovered after the panel closes. A token identifies a household, not a person, so completing a chore through one requires memberId. The ledger has to credit somebody real, and silently crediting nobody would quietly corrupt the fairness score that is the entire point of the app. The export leaves secrets out on purpose. Join codes, session tokens, API token hashes and push subscriptions are credentials, not content, and an export is a file people email to themselves. There is a check confirming none of them appear. The lastUsedAt write is fire-and-forget but explicitly caught. An unhandled rejection would take the whole server down over a bookkeeping write nobody is waiting for. 197 tests still green. Verified end to end against the running server: 401 unauthenticated on every endpoint, working bearer auth, 401 on a forged token, a helpful 400 when memberId is missing, correct crediting, lastUsedAt recorded, and an export whose Content-Disposition downloads a dated file containing no secrets.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two things the research kept surfacing. Donetick — the strongest open source competitor — ships a REST API, webhooks and long-lived tokens, and self-hosters ask for data portability specifically and repeatedly. This also gives a native client something to talk to.
Endpoints
Under
/api/v1:GET /agendaGET /balancePOST /occurrences/:id{"action":"complete"|"skip"}GET /exportResponses are flattened shapes rather than raw table rows, so the schema can move without breaking somebody's script.
Two ways in
A bearer token for scripts, wall tablets and native clients, or the ordinary session cookie so the browser needs no second credential.
Tokens are stored only as a SHA-256 hash — the same treatment as sessions, because a leaked database backup must not hand over working credentials. The plaintext is shown exactly once, and the UI says so plainly rather than leaving it to be discovered after the panel closes.
One decision worth reviewing
A token identifies a household, not a person, so completing a chore through one requires an explicit
memberId. It returns a 400 explaining that rather than guessing.Silently crediting nobody would quietly corrupt the fairness score, which is the entire point of the app — so this is a case where being annoying is correct.
The export
Deliberately leaves secrets out. Join codes, session tokens, API token hashes and push subscriptions are credentials, not content, and an export is a file people email to themselves. There's a verification step confirming none of them appear in the output.
It sets
Content-Dispositionso it downloads as a dated file rather than filling a browser tab with JSON.A bug avoided
The
lastUsedAtwrite is fire-and-forget, and now explicitly.catch()ed. An unhandled promise rejection would take the entire Node server down over a bookkeeping write nobody is waiting for.Verified end to end
Against the running server: 401 unauthenticated on every endpoint; working bearer auth returning the right household; 401 on a forged token; a helpful 400 when
memberIdis missing; correct crediting when it's supplied;lastUsedAtrecorded; and an export containing zero secrets.197 tests still green, lint and typecheck clean.