feat(notebook): m009 sort_order 컬럼 + reorder 메서드 + IPC notebook:reorder

- m009 마이그레이션: notebooks.sort_order INTEGER 컬럼 추가, 기존 rows created_at 순으로 backfill
- NotebookRepository.list ORDER BY sort_order ASC, name ASC 로 변경
- NotebookRepository.create 신규 노트북 sort_order = max+1 자동 할당
- NotebookRepository.reorder(id, direction) — swap transaction 으로 atomic 순서 변경
- IPC notebook:reorder 핸들러 등록, preload/shared types pass-through
- 테스트 45개 추가 (m009, reorder 케이스 4, list ORDER BY, IPC 핸들러 2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
th-kim0823
2026-05-15 15:06:44 +09:00
parent fb17704570
commit eca91a1e7c
10 changed files with 142 additions and 8 deletions

View File

@@ -11,7 +11,7 @@ export class NotebookRepository {
(SELECT COUNT(*) FROM notes n
WHERE n.notebook_id = nb.id AND n.status = 'active') AS note_count
FROM notebooks nb
ORDER BY nb.name ASC`
ORDER BY nb.sort_order ASC, nb.name ASC`
).all() as Array<Record<string, unknown>>;
return rows.map((r) => this.hydrate(r));
}
@@ -30,12 +30,31 @@ export class NotebookRepository {
create(input: { name: string; color?: string | null }): Notebook {
const id = uuidv7();
const now = new Date().toISOString();
const maxRow = this.db.prepare(`SELECT COALESCE(MAX(sort_order), -1) AS m FROM notebooks`).get() as { m: number };
const sortOrder = maxRow.m + 1;
this.db.prepare(
`INSERT INTO notebooks(id, name, color, created_at, updated_at) VALUES(?,?,?,?,?)`
).run(id, input.name, input.color ?? null, now, now);
`INSERT INTO notebooks(id, name, color, created_at, updated_at, sort_order) VALUES(?,?,?,?,?,?)`
).run(id, input.name, input.color ?? null, now, now, sortOrder);
return { id, name: input.name, color: input.color ?? null, createdAt: now, updatedAt: now, noteCount: 0 };
}
reorder(id: string, direction: 'up' | 'down'): { ok: boolean } {
const cur = this.db.prepare(`SELECT sort_order FROM notebooks WHERE id = ?`).get(id) as { sort_order: number } | undefined;
if (!cur) return { ok: false };
const neighbor = direction === 'up'
? this.db.prepare(`SELECT id, sort_order FROM notebooks WHERE sort_order < ? ORDER BY sort_order DESC LIMIT 1`).get(cur.sort_order)
: this.db.prepare(`SELECT id, sort_order FROM notebooks WHERE sort_order > ? ORDER BY sort_order ASC LIMIT 1`).get(cur.sort_order);
if (!neighbor) return { ok: false }; // 이미 끝
const n = neighbor as { id: string; sort_order: number };
const now = new Date().toISOString();
const tx = this.db.transaction(() => {
this.db.prepare(`UPDATE notebooks SET sort_order = ?, updated_at = ? WHERE id = ?`).run(n.sort_order, now, id);
this.db.prepare(`UPDATE notebooks SET sort_order = ?, updated_at = ? WHERE id = ?`).run(cur.sort_order, now, n.id);
});
tx();
return { ok: true };
}
rename(id: string, name: string): void {
const now = new Date().toISOString();
this.db.prepare(`UPDATE notebooks SET name=?, updated_at=? WHERE id=?`).run(name, now, id);