HEX
Server: Apache/2.4.65 (Debian)
System: Linux kubikelcreative 5.10.0-35-amd64 #1 SMP Debian 5.10.237-1 (2025-05-19) x86_64
User: www-data (33)
PHP: 8.4.13
Disabled: NONE
Upload Files
File: /var/www/indoadvisory_new/web2/webapp/routes/clients.js
const express = require('express');
const { body } = require('express-validator');
const { db } = require('../config/database');
const { logAuditEvent } = require('../middleware/auth');
const { 
  handleAPIValidation, 
  upload, 
  handleUploadError,
  uploadRateLimit 
} = require('../middleware/security');

const router = express.Router();

// Get all clients (API endpoint)
router.get('/', async (req, res) => {
  try {
    const { featured, showcase, active = 'true', limit, offset } = req.query;
    
    let whereConditions = [];
    let params = [];
    let paramCount = 0;
    
    if (active !== 'all') {
      paramCount++;
      whereConditions.push(`is_active = $${paramCount}`);
      params.push(active === 'true');
    }
    
    if (featured !== undefined) {
      paramCount++;
      whereConditions.push(`is_featured = $${paramCount}`);
      params.push(featured === 'true');
    }
    
    if (showcase !== undefined) {
      paramCount++;
      whereConditions.push(`is_showcase = $${paramCount}`);
      params.push(showcase === 'true');
    }
    
    const whereClause = whereConditions.length > 0 
      ? `WHERE ${whereConditions.join(' AND ')}`
      : '';
    
    let limitClause = '';
    if (limit) {
      paramCount++;
      limitClause += ` LIMIT $${paramCount}`;
      params.push(parseInt(limit));
      
      if (offset) {
        paramCount++;
        limitClause += ` OFFSET $${paramCount}`;
        params.push(parseInt(offset));
      }
    }
    
    const clients = await db.query(`
      SELECT 
        id, company_name, industry_en, industry_id, logo_url, website_url,
        project_title_en, project_title_id, project_description_en, project_description_id,
        project_value, project_year, project_status, case_study_en, case_study_id,
        testimonial_en, testimonial_id, testimonial_author, testimonial_position,
        display_order, is_featured, is_showcase, is_active, created_at, updated_at
      FROM clients
      ${whereClause}
      ORDER BY display_order ASC, created_at DESC
      ${limitClause}
    `, params);
    
    res.json({
      success: true,
      clients: clients.rows
    });
    
  } catch (error) {
    console.error('Get clients error:', error);
    res.status(500).json({
      success: false,
      message: 'Error retrieving clients'
    });
  }
});

// Get single client
router.get('/:id', async (req, res) => {
  try {
    const { id } = req.params;
    
    const result = await db.query(`
      SELECT *
      FROM clients
      WHERE id = $1
    `, [id]);
    
    if (result.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Client not found'
      });
    }
    
    res.json({
      success: true,
      client: result.rows[0]
    });
    
  } catch (error) {
    console.error('Get client error:', error);
    res.status(500).json({
      success: false,
      message: 'Error retrieving client'
    });
  }
});

// Create new client
router.post('/',
  uploadRateLimit,
  upload.single('logo'),
  handleUploadError,
  [
    body('company_name')
      .trim()
      .isLength({ min: 2, max: 255 })
      .withMessage('Company name must be between 2 and 255 characters'),
    
    body('industry_en')
      .optional()
      .trim()
      .isLength({ max: 255 })
      .withMessage('Industry (EN) must be less than 255 characters'),
    
    body('industry_id')
      .optional()
      .trim()
      .isLength({ max: 255 })
      .withMessage('Industry (ID) must be less than 255 characters'),
    
    body('project_title_en')
      .optional()
      .trim()
      .isLength({ max: 255 })
      .withMessage('Project title (EN) must be less than 255 characters'),
    
    body('project_title_id')
      .optional()
      .trim()
      .isLength({ max: 255 })
      .withMessage('Project title (ID) must be less than 255 characters'),
    
    body('project_value')
      .optional()
      .isFloat({ min: 0 })
      .withMessage('Project value must be a positive number'),
    
    body('project_year')
      .optional()
      .isInt({ min: 1900, max: new Date().getFullYear() + 10 })
      .withMessage('Project year must be a valid year'),
    
    body('project_status')
      .optional()
      .isIn(['completed', 'ongoing', 'planned'])
      .withMessage('Project status must be completed, ongoing, or planned'),
    
    body('website_url')
      .optional()
      .isURL()
      .withMessage('Website URL must be valid'),
    
    body('display_order')
      .optional()
      .isInt({ min: 0 })
      .withMessage('Display order must be a non-negative integer')
  ],
  handleAPIValidation,
  async (req, res) => {
    try {
      const {
        company_name, industry_en, industry_id, website_url,
        project_title_en, project_title_id, project_description_en, project_description_id,
        project_value, project_year, project_status = 'completed',
        case_study_en, case_study_id, testimonial_en, testimonial_id,
        testimonial_author, testimonial_position, display_order = 0,
        is_featured = false, is_showcase = true
      } = req.body;
      
      const logo_url = req.file ? `/uploads/${req.file.filename}` : null;
      
      const result = await db.query(`
        INSERT INTO clients (
          company_name, industry_en, industry_id, logo_url, website_url,
          project_title_en, project_title_id, project_description_en, project_description_id,
          project_value, project_year, project_status, case_study_en, case_study_id,
          testimonial_en, testimonial_id, testimonial_author, testimonial_position,
          display_order, is_featured, is_showcase
        ) VALUES (
          $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21
        ) RETURNING id
      `, [
        company_name, industry_en, industry_id, logo_url, website_url,
        project_title_en, project_title_id, project_description_en, project_description_id,
        project_value ? parseFloat(project_value) : null,
        project_year ? parseInt(project_year) : null,
        project_status, case_study_en, case_study_id, testimonial_en, testimonial_id,
        testimonial_author, testimonial_position, parseInt(display_order),
        is_featured === 'true' || is_featured === true,
        is_showcase === 'true' || is_showcase === true
      ]);
      
      const clientId = result.rows[0].id;
      
      await logAuditEvent(req.user.id, 'client_created', 'client', clientId, req, { company_name });
      
      res.json({
        success: true,
        message: 'Client created successfully',
        client_id: clientId
      });
      
    } catch (error) {
      console.error('Create client error:', error);
      res.status(500).json({
        success: false,
        message: 'Error creating client'
      });
    }
  }
);

// Update client
router.put('/:id',
  uploadRateLimit,
  upload.single('logo'),
  handleUploadError,
  [
    body('company_name')
      .optional()
      .trim()
      .isLength({ min: 2, max: 255 })
      .withMessage('Company name must be between 2 and 255 characters'),
    
    body('project_value')
      .optional()
      .isFloat({ min: 0 })
      .withMessage('Project value must be a positive number'),
    
    body('project_year')
      .optional()
      .isInt({ min: 1900, max: new Date().getFullYear() + 10 })
      .withMessage('Project year must be a valid year'),
    
    body('project_status')
      .optional()
      .isIn(['completed', 'ongoing', 'planned'])
      .withMessage('Project status must be completed, ongoing, or planned'),
    
    body('website_url')
      .optional()
      .isURL()
      .withMessage('Website URL must be valid'),
    
    body('display_order')
      .optional()
      .isInt({ min: 0 })
      .withMessage('Display order must be a non-negative integer')
  ],
  handleAPIValidation,
  async (req, res) => {
    try {
      const { id } = req.params;
      
      // Check if client exists
      const existingClient = await db.query('SELECT * FROM clients WHERE id = $1', [id]);
      
      if (existingClient.rows.length === 0) {
        return res.status(404).json({
          success: false,
          message: 'Client not found'
        });
      }
      
      const updateFields = [];
      const updateValues = [];
      let paramCount = 0;
      
      // Build dynamic update query
      for (const [key, value] of Object.entries(req.body)) {
        if (value !== undefined && value !== '') {
          paramCount++;
          updateFields.push(`${key} = $${paramCount}`);
          
          if (key === 'project_value') {
            updateValues.push(parseFloat(value));
          } else if (key === 'project_year' || key === 'display_order') {
            updateValues.push(parseInt(value));
          } else if (key === 'is_featured' || key === 'is_showcase' || key === 'is_active') {
            updateValues.push(value === 'true' || value === true);
          } else {
            updateValues.push(value);
          }
        }
      }
      
      // Handle file upload
      if (req.file) {
        paramCount++;
        updateFields.push(`logo_url = $${paramCount}`);
        updateValues.push(`/uploads/${req.file.filename}`);
      }
      
      // Add updated_at timestamp
      paramCount++;
      updateFields.push(`updated_at = $${paramCount}`);
      updateValues.push(new Date());
      
      // Add WHERE clause parameter
      paramCount++;
      updateValues.push(id);
      
      if (updateFields.length === 1) { // Only updated_at was added
        return res.status(400).json({
          success: false,
          message: 'No valid fields to update'
        });
      }
      
      await db.query(`
        UPDATE clients 
        SET ${updateFields.join(', ')}
        WHERE id = $${paramCount}
      `, updateValues);
      
      await logAuditEvent(req.user.id, 'client_updated', 'client', id, req, req.body);
      
      res.json({
        success: true,
        message: 'Client updated successfully'
      });
      
    } catch (error) {
      console.error('Update client error:', error);
      res.status(500).json({
        success: false,
        message: 'Error updating client'
      });
    }
  }
);

// Delete client
router.delete('/:id', async (req, res) => {
  try {
    const { id } = req.params;
    
    // Check if client exists
    const existingClient = await db.query(
      'SELECT company_name FROM clients WHERE id = $1', 
      [id]
    );
    
    if (existingClient.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Client not found'
      });
    }
    
    await db.query('DELETE FROM clients WHERE id = $1', [id]);
    
    await logAuditEvent(req.user.id, 'client_deleted', 'client', id, req, {
      company_name: existingClient.rows[0].company_name
    });
    
    res.json({
      success: true,
      message: 'Client deleted successfully'
    });
    
  } catch (error) {
    console.error('Delete client error:', error);
    res.status(500).json({
      success: false,
      message: 'Error deleting client'
    });
  }
});

// Toggle client featured status
router.post('/:id/featured', async (req, res) => {
  try {
    const { id } = req.params;
    
    const result = await db.query(`
      UPDATE clients 
      SET is_featured = NOT is_featured, updated_at = CURRENT_TIMESTAMP
      WHERE id = $1
      RETURNING is_featured, company_name
    `, [id]);
    
    if (result.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Client not found'
      });
    }
    
    const { is_featured, company_name } = result.rows[0];
    
    await logAuditEvent(req.user.id, 'client_featured_toggled', 'client', id, req, {
      company_name,
      is_featured
    });
    
    res.json({
      success: true,
      message: `Client ${is_featured ? 'featured' : 'unfeatured'} successfully`,
      is_featured
    });
    
  } catch (error) {
    console.error('Toggle featured error:', error);
    res.status(500).json({
      success: false,
      message: 'Error updating client'
    });
  }
});

// Reorder clients
router.post('/reorder', 
  [
    body('clients')
      .isArray()
      .withMessage('Clients must be an array'),
    body('clients.*.id')
      .isUUID()
      .withMessage('Each client must have a valid ID'),
    body('clients.*.display_order')
      .isInt({ min: 0 })
      .withMessage('Each client must have a valid display order')
  ],
  handleAPIValidation,
  async (req, res) => {
    try {
      const { clients } = req.body;
      
      // Use transaction for atomic updates
      await db.transaction(async (client) => {
        for (const clientData of clients) {
          await client.query(
            'UPDATE clients SET display_order = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
            [clientData.display_order, clientData.id]
          );
        }
      });
      
      await logAuditEvent(req.user.id, 'clients_reordered', 'client', null, req, {
        count: clients.length
      });
      
      res.json({
        success: true,
        message: 'Clients reordered successfully'
      });
      
    } catch (error) {
      console.error('Reorder clients error:', error);
      res.status(500).json({
        success: false,
        message: 'Error reordering clients'
      });
    }
  }
);

module.exports = router;