const express = require('express'); const router = express.Router(); const Template = require('../models/template'); const Feature = require('../models/feature'); const database = require('../config/database'); // GET /api/admin/templates - Get all templates for admin management router.get('/', async (req, res) => { try { console.log('🔧 [ADMIN-TEMPLATES] Fetching all templates for admin management...'); const limit = parseInt(req.query.limit) || 50; const offset = parseInt(req.query.offset) || 0; const category = req.query.category || null; const search = req.query.search || null; console.log('📋 [ADMIN-TEMPLATES] Query parameters:', { limit, offset, category, search }); // Build the query with optional filters let whereClause = 'WHERE t.is_active = true AND t.type != \'_migration_test\''; let queryParams = []; let paramIndex = 1; if (category && category !== 'all') { whereClause += ` AND t.category = $${paramIndex}`; queryParams.push(category); paramIndex++; } if (search) { whereClause += ` AND (LOWER(t.title) LIKE LOWER($${paramIndex}) OR LOWER(t.description) LIKE LOWER($${paramIndex + 1}))`; queryParams.push(`%${search}%`, `%${search}%`); paramIndex += 2; } // Add pagination const limitClause = `LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`; queryParams.push(limit, offset); const query = ` SELECT t.*, COUNT(tf.id) as feature_count, AVG(tf.user_rating) as avg_rating FROM templates t LEFT JOIN template_features tf ON t.id = tf.template_id ${whereClause} GROUP BY t.id ORDER BY t.created_at DESC, t.title ${limitClause} `; console.log('🔍 [ADMIN-TEMPLATES] Executing query:', query); console.log('📊 [ADMIN-TEMPLATES] Query params:', queryParams); const result = await database.query(query, queryParams); const templates = result.rows.map(row => ({ id: row.id, type: row.type, title: row.title, description: row.description, icon: row.icon, category: row.category, gradient: row.gradient, border: row.border, text: row.text, subtext: row.subtext, is_active: row.is_active, created_at: row.created_at, updated_at: row.updated_at, feature_count: parseInt(row.feature_count) || 0, avg_rating: parseFloat(row.avg_rating) || 0 })); // Get total count for pagination let countWhereClause = 'WHERE is_active = true AND type != \'_migration_test\''; let countParams = []; let countParamIndex = 1; if (category && category !== 'all') { countWhereClause += ` AND category = $${countParamIndex}`; countParams.push(category); countParamIndex++; } if (search) { countWhereClause += ` AND (LOWER(title) LIKE LOWER($${countParamIndex}) OR LOWER(description) LIKE LOWER($${countParamIndex + 1}))`; countParams.push(`%${search}%`, `%${search}%`); } const countQuery = `SELECT COUNT(*) as total FROM templates ${countWhereClause}`; const countResult = await database.query(countQuery, countParams); const total = parseInt(countResult.rows[0].total); console.log('✅ [ADMIN-TEMPLATES] Found templates:', { returned: templates.length, total, hasMore: offset + templates.length < total }); res.json({ success: true, data: templates, pagination: { total, limit, offset, hasMore: offset + templates.length < total }, message: `Found ${templates.length} templates for admin management` }); } catch (error) { console.error('❌ [ADMIN-TEMPLATES] Error fetching templates:', error.message); res.status(500).json({ success: false, error: 'Failed to fetch admin templates', message: error.message }); } }); // GET /api/admin/templates/stats - Get template statistics for admin router.get('/stats', async (req, res) => { try { console.log('📊 [ADMIN-TEMPLATES] Fetching template statistics...'); const statsQuery = ` SELECT COUNT(*) as total_templates, COUNT(DISTINCT category) as total_categories, AVG(feature_counts.feature_count) as avg_features_per_template, COUNT(CASE WHEN feature_counts.feature_count = 0 THEN 1 END) as templates_without_features, COUNT(CASE WHEN feature_counts.feature_count > 0 THEN 1 END) as templates_with_features FROM ( SELECT t.id, t.category, COUNT(tf.id) as feature_count FROM templates t LEFT JOIN template_features tf ON t.id = tf.template_id WHERE t.is_active = true AND t.type != '_migration_test' GROUP BY t.id, t.category ) feature_counts `; const categoryStatsQuery = ` SELECT category, COUNT(*) as template_count, AVG(feature_counts.feature_count) as avg_features FROM ( SELECT t.id, t.category, COUNT(tf.id) as feature_count FROM templates t LEFT JOIN template_features tf ON t.id = tf.template_id WHERE t.is_active = true AND t.type != '_migration_test' GROUP BY t.id, t.category ) feature_counts GROUP BY category ORDER BY template_count DESC `; const [statsResult, categoryStatsResult] = await Promise.all([ database.query(statsQuery), database.query(categoryStatsQuery) ]); const stats = { ...statsResult.rows[0], avg_features_per_template: parseFloat(statsResult.rows[0].avg_features_per_template) || 0, categories: categoryStatsResult.rows.map(row => ({ category: row.category, template_count: parseInt(row.template_count), avg_features: parseFloat(row.avg_features) || 0 })) }; console.log('✅ [ADMIN-TEMPLATES] Template statistics:', stats); res.json({ success: true, data: stats, message: 'Template statistics retrieved successfully' }); } catch (error) { console.error('❌ [ADMIN-TEMPLATES] Error fetching template stats:', error.message); res.status(500).json({ success: false, error: 'Failed to fetch template statistics', message: error.message }); } }); // GET /api/admin/templates/:id/features - Get features for a template router.get('/:id/features', async (req, res) => { try { const { id } = req.params; console.log('🔍 [ADMIN-TEMPLATES] Fetching features for template:', id); // Validate template exists const template = await Template.getByIdWithFeatures(id); if (!template) { return res.status(404).json({ success: false, error: 'Template not found', message: `Template with ID ${id} does not exist` }); } // Get features for the template with business rules const featuresQuery = ` SELECT tf.*, fbr.business_rules as stored_business_rules FROM template_features tf LEFT JOIN feature_business_rules fbr ON CAST(tf.id AS TEXT) = fbr.feature_id WHERE tf.template_id = $1 ORDER BY tf.display_order, tf.created_at `; const result = await database.query(featuresQuery, [id]); const features = result.rows.map(row => ({ id: row.id, template_id: row.template_id, feature_id: row.feature_id, name: row.name, description: row.description, feature_type: row.feature_type || 'suggested', complexity: row.complexity || 'medium', display_order: row.display_order, usage_count: row.usage_count || 0, user_rating: row.user_rating || 0, is_default: row.is_default || false, created_by_user: row.created_by_user || false, created_at: row.created_at, updated_at: row.updated_at, business_rules: row.stored_business_rules || row.business_rules, technical_requirements: row.stored_business_rules || row.technical_requirements })); console.log('✅ [ADMIN-TEMPLATES] Found features:', features.length); res.json({ success: true, data: features, message: `Found ${features.length} features for template '${template.title}'` }); } catch (error) { console.error('❌ [ADMIN-TEMPLATES] Error fetching template features:', error); console.error('❌ [ADMIN-TEMPLATES] Full error stack:', error.stack); res.status(500).json({ success: false, error: 'Failed to fetch template features', message: error.message, details: error.stack }); } }); // POST /api/admin/templates/:id/features - Add feature to template router.post('/:id/features', async (req, res) => { try { const { id } = req.params; const featureData = req.body; console.log('➕ [ADMIN-TEMPLATES] Adding feature to template:', id); console.log('📋 [ADMIN-TEMPLATES] Feature data:', featureData); // Validate template exists const template = await Template.getByIdWithFeatures(id); if (!template) { return res.status(404).json({ success: false, error: 'Template not found', message: `Template with ID ${id} does not exist` }); } // Use Feature.create() method to ensure business rules are stored const displayOrder = template.features ? template.features.length + 1 : 1; const feature = await Feature.create({ template_id: id, name: featureData.name, description: featureData.description || '', feature_type: featureData.feature_type || 'custom', complexity: featureData.complexity || 'medium', display_order: displayOrder, is_default: featureData.is_default || false, created_by_user: featureData.created_by_user || true, logic_rules: featureData.logic_rules, business_rules: featureData.business_rules }); console.log('✅ [ADMIN-TEMPLATES] Feature created:', feature.id); res.status(201).json({ success: true, data: feature, message: `Feature '${feature.name}' added to template '${template.title}'` }); } catch (error) { console.error('❌ [ADMIN-TEMPLATES] Error adding feature:', error.message); res.status(500).json({ success: false, error: 'Failed to add feature to template', message: error.message }); } }); // PUT /api/admin/templates/:templateId/features/:featureId - Update feature router.put('/:templateId/features/:featureId', async (req, res) => { try { const { templateId, featureId } = req.params; const updateData = req.body; console.log('✏️ [ADMIN-TEMPLATES] Updating feature:', featureId, 'in template:', templateId); console.log('📦 [ADMIN-TEMPLATES] Raw request body:', JSON.stringify(req.body, null, 2)); console.log('📦 [ADMIN-TEMPLATES] Request headers:', req.headers['content-type']); console.log('📦 [ADMIN-TEMPLATES] Content-Length:', req.headers['content-length']); console.log('📦 [ADMIN-TEMPLATES] Request method:', req.method); console.log('📦 [ADMIN-TEMPLATES] Request URL:', req.url); console.log('📦 [ADMIN-TEMPLATES] Update data keys:', Object.keys(updateData || {})); console.log('📦 [ADMIN-TEMPLATES] Body type:', typeof req.body); console.log('📦 [ADMIN-TEMPLATES] Body constructor:', req.body?.constructor?.name); // Validate template exists const template = await Template.getByIdWithFeatures(templateId); if (!template) { return res.status(404).json({ success: false, error: 'Template not found', message: `Template with ID ${templateId} does not exist` }); } // Update the feature in template_features table const updateQuery = ` UPDATE template_features SET name = $1, description = $2, complexity = $3, updated_at = NOW() WHERE id = $4 AND template_id = $5 RETURNING * `; // Validate required fields if (!updateData.name || updateData.name.trim() === '') { console.error('❌ [ADMIN-TEMPLATES] Validation failed: Feature name is required'); return res.status(400).json({ success: false, error: 'Validation failed', message: 'Feature name is required', received_data: updateData }); } console.log('📝 [ADMIN-TEMPLATES] Update data received:', JSON.stringify(updateData, null, 2)); const result = await database.query(updateQuery, [ updateData.name.trim(), updateData.description || '', updateData.complexity || 'medium', featureId, templateId ]); // Update or insert business rules in feature_business_rules table if (updateData.business_rules) { const businessRulesQuery = ` INSERT INTO feature_business_rules (template_id, feature_id, business_rules) VALUES ($1, $2, $3) ON CONFLICT (template_id, feature_id) DO UPDATE SET business_rules = $3, updated_at = NOW() `; // Convert business_rules to JSON string if it's an array/object const businessRulesData = typeof updateData.business_rules === 'string' ? updateData.business_rules : JSON.stringify(updateData.business_rules); await database.query(businessRulesQuery, [templateId, featureId, businessRulesData]); } if (result.rows.length === 0) { return res.status(404).json({ success: false, error: 'Feature not found', message: `Feature with ID ${featureId} does not exist in template ${templateId}` }); } const updatedFeature = result.rows[0]; console.log('✅ [ADMIN-TEMPLATES] Feature updated:', updatedFeature.id); res.json({ success: true, data: updatedFeature, message: `Feature '${updatedFeature.name}' updated successfully` }); } catch (error) { console.error('❌ [ADMIN-TEMPLATES] Error updating feature:', error); console.error('❌ [ADMIN-TEMPLATES] Full error stack:', error.stack); res.status(500).json({ success: false, error: 'Failed to update feature', message: error.message, details: error.stack }); } }); // DELETE /api/admin/templates/:templateId/features/:featureId - Remove feature router.delete('/:templateId/features/:featureId', async (req, res) => { try { const { templateId, featureId } = req.params; console.log('🗑️ [ADMIN-TEMPLATES] Removing feature:', featureId, 'from template:', templateId); // Validate template exists const template = await Template.getByIdWithFeatures(templateId); if (!template) { return res.status(404).json({ success: false, error: 'Template not found', message: `Template with ID ${templateId} does not exist` }); } // Delete the feature from template_features table const deleteQuery = ` DELETE FROM template_features WHERE id = $1 AND template_id = $2 RETURNING id `; const result = await database.query(deleteQuery, [featureId, templateId]); if (result.rows.length === 0) { return res.status(404).json({ success: false, error: 'Feature not found', message: `Feature with ID ${featureId} does not exist in template ${templateId}` }); } console.log('✅ [ADMIN-TEMPLATES] Feature deleted:', featureId); res.json({ success: true, message: 'Feature removed successfully' }); } catch (error) { console.error('❌ [ADMIN-TEMPLATES] Error removing feature:', error.message); res.status(500).json({ success: false, error: 'Failed to remove feature', message: error.message }); } }); // POST /api/admin/templates/:id/features/bulk - Bulk add features to template router.post('/:id/features/bulk', async (req, res) => { try { const { id } = req.params; const { features } = req.body; console.log('📦 [ADMIN-TEMPLATES] Bulk adding features to template:', id); console.log('📋 [ADMIN-TEMPLATES] Features count:', features?.length || 0); if (!features || !Array.isArray(features) || features.length === 0) { return res.status(400).json({ success: false, error: 'Invalid features data', message: 'Features array is required and must not be empty' }); } // Validate template exists const template = await Template.getByIdWithFeatures(id); if (!template) { return res.status(404).json({ success: false, error: 'Template not found', message: `Template with ID ${id} does not exist` }); } // Create all features in template_features table const createdFeatures = []; let displayOrder = template.features ? template.features.length + 1 : 1; for (const featureData of features) { try { // Use Feature.create() method to ensure business rules are stored const feature = await Feature.create({ template_id: id, name: featureData.name, description: featureData.description || '', feature_type: featureData.feature_type || 'custom', complexity: featureData.complexity || 'medium', display_order: displayOrder++, is_default: featureData.is_default || false, created_by_user: featureData.created_by_user || true, logic_rules: featureData.logic_rules, business_rules: featureData.business_rules }); createdFeatures.push(feature); } catch (featureError) { console.error('⚠️ [ADMIN-TEMPLATES] Error creating feature:', featureData.name, featureError.message); // Continue with other features instead of failing completely } } console.log('✅ [ADMIN-TEMPLATES] Bulk features created:', createdFeatures.length, 'out of', features.length); res.status(201).json({ success: true, data: createdFeatures, message: `${createdFeatures.length} features added to template '${template.title}'` }); } catch (error) { console.error('❌ [ADMIN-TEMPLATES] Error bulk adding features:', error.message); res.status(500).json({ success: false, error: 'Failed to bulk add features', message: error.message }); } }); module.exports = router;