init Backend

This commit is contained in:
JakkrapartXD 2026-01-08 06:51:33 +00:00
parent 08a4e0d8fa
commit 924000b084
29 changed files with 10080 additions and 13 deletions

View file

@ -0,0 +1,91 @@
const { verifyToken, extractToken } = require('../utils/jwt');
const prisma = require('../config/database');
const logger = require('../config/logger');
/**
* Authentication middleware
* Verifies JWT token and attaches user to request
*/
async function authenticate(req, res, next) {
try {
const token = extractToken(req.headers.authorization);
if (!token) {
return res.status(401).json({
error: {
code: 'UNAUTHORIZED',
message: 'Authentication required',
},
});
}
const decoded = verifyToken(token);
// Fetch user from database
const user = await prisma.user.findUnique({
where: { id: decoded.userId },
include: { role: true },
});
if (!user || !user.is_active) {
return res.status(401).json({
error: {
code: 'UNAUTHORIZED',
message: 'Invalid or inactive user',
},
});
}
// Attach user to request
req.user = {
id: user.id,
username: user.username,
email: user.email,
role: user.role,
};
next();
} catch (error) {
logger.error('Authentication error:', error.message);
return res.status(401).json({
error: {
code: 'UNAUTHORIZED',
message: 'Invalid or expired token',
},
});
}
}
/**
* Authorization middleware
* Checks if user has required role
* @param {string[]} allowedRoles - Array of allowed role codes
*/
function authorize(...allowedRoles) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({
error: {
code: 'UNAUTHORIZED',
message: 'Authentication required',
},
});
}
if (!allowedRoles.includes(req.user.role.code)) {
return res.status(403).json({
error: {
code: 'FORBIDDEN',
message: 'Insufficient permissions',
},
});
}
next();
};
}
module.exports = {
authenticate,
authorize,
};

View file

@ -0,0 +1,85 @@
const logger = require('../config/logger');
/**
* Global error handler middleware
*/
function errorHandler(err, req, res, next) {
logger.error('Error:', {
message: err.message,
stack: err.stack,
url: req.url,
method: req.method,
});
// Prisma errors
if (err.code && err.code.startsWith('P')) {
if (err.code === 'P2002') {
return res.status(409).json({
error: {
code: 'DUPLICATE_ENTRY',
message: 'A record with this value already exists',
field: err.meta?.target?.[0],
},
});
}
if (err.code === 'P2025') {
return res.status(404).json({
error: {
code: 'NOT_FOUND',
message: 'Record not found',
},
});
}
}
// Validation errors
if (err.isJoi || err.name === 'ValidationError') {
return res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: err.message,
details: err.details?.map((d) => ({
field: d.path.join('.'),
message: d.message,
})),
},
});
}
// JWT errors
if (err.name === 'JsonWebTokenError' || err.name === 'TokenExpiredError') {
return res.status(401).json({
error: {
code: 'UNAUTHORIZED',
message: 'Invalid or expired token',
},
});
}
// Default error
const statusCode = err.statusCode || 500;
res.status(statusCode).json({
error: {
code: err.code || 'INTERNAL_ERROR',
message: err.message || 'An unexpected error occurred',
},
});
}
/**
* 404 Not Found handler
*/
function notFoundHandler(req, res) {
res.status(404).json({
error: {
code: 'NOT_FOUND',
message: `Route ${req.method} ${req.url} not found`,
},
});
}
module.exports = {
errorHandler,
notFoundHandler,
};