// src/main/db/schema.ts // 설계서 03의 drizzle-orm 스키마 정의 import { sqliteTable, text, integer, real, index, uniqueIndex } from 'drizzle-orm/sqlite-core' // ── history ────────────────────────────────────────────── export const history = sqliteTable( 'history', { id: text('id').primaryKey(), title: text('title'), originalText: text('original_text').notNull(), polishedText: text('polished_text'), focusedApp: text('focused_app'), focusedAppName: text('focused_app_name'), focusedAppWindowTitle: text('focused_app_window_title'), mode: text('mode', { enum: ['dictation', 'translate', 'command', 'caption', 'file-transcription'] }) .notNull() .default('dictation'), status: text('status', { enum: ['completed', 'cancelled', 'error'] }) .notNull() .default('completed'), errorCode: text('error_code'), audioLocalPath: text('audio_local_path'), duration: real('duration').notNull(), detectedLanguage: text('detected_language'), micDevice: text('mic_device'), wordCount: integer('word_count').notNull().default(0), sttModel: text('stt_model'), llmModel: text('llm_model'), sttLatencyMs: integer('stt_latency_ms'), llmLatencyMs: integer('llm_latency_ms'), createdAt: integer('created_at').notNull(), updatedAt: integer('updated_at').notNull(), appVersion: text('app_version').notNull().default('1.0.0'), /** Phase 12.2: 회의록 자동 요약 텍스트 (마크다운) */ summaryText: text('summary_text'), }, (table) => [ index('idx_history_created_at').on(table.createdAt), index('idx_history_status').on(table.status), index('idx_history_detected_language').on(table.detectedLanguage), index('idx_history_focused_app_name').on(table.focusedAppName), index('idx_history_mode').on(table.mode) ] ) // ── dictionary ─────────────────────────────────────────── export const dictionary = sqliteTable( 'dictionary', { id: text('id').primaryKey(), word: text('word').notNull(), pronunciation: text('pronunciation'), category: text('category', { enum: ['user', 'auto', 'technical'] }) .notNull() .default('user'), usageCount: integer('usage_count').notNull().default(0), lastUsedAt: integer('last_used_at'), createdAt: integer('created_at').notNull(), updatedAt: integer('updated_at').notNull() }, (table) => [ uniqueIndex('idx_dictionary_word_category').on(table.word, table.category), index('idx_dictionary_created_at').on(table.createdAt), index('idx_dictionary_usage_count').on(table.usageCount) ] ) // ── stats ──────────────────────────────────────────────── export const stats = sqliteTable('stats', { id: integer('id').primaryKey(), totalDuration: real('total_duration').notNull().default(0), totalWords: integer('total_words').notNull().default(0), sessionCount: integer('session_count').notNull().default(0), streakDays: integer('streak_days').notNull().default(0), lastSessionAt: integer('last_session_at'), lastUpdated: integer('last_updated').notNull() }) // ── memo_tags (Phase 10.3) ──────────────────────────────── export const memoTags = sqliteTable( 'memo_tags', { id: text('id').primaryKey(), historyId: text('history_id').notNull(), tag: text('tag').notNull(), createdAt: integer('created_at').notNull(), }, (table) => [ uniqueIndex('idx_memo_tags_unique').on(table.historyId, table.tag), index('idx_memo_tags_history_id').on(table.historyId), index('idx_memo_tags_tag').on(table.tag), ] ) export type MemoTagRow = typeof memoTags.$inferSelect export type NewMemoTagRow = typeof memoTags.$inferInsert // ── daily_usage (Phase 11) ──────────────────────────────── export const dailyUsage = sqliteTable( 'daily_usage', { id: integer('id').primaryKey({ autoIncrement: true }), date: text('date').notNull(), feature: text('feature').notNull(), count: integer('count').notNull().default(0), }, (table) => [ uniqueIndex('idx_daily_usage_date_feature').on(table.date, table.feature), index('idx_daily_usage_date').on(table.date), ] ) export type DailyUsageRow = typeof dailyUsage.$inferSelect export type NewDailyUsageRow = typeof dailyUsage.$inferInsert // ── rag_documents (Phase 13.2) ────────────────────────── export const ragDocuments = sqliteTable( 'rag_documents', { id: text('id').primaryKey(), fileName: text('file_name').notNull(), filePath: text('file_path').notNull(), fileType: text('file_type', { enum: ['txt', 'md', 'pdf', 'docx'] }).notNull(), chunkCount: integer('chunk_count').notNull().default(0), indexed: integer('indexed', { mode: 'boolean' }).notNull().default(false), indexedAt: integer('indexed_at'), addedAt: integer('added_at').notNull(), }, (table) => [ index('idx_rag_documents_added_at').on(table.addedAt), ] ) export const ragChunks = sqliteTable( 'rag_chunks', { id: text('id').primaryKey(), documentId: text('document_id').notNull(), content: text('content').notNull(), /** 임베딩 벡터 (JSON 직렬화 float[]) */ embedding: text('embedding').notNull(), chunkIndex: integer('chunk_index').notNull(), }, (table) => [ index('idx_rag_chunks_document_id').on(table.documentId), ] ) export type RAGDocumentRow = typeof ragDocuments.$inferSelect export type NewRAGDocumentRow = typeof ragDocuments.$inferInsert export type RAGChunkRow = typeof ragChunks.$inferSelect export type NewRAGChunkRow = typeof ragChunks.$inferInsert // ── meetingSessions (Phase 14) ────────────────────────── export const meetingSessions = sqliteTable( 'meeting_sessions', { id: text('id').primaryKey(), title: text('title'), status: text('status', { enum: ['recording', 'processing', 'completed', 'error'], }).notNull().default('recording'), startedAt: integer('started_at').notNull(), endedAt: integer('ended_at'), durationMs: integer('duration_ms'), rawTranscript: text('raw_transcript'), editedTranscript: text('edited_transcript'), minutesMarkdown: text('minutes_markdown'), minutesJson: text('minutes_json'), sttModel: text('stt_model'), llmModel: text('llm_model'), sttLatencyMs: integer('stt_latency_ms'), llmLatencyMs: integer('llm_latency_ms'), errorMessage: text('error_message'), createdAt: integer('created_at').notNull(), updatedAt: integer('updated_at').notNull(), }, (table) => [ index('idx_meeting_sessions_created_at').on(table.createdAt), index('idx_meeting_sessions_status').on(table.status), ] ) // ── meetingMemos (Phase 14) ───────────────────────────── export const meetingMemos = sqliteTable( 'meeting_memos', { id: text('id').primaryKey(), sessionId: text('session_id').notNull(), content: text('content').notNull(), timestampMs: integer('timestamp_ms').notNull(), createdAt: integer('created_at').notNull(), }, (table) => [ index('idx_meeting_memos_session_id').on(table.sessionId), index('idx_meeting_memos_timestamp').on(table.timestampMs), ] ) export type MeetingSessionRow = typeof meetingSessions.$inferSelect export type NewMeetingSessionRow = typeof meetingSessions.$inferInsert export type MeetingMemoRow = typeof meetingMemos.$inferSelect export type NewMeetingMemoRow = typeof meetingMemos.$inferInsert // ── meetingDocuments (Phase 14.5) ─────────────────────── export const meetingDocuments = sqliteTable( 'meeting_documents', { id: text('id').primaryKey(), sessionId: text('session_id').notNull(), templateType: text('template_type', { enum: ['minutes', 'report', 'idea-note', 'custom', 'mindmap'], }).notNull(), title: text('title').notNull(), content: text('content').notNull().default(''), promptUsed: text('prompt_used'), llmModel: text('llm_model'), llmLatencyMs: integer('llm_latency_ms'), createdAt: integer('created_at').notNull(), updatedAt: integer('updated_at').notNull(), }, (table) => [ index('idx_meeting_documents_session_id').on(table.sessionId), ] ) export type MeetingDocumentRow = typeof meetingDocuments.$inferSelect export type NewMeetingDocumentRow = typeof meetingDocuments.$inferInsert // ── 타입 추출 ──────────────────────────────────────────── export type History = typeof history.$inferSelect export type NewHistory = typeof history.$inferInsert export type Dictionary = typeof dictionary.$inferSelect export type NewDictionary = typeof dictionary.$inferInsert export type Stats = typeof stats.$inferSelect