feat(trash): migration v3 + Note type extension (#4 v0.2.3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import type Database from 'better-sqlite3';
|
||||
import * as m001 from './m001_initial.js';
|
||||
import * as m002 from './m002_due_date.js';
|
||||
import * as m003 from './m003_soft_delete.js';
|
||||
|
||||
const migrations = [m001, m002];
|
||||
const migrations = [m001, m002, m003];
|
||||
|
||||
export function latestVersion(): number {
|
||||
return migrations[migrations.length - 1]!.version;
|
||||
|
||||
12
src/main/db/migrations/m003_soft_delete.ts
Normal file
12
src/main/db/migrations/m003_soft_delete.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type Database from 'better-sqlite3';
|
||||
|
||||
export const version = 3;
|
||||
|
||||
export function up(db: Database.Database): void {
|
||||
db.exec(`
|
||||
ALTER TABLE notes ADD COLUMN deleted_at TEXT;
|
||||
ALTER TABLE notes ADD COLUMN last_recalled_at TEXT;
|
||||
ALTER TABLE notes ADD COLUMN recall_dismissed_at TEXT;
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_deleted_at ON notes(deleted_at);
|
||||
`);
|
||||
}
|
||||
@@ -375,6 +375,9 @@ export class NoteRepository {
|
||||
intentPromptedAt: row.intent_prompted_at,
|
||||
dueDate: row.due_date ?? null,
|
||||
dueDateEditedByUser: row.due_date_edited_by_user === 1,
|
||||
deletedAt: row.deleted_at ?? null,
|
||||
lastRecalledAt: row.last_recalled_at ?? null,
|
||||
recallDismissedAt: row.recall_dismissed_at ?? null,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
tags: tags as NoteTag[],
|
||||
|
||||
@@ -33,6 +33,10 @@ export interface Note {
|
||||
intentPromptedAt: string | null;
|
||||
dueDate: string | null;
|
||||
dueDateEditedByUser: boolean;
|
||||
// 신규 v3:
|
||||
deletedAt: string | null;
|
||||
lastRecalledAt: string | null;
|
||||
recallDismissedAt: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
tags: NoteTag[];
|
||||
|
||||
@@ -29,3 +29,47 @@ describe('migrations', () => {
|
||||
db.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('migration v3 — soft delete columns', () => {
|
||||
it('adds deleted_at, last_recalled_at, recall_dismissed_at to notes', () => {
|
||||
const db = new Database(':memory:');
|
||||
runMigrations(db);
|
||||
const cols = db.prepare(`PRAGMA table_info(notes)`).all().map((r: any) => r.name);
|
||||
expect(cols).toEqual(
|
||||
expect.arrayContaining(['deleted_at', 'last_recalled_at', 'recall_dismissed_at'])
|
||||
);
|
||||
db.close();
|
||||
});
|
||||
|
||||
it('creates idx_notes_deleted_at index', () => {
|
||||
const db = new Database(':memory:');
|
||||
runMigrations(db);
|
||||
const indexes = db
|
||||
.prepare(`SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='notes'`)
|
||||
.all() as Array<{ name: string }>;
|
||||
expect(indexes.map((i) => i.name)).toContain('idx_notes_deleted_at');
|
||||
db.close();
|
||||
});
|
||||
|
||||
it('user_version reaches 3', () => {
|
||||
const db = new Database(':memory:');
|
||||
runMigrations(db);
|
||||
const row = db.prepare('PRAGMA user_version').get() as { user_version: number };
|
||||
expect(row.user_version).toBe(3);
|
||||
db.close();
|
||||
});
|
||||
|
||||
it('all 3 new columns default to NULL', () => {
|
||||
const db = new Database(':memory:');
|
||||
runMigrations(db);
|
||||
db.prepare(
|
||||
`INSERT INTO notes (id, raw_text, ai_status, created_at, updated_at)
|
||||
VALUES ('n1', 't', 'pending', '2026-05-01T00:00:00Z', '2026-05-01T00:00:00Z')`
|
||||
).run();
|
||||
const row = db.prepare('SELECT deleted_at, last_recalled_at, recall_dismissed_at FROM notes WHERE id=?').get('n1') as any;
|
||||
expect(row.deleted_at).toBeNull();
|
||||
expect(row.last_recalled_at).toBeNull();
|
||||
expect(row.recall_dismissed_at).toBeNull();
|
||||
db.close();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,9 @@ function sample(id: string, tags: string[]): Note {
|
||||
intentPromptedAt: null,
|
||||
dueDate: null,
|
||||
dueDateEditedByUser: false,
|
||||
deletedAt: null,
|
||||
lastRecalledAt: null,
|
||||
recallDismissedAt: null,
|
||||
createdAt: '2026-04-26T00:00:00Z',
|
||||
updatedAt: '2026-04-26T00:00:00Z',
|
||||
tags: tags.map((name) => ({ name, source: 'ai' as const })),
|
||||
|
||||
Reference in New Issue
Block a user