mirror of
https://github.com/Shineii86/AniKotoAPI.git
synced 2026-10-22 10:55:10 +00:00
feat: add creator info middleware from MiruroAPI
- Add src/middleware/creatorInfo.js (ESM adapted) - Injects creator attribution (name, GitHub, Telegram, timestamp) into every JSON response - Updates server.js to use addCreatorInfo middleware - Updates CHANGELOG.md, README.md, and docs/architecture.md
This commit is contained in:
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [2.0.0] - 2026-07-04
|
## [2.0.0] - 2026-07-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- **Creator Info Middleware**: Injects creator attribution (name, GitHub, Telegram, timestamp) into every JSON response
|
||||||
- **Multi-Mirror Fallback System**: Automatic failover across 5 mirror domains (anikototv.to, anikoto.cz, anikoto.me, anikoto.net, anikototv.se)
|
- **Multi-Mirror Fallback System**: Automatic failover across 5 mirror domains (anikototv.to, anikoto.cz, anikoto.me, anikoto.net, anikototv.se)
|
||||||
- **LRU Cache with Configurable TTL**: Replaced basic Map cache with LRU cache supporting per-endpoint TTL (3min to 60min)
|
- **LRU Cache with Configurable TTL**: Replaced basic Map cache with LRU cache supporting per-endpoint TTL (3min to 60min)
|
||||||
- **Response Compression**: Added gzip compression middleware (level 6, 1024 byte threshold)
|
- **Response Compression**: Added gzip compression middleware (level 6, 1024 byte threshold)
|
||||||
|
|||||||
@@ -339,6 +339,9 @@ AniKotoAPI/
|
|||||||
│ │ ├── 📄 extractPages.helper.js # 📃 Page fetcher
|
│ │ ├── 📄 extractPages.helper.js # 📃 Page fetcher
|
||||||
│ │ └── 📄 formatTitle.helper.js # 🔤 Title formatter
|
│ │ └── 📄 formatTitle.helper.js # 🔤 Title formatter
|
||||||
│ │
|
│ │
|
||||||
|
│ ├── 📂 middleware/ # 🔧 Express middleware
|
||||||
|
│ │ └── 📄 creatorInfo.js # ✍️ Creator attribution injection
|
||||||
|
│ │
|
||||||
│ └── 📂 routes/ # 🛤️ Express routes
|
│ └── 📂 routes/ # 🛤️ Express routes
|
||||||
│ ├── 📄 apiRoutes.js # 🌐 Main API routes (33 endpoints)
|
│ ├── 📄 apiRoutes.js # 🌐 Main API routes (33 endpoints)
|
||||||
│ └── 📄 category.route.js # 🏷️ Category routes
|
│ └── 📄 category.route.js # 🏷️ Category routes
|
||||||
|
|||||||
+13
-1
@@ -84,6 +84,9 @@ AniKotoAPI/
|
|||||||
│ ├── mirror.helper.js # Multi-mirror fallback (5 domains)
|
│ ├── mirror.helper.js # Multi-mirror fallback (5 domains)
|
||||||
│ └── extractPages.helper.js # Page fetching with mirror fallback
|
│ └── extractPages.helper.js # Page fetching with mirror fallback
|
||||||
│
|
│
|
||||||
|
│ └── middleware/
|
||||||
|
│ └── creatorInfo.js # Creator attribution injection
|
||||||
|
│
|
||||||
├── test.js # Test suite (27 tests)
|
├── test.js # Test suite (27 tests)
|
||||||
├── agents/ # AI agent prompts (6 agents)
|
├── agents/ # AI agent prompts (6 agents)
|
||||||
└── CHANGELOG.md # Version history
|
└── CHANGELOG.md # Version history
|
||||||
@@ -100,6 +103,7 @@ AniKotoAPI/
|
|||||||
| Caching | LRU cache with configurable TTL |
|
| Caching | LRU cache with configurable TTL |
|
||||||
| Compression | gzip (level 6, 1024 byte threshold) |
|
| Compression | gzip (level 6, 1024 byte threshold) |
|
||||||
| Mirror Fallback | 5 mirror domains with health checks |
|
| Mirror Fallback | 5 mirror domains with health checks |
|
||||||
|
| Middleware | Creator info attribution injection |
|
||||||
| Static Files | Express static middleware |
|
| Static Files | Express static middleware |
|
||||||
|
|
||||||
## Request Flow
|
## Request Flow
|
||||||
@@ -109,6 +113,14 @@ Client Request
|
|||||||
↓
|
↓
|
||||||
Vercel Routes (/api/* → server.js)
|
Vercel Routes (/api/* → server.js)
|
||||||
↓
|
↓
|
||||||
|
Express Middleware
|
||||||
|
↓
|
||||||
|
├── CORS
|
||||||
|
├── Security Headers
|
||||||
|
├── Compression (gzip)
|
||||||
|
├── Rate Limiting
|
||||||
|
├── Creator Info Injection
|
||||||
|
↓
|
||||||
Express Router (apiRoutes.js)
|
Express Router (apiRoutes.js)
|
||||||
↓
|
↓
|
||||||
Controller (e.g., search.controller.js)
|
Controller (e.g., search.controller.js)
|
||||||
@@ -119,7 +131,7 @@ HTTP Request to anikototv.to (with headers)
|
|||||||
↓
|
↓
|
||||||
Cheerio parses HTML response
|
Cheerio parses HTML response
|
||||||
↓
|
↓
|
||||||
Returns structured JSON
|
Returns structured JSON with Creator Attribution
|
||||||
↓
|
↓
|
||||||
Client Response
|
Client Response
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { fileURLToPath } from "url";
|
|||||||
import { dirname } from "path";
|
import { dirname } from "path";
|
||||||
import { createApiRoutes } from "./src/routes/apiRoutes.js";
|
import { createApiRoutes } from "./src/routes/apiRoutes.js";
|
||||||
import { getCacheStats } from "./src/helper/cache.helper.js";
|
import { getCacheStats } from "./src/helper/cache.helper.js";
|
||||||
|
import { addCreatorInfo } from "./src/middleware/creatorInfo.js";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@@ -160,6 +161,9 @@ app.get("/api/cache/stats", (req, res) => {
|
|||||||
res.json({ success: true, results: getCacheStats() });
|
res.json({ success: true, results: getCacheStats() });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- FEATURE: Creator info middleware (injects attribution into all responses) ----
|
||||||
|
app.use(addCreatorInfo);
|
||||||
|
|
||||||
createApiRoutes(app, jsonResponse, jsonError);
|
createApiRoutes(app, jsonResponse, jsonError);
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* ======= • ======= • ======= • ======= • =======• =======
|
||||||
|
* AniKotoAPI — creatorInfo.js
|
||||||
|
* Repository: https://github.com/Shineii86/AniKotoAPI
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Express middleware that injects creator attribution metadata
|
||||||
|
* into every JSON API response. Overrides res.json() to append
|
||||||
|
* author details and a server-side timestamp, unless the
|
||||||
|
* response already contains creator information.
|
||||||
|
*
|
||||||
|
* @exports
|
||||||
|
* addCreatorInfo — Express middleware function
|
||||||
|
* creatorInfo — Static creator metadata object
|
||||||
|
*
|
||||||
|
* @author Shinei Nouzen
|
||||||
|
* @license MIT
|
||||||
|
* ======= • ======= • ======= • ======= • =======• =======
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// CREATOR METADATA
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// NOTE: Injected into every response unless `data.creator` already exists
|
||||||
|
const creatorInfo = {
|
||||||
|
creator: "Shinei Nouzen",
|
||||||
|
github: "https://github.com/Shineii86",
|
||||||
|
telegram: "https://telegram.me/Shineii86",
|
||||||
|
message: "Built with ❤️ by Shinei Nouzen",
|
||||||
|
};
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// MIDDLEWARE
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Express middleware that enriches every JSON response with
|
||||||
|
* creator attribution and an IN-formatted timestamp.
|
||||||
|
*
|
||||||
|
* @param {import('express').Request} req - Express request object
|
||||||
|
* @param {import('express').Response} res - Express response object
|
||||||
|
* @param {import('express').NextFunction} next - Passes control forward
|
||||||
|
* @returns {void}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* app.use(addCreatorInfo);
|
||||||
|
* res.json({ anime: [...] });
|
||||||
|
* // Response includes creator, github, telegram, message, and timestamp
|
||||||
|
*/
|
||||||
|
const addCreatorInfo = (req, res, next) => {
|
||||||
|
// NOTE: Preserve the original json method so we can wrap it
|
||||||
|
const originalJson = res.json.bind(res);
|
||||||
|
|
||||||
|
// NOTE: Monkey-patch res.json to inject creator info
|
||||||
|
res.json = (data) => {
|
||||||
|
// NOTE: Only inject if data is a plain object and missing creator field
|
||||||
|
if (typeof data === "object" && data !== null && !data.creator) {
|
||||||
|
data = {
|
||||||
|
...data,
|
||||||
|
creator: creatorInfo.creator,
|
||||||
|
github: creatorInfo.github,
|
||||||
|
telegram: creatorInfo.telegram,
|
||||||
|
timestamp: new Date().toLocaleString("en-IN", { timeZone: "Asia/Kolkata", hour12: true }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return originalJson(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export { addCreatorInfo, creatorInfo };
|
||||||
|
// ══════════════════════════════════════════════════════════════ END: creatorInfo.js
|
||||||
Reference in New Issue
Block a user