feat: implement notes CRUD for SQLite and API backends

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>
This commit was merged in pull request #3.
This commit is contained in:
2026-07-20 02:33:37 +02:00
committed by codinget
co-authored by Claude
parent 73c4b169c5
commit dd8d31633f
5 changed files with 199 additions and 19 deletions
+56 -7
View File
@@ -33,6 +33,8 @@ import {
selectClientApikeys,
selectClientUser,
selectClientUsers,
selectNote,
selectPartialNotes,
selectResident,
selectResidents,
} from "./query.js";
@@ -465,24 +467,71 @@ export class SqliteInterface implements BackendDbInterface {
}
async listNotes(): Promise<PartialNote[]> {
throw new Error("Unimplemented");
return selectPartialNotes(this.#db);
}
#getNoteById(nid: string): Note {
const note = selectNote(this.#db, sql`n."nid" = ${{ uuid: nid }}`);
if (!note) throw new NotFoundAbodeError();
return note;
}
async getNoteById(nid: string): Promise<Note> {
throw new Error("Unimplemented");
return this.#getNoteById(nid);
}
async deleteNoteById(nid: string): Promise<void> {
throw new Error("Unimplemented");
this.#checkReadonly();
const { changes } = this.#db.run(
sql`DELETE FROM "notes" WHERE "nid" = ${{ uuid: nid }}`
);
if (!changes) throw new NotFoundAbodeError();
}
async createNote(note: CreateNote, ctx: { uid: string }): Promise<Note> {
throw new Error("Unimplemented");
this.#checkReadonly();
const nid = crypto.randomUUID();
return this.#db.rethrow(() =>
this.#db.multi(() => {
this.#db.run(sql`
INSERT INTO "notes"("nid", "aid", "name", "content", "properties", "created_by", "updated_by")
VALUES(
${{ uuid: nid }},
${{ uuid: note.aid }},
${{ text: note.name }},
${{ text: note.content ?? "" }},
${{ jsonb: note.properties }},
${{ uuid: ctx.uid }},
${{ uuid: ctx.uid }}
)
`);
return this.#getNoteById(nid);
})
);
}
async updateNote(note: UpdateNote, ctx: { uid: string }): Promise<Note> {
throw new Error("Unimplemented");
this.#checkReadonly();
const updates = calcUpdates({
name: (value: string) => sql`"name" = ${{ text: value }}`,
content: (value: string) => sql`"content" = ${{ text: value }}`,
properties: (value: object) => sql`"properties" = ${{ jsonb: value }}`,
})(note);
if (!updates.length) throw new InvalidAbodeError();
return this.#db.rethrow(() =>
this.#db.multi(() => {
const { changes } = this.#db.run(sql`
UPDATE "notes"
SET
"updated_at" = datetime('now', 'localtime', 'subsec'),
"updated_by" = ${{ uuid: ctx.uid }},
${joinSql(updates, sql`, `)}
WHERE "nid" = ${{ uuid: note.nid }}
`);
if (!changes) throw new NotFoundAbodeError();
return this.#getNoteById(note.nid);
})
);
}
async listNotesByAbodeId(aid: string): Promise<PartialNote[]> {
throw new Error("Unimplemented");
return selectPartialNotes(this.#db, sql`n."aid" = ${{ uuid: aid }}`);
}
async listNotesByUserId(uid: string): Promise<PartialNote[]> {
throw new Error("Unimplemented");
return selectPartialNotes(this.#db, sql`n."created_by" = ${{ uuid: uid }}`);
}
}