const express = require('express'); const { User, UserAddress } = require('../models'); // Import from models/index.js to get models with associations const { authenticateToken } = require('../middleware/auth'); const logger = require('../utils/logger'); const userService = require('../services/UserService'); const { validateS3Keys } = require('../utils/s3KeyValidator'); const { IMAGE_LIMITS } = require('../config/imageLimits'); const router = express.Router(); // Allowed fields for profile update (prevents mass assignment) const ALLOWED_PROFILE_FIELDS = [ 'firstName', 'lastName', 'email', 'phone', 'address1', 'address2', 'city', 'state', 'zipCode', 'country', 'imageFilename', 'itemRequestNotificationRadius', ]; /** * Extract only allowed fields from request body */ function extractAllowedProfileFields(body) { const result = {}; for (const field of ALLOWED_PROFILE_FIELDS) { if (body[field] !== undefined) { result[field] = body[field]; } } return result; } router.get('/profile', authenticateToken, async (req, res, next) => { try { const user = await User.findByPk(req.user.id, { attributes: { exclude: ['password'] } }); const reqLogger = logger.withRequestId(req.id); reqLogger.info("User profile fetched", { userId: req.user.id }); res.json(user); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("User profile fetch failed", { error: error.message, stack: error.stack, userId: req.user.id }); next(error); } }); // Address routes (must come before /:id route) router.get('/addresses', authenticateToken, async (req, res, next) => { try { const addresses = await UserAddress.findAll({ where: { userId: req.user.id }, order: [['isPrimary', 'DESC'], ['createdAt', 'ASC']] }); const reqLogger = logger.withRequestId(req.id); reqLogger.info("User addresses fetched", { userId: req.user.id, addressCount: addresses.length }); res.json(addresses); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("User addresses fetch failed", { error: error.message, stack: error.stack, userId: req.user.id }); next(error); } }); router.post('/addresses', authenticateToken, async (req, res, next) => { try { const address = await userService.createUserAddress(req.user.id, req.body); res.status(201).json(address); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("User address creation failed", { error: error.message, stack: error.stack, userId: req.user.id, addressData: logger.sanitize(req.body) }); next(error); } }); router.put('/addresses/:id', authenticateToken, async (req, res, next) => { try { const address = await userService.updateUserAddress(req.user.id, req.params.id, req.body); res.json(address); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("User address update failed", { error: error.message, stack: error.stack, userId: req.user.id, addressId: req.params.id }); if (error.message === 'Address not found') { return res.status(404).json({ error: 'Address not found' }); } next(error); } }); router.delete('/addresses/:id', authenticateToken, async (req, res, next) => { try { await userService.deleteUserAddress(req.user.id, req.params.id); res.status(204).send(); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("User address deletion failed", { error: error.message, stack: error.stack, userId: req.user.id, addressId: req.params.id }); if (error.message === 'Address not found') { return res.status(404).json({ error: 'Address not found' }); } next(error); } }); // User availability routes (must come before /:id route) router.get('/availability', authenticateToken, async (req, res, next) => { try { const user = await User.findByPk(req.user.id, { attributes: ['defaultAvailableAfter', 'defaultAvailableBefore', 'defaultSpecifyTimesPerDay', 'defaultWeeklyTimes'] }); res.json({ generalAvailableAfter: user.defaultAvailableAfter, generalAvailableBefore: user.defaultAvailableBefore, specifyTimesPerDay: user.defaultSpecifyTimesPerDay, weeklyTimes: user.defaultWeeklyTimes }); } catch (error) { next(error); } }); router.put('/availability', authenticateToken, async (req, res, next) => { try { const { generalAvailableAfter, generalAvailableBefore, specifyTimesPerDay, weeklyTimes } = req.body; await User.update({ defaultAvailableAfter: generalAvailableAfter, defaultAvailableBefore: generalAvailableBefore, defaultSpecifyTimesPerDay: specifyTimesPerDay, defaultWeeklyTimes: weeklyTimes }, { where: { id: req.user.id } }); res.json({ message: 'Availability updated successfully' }); } catch (error) { next(error); } }); router.get('/:id', async (req, res, next) => { try { const user = await User.findByPk(req.params.id, { attributes: { exclude: ['password', 'email', 'phone', 'address'] } }); if (!user) { return res.status(404).json({ error: 'User not found' }); } const reqLogger = logger.withRequestId(req.id); reqLogger.info("Public user profile fetched", { requestedUserId: req.params.id }); res.json(user); } catch (error) { const reqLogger = logger.withRequestId(req.id); reqLogger.error("Public user profile fetch failed", { error: error.message, stack: error.stack, requestedUserId: req.params.id }); next(error); } }); router.put('/profile', authenticateToken, async (req, res, next) => { try { // Extract only allowed fields (prevents mass assignment) const allowedData = extractAllowedProfileFields(req.body); // Validate imageFilename if provided if (allowedData.imageFilename !== undefined && allowedData.imageFilename !== null) { const keyValidation = validateS3Keys([allowedData.imageFilename], 'profiles', { maxKeys: IMAGE_LIMITS.profile }); if (!keyValidation.valid) { return res.status(400).json({ error: keyValidation.error, details: keyValidation.invalidKeys }); } } // Use UserService to handle update and email notification const updatedUser = await userService.updateProfile(req.user.id, allowedData); res.json(updatedUser); } catch (error) { console.error('Profile update error:', error); next(error); } }); module.exports = router;