Source: app.js

/**
 * Diese Datei initialisiert und konfiguriert den Server, einschließlich Middleware, Routen, Datenbankverbindungen und Sicherheitskonfigurationen für eine vollständige Backend-Anwendung.
 * 
 * @file app.js - Express Server Hauptanwendung
 * @author Farah, Ayoub, Luca, Miray, Ilyass, Lennart
 * @copyright 2024
 * @requires cors
 * @requires express
 * @requires body-parser
 * @requires path
 * @requires express-session
 * @requires ./sequelize.config
 */

const cors = require("cors");
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const path = require("path");
const session = require("express-session");
const sequelize = require("./sequelize.config.js");

// Import routes
const authRoutes = require('./backend/routes/authRoutes');
const docUploadRoutes = require("./backend/routes/docUploadRoutes");
const foldersRoutes = require("./backend/routes/foldersRoutes");
const semanticSearchRoutes = require('./backend/routes/semanticSearchRoutes');
const adminRoutes = require('./backend/routes/adminRoutes');
const passwordResetRoutes = require('./backend/models/passwordReset');
const {registerUser, verifyUserCode} = require ('./backend/models/userRegistrationToDB.js');
const monitorRoutes = require('./backend/routes/monitorRoutes.js');

// Import models
const User = require("./database/User");
const Folder = require("./database/Folder");
const File = require("./database/File");
const UserRole = require("./database/UserRole");
const UserRoleMapping = require("./database/UserRoleMapping");

const PORT = process.env.PORT || 3000;

/**
 * CORS-Konfiguration
 * @name CORSConfiguration
 * @memberof module:middleware
 * @property {string} origin - Erlaubte Origin für CORS
 * @property {string[]} methods - Erlaubte HTTP-Methoden
 * @property {boolean} credentials - Erlaubt Credentials in CORS-Requests
 */
app.use(
  cors({
    origin: "http://localhost:5173",
    methods: ["POST", "PUT", "GET", "DELETE", "OPTIONS", "HEAD"],
    credentials: true,
  })
);

// Basic security headers
app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; img-src 'self' data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self' localhost:* ws://localhost:*"
  );
  next();
});

/**
 * Body-Parser Konfiguration für JSON-Verarbeitung
 * @name BodyParserConfiguration
 * @memberof module:middleware
 */
app.use(express.json());

/**
 * Session-Konfiguration für Express
 * @name SessionConfiguration
 * @memberof module:middleware
 * @property {string} name - Name der Session-ID
 * @property {string} secret - Geheimer Schlüssel für Session-Verschlüsselung
 * @property {boolean} resave - Verhindert das Neu-Speichern unmodifizierter Sessions
 * @property {boolean} saveUninitialized - Verhindert Speichern nicht initialisierter Sessions
 * @property {Object} cookie - Cookie-Konfigurationen
 */
app.use(
  session({
    name: "userId",
    secret: "your_secret_key",
    resave: false,
    saveUninitialized: false,
    cookie: {
      sameSite: false,
      secure: false,
      httpOnly: true,
      maxAge: 24 * 60 * 60 * 1000,
    },
  })
);

/**
 * Modell-Beziehungsdefinitionen
 * @namespace ModelRelationships
 * @description Definiert die Beziehungen zwischen den Datenbank-Modellen
 * @property {Object} User.hasMany.Folder - Ein-zu-viele Beziehung zwischen User und Folder
 * @property {Object} Folder.belongsTo.User - Viele-zu-eins Beziehung zwischen Folder und User
 * @property {Object} Folder.hasMany.Folder - Selbstreferenzierende Beziehung für Unterordner
 * @property {Object} User.hasMany.File - Ein-zu-viele Beziehung zwischen User und File
 * @property {Object} Folder.hasMany.File - Ein-zu-viele Beziehung zwischen Folder und File
 * @property {Object} UserRoleMapping - Verknüpfungstabelle zwischen User und UserRole
 * 
 * Datenbank-Initialisierung und Beziehungsdefinition zwischen Models
 * @name DatabaseSetup
 * @async
 * @function
 * @throws {Error} Wenn die Datenbankverbindung oder Synchronisation fehlschlägt
 */
(async () => {
  try {
    await sequelize.authenticate();
    console.log("Database connection successful.");

    // Define Model Relationships
    User.hasMany(Folder, { foreignKey: "user_id", onDelete: "CASCADE" });
    Folder.belongsTo(User, { foreignKey: "user_id" });

    Folder.hasMany(Folder, { foreignKey: "parent_folder_id", as: "subfolders", onDelete: "SET NULL" });
    Folder.belongsTo(Folder, { foreignKey: "parent_folder_id", as: "parentFolder" });

    User.hasMany(File, { foreignKey: "user_id", onDelete: "CASCADE" });
    File.belongsTo(User, { foreignKey: "user_id" });

    Folder.hasMany(File, { foreignKey: "folder_id", onDelete: "SET NULL" });
    File.belongsTo(Folder, { foreignKey: "folder_id" });

    UserRoleMapping.belongsTo(User, { foreignKey: "user_id" });
    UserRoleMapping.belongsTo(UserRole, { foreignKey: "role_id" });
    
    User.belongsToMany(UserRole, { through: UserRoleMapping, foreignKey: "user_id" });
    UserRole.belongsToMany(User, { through: UserRoleMapping, foreignKey: "role_id" });

    await sequelize.sync();
    console.log("Database synchronization successful.");
  } catch (error) {
    console.error("Database error:", error);
  }
})();

/**
 * Authentifizierungs-Middleware zur Überprüfung der Benutzeranmeldung
 * @function authenticateMiddleware
 * @param {express.Request} req - Express Request Objekt
 * @param {express.Response} res - Express Response Objekt
 * @param {express.NextFunction} next - Express Next Middleware Funktion
 * @returns {void}
 */
const authenticateMiddleware = (req, res, next) => {
  if (req.session.userId) {
    next();
  } else {
    res.status(401).json({ message: "Unauthorized: Please log in" });
  }
};

app.use('/api/admin', adminRoutes);

/**
 * Route zum Abrufen des Admin-Status
 * @name get/api/admin/status
 * @function
 * @memberof module:routes
 * @param {express.Request} req - Express Request Objekt
 * @param {express.Response} res - Express Response Objekt
 */
app.get("/api/admin/status", (req, res) => {
  if (req.session.isAdmin) {
    res.json({ isAdmin: true });
  } else {
    res.json({ isAdmin: false });
  }
});

/**
 * Route zum Abrufen der aktuellen Benutzerinformationen
 * @name get/api/current-user
 * @function
 * @param {express.Request} req - Express Request Objekt
 * @param {express.Response} res - Express Response Objekt
 * @returns {Object} Objekt mit userId und isAdmin Status
 */
app.get("/api/current-user", authenticateMiddleware, (req, res) => {
  res.json({
    userId: req.session.userId,
    isAdmin: req.session.isAdmin || false,
  });
});

/**
 * Registrierungsroute für neue Benutzer
 * @name post/register
 * @function
 * @async
 * @param {express.Request} req - Express Request Objekt mit username, email und password im Body
 * @param {express.Response} res - Express Response Objekt
 * @throws {Error} Wenn die Registrierung fehlschlägt
 */
app.post("/register", async (req, res) => {
  console.log("Received registration request:", req.body);
  const { username, email, password } = req.body;

  try {
    const userId = await registerUser(username, email, password);
    console.log("User registered successfully:", userId);
    res.status(201).json({
      message: "User registered successfully. Please log in.",
      userId,
    });
  } catch (error) {
    console.error("Error registering user:", error);
    if (error.message === "Username or email already exists") {
      res.status(400).json({ message: error.message });
    } else if (error.message === "Failed to insert user: No ID returned") {
      res.status(500).json({
        message:
          "User was created but an error occurred. Please contact support.",
      });
    } else {
      res.status(500).json({
        message: "An unexpected error occurred. Please try again later.",
      });
    }
  }
});

/**
 * Verifizierungsroute für Benutzer-Codes
 * @name post/api/verify-code
 * @function
 * @async
 * @param {express.Request} req - Express Request Objekt mit email und verificationCode im Body
 * @param {express.Response} res - Express Response Objekt
 * @throws {Error} Wenn die Verifizierung fehlschlägt
 */
app.post('/api/verify-code', async (req, res) => {
    const { email, verificationCode } = req.body;

    if (!email || !verificationCode) {
        return res.status(400).json({ message: 'Email und verification key sind notwendig' });
    }

    try {
        const result = await verifyUserCode(email, verificationCode);

        if (result.success) {
            res.status(200).json({ message: result.message });
        } else {
            res.status(400).json({ message: result.message });
        }
    } catch (error) {
        console.error(error);
        res.status(500).json({ message: 'An error occurred during verification' });
    }
});

// Route Middleware
app.use('/auth', authRoutes);
app.use('/api/admin', adminRoutes);
app.use("/docupload", authenticateMiddleware, docUploadRoutes);
app.use("/folders", authenticateMiddleware, foldersRoutes);
app.use("/search", authenticateMiddleware, semanticSearchRoutes);
app.use("/passwordReset", passwordResetRoutes);
app.use('/monitor', monitorRoutes);

app.use(express.static(path.join(__dirname, "frontend", "dist")));

/**
 * Catch-all Route für Client-seitiges Routing
 * @name get/*
 * @function
 * @param {express.Request} req - Express Request Objekt
 * @param {express.Response} res - Express Response Objekt
 */
app.get("*", (req, res) => {
  console.log(`Catch-All Route hit: ${req.url}`);
  res.sendFile(path.join(__dirname, "frontend", "dist", "index.html"));
});

/**
 * Server-Start Konfiguration
 * @name ServerStart
 * @function
 * @param {number} PORT - Der Port auf dem der Server läuft
 * @listens {number} PORT
 */
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});