backend changes

This commit is contained in:
Chandini 2025-09-16 12:53:13 +05:30
parent 89cc485734
commit f12b236e46
7 changed files with 135 additions and 45 deletions

View File

@ -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

View File

@ -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;

View File

@ -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
`;

View File

@ -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();

View File

@ -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,

View File

@ -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.

View File

@ -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 });