fix: 대체된 사용자 아이템 보존 및 상태 표시
This commit is contained in:
@@ -115,6 +115,21 @@ function mapTopicItemRow(row) {
|
||||
}
|
||||
}
|
||||
|
||||
function mapCustomItemRow(row) {
|
||||
if (!row) return null
|
||||
return {
|
||||
id: row.id,
|
||||
ownerId: row.owner_id,
|
||||
src: row.src,
|
||||
label: row.label,
|
||||
createdAt: Number(row.created_at),
|
||||
replacedByItemId: row.replaced_by_item_id || '',
|
||||
replacedBySrc: row.replaced_by_src || '',
|
||||
replacedByLabel: row.replaced_by_label || '',
|
||||
replacedAt: Number(row.replaced_at || 0),
|
||||
}
|
||||
}
|
||||
|
||||
function mapImageAssetRow(row) {
|
||||
if (!row) return null
|
||||
return {
|
||||
@@ -360,12 +375,21 @@ async function ensureSchema() {
|
||||
owner_id VARCHAR(64) NOT NULL,
|
||||
src VARCHAR(255) NOT NULL,
|
||||
label VARCHAR(120) NOT NULL,
|
||||
replaced_by_item_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
replaced_by_src VARCHAR(255) NOT NULL DEFAULT '',
|
||||
replaced_by_label VARCHAR(120) NOT NULL DEFAULT '',
|
||||
replaced_at BIGINT NOT NULL DEFAULT 0,
|
||||
created_at BIGINT NOT NULL,
|
||||
INDEX idx_custom_items_owner_id (owner_id),
|
||||
CONSTRAINT fk_custom_items_owner FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
`)
|
||||
|
||||
await query(`ALTER TABLE custom_items ADD COLUMN IF NOT EXISTS replaced_by_item_id VARCHAR(64) NOT NULL DEFAULT '' AFTER label`)
|
||||
await query(`ALTER TABLE custom_items ADD COLUMN IF NOT EXISTS replaced_by_src VARCHAR(255) NOT NULL DEFAULT '' AFTER replaced_by_item_id`)
|
||||
await query(`ALTER TABLE custom_items ADD COLUMN IF NOT EXISTS replaced_by_label VARCHAR(120) NOT NULL DEFAULT '' AFTER replaced_by_src`)
|
||||
await query(`ALTER TABLE custom_items ADD COLUMN IF NOT EXISTS replaced_at BIGINT NOT NULL DEFAULT 0 AFTER replaced_by_label`)
|
||||
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS tierlists (
|
||||
id VARCHAR(64) PRIMARY KEY,
|
||||
@@ -1518,7 +1542,7 @@ async function updateTopicItemDisplayOrder(topicId, itemIds) {
|
||||
async function updateCustomItemLabel(itemId, label) {
|
||||
await query('UPDATE custom_items SET label = ? WHERE id = ?', [label, itemId])
|
||||
const rows = await query(`
|
||||
SELECT c.id, c.owner_id, c.src, c.label, c.created_at, u.nickname, u.email
|
||||
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
|
||||
WHERE c.id = ?
|
||||
@@ -1527,16 +1551,20 @@ async function updateCustomItemLabel(itemId, label) {
|
||||
const row = rows[0]
|
||||
if (!row) return null
|
||||
return {
|
||||
id: row.id,
|
||||
ownerId: row.owner_id,
|
||||
src: row.src,
|
||||
label: row.label,
|
||||
createdAt: Number(row.created_at),
|
||||
...mapCustomItemRow(row),
|
||||
ownerName: row.nickname || row.email,
|
||||
ownerEmail: row.email,
|
||||
}
|
||||
}
|
||||
|
||||
async function markCustomItemReplaced({ itemId, replacedByItemId = '', replacedBySrc = '', replacedByLabel = '' }) {
|
||||
await query(
|
||||
'UPDATE custom_items SET replaced_by_item_id = ?, replaced_by_src = ?, replaced_by_label = ?, replaced_at = ? WHERE id = ?',
|
||||
[replacedByItemId || '', replacedBySrc || '', replacedByLabel || '', now(), itemId]
|
||||
)
|
||||
return findCustomItemById(itemId)
|
||||
}
|
||||
|
||||
async function updateImageAssetLabel(assetId, label) {
|
||||
await query('UPDATE image_assets SET label_override = ? WHERE id = ?', [label, assetId])
|
||||
const rows = await query('SELECT id, content_hash, src, label_override, mime_type, byte_size, original_byte_size, width, height, created_at FROM image_assets WHERE id = ? LIMIT 1', [assetId])
|
||||
@@ -1626,7 +1654,7 @@ async function syncOwnedCustomItemLabels({ ownerId, items }) {
|
||||
async function findCustomItemById(id) {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT id, owner_id, src, label, created_at
|
||||
SELECT id, owner_id, src, label, replaced_by_item_id, replaced_by_src, replaced_by_label, replaced_at, created_at
|
||||
FROM custom_items
|
||||
WHERE id = ?
|
||||
LIMIT 1
|
||||
@@ -1635,14 +1663,7 @@ async function findCustomItemById(id) {
|
||||
)
|
||||
|
||||
const row = rows[0]
|
||||
if (!row) return null
|
||||
return {
|
||||
id: row.id,
|
||||
ownerId: row.owner_id,
|
||||
src: row.src,
|
||||
label: row.label,
|
||||
createdAt: Number(row.created_at),
|
||||
}
|
||||
return mapCustomItemRow(row)
|
||||
}
|
||||
|
||||
async function getCustomItemUsageMeta() {
|
||||
@@ -1707,6 +1728,10 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod
|
||||
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
|
||||
@@ -1786,17 +1811,14 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod
|
||||
const linkedTemplates = Array.from((templateLinkedBySrc.get(row.src) || new Map()).values())
|
||||
return {
|
||||
id: row.id,
|
||||
ownerId: row.owner_id,
|
||||
src: row.src,
|
||||
label: row.label,
|
||||
createdAt: Number(row.created_at),
|
||||
...mapCustomItemRow(row),
|
||||
ownerName: row.nickname || row.email,
|
||||
ownerEmail: row.email,
|
||||
usageCount: usageMeta.usageMap.get(row.id) || 0,
|
||||
linkedTemplates,
|
||||
assetKind: resolveLibraryAssetKind(row.src),
|
||||
sourceType: 'user',
|
||||
sourceLabel: '사용자 아이템',
|
||||
sourceLabel: Number(row.replaced_at || 0) > 0 ? '대체된 사용자 아이템' : '사용자 아이템',
|
||||
canDelete: true,
|
||||
}
|
||||
})
|
||||
@@ -1895,6 +1917,10 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod
|
||||
usageCount: entry.usageCount || 0,
|
||||
linkedTemplates: entry.linkedTemplates || [],
|
||||
isAssetLibraryItem: !!entry.isAssetLibraryItem,
|
||||
replacedByItemId: entry.replacedByItemId || '',
|
||||
replacedBySrc: entry.replacedBySrc || '',
|
||||
replacedByLabel: entry.replacedByLabel || '',
|
||||
replacedAt: entry.replacedAt || 0,
|
||||
})),
|
||||
}
|
||||
})
|
||||
@@ -1947,6 +1973,10 @@ async function findUnusedCustomItems({ queryText = '' } = {}) {
|
||||
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
|
||||
@@ -1961,11 +1991,7 @@ async function findUnusedCustomItems({ queryText = '' } = {}) {
|
||||
const { usageMap } = await getCustomItemUsageMeta()
|
||||
return rows
|
||||
.map((row) => ({
|
||||
id: row.id,
|
||||
ownerId: row.owner_id,
|
||||
src: row.src,
|
||||
label: row.label,
|
||||
createdAt: Number(row.created_at),
|
||||
...mapCustomItemRow(row),
|
||||
ownerName: row.nickname || row.email,
|
||||
ownerEmail: row.email,
|
||||
usageCount: usageMap.get(row.id) || 0,
|
||||
@@ -2895,20 +2921,14 @@ async function findCustomItemsByIds(ids) {
|
||||
const placeholders = ids.map(() => '?').join(', ')
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT id, owner_id, src, label, created_at
|
||||
SELECT id, owner_id, src, label, replaced_by_item_id, replaced_by_src, replaced_by_label, replaced_at, created_at
|
||||
FROM custom_items
|
||||
WHERE id IN (${placeholders})
|
||||
`,
|
||||
ids
|
||||
)
|
||||
|
||||
return rows.map((row) => ({
|
||||
id: row.id,
|
||||
ownerId: row.owner_id,
|
||||
src: row.src,
|
||||
label: row.label,
|
||||
createdAt: Number(row.created_at),
|
||||
}))
|
||||
return rows.map(mapCustomItemRow)
|
||||
}
|
||||
|
||||
async function deleteCustomItems(ids) {
|
||||
@@ -3064,6 +3084,7 @@ module.exports = {
|
||||
deleteTopic,
|
||||
updateTopicDisplayOrder,
|
||||
updateCustomItemLabel,
|
||||
markCustomItemReplaced,
|
||||
updateImageAssetLabel,
|
||||
createCustomItem,
|
||||
findCustomItemById,
|
||||
|
||||
@@ -33,6 +33,7 @@ const {
|
||||
findUnusedCustomItems,
|
||||
findCustomItemsByIds,
|
||||
deleteCustomItems,
|
||||
markCustomItemReplaced,
|
||||
listUsers,
|
||||
findPrimaryAdminUser,
|
||||
listAdminTierLists,
|
||||
@@ -835,10 +836,12 @@ router.post('/custom-items/:itemId/replace', requireAdmin, async (req, res) => {
|
||||
toSrc: targetItem.src,
|
||||
toLabel: targetItem.label || '',
|
||||
})
|
||||
|
||||
const sourceCustomItems = await findCustomItemsByIds([sourceItem.id])
|
||||
await deleteCustomItems([sourceItem.id])
|
||||
await removeCustomItemFiles(sourceCustomItems)
|
||||
await markCustomItemReplaced({
|
||||
itemId: sourceItem.id,
|
||||
replacedByItemId: targetItem.id || '',
|
||||
replacedBySrc: targetItem.src || '',
|
||||
replacedByLabel: targetItem.label || '',
|
||||
})
|
||||
|
||||
res.json({
|
||||
ok: true,
|
||||
|
||||
Reference in New Issue
Block a user