codenuk_backend_mine/services/template-manager/src/routes/admin.js
2025-09-15 14:55:50 +05:30

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;