const express = require('express') const bcrypt = require('bcryptjs') const { z } = require('zod') const { nanoid } = require('nanoid') const multer = require('multer') const { countUsers, findUserByEmail, findUserById, createUser, updateUserProfile, findPrimaryAdminUser, } = require('../db') const { requireAuth } = require('../middleware/auth') const { createMemoryUpload, writeOptimizedImage } = require('../lib/image-storage') const router = express.Router() 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), removeAvatar: z.union([z.string(), z.undefined()]).optional(), }) function establishSession(req, user) { return new Promise((resolve, reject) => { req.session.regenerate((regenerateError) => { if (regenerateError) return reject(regenerateError) req.session.userId = user.id req.session.isAdmin = !!user.isAdmin req.session.save((saveError) => { if (saveError) return reject(saveError) resolve() }) }) }) } async function serializeUser(user) { if (!user) return null const primaryAdmin = await findPrimaryAdminUser() const isPrimaryAdmin = !!user.isAdmin && primaryAdmin?.id === user.id return { id: user.id, email: user.email, nickname: user.nickname || '', isAdmin: !!user.isAdmin, isPrimaryAdmin, isOperator: !!user.isAdmin && !isPrimaryAdmin, role: isPrimaryAdmin ? 'owner' : user.isAdmin ? 'operator' : 'user', avatarSrc: user.avatarSrc || '', createdAt: user.createdAt, } } 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 }) try { await establishSession(req, user) res.json(await serializeUser(user)) } catch (err) { return res.status(500).json({ error: 'session_save_failed' }) } }) 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' }) try { await establishSession(req, user) res.json(await serializeUser(user)) } catch (err) { return res.status(500).json({ error: 'session_save_failed' }) } }) 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: await serializeUser(user) }) }) router.get('/meta', async (req, res) => { res.json({ hasUsers: (await countUsers()) > 0 }) }) const upload = createMemoryUpload(multer, { fileSize: 4 * 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 optimized = req.file ? await writeOptimizedImage({ file: req.file, directory: 'avatars', width: 512, height: 512, fit: 'cover', quality: 82, }) : null const shouldRemoveAvatar = parsed.data.removeAvatar === '1' const nextAvatarSrc = shouldRemoveAvatar ? '' : optimized?.src || user.avatarSrc || '' const updated = await updateUserProfile({ id: user.id, nickname: parsed.data.nickname, avatarSrc: nextAvatarSrc, }) res.json({ user: await serializeUser(updated) }) }) module.exports = router