릴리스: v0.1.23 홈 게임 정렬과 관리자 순서 관리 추가
This commit is contained in:
@@ -46,6 +46,7 @@ function mapGameRow(row) {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
thumbnailSrc: row.thumbnail_src || '',
|
||||
displayRank: row.display_rank == null ? null : Number(row.display_rank),
|
||||
createdAt: Number(row.created_at),
|
||||
}
|
||||
}
|
||||
@@ -154,10 +155,16 @@ async function ensureSchema() {
|
||||
id VARCHAR(120) PRIMARY KEY,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
thumbnail_src VARCHAR(255) NOT NULL DEFAULT '',
|
||||
display_rank INT NULL DEFAULT NULL,
|
||||
created_at BIGINT NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
`)
|
||||
|
||||
const displayRankColumns = await query("SHOW COLUMNS FROM games LIKE 'display_rank'")
|
||||
if (!displayRankColumns.length) {
|
||||
await query('ALTER TABLE games ADD COLUMN display_rank INT NULL DEFAULT NULL AFTER thumbnail_src')
|
||||
}
|
||||
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS game_items (
|
||||
id VARCHAR(64) PRIMARY KEY,
|
||||
@@ -326,14 +333,23 @@ async function adminDeleteUser(id) {
|
||||
|
||||
async function listGames() {
|
||||
const rows = await query(
|
||||
'SELECT id, name, thumbnail_src, created_at FROM games WHERE id <> ? ORDER BY created_at ASC, name ASC',
|
||||
`
|
||||
SELECT id, name, thumbnail_src, display_rank, created_at
|
||||
FROM games
|
||||
WHERE id <> ?
|
||||
ORDER BY
|
||||
CASE WHEN display_rank IS NULL THEN 1 ELSE 0 END ASC,
|
||||
display_rank ASC,
|
||||
created_at DESC,
|
||||
name ASC
|
||||
`,
|
||||
[FREEFORM_GAME_ID]
|
||||
)
|
||||
return rows.map(mapGameRow)
|
||||
}
|
||||
|
||||
async function findGameById(id) {
|
||||
const rows = await query('SELECT id, name, thumbnail_src, created_at FROM games WHERE id = ? LIMIT 1', [id])
|
||||
const rows = await query('SELECT id, name, thumbnail_src, display_rank, created_at FROM games WHERE id = ? LIMIT 1', [id])
|
||||
return mapGameRow(rows[0])
|
||||
}
|
||||
|
||||
@@ -353,7 +369,13 @@ async function getGameDetail(gameId) {
|
||||
}
|
||||
|
||||
async function createGame({ id, name }) {
|
||||
await query('INSERT INTO games (id, name, thumbnail_src, created_at) VALUES (?, ?, ?, ?)', [id, name, '', now()])
|
||||
await query('INSERT INTO games (id, name, thumbnail_src, display_rank, created_at) VALUES (?, ?, ?, ?, ?)', [
|
||||
id,
|
||||
name,
|
||||
'',
|
||||
null,
|
||||
now(),
|
||||
])
|
||||
return findGameById(id)
|
||||
}
|
||||
|
||||
@@ -411,6 +433,20 @@ async function deleteGame(gameId) {
|
||||
await query('DELETE FROM games WHERE id = ?', [gameId])
|
||||
}
|
||||
|
||||
async function updateGameDisplayOrder(gameIds) {
|
||||
const normalizedIds = Array.from(new Set((gameIds || []).filter((id) => id && id !== FREEFORM_GAME_ID))).slice(0, 50)
|
||||
|
||||
await query('UPDATE games SET display_rank = NULL WHERE id <> ?', [FREEFORM_GAME_ID])
|
||||
|
||||
await Promise.all(
|
||||
normalizedIds.map((gameId, index) =>
|
||||
query('UPDATE games SET display_rank = ? WHERE id = ? AND id <> ?', [index + 1, gameId, FREEFORM_GAME_ID])
|
||||
)
|
||||
)
|
||||
|
||||
return listGames()
|
||||
}
|
||||
|
||||
async function createCustomItem({ id, ownerId, src, label }) {
|
||||
const createdAt = now()
|
||||
await query('INSERT INTO custom_items (id, owner_id, src, label, created_at) VALUES (?, ?, ?, ?, ?)', [
|
||||
@@ -721,6 +757,7 @@ module.exports = {
|
||||
createGameItem,
|
||||
deleteGameItem,
|
||||
deleteGame,
|
||||
updateGameDisplayOrder,
|
||||
createCustomItem,
|
||||
listCustomItems,
|
||||
findUnusedCustomItems,
|
||||
|
||||
@@ -9,10 +9,12 @@ const {
|
||||
findUserById,
|
||||
findGameById,
|
||||
createGame,
|
||||
listGames,
|
||||
updateGameThumbnail,
|
||||
createGameItem,
|
||||
deleteGameItem,
|
||||
deleteGame,
|
||||
updateGameDisplayOrder,
|
||||
listCustomItems,
|
||||
findUnusedCustomItems,
|
||||
findCustomItemsByIds,
|
||||
@@ -50,6 +52,20 @@ router.post('/games', requireAdmin, async (req, res) => {
|
||||
res.json({ game })
|
||||
})
|
||||
|
||||
router.patch('/games/display-order', requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
gameIds: z.array(z.string().min(1)).max(50),
|
||||
})
|
||||
const parsed = schema.safeParse(req.body)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
|
||||
const games = await listGames()
|
||||
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 })
|
||||
})
|
||||
|
||||
router.post('/games/:gameId/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)
|
||||
|
||||
Reference in New Issue
Block a user