aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/server/routes/notes.ts
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-31 01:10:56 +0900
committernsfisis <nsfisis@gmail.com>2025-12-31 01:10:56 +0900
commit352f7891588e9c33d27c2a189a414c5b4822e9fa (patch)
tree19b086ba815816ba0b6825c48c0bccdec4958a03 /src/server/routes/notes.ts
parent9165e1d82c49f247d0d2aa763395a199d9b3f8e4 (diff)
downloadkioku-352f7891588e9c33d27c2a189a414c5b4822e9fa.tar.gz
kioku-352f7891588e9c33d27c2a189a414c5b4822e9fa.tar.zst
kioku-352f7891588e9c33d27c2a189a414c5b4822e9fa.zip
feat(api): add Note API routes for CRUD operations
Add REST endpoints for notes under /api/decks/:deckId/notes: - GET / - List notes in deck - POST / - Create note with auto-generated cards - GET /:noteId - Get note with field values - PUT /:noteId - Update note field values - DELETE /:noteId - Delete note and its cards (cascade) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/server/routes/notes.ts')
-rw-r--r--src/server/routes/notes.ts152
1 files changed, 152 insertions, 0 deletions
diff --git a/src/server/routes/notes.ts b/src/server/routes/notes.ts
new file mode 100644
index 0000000..16ffb09
--- /dev/null
+++ b/src/server/routes/notes.ts
@@ -0,0 +1,152 @@
+import { zValidator } from "@hono/zod-validator";
+import { Hono } from "hono";
+import { z } from "zod";
+import { authMiddleware, Errors, getAuthUser } from "../middleware/index.js";
+import {
+ type DeckRepository,
+ deckRepository,
+ type NoteRepository,
+ noteRepository,
+} from "../repositories/index.js";
+import { createNoteSchema, updateNoteSchema } from "../schemas/index.js";
+
+export interface NoteDependencies {
+ noteRepo: NoteRepository;
+ deckRepo: DeckRepository;
+}
+
+const deckIdParamSchema = z.object({
+ deckId: z.uuid(),
+});
+
+const noteIdParamSchema = z.object({
+ deckId: z.uuid(),
+ noteId: z.uuid(),
+});
+
+export function createNotesRouter(deps: NoteDependencies) {
+ const { noteRepo, deckRepo } = deps;
+
+ return (
+ new Hono()
+ .use("*", authMiddleware)
+ // List notes in deck
+ .get("/", zValidator("param", deckIdParamSchema), async (c) => {
+ const user = getAuthUser(c);
+ const { deckId } = c.req.valid("param");
+
+ const deck = await deckRepo.findById(deckId, user.id);
+ if (!deck) {
+ throw Errors.notFound("Deck not found", "DECK_NOT_FOUND");
+ }
+
+ const notes = await noteRepo.findByDeckId(deckId);
+ return c.json({ notes }, 200);
+ })
+ // Create note (auto-generates cards)
+ .post(
+ "/",
+ zValidator("param", deckIdParamSchema),
+ zValidator("json", createNoteSchema),
+ async (c) => {
+ const user = getAuthUser(c);
+ const { deckId } = c.req.valid("param");
+ const data = c.req.valid("json");
+
+ const deck = await deckRepo.findById(deckId, user.id);
+ if (!deck) {
+ throw Errors.notFound("Deck not found", "DECK_NOT_FOUND");
+ }
+
+ try {
+ const result = await noteRepo.create(deckId, {
+ noteTypeId: data.noteTypeId,
+ fields: data.fields,
+ });
+
+ return c.json(
+ {
+ note: result.note,
+ fieldValues: result.fieldValues,
+ cards: result.cards,
+ },
+ 201,
+ );
+ } catch (error) {
+ if (
+ error instanceof Error &&
+ error.message === "Note type not found"
+ ) {
+ throw Errors.notFound(
+ "Note type not found",
+ "NOTE_TYPE_NOT_FOUND",
+ );
+ }
+ throw error;
+ }
+ },
+ )
+ // Get note with field values
+ .get("/:noteId", zValidator("param", noteIdParamSchema), async (c) => {
+ const user = getAuthUser(c);
+ const { deckId, noteId } = c.req.valid("param");
+
+ const deck = await deckRepo.findById(deckId, user.id);
+ if (!deck) {
+ throw Errors.notFound("Deck not found", "DECK_NOT_FOUND");
+ }
+
+ const note = await noteRepo.findByIdWithFieldValues(noteId, deckId);
+ if (!note) {
+ throw Errors.notFound("Note not found", "NOTE_NOT_FOUND");
+ }
+
+ return c.json({ note }, 200);
+ })
+ // Update note field values
+ .put(
+ "/:noteId",
+ zValidator("param", noteIdParamSchema),
+ zValidator("json", updateNoteSchema),
+ async (c) => {
+ const user = getAuthUser(c);
+ const { deckId, noteId } = c.req.valid("param");
+ const data = c.req.valid("json");
+
+ const deck = await deckRepo.findById(deckId, user.id);
+ if (!deck) {
+ throw Errors.notFound("Deck not found", "DECK_NOT_FOUND");
+ }
+
+ const note = await noteRepo.update(noteId, deckId, data.fields);
+ if (!note) {
+ throw Errors.notFound("Note not found", "NOTE_NOT_FOUND");
+ }
+
+ return c.json({ note }, 200);
+ },
+ )
+ // Delete note and its cards
+ .delete("/:noteId", zValidator("param", noteIdParamSchema), async (c) => {
+ const user = getAuthUser(c);
+ const { deckId, noteId } = c.req.valid("param");
+
+ const deck = await deckRepo.findById(deckId, user.id);
+ if (!deck) {
+ throw Errors.notFound("Deck not found", "DECK_NOT_FOUND");
+ }
+
+ const deleted = await noteRepo.softDelete(noteId, deckId);
+ if (!deleted) {
+ throw Errors.notFound("Note not found", "NOTE_NOT_FOUND");
+ }
+
+ return c.json({ success: true }, 200);
+ })
+ );
+}
+
+export const notes = createNotesRouter({
+ noteRepo: noteRepository,
+ deckRepo: deckRepository,
+});