릴리스: v0.1.11 관리자 레이아웃과 자유 티어표 흐름 정리

This commit is contained in:
2026-03-19 16:20:06 +09:00
parent f9ae036890
commit c71a19873d
11 changed files with 268 additions and 230 deletions

View File

@@ -6,6 +6,7 @@ const DB_USER = process.env.DB_USER || 'root'
const DB_PASSWORD = process.env.DB_PASSWORD || ''
const DB_NAME = process.env.DB_NAME || 'tier_cursor'
const DB_CONNECTION_LIMIT = process.env.DB_CONNECTION_LIMIT ? Number(process.env.DB_CONNECTION_LIMIT) : 10
const FREEFORM_GAME_ID = 'freeform'
let poolPromise = null
let initPromise = null
@@ -182,16 +183,17 @@ async function ensureSchema() {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
await query(`
CREATE TABLE IF NOT EXISTS game_suggestions (
id VARCHAR(64) PRIMARY KEY,
name VARCHAR(120) NOT NULL,
created_at BIGINT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
await query(
`
INSERT INTO games (id, name, thumbnail_src, created_at)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE name = VALUES(name)
`,
[FREEFORM_GAME_ID, '직접 티어표 만들기', '', now()]
)
const countRows = await query('SELECT COUNT(*) AS count FROM games')
if (Number(countRows[0]?.count || 0) === 0) {
if (Number(countRows[0]?.count || 0) <= 1) {
const createdAt = now()
await query(
`
@@ -278,7 +280,10 @@ async function updateUserProfile({ id, nickname, avatarSrc }) {
}
async function listGames() {
const rows = await query('SELECT id, name, thumbnail_src, created_at FROM games ORDER BY created_at ASC, name ASC')
const rows = await query(
'SELECT id, name, thumbnail_src, created_at FROM games WHERE id <> ? ORDER BY created_at ASC, name ASC',
[FREEFORM_GAME_ID]
)
return rows.map(mapGameRow)
}
@@ -373,10 +378,33 @@ async function createCustomItem({ id, ownerId, src, label }) {
return { id, ownerId, src, label, origin: 'custom', createdAt }
}
async function createGameSuggestion({ id, name }) {
const createdAt = now()
await query('INSERT INTO game_suggestions (id, name, created_at) VALUES (?, ?, ?)', [id, name, createdAt])
return { id, name, createdAt }
async function listCustomItems() {
const rows = await query(
`
SELECT
c.id,
c.owner_id,
c.src,
c.label,
c.created_at,
u.nickname,
u.email
FROM custom_items c
INNER JOIN users u ON u.id = c.owner_id
ORDER BY c.created_at DESC
LIMIT 200
`
)
return rows.map((row) => ({
id: row.id,
ownerId: row.owner_id,
src: row.src,
label: row.label,
createdAt: Number(row.created_at),
ownerName: row.nickname || row.email,
ownerEmail: row.email,
}))
}
async function listPublicTierLists(gameId) {
@@ -498,7 +526,7 @@ module.exports = {
deleteGameItem,
deleteGame,
createCustomItem,
createGameSuggestion,
listCustomItems,
listPublicTierLists,
listUserTierLists,
findTierListById,

View File

@@ -10,6 +10,7 @@ const {
createGameItem,
deleteGameItem,
deleteGame,
listCustomItems,
} = require('../db')
const { requireAdmin } = require('../middleware/auth')
@@ -77,4 +78,9 @@ router.delete('/games/:gameId', requireAdmin, async (req, res) => {
res.json({ ok: true })
})
router.get('/custom-items', requireAdmin, async (req, res) => {
const items = await listCustomItems()
res.json({ items })
})
module.exports = router

View File

@@ -1,7 +1,5 @@
const express = require('express')
const { z } = require('zod')
const { nanoid } = require('nanoid')
const { listGames, getGameDetail, createGameSuggestion } = require('../db')
const { listGames, getGameDetail } = require('../db')
const router = express.Router()
@@ -16,16 +14,4 @@ router.get('/:gameId', async (req, res) => {
res.json({ game: detail.game, items: detail.items })
})
router.post('/suggest', async (req, res) => {
const schema = z.object({ name: z.string().min(1).max(60) })
const parsed = schema.safeParse(req.body)
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
const suggestion = await createGameSuggestion({
id: nanoid(),
name: parsed.data.name,
})
res.json({ suggestion })
})
module.exports = router