Files
rentall-app/backend/routes/users.js

266 lines
7.4 KiB
JavaScript

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',
];
// Allowed fields for user address create/update (prevents mass assignment)
const ALLOWED_ADDRESS_FIELDS = [
'address1',
'address2',
'city',
'state',
'zipCode',
'country',
'latitude',
'longitude',
];
/**
* 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;
}
/**
* Extract only allowed address fields from request body
*/
function extractAllowedAddressFields(body) {
const result = {};
for (const field of ALLOWED_ADDRESS_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 {
// Extract only allowed fields (prevents mass assignment)
const allowedData = extractAllowedAddressFields(req.body);
const address = await userService.createUserAddress(req.user.id, allowedData);
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 {
// Extract only allowed fields (prevents mass assignment)
const allowedData = extractAllowedAddressFields(req.body);
const address = await userService.updateUserAddress(req.user.id, req.params.id, allowedData);
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;