feat: implement notes CRUD for SQLite and API backends #3

Merged
codinget merged 1 commits from worktree-bridge-cse_013f1h5ZczJokqQ73KMAYrSn into master 2026-07-20 02:33:38 +02:00
Owner

Summary

Implements the previously-stubbed Notes feature across all layers of the stack.

What was missing

All 7 note methods in both database backends threw new Error("Unimplemented"):

  • listNotes(), getNoteById(), deleteNoteById()
  • createNote(), updateNote()
  • listNotesByAbodeId(), listNotesByUserId()

The API router also had three issues blocking the feature:

  • PATCH /notes/:nid was missing the updatenote validator
  • /user/:uid/auth/clear-sessions and /user/:uid/notes had a typo (/user/ instead of /users/), which also caused them to bypass the authenticate middleware

Changes

  • src/db/sqlite/cast.ts — Added sqliteToNote, sqliteToPartialNote, and property cast helpers. PartialNote always has type (defaults to "note").
  • src/db/sqlite/query.ts — Added selectNote, selectNotes, selectPartialNotes helpers following the existing pattern.
  • src/db/sqlite/SqliteInterface.ts — Implemented all 7 note methods. listNotesByUserId filters by created_by.
  • src/db/api/ApiInterface.ts — Implemented all 7 note methods as fetch calls to the appropriate routes. Added the missing ctx parameter to createNote/updateNote to match DbInterface.
  • src/webapi/apirouter.ts — Added updatenote import + validator to PATCH /notes/:nid; fixed both /user/:uid//users/:uid/ typos.

Test plan

  • POST /abodes/:aid/notes — creates a note and returns full Note
  • GET /notes — lists all notes as PartialNote[] (no content field)
  • GET /notes/:nid — returns full Note with content
  • PATCH /notes/:nid — updates name/content/properties; invalid body returns 400
  • DELETE /notes/:nid — deletes note, returns 204
  • GET /abodes/:aid/notes — lists notes for a specific abode
  • GET /users/:uid/notes — lists notes created by a user
  • Confirm PATCH /notes/:nid without a valid body returns { ok: false, error: "invalid" }
  • Confirm /users/:uid/notes requires authentication (was previously unauthenticated)

🤖 Generated with Claude Code

## Summary Implements the previously-stubbed Notes feature across all layers of the stack. ### What was missing All 7 note methods in both database backends threw `new Error("Unimplemented")`: - `listNotes()`, `getNoteById()`, `deleteNoteById()` - `createNote()`, `updateNote()` - `listNotesByAbodeId()`, `listNotesByUserId()` The API router also had three issues blocking the feature: - `PATCH /notes/:nid` was missing the `updatenote` validator - `/user/:uid/auth/clear-sessions` and `/user/:uid/notes` had a typo (`/user/` instead of `/users/`), which also caused them to bypass the `authenticate` middleware ### Changes - **`src/db/sqlite/cast.ts`** — Added `sqliteToNote`, `sqliteToPartialNote`, and property cast helpers. `PartialNote` always has `type` (defaults to `"note"`). - **`src/db/sqlite/query.ts`** — Added `selectNote`, `selectNotes`, `selectPartialNotes` helpers following the existing pattern. - **`src/db/sqlite/SqliteInterface.ts`** — Implemented all 7 note methods. `listNotesByUserId` filters by `created_by`. - **`src/db/api/ApiInterface.ts`** — Implemented all 7 note methods as `fetch` calls to the appropriate routes. Added the missing `ctx` parameter to `createNote`/`updateNote` to match `DbInterface`. - **`src/webapi/apirouter.ts`** — Added `updatenote` import + validator to `PATCH /notes/:nid`; fixed both `/user/:uid/` → `/users/:uid/` typos. ## Test plan - [ ] `POST /abodes/:aid/notes` — creates a note and returns full `Note` - [ ] `GET /notes` — lists all notes as `PartialNote[]` (no `content` field) - [ ] `GET /notes/:nid` — returns full `Note` with `content` - [ ] `PATCH /notes/:nid` — updates `name`/`content`/`properties`; invalid body returns 400 - [ ] `DELETE /notes/:nid` — deletes note, returns 204 - [ ] `GET /abodes/:aid/notes` — lists notes for a specific abode - [ ] `GET /users/:uid/notes` — lists notes created by a user - [ ] Confirm `PATCH /notes/:nid` without a valid body returns `{ ok: false, error: "invalid" }` - [ ] Confirm `/users/:uid/notes` requires authentication (was previously unauthenticated) 🤖 Generated with [Claude Code](https://claude.ai/claude-code)
codinget added the Kind/FeatureAgentic
Agent
Sonnet
labels 2026-07-01 02:23:57 +02:00
codinget added 1 commit 2026-07-01 02:23:57 +02:00
Fills in all 7 previously-unimplemented note methods in SqliteInterface
and ApiInterface, adds cast/query helpers for notes, and fixes the
apirouter (missing updatenote validator, two /user/ → /users/ typos that
were also bypassing auth middleware).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

Review: request changes — two confirmed bugs, please rebase on #2 or cherry-pick its fixes

Great catch on the /user/:uid//users/:uid/ typo fix in apirouter.ts — that's actually security-relevant, not cosmetic: the authenticate middleware is mounted on /users, so POST /user/:uid/auth/clear-sessions and GET /user/:uid/notes were running without authentication before this fix. Also confirmed the updatenote validator is correctly wired into PATCH /notes/:nid, and the SqliteInterface note methods mirror the established resident/abode patterns correctly (readonly checks, calcUpdates/InvalidAbodeError, NotFoundAbodeError on 0 rows affected, FK violations correctly mapped).

However, two bugs in src/db/api/ApiInterface.ts — both pre-existing in #url()/#call() and independently fixed by PR #2 on the same lines, but not present here since this branch forked before that fix:

  1. #url() (~L58): remaining.delete(part) should be remaining.delete(part.slice(1)). remaining is keyed by bare param names ("nid"), but part still has the leading colon (":nid"), so the delete is a no-op. Verified directly — every new note route with a path param (GET/PATCH/DELETE /notes/:nid, POST/GET /abodes/:aid/notes, GET /users/:uid/notes) leaks that param into the query string on every request.

  2. #call() (~L116): no handling for HTTP 204 before return res.json(). Verified apirouter.ts's DELETE /notes/:nid returns ctx.status = 204 with no body — so ApiInterface.deleteNoteById() will throw SyntaxError: Unexpected end of JSON input on every single call. This method is completely broken as written. (This isn't notes-specific — the same bug affects deleteUserById/deleteAbodeById/etc. via the API backend — but this PR's deleteNoteById is what actually surfaces it here since it's new code routing through the same broken #call.)

Recommendation: merge #2 first (it fixes both of these at the source), then rebase this branch on top so it inherits the fixes — cleaner than patching the same lines twice. Everything else in this PR looks ready to go once that's resolved.

**Review: request changes — two confirmed bugs, please rebase on #2 or cherry-pick its fixes** Great catch on the `/user/:uid/` → `/users/:uid/` typo fix in apirouter.ts — that's actually security-relevant, not cosmetic: the `authenticate` middleware is mounted on `/users`, so `POST /user/:uid/auth/clear-sessions` and `GET /user/:uid/notes` were running **without authentication** before this fix. Also confirmed the `updatenote` validator is correctly wired into `PATCH /notes/:nid`, and the SqliteInterface note methods mirror the established resident/abode patterns correctly (readonly checks, `calcUpdates`/`InvalidAbodeError`, `NotFoundAbodeError` on 0 rows affected, FK violations correctly mapped). **However, two bugs in `src/db/api/ApiInterface.ts` — both pre-existing in `#url()`/`#call()` and independently fixed by PR #2 on the same lines, but not present here since this branch forked before that fix:** 1. `#url()` (~L58): `remaining.delete(part)` should be `remaining.delete(part.slice(1))`. `remaining` is keyed by bare param names (`"nid"`), but `part` still has the leading colon (`":nid"`), so the delete is a no-op. Verified directly — every new note route with a path param (`GET/PATCH/DELETE /notes/:nid`, `POST/GET /abodes/:aid/notes`, `GET /users/:uid/notes`) leaks that param into the query string on every request. 2. `#call()` (~L116): no handling for HTTP 204 before `return res.json()`. Verified `apirouter.ts`'s `DELETE /notes/:nid` returns `ctx.status = 204` with no body — so `ApiInterface.deleteNoteById()` will throw `SyntaxError: Unexpected end of JSON input` on every single call. This method is completely broken as written. (This isn't notes-specific — the same bug affects `deleteUserById`/`deleteAbodeById`/etc. via the API backend — but this PR's `deleteNoteById` is what actually surfaces it here since it's new code routing through the same broken `#call`.) **Recommendation:** merge #2 first (it fixes both of these at the source), then rebase this branch on top so it inherits the fixes — cleaner than patching the same lines twice. Everything else in this PR looks ready to go once that's resolved.
codinget merged commit dd8d31633f into master 2026-07-20 02:33:38 +02:00
codinget deleted branch worktree-bridge-cse_013f1h5ZczJokqQ73KMAYrSn 2026-07-20 02:33:38 +02:00
Sign in to join this conversation.