Files
Jellystat/backend/server.js
CyferShepard f70bce6b9a Version Bump 1.1.0 => 1.1.1
Added Dynamic API loader framework for Emby/Jellyfin switching, Emby API is still WIP, DO NOT USE as per #133

Reworked ome pages for correct url mapping of emby external links

Added IS_JELLYFIN flag to config endpoint to indicate if server is displaying Emby or Jellyfin Data

Fix for #218 Require Login set to false still displays Login Page until reload

New feat: Grouped Recently added Episodes under Seasons and Episode count on Home page. Toggle to revert back to ugrouped display will be added later

Added middleware to infer param types in API to simplify value checks, eg bool or numeric parameters
2024-06-29 22:57:33 +02:00

176 lines
5.3 KiB
JavaScript

// core
require("dotenv").config();
const http = require("http");
const path = require("path");
const express = require("express");
const compression = require("compression");
const cors = require("cors");
const jwt = require("jsonwebtoken");
const knex = require("knex");
const swaggerUi = require("swagger-ui-express");
const swaggerDocument = require("./swagger.json");
// db
const dbInstance = require("./db");
const createdb = require("./create_database");
const knexConfig = require("./migrations");
// routes
const authRouter = require("./routes/auth");
const apiRouter = require("./routes/api");
const proxyRouter = require("./routes/proxy");
const { router: syncRouter } = require("./routes/sync");
const statsRouter = require("./routes/stats");
const backupRouter = require("./routes/backup");
const logRouter = require("./routes/logging");
const utilsRouter = require("./routes/utils");
// tasks
const ActivityMonitor = require("./tasks/ActivityMonitor");
const tasks = require("./tasks/tasks");
// websocket
const { setupWebSocketServer } = require("./ws");
const app = express();
const db = knex(knexConfig.development);
const PORT = 3000;
const LISTEN_IP = "0.0.0.0";
const JWT_SECRET = process.env.JWT_SECRET;
if (JWT_SECRET === undefined) {
console.log("JWT Secret cannot be undefined");
process.exit(1); // end the program with error status code
}
// middlewares
app.use(express.json()); // middleware to parse JSON request bodies
app.use(cors());
app.set("trust proxy", 1);
app.disable("x-powered-by");
app.use(compression());
function typeInferenceMiddleware(req, res, next) {
Object.keys(req.query).forEach((key) => {
const value = req.query[key];
if (value.toLowerCase() === "true" || value.toLowerCase() === "false") {
// Convert to boolean
req.query[key] = value.toLowerCase() === "true";
} else if (!isNaN(value) && value.trim() !== "") {
// Convert to number if it's a valid number
req.query[key] = +value;
}
});
next();
}
app.use(typeInferenceMiddleware);
// initiate routes
app.use("/auth", authRouter, () => {
/* #swagger.tags = ['Auth'] */
}); // mount the API router at /auth
app.use("/proxy", proxyRouter, () => {
/* #swagger.tags = ['Proxy']*/
}); // mount the API router at /proxy
app.use("/api", authenticate, apiRouter, () => {
/* #swagger.tags = ['API']*/
}); // mount the API router at /api, with JWT middleware
app.use("/sync", authenticate, syncRouter, () => {
/* #swagger.tags = ['Sync']*/
}); // mount the API router at /sync, with JWT middleware
app.use("/stats", authenticate, statsRouter, () => {
/* #swagger.tags = ['Stats']*/
}); // mount the API router at /stats, with JWT middleware
app.use("/backup", authenticate, backupRouter, () => {
/* #swagger.tags = ['Backup']*/
}); // mount the API router at /backup, with JWT middleware
app.use("/logs", authenticate, logRouter, () => {
/* #swagger.tags = ['Logs']*/
}); // mount the API router at /logs, with JWT middleware
app.use("/utils", authenticate, utilsRouter, () => {
/* #swagger.tags = ['Utils']*/
}); // mount the API router at /utils, with JWT middleware
// Swagger
app.use("/swagger", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
// for deployment of static page
const root = path.join(__dirname, "..", "dist");
app.use(express.static(root));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "..", "dist", "index.html"));
});
// JWT middleware
async function authenticate(req, res, next) {
const token = req.headers.authorization;
const apiKey = req.headers["x-api-token"] || req.query.apiKey;
if (!token && !apiKey) {
return res.status(401).json({
message: "Authentication failed. No token or API key provided.",
});
}
if (token) {
const extracted_token = token.split(" ")[1];
if (!extracted_token || extracted_token === "null") {
return res.sendStatus(403);
}
try {
const decoded = jwt.verify(extracted_token, JWT_SECRET);
req.user = decoded.user;
next();
} catch (error) {
console.log("Invalid token");
return res.status(401).json({ message: "Invalid token" });
}
} else {
if (apiKey) {
const keysjson = await dbInstance.query('SELECT api_keys FROM app_config where "ID"=1').then((res) => res.rows[0].api_keys);
if (!keysjson || Object.keys(keysjson).length === 0) {
return res.status(404).json({ message: "No API keys configured" });
}
const keys = keysjson || [];
const keyExists = keys.some((obj) => obj.key === apiKey);
if (keyExists) {
next();
} else {
return res.status(403).json({ message: "Invalid API key" });
}
}
}
}
// start server
try {
createdb.createDatabase().then((result) => {
if (result) {
console.log("[JELLYSTAT] Database created");
} else {
console.log("[JELLYSTAT] Database exists. Skipping creation");
}
db.migrate.latest().then(() => {
const server = http.createServer(app);
setupWebSocketServer(server);
server.listen(PORT, LISTEN_IP, async () => {
console.log(`[JELLYSTAT] Server listening on http://127.0.0.1:${PORT}`);
ActivityMonitor.ActivityMonitor(1000);
tasks.FullSyncTask();
tasks.RecentlyAddedItemsSyncTask();
tasks.BackupTask();
});
});
});
} catch (error) {
console.log("[JELLYSTAT] An error has occured on startup: " + error);
}