릴리스: v0.1.17 티어표 삭제와 미사용 이미지 정리 추가

This commit is contained in:
2026-03-19 17:52:09 +09:00
parent 85ee6e3649
commit 8af2726574
11 changed files with 305 additions and 34 deletions

View File

@@ -1,3 +1,4 @@
const fs = require('fs/promises')
const path = require('path')
const express = require('express')
const multer = require('multer')
@@ -13,6 +14,9 @@ const {
deleteGameItem,
deleteGame,
listCustomItems,
findUnusedCustomItems,
findCustomItemsByIds,
deleteCustomItems,
listUsers,
adminUpdateUser,
adminUpdateUserPassword,
@@ -89,6 +93,11 @@ 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),
orphanOnly: z
.union([z.literal('true'), z.literal('false'), z.boolean()])
.optional()
.default('false')
.transform((value) => value === true || value === 'true'),
})
const parsed = schema.safeParse(req.query)
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
@@ -97,10 +106,51 @@ router.get('/custom-items', requireAdmin, async (req, res) => {
queryText: parsed.data.q,
page: parsed.data.page,
limit: parsed.data.limit,
orphanOnly: parsed.data.orphanOnly,
})
res.json(result)
})
async function removeCustomItemFiles(items) {
await Promise.all(
items.map(async (item) => {
if (!item?.src || !item.src.startsWith('/uploads/custom/')) return
const absolutePath = path.join(__dirname, '..', '..', item.src.replace(/^\//, ''))
try {
await fs.unlink(absolutePath)
} catch (e) {
if (e?.code !== 'ENOENT') throw e
}
})
)
}
router.delete('/custom-items/:itemId', requireAdmin, async (req, res) => {
const result = await listCustomItems({ page: 1, limit: 200, orphanOnly: false })
const target = result.items.find((item) => item.id === req.params.itemId)
if (!target) return res.status(404).json({ error: 'not_found' })
if (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)
res.json({ ok: true })
})
router.delete('/custom-items', requireAdmin, async (req, res) => {
const schema = z.object({
q: z.string().trim().max(120).optional().default(''),
})
const parsed = schema.safeParse(req.query)
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
const items = await findUnusedCustomItems({ queryText: parsed.data.q })
const ids = items.map((item) => item.id)
await deleteCustomItems(ids)
await removeCustomItemFiles(items)
res.json({ ok: true, deletedCount: ids.length })
})
router.get('/users', requireAdmin, async (req, res) => {
const users = await listUsers()
res.json({ users })