diff --git a/backend/src/db.js b/backend/src/db.js index 251ead8..1945340 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -1152,7 +1152,7 @@ function replaceItemSrc(items, fromSrc, toSrc, toLabel = '') { return { changed, items: nextItems } } -async function replaceUploadSourceReferences({ fromSrc, toSrc, toLabel = '' }) { +async function replaceUploadSourceReferences({ fromSrc, toSrc, toLabel = '', updateCustomItemsBySrc = true }) { if (!fromSrc || !toSrc || fromSrc === toSrc) return { updatedRows: 0 } const normalizedLabel = typeof toLabel === 'string' ? toLabel.trim().slice(0, 60) : '' @@ -1162,9 +1162,11 @@ async function replaceUploadSourceReferences({ fromSrc, toSrc, toLabel = '' }) { normalizedLabel ? query('UPDATE topic_items SET src = ?, label = ? WHERE src = ?', [toSrc, normalizedLabel, fromSrc]) : query('UPDATE topic_items SET src = ? WHERE src = ?', [toSrc, fromSrc]), - normalizedLabel - ? query('UPDATE custom_items SET src = ?, label = ? WHERE src = ?', [toSrc, normalizedLabel, fromSrc]) - : query('UPDATE custom_items SET src = ? WHERE src = ?', [toSrc, fromSrc]), + updateCustomItemsBySrc + ? normalizedLabel + ? query('UPDATE custom_items SET src = ?, label = ? WHERE src = ?', [toSrc, normalizedLabel, fromSrc]) + : query('UPDATE custom_items SET src = ? WHERE src = ?', [toSrc, fromSrc]) + : Promise.resolve({ affectedRows: 0 }), ]) let updatedRows = Number(userResult.affectedRows || 0) + Number(topicResult.affectedRows || 0) + Number(topicItemResult.affectedRows || 0) + Number(customItemResult.affectedRows || 0) diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index 0dbe98a..5b958d5 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -835,6 +835,7 @@ router.post('/custom-items/:itemId/replace', requireAdmin, async (req, res) => { fromSrc: sourceItem.src, toSrc: targetItem.src, toLabel: targetItem.label || '', + updateCustomItemsBySrc: false, }) await markCustomItemReplaced({ itemId: sourceItem.id, diff --git a/docs/history.md b/docs/history.md index 685943e..42b419d 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # 의사결정 이력 +## 2026-04-06 v1.4.81 +- 원본 보존을 하더라도 실제 `custom_items.src`와 `label`까지 대체 대상 값으로 덮어쓰면 관리자 입장에서는 “원본을 남겼다”기보다 “같은 새 이미지가 두 번 보이는 상태”로 읽히므로, 원본 아이템 레코드는 실제 이미지/라벨을 그대로 유지하고 참조 이동 대상에서만 제외하는 편이 맞다고 정리했다. +- 즉 대체 이력 메타(`어떤 아이템으로 대체됐는지`)와 원본 본문 데이터(`원래 A 이미지/이름`)는 분리해서 보존해야, 이후 복구나 검수 기준으로 의미가 살아난다고 판단했다. + ## 2026-04-06 v1.4.80 - 이미지 대체 직후 원본 사용자 아이템을 완전히 지워버리면 관리자 입장에서는 “왜 바꿨는지”, “나중에 정리해도 되는지”를 다시 확인할 근거가 사라지므로, 참조 이동과 원본 보존을 분리하는 편이 운영 흐름에 더 맞다고 판단했다. - 다만 대체 완료된 원본까지 별도 보호 대상으로 빼면 라이브러리 정리가 끝없이 쌓일 수 있으므로, 원본은 `대체됨` 상태로 계속 보이게 하되 이미 미사용인 이상 `미사용 아이템 일괄 삭제`와 개별 삭제로 언제든 정리할 수 있게 두는 쪽이 균형이 맞는다고 정리했다. diff --git a/docs/update.md b/docs/update.md index f9b98ad..27b9678 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,11 @@ # 업데이트 로그 +## 2026-04-06 v1.4.81 +- 관리자 이미지 대체 시 원본 사용자 아이템 레코드의 `src / label`까지 대체 대상 이미지로 덮어써져, 카드와 상세 모달에서 원래 A 이미지 정보를 다시 볼 수 없던 문제를 바로잡았다. +- 이제 대체 처리에서는 티어표/요청/템플릿 참조만 새 이미지로 옮기고, 원본 사용자 아이템 레코드는 원래 이미지와 이름을 그대로 유지한 채 `대체됨` 메타만 남긴다. +- 그래서 관리자 화면에서는 원래 A 썸네일과 A 라벨을 계속 확인하면서도, 이 아이템이 어떤 대상(Y)으로 대체되었는지 함께 보고 이후 수동 정리나 복구 판단을 할 수 있다. +- 확인: `node --check backend/src/db.js`, `node --check backend/src/routes/admin.js`, `npm run build` + ## 2026-04-06 v1.4.80 - 관리자 이미지 대체는 더 이상 원본 사용자 아이템을 즉시 삭제하지 않고, 원본 레코드와 파일을 남겨 둔 채 `어떤 아이템으로 대체됐는지` 메타만 기록하도록 바꿨다. - 따라서 대체 후에도 원본 이미지는 아이템 라이브러리에서 계속 확인할 수 있고, 카드와 상세 모달에서 `대체됨` 상태와 대체 대상 라벨을 함께 볼 수 있다.