codenuk_backend_mine/services/template-manager/src/routes/admin-templates.js
2025-10-10 08:56:39 +05:30

545 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;