first commit

This commit is contained in:
2025-11-06 11:08:59 +01:00
commit 3c5117c2c3
85 changed files with 13275 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
const jwt = require('jsonwebtoken');
const { User } = require('../models');
const logger = require('../utils/logger');
const authenticate = async (req, res, next) => {
try {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({
success: false,
error: 'Access denied. No token provided.'
});
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByPk(decoded.userId);
if (!user || !user.is_active) {
return res.status(401).json({
success: false,
error: 'Invalid token or user not found'
});
}
req.user = { userId: user.id, role: user.role };
next();
} catch (error) {
if (error.name === 'JsonWebTokenError') {
return res.status(401).json({
success: false,
error: 'Invalid token'
});
}
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
success: false,
error: 'Token expired'
});
}
logger.error('Authentication error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const authorize = (...roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({
success: false,
error: 'Access denied. Authentication required.'
});
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
error: 'Access denied. Insufficient permissions.'
});
}
next();
};
};
module.exports = {
authenticate,
authorize
};
+58
View File
@@ -0,0 +1,58 @@
const logger = require('../utils/logger');
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Log error
logger.error(err);
// Mongoose bad ObjectId
if (err.name === 'CastError') {
const message = 'Resource not found';
error = { message, statusCode: 404 };
}
// Mongoose duplicate key
if (err.code === 11000) {
const message = 'Duplicate field value entered';
error = { message, statusCode: 400 };
}
// Mongoose validation error
if (err.name === 'ValidationError') {
const message = Object.values(err.errors).map(val => val.message).join(', ');
error = { message, statusCode: 400 };
}
// Sequelize validation error
if (err.name === 'SequelizeValidationError') {
const message = err.errors.map(e => e.message).join(', ');
error = { message, statusCode: 400 };
}
// Sequelize unique constraint error
if (err.name === 'SequelizeUniqueConstraintError') {
const message = 'Duplicate field value entered';
error = { message, statusCode: 400 };
}
// JWT errors
if (err.name === 'JsonWebTokenError') {
const message = 'Invalid token';
error = { message, statusCode: 401 };
}
if (err.name === 'TokenExpiredError') {
const message = 'Token expired';
error = { message, statusCode: 401 };
}
res.status(error.statusCode || 500).json({
success: false,
error: error.message || 'Server Error',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
};
module.exports = errorHandler;
+24
View File
@@ -0,0 +1,24 @@
const { RateLimiterMemory } = require('rate-limiter-flexible');
const rateLimiter = new RateLimiterMemory({
keyPrefix: 'middleware',
points: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100,
duration: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 900, // 15 minutes
});
const rateLimiterMiddleware = async (req, res, next) => {
try {
const key = req.ip;
await rateLimiter.consume(key);
next();
} catch (rejRes) {
const secs = Math.round(rejRes.msBeforeNext / 1000) || 1;
res.set('Retry-After', String(secs));
res.status(429).json({
error: 'Too Many Requests',
retryAfter: secs
});
}
};
module.exports = rateLimiterMiddleware;
+87
View File
@@ -0,0 +1,87 @@
const Joi = require('joi');
const validate = (schema) => {
return (req, res, next) => {
const { error } = schema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
error: error.details[0].message
});
}
next();
};
};
// Validation schemas
const schemas = {
register: Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
firstName: Joi.string().min(2).required(),
lastName: Joi.string().min(2).required(),
role: Joi.string().valid('user', 'admin', 'expert').optional()
}),
login: Joi.object({
email: Joi.string().email().required(),
password: Joi.string().required()
}),
updateProfile: Joi.object({
firstName: Joi.string().min(2).optional(),
lastName: Joi.string().min(2).optional(),
preferences: Joi.object().optional()
}),
createConversation: Joi.object({
title: Joi.string().min(1).optional()
}),
sendMessage: Joi.object({
conversationId: Joi.string().uuid().required(),
content: Joi.string().min(1).required(),
messageType: Joi.string().valid('text', 'plan', 'execution', 'feedback').optional()
}),
submitFeedback: Joi.object({
messageId: Joi.string().uuid().optional(),
feedbackType: Joi.string().valid('positive', 'negative', 'correction', 'suggestion').required(),
rating: Joi.number().min(1).max(5).optional(),
comment: Joi.string().optional(),
correctedContent: Joi.string().optional()
}),
createModelVersion: Joi.object({
modelName: Joi.string().min(1).required(),
modelType: Joi.string().valid('MODEL1', 'QUERYMODEL').required(),
baseModel: Joi.string().min(1).required(),
fineTuningMethod: Joi.string().valid('SFT', 'DPO', 'PPO', 'LoRA', 'QLoRA').optional(),
hyperparameters: Joi.object().optional()
}),
executeTool: Joi.object({
planId: Joi.string().uuid().required(),
toolName: Joi.string().min(1).required(),
toolType: Joi.string().valid('query_expander', 'extraction', 'report1', 'report2', 'web_search', 'encyclopedia_pdf').required(),
inputParameters: Joi.object().required()
}),
generatePlan: Joi.object({
query: Joi.string().min(1).required(),
conversationId: Joi.string().uuid().optional(),
context: Joi.object().optional()
}),
executePlan: Joi.object({
planId: Joi.string().uuid().required(),
options: Joi.object().optional()
})
};
module.exports = {
validate,
schemas
};