40227-vm/backend/src/index.ts
2026-06-10 18:27:19 +02:00

196 lines
7.4 KiB
TypeScript

import express from 'express';
import cors from 'cors';
import passport from 'passport';
import path from 'path';
import fs from 'fs';
import swaggerUI from 'swagger-ui-express';
import swaggerJsDoc from 'swagger-jsdoc';
import config from '@/shared/config';
import csrfOrigin from '@/middlewares/csrf-origin';
import ForbiddenError from '@/shared/errors/forbidden';
import {
errorHandler,
notFoundHandler,
} from '@/middlewares/error-handler';
import logger from '@/shared/logger';
import '@/auth/auth';
import authRoutes from '@/routes/auth';
import fileRoutes from '@/routes/file';
import searchRoutes from '@/routes/search';
import publicCampusesRoutes from '@/routes/public_campuses';
import publicContentCatalogRoutes from '@/routes/public_content_catalog';
import contentCatalogRoutes from '@/routes/content_catalog';
import usersRoutes from '@/routes/users';
import rolesRoutes from '@/routes/roles';
import permissionsRoutes from '@/routes/permissions';
import organizationsRoutes from '@/routes/organizations';
import campusesRoutes from '@/routes/campuses';
import academicYearsRoutes from '@/routes/academic_years';
import gradesRoutes from '@/routes/grades';
import subjectsRoutes from '@/routes/subjects';
import studentsRoutes from '@/routes/students';
import guardiansRoutes from '@/routes/guardians';
import staffRoutes from '@/routes/staff';
import classesRoutes from '@/routes/classes';
import classEnrollmentsRoutes from '@/routes/class_enrollments';
import classSubjectsRoutes from '@/routes/class_subjects';
import timetablesRoutes from '@/routes/timetables';
import timetablePeriodsRoutes from '@/routes/timetable_periods';
import attendanceSessionsRoutes from '@/routes/attendance_sessions';
import attendanceRecordsRoutes from '@/routes/attendance_records';
import feePlansRoutes from '@/routes/fee_plans';
import invoicesRoutes from '@/routes/invoices';
import paymentsRoutes from '@/routes/payments';
import assessmentsRoutes from '@/routes/assessments';
import assessmentResultsRoutes from '@/routes/assessment_results';
import messagesRoutes from '@/routes/messages';
import messageRecipientsRoutes from '@/routes/message_recipients';
import documentsRoutes from '@/routes/documents';
import frameEntriesRoutes from '@/routes/frame_entries';
import userProgressRoutes from '@/routes/user_progress';
import safetyQuizResultsRoutes from '@/routes/safety_quiz_results';
import walkthroughCheckinsRoutes from '@/routes/walkthrough_checkins';
import communicationsRoutes from '@/routes/communications';
import personalityQuizResultsRoutes from '@/routes/personality_quiz_results';
import campusAttendanceRoutes from '@/routes/campus_attendance';
import staffAttendanceRoutes from '@/routes/staff_attendance';
const app = express();
const authenticated = passport.authenticate('jwt', { session: false });
function getBaseUrl(url: string | undefined): string {
if (!url) return '';
return url.endsWith('/api') ? url.slice(0, -4) : url;
}
const swaggerOptions: swaggerJsDoc.Options = {
definition: {
openapi: '3.0.0',
info: {
version: '1.0.0',
title: 'School Chain Manager',
description:
'School Chain Manager Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.',
},
servers: [
{
url: getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || config.swaggerUrl,
description: 'Development server',
},
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
responses: {
UnauthorizedError: {
description: 'Access token is missing or invalid',
},
},
},
security: [{ bearerAuth: [] }],
},
apis: ['./src/routes/*.ts'],
};
const specs = swaggerJsDoc(swaggerOptions);
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(specs));
app.use(
cors({
credentials: true,
origin(origin, callback) {
if (
!origin ||
config.auth.allowAllOrigins ||
config.auth.allowedOrigins.includes(origin)
) {
callback(null, origin || true);
return;
}
callback(new ForbiddenError());
},
}),
);
app.use(express.json());
app.use('/api', csrfOrigin);
app.use('/api/auth', authRoutes);
app.use('/api/file', fileRoutes);
app.use('/api/public/campuses', publicCampusesRoutes);
app.use('/api/public/content-catalog', publicContentCatalogRoutes);
app.enable('trust proxy');
app.use('/api/users', authenticated, usersRoutes);
app.use('/api/roles', authenticated, rolesRoutes);
app.use('/api/permissions', authenticated, permissionsRoutes);
app.use('/api/organizations', authenticated, organizationsRoutes);
app.use('/api/campuses', authenticated, campusesRoutes);
app.use('/api/academic_years', authenticated, academicYearsRoutes);
app.use('/api/grades', authenticated, gradesRoutes);
app.use('/api/subjects', authenticated, subjectsRoutes);
app.use('/api/students', authenticated, studentsRoutes);
app.use('/api/guardians', authenticated, guardiansRoutes);
app.use('/api/staff', authenticated, staffRoutes);
app.use('/api/classes', authenticated, classesRoutes);
app.use('/api/class_enrollments', authenticated, classEnrollmentsRoutes);
app.use('/api/class_subjects', authenticated, classSubjectsRoutes);
app.use('/api/timetables', authenticated, timetablesRoutes);
app.use('/api/timetable_periods', authenticated, timetablePeriodsRoutes);
app.use('/api/attendance_sessions', authenticated, attendanceSessionsRoutes);
app.use('/api/attendance_records', authenticated, attendanceRecordsRoutes);
app.use('/api/fee_plans', authenticated, feePlansRoutes);
app.use('/api/invoices', authenticated, invoicesRoutes);
app.use('/api/payments', authenticated, paymentsRoutes);
app.use('/api/assessments', authenticated, assessmentsRoutes);
app.use('/api/assessment_results', authenticated, assessmentResultsRoutes);
app.use('/api/messages', authenticated, messagesRoutes);
app.use('/api/message_recipients', authenticated, messageRecipientsRoutes);
app.use('/api/documents', authenticated, documentsRoutes);
app.use('/api/frame_entries', authenticated, frameEntriesRoutes);
app.use('/api/user_progress', authenticated, userProgressRoutes);
app.use('/api/safety_quiz_results', authenticated, safetyQuizResultsRoutes);
app.use('/api/walkthrough_checkins', authenticated, walkthroughCheckinsRoutes);
app.use('/api/communications', authenticated, communicationsRoutes);
app.use(
'/api/personality_quiz_results',
authenticated,
personalityQuizResultsRoutes,
);
app.use('/api/campus_attendance', authenticated, campusAttendanceRoutes);
app.use('/api/staff_attendance', authenticated, staffAttendanceRoutes);
app.use('/api/content-catalog', authenticated, contentCatalogRoutes);
app.use('/api/search', authenticated, searchRoutes);
// Unmatched API routes → centralized 404 (the SPA fallback below handles the rest).
app.use('/api', notFoundHandler);
const __dirname = import.meta.dirname;
const publicDir = path.join(__dirname, '../public');
if (fs.existsSync(publicDir)) {
app.use('/', express.static(publicDir));
app.get('/*splat', (_request, response) => {
response.sendFile(path.resolve(publicDir, 'index.html'));
});
}
// Terminal error middleware — must be registered after all routes/middleware.
app.use(errorHandler);
const PORT = config.serverPort;
app.listen(PORT, () => {
logger.info(`Listening on port ${PORT}`);
});
export default app;