릴리스: v0.1.41 커스텀 아이템 승격 연결 수정
This commit is contained in:
@@ -472,6 +472,28 @@ async function createCustomItem({ id, ownerId, src, label }) {
|
||||
return { id, ownerId, src, label, origin: 'custom', createdAt }
|
||||
}
|
||||
|
||||
async function findCustomItemById(id) {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT id, owner_id, src, label, created_at
|
||||
FROM custom_items
|
||||
WHERE id = ?
|
||||
LIMIT 1
|
||||
`,
|
||||
[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),
|
||||
}
|
||||
}
|
||||
|
||||
async function getCustomItemUsageMap() {
|
||||
const rows = await query('SELECT groups_json, pool_json FROM tierlists')
|
||||
const usageMap = new Map()
|
||||
@@ -778,6 +800,7 @@ module.exports = {
|
||||
deleteGame,
|
||||
updateGameDisplayOrder,
|
||||
createCustomItem,
|
||||
findCustomItemById,
|
||||
listCustomItems,
|
||||
findUnusedCustomItems,
|
||||
listPublicTierLists,
|
||||
|
||||
@@ -17,6 +17,7 @@ const {
|
||||
deleteGame,
|
||||
updateGameDisplayOrder,
|
||||
listCustomItems,
|
||||
findCustomItemById,
|
||||
findUnusedCustomItems,
|
||||
findCustomItemsByIds,
|
||||
deleteCustomItems,
|
||||
@@ -174,6 +175,23 @@ async function removeCustomItemFiles(items) {
|
||||
)
|
||||
}
|
||||
|
||||
async function promoteCustomItemToGameItem({ customItem, gameId }) {
|
||||
const originalName = path.basename(customItem.src || '')
|
||||
const nextFilename = buildUploadFilename({ originalname: originalName })
|
||||
const sourcePath = path.join(__dirname, '..', '..', customItem.src.replace(/^\//, ''))
|
||||
const targetRelativePath = path.join('uploads', 'games', nextFilename)
|
||||
const targetPath = path.join(__dirname, '..', '..', targetRelativePath)
|
||||
|
||||
await fs.copyFile(sourcePath, targetPath)
|
||||
|
||||
return createGameItem({
|
||||
id: nanoid(),
|
||||
gameId,
|
||||
src: `/${targetRelativePath.replace(/\\/g, '/')}`,
|
||||
label: customItem.label,
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -186,6 +204,23 @@ router.delete('/custom-items/:itemId', requireAdmin, async (req, res) => {
|
||||
res.json({ ok: true })
|
||||
})
|
||||
|
||||
router.post('/custom-items/:itemId/promote', requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
gameId: z.string().min(1),
|
||||
})
|
||||
const parsed = schema.safeParse(req.body)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
|
||||
const game = await findGameById(parsed.data.gameId)
|
||||
if (!game) return res.status(404).json({ error: 'game_not_found' })
|
||||
|
||||
const customItem = await findCustomItemById(req.params.itemId)
|
||||
if (!customItem) return res.status(404).json({ error: 'not_found' })
|
||||
|
||||
const item = await promoteCustomItemToGameItem({ customItem, gameId: game.id })
|
||||
res.json({ item })
|
||||
})
|
||||
|
||||
router.delete('/custom-items', requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
q: z.string().trim().max(120).optional().default(''),
|
||||
|
||||
Reference in New Issue
Block a user