fix(dictionary): let an edit clear the pronunciation and report duplicate renames

This commit is contained in:
Yun Chan 2026-09-28 00:53:46 +09:00
parent 9d5d043e8b
commit de1e8a82a4
6 changed files with 350 additions and 24 deletions

View file

@ -12,6 +12,12 @@ import { getLogger } from './LoggerService'
import { getCloudSyncService } from './CloudSyncService'
import { getMainWindow } from '../windows/WindowManager'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import {
buildDictionaryUpdatePatch,
isUniqueConstraintViolation,
normalizeDictionaryPronunciation,
normalizeDictionaryWord
} from '@d3ro/core/dictionary-policy'
import type {
DictionaryEntry,
DictionaryQueryParams,
@ -114,7 +120,7 @@ function normalizeCategory(value: string | null): DictionaryEntry['category'] {
class DictionaryService {
add(params: DictionaryAddParams): DictionaryEntry {
const word = params.word.trim()
const word = normalizeDictionaryWord(params.word)
if (!word) {
throw new D3ROError(ErrorCode.DictionaryImportInvalidFormat, 'Dictionary word is empty')
}
@ -126,7 +132,7 @@ class DictionaryService {
const entry: NewDictionary = {
id,
word,
pronunciation: params.pronunciation ?? null,
pronunciation: normalizeDictionaryPronunciation(params.pronunciation) ?? null,
category: params.category ?? 'user',
usageCount: 0,
lastUsedAt: null,
@ -137,8 +143,7 @@ class DictionaryService {
try {
db.insert(dictionary).values(entry).run()
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
if (message.includes('UNIQUE') || message.includes('unique')) {
if (isUniqueConstraintViolation(err)) {
throw new D3ROError(ErrorCode.DictionaryDuplicate, `Duplicate word: ${word}`)
}
throw err
@ -150,16 +155,27 @@ class DictionaryService {
}
update(params: DictionaryUpdateParams): DictionaryEntry | null {
const result = buildDictionaryUpdatePatch(params)
if (!result.ok) {
throw new D3ROError(ErrorCode.DictionaryImportInvalidFormat, 'Dictionary word is empty')
}
const db = getDatabase()
const existing = db.select().from(dictionary).where(eq(dictionary.id, params.id)).get()
if (!existing) return null
const updates: Partial<NewDictionary> = { 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 updates: Partial<NewDictionary> = { ...result.patch, updatedAt: Date.now() }
try {
db.update(dictionary).set(updates).where(eq(dictionary.id, params.id)).run()
} catch (err) {
if (isUniqueConstraintViolation(err)) {
throw new D3ROError(
ErrorCode.DictionaryDuplicate,
`Duplicate word: ${result.patch.word ?? existing.word}`
)
}
throw err
}
const updated = db.select().from(dictionary).where(eq(dictionary.id, params.id)).get()
// Phase 3.3: 자동 push (fire-and-forget)