릴리스: v0.1.6 MariaDB 개발 환경 및 저장소 설정 정리
This commit is contained in:
151
backend/src/routes/tierlists.js
Normal file
151
backend/src/routes/tierlists.js
Normal file
@@ -0,0 +1,151 @@
|
||||
const express = require('express')
|
||||
const path = require('path')
|
||||
const multer = require('multer')
|
||||
const { z } = require('zod')
|
||||
const { nanoid } = require('nanoid')
|
||||
const {
|
||||
findTierListById,
|
||||
listPublicTierLists,
|
||||
listUserTierLists,
|
||||
saveTierList,
|
||||
createCustomItem,
|
||||
} = require('../db')
|
||||
const { requireAuth } = require('../middleware/auth')
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
function normalizePoolItem(item) {
|
||||
if (!item || item.origin !== 'game' || typeof item.src !== 'string') return item
|
||||
if (item.src.startsWith('/uploads/')) return item
|
||||
|
||||
try {
|
||||
const url = new URL(item.src)
|
||||
if (url.pathname.startsWith('/uploads/')) {
|
||||
return { ...item, src: url.pathname }
|
||||
}
|
||||
} catch (e) {
|
||||
return item
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
function normalizeTierList(tierList) {
|
||||
return {
|
||||
...tierList,
|
||||
pool: Array.isArray(tierList.pool) ? tierList.pool.map(normalizePoolItem) : [],
|
||||
}
|
||||
}
|
||||
|
||||
function buildUploadFilename(file) {
|
||||
const ext = path.extname(file.originalname || '').toLowerCase()
|
||||
const safeExt = ext && /^[.a-z0-9]+$/.test(ext) ? ext : ''
|
||||
return `${Date.now()}-${nanoid()}${safeExt}`
|
||||
}
|
||||
|
||||
const upload = multer({
|
||||
storage: multer.diskStorage({
|
||||
destination: (req, file, cb) => cb(null, path.join(__dirname, '..', '..', 'uploads', 'custom')),
|
||||
filename: (req, file, cb) => cb(null, buildUploadFilename(file)),
|
||||
}),
|
||||
limits: { fileSize: 6 * 1024 * 1024 },
|
||||
})
|
||||
|
||||
const tierListUpsertSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
gameId: z.string().min(1),
|
||||
title: z.string().min(1).max(120),
|
||||
description: z.string().max(1000).optional().default(''),
|
||||
isPublic: z.boolean().default(false),
|
||||
groups: z.array(
|
||||
z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1).max(16),
|
||||
itemIds: z.array(z.string()),
|
||||
})
|
||||
),
|
||||
pool: z.array(
|
||||
z.object({
|
||||
id: z.string().min(1),
|
||||
src: z.string().min(1),
|
||||
label: z.string().min(1).max(60),
|
||||
origin: z.enum(['game', 'custom']).default('game'),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
router.get('/public', async (req, res) => {
|
||||
const gameId = req.query.gameId
|
||||
const lists = await listPublicTierLists(gameId)
|
||||
res.json({ tierLists: lists })
|
||||
})
|
||||
|
||||
router.get('/me', requireAuth, async (req, res) => {
|
||||
const lists = await listUserTierLists(req.session.userId)
|
||||
res.json({ tierLists: lists })
|
||||
})
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
const t = await findTierListById(req.params.id)
|
||||
if (!t) return res.status(404).json({ error: 'not_found' })
|
||||
if (!t.isPublic) {
|
||||
if (!req.session || req.session.userId !== t.authorId) return res.status(403).json({ error: 'forbidden' })
|
||||
}
|
||||
res.json({ tierList: normalizeTierList(t) })
|
||||
})
|
||||
|
||||
router.post('/custom-items', requireAuth, upload.single('image'), async (req, res) => {
|
||||
if (!req.file) return res.status(400).json({ error: 'file_required' })
|
||||
|
||||
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 item = await createCustomItem({
|
||||
id: nanoid(),
|
||||
ownerId: req.session.userId,
|
||||
src: `/uploads/custom/${req.file.filename}`,
|
||||
label: parsed.data.label,
|
||||
})
|
||||
|
||||
res.json({ item })
|
||||
})
|
||||
|
||||
router.post('/', requireAuth, async (req, res) => {
|
||||
const parsed = tierListUpsertSchema.safeParse(req.body)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
const payload = parsed.data
|
||||
const normalizedPool = payload.pool.map(normalizePoolItem)
|
||||
|
||||
let existing = null
|
||||
if (payload.id) existing = await findTierListById(payload.id)
|
||||
|
||||
if (existing) {
|
||||
if (existing.authorId !== req.session.userId) return res.status(403).json({ error: 'forbidden' })
|
||||
const updated = await saveTierList({
|
||||
id: existing.id,
|
||||
authorId: existing.authorId,
|
||||
gameId: existing.gameId,
|
||||
title: payload.title,
|
||||
description: payload.description || '',
|
||||
isPublic: !!payload.isPublic,
|
||||
groups: payload.groups,
|
||||
pool: normalizedPool,
|
||||
})
|
||||
return res.json({ tierList: normalizeTierList(updated) })
|
||||
}
|
||||
|
||||
const created = await saveTierList({
|
||||
id: nanoid(),
|
||||
authorId: req.session.userId,
|
||||
gameId: payload.gameId,
|
||||
title: payload.title,
|
||||
description: payload.description || '',
|
||||
isPublic: !!payload.isPublic,
|
||||
groups: payload.groups,
|
||||
pool: normalizedPool,
|
||||
})
|
||||
res.json({ tierList: normalizeTierList(created) })
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
Reference in New Issue
Block a user