Initial Claude Run
This commit is contained in:
225
backend/src/routes/users.js
Normal file
225
backend/src/routes/users.js
Normal file
@@ -0,0 +1,225 @@
|
||||
const express = require('express');
|
||||
const pool = require('../config/database');
|
||||
const { validateRequest, validateParams } = require('../utils/validation');
|
||||
const { updateUserSchema, idParamSchema } = require('../utils/validation');
|
||||
const { requireOwnership } = require('../middleware/auth');
|
||||
const { AppError } = require('../middleware/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// @route GET /api/users/profile
|
||||
// @desc Get current user profile
|
||||
// @access Private
|
||||
router.get('/profile', async (req, res, next) => {
|
||||
try {
|
||||
const userResult = await pool.query(
|
||||
`SELECT u.id, u.email, u.first_name, u.last_name, u.role, u.created_at, u.updated_at,
|
||||
COUNT(p.id) as property_count
|
||||
FROM users u
|
||||
LEFT JOIN properties p ON u.id = p.user_id
|
||||
WHERE u.id = $1
|
||||
GROUP BY u.id`,
|
||||
[req.user.id]
|
||||
);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
throw new AppError('User not found', 404);
|
||||
}
|
||||
|
||||
const user = userResult.rows[0];
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
role: user.role,
|
||||
propertyCount: parseInt(user.property_count),
|
||||
createdAt: user.created_at,
|
||||
updatedAt: user.updated_at
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// @route PUT /api/users/profile
|
||||
// @desc Update current user profile
|
||||
// @access Private
|
||||
router.put('/profile', validateRequest(updateUserSchema), async (req, res, next) => {
|
||||
try {
|
||||
const { firstName, lastName, email } = req.body;
|
||||
const userId = req.user.id;
|
||||
|
||||
// Check if email is already taken by another user
|
||||
if (email) {
|
||||
const emailCheck = await pool.query(
|
||||
'SELECT id FROM users WHERE email = $1 AND id != $2',
|
||||
[email.toLowerCase(), userId]
|
||||
);
|
||||
|
||||
if (emailCheck.rows.length > 0) {
|
||||
throw new AppError('Email is already taken by another user', 409);
|
||||
}
|
||||
}
|
||||
|
||||
// Build dynamic update query
|
||||
const updates = [];
|
||||
const values = [];
|
||||
let paramCount = 1;
|
||||
|
||||
if (firstName) {
|
||||
updates.push(`first_name = $${paramCount}`);
|
||||
values.push(firstName);
|
||||
paramCount++;
|
||||
}
|
||||
|
||||
if (lastName) {
|
||||
updates.push(`last_name = $${paramCount}`);
|
||||
values.push(lastName);
|
||||
paramCount++;
|
||||
}
|
||||
|
||||
if (email) {
|
||||
updates.push(`email = $${paramCount}`);
|
||||
values.push(email.toLowerCase());
|
||||
paramCount++;
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
throw new AppError('No valid fields to update', 400);
|
||||
}
|
||||
|
||||
updates.push(`updated_at = CURRENT_TIMESTAMP`);
|
||||
values.push(userId);
|
||||
|
||||
const query = `
|
||||
UPDATE users
|
||||
SET ${updates.join(', ')}
|
||||
WHERE id = $${paramCount}
|
||||
RETURNING id, email, first_name, last_name, role, updated_at
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, values);
|
||||
const user = result.rows[0];
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Profile updated successfully',
|
||||
data: {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
role: user.role,
|
||||
updatedAt: user.updated_at
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// @route DELETE /api/users/account
|
||||
// @desc Delete current user account
|
||||
// @access Private
|
||||
router.delete('/account', async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
|
||||
// Check if user has any ongoing applications
|
||||
const ongoingApps = await pool.query(
|
||||
`SELECT COUNT(*) as count
|
||||
FROM application_plans
|
||||
WHERE user_id = $1 AND status IN ('planned', 'in_progress')`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (parseInt(ongoingApps.rows[0].count) > 0) {
|
||||
throw new AppError('Cannot delete account with ongoing applications. Please complete or cancel them first.', 400);
|
||||
}
|
||||
|
||||
// Delete user (cascading will handle related records)
|
||||
await pool.query('DELETE FROM users WHERE id = $1', [userId]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Account deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// @route GET /api/users/stats
|
||||
// @desc Get user statistics
|
||||
// @access Private
|
||||
router.get('/stats', async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
|
||||
const statsQuery = `
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM properties WHERE user_id = $1) as total_properties,
|
||||
(SELECT COUNT(*) FROM lawn_sections ls
|
||||
JOIN properties p ON ls.property_id = p.id
|
||||
WHERE p.user_id = $1) as total_sections,
|
||||
(SELECT COALESCE(SUM(ls.area), 0) FROM lawn_sections ls
|
||||
JOIN properties p ON ls.property_id = p.id
|
||||
WHERE p.user_id = $1) as total_area,
|
||||
(SELECT COUNT(*) FROM user_equipment WHERE user_id = $1) as total_equipment,
|
||||
(SELECT COUNT(*) FROM application_plans WHERE user_id = $1) as total_plans,
|
||||
(SELECT COUNT(*) FROM application_logs WHERE user_id = $1) as total_applications,
|
||||
(SELECT COUNT(*) FROM application_plans
|
||||
WHERE user_id = $1 AND status = 'completed') as completed_plans,
|
||||
(SELECT COUNT(*) FROM application_plans
|
||||
WHERE user_id = $1 AND planned_date >= CURRENT_DATE) as upcoming_plans
|
||||
`;
|
||||
|
||||
const result = await pool.query(statsQuery, [userId]);
|
||||
const stats = result.rows[0];
|
||||
|
||||
// Get recent activity
|
||||
const recentActivity = await pool.query(
|
||||
`SELECT 'application' as type, al.application_date as date,
|
||||
ls.name as section_name, p.name as property_name
|
||||
FROM application_logs al
|
||||
JOIN lawn_sections ls ON al.lawn_section_id = ls.id
|
||||
JOIN properties p ON ls.property_id = p.id
|
||||
WHERE al.user_id = $1
|
||||
ORDER BY al.application_date DESC
|
||||
LIMIT 5`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
stats: {
|
||||
totalProperties: parseInt(stats.total_properties),
|
||||
totalSections: parseInt(stats.total_sections),
|
||||
totalArea: parseFloat(stats.total_area),
|
||||
totalEquipment: parseInt(stats.total_equipment),
|
||||
totalPlans: parseInt(stats.total_plans),
|
||||
totalApplications: parseInt(stats.total_applications),
|
||||
completedPlans: parseInt(stats.completed_plans),
|
||||
upcomingPlans: parseInt(stats.upcoming_plans),
|
||||
completionRate: stats.total_plans > 0 ?
|
||||
Math.round((stats.completed_plans / stats.total_plans) * 100) : 0
|
||||
},
|
||||
recentActivity: recentActivity.rows
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user