backend changes
This commit is contained in:
parent
89cc485734
commit
f12b236e46
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
`;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 });
|
||||
|
||||
Loading…
Reference in New Issue
Block a user