import { Router } from 'express'; import { prisma } from '../config/database.js'; import { authenticate } from '../middleware/auth.js'; import { upload } from '../middleware/upload.js'; import { validate } from '../middleware/validate.js'; import { hashPassword, comparePassword } from '../utils/password.js'; import { AppError } from '../middleware/errorHandler.js'; import { updateProfileSchema, updateSettingsSchema, deleteAccountSchema } from '../validators/user.js'; const router = Router(); const userSelect = { id: true, email: true, fullName: true, nickname: true, avatar: true, phone: true, location: true, bio: true, rating: true, ratingCount: true, showEmail: true, showPhone: true, showLocation: true, showOnline: true, showRating: true, createdAt: true, }; // --- Avatar upload --- router.post('/avatar', authenticate, upload.single('avatar'), async (req, res, next) => { try { const file = req.file; if (!file) throw new AppError(400, 'No file uploaded'); const user = await prisma.user.update({ where: { id: req.userId }, data: { avatar: `/uploads/${file.filename}` }, select: userSelect, }); res.json(user); } catch (error) { next(error); } }); // --- Settings --- router.get('/settings', authenticate, async (req, res, next) => { try { const user = await prisma.user.findUnique({ where: { id: req.userId }, select: { showEmail: true, showPhone: true, showLocation: true, showOnline: true, showRating: true, notifNewOffer: true, notifMessages: true, notifItemSold: true, notifFavorites: true, notifEmail: true, marketingEmail: true, twoFactorEnabled: true, }, }); if (!user) throw new AppError(404, 'User not found'); res.json(user); } catch (error) { next(error); } }); router.put('/settings', authenticate, validate(updateSettingsSchema), async (req, res, next) => { try { const user = await prisma.user.update({ where: { id: req.userId }, data: req.body, select: { showEmail: true, showPhone: true, showLocation: true, showOnline: true, showRating: true, notifNewOffer: true, notifMessages: true, notifItemSold: true, notifFavorites: true, notifEmail: true, marketingEmail: true, twoFactorEnabled: true, }, }); res.json(user); } catch (error) { next(error); } }); // --- Sessions --- router.get('/sessions', authenticate, async (req, res, next) => { try { const sessions = await prisma.session.findMany({ where: { userId: req.userId }, select: { id: true, userAgent: true, ipAddress: true, createdAt: true, expiresAt: true }, orderBy: { createdAt: 'desc' }, }); res.json(sessions); } catch (error) { next(error); } }); // --- Account deletion --- router.delete('/account', authenticate, validate(deleteAccountSchema), async (req, res, next) => { try { const { password } = req.body; const user = await prisma.user.findUnique({ where: { id: req.userId } }); if (!user) throw new AppError(404, 'User not found'); const valid = await comparePassword(password, user.passwordHash); if (!valid) throw new AppError(400, 'Password is incorrect'); await prisma.session.deleteMany({ where: { userId: req.userId } }); await prisma.user.update({ where: { id: req.userId }, data: { isActive: false }, }); res.clearCookie('refreshToken'); res.json({ message: 'Account deactivated' }); } catch (error) { next(error); } }); // --- Profile --- router.get('/profile', authenticate, async (req, res, next) => { try { const user = await prisma.user.findUnique({ where: { id: req.userId }, select: userSelect }); if (!user) throw new AppError(404, 'User not found'); res.json(user); } catch (error) { next(error); } }); router.put('/profile', authenticate, validate(updateProfileSchema), async (req, res, next) => { try { const user = await prisma.user.update({ where: { id: req.userId }, data: req.body, select: userSelect, }); res.json(user); } catch (error) { next(error); } }); // --- Password --- router.put('/password', authenticate, async (req, res, next) => { try { const { currentPassword, newPassword } = req.body; if (!currentPassword || !newPassword) throw new AppError(400, 'Both current and new passwords are required'); if (newPassword.length < 8) throw new AppError(400, 'New password must be at least 8 characters'); const user = await prisma.user.findUnique({ where: { id: req.userId } }); if (!user) throw new AppError(404, 'User not found'); const valid = await comparePassword(currentPassword, user.passwordHash); if (!valid) throw new AppError(400, 'Current password is incorrect'); const passwordHash = await hashPassword(newPassword); await prisma.user.update({ where: { id: req.userId }, data: { passwordHash } }); res.json({ message: 'Password updated' }); } catch (error) { next(error); } }); // --- Block/Unblock --- router.post('/:id/block', authenticate, async (req, res, next) => { try { if (req.params.id === req.userId) throw new AppError(400, 'Cannot block yourself'); const target = await prisma.user.findUnique({ where: { id: req.params.id } }); if (!target) throw new AppError(404, 'User not found'); const existing = await prisma.blockedUser.findUnique({ where: { blockerId_blockedId: { blockerId: req.userId!, blockedId: req.params.id } }, }); if (existing) throw new AppError(409, 'User already blocked'); await prisma.blockedUser.create({ data: { blockerId: req.userId!, blockedId: req.params.id }, }); res.json({ message: 'User blocked' }); } catch (error) { next(error); } }); router.delete('/:id/block', authenticate, async (req, res, next) => { try { const existing = await prisma.blockedUser.findUnique({ where: { blockerId_blockedId: { blockerId: req.userId!, blockedId: req.params.id } }, }); if (!existing) throw new AppError(404, 'Block not found'); await prisma.blockedUser.delete({ where: { id: existing.id } }); res.json({ message: 'User unblocked' }); } catch (error) { next(error); } }); // --- Public profile (must be LAST due to /:id param) --- router.get('/:id', async (req, res, next) => { try { const user = await prisma.user.findUnique({ where: { id: req.params.id }, select: { ...userSelect, _count: { select: { listings: { where: { status: 'ACTIVE' } } } }, }, }); if (!user || !(await prisma.user.findUnique({ where: { id: req.params.id, isActive: true } }))) { throw new AppError(404, 'User not found'); } // Enforce privacy flags const publicUser: Record = { ...user }; if (!user.showEmail) delete publicUser.email; if (!user.showPhone) delete publicUser.phone; if (!user.showLocation) delete publicUser.location; if (!user.showRating) { delete publicUser.rating; delete publicUser.ratingCount; } res.json(publicUser); } catch (error) { next(error); } }); export default router;