225 lines
6.6 KiB
JavaScript
225 lines
6.6 KiB
JavaScript
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; |