const express = require('express'); const router = express.Router(); const TechStackRecommendation = require('../models/tech_stack_recommendation'); const IntelligentTechStackAnalyzer = require('../services/intelligent-tech-stack-analyzer'); const autoTechStackAnalyzer = require('../services/auto_tech_stack_analyzer'); const Template = require('../models/template'); const CustomTemplate = require('../models/custom_template'); const Feature = require('../models/feature'); const CustomFeature = require('../models/custom_feature'); const database = require('../config/database'); // Initialize analyzer const analyzer = new IntelligentTechStackAnalyzer(); // GET /api/tech-stack/recommendations - Get all tech stack recommendations router.get('/recommendations', async (req, res) => { try { const limit = parseInt(req.query.limit) || 50; const offset = parseInt(req.query.offset) || 0; const status = req.query.status || null; console.log(`πŸ“Š [TechStack] Fetching recommendations (status: ${status || 'all'}, limit: ${limit}, offset: ${offset})`); let recommendations; if (status) { recommendations = await TechStackRecommendation.getByStatus(status, limit, offset); } else { recommendations = await TechStackRecommendation.getAll(limit, offset); } res.json({ success: true, data: recommendations, count: recommendations.length, message: `Found ${recommendations.length} tech stack recommendations` }); } catch (error) { console.error('❌ Error fetching tech stack recommendations:', error.message); res.status(500).json({ success: false, error: 'Failed to fetch recommendations', message: error.message }); } }); // GET /api/tech-stack/recommendations/with-details - Get recommendations with template details router.get('/recommendations/with-details', async (req, res) => { try { const limit = parseInt(req.query.limit) || 50; const offset = parseInt(req.query.offset) || 0; console.log(`πŸ“Š [TechStack] Fetching recommendations with template details (limit: ${limit}, offset: ${offset})`); const recommendations = await TechStackRecommendation.getWithTemplateDetails(limit, offset); res.json({ success: true, data: recommendations, count: recommendations.length, message: `Found ${recommendations.length} recommendations with template details` }); } catch (error) { console.error('❌ Error fetching recommendations with details:', error.message); res.status(500).json({ success: false, error: 'Failed to fetch recommendations with details', message: error.message }); } }); // GET /api/tech-stack/recommendations/:templateId - Get recommendation for specific template router.get('/recommendations/:templateId', async (req, res) => { try { const { templateId } = req.params; const templateType = req.query.templateType || null; console.log(`πŸ” [TechStack] Fetching recommendation for template: ${templateId} (type: ${templateType || 'any'})`); const recommendation = await TechStackRecommendation.getByTemplateId(templateId, templateType); if (!recommendation) { return res.status(404).json({ success: false, error: 'Recommendation not found', message: `No tech stack recommendation found for template ${templateId}` }); } res.json({ success: true, data: recommendation, message: `Tech stack recommendation found for template ${templateId}` }); } catch (error) { console.error('❌ Error fetching recommendation:', error.message); res.status(500).json({ success: false, error: 'Failed to fetch recommendation', message: error.message }); } }); // POST /api/tech-stack/analyze/:templateId - Analyze specific template router.post('/analyze/:templateId', async (req, res) => { try { const { templateId } = req.params; const forceUpdate = req.query.force === 'true'; console.log(`πŸ€– [TechStack] Starting analysis for template: ${templateId} (force: ${forceUpdate})`); // Check if recommendation already exists if (!forceUpdate) { const existing = await TechStackRecommendation.getByTemplateId(templateId); if (existing) { return res.json({ success: true, data: existing, message: `Recommendation already exists for template ${templateId}. Use ?force=true to update.`, cached: true }); } } // Fetch template with features and business rules const templateData = await fetchTemplateWithFeatures(templateId); if (!templateData) { return res.status(404).json({ success: false, error: 'Template not found', message: `Template with ID ${templateId} does not exist` }); } // Analyze template const analysisResult = await analyzer.analyzeTemplate(templateData); // Save recommendation const recommendation = await TechStackRecommendation.upsert( templateId, templateData.is_custom ? 'custom' : 'default', analysisResult ); res.json({ success: true, data: recommendation, message: `Tech stack analysis completed for template ${templateData.title}`, cached: false }); } catch (error) { console.error('❌ Error analyzing template:', error.message); res.status(500).json({ success: false, error: 'Analysis failed', message: error.message }); } }); // POST /api/tech-stack/analyze/batch - Batch analyze all templates router.post('/analyze/batch', async (req, res) => { try { const { forceUpdate = false, templateIds = null, includeCustom = true, includeDefault = true } = req.body; console.log(`πŸš€ [TechStack] Starting batch analysis (force: ${forceUpdate}, custom: ${includeCustom}, default: ${includeDefault})`); // Fetch all templates with features const templates = await fetchAllTemplatesWithFeatures(includeCustom, includeDefault, templateIds); if (templates.length === 0) { return res.json({ success: true, data: [], message: 'No templates found for analysis', summary: { total: 0, processed: 0, failed: 0 } }); } console.log(`πŸ“Š [TechStack] Found ${templates.length} templates for analysis`); // Filter out templates that already have recommendations (unless force update) let templatesToAnalyze = templates; if (!forceUpdate) { const existingRecommendations = await Promise.all( templates.map(t => TechStackRecommendation.getByTemplateId(t.id)) ); templatesToAnalyze = templates.filter((template, index) => !existingRecommendations[index]); console.log(`πŸ“Š [TechStack] ${templates.length - templatesToAnalyze.length} templates already have recommendations`); } if (templatesToAnalyze.length === 0) { return res.json({ success: true, data: [], message: 'All templates already have recommendations. Use forceUpdate=true to re-analyze.', summary: { total: templates.length, processed: 0, failed: 0, skipped: templates.length } }); } // Start batch analysis const results = await analyzer.batchAnalyze(templatesToAnalyze, (current, total, title, status) => { console.log(`πŸ“ˆ [TechStack] Progress: ${current}/${total} - ${title} (${status})`); }); // Save all results const savedRecommendations = []; const failedRecommendations = []; for (const result of results) { try { const recommendation = await TechStackRecommendation.upsert( result.template_id, result.template_type, result ); savedRecommendations.push(recommendation); } catch (saveError) { console.error(`❌ Failed to save recommendation for ${result.template_id}:`, saveError.message); failedRecommendations.push({ template_id: result.template_id, error: saveError.message }); } } const summary = { total: templates.length, processed: templatesToAnalyze.length, successful: savedRecommendations.length, failed: failedRecommendations.length, skipped: templates.length - templatesToAnalyze.length }; res.json({ success: true, data: savedRecommendations, failed: failedRecommendations, summary, message: `Batch analysis completed: ${summary.successful} successful, ${summary.failed} failed, ${summary.skipped} skipped` }); } catch (error) { console.error('❌ Error in batch analysis:', error.message); res.status(500).json({ success: false, error: 'Batch analysis failed', message: error.message }); } }); // GET /api/tech-stack/stats - Get statistics router.get('/stats', async (req, res) => { try { console.log('πŸ“Š [TechStack] Fetching statistics...'); const stats = await TechStackRecommendation.getStats(); res.json({ success: true, data: stats, message: 'Tech stack statistics retrieved successfully' }); } catch (error) { console.error('❌ Error fetching stats:', error.message); res.status(500).json({ success: false, error: 'Failed to fetch statistics', message: error.message }); } }); // GET /api/tech-stack/stale - Get recommendations that need updating router.get('/stale', async (req, res) => { try { const daysOld = parseInt(req.query.days) || 30; const limit = parseInt(req.query.limit) || 100; console.log(`πŸ“Š [TechStack] Fetching stale recommendations (older than ${daysOld} days, limit: ${limit})`); const staleRecommendations = await TechStackRecommendation.getStaleRecommendations(daysOld, limit); res.json({ success: true, data: staleRecommendations, count: staleRecommendations.length, message: `Found ${staleRecommendations.length} recommendations older than ${daysOld} days` }); } catch (error) { console.error('❌ Error fetching stale recommendations:', error.message); res.status(500).json({ success: false, error: 'Failed to fetch stale recommendations', message: error.message }); } }); // DELETE /api/tech-stack/recommendations/:id - Delete recommendation router.delete('/recommendations/:id', async (req, res) => { try { const { id } = req.params; console.log(`πŸ—‘οΈ [TechStack] Deleting recommendation: ${id}`); const deleted = await TechStackRecommendation.delete(id); if (!deleted) { return res.status(404).json({ success: false, error: 'Recommendation not found', message: `Recommendation with ID ${id} does not exist` }); } res.json({ success: true, message: `Recommendation ${id} deleted successfully` }); } catch (error) { console.error('❌ Error deleting recommendation:', error.message); res.status(500).json({ success: false, error: 'Failed to delete recommendation', message: error.message }); } }); // POST /api/tech-stack/auto-analyze/all - Automatically analyze all templates without recommendations router.post('/auto-analyze/all', async (req, res) => { try { console.log('πŸ€– [TechStack] πŸš€ Starting auto-analysis for all templates without recommendations...'); const result = await autoTechStackAnalyzer.analyzeAllPendingTemplates(); res.json({ success: true, data: result, message: result.message }); } catch (error) { console.error('❌ Error in auto-analysis:', error.message); res.status(500).json({ success: false, error: 'Auto-analysis failed', message: error.message }); } }); // POST /api/tech-stack/auto-analyze/force-all - Force analyze ALL templates regardless of existing recommendations router.post('/auto-analyze/force-all', async (req, res) => { try { console.log('πŸ€– [TechStack] πŸš€ Starting FORCE analysis for ALL templates...'); const result = await autoTechStackAnalyzer.analyzeAllTemplates(true); res.json({ success: true, data: result, message: result.message }); } catch (error) { console.error('❌ Error in force auto-analysis:', error.message); res.status(500).json({ success: false, error: 'Force auto-analysis failed', message: error.message }); } }); // POST /api/tech-stack/analyze-existing - Analyze all existing templates in database (including those with old recommendations) router.post('/analyze-existing', async (req, res) => { try { const { forceUpdate = false, daysOld = 30 } = req.body; console.log(`πŸ€– [TechStack] πŸ” Starting analysis of existing templates (force: ${forceUpdate}, daysOld: ${daysOld})...`); // Get all templates from database const allTemplates = await fetchAllTemplatesWithFeatures(true, true); console.log(`πŸ“Š [TechStack] πŸ“Š Found ${allTemplates.length} total templates in database`); if (allTemplates.length === 0) { return res.json({ success: true, data: { total: 0, queued: 0, skipped: 0 }, message: 'No templates found in database' }); } let queuedCount = 0; let skippedCount = 0; // Process each template for (const template of allTemplates) { const templateType = template.is_custom ? 'custom' : 'default'; if (!forceUpdate) { // Check if recommendation exists and is recent const existing = await TechStackRecommendation.getByTemplateId(template.id, templateType); if (existing && autoTechStackAnalyzer.isRecentRecommendation(existing, daysOld)) { console.log(`⏭️ [TechStack] ⏸️ Skipping ${template.title} - recent recommendation exists`); skippedCount++; continue; } } // Queue for analysis console.log(`πŸ“ [TechStack] πŸ“ Queuing existing template: ${template.title} (${templateType})`); autoTechStackAnalyzer.queueForAnalysis(template.id, templateType, 2); // Normal priority queuedCount++; } const result = { total: allTemplates.length, queued: queuedCount, skipped: skippedCount, forceUpdate }; console.log(`βœ… [TechStack] βœ… Existing templates analysis queued: ${queuedCount} queued, ${skippedCount} skipped`); res.json({ success: true, data: result, message: `Queued ${queuedCount} existing templates for analysis (${skippedCount} skipped)` }); } catch (error) { console.error('❌ Error analyzing existing templates:', error.message); res.status(500).json({ success: false, error: 'Failed to analyze existing templates', message: error.message }); } }); // GET /api/tech-stack/auto-analyze/queue - Get automation queue status router.get('/auto-analyze/queue', async (req, res) => { try { const queueStatus = autoTechStackAnalyzer.getQueueStatus(); res.json({ success: true, data: queueStatus, message: `Queue status: ${queueStatus.isProcessing ? 'processing' : 'idle'}, ${queueStatus.queueLength} items queued` }); } catch (error) { console.error('❌ Error getting queue status:', error.message); res.status(500).json({ success: false, error: 'Failed to get queue status', message: error.message }); } }); // POST /api/tech-stack/auto-analyze/queue/clear - Clear the processing queue router.post('/auto-analyze/queue/clear', async (req, res) => { try { const clearedCount = autoTechStackAnalyzer.clearQueue(); res.json({ success: true, data: { clearedCount }, message: `Cleared ${clearedCount} items from processing queue` }); } catch (error) { console.error('❌ Error clearing queue:', error.message); res.status(500).json({ success: false, error: 'Failed to clear queue', message: error.message }); } }); // POST /api/tech-stack/auto-analyze/trigger/:templateId - Manually trigger auto-analysis for specific template router.post('/auto-analyze/trigger/:templateId', async (req, res) => { try { const { templateId } = req.params; const { templateType = null, priority = 1 } = req.body; console.log(`πŸ€– [TechStack] Manually triggering auto-analysis for template: ${templateId}`); // Queue for analysis autoTechStackAnalyzer.queueForAnalysis(templateId, templateType, priority); res.json({ success: true, data: { templateId, templateType, priority }, message: `Template ${templateId} queued for auto-analysis with priority ${priority}` }); } catch (error) { console.error('❌ Error triggering auto-analysis:', error.message); res.status(500).json({ success: false, error: 'Failed to trigger auto-analysis', message: error.message }); } }); // Helper function to fetch template with features and business rules async function fetchTemplateWithFeatures(templateId) { try { // Check if template exists in default templates let template = await Template.getByIdWithFeatures(templateId); let isCustom = false; if (!template) { // Check custom templates template = await CustomTemplate.getByIdWithFeatures(templateId); isCustom = true; } if (!template) { return null; } // Get features and business rules const features = await Feature.getByTemplateId(templateId); // Extract business rules const businessRules = {}; features.forEach(feature => { if (feature.additional_business_rules) { businessRules[feature.id] = feature.additional_business_rules; } }); return { ...template, features, business_rules: businessRules, feature_count: features.length, is_custom: isCustom }; } catch (error) { console.error('❌ Error fetching template with features:', error.message); throw error; } } // Helper function to fetch all templates with features async function fetchAllTemplatesWithFeatures(includeCustom = true, includeDefault = true, templateIds = null) { try { const templates = []; if (includeDefault) { const defaultTemplates = await Template.getAllByCategory(); const defaultTemplatesFlat = Object.values(defaultTemplates).flat(); templates.push(...defaultTemplatesFlat); } if (includeCustom) { const customTemplates = await CustomTemplate.getAll(1000, 0); templates.push(...customTemplates); } // Filter by template IDs if provided let filteredTemplates = templates; if (templateIds && Array.isArray(templateIds)) { filteredTemplates = templates.filter(t => templateIds.includes(t.id)); } // Fetch features for each template const templatesWithFeatures = await Promise.all( filteredTemplates.map(async (template) => { try { const features = await Feature.getByTemplateId(template.id); // Extract business rules const businessRules = {}; features.forEach(feature => { if (feature.additional_business_rules) { businessRules[feature.id] = feature.additional_business_rules; } }); return { ...template, features, business_rules: businessRules, feature_count: features.length, is_custom: !template.is_active }; } catch (error) { console.error(`⚠️ Error fetching features for template ${template.id}:`, error.message); return { ...template, features: [], business_rules: {}, feature_count: 0, is_custom: !template.is_active, error: error.message }; } }) ); return templatesWithFeatures; } catch (error) { console.error('❌ Error fetching all templates with features:', error.message); throw error; } } module.exports = router;