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/public.js
const express = require('express');
const { body } = require('express-validator');
const { db } = require('../config/database');
const { getBilingualContent } = require('../middleware/i18n');
const { 
  validateEmail, 
  validateName, 
  validatePhone,
  checkValidationResult,
  createRateLimit
} = require('../middleware/security');

const router = express.Router();

// Contact form rate limiting
const contactRateLimit = createRateLimit(
  60 * 1000, // 1 minute
  3, // 3 submissions per minute
  'Too many contact form submissions. Please wait before submitting again.'
);

// Homepage
router.get('/', async (req, res) => {
  try {
    // Get company settings
    const settingsResult = await db.query(`
      SELECT key, value_en, value_id FROM settings WHERE is_public = true
    `);
    
    const settings = {};
    settingsResult.rows.forEach(row => {
      const value = getBilingualContent(req, row.value_en, row.value_id);
      settings[row.key] = value;
    });
    
    // Get featured team members
    const teamResult = await db.query(`
      SELECT name, position_en, position_id, bio_en, bio_id, avatar_url, linkedin_url
      FROM team_members 
      WHERE is_featured = true AND is_active = true 
      ORDER BY display_order ASC
      LIMIT 3
    `);
    
    const team = teamResult.rows.map(member => ({
      ...member,
      position: getBilingualContent(req, member.position_en, member.position_id),
      bio: getBilingualContent(req, member.bio_en, member.bio_id)
    }));
    
    // Get featured clients for showcase
    const clientsResult = await db.query(`
      SELECT 
        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
      FROM clients 
      WHERE is_featured = true AND is_showcase = true AND is_active = true 
      ORDER BY display_order ASC
      LIMIT 6
    `);
    
    const clients = clientsResult.rows.map(client => ({
      ...client,
      industry: getBilingualContent(req, client.industry_en, client.industry_id),
      project_title: getBilingualContent(req, client.project_title_en, client.project_title_id),
      project_description: getBilingualContent(req, client.project_description_en, client.project_description_id)
    }));
    
    // Get featured articles
    const articlesResult = await db.query(`
      SELECT 
        a.slug, a.title_en, a.title_id, a.summary_en, a.summary_id,
        a.featured_image_url, a.published_at, a.category_en, a.category_id,
        u.name as author_name
      FROM articles a
      LEFT JOIN users u ON a.author_id = u.id
      WHERE a.is_featured = true AND a.is_published = true 
      ORDER BY a.published_at DESC
      LIMIT 3
    `);
    
    const articles = articlesResult.rows.map(article => ({
      ...article,
      title: getBilingualContent(req, article.title_en, article.title_id),
      summary: getBilingualContent(req, article.summary_en, article.summary_id),
      category: getBilingualContent(req, article.category_en, article.category_id)
    }));
    
    res.render('public/index', {
      title: settings.company_name || 'Indo Advisory',
      settings,
      team,
      clients,
      articles
    });
    
  } catch (error) {
    console.error('Homepage error:', error);
    res.render('public/index', {
      title: 'Indo Advisory',
      settings: {},
      team: [],
      clients: [],
      articles: []
    });
  }
});

// About page
router.get('/about', async (req, res) => {
  try {
    // Get company settings
    const settingsResult = await db.query(`
      SELECT key, value_en, value_id FROM settings WHERE is_public = true
    `);
    
    const settings = {};
    settingsResult.rows.forEach(row => {
      const value = getBilingualContent(req, row.value_en, row.value_id);
      settings[row.key] = value;
    });
    
    // Get all active team members
    const teamResult = await db.query(`
      SELECT name, position_en, position_id, bio_en, bio_id, avatar_url, 
             email, linkedin_url, is_featured
      FROM team_members 
      WHERE is_active = true 
      ORDER BY display_order ASC, created_at ASC
    `);
    
    const team = teamResult.rows.map(member => ({
      ...member,
      position: getBilingualContent(req, member.position_en, member.position_id),
      bio: getBilingualContent(req, member.bio_en, member.bio_id)
    }));
    
    res.render('public/about', {
      title: `${res.locals.__('nav_about')} - ${settings.company_name || 'Indo Advisory'}`,
      settings,
      team
    });
    
  } catch (error) {
    console.error('About page error:', error);
    res.render('public/about', {
      title: 'About - Indo Advisory',
      settings: {},
      team: []
    });
  }
});

// Portfolio page
router.get('/portfolio', async (req, res) => {
  try {
    const page = parseInt(req.query.page) || 1;
    const limit = 12;
    const offset = (page - 1) * limit;
    
    // Get all active clients for portfolio
    const clientsResult = await db.query(`
      SELECT 
        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
      FROM clients 
      WHERE is_active = true 
      ORDER BY 
        CASE WHEN is_featured THEN 0 ELSE 1 END,
        display_order ASC, created_at DESC
      LIMIT $1 OFFSET $2
    `, [limit, offset]);
    
    const clients = clientsResult.rows.map(client => ({
      ...client,
      industry: getBilingualContent(req, client.industry_en, client.industry_id),
      project_title: getBilingualContent(req, client.project_title_en, client.project_title_id),
      project_description: getBilingualContent(req, client.project_description_en, client.project_description_id),
      case_study: getBilingualContent(req, client.case_study_en, client.case_study_id),
      testimonial: getBilingualContent(req, client.testimonial_en, client.testimonial_id)
    }));
    
    // Get total count for pagination
    const countResult = await db.query('SELECT COUNT(*) as total FROM clients WHERE is_active = true');
    const totalClients = parseInt(countResult.rows[0].total);
    const totalPages = Math.ceil(totalClients / limit);
    
    res.render('public/portfolio', {
      title: `${res.locals.__('nav_portfolio')} - Indo Advisory`,
      clients,
      currentPage: page,
      totalPages,
      totalClients
    });
    
  } catch (error) {
    console.error('Portfolio page error:', error);
    res.render('public/portfolio', {
      title: 'Portfolio - Indo Advisory',
      clients: [],
      currentPage: 1,
      totalPages: 0,
      totalClients: 0
    });
  }
});

// Insights/Articles page
router.get('/insights', async (req, res) => {
  try {
    const page = parseInt(req.query.page) || 1;
    const category = req.query.category;
    const limit = 10;
    const offset = (page - 1) * limit;
    
    let whereConditions = ['a.is_published = true'];
    let params = [limit, offset];
    let paramCount = 2;
    
    if (category) {
      paramCount++;
      whereConditions.push(`(a.category_en ILIKE $${paramCount} OR a.category_id ILIKE $${paramCount})`);
      params.push(`%${category}%`);
    }
    
    const whereClause = `WHERE ${whereConditions.join(' AND ')}`;
    
    // Get published articles
    const articlesResult = await db.query(`
      SELECT 
        a.slug, a.title_en, a.title_id, a.summary_en, a.summary_id,
        a.featured_image_url, a.published_at, a.category_en, a.category_id,
        a.view_count, a.is_featured,
        u.name as author_name
      FROM articles a
      LEFT JOIN users u ON a.author_id = u.id
      ${whereClause}
      ORDER BY 
        CASE WHEN a.is_featured THEN 0 ELSE 1 END,
        a.published_at DESC
      LIMIT $1 OFFSET $2
    `, params);
    
    const articles = articlesResult.rows.map(article => ({
      ...article,
      title: getBilingualContent(req, article.title_en, article.title_id),
      summary: getBilingualContent(req, article.summary_en, article.summary_id),
      category: getBilingualContent(req, article.category_en, article.category_id)
    }));
    
    // Get total count for pagination
    const countParams = category ? [category] : [];
    const countResult = await db.query(`
      SELECT COUNT(*) as total FROM articles a ${whereClause.replace('LIMIT $1 OFFSET $2', '')}
    `, countParams);
    const totalArticles = parseInt(countResult.rows[0].total);
    const totalPages = Math.ceil(totalArticles / limit);
    
    // Get categories for filter
    const categoriesResult = await db.query(`
      SELECT DISTINCT category_en, category_id 
      FROM articles 
      WHERE is_published = true AND category_en IS NOT NULL
      ORDER BY category_en
    `);
    
    const categories = categoriesResult.rows.map(cat => ({
      value_en: cat.category_en,
      value_id: cat.category_id,
      name: getBilingualContent(req, cat.category_en, cat.category_id)
    }));
    
    res.render('public/insights', {
      title: `${res.locals.__('nav_insights')} - Indo Advisory`,
      articles,
      categories,
      currentPage: page,
      totalPages,
      totalArticles,
      currentCategory: category
    });
    
  } catch (error) {
    console.error('Insights page error:', error);
    res.render('public/insights', {
      title: 'Insights - Indo Advisory',
      articles: [],
      categories: [],
      currentPage: 1,
      totalPages: 0,
      totalArticles: 0,
      currentCategory: null
    });
  }
});

// Single article page
router.get('/insights/:slug', async (req, res) => {
  try {
    const { slug } = req.params;
    
    const articleResult = await db.query(`
      SELECT 
        a.*,
        u.name as author_name
      FROM articles a
      LEFT JOIN users u ON a.author_id = u.id
      WHERE a.slug = $1 AND a.is_published = true
    `, [slug]);
    
    if (articleResult.rows.length === 0) {
      return res.status(404).render('error', {
        title: 'Article Not Found',
        message: 'The article you are looking for does not exist or has been removed.',
        error: { status: 404 }
      });
    }
    
    const article = articleResult.rows[0];
    
    // Increment view count
    await db.query(
      'UPDATE articles SET view_count = view_count + 1 WHERE id = $1',
      [article.id]
    );
    
    // Process bilingual content
    const processedArticle = {
      ...article,
      title: getBilingualContent(req, article.title_en, article.title_id),
      summary: getBilingualContent(req, article.summary_en, article.summary_id),
      content: getBilingualContent(req, article.content_en, article.content_id),
      category: getBilingualContent(req, article.category_en, article.category_id),
      seo_title: getBilingualContent(req, article.seo_title_en, article.seo_title_id),
      seo_description: getBilingualContent(req, article.seo_description_en, article.seo_description_id)
    };
    
    // Get related articles
    const relatedResult = await db.query(`
      SELECT 
        slug, title_en, title_id, summary_en, summary_id,
        featured_image_url, published_at, category_en, category_id
      FROM articles
      WHERE id != $1 AND is_published = true 
        AND (category_en = $2 OR category_id = $3)
      ORDER BY published_at DESC
      LIMIT 3
    `, [article.id, article.category_en, article.category_id]);
    
    const relatedArticles = relatedResult.rows.map(related => ({
      ...related,
      title: getBilingualContent(req, related.title_en, related.title_id),
      summary: getBilingualContent(req, related.summary_en, related.summary_id),
      category: getBilingualContent(req, related.category_en, related.category_id)
    }));
    
    res.render('public/article', {
      title: `${processedArticle.seo_title || processedArticle.title} - Indo Advisory`,
      article: processedArticle,
      relatedArticles
    });
    
  } catch (error) {
    console.error('Article page error:', error);
    res.status(500).render('error', {
      title: 'Error',
      message: 'An error occurred while loading the article.',
      error: {}
    });
  }
});

// Contact page
router.get('/contact', async (req, res) => {
  try {
    // Get company contact settings
    const settingsResult = await db.query(`
      SELECT key, value_en, value_id FROM settings 
      WHERE key IN ('contact_email', 'contact_phone', 'office_address_en') AND is_public = true
    `);
    
    const settings = {};
    settingsResult.rows.forEach(row => {
      const value = getBilingualContent(req, row.value_en, row.value_id);
      settings[row.key] = value;
    });
    
    res.render('public/contact', {
      title: `${res.locals.__('nav_contact')} - Indo Advisory`,
      settings
    });
    
  } catch (error) {
    console.error('Contact page error:', error);
    res.render('public/contact', {
      title: 'Contact - Indo Advisory',
      settings: {}
    });
  }
});

// Contact form submission
router.post('/contact',
  contactRateLimit,
  [
    validateName,
    validateEmail,
    validatePhone,
    body('company')
      .optional()
      .trim()
      .isLength({ max: 255 })
      .withMessage('Company name must be less than 255 characters'),
    body('subject')
      .trim()
      .isLength({ min: 5, max: 500 })
      .withMessage('Subject must be between 5 and 500 characters'),
    body('message')
      .trim()
      .isLength({ min: 10, max: 2000 })
      .withMessage('Message must be between 10 and 2000 characters')
  ],
  checkValidationResult,
  async (req, res) => {
    try {
      const { name, email, phone, company, subject, message } = req.body;
      
      // Get client IP and user agent for security
      const clientIP = req.ip || req.connection.remoteAddress;
      const userAgent = req.get('User-Agent');
      
      // Insert inquiry into database
      await db.query(`
        INSERT INTO inquiries (
          name, email, phone, company, subject, message, 
          inquiry_type, ip_address, user_agent
        ) VALUES ($1, $2, $3, $4, $5, $6, 'general', $7, $8)
      `, [name, email, phone, company, subject, message, clientIP, userAgent]);
      
      req.flash('success', res.locals.__('contact_success'));
      res.redirect('/contact');
      
    } catch (error) {
      console.error('Contact form error:', error);
      req.flash('error', 'An error occurred while sending your message. Please try again.');
      res.redirect('/contact');
    }
  }
);

// Language switcher
router.get('/lang/:lang', (req, res) => {
  const { lang } = req.params;
  const returnTo = req.get('Referer') || '/';
  
  if (['en', 'id'].includes(lang)) {
    req.session.lang = lang;
  }
  
  res.redirect(returnTo);
});

// API endpoints for frontend JavaScript

// Get clients for showcase carousel
router.get('/api/showcase/clients', async (req, res) => {
  try {
    const clientsResult = await db.query(`
      SELECT 
        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
      FROM clients 
      WHERE is_showcase = true AND is_active = true 
      ORDER BY display_order ASC
    `);
    
    const clients = clientsResult.rows.map(client => ({
      company_name: client.company_name,
      industry: getBilingualContent(req, client.industry_en, client.industry_id),
      logo_url: client.logo_url,
      website_url: client.website_url,
      project_title: getBilingualContent(req, client.project_title_en, client.project_title_id),
      project_description: getBilingualContent(req, client.project_description_en, client.project_description_id),
      project_value: client.project_value,
      project_year: client.project_year
    }));
    
    res.json({
      success: true,
      clients
    });
    
  } catch (error) {
    console.error('API showcase clients error:', error);
    res.status(500).json({
      success: false,
      message: 'Error loading clients'
    });
  }
});

module.exports = router;