Files
AniKotoAPI/server.js
T
Sʜɪɴᴇɪ Nᴏᴜᴢᴇɴ 445b6f7c71 fix: clean URLs — /tos and /privacy instead of /tos.html and /privacy.html
- Added Express routes for /tos and /privacy
- Updated all internal links (index.html, privacy.html, tos.html)
- Updated sitemap.xml URLs
- Updated README and docs architecture
2026-06-08 21:21:55 +05:30

144 lines
6.7 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 publicDir = path.join(process.cwd(), "public");
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",");
// ══════════════════════════════════════════════════════════════
// CORS MIDDLEWARE
// ══════════════════════════════════════════════════════════════
// NOTE: CORS is configured to only allow GET requests from specified origins
app.use(
cors({
origin: allowedOrigins?.includes("*") ? "*" : allowedOrigins || [],
methods: ["GET"],
})
);
// ---- FEATURE: Custom origin validation middleware ----
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");
return next();
}
res
.status(403)
.json({ success: false, message: "Forbidden: Origin not allowed" });
});
// ══════════════════════════════════════════════════════════════
// 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