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;