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