릴리스: v1.4.12 topics/templates API 경로 정리

This commit is contained in:
2026-04-02 19:03:02 +09:00
parent 9b97a7c23b
commit 1fabf66f04
9 changed files with 59 additions and 39 deletions

View File

@@ -54,6 +54,10 @@ const { createMemoryUpload, writeOptimizedImage, getImageOptimizationQueueState
const router = express.Router()
function getTemplateIdParam(req) {
return req.params.templateId || req.params.gameId || ''
}
function buildUploadFilename(file) {
const ext = path.extname(file.originalname || '').toLowerCase()
const safeExt = ext && /^[.a-z0-9]+$/.test(ext) ? ext : ''
@@ -110,7 +114,7 @@ function canManageAdminRole(actingUser, primaryAdmin) {
return !!actingUser?.isAdmin && primaryAdmin?.id === actingUser.id
}
router.post('/games', requireAdmin, async (req, res) => {
router.post(['/games', '/templates'], requireAdmin, async (req, res) => {
const schema = z.object({
id: z.string().min(1),
name: z.string().min(1).max(60),
@@ -126,24 +130,26 @@ router.post('/games', requireAdmin, async (req, res) => {
const copiedThumb = await copyUploadIntoGameAsset(parsed.data.thumbnailSrc)
await updateGameThumbnail(game.id, copiedThumb)
}
res.json({ game: await findGameById(game.id) })
const template = await findGameById(game.id)
res.json({ game: template, template })
})
router.patch('/games/:gameId', requireAdmin, async (req, res) => {
router.patch(['/games/:gameId', '/templates/:templateId'], requireAdmin, async (req, res) => {
const schema = z.object({
isPublic: z.boolean(),
})
const parsed = schema.safeParse(req.body)
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
const game = await findGameById(req.params.gameId)
const templateId = getTemplateIdParam(req)
const game = await findGameById(templateId)
if (!game) return res.status(404).json({ error: 'not_found' })
const updated = await updateGameVisibility(game.id, parsed.data.isPublic)
res.json({ game: updated })
res.json({ game: updated, template: updated })
})
router.patch('/games/display-order', requireAdmin, async (req, res) => {
router.patch(['/games/display-order', '/templates/display-order'], requireAdmin, async (req, res) => {
const schema = z.object({
gameIds: z.array(z.string().min(1)).max(50),
})
@@ -154,26 +160,28 @@ router.patch('/games/display-order', requireAdmin, async (req, res) => {
const validGameIds = new Set(games.map((game) => game.id))
const filteredIds = parsed.data.gameIds.filter((gameId) => validGameIds.has(gameId))
const updatedGames = await updateGameDisplayOrder(filteredIds)
res.json({ games: updatedGames })
res.json({ games: updatedGames, templates: updatedGames })
})
router.patch('/games/:gameId/items/display-order', requireAdmin, async (req, res) => {
router.patch(['/games/:gameId/items/display-order', '/templates/:templateId/items/display-order'], requireAdmin, async (req, res) => {
const schema = z.object({
itemIds: z.array(z.string().min(1)).min(1),
})
const parsed = schema.safeParse(req.body)
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
const game = await findGameById(req.params.gameId)
const templateId = getTemplateIdParam(req)
const game = await findGameById(templateId)
if (!game) return res.status(404).json({ error: 'not_found' })
const items = await updateGameItemDisplayOrder(game.id, parsed.data.itemIds)
res.json({ items })
})
router.post('/games/:gameId/thumbnail', requireAdmin, upload.single('thumbnail'), async (req, res) => {
router.post(['/games/:gameId/thumbnail', '/templates/:templateId/thumbnail'], requireAdmin, upload.single('thumbnail'), async (req, res) => {
if (!req.file) return res.status(400).json({ error: 'file_required' })
const game = await findGameById(req.params.gameId)
const templateId = getTemplateIdParam(req)
const game = await findGameById(templateId)
if (!game) return res.status(404).json({ error: 'not_found' })
const optimized = await writeOptimizedImage({
@@ -185,14 +193,15 @@ router.post('/games/:gameId/thumbnail', requireAdmin, upload.single('thumbnail')
quality: 84,
})
const updated = await updateGameThumbnail(req.params.gameId, optimized.src)
res.json({ game: updated })
const updated = await updateGameThumbnail(templateId, optimized.src)
res.json({ game: updated, template: updated })
})
router.post('/games/:gameId/images', requireAdmin, upload.array('images', 50), async (req, res) => {
router.post(['/games/:gameId/images', '/templates/:templateId/images'], requireAdmin, upload.array('images', 50), async (req, res) => {
const files = Array.isArray(req.files) ? req.files : []
if (!files.length) return res.status(400).json({ error: 'file_required' })
const game = await findGameById(req.params.gameId)
const templateId = getTemplateIdParam(req)
const game = await findGameById(templateId)
if (!game) return res.status(404).json({ error: 'not_found' })
const labelsRaw = req.body?.labels
@@ -223,19 +232,19 @@ router.post('/games/:gameId/images', requireAdmin, upload.array('images', 50), a
res.json({ item: items[0], items })
})
router.delete('/games/:gameId/items/:itemId', requireAdmin, async (req, res) => {
const game = await findGameById(req.params.gameId)
router.delete(['/games/:gameId/items/:itemId', '/templates/:templateId/items/:itemId'], requireAdmin, async (req, res) => {
const game = await findGameById(getTemplateIdParam(req))
if (!game) return res.status(404).json({ error: 'not_found' })
await deleteGameItem(req.params.itemId)
res.json({ ok: true })
})
router.patch('/games/:gameId/items/:itemId', requireAdmin, async (req, res) => {
router.patch(['/games/:gameId/items/:itemId', '/templates/:templateId/items/:itemId'], requireAdmin, async (req, res) => {
const schema = z.object({ label: z.string().trim().min(1).max(60) })
const parsed = schema.safeParse(req.body)
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
const game = await findGameById(req.params.gameId)
const game = await findGameById(getTemplateIdParam(req))
if (!game) return res.status(404).json({ error: 'not_found' })
const updated = await updateGameItemLabel(req.params.itemId, parsed.data.label)
@@ -243,10 +252,11 @@ router.patch('/games/:gameId/items/:itemId', requireAdmin, async (req, res) => {
res.json({ item: updated })
})
router.delete('/games/:gameId', requireAdmin, async (req, res) => {
const game = await findGameById(req.params.gameId)
router.delete(['/games/:gameId', '/templates/:templateId'], requireAdmin, async (req, res) => {
const templateId = getTemplateIdParam(req)
const game = await findGameById(templateId)
if (!game) return res.status(404).json({ error: 'not_found' })
await deleteGame(req.params.gameId)
await deleteGame(templateId)
res.json({ ok: true })
})