mirror of
https://github.com/Shineii86/AniKotoAPI.git
synced 2026-10-22 10:55:10 +00:00
Critical fixes: - extractPages.helper.js: Pagination URL bug (?page → &page) - package.json: Fixed invalid dep versions (axios, dotenv, express) - privacy.html + tos.html: Broken footer links (//tos → /tos) - manifest.json: Invalid icon purpose (any maskable → maskable) High fixes: - server.js: Removed duplicate CORS middleware (triple → single) - search.controller.js: Cache key collision with suggestion controller - apiRoutes.js: Dynamic endpoint count (was hardcoded 27) - vercel.json: Removed duplicate CORS header Medium fixes: - header.config.js: Updated User-Agent to Chrome 130 - dataUrl.js: Removed redundant domain from ALT_DOMAINS - category.extractor.js: Removed unused cheerio import - server.js: Added __dirname, removed unused cors import Documentation: - CHANGELOG.md: Added v1.8.0 entry - README.md: Updated version badges (1.8.0, Express 4.21) - test.js: Fixed status endpoint (currently-airing → airing)
143 lines
6.9 KiB
JavaScript
143 lines
6.9 KiB
JavaScript
/*
|
|
* ======= • ======= • ======= • ======= • =======• =======
|
|
* AniKotoAPI — server.js
|
|
* Repository: https://github.com/Shineii86/AniKotoAPI
|
|
*
|
|
* @description
|
|
* Main entry point for the AniKotoAPI Express server.
|
|
* Configures CORS, middleware, static files, API routes,
|
|
* and 404 handling. Starts the server on the configured port.
|
|
*
|
|
* @exports
|
|
* None (side-effect: starts Express server)
|
|
*
|
|
* @author Shinei Nouzen
|
|
* @license MIT
|
|
* ======= • ======= • ======= • ======= • =======• =======
|
|
*/
|
|
|
|
import dotenv from "dotenv";
|
|
import express from "express";
|
|
import cors from "cors";
|
|
import path from "path";
|
|
import fs from "fs";
|
|
import { fileURLToPath } from "url";
|
|
import { dirname } from "path";
|
|
import { createApiRoutes } from "./src/routes/apiRoutes.js";
|
|
|
|
dotenv.config();
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// SERVER CONFIGURATION
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 4444;
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
const publicDir = path.join(process.cwd(), "public");
|
|
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",");
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// CORS MIDDLEWARE
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
// NOTE: Single unified CORS middleware — handles all origin validation
|
|
app.use((req, res, next) => {
|
|
const origin = req.headers.origin;
|
|
if (!allowedOrigins || allowedOrigins.includes("*") || (origin && allowedOrigins.includes(origin))) {
|
|
res.setHeader("Access-Control-Allow-Origin", origin || "*");
|
|
}
|
|
res.setHeader("Access-Control-Allow-Methods", "GET");
|
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
if (req.method === "OPTIONS") {
|
|
return res.status(204).end();
|
|
}
|
|
next();
|
|
});
|
|
|
|
// ---- FEATURE: Security headers ----
|
|
app.use((req, res, next) => {
|
|
res.setHeader("X-Frame-Options", "DENY");
|
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
res.setHeader("X-XSS-Protection", "1; mode=block");
|
|
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
|
|
next();
|
|
});
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// STATIC FILES
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
// NOTE: redirect: false prevents automatic redirects to index.html
|
|
app.use(express.static(publicDir, { redirect: false }));
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// CLEAN URL ROUTES
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
// Serve HTML files without .html extension
|
|
app.get("/tos", (req, res) => {
|
|
res.sendFile(path.join(publicDir, "tos.html"));
|
|
});
|
|
|
|
app.get("/privacy", (req, res) => {
|
|
res.sendFile(path.join(publicDir, "privacy.html"));
|
|
});
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// RESPONSE HELPERS
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
// ---- FEATURE: Standardized JSON response wrapper ----
|
|
/**
|
|
* Wraps data in a standardized success JSON response.
|
|
*
|
|
* @param {object} res - Express response object
|
|
* @param {*} data - The data to return in the response
|
|
* @param {number} status - HTTP status code (default: 200)
|
|
*/
|
|
const jsonResponse = (res, data, status = 200) =>
|
|
res.status(status).json({ success: true, results: data });
|
|
|
|
// ---- FEATURE: Standardized error response wrapper ----
|
|
/**
|
|
* Returns a standardized error JSON response.
|
|
*
|
|
* @param {object} res - Express response object
|
|
* @param {string} message - Error message to return (default: "Internal server error")
|
|
* @param {number} status - HTTP status code (default: 500)
|
|
*/
|
|
const jsonError = (res, message = "Internal server error", status = 500) =>
|
|
res.status(status).json({ success: false, message });
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// API ROUTES
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
createApiRoutes(app, jsonResponse, jsonError);
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// 404 HANDLER
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
// ---- FEATURE: Catch-all 404 handler for undefined routes ----
|
|
app.use((req, res) => {
|
|
const filePath = path.join(publicDir, "404.html");
|
|
if (fs.existsSync(filePath)) {
|
|
res.status(404).sendFile(filePath);
|
|
} else {
|
|
res.status(404).json({ success: false, message: "Endpoint not found" });
|
|
}
|
|
});
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// SERVER START
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
app.listen(PORT, () => {
|
|
console.info(`AniKotoAPI listening at ${PORT}`);
|
|
});
|
|
|
|
// ══════════════════════════════════════════════════════════════ END: server.js
|