릴리스: v0.1.12 작성 권한과 회원 관리 보강

This commit is contained in:
2026-03-19 16:44:50 +09:00
parent c71a19873d
commit f6a031cfe4
14 changed files with 372 additions and 26 deletions

View File

@@ -279,6 +279,27 @@ async function updateUserProfile({ id, nickname, avatarSrc }) {
return findUserById(id)
}
async function listUsers() {
const rows = await query(
'SELECT id, email, nickname, is_admin, avatar_src, created_at FROM users ORDER BY created_at ASC, email ASC'
)
return rows.map(mapUserRow)
}
async function adminUpdateUser({ id, email, nickname, isAdmin }) {
await query('UPDATE users SET email = ?, nickname = ?, is_admin = ? WHERE id = ?', [
email,
nickname || '',
isAdmin ? 1 : 0,
id,
])
return findUserById(id)
}
async function adminDeleteUser(id) {
await query('DELETE FROM users WHERE id = ?', [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',
@@ -516,6 +537,9 @@ module.exports = {
findUserById,
createUser,
updateUserProfile,
listUsers,
adminUpdateUser,
adminDeleteUser,
listGames,
findGameById,
listGameItems,

View File

@@ -4,6 +4,7 @@ const multer = require('multer')
const { z } = require('zod')
const { nanoid } = require('nanoid')
const {
findUserById,
findGameById,
createGame,
updateGameThumbnail,
@@ -11,6 +12,9 @@ const {
deleteGameItem,
deleteGame,
listCustomItems,
listUsers,
adminUpdateUser,
adminDeleteUser,
} = require('../db')
const { requireAdmin } = require('../middleware/auth')
@@ -83,4 +87,53 @@ router.get('/custom-items', requireAdmin, async (req, res) => {
res.json({ items })
})
router.get('/users', requireAdmin, async (req, res) => {
const users = await listUsers()
res.json({ users })
})
router.patch('/users/:userId', requireAdmin, async (req, res) => {
const schema = z.object({
email: z.string().email(),
nickname: z.string().trim().max(40).default(''),
isAdmin: z.boolean(),
})
const parsed = schema.safeParse(req.body)
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
if (req.params.userId === req.session.userId && !parsed.data.isAdmin) {
return res.status(400).json({ error: 'self_admin_required' })
}
const user = await findUserById(req.params.userId)
if (!user) return res.status(404).json({ error: 'not_found' })
try {
const updated = await adminUpdateUser({
id: user.id,
email: parsed.data.email,
nickname: parsed.data.nickname,
isAdmin: parsed.data.isAdmin,
})
res.json({ user: updated })
} catch (e) {
if (e && e.code === 'ER_DUP_ENTRY') {
return res.status(409).json({ error: 'email_taken' })
}
throw e
}
})
router.delete('/users/:userId', requireAdmin, async (req, res) => {
if (req.params.userId === req.session.userId) {
return res.status(400).json({ error: 'cannot_delete_self' })
}
const user = await findUserById(req.params.userId)
if (!user) return res.status(404).json({ error: 'not_found' })
await adminDeleteUser(user.id)
res.json({ ok: true })
})
module.exports = router