릴리스: v0.1.38 아이템 이름 수정과 티어표 썸네일 추가
This commit is contained in:
@@ -24,7 +24,7 @@ const allowedOrigins = (process.env.CORS_ORIGINS || '')
|
||||
|
||||
const FileStore = FileStoreFactory(session)
|
||||
|
||||
;['uploads/avatars', 'uploads/games', 'uploads/custom', '.sessions'].forEach((relativePath) => {
|
||||
;['uploads/avatars', 'uploads/games', 'uploads/custom', 'uploads/tierlists', '.sessions'].forEach((relativePath) => {
|
||||
fs.mkdirSync(path.join(__dirname, relativePath), { recursive: true })
|
||||
})
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ function mapTierListRow(row) {
|
||||
authorAvatarSrc: row.avatar_src || '',
|
||||
gameId: row.game_id,
|
||||
title: row.title,
|
||||
thumbnailSrc: row.thumbnail_src || '',
|
||||
description: row.description || '',
|
||||
isPublic: !!row.is_public,
|
||||
groups: parseJson(row.groups_json, []),
|
||||
@@ -195,6 +196,7 @@ async function ensureSchema() {
|
||||
author_id VARCHAR(64) NOT NULL,
|
||||
game_id VARCHAR(120) NOT NULL,
|
||||
title VARCHAR(120) NOT NULL,
|
||||
thumbnail_src VARCHAR(255) NOT NULL DEFAULT '',
|
||||
description TEXT NOT NULL,
|
||||
is_public TINYINT(1) NOT NULL DEFAULT 0,
|
||||
groups_json LONGTEXT NOT NULL,
|
||||
@@ -209,6 +211,11 @@ async function ensureSchema() {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
`)
|
||||
|
||||
const tierListThumbnailColumns = await query("SHOW COLUMNS FROM tierlists LIKE 'thumbnail_src'")
|
||||
if (!tierListThumbnailColumns.length) {
|
||||
await query("ALTER TABLE tierlists ADD COLUMN thumbnail_src VARCHAR(255) NOT NULL DEFAULT '' AFTER title")
|
||||
}
|
||||
|
||||
await query(
|
||||
`
|
||||
INSERT INTO games (id, name, thumbnail_src, created_at)
|
||||
@@ -397,6 +404,12 @@ async function createGameItem({ id, gameId, src, label }) {
|
||||
return mapGameItemRow(rows[0])
|
||||
}
|
||||
|
||||
async function updateGameItemLabel(itemId, label) {
|
||||
await query('UPDATE game_items SET label = ? WHERE id = ?', [label, itemId])
|
||||
const rows = await query('SELECT id, game_id, src, label, created_at FROM game_items WHERE id = ? LIMIT 1', [itemId])
|
||||
return mapGameItemRow(rows[0])
|
||||
}
|
||||
|
||||
async function deleteGameItem(itemId) {
|
||||
const gameItemRows = await query('SELECT game_id FROM game_items WHERE id = ? LIMIT 1', [itemId])
|
||||
const gameId = gameItemRows[0]?.game_id
|
||||
@@ -588,6 +601,7 @@ async function listPublicTierLists(gameId) {
|
||||
t.id,
|
||||
t.game_id,
|
||||
t.title,
|
||||
t.thumbnail_src,
|
||||
t.created_at,
|
||||
t.updated_at,
|
||||
t.author_id,
|
||||
@@ -607,6 +621,7 @@ async function listPublicTierLists(gameId) {
|
||||
id: row.id,
|
||||
gameId: row.game_id,
|
||||
title: row.title,
|
||||
thumbnailSrc: row.thumbnail_src || '',
|
||||
createdAt: Number(row.created_at),
|
||||
updatedAt: Number(row.updated_at),
|
||||
authorId: row.author_id,
|
||||
@@ -623,6 +638,7 @@ async function listUserTierLists(userId) {
|
||||
t.id,
|
||||
t.game_id,
|
||||
t.title,
|
||||
t.thumbnail_src,
|
||||
t.created_at,
|
||||
t.updated_at,
|
||||
t.is_public,
|
||||
@@ -641,6 +657,7 @@ async function listUserTierLists(userId) {
|
||||
id: row.id,
|
||||
gameId: row.game_id,
|
||||
title: row.title,
|
||||
thumbnailSrc: row.thumbnail_src || '',
|
||||
createdAt: Number(row.created_at),
|
||||
updatedAt: Number(row.updated_at),
|
||||
isPublic: !!row.is_public,
|
||||
@@ -658,6 +675,7 @@ async function findTierListById(id) {
|
||||
t.author_id,
|
||||
t.game_id,
|
||||
t.title,
|
||||
t.thumbnail_src,
|
||||
t.description,
|
||||
t.is_public,
|
||||
t.groups_json,
|
||||
@@ -708,17 +726,17 @@ async function deleteCustomItems(ids) {
|
||||
await query(`DELETE FROM custom_items WHERE id IN (${placeholders})`, ids)
|
||||
}
|
||||
|
||||
async function saveTierList({ id, authorId, gameId, title, description, isPublic, groups, pool }) {
|
||||
async function saveTierList({ id, authorId, gameId, title, thumbnailSrc = '', description, isPublic, groups, pool }) {
|
||||
const existing = id ? await findTierListById(id) : null
|
||||
|
||||
if (existing) {
|
||||
await query(
|
||||
`
|
||||
UPDATE tierlists
|
||||
SET title = ?, description = ?, is_public = ?, groups_json = ?, pool_json = ?, updated_at = ?
|
||||
SET title = ?, thumbnail_src = ?, description = ?, is_public = ?, groups_json = ?, pool_json = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
`,
|
||||
[title, description || '', isPublic ? 1 : 0, serializeJson(groups), serializeJson(pool), now(), existing.id]
|
||||
[title, thumbnailSrc, description || '', isPublic ? 1 : 0, serializeJson(groups), serializeJson(pool), now(), existing.id]
|
||||
)
|
||||
return findTierListById(existing.id)
|
||||
}
|
||||
@@ -727,11 +745,11 @@ async function saveTierList({ id, authorId, gameId, title, description, isPublic
|
||||
await query(
|
||||
`
|
||||
INSERT INTO tierlists (
|
||||
id, author_id, game_id, title, description, is_public, groups_json, pool_json, created_at, updated_at
|
||||
id, author_id, game_id, title, thumbnail_src, description, is_public, groups_json, pool_json, created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`,
|
||||
[id, authorId, gameId, title, description || '', isPublic ? 1 : 0, serializeJson(groups), serializeJson(pool), createdAt, createdAt]
|
||||
[id, authorId, gameId, title, thumbnailSrc, description || '', isPublic ? 1 : 0, serializeJson(groups), serializeJson(pool), createdAt, createdAt]
|
||||
)
|
||||
return findTierListById(id)
|
||||
}
|
||||
@@ -755,6 +773,7 @@ module.exports = {
|
||||
createGame,
|
||||
updateGameThumbnail,
|
||||
createGameItem,
|
||||
updateGameItemLabel,
|
||||
deleteGameItem,
|
||||
deleteGame,
|
||||
updateGameDisplayOrder,
|
||||
|
||||
@@ -12,6 +12,7 @@ const {
|
||||
listGames,
|
||||
updateGameThumbnail,
|
||||
createGameItem,
|
||||
updateGameItemLabel,
|
||||
deleteGameItem,
|
||||
deleteGame,
|
||||
updateGameDisplayOrder,
|
||||
@@ -116,6 +117,19 @@ router.delete('/games/:gameId/items/:itemId', requireAdmin, async (req, res) =>
|
||||
res.json({ ok: true })
|
||||
})
|
||||
|
||||
router.patch('/games/:gameId/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)
|
||||
if (!game) return res.status(404).json({ error: 'not_found' })
|
||||
|
||||
const updated = await updateGameItemLabel(req.params.itemId, parsed.data.label)
|
||||
if (!updated || updated.gameId !== game.id) return res.status(404).json({ error: 'not_found' })
|
||||
res.json({ item: updated })
|
||||
})
|
||||
|
||||
router.delete('/games/:gameId', requireAdmin, async (req, res) => {
|
||||
const game = await findGameById(req.params.gameId)
|
||||
if (!game) return res.status(404).json({ error: 'not_found' })
|
||||
|
||||
@@ -52,10 +52,19 @@ const upload = multer({
|
||||
limits: { fileSize: 6 * 1024 * 1024 },
|
||||
})
|
||||
|
||||
const thumbnailUpload = multer({
|
||||
storage: multer.diskStorage({
|
||||
destination: (req, file, cb) => cb(null, path.join(__dirname, '..', '..', 'uploads', 'tierlists')),
|
||||
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),
|
||||
thumbnailSrc: z.string().max(255).optional().default(''),
|
||||
description: z.string().max(1000).optional().default(''),
|
||||
isPublic: z.boolean().default(false),
|
||||
groups: z.array(
|
||||
@@ -121,6 +130,11 @@ router.post('/custom-items', requireAuth, upload.single('image'), async (req, re
|
||||
res.json({ item })
|
||||
})
|
||||
|
||||
router.post('/thumbnail', requireAuth, thumbnailUpload.single('thumbnail'), async (req, res) => {
|
||||
if (!req.file) return res.status(400).json({ error: 'file_required' })
|
||||
res.json({ thumbnailSrc: `/uploads/tierlists/${req.file.filename}` })
|
||||
})
|
||||
|
||||
router.post('/', requireAuth, async (req, res) => {
|
||||
const parsed = tierListUpsertSchema.safeParse(req.body)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
@@ -137,6 +151,7 @@ router.post('/', requireAuth, async (req, res) => {
|
||||
authorId: existing.authorId,
|
||||
gameId: existing.gameId,
|
||||
title: payload.title,
|
||||
thumbnailSrc: payload.thumbnailSrc || '',
|
||||
description: payload.description || '',
|
||||
isPublic: !!payload.isPublic,
|
||||
groups: payload.groups,
|
||||
@@ -150,6 +165,7 @@ router.post('/', requireAuth, async (req, res) => {
|
||||
authorId: req.session.userId,
|
||||
gameId: payload.gameId,
|
||||
title: payload.title,
|
||||
thumbnailSrc: payload.thumbnailSrc || '',
|
||||
description: payload.description || '',
|
||||
isPublic: !!payload.isPublic,
|
||||
groups: payload.groups,
|
||||
|
||||
Reference in New Issue
Block a user