backend changes
This commit is contained in:
parent
c5e62ae68b
commit
7cdc7b1165
@ -503,6 +503,7 @@ services:
|
||||
- JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-secure_pipeline_2024
|
||||
- JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-${POSTGRES_PASSWORD}
|
||||
- JWT_ACCESS_EXPIRY=24h
|
||||
- JWT_ADMIN_ACCESS_EXPIRY=7d
|
||||
- JWT_REFRESH_EXPIRY=7d
|
||||
- FRONTEND_URL=*
|
||||
# Email Configuration
|
||||
|
||||
@ -99,6 +99,7 @@ app.use('/api/gateway', express.json({ limit: '10mb' }));
|
||||
app.use('/api/auth', express.json({ limit: '10mb' }));
|
||||
app.use('/api/templates', express.json({ limit: '10mb' }));
|
||||
app.use('/api/features', express.json({ limit: '10mb' }));
|
||||
app.use('/api/admin', express.json({ limit: '10mb' }));
|
||||
app.use('/api/github', express.json({ limit: '10mb' }));
|
||||
app.use('/api/mockup', express.json({ limit: '10mb' }));
|
||||
app.use('/health', express.json({ limit: '10mb' }));
|
||||
|
||||
@ -213,17 +213,13 @@ router.get('/:id/features', async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Get features for the template
|
||||
// Get features for the template with business rules
|
||||
const featuresQuery = `
|
||||
SELECT
|
||||
tf.*,
|
||||
f.name,
|
||||
f.description,
|
||||
f.complexity,
|
||||
f.business_rules,
|
||||
f.technical_requirements
|
||||
fbr.business_rules as stored_business_rules
|
||||
FROM template_features tf
|
||||
LEFT JOIN features f ON tf.feature_id = f.id
|
||||
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
|
||||
`;
|
||||
@ -244,8 +240,8 @@ router.get('/:id/features', async (req, res) => {
|
||||
created_by_user: row.created_by_user || false,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
business_rules: row.business_rules,
|
||||
technical_requirements: row.technical_requirements
|
||||
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);
|
||||
@ -257,11 +253,13 @@ router.get('/:id/features', async (req, res) => {
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [ADMIN-TEMPLATES] Error fetching template features:', error.message);
|
||||
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
|
||||
message: error.message,
|
||||
details: error.stack
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -325,6 +323,14 @@ router.put('/:templateId/features/:featureId', async (req, res) => {
|
||||
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);
|
||||
@ -344,14 +350,44 @@ router.put('/:templateId/features/:featureId', async (req, res) => {
|
||||
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,
|
||||
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,
|
||||
@ -371,11 +407,13 @@ router.put('/:templateId/features/:featureId', async (req, res) => {
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [ADMIN-TEMPLATES] Error updating feature:', error.message);
|
||||
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
|
||||
message: error.message,
|
||||
details: error.stack
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -12,7 +12,7 @@ router.get('/', async (req, res) => {
|
||||
try {
|
||||
console.log('📂 Fetching all templates by category...');
|
||||
const templates = await Template.getAllByCategory();
|
||||
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: templates,
|
||||
@ -33,7 +33,7 @@ router.get('/stats', async (req, res) => {
|
||||
try {
|
||||
console.log('📊 Fetching template statistics...');
|
||||
const stats = await Template.getStats();
|
||||
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: stats,
|
||||
@ -140,28 +140,28 @@ router.get('/merged', async (req, res) => {
|
||||
category: req.query.category || 'all categories',
|
||||
search: req.query.search || 'no search query'
|
||||
});
|
||||
|
||||
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
const offset = parseInt(req.query.offset) || 0;
|
||||
const categoryFilter = req.query.category || null;
|
||||
const searchQuery = req.query.search ? req.query.search.toLowerCase() : null;
|
||||
|
||||
console.log("req.query __", req.query)
|
||||
|
||||
|
||||
console.log('⚙️ [MERGED-TEMPLATES] Parsed parameters:', { limit, offset, categoryFilter, searchQuery });
|
||||
|
||||
|
||||
// Get all default templates
|
||||
console.log('🏗️ [MERGED-TEMPLATES] Fetching default templates by category...');
|
||||
const defaultTemplatesByCat = await Template.getAllByCategory();
|
||||
console.log('📊 [MERGED-TEMPLATES] Default templates by category structure:', Object.keys(defaultTemplatesByCat));
|
||||
|
||||
|
||||
let defaultTemplates = [];
|
||||
for (const cat in defaultTemplatesByCat) {
|
||||
const catTemplates = defaultTemplatesByCat[cat];
|
||||
console.log(`📁 [MERGED-TEMPLATES] Category "${cat}": ${catTemplates.length} templates`);
|
||||
defaultTemplates = defaultTemplates.concat(catTemplates);
|
||||
}
|
||||
|
||||
|
||||
console.log('✅ [MERGED-TEMPLATES] Total default templates collected:', defaultTemplates.length);
|
||||
console.log('🔍 [MERGED-TEMPLATES] Sample default template:', defaultTemplates[0] ? {
|
||||
id: defaultTemplates[0].id,
|
||||
@ -169,16 +169,16 @@ router.get('/merged', async (req, res) => {
|
||||
category: defaultTemplates[0].category,
|
||||
type: defaultTemplates[0].type
|
||||
} : 'No default templates found');
|
||||
|
||||
|
||||
// Get all custom templates for the current user
|
||||
console.log('🎨 [MERGED-TEMPLATES] Fetching custom templates...');
|
||||
console.log('🔍 [MERGED-TEMPLATES] Request userId:', req.query.userId);
|
||||
console.log('🔍 [MERGED-TEMPLATES] Request includeOthers:', req.query.includeOthers);
|
||||
|
||||
|
||||
let customTemplates = [];
|
||||
let userOwnCustomCount = 0;
|
||||
let approvedOthersCustomCount = 0;
|
||||
|
||||
|
||||
if (req.query.userId) {
|
||||
// Validate UUID v4 for userId to avoid DB errors like "invalid input syntax for type uuid"
|
||||
const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
@ -195,7 +195,7 @@ router.get('/merged', async (req, res) => {
|
||||
customTemplates = await CustomTemplate.getByUserId(req.query.userId, 1000, 0);
|
||||
userOwnCustomCount = customTemplates.length;
|
||||
console.log('📈 [MERGED-TEMPLATES] ALL custom templates for THIS user:', userOwnCustomCount);
|
||||
|
||||
|
||||
// Optionally include ALL custom templates from other users if explicitly requested
|
||||
const includeOthers = String(req.query.includeOthers || '').toLowerCase() === 'true';
|
||||
if (includeOthers) {
|
||||
@ -216,9 +216,9 @@ router.get('/merged', async (req, res) => {
|
||||
approvedOthersCustomCount = customTemplates.length;
|
||||
console.log('📈 [MERGED-TEMPLATES] All custom templates (no user specified):', approvedOthersCustomCount);
|
||||
}
|
||||
|
||||
|
||||
console.log('📈 [MERGED-TEMPLATES] Totals → userOwn:', userOwnCustomCount, ', approvedOthers:', approvedOthersCustomCount, ', combinedCustoms:', customTemplates.length);
|
||||
|
||||
|
||||
if (customTemplates.length > 0) {
|
||||
console.log('🔍 [MERGED-TEMPLATES] Sample custom template:', {
|
||||
id: customTemplates[0].id,
|
||||
@ -227,7 +227,7 @@ router.get('/merged', async (req, res) => {
|
||||
status: customTemplates[0].status
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Convert customs to standard template format and merge into flat array
|
||||
console.log('🔄 [MERGED-TEMPLATES] Converting custom templates to standard format...');
|
||||
const convertedCustomTemplates = customTemplates.map(customTemplate => ({
|
||||
@ -249,12 +249,12 @@ router.get('/merged', async (req, res) => {
|
||||
business_rules: customTemplate.business_rules,
|
||||
technical_requirements: customTemplate.technical_requirements
|
||||
}));
|
||||
|
||||
|
||||
console.log('✅ [MERGED-TEMPLATES] Custom templates converted:', convertedCustomTemplates.length);
|
||||
|
||||
|
||||
let allTemplates = defaultTemplates.concat(convertedCustomTemplates);
|
||||
console.log('🔗 [MERGED-TEMPLATES] Combined templates total:', allTemplates.length);
|
||||
|
||||
|
||||
// Apply category filter if specified
|
||||
if (categoryFilter && categoryFilter !== 'all') {
|
||||
console.log(`🎯 [MERGED-TEMPLATES] Applying category filter: "${categoryFilter}"`);
|
||||
@ -263,29 +263,29 @@ router.get('/merged', async (req, res) => {
|
||||
const afterFilter = allTemplates.length;
|
||||
console.log(`📊 [MERGED-TEMPLATES] Category filter result: ${beforeFilter} → ${afterFilter} templates`);
|
||||
}
|
||||
|
||||
|
||||
// Apply search filter if specified
|
||||
if (searchQuery) {
|
||||
console.log(`🔍 [MERGED-TEMPLATES] Applying search filter: "${searchQuery}"`);
|
||||
const beforeSearch = allTemplates.length;
|
||||
allTemplates = allTemplates.filter(t =>
|
||||
t.title.toLowerCase().includes(searchQuery) ||
|
||||
allTemplates = allTemplates.filter(t =>
|
||||
t.title.toLowerCase().includes(searchQuery) ||
|
||||
t.description.toLowerCase().includes(searchQuery)
|
||||
);
|
||||
const afterSearch = allTemplates.length;
|
||||
console.log(`📊 [MERGED-TEMPLATES] Search filter result: ${beforeSearch} → ${afterSearch} templates`);
|
||||
}
|
||||
|
||||
|
||||
// Sort by created_at descending
|
||||
console.log('📅 [MERGED-TEMPLATES] Sorting templates by creation date...');
|
||||
allTemplates.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
||||
console.log('✅ [MERGED-TEMPLATES] Templates sorted successfully');
|
||||
|
||||
|
||||
// Paginate
|
||||
const total = allTemplates.length;
|
||||
console.log('📊 [MERGED-TEMPLATES] Final template count before pagination:', total);
|
||||
console.log('📄 [MERGED-TEMPLATES] Pagination parameters:', { offset, limit, total });
|
||||
|
||||
|
||||
const paginatedTemplates = allTemplates.slice(offset, offset + limit);
|
||||
console.log('📋 [MERGED-TEMPLATES] Paginated result:', {
|
||||
requested: limit,
|
||||
@ -293,45 +293,45 @@ router.get('/merged', async (req, res) => {
|
||||
startIndex: offset,
|
||||
endIndex: offset + paginatedTemplates.length - 1
|
||||
});
|
||||
|
||||
|
||||
// Add feature counts to each template
|
||||
console.log('🔢 [MERGED-TEMPLATES] Fetching feature counts for templates...');
|
||||
|
||||
|
||||
// Separate default and custom templates for feature counting
|
||||
const defaultTemplateIds = paginatedTemplates.filter(t => !t.is_custom).map(t => t.id);
|
||||
const customTemplateIds = paginatedTemplates.filter(t => t.is_custom).map(t => t.id);
|
||||
|
||||
|
||||
console.log('📊 [MERGED-TEMPLATES] Template ID breakdown:', {
|
||||
defaultTemplates: defaultTemplateIds.length,
|
||||
customTemplates: customTemplateIds.length
|
||||
});
|
||||
|
||||
|
||||
// Fetch feature counts for both types
|
||||
let defaultFeatureCounts = {};
|
||||
let customFeatureCounts = {};
|
||||
|
||||
|
||||
if (defaultTemplateIds.length > 0) {
|
||||
console.log('🔍 [MERGED-TEMPLATES] Fetching default template feature counts...');
|
||||
defaultFeatureCounts = await Feature.countByTemplateIds(defaultTemplateIds);
|
||||
console.log('✅ [MERGED-TEMPLATES] Default feature counts:', Object.keys(defaultFeatureCounts).length, 'templates');
|
||||
}
|
||||
|
||||
|
||||
if (customTemplateIds.length > 0) {
|
||||
console.log('🔍 [MERGED-TEMPLATES] Fetching custom template feature counts...');
|
||||
customFeatureCounts = await CustomFeature.countByTemplateIds(customTemplateIds);
|
||||
console.log('✅ [MERGED-TEMPLATES] Custom feature counts:', Object.keys(customFeatureCounts).length, 'templates');
|
||||
}
|
||||
|
||||
|
||||
// Add feature counts to each template
|
||||
const templatesWithFeatureCounts = paginatedTemplates.map(template => ({
|
||||
...template,
|
||||
feature_count: template.is_custom
|
||||
feature_count: template.is_custom
|
||||
? (customFeatureCounts[template.id] || 0)
|
||||
: (defaultFeatureCounts[template.id] || 0)
|
||||
}));
|
||||
|
||||
|
||||
console.log('🎯 [MERGED-TEMPLATES] Feature counts added to all templates');
|
||||
|
||||
|
||||
// Log sample of returned templates with feature counts
|
||||
if (templatesWithFeatureCounts.length > 0) {
|
||||
console.log('🔍 [MERGED-TEMPLATES] First template in result:', {
|
||||
@ -341,7 +341,7 @@ router.get('/merged', async (req, res) => {
|
||||
is_custom: templatesWithFeatureCounts[0].is_custom,
|
||||
feature_count: templatesWithFeatureCounts[0].feature_count
|
||||
});
|
||||
|
||||
|
||||
if (templatesWithFeatureCounts.length > 1) {
|
||||
console.log('🔍 [MERGED-TEMPLATES] Last template in result:', {
|
||||
id: templatesWithFeatureCounts[templatesWithFeatureCounts.length - 1].id,
|
||||
@ -352,7 +352,7 @@ router.get('/merged', async (req, res) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const responseData = {
|
||||
success: true,
|
||||
data: templatesWithFeatureCounts,
|
||||
@ -364,7 +364,7 @@ router.get('/merged', async (req, res) => {
|
||||
},
|
||||
message: `Found ${templatesWithFeatureCounts.length} templates (out of ${total}) with feature counts`
|
||||
};
|
||||
|
||||
|
||||
console.log('🎉 [MERGED-TEMPLATES] Response prepared successfully:', {
|
||||
success: responseData.success,
|
||||
dataCount: responseData.data.length,
|
||||
@ -376,9 +376,9 @@ router.get('/merged', async (req, res) => {
|
||||
is_custom: t.is_custom
|
||||
}))
|
||||
});
|
||||
|
||||
|
||||
res.json(responseData);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('💥 [MERGED-TEMPLATES] Critical error occurred:', error.message);
|
||||
console.error('📚 [MERGED-TEMPLATES] Error stack:', error.stack);
|
||||
@ -387,7 +387,7 @@ router.get('/merged', async (req, res) => {
|
||||
code: error.code,
|
||||
sqlMessage: error.sqlMessage
|
||||
});
|
||||
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch merged templates',
|
||||
@ -396,14 +396,44 @@ router.get('/merged', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/all-templates-without-pagination', async (req, res) => {
|
||||
try {
|
||||
// Fetch templates (assuming Sequelize models)
|
||||
const templates = await Template.findAll({ raw: true });
|
||||
const customTemplates = await CustomTemplate.findAll({ raw: true });
|
||||
|
||||
// Merge both arrays
|
||||
const allTemplates = [...(templates || []), ...(customTemplates || [])];
|
||||
|
||||
// Sort by created_at (descending)
|
||||
allTemplates.sort((a, b) => {
|
||||
return new Date(b.created_at) - new Date(a.created_at);
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: allTemplates,
|
||||
message: `Found ${allTemplates.length} templates`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Error fetching all templates without pagination:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch all templates without pagination',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// GET /api/templates/type/:type - Get template by type
|
||||
router.get('/type/:type', async (req, res) => {
|
||||
try {
|
||||
const { type } = req.params;
|
||||
console.log(`🔍 Fetching template by type: ${type}`);
|
||||
|
||||
|
||||
const template = await Template.getByType(type);
|
||||
|
||||
|
||||
if (!template) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
@ -411,11 +441,11 @@ router.get('/type/:type', async (req, res) => {
|
||||
message: `Template with type ${type} does not exist`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Get features for this template
|
||||
const features = await Feature.getByTemplateId(template.id);
|
||||
template.features = features;
|
||||
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: template,
|
||||
@ -432,7 +462,6 @@ router.get('/type/:type', async (req, res) => {
|
||||
});
|
||||
|
||||
|
||||
|
||||
// GET /api/templates/:id - Get specific template with features (UUID constrained)
|
||||
router.get('/:id([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})', async (req, res) => {
|
||||
try {
|
||||
@ -447,9 +476,9 @@ router.get('/:id([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})',
|
||||
message: 'id must be a valid UUID v4'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const template = await Template.getByIdWithFeatures(id);
|
||||
|
||||
|
||||
if (!template) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
@ -457,7 +486,7 @@ router.get('/:id([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})',
|
||||
message: `Template with ID ${id} does not exist`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: template,
|
||||
@ -480,27 +509,27 @@ router.get('/:id([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/f
|
||||
try {
|
||||
const { id } = req.params;
|
||||
console.log(`🎯 Fetching features for template: ${id}`);
|
||||
|
||||
|
||||
// Check if template exists in either templates or custom_templates table
|
||||
console.log(`🔍 Searching for template ID: ${id}`);
|
||||
|
||||
|
||||
// First check templates table
|
||||
const defaultTemplateCheck = await database.query(`
|
||||
SELECT id, title, 'default' as template_type FROM templates WHERE id = $1 AND is_active = true
|
||||
`, [id]);
|
||||
console.log(`📊 Default templates found: ${defaultTemplateCheck.rows.length}`);
|
||||
|
||||
|
||||
// Then check custom_templates table
|
||||
const customTemplateCheck = await database.query(`
|
||||
SELECT id, title, 'custom' as template_type FROM custom_templates WHERE id = $1
|
||||
`, [id]);
|
||||
console.log(`📊 Custom templates found: ${customTemplateCheck.rows.length}`);
|
||||
|
||||
|
||||
// Combine results
|
||||
const templateCheck = {
|
||||
rows: [...defaultTemplateCheck.rows, ...customTemplateCheck.rows]
|
||||
};
|
||||
|
||||
|
||||
if (templateCheck.rows.length === 0) {
|
||||
console.log(`❌ Template not found in either table: ${id}`);
|
||||
return res.status(404).json({
|
||||
@ -509,12 +538,12 @@ router.get('/:id([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/f
|
||||
message: `Template with ID ${id} does not exist in templates or custom_templates`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
console.log(`✅ Template found: ${templateCheck.rows[0].title} (${templateCheck.rows[0].template_type})`);
|
||||
|
||||
|
||||
// Fetch features from both tables for proper separation
|
||||
console.log('📋 Fetching features from both template_features and custom_features tables');
|
||||
|
||||
|
||||
// Get default/suggested features from template_features table
|
||||
// Include aggregated business rules from feature_business_rules when available
|
||||
const defaultFeaturesQuery = `
|
||||
@ -542,7 +571,7 @@ 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`);
|
||||
|
||||
|
||||
// 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.
|
||||
const fbrExistsProbe = await database.query("SELECT to_regclass('public.feature_business_rules') AS tbl");
|
||||
@ -604,10 +633,10 @@ 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 customFeaturesResult = await database.query(customFeaturesQuery, [id]);
|
||||
const customFeatures = customFeaturesResult.rows;
|
||||
console.log(`📊 Found ${customFeatures.length} custom features`);
|
||||
|
||||
|
||||
// Combine both types of features
|
||||
const features = [...defaultFeatures, ...customFeatures];
|
||||
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: features,
|
||||
@ -642,7 +671,7 @@ router.post('/', async (req, res) => {
|
||||
}
|
||||
};
|
||||
console.log('🏗️ Creating new template - incoming body:', JSON.stringify(debugPayload));
|
||||
|
||||
|
||||
// Validate required fields
|
||||
const requiredFields = ['type', 'title', 'category'];
|
||||
for (const field of requiredFields) {
|
||||
@ -676,7 +705,7 @@ router.post('/', async (req, res) => {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// If flagged as a custom template, store in custom_templates instead
|
||||
if (templateData.isCustom === true || templateData.is_custom === true || templateData.source === 'custom') {
|
||||
try {
|
||||
@ -707,7 +736,7 @@ router.post('/', async (req, res) => {
|
||||
}
|
||||
|
||||
const incomingUserId = templateData.user_id || templateData.userId || (req.user && (req.user.id || req.user.user_id)) || null;
|
||||
|
||||
|
||||
// Check for duplicates in custom templates for this user
|
||||
const duplicatePayload = {
|
||||
type: templateData.type,
|
||||
@ -715,7 +744,7 @@ router.post('/', async (req, res) => {
|
||||
category: templateData.category,
|
||||
user_id: incomingUserId
|
||||
};
|
||||
|
||||
|
||||
console.log('[POST /api/templates - custom] duplicate payload:', duplicatePayload);
|
||||
const existingCustomTemplate = await CustomTemplate.checkForDuplicate(duplicatePayload);
|
||||
if (existingCustomTemplate) {
|
||||
@ -804,7 +833,7 @@ router.post('/', async (req, res) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const template = await Template.create(templateData);
|
||||
|
||||
// Link back to custom_templates when approving from a custom
|
||||
@ -835,7 +864,7 @@ router.post('/', async (req, res) => {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Error creating template:', error.message);
|
||||
|
||||
|
||||
// Handle unique constraint violation
|
||||
if (error.code === '23505') {
|
||||
return res.status(409).json({
|
||||
@ -844,7 +873,7 @@ router.post('/', async (req, res) => {
|
||||
message: 'A template with this type already exists'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to create template',
|
||||
@ -928,10 +957,10 @@ router.put('/:id', async (req, res) => {
|
||||
}
|
||||
// Validate allowed fields for custom templates to avoid no-op updates
|
||||
const allowed = [
|
||||
'title','description','icon','category','gradient','border','text','subtext',
|
||||
'complexity','business_rules','technical_requirements','approved','usage_count',
|
||||
'status','admin_notes','admin_reviewed_at','admin_reviewed_by',
|
||||
'canonical_template_id','similarity_score','user_id'
|
||||
'title', 'description', 'icon', 'category', 'gradient', 'border', 'text', 'subtext',
|
||||
'complexity', 'business_rules', 'technical_requirements', 'approved', 'usage_count',
|
||||
'status', 'admin_notes', 'admin_reviewed_at', 'admin_reviewed_by',
|
||||
'canonical_template_id', 'similarity_score', 'user_id'
|
||||
];
|
||||
const providedKeys = Object.keys(updateData || {});
|
||||
const updatableKeys = providedKeys.filter(k => allowed.includes(k));
|
||||
@ -967,7 +996,7 @@ router.put('/:id', async (req, res) => {
|
||||
console.log('📝 Updating default template...');
|
||||
const updatedTemplate = await template.update(updateData);
|
||||
console.log('📝 Update result (default):', { updated: !!updatedTemplate });
|
||||
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: updatedTemplate,
|
||||
|
||||
@ -34,5 +34,6 @@ REDIS_PASSWORD=your_redis_password
|
||||
# JWT Configuration
|
||||
JWT_ACCESS_SECRET=your_access_secret_key
|
||||
JWT_REFRESH_SECRET=your_refresh_secret_key
|
||||
JWT_ACCESS_EXPIRY=15m
|
||||
JWT_ACCESS_EXPIRY=24h
|
||||
JWT_ADMIN_ACCESS_EXPIRY=7d
|
||||
JWT_REFRESH_EXPIRY=7d
|
||||
|
||||
@ -6,11 +6,13 @@ class JWTConfig {
|
||||
this.refreshTokenSecret = process.env.JWT_REFRESH_SECRET || 'refresh-secret-key-2024-tech4biz';
|
||||
this.accessTokenExpiry = process.env.JWT_ACCESS_EXPIRY || '24h';
|
||||
this.refreshTokenExpiry = process.env.JWT_REFRESH_EXPIRY || '7d';
|
||||
this.adminAccessTokenExpiry = process.env.JWT_ADMIN_ACCESS_EXPIRY || '7d'; // Extended expiry for admins
|
||||
}
|
||||
|
||||
generateAccessToken(payload) {
|
||||
generateAccessToken(payload, isAdmin = false) {
|
||||
const expiry = isAdmin ? this.adminAccessTokenExpiry : this.accessTokenExpiry;
|
||||
return jwt.sign(payload, this.accessTokenSecret, {
|
||||
expiresIn: this.accessTokenExpiry,
|
||||
expiresIn: expiry,
|
||||
issuer: 'tech4biz-auth',
|
||||
audience: 'tech4biz-users'
|
||||
});
|
||||
@ -54,13 +56,15 @@ class JWTConfig {
|
||||
role: user.role || 'user'
|
||||
};
|
||||
|
||||
const accessToken = this.generateAccessToken(payload);
|
||||
const isAdmin = user.role === 'admin' || user.role === 'super_admin';
|
||||
const accessToken = this.generateAccessToken(payload, isAdmin);
|
||||
const refreshToken = this.generateRefreshToken({ userId: user.id });
|
||||
const expiry = isAdmin ? this.adminAccessTokenExpiry : this.accessTokenExpiry;
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn: this.accessTokenExpiry
|
||||
expiresIn: expiry
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user