// src/main/services/DictionaryService.ts // 사용자 커스텀 단어 사전. 설계서 01/03 IDictionaryService 구현. import { eq, like, desc, count, sql } from 'drizzle-orm' import { nanoid } from 'nanoid' import { getDatabase } from '../db' import { dictionary } from '../db/schema' import type { Dictionary, NewDictionary } from '../db/schema' import { getLogger } from './LoggerService' import type { DictionaryEntry, DictionaryQueryParams, DictionaryPage, DictionaryAddParams, DictionaryUpdateParams, DictionarySearchParams } from '@d3ro/core/types' const logger = getLogger('DictionaryService') class DictionaryService { add(params: DictionaryAddParams): DictionaryEntry { const db = getDatabase() const now = Date.now() const id = nanoid() const entry: NewDictionary = { id, word: params.word, pronunciation: params.pronunciation ?? null, category: params.category ?? 'user', usageCount: 0, lastUsedAt: null, createdAt: now, updatedAt: now } db.insert(dictionary).values(entry).run() logger.info(`Dictionary entry added: "${params.word}"`) return this._toEntry(entry as Dictionary) } update(params: DictionaryUpdateParams): DictionaryEntry | null { const db = getDatabase() const existing = db.select().from(dictionary).where(eq(dictionary.id, params.id)).get() if (!existing) return null const updates: Partial = { updatedAt: Date.now() } if (params.word !== undefined) updates.word = params.word if (params.pronunciation !== undefined) updates.pronunciation = params.pronunciation if (params.category !== undefined) updates.category = params.category db.update(dictionary).set(updates).where(eq(dictionary.id, params.id)).run() const updated = db.select().from(dictionary).where(eq(dictionary.id, params.id)).get() return updated ? this._toEntry(updated) : null } delete(id: string): boolean { const db = getDatabase() const result = db.delete(dictionary).where(eq(dictionary.id, id)).run() return result.changes > 0 } list(params: DictionaryQueryParams): DictionaryPage { const db = getDatabase() const { page, pageSize } = params const totalResult = db.select({ count: count() }).from(dictionary).get() const total = totalResult?.count ?? 0 const entries = db .select() .from(dictionary) .orderBy(desc(dictionary.createdAt)) .limit(pageSize) .offset(page * pageSize) .all() return { entries: entries.map((e) => this._toEntry(e)), total, page, pageSize, totalPages: Math.ceil(total / pageSize) } } search(params: DictionarySearchParams): DictionaryPage { const db = getDatabase() const { query, page, pageSize } = params const pattern = `%${query}%` const totalResult = db .select({ count: count() }) .from(dictionary) .where(like(dictionary.word, pattern)) .get() const total = totalResult?.count ?? 0 const entries = db .select() .from(dictionary) .where(like(dictionary.word, pattern)) .orderBy(desc(dictionary.usageCount)) .limit(pageSize) .offset(page * pageSize) .all() return { entries: entries.map((e) => this._toEntry(e)), total, page, pageSize, totalPages: Math.ceil(total / pageSize) } } incrementUsage(id: string): void { const db = getDatabase() db.update(dictionary) .set({ usageCount: sql`usage_count + 1`, lastUsedAt: Date.now() }) .where(eq(dictionary.id, id)) .run() } /** * 사전 단어 목록을 STT initialPrompt 형태로 반환한다. */ getPromptHints(limit = 50): string { const db = getDatabase() const words = db .select({ word: dictionary.word }) .from(dictionary) .orderBy(desc(dictionary.usageCount)) .limit(limit) .all() return words.map((w) => w.word).join(', ') } dispose(): void { logger.info('DictionaryService disposed') } private _toEntry(row: Dictionary): DictionaryEntry { return { id: row.id, word: row.word, pronunciation: row.pronunciation, category: row.category as DictionaryEntry['category'], usageCount: row.usageCount, lastUsedAt: row.lastUsedAt, createdAt: row.createdAt, updatedAt: row.updatedAt } } } let instance: DictionaryService | null = null export function getDictionaryService(): DictionaryService { if (!instance) { instance = new DictionaryService() } return instance }