fix: 대체 아이템 삭제와 미사용 정리 기준 보정

This commit is contained in:
2026-04-06 11:17:19 +09:00
parent a2fc8f8cd4
commit 8b3d469503
5 changed files with 75 additions and 29 deletions

View File

@@ -2027,37 +2027,57 @@ async function findUnusedCustomItems({ queryText = '' } = {}) {
const whereClause = hasQuery ? 'WHERE c.label LIKE ? OR c.src LIKE ? OR u.email LIKE ? OR u.nickname LIKE ?' : ''
const params = hasQuery ? [search, search, search, search] : []
const rows = await query(
`
SELECT
c.id,
c.owner_id,
c.src,
c.label,
c.replaced_by_item_id,
c.replaced_by_src,
c.replaced_by_label,
c.replaced_at,
c.created_at,
u.nickname,
u.email
FROM custom_items c
INNER JOIN users u ON u.id = c.owner_id
${whereClause}
ORDER BY c.created_at DESC
`,
params
)
const [rows, topicItemRows, usageMeta] = await Promise.all([
query(
`
SELECT
c.id,
c.owner_id,
c.src,
c.label,
c.replaced_by_item_id,
c.replaced_by_src,
c.replaced_by_label,
c.replaced_at,
c.created_at,
u.nickname,
u.email
FROM custom_items c
INNER JOIN users u ON u.id = c.owner_id
${whereClause}
ORDER BY c.created_at DESC
`,
params
),
query(
`
SELECT ti.topic_id, tp.name AS topic_name, ti.src
FROM topic_items ti
LEFT JOIN topics tp ON tp.id = ti.topic_id
`
),
getCustomItemUsageMeta(),
])
const templateLinkedBySrc = new Map()
topicItemRows.forEach((row) => {
if (!row?.src) return
if (!templateLinkedBySrc.has(row.src)) templateLinkedBySrc.set(row.src, new Map())
templateLinkedBySrc.get(row.src).set(row.topic_id, {
id: row.topic_id,
name: row.topic_name || row.topic_id,
})
})
const { usageMap } = await getCustomItemUsageMeta()
return rows
.map((row) => ({
...mapCustomItemRow(row),
ownerName: row.nickname || row.email,
ownerEmail: row.email,
usageCount: usageMap.get(row.id) || 0,
usageCount: usageMeta.usageMap.get(row.id) || 0,
linkedTemplates: Array.from((templateLinkedBySrc.get(row.src) || new Map()).values()),
}))
.filter((item) => item.usageCount === 0 || !!item.replacedAt)
.filter((item) => ((item.usageCount === 0 && item.linkedTemplates.length === 0) || !!item.replacedAt))
}
async function getFavoriteStatsForTierListIds(tierListIds, userId = '') {

View File

@@ -54,6 +54,7 @@ const {
listRecentImageOptimizationJobs,
clearImageOptimizationJobs,
cleanupMissingUploadReferences,
listReferencedUploadSources,
replaceUploadSourceReferences,
updateCustomItemDisplayReferences,
clearCustomItemReplacement,
@@ -546,6 +547,12 @@ async function removeCustomItemFiles(items) {
)
}
async function removeUnreferencedCustomItemFiles(items) {
const referencedSrcs = new Set(await listReferencedUploadSources())
const removableItems = (items || []).filter((item) => item?.src && !referencedSrcs.has(item.src))
await removeCustomItemFiles(removableItems)
}
async function promoteLibraryItemToTemplateItem({ item, templateId }) {
return createTopicItem({
id: nanoid(),
@@ -776,13 +783,14 @@ router.delete('/custom-items/:itemId', requireAdmin, async (req, res) => {
return res.json({ ok: true, sourceType: 'template' })
}
const canDeleteReplacedUserItem = target.sourceType === 'user' && !!target.replacedAt
if (!target.canDelete) return res.status(409).json({ error: 'item_locked' })
if (target.linkedTemplates.length > 0) return res.status(409).json({ error: 'item_linked' })
if (target.usageCount > 0) return res.status(409).json({ error: 'item_in_use' })
if (!canDeleteReplacedUserItem && target.linkedTemplates.length > 0) return res.status(409).json({ error: 'item_linked' })
if (!canDeleteReplacedUserItem && target.usageCount > 0) return res.status(409).json({ error: 'item_in_use' })
const items = await findCustomItemsByIds([target.id])
await deleteCustomItems([target.id])
await removeCustomItemFiles(items)
await removeUnreferencedCustomItemFiles(items)
res.json({ ok: true, sourceType: 'user' })
})
@@ -1135,7 +1143,7 @@ router.delete('/custom-items', requireAdmin, async (req, res) => {
const items = await findUnusedCustomItems({ queryText: parsed.data.q })
const ids = items.map((item) => item.id)
await deleteCustomItems(ids)
await removeCustomItemFiles(items)
await removeUnreferencedCustomItemFiles(items)
res.json({ ok: true, deletedCount: ids.length })
})