아이템 관리 자산 분류와 기본 필터를 정리한다

This commit is contained in:
2026-04-03 13:27:33 +09:00
parent 953837137a
commit 426e7de177
9 changed files with 87 additions and 22 deletions

View File

@@ -201,6 +201,14 @@ function getUserAccountName(row) {
return email.split('@')[0] || email
}
function getAssetLibrarySourceLabel(src) {
const normalizedSrc = String(src || '').trim()
if (normalizedSrc.includes('/uploads/assets/avatars/')) return '프로필 아바타'
if (normalizedSrc.includes('/uploads/assets/tierlists/')) return '티어표 썸네일'
if (normalizedSrc.includes('/uploads/assets/topics/')) return '템플릿 썸네일'
return '보관 자산'
}
async function createPool() {
const rootConnection = await mysql.createConnection({
host: DB_HOST,
@@ -1857,8 +1865,8 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod
ownerEmail: '',
usageCount: 0,
linkedTemplates: [],
sourceType: 'template',
sourceLabel: '관리자 템플릿',
sourceType: 'asset',
sourceLabel: getAssetLibrarySourceLabel(row.src),
canDelete: true,
sourceTopicId: '',
sourceTopicName: '',
@@ -1900,7 +1908,7 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod
siblings.forEach((entry) => {
if (entry.sourceType === 'user') userReferenceCount += 1
else if (entry.isAssetLibraryItem) assetReferenceCount += 1
else if (entry.sourceType === 'asset' || entry.isAssetLibraryItem) assetReferenceCount += 1
else templateReferenceCount += 1
;(entry.linkedTemplates || []).forEach((template) => {
if (template?.id) linkedTemplates.set(template.id, template)
@@ -1939,11 +1947,13 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod
case 'template':
return item.sourceType === 'template' && !item.isAssetLibraryItem
case 'asset':
return !!item.isAssetLibraryItem
return item.sourceType === 'asset' || !!item.isAssetLibraryItem
case 'library':
return item.sourceType === 'user' || (item.sourceType === 'template' && !item.isAssetLibraryItem)
case 'unused-user':
return item.sourceType === 'user' && item.usageCount === 0 && item.linkedTemplates.length === 0
case 'unused-admin':
return !!item.isAssetLibraryItem
return item.sourceType === 'asset' || !!item.isAssetLibraryItem
default:
return true
}

View File

@@ -304,7 +304,7 @@ router.delete('/templates/:templateId', requireAdmin, async (req, res) => {
router.patch('/custom-items/:itemId/label', requireAdmin, async (req, res) => {
const schema = z.object({
label: z.string().trim().min(1).max(60),
sourceType: z.enum(['template', 'user']).optional().default('user'),
sourceType: z.enum(['template', 'user', 'asset']).optional().default('user'),
})
const parsed = schema.safeParse(req.body)
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
@@ -332,7 +332,7 @@ router.get('/custom-items', requireAdmin, async (req, res) => {
q: z.string().trim().max(120).optional().default(''),
page: z.coerce.number().int().min(1).optional().default(1),
limit: z.coerce.number().int().min(1).max(200).optional().default(50),
filter: z.enum(['all', 'user', 'template', 'asset', 'unused-user', 'unused-admin']).optional().default('all'),
filter: z.enum(['library', 'all', 'user', 'template', 'asset', 'unused-user', 'unused-admin']).optional().default('library'),
})
const parsed = schema.safeParse(req.query)
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
@@ -672,6 +672,15 @@ router.delete('/custom-items/:itemId', requireAdmin, async (req, res) => {
const result = await listCustomItems({ page: 1, limit: 10000, filterMode: 'all' })
const target = result.items.find((item) => item.id === req.params.itemId)
if (!target) return res.status(404).json({ error: 'not_found' })
if (target.sourceType === 'asset' || String(target.id || '').startsWith('asset:')) {
const assetId = String(target.id).slice('asset:'.length)
const asset = await findImageAssetById(assetId)
if (!asset) return res.status(404).json({ error: 'not_found' })
await deleteImageAssets([assetId])
await removeUploadFiles([asset.src])
return res.json({ ok: true, sourceType: 'asset' })
}
if (target.sourceType === 'template') {
if (String(target.id || '').startsWith('asset:')) {
const assetId = String(target.id).slice('asset:'.length)