122 lines
3.8 KiB
JavaScript
122 lines
3.8 KiB
JavaScript
const express = require('express')
|
|
const path = require('path')
|
|
const bcrypt = require('bcryptjs')
|
|
const { z } = require('zod')
|
|
const { nanoid } = require('nanoid')
|
|
const multer = require('multer')
|
|
const {
|
|
countUsers,
|
|
findUserByEmail,
|
|
findUserById,
|
|
createUser,
|
|
updateUserProfile,
|
|
} = require('../db')
|
|
const { requireAuth } = require('../middleware/auth')
|
|
|
|
const router = express.Router()
|
|
|
|
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 signupSchema = z.object({
|
|
email: z.string().email(),
|
|
password: z.string().min(6),
|
|
})
|
|
|
|
const profileSchema = z.object({
|
|
nickname: z.string().trim().min(1).max(40),
|
|
})
|
|
|
|
router.post('/signup', async (req, res) => {
|
|
const parsed = signupSchema.safeParse(req.body)
|
|
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
|
|
|
const { email, password } = parsed.data
|
|
const exists = await findUserByEmail(email)
|
|
if (exists) return res.status(409).json({ error: 'email_taken' })
|
|
|
|
const passwordHash = await bcrypt.hash(password, 10)
|
|
const isAdmin = (await countUsers()) === 0
|
|
const user = await createUser({ id: nanoid(), email, nickname: '', passwordHash, isAdmin })
|
|
|
|
req.session.userId = user.id
|
|
req.session.isAdmin = !!user.isAdmin
|
|
// 세션을 응답 전에 명시적으로 저장해 Set-Cookie가 확실히 내려오도록 보강
|
|
req.session.save((err) => {
|
|
if (err) return res.status(500).json({ error: 'session_save_failed' })
|
|
res.json(user)
|
|
})
|
|
})
|
|
|
|
router.post('/login', async (req, res) => {
|
|
const parsed = signupSchema.safeParse(req.body)
|
|
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
|
|
|
const { email, password } = parsed.data
|
|
const user = await findUserByEmail(email)
|
|
if (!user) return res.status(401).json({ error: 'invalid_credentials' })
|
|
|
|
const ok = await bcrypt.compare(password, user.passwordHash)
|
|
if (!ok) return res.status(401).json({ error: 'invalid_credentials' })
|
|
|
|
req.session.userId = user.id
|
|
req.session.isAdmin = !!user.isAdmin
|
|
req.session.save((err) => {
|
|
if (err) return res.status(500).json({ error: 'session_save_failed' })
|
|
res.json({
|
|
id: user.id,
|
|
email: user.email,
|
|
nickname: user.nickname || '',
|
|
isAdmin: !!user.isAdmin,
|
|
avatarSrc: user.avatarSrc || '',
|
|
createdAt: user.createdAt,
|
|
})
|
|
})
|
|
})
|
|
|
|
router.post('/logout', async (req, res) => {
|
|
if (!req.session) return res.json({ ok: true })
|
|
req.session.destroy(() => res.json({ ok: true }))
|
|
})
|
|
|
|
router.get('/me', async (req, res) => {
|
|
if (!req.session || !req.session.userId) return res.json({ user: null })
|
|
const user = await findUserById(req.session.userId)
|
|
if (!user) return res.json({ user: null })
|
|
res.json({ user })
|
|
})
|
|
|
|
router.get('/meta', async (req, res) => {
|
|
res.json({ hasUsers: (await countUsers()) > 0 })
|
|
})
|
|
|
|
const upload = multer({
|
|
storage: multer.diskStorage({
|
|
destination: (req, file, cb) => cb(null, path.join(__dirname, '..', '..', 'uploads', 'avatars')),
|
|
filename: (req, file, cb) => cb(null, buildUploadFilename(file)),
|
|
}),
|
|
limits: { fileSize: 3 * 1024 * 1024 },
|
|
})
|
|
|
|
router.post('/profile', requireAuth, upload.single('avatar'), async (req, res) => {
|
|
const parsed = profileSchema.safeParse(req.body)
|
|
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
|
|
|
const user = await findUserById(req.session.userId)
|
|
if (!user) return res.status(404).json({ error: 'not_found' })
|
|
|
|
const nextAvatarSrc = req.file ? `/uploads/avatars/${req.file.filename}` : user.avatarSrc || ''
|
|
const updated = await updateUserProfile({
|
|
id: user.id,
|
|
nickname: parsed.data.nickname,
|
|
avatarSrc: nextAvatarSrc,
|
|
})
|
|
|
|
res.json({ user: updated })
|
|
})
|
|
|
|
module.exports = router
|