Files
inkling/src/main/db/migrations/m005_ai_disabled.ts
altair823 fd839f6afe feat(v029): ai_status 'disabled' enum + CaptureService ai_enabled 분기 (skip pending_jobs)
- AiStatus enum 'disabled' 추가 — settings.ai_enabled=false 일 때 새 노트의 초기 status.
- m005 migration: ai_status CHECK 제약을 ('pending','done','failed','disabled') 로 relax.
  SQLite 가 ALTER COLUMN CHECK 미지원 → table recreate (notes_new INSERT SELECT DROP RENAME).
  기존 인덱스 (idx_notes_created_at, idx_notes_ai_status, idx_notes_deleted_at) 재생성.
- SettingsService schema 에 ai_enabled / onboarding_completed (optional) 추가 +
  isAiEnabled / setAiEnabled / isOnboardingCompleted / setOnboardingCompleted accessor.
  기본 fallback (ai_enabled=true, onboarding_completed=false) — 기존 settings.json 무영향.
- NoteRepository.create 가 optional aiStatus 받도록 — 'pending' 외 값일 때 pending_jobs skip.
  기존 caller (rawText 만 전달) 무영향.
- CaptureService deps 에 settings (좁은 AiEnabledSource 인터페이스) 추가.
  submit() 가 ai_enabled 조회 → false 면 ai_status='disabled' insert + enqueue skip.
  settings 미주입 시 기존 동작 (항상 enabled) 보존 — 테스트 케이스 무영향.
- main/index.ts wiring: settings: settingsSvc 주입.

Tests: 489 → 494 (CaptureService ai_enabled 2건 + m005 migration 3건). typecheck 0.
2026-05-09 15:43:01 +09:00

66 lines
3.1 KiB
TypeScript

// v5: ai_status enum 에 'disabled' 추가 (v0.2.9 Cut B). settings.ai_enabled=false 일 때
// CaptureService 가 새 노트를 ai_status='disabled' 로 insert + pending_jobs enqueue skip.
//
// SQLite 는 ALTER COLUMN ... CHECK 미지원 → table recreate 패턴.
// 외래키 (note_tags / media / pending_jobs) 는 notes.id 를 참조 + ON DELETE CASCADE 라
// FK off + DROP/RENAME 시 데이터 보존 위해 새 테이블 생성 → INSERT SELECT → DROP old → RENAME new.
// PRAGMA foreign_keys=OFF 안에서 single transaction (runMigrations 가 transaction 으로 감쌈).
import type Database from 'better-sqlite3';
export const version = 5;
export function up(db: Database.Database): void {
// 기존 인덱스/CHECK 제약을 그대로 유지하되 ai_status 만 'disabled' 추가.
db.exec(`
PRAGMA foreign_keys=OFF;
CREATE TABLE notes_new (
id TEXT PRIMARY KEY,
raw_text TEXT NOT NULL,
ai_title TEXT,
ai_summary TEXT,
ai_status TEXT NOT NULL
CHECK (ai_status IN ('pending','done','failed','disabled')),
ai_error TEXT,
ai_provider TEXT,
ai_generated_at TEXT,
title_edited_by_user INTEGER NOT NULL DEFAULT 0
CHECK (title_edited_by_user IN (0,1)),
summary_edited_by_user INTEGER NOT NULL DEFAULT 0
CHECK (summary_edited_by_user IN (0,1)),
user_intent TEXT,
intent_prompted_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
due_date TEXT,
due_date_edited_by_user INTEGER NOT NULL DEFAULT 0
CHECK (due_date_edited_by_user IN (0,1)),
deleted_at TEXT,
last_recalled_at TEXT,
recall_dismissed_at TEXT,
status TEXT NOT NULL DEFAULT 'active',
status_changed_at TEXT,
move_reason TEXT
);
INSERT INTO notes_new (
id, raw_text, ai_title, ai_summary, ai_status, ai_error, ai_provider, ai_generated_at,
title_edited_by_user, summary_edited_by_user, user_intent, intent_prompted_at,
created_at, updated_at, due_date, due_date_edited_by_user,
deleted_at, last_recalled_at, recall_dismissed_at,
status, status_changed_at, move_reason
)
SELECT
id, raw_text, ai_title, ai_summary, ai_status, ai_error, ai_provider, ai_generated_at,
title_edited_by_user, summary_edited_by_user, user_intent, intent_prompted_at,
created_at, updated_at, due_date, due_date_edited_by_user,
deleted_at, last_recalled_at, recall_dismissed_at,
status, status_changed_at, move_reason
FROM notes;
DROP TABLE notes;
ALTER TABLE notes_new RENAME TO notes;
CREATE INDEX idx_notes_created_at ON notes(created_at DESC);
CREATE INDEX idx_notes_ai_status ON notes(ai_status);
CREATE INDEX IF NOT EXISTS idx_notes_deleted_at ON notes(deleted_at);
PRAGMA foreign_keys=ON;
`);
}