626 lines
20 KiB
JavaScript
626 lines
20 KiB
JavaScript
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;
|