966 lines
29 KiB
JavaScript
966 lines
29 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const CustomFeature = require('../models/custom_feature');
|
|
const CustomTemplate = require('../models/custom_template');
|
|
const AdminNotification = require('../models/admin_notification');
|
|
const FeatureSimilarityService = require('../services/feature_similarity');
|
|
const jwt = require('jsonwebtoken');
|
|
const Joi = require('joi');
|
|
|
|
// Initialize similarity service
|
|
const similarityService = new FeatureSimilarityService();
|
|
|
|
// Middleware to check if user is admin using JWT from Authorization header
|
|
const requireAdmin = (req, res, next) => {
|
|
try {
|
|
const authHeader = req.headers.authorization || '';
|
|
if (!authHeader.startsWith('Bearer ')) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: 'Authentication required',
|
|
message: 'Missing or invalid Authorization header'
|
|
});
|
|
}
|
|
const token = authHeader.substring(7);
|
|
const decoded = jwt.verify(
|
|
token,
|
|
process.env.JWT_ACCESS_SECRET || 'access-secret-key-2024-tech4biz',
|
|
{ issuer: 'tech4biz-auth', audience: 'tech4biz-users' }
|
|
);
|
|
if (!decoded || decoded.role !== 'admin') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'Insufficient permissions',
|
|
message: 'Admin role required'
|
|
});
|
|
}
|
|
req.user = decoded;
|
|
next();
|
|
} catch (err) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: 'Invalid token',
|
|
message: 'Failed to authenticate admin'
|
|
});
|
|
}
|
|
};
|
|
|
|
// Apply admin middleware to all routes
|
|
router.use(requireAdmin);
|
|
|
|
// GET /api/admin/custom-features - Proxy to existing custom features functionality
|
|
router.get('/custom-features', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit) || 50;
|
|
const offset = parseInt(req.query.offset) || 0;
|
|
const status = req.query.status;
|
|
|
|
console.log(`Admin: Fetching custom features (status: ${status || 'all'}, limit: ${limit}, offset: ${offset})`);
|
|
|
|
let features;
|
|
if (status) {
|
|
features = await CustomFeature.getFeaturesByStatus(status, limit, offset);
|
|
} else {
|
|
features = await CustomFeature.getAllFeatures(limit, offset);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: features,
|
|
count: features.length,
|
|
message: `Found ${features.length} custom features`
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching custom features:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch custom features',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/admin/custom-features/:id/review - Review custom feature
|
|
router.post('/custom-features/:id/review', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { status, admin_notes, admin_reviewed_by } = req.body;
|
|
|
|
const validStatuses = ['approved', 'rejected', 'duplicate'];
|
|
if (!validStatuses.includes(status)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid status',
|
|
message: `Status must be one of: ${validStatuses.join(', ')}`
|
|
});
|
|
}
|
|
|
|
console.log(`🔍 Admin: Reviewing custom feature ${id} with status: ${status}`);
|
|
|
|
const feature = await CustomFeature.getById(id);
|
|
if (!feature) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Feature not found',
|
|
message: 'The specified feature does not exist'
|
|
});
|
|
}
|
|
|
|
const reviewData = {
|
|
status,
|
|
admin_notes,
|
|
admin_reviewed_by: admin_reviewed_by || req.user.id
|
|
};
|
|
|
|
const updatedFeature = await CustomFeature.reviewFeature(id, reviewData);
|
|
|
|
try {
|
|
await AdminNotification.notifyFeatureReviewed(id, feature.name, status);
|
|
} catch (notifError) {
|
|
console.warn('⚠️ Failed to create notification:', notifError.message);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: updatedFeature,
|
|
message: `Feature "${feature.name}" has been ${status}`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error reviewing custom feature:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to review custom feature',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/custom-templates - Get custom templates
|
|
router.get('/custom-templates', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit) || 50;
|
|
const offset = parseInt(req.query.offset) || 0;
|
|
const status = req.query.status;
|
|
|
|
console.log(`Admin: Fetching custom templates (status: ${status || 'all'}, limit: ${limit}, offset: ${offset})`);
|
|
|
|
let templates;
|
|
if (status) {
|
|
templates = await CustomTemplate.getTemplatesByStatus(status, limit, offset);
|
|
} else {
|
|
templates = await CustomTemplate.getAllTemplates(limit, offset);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: templates,
|
|
count: templates.length,
|
|
message: `Found ${templates.length} custom templates`
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching custom templates:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch custom templates',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/admin/custom-templates/:id/review - Review custom template
|
|
router.post('/custom-templates/:id/review', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { status, admin_notes, admin_reviewed_by } = req.body;
|
|
|
|
const validStatuses = ['approved', 'rejected', 'duplicate'];
|
|
if (!validStatuses.includes(status)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid status',
|
|
message: `Status must be one of: ${validStatuses.join(', ')}`
|
|
});
|
|
}
|
|
|
|
console.log(`🔍 Admin: Reviewing custom template ${id} with status: ${status}`);
|
|
|
|
const template = await CustomTemplate.getById(id);
|
|
if (!template) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Template not found',
|
|
message: 'The specified template does not exist'
|
|
});
|
|
}
|
|
|
|
const reviewData = {
|
|
status,
|
|
admin_notes,
|
|
admin_reviewed_by: admin_reviewed_by || req.user.id
|
|
};
|
|
|
|
const updatedTemplate = await CustomTemplate.reviewTemplate(id, reviewData);
|
|
|
|
try {
|
|
await AdminNotification.notifyTemplateReviewed(id, template.title, status);
|
|
} catch (notifError) {
|
|
console.warn('⚠️ Failed to create notification:', notifError.message);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: updatedTemplate,
|
|
message: `Template "${template.title}" has been ${status}`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error reviewing custom template:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to review custom template',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/templates/stats - Get template statistics
|
|
router.get('/templates/stats', async (req, res) => {
|
|
try {
|
|
console.log('📊 Admin: Fetching template statistics...');
|
|
|
|
const stats = await CustomTemplate.getTemplateStats();
|
|
const notificationCounts = await AdminNotification.getCounts();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
templates: stats,
|
|
notifications: notificationCounts
|
|
},
|
|
message: 'Template statistics retrieved successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error fetching template stats:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch template statistics',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/features/pending - Get pending features for review
|
|
router.get('/features/pending', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit) || 50;
|
|
const offset = parseInt(req.query.offset) || 0;
|
|
|
|
console.log(`Admin: Fetching pending features (limit: ${limit}, offset: ${offset})`);
|
|
|
|
const features = await CustomFeature.getPendingFeatures(limit, offset);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: features,
|
|
count: features.length,
|
|
message: `Found ${features.length} pending features`
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching pending features:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch pending features',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/features/status/:status - Get features by status
|
|
router.get('/features/status/:status', async (req, res) => {
|
|
try {
|
|
const { status } = req.params;
|
|
const limit = parseInt(req.query.limit) || 50;
|
|
const offset = parseInt(req.query.offset) || 0;
|
|
|
|
const validStatuses = ['pending', 'approved', 'rejected', 'duplicate'];
|
|
if (!validStatuses.includes(status)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid status',
|
|
message: `Status must be one of: ${validStatuses.join(', ')}`
|
|
});
|
|
}
|
|
|
|
console.log(`🔍 Admin: Fetching ${status} features (limit: ${limit}, offset: ${offset})`);
|
|
|
|
const features = await CustomFeature.getFeaturesByStatus(status, limit, offset);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: features,
|
|
count: features.length,
|
|
message: `Found ${features.length} ${status} features`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error fetching features by status:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch features by status',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/features/stats - Get feature statistics
|
|
router.get('/features/stats', async (req, res) => {
|
|
try {
|
|
console.log('📊 Admin: Fetching feature statistics...');
|
|
|
|
const stats = await CustomFeature.getFeatureStats();
|
|
const notificationCounts = await AdminNotification.getCounts();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
features: stats,
|
|
notifications: notificationCounts
|
|
},
|
|
message: 'Feature statistics retrieved successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error fetching feature stats:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch feature statistics',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/admin/features/:id/review - Review a feature
|
|
router.post('/features/:id/review', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { status, notes, canonical_feature_id, admin_reviewed_by } = req.body;
|
|
|
|
// Validate input
|
|
const schema = Joi.object({
|
|
status: Joi.string().valid('approved', 'rejected', 'duplicate').required(),
|
|
notes: Joi.string().optional(),
|
|
canonical_feature_id: Joi.string().uuid().optional(),
|
|
admin_reviewed_by: Joi.string().required()
|
|
});
|
|
|
|
const { error } = schema.validate(req.body);
|
|
if (error) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Validation error',
|
|
message: error.details[0].message
|
|
});
|
|
}
|
|
|
|
// Validate canonical_feature_id is provided for duplicate status
|
|
if (status === 'duplicate' && !canonical_feature_id) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Missing canonical feature ID',
|
|
message: 'Canonical feature ID is required when marking as duplicate'
|
|
});
|
|
}
|
|
|
|
console.log(`🔍 Admin: Reviewing feature ${id} with status: ${status}`);
|
|
|
|
// Get the feature first to get its name for notification
|
|
const feature = await CustomFeature.getById(id);
|
|
if (!feature) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Feature not found',
|
|
message: 'The specified feature does not exist'
|
|
});
|
|
}
|
|
|
|
// Review the feature
|
|
const reviewData = {
|
|
status,
|
|
admin_notes: notes,
|
|
canonical_feature_id,
|
|
admin_reviewed_by
|
|
};
|
|
|
|
const updatedFeature = await CustomFeature.reviewFeature(id, reviewData);
|
|
|
|
// Create notification
|
|
await AdminNotification.notifyFeatureReviewed(id, feature.name, status);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: updatedFeature,
|
|
message: `Feature "${feature.name}" has been ${status}`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error reviewing feature:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to review feature',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/features/similar - Find similar features
|
|
router.get('/features/similar', async (req, res) => {
|
|
try {
|
|
const { q: query, threshold = 0.7, limit = 5 } = req.query;
|
|
|
|
if (!query) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Query parameter required',
|
|
message: 'Please provide a query parameter "q"'
|
|
});
|
|
}
|
|
|
|
console.log(`🔍 Admin: Finding similar features for "${query}"`);
|
|
|
|
const similarFeatures = await similarityService.findSimilarFeatures(
|
|
query,
|
|
parseFloat(threshold),
|
|
parseInt(limit)
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: similarFeatures,
|
|
count: similarFeatures.length,
|
|
message: `Found ${similarFeatures.length} similar features`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error finding similar features:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to find similar features',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/admin/features/:id/synonyms - Add feature synonym
|
|
router.post('/features/:id/synonyms', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { synonym, created_by } = req.body;
|
|
|
|
// Validate input
|
|
const schema = Joi.object({
|
|
synonym: Joi.string().min(1).max(200).required(),
|
|
created_by: Joi.string().optional()
|
|
});
|
|
|
|
const { error } = schema.validate(req.body);
|
|
if (error) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Validation error',
|
|
message: error.details[0].message
|
|
});
|
|
}
|
|
|
|
console.log(`🔍 Admin: Adding synonym "${synonym}" to feature ${id}`);
|
|
|
|
const newSynonym = await similarityService.addSynonym(id, synonym, created_by || 'admin');
|
|
|
|
res.json({
|
|
success: true,
|
|
data: newSynonym,
|
|
message: `Synonym "${synonym}" added successfully`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error adding synonym:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to add synonym',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/features/:id/synonyms - Get feature synonyms
|
|
router.get('/features/:id/synonyms', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
console.log(`🔍 Admin: Getting synonyms for feature ${id}`);
|
|
|
|
const synonyms = await similarityService.getSynonyms(id);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: synonyms,
|
|
count: synonyms.length,
|
|
message: `Found ${synonyms.length} synonyms`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error getting synonyms:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get synonyms',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/notifications - Get admin notifications
|
|
router.get('/notifications', async (req, res) => {
|
|
try {
|
|
const { unread_only = 'false' } = req.query;
|
|
const limit = parseInt(req.query.limit) || 50;
|
|
const offset = parseInt(req.query.offset) || 0;
|
|
|
|
console.log(`🔍 Admin: Fetching notifications (unread_only: ${unread_only})`);
|
|
|
|
let notifications;
|
|
if (unread_only === 'true') {
|
|
notifications = await AdminNotification.getUnread(limit);
|
|
} else {
|
|
notifications = await AdminNotification.getAll(limit, offset);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: notifications,
|
|
count: notifications.length,
|
|
message: `Found ${notifications.length} notifications`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error fetching notifications:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch notifications',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/admin/notifications/:id/read - Mark notification as read
|
|
router.post('/notifications/:id/read', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
console.log(`🔍 Admin: Marking notification ${id} as read`);
|
|
|
|
const notification = await AdminNotification.markAsRead(id);
|
|
|
|
if (!notification) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Notification not found',
|
|
message: 'The specified notification does not exist'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: notification,
|
|
message: 'Notification marked as read'
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error marking notification as read:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to mark notification as read',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/admin/notifications/read-all - Mark all notifications as read
|
|
router.post('/notifications/read-all', async (req, res) => {
|
|
try {
|
|
console.log('🔍 Admin: Marking all notifications as read');
|
|
|
|
const count = await AdminNotification.markAllAsRead();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { count },
|
|
message: `${count} notifications marked as read`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error marking all notifications as read:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to mark all notifications as read',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// ---------- CUSTOM TEMPLATES ADMIN ROUTES ----------
|
|
|
|
// GET /api/admin/templates/pending - Get pending templates for review
|
|
router.get('/templates/pending', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit) || 50;
|
|
const offset = parseInt(req.query.offset) || 0;
|
|
|
|
console.log(`Admin: Fetching pending templates (limit: ${limit}, offset: ${offset})`);
|
|
|
|
const templates = await CustomTemplate.getPendingTemplates(limit, offset);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: templates,
|
|
count: templates.length,
|
|
message: `Found ${templates.length} pending templates`
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching pending templates:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch pending templates',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/templates/status/:status - Get templates by status
|
|
router.get('/templates/status/:status', async (req, res) => {
|
|
try {
|
|
const { status } = req.params;
|
|
const limit = parseInt(req.query.limit) || 50;
|
|
const offset = parseInt(req.query.offset) || 0;
|
|
|
|
const validStatuses = ['pending', 'approved', 'rejected', 'duplicate'];
|
|
if (!validStatuses.includes(status)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid status',
|
|
message: `Status must be one of: ${validStatuses.join(', ')}`
|
|
});
|
|
}
|
|
|
|
console.log(`🔍 Admin: Fetching ${status} templates (limit: ${limit}, offset: ${offset})`);
|
|
|
|
const templates = await CustomTemplate.getTemplatesByStatus(status, limit, offset);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: templates,
|
|
count: templates.length,
|
|
message: `Found ${templates.length} ${status} templates`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error fetching templates by status:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch templates by status',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/admin/templates/:id/review - Review custom template
|
|
router.post('/templates/:id/review', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { status, admin_notes, canonical_template_id } = req.body;
|
|
|
|
if (!status) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Status required',
|
|
message: 'Status is required for template review'
|
|
});
|
|
}
|
|
|
|
const validStatuses = ['approved', 'rejected', 'duplicate'];
|
|
if (!validStatuses.includes(status)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid status',
|
|
message: `Status must be one of: ${validStatuses.join(', ')}`
|
|
});
|
|
}
|
|
|
|
console.log(`🔍 Admin: Reviewing template ${id} with status: ${status}`);
|
|
|
|
// If approving, also create a main template entry and link it, atomically
|
|
if (status === 'approved') {
|
|
const existingCustom = await CustomTemplate.getById(id);
|
|
if (!existingCustom) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Template not found',
|
|
message: 'The specified template does not exist'
|
|
});
|
|
}
|
|
// Create in main templates
|
|
const Template = require('../models/template');
|
|
const payload = {
|
|
type: existingCustom.type,
|
|
title: existingCustom.title,
|
|
description: existingCustom.description,
|
|
icon: existingCustom.icon,
|
|
category: existingCustom.category,
|
|
gradient: existingCustom.gradient,
|
|
border: existingCustom.border,
|
|
text: existingCustom.text,
|
|
subtext: existingCustom.subtext,
|
|
};
|
|
const created = await Template.create(payload);
|
|
// Update custom template flags and link canonical_template_id
|
|
const updatedCustom = await CustomTemplate.reviewTemplate(id, {
|
|
status: 'approved',
|
|
admin_notes,
|
|
canonical_template_id: created.id,
|
|
admin_reviewed_by: req.user.username || req.user.email
|
|
});
|
|
return res.json({
|
|
success: true,
|
|
data: { custom_template: updatedCustom, template: created },
|
|
message: `Template '${created.title}' created and custom template approved`
|
|
});
|
|
}
|
|
|
|
const template = await CustomTemplate.reviewTemplate(id, {
|
|
status,
|
|
admin_notes,
|
|
canonical_template_id,
|
|
admin_reviewed_by: req.user.username || req.user.email
|
|
});
|
|
|
|
if (!template) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Template not found',
|
|
message: 'The specified template does not exist'
|
|
});
|
|
}
|
|
|
|
// If approved, activate the mirrored template
|
|
if (status === 'approved') {
|
|
try {
|
|
const Template = require('../models/template');
|
|
const mirroredTemplate = await Template.getByType(`custom_${id}`);
|
|
if (mirroredTemplate) {
|
|
await mirroredTemplate.update({ is_active: true });
|
|
}
|
|
} catch (activateErr) {
|
|
console.error('Failed to activate approved template:', activateErr.message);
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: template,
|
|
message: `Template '${template.title}' ${status} successfully`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error reviewing template:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to review template',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/templates/stats - Get custom template statistics
|
|
router.get('/templates/stats', async (req, res) => {
|
|
try {
|
|
console.log('📊 Admin: Fetching custom template statistics...');
|
|
|
|
const stats = await CustomTemplate.getTemplateStats();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: stats,
|
|
message: 'Custom template statistics retrieved successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error fetching custom template stats:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch custom template statistics',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/custom-features - Get all custom features (Admin only)
|
|
router.get('/custom-features', async (req, res) => {
|
|
try {
|
|
const { status, limit = 50, offset = 0 } = req.query;
|
|
const limitNum = parseInt(limit);
|
|
const offsetNum = parseInt(offset);
|
|
|
|
console.log(`🔍 Admin: Fetching custom features (status: ${status || 'all'}, limit: ${limitNum}, offset: ${offsetNum})`);
|
|
|
|
let features;
|
|
if (status) {
|
|
features = await CustomFeature.getFeaturesByStatus(status, limitNum, offsetNum);
|
|
} else {
|
|
features = await CustomFeature.getAllFeatures(limitNum, offsetNum);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: features,
|
|
count: features.length,
|
|
message: `Found ${features.length} custom features`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error fetching custom features:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch custom features',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/admin/custom-features/:id/review - Review custom feature (Admin only)
|
|
router.post('/custom-features/:id/review', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { status, admin_notes, canonical_feature_id } = req.body;
|
|
|
|
if (!status) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Status required',
|
|
message: 'Status is required for feature review'
|
|
});
|
|
}
|
|
|
|
const validStatuses = ['approved', 'rejected', 'duplicate'];
|
|
if (!validStatuses.includes(status)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid status',
|
|
message: `Status must be one of: ${validStatuses.join(', ')}`
|
|
});
|
|
}
|
|
|
|
console.log(`🔍 Admin: Reviewing custom feature ${id} with status: ${status}`);
|
|
|
|
const feature = await CustomFeature.reviewFeature(id, {
|
|
status,
|
|
admin_notes,
|
|
canonical_feature_id,
|
|
admin_reviewed_by: req.user.username || req.user.email
|
|
});
|
|
|
|
if (!feature) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Feature not found',
|
|
message: 'The specified feature does not exist'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: feature,
|
|
message: `Feature '${feature.name}' ${status} successfully`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error reviewing custom feature:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to review custom feature',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/admin/custom-templates - Get all custom templates (Admin only)
|
|
router.get('/custom-templates', async (req, res) => {
|
|
try {
|
|
const { status, limit = 50, offset = 0 } = req.query;
|
|
const limitNum = parseInt(limit);
|
|
const offsetNum = parseInt(offset);
|
|
|
|
console.log(`🔍 Admin: Fetching custom templates (status: ${status || 'all'}, limit: ${limitNum}, offset: ${offsetNum})`);
|
|
|
|
let templates;
|
|
if (status) {
|
|
templates = await CustomTemplate.getTemplatesByStatus(status, limitNum, offsetNum);
|
|
} else {
|
|
templates = await CustomTemplate.getAllTemplates(limitNum, offsetNum);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: templates,
|
|
count: templates.length,
|
|
message: `Found ${templates.length} custom templates`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error fetching custom templates:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch custom templates',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// POST /api/admin/custom-templates/:id/review - Review custom template (Admin only)
|
|
router.post('/custom-templates/:id/review', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { status, admin_notes, canonical_template_id } = req.body;
|
|
|
|
if (!status) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Status required',
|
|
message: 'Status is required for template review'
|
|
});
|
|
}
|
|
|
|
const validStatuses = ['approved', 'rejected', 'duplicate'];
|
|
if (!validStatuses.includes(status)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid status',
|
|
message: `Status must be one of: ${validStatuses.join(', ')}`
|
|
});
|
|
}
|
|
|
|
console.log(`🔍 Admin: Reviewing custom template ${id} with status: ${status}`);
|
|
|
|
const template = await CustomTemplate.reviewTemplate(id, {
|
|
status,
|
|
admin_notes,
|
|
canonical_template_id,
|
|
admin_reviewed_by: req.user.username || req.user.email
|
|
});
|
|
|
|
if (!template) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Template not found',
|
|
message: 'The specified template does not exist'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: template,
|
|
message: `Template '${template.title}' ${status} successfully`
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error reviewing custom template:', error.message);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to review custom template',
|
|
message: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|