From f12b236e46b755459e59a13fd76b89e873603899 Mon Sep 17 00:00:00 2001 From: Chandini Date: Tue, 16 Sep 2025 12:53:13 +0530 Subject: [PATCH] backend changes --- .../002_admin_approval_workflow.sql | 8 ++- .../005_fix_custom_features_foreign_key.sql | 16 ++++- .../src/models/custom_feature.js | 68 +++++++++---------- .../src/models/custom_template.js | 28 ++++++++ .../template-manager/src/routes/features.js | 33 ++++++++- .../template-manager/src/routes/templates.js | 23 ++++++- services/user-auth/src/routes/auth.js | 4 +- 7 files changed, 135 insertions(+), 45 deletions(-) diff --git a/services/template-manager/src/migrations/002_admin_approval_workflow.sql b/services/template-manager/src/migrations/002_admin_approval_workflow.sql index fec8114..af0dcde 100644 --- a/services/template-manager/src/migrations/002_admin_approval_workflow.sql +++ b/services/template-manager/src/migrations/002_admin_approval_workflow.sql @@ -90,9 +90,15 @@ CREATE INDEX IF NOT EXISTS idx_admin_notifications_type ON admin_notifications(t CREATE INDEX IF NOT EXISTS idx_admin_notifications_is_read ON admin_notifications(is_read); CREATE INDEX IF NOT EXISTS idx_admin_notifications_created_at ON admin_notifications(created_at DESC); --- 6. Update existing custom_features to have 'approved' status if they were previously approved +-- 6. Clean up orphaned custom_features records before updating status DO $$ BEGIN + -- Delete custom_features records that reference non-existent templates + DELETE FROM custom_features + WHERE NOT EXISTS (SELECT 1 FROM templates WHERE id = custom_features.template_id AND is_active = true) + AND NOT EXISTS (SELECT 1 FROM custom_templates WHERE id = custom_features.template_id); + + -- Update existing custom_features to have 'approved' status if they were previously approved IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'custom_features' AND column_name = 'status') THEN UPDATE custom_features SET status = CASE diff --git a/services/template-manager/src/migrations/005_fix_custom_features_foreign_key.sql b/services/template-manager/src/migrations/005_fix_custom_features_foreign_key.sql index 3608e04..6932894 100644 --- a/services/template-manager/src/migrations/005_fix_custom_features_foreign_key.sql +++ b/services/template-manager/src/migrations/005_fix_custom_features_foreign_key.sql @@ -12,11 +12,18 @@ ADD COLUMN IF NOT EXISTS template_type VARCHAR(20) DEFAULT 'default' CHECK (temp -- Update existing records to have the correct template_type UPDATE custom_features SET template_type = CASE - WHEN EXISTS (SELECT 1 FROM templates WHERE id = template_id) THEN 'default' + WHEN EXISTS (SELECT 1 FROM templates WHERE id = template_id AND is_active = true) THEN 'default' WHEN EXISTS (SELECT 1 FROM custom_templates WHERE id = template_id) THEN 'custom' ELSE 'default' END -WHERE template_type IS NULL; +WHERE template_type IS NULL OR template_type = ''; + +-- Fix any existing records where template_type is 'custom' but template_id exists in templates table +UPDATE custom_features +SET template_type = 'default' +WHERE template_type = 'custom' +AND EXISTS (SELECT 1 FROM templates WHERE id = template_id AND is_active = true) +AND NOT EXISTS (SELECT 1 FROM custom_templates WHERE id = template_id); -- Create a function to validate template_id references CREATE OR REPLACE FUNCTION validate_template_reference() @@ -28,8 +35,11 @@ BEGIN RAISE EXCEPTION 'Template ID % does not exist in templates table or is not active', NEW.template_id; END IF; ELSIF NEW.template_type = 'custom' THEN + -- First check custom_templates, then fall back to templates table IF NOT EXISTS (SELECT 1 FROM custom_templates WHERE id = NEW.template_id) THEN - RAISE EXCEPTION 'Custom template ID % does not exist in custom_templates table', NEW.template_id; + IF NOT EXISTS (SELECT 1 FROM templates WHERE id = NEW.template_id AND is_active = true) THEN + RAISE EXCEPTION 'Template ID % does not exist in custom_templates or templates table', NEW.template_id; + END IF; END IF; ELSE RAISE EXCEPTION 'Invalid template_type: %. Must be either "default" or "custom"', NEW.template_type; diff --git a/services/template-manager/src/models/custom_feature.js b/services/template-manager/src/models/custom_feature.js index 39a0a97..e60cfd8 100644 --- a/services/template-manager/src/models/custom_feature.js +++ b/services/template-manager/src/models/custom_feature.js @@ -170,9 +170,12 @@ class CustomFeature { // Admin workflow methods static async getPendingFeatures(limit = 50, offset = 0) { const query = ` - SELECT cf.*, t.title as template_title + SELECT cf.*, + COALESCE(t.title, ct.title) as template_title, + COALESCE(t.type, 'custom') as template_type FROM custom_features cf LEFT JOIN templates t ON cf.template_id = t.id + LEFT JOIN custom_templates ct ON cf.template_id = ct.id WHERE cf.status = 'pending' ORDER BY cf.created_at ASC LIMIT $1 OFFSET $2 @@ -183,9 +186,12 @@ class CustomFeature { static async getFeaturesByStatus(status, limit = 50, offset = 0) { const query = ` - SELECT cf.*, t.title as template_title + SELECT cf.*, + COALESCE(t.title, ct.title) as template_title, + COALESCE(t.type, 'custom') as template_type FROM custom_features cf LEFT JOIN templates t ON cf.template_id = t.id + LEFT JOIN custom_templates ct ON cf.template_id = ct.id WHERE cf.status = $1 ORDER BY cf.created_at DESC LIMIT $2 OFFSET $3 @@ -229,8 +235,7 @@ class CustomFeature { const updated = await CustomFeature.update(id, updates); - // If approved, ensure a mirrored entry exists/updates in template_features - // Only mirror if the template_id exists in the main templates table + // If approved, create a NEW record in template_features table with feature_type='essential' if (updated && status === 'approved') { try { // Check if template_id exists in main templates table @@ -240,39 +245,29 @@ class CustomFeature { ); if (templateCheck.rows.length > 0) { - // Template exists in main templates table, safe to mirror + // Template exists in main templates table, create new essential feature const Feature = require('./feature'); - const featureId = `custom_${updated.id}`; - const existingMirror = await Feature.getByFeatureId(updated.template_id, featureId); - if (existingMirror) { - await Feature.update(existingMirror.id, { - name: updated.name, - description: updated.description, - complexity: updated.complexity, - feature_type: 'custom', - is_default: false - }); - console.log('✅ Updated mirrored feature in template_features for approved custom feature'); - } else { - await Feature.create({ - template_id: updated.template_id, - feature_id: featureId, - name: updated.name, - description: updated.description, - feature_type: 'custom', - complexity: updated.complexity, - display_order: 999, - is_default: false, - created_by_user: true - }); - console.log('✅ Created mirrored feature in template_features for approved custom feature'); - } + + // Create a completely new record in template_features with feature_type='essential' + await Feature.create({ + template_id: updated.template_id, + feature_id: `approved_custom_${updated.id}`, + name: updated.name, + description: updated.description, + feature_type: 'essential', + complexity: updated.complexity, + display_order: 1, // High priority for approved features + is_default: false, + created_by_user: false, // This is now an approved essential feature + usage_count: 1 + }); + console.log('✅ Created NEW essential feature in template_features for approved custom feature'); } else { - // Template is likely a custom template, don't mirror to template_features - console.log('ℹ️ Custom feature approved but template_id references custom template, skipping mirror to template_features'); + // Template is likely a custom template, don't create in template_features + console.log('ℹ️ Custom feature approved but template_id references custom template, skipping creation in template_features'); } - } catch (mirrorErr) { - console.error('⚠️ Failed to mirror approved custom feature into template_features:', mirrorErr.message); + } catch (createError) { + console.error('⚠️ Failed to create new essential feature in template_features:', createError.message); } } @@ -327,9 +322,12 @@ class CustomFeature { // Get all custom features with pagination static async getAllFeatures(limit = 50, offset = 0) { const query = ` - SELECT cf.*, t.title as template_title + SELECT cf.*, + COALESCE(t.title, ct.title) as template_title, + COALESCE(t.type, 'custom') as template_type FROM custom_features cf LEFT JOIN templates t ON cf.template_id = t.id + LEFT JOIN custom_templates ct ON cf.template_id = ct.id ORDER BY cf.created_at DESC LIMIT $1 OFFSET $2 `; diff --git a/services/template-manager/src/models/custom_template.js b/services/template-manager/src/models/custom_template.js index c09e337..a558b9f 100644 --- a/services/template-manager/src/models/custom_template.js +++ b/services/template-manager/src/models/custom_template.js @@ -37,6 +37,34 @@ class CustomTemplate { return result.rows.length ? new CustomTemplate(result.rows[0]) : null; } + // Get custom template by ID with features + static async getByIdWithFeatures(id) { + const templateQuery = ` + SELECT * FROM custom_templates + WHERE id = $1 + `; + + const featuresQuery = ` + SELECT * FROM custom_features + WHERE template_id = $1 + ORDER BY created_at DESC + `; + + const [templateResult, featuresResult] = await Promise.all([ + database.query(templateQuery, [id]), + database.query(featuresQuery, [id]) + ]); + + if (templateResult.rows.length === 0) { + return null; + } + + const template = new CustomTemplate(templateResult.rows[0]); + template.features = featuresResult.rows; + + return template; + } + // Check for duplicate custom templates based on title, type, category, and user_id static async checkForDuplicate(templateData) { const normalizedTitle = (templateData.title || '').toLowerCase(); diff --git a/services/template-manager/src/routes/features.js b/services/template-manager/src/routes/features.js index 7b7e393..0687d29 100644 --- a/services/template-manager/src/routes/features.js +++ b/services/template-manager/src/routes/features.js @@ -226,12 +226,43 @@ router.post('/', async (req, res) => { return res.status(400).json({ success: false, error: 'Invalid complexity', message: `Complexity must be one of: ${validComplexity.join(', ')}` }); } + // Validate that template_id exists in either templates or custom_templates table + const templateCheck = await database.query(` + SELECT id, title, 'default' as template_type FROM templates WHERE id = $1 AND is_active = true + UNION + SELECT id, title, 'custom' as template_type FROM custom_templates WHERE id = $1 + `, [featureData.template_id]); + + if (templateCheck.rows.length === 0) { + console.error('❌ Template not found in either table:', featureData.template_id); + return res.status(400).json({ + success: false, + error: 'Template not found', + message: `Template with ID ${featureData.template_id} does not exist in templates or custom_templates` + }); + } + + const templateType = templateCheck.rows[0].template_type; + + // Allow admin-approved features to be created in template_features table regardless of template type + // Only redirect regular user-created features for custom templates + const isAdminApproval = featureData.feature_type === 'essential' && featureData.created_by_user !== true; + + if (templateType === 'custom' && !isAdminApproval) { + console.log('🔄 Redirecting to custom features endpoint for custom template'); + return res.status(400).json({ + success: false, + error: 'Invalid template type', + message: 'Features for custom templates should be created using the /api/features/custom endpoint' + }); + } + const feature = await Feature.create({ template_id: featureData.template_id, feature_id: featureData.id, name: featureData.name, description: featureData.description, - feature_type: featureData.feature_type || 'suggested', + feature_type: featureData.feature_type || 'essential', complexity: featureData.complexity, display_order: featureData.display_order || 999, is_default: featureData.is_default || false, diff --git a/services/template-manager/src/routes/templates.js b/services/template-manager/src/routes/templates.js index 6487f81..a58f2ef 100644 --- a/services/template-manager/src/routes/templates.js +++ b/services/template-manager/src/routes/templates.js @@ -477,7 +477,16 @@ router.get('/:id([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})', }); } - const template = await Template.getByIdWithFeatures(id); + // First try to find in default templates + let template = await Template.getByIdWithFeatures(id); + let templateType = 'default'; + + // If not found in default templates, try custom templates + if (!template) { + const CustomTemplate = require('../models/custom_template'); + template = await CustomTemplate.getByIdWithFeatures(id); + templateType = 'custom'; + } if (!template) { return res.status(404).json({ @@ -487,9 +496,16 @@ router.get('/:id([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})', }); } + // Add template type information to response + const responseData = { + ...template, + template_type: templateType, + is_custom: templateType === 'custom' + }; + res.json({ success: true, - data: template, + data: responseData, message: `Template ${template.title} retrieved successfully` }); } catch (error) { @@ -570,7 +586,8 @@ router.get('/:id([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/f `; const defaultFeaturesResult = await database.query(defaultFeaturesQuery, [id]); const defaultFeatures = defaultFeaturesResult.rows; - console.log(`📊 Found ${defaultFeatures.length} default/suggested features`); + console.log(`📊 Found ${defaultFeatures.length} template features (all types)`); + console.log(`📋 Template features for ${id}:`, defaultFeatures.map(f => ({ name: f.name, type: f.feature_type, id: f.id }))); // Get custom features from custom_features table with business rules (if table exists) // Some environments may not have run the feature_business_rules migration yet. Probe first. diff --git a/services/user-auth/src/routes/auth.js b/services/user-auth/src/routes/auth.js index f794f1b..c406dbc 100644 --- a/services/user-auth/src/routes/auth.js +++ b/services/user-auth/src/routes/auth.js @@ -65,7 +65,7 @@ router.get('/verify-email', async (req, res) => { const { token } = req.query; await authService.verifyEmailToken(token); - const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3001'; + const frontendUrl = process.env.FRONTEND_URL || 'http://192.168.1.20:3001'; const redirectUrl = `${frontendUrl}/signin?verified=true`; // Prefer redirect by default; only return JSON if explicitly requested if (req.query.format === 'json') { @@ -73,7 +73,7 @@ router.get('/verify-email', async (req, res) => { } return res.redirect(302, redirectUrl); } catch (error) { - const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3001'; + const frontendUrl = process.env.FRONTEND_URL || 'http://192.168.1.20:3001'; const redirectUrl = `${frontendUrl}/signin?error=${encodeURIComponent(error.message)}`; if (req.query.format === 'json') { return res.status(400).json({ success: false, message: error.message, redirect: redirectUrl });