Initial commit: teaching institution management API.

Express/MongoDB backend with JWT auth, RBAC, S3 uploads, notifications, and scheduled jobs.
This commit is contained in:
2026-08-09 04:18:08 +02:00
commit f04c797be6
107 changed files with 9190 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
// /components/auth/authController.js
const catchAsync = require('../../utils/catchAsync');
const authService = require('./authService');
const { successResponse } = require('../../utils/apiResponse');
exports.login = catchAsync(async (req, res, next) => {
const { username, password } = req.body;
const result = await authService.login(username, password);
return successResponse(res, 200, 'Login successful', result);
});
exports.refresh = catchAsync(async (req, res, next) => {
const { refreshToken } = req.body;
const result = await authService.refreshToken(refreshToken);
return successResponse(res, 200, 'Tokens refreshed successfully', result);
});
exports.logout = catchAsync(async (req, res, next) => {
const { refreshToken } = req.body;
const userId = req.user?._id;
await authService.logout(userId, refreshToken);
return successResponse(res, 200, 'Logout successful');
});
+14
View File
@@ -0,0 +1,14 @@
// /components/auth/authRoutes.js
const express = require('express');
const authController = require('./authController');
const { validateLogin, validateRefresh } = require('./authValidator');
const authMiddleware = require('../../middlewares/authMiddleware');
const router = express.Router();
router.post('/login', validateLogin, authController.login);
router.post('/refresh', validateRefresh, authController.refresh);
router.post('/logout', authMiddleware, authController.logout);
module.exports = router;
+110
View File
@@ -0,0 +1,110 @@
// /components/auth/authService.js
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const User = require('../users/userModel');
const config = require('../../config/config');
const AppError = require('../../utils/AppError');
const generateTokens = (user) => {
const payload = {
id: user._id,
username: user.username,
role: user.role?._id || user.role
};
const accessToken = jwt.sign(payload, config.JWT_ACCESS_SECRET, {
expiresIn: config.JWT_ACCESS_EXPIRES_IN
});
const refreshToken = jwt.sign({ id: user._id }, config.JWT_REFRESH_SECRET, {
expiresIn: config.JWT_REFRESH_EXPIRES_IN
});
return { accessToken, refreshToken };
};
const login = async (username, password) => {
const user = await User.findOne({ username }).populate('role');
if (!user || !user.isActive) {
throw new AppError('INVALID_CREDENTIALS');
}
const isMatch = await bcrypt.compare(password, user.passwordHash);
if (!isMatch) {
throw new AppError('INVALID_CREDENTIALS');
}
const { accessToken, refreshToken: refreshTokenString } = generateTokens(user);
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7);
user.refreshTokens.push({ token: refreshTokenString, expiresAt });
await user.save();
const userObject = user.toObject();
delete userObject.passwordHash;
delete userObject.refreshTokens;
return {
user: userObject,
accessToken,
refreshToken: refreshTokenString
};
};
const refreshToken = async (refreshTokenString) => {
let decoded;
try {
decoded = jwt.verify(refreshTokenString, config.JWT_REFRESH_SECRET);
} catch (err) {
throw new AppError('INVALID_REFRESH_TOKEN');
}
const user = await User.findById(decoded.id).populate('role');
if (!user || !user.isActive) {
throw new AppError('INVALID_REFRESH_TOKEN');
}
const tokenIndex = user.refreshTokens.findIndex(rt => rt.token === refreshTokenString);
if (tokenIndex === -1) {
throw new AppError('INVALID_REFRESH_TOKEN');
}
if (new Date() > new Date(user.refreshTokens[tokenIndex].expiresAt)) {
user.refreshTokens.splice(tokenIndex, 1);
await user.save();
throw new AppError('TOKEN_EXPIRED');
}
user.refreshTokens.splice(tokenIndex, 1);
const { accessToken, refreshToken: newRefreshToken } = generateTokens(user);
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7);
user.refreshTokens.push({ token: newRefreshToken, expiresAt });
await user.save();
return {
accessToken,
refreshToken: newRefreshToken
};
};
const logout = async (userId, refreshTokenString) => {
const user = await User.findById(userId);
if (user) {
user.refreshTokens = user.refreshTokens.filter(rt => rt.token !== refreshTokenString);
await user.save();
}
return true;
};
module.exports = {
login,
refreshToken,
logout
};
+6
View File
@@ -0,0 +1,6 @@
// Stub validator — pass-through middleware (no validation yet)
const passThrough = (req, res, next) => next();
module.exports = new Proxy({}, {
get: () => passThrough
});