From 935e59fcac586d3a613b1ca857f142f5a6c4f604 Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:02:50 +0530 Subject: [PATCH 01/16] refactor: rename prettier check workflow to `link-&-format` --- .../workflows/{prettier-check.yml => lint-&-format-check.yml} | 3 +++ 1 file changed, 3 insertions(+) rename .github/workflows/{prettier-check.yml => lint-&-format-check.yml} (87%) diff --git a/.github/workflows/prettier-check.yml b/.github/workflows/lint-&-format-check.yml similarity index 87% rename from .github/workflows/prettier-check.yml rename to .github/workflows/lint-&-format-check.yml index 15008dd..4995e30 100644 --- a/.github/workflows/prettier-check.yml +++ b/.github/workflows/lint-&-format-check.yml @@ -24,3 +24,6 @@ jobs: - name: Check lint run: npm run lint:ci + + - name: Check prettier + run: npm run format:check From 21ca35904b18487140a586a70ef36d93b99e378e Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:03:42 +0530 Subject: [PATCH 02/16] feat: add envalid, pino & format scripts --- package.json | 104 +++++++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 0fd1043..6c55ade 100644 --- a/package.json +++ b/package.json @@ -1,51 +1,57 @@ { - "name": "aniwatch-api", - "version": "2.12.0", - "description": "Node.js API for obtaining anime information from hianime.to", - "main": "src/server.ts", - "type": "module", - "scripts": { - "start": "tsx src/server.ts", - "dev": "tsx watch src/server.ts", - "build": "tsc -p tsconfig.json", - "vercel-build": "echo \"Hello\"", - "prepare": "husky install", - "test": "vitest run --config vitest.config.ts", - "healthcheck": "curl -f http://localhost:4000/health", - "lint": "prettier --cache --write .", - "lint:ci": "prettier --cache --check ." - }, - "repository": { - "type": "git", - "url": "git+https://github.com/ghoshRitesh12/aniwatch-api.git" - }, - "bugs": { - "url": "https://github.com/ghoshRitesh12/aniwatch-api/issues" - }, - "homepage": "https://github.com/ghoshRitesh12/aniwatch-api#readme", - "keywords": [ - "anime", - "weeb", - "hianime", - "aniwatch", - "scraper" - ], - "author": "https://github.com/ghoshRitesh12", - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.13.7", - "aniwatch": "^2.18.3", - "dotenv": "^16.4.7", - "hono": "^4.6.15", - "hono-rate-limiter": "^0.4.2", - "ioredis": "^5.4.2" - }, - "devDependencies": { - "@types/node": "^22.10.3", - "husky": "^9.1.7", - "prettier": "^3.4.2", - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "vitest": "^2.1.8" - } + "name": "aniwatch-api", + "version": "2.12.0", + "description": "Node.js API for obtaining anime information from hianimez.to", + "main": "src/server.ts", + "type": "module", + "scripts": { + "start": "tsx src/server.ts", + "dev": "tsx watch src/server.ts", + "build": "tsc -p tsconfig.json", + "vercel-build": "echo \"Hello\"", + "prepare": "node .husky/install.mjs", + "test": "vitest run --config vitest.config.ts", + "healthcheck": "curl -f http://localhost:4000/health", + "lint": "tsc", + "format": "prettier --cache --write .", + "format:check": "prettier --cache --check ." + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ghoshRitesh12/aniwatch-api.git" + }, + "bugs": { + "url": "https://github.com/ghoshRitesh12/aniwatch-api/issues" + }, + "homepage": "https://github.com/ghoshRitesh12/aniwatch-api#readme", + "keywords": [ + "anime", + "weeb", + "scraper", + "zoro.to", + "aniwatch.to", + "hianime.to", + "hianimez.to" + ], + "author": "https://github.com/ghoshRitesh12", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.14.1", + "aniwatch": "^2.21.2", + "dotenv": "^16.5.0", + "envalid": "^8.0.0", + "hono": "^4.7.9", + "hono-rate-limiter": "^0.4.2", + "ioredis": "^5.6.1", + "pino": "^9.6.0", + "tsx": "^4.19.4" + }, + "devDependencies": { + "@types/node": "^22.15.17", + "husky": "^9.1.7", + "pino-pretty": "^13.0.0", + "prettier": "^3.5.3", + "typescript": "^5.8.3", + "vitest": "^3.1.3" + } } \ No newline at end of file From 797fc2455c143aaa8e7adb08865277b2c0806341 Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:05:23 +0530 Subject: [PATCH 03/16] style: prettier format changes --- CHANGELOG.md | 20 +- CONTRIBUTING.md | 34 +-- README.md | 4 +- public/index.html | 451 +++++++++++++++-------------- src/config/variables.ts | 10 +- test/animeAboutInfo.test.ts | 12 +- test/animeCategory.test.ts | 14 +- test/animeEpisodeSrcs.test.ts | 14 +- test/animeEpisodes.test.ts | 8 +- test/animeGenre.test.ts | 10 +- test/animeProducer.test.ts | 14 +- test/animeSearch.test.ts | 10 +- test/animeSearchSuggestion.test.ts | 6 +- test/episodeServers.test.ts | 12 +- test/estimatedSchedule.test.ts | 8 +- test/homePage.test.ts | 28 +- vitest.config.ts | 10 +- 17 files changed, 326 insertions(+), 339 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69b8c40..da6bbb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,35 +1,21 @@ # [2.12.0](https://github.com/ghoshRitesh12/aniwatch-api/compare/v2.11.3...v2.12.0) (2025-04-14) - ### Features -* **getNextEpisodeSchedule:** add `/anime/:animeId/next-episode-schedule` endpoint ([f5cd341](https://github.com/ghoshRitesh12/aniwatch-api/commit/f5cd3415d8134da1ab1e3b3f8f9be6b5212aa353)) - - +- **getNextEpisodeSchedule:** add `/anime/:animeId/next-episode-schedule` endpoint ([f5cd341](https://github.com/ghoshRitesh12/aniwatch-api/commit/f5cd3415d8134da1ab1e3b3f8f9be6b5212aa353)) ## [2.11.3](https://github.com/ghoshRitesh12/aniwatch-api/compare/v2.11.2...v2.11.3) (2025-03-20) - - ## [2.11.2](https://github.com/ghoshRitesh12/aniwatch-api/compare/v2.11.1...v2.11.2) (2025-03-15) - - ## [2.11.1](https://github.com/ghoshRitesh12/aniwatch-api/compare/v2.11.0...v2.11.1) (2025-01-01) - ### Bug Fixes -* **ts build error:** fixed ts build error due to conflicting types ([cb5a467](https://github.com/ghoshRitesh12/aniwatch-api/commit/cb5a4672a8c3b0729bbb4522a3af252f7b336b97)) - - +- **ts build error:** fixed ts build error due to conflicting types ([cb5a467](https://github.com/ghoshRitesh12/aniwatch-api/commit/cb5a4672a8c3b0729bbb4522a3af252f7b336b97)) # [2.11.0](https://github.com/ghoshRitesh12/aniwatch-api/compare/v2.10.0...v2.11.0) (2024-12-25) - ### Features -* **`/qtip`:** add new `/qtip` endpoint ([f0acd89](https://github.com/ghoshRitesh12/aniwatch-api/commit/f0acd89d87e5e62c12e20a95225ca9261fefe411)) - - - +- **`/qtip`:** add new `/qtip` endpoint ([f0acd89](https://github.com/ghoshRitesh12/aniwatch-api/commit/f0acd89d87e5e62c12e20a95225ca9261fefe411)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d470d24..6df0505 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,8 +8,8 @@ Thank you for your interest in contributing to aniwatch-api. We appreciate whate - [Ground Rules & Expectations](#ground-rules--expectations) - [How To Contribute](#how-to-contribute) - [Prerequisites](#prerequisites) - - [Clone the repository](#clone-the-repository) - - [Project Structure](#project-structure) + - [Clone the repository](#clone-the-repository) + - [Project Structure](#project-structure) - [Commit Messages](#commit-messages) ## Types of contributions we are looking for @@ -42,10 +42,10 @@ To contribute to this project, you must know the following: - [NodeJS](https://nodejs.org/) - [TypeScript](https://www.typescriptlang.org/) - Web Scraping - - [Cheerio](https://cheerio.js.org/) - - [Axios](https://axios-http.com/docs/intro) - - [CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) - - [Browser Dev Tools](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/What_are_browser_developer_tools) + - [Cheerio](https://cheerio.js.org/) + - [Axios](https://axios-http.com/docs/intro) + - [CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) + - [Browser Dev Tools](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/What_are_browser_developer_tools) ### Clone the repository @@ -58,9 +58,9 @@ git clone https://github.com//aniwatch-api 3. Creating a new branch
Replace \ with any of the following naming conventions:
- - `feature/` - for adding new features - - `bug/` - for fixing known bugs - - `misc/` - for anything other than bug or features + - `feature/` - for adding new features + - `bug/` - for fixing known bugs + - `misc/` - for anything other than bug or features ```bash git checkout -b @@ -70,14 +70,14 @@ git checkout -b - `src` directory contains all the source code required for this project - - `controllers` directory contains all the controller logic - - `types` directory contains all types & interfaces used for this project - - `parsers` directory contains all the parsing aka scraping logic - - `routes` directory contains all the routers - - `utils` directory contains handy utility methods and properties - - `config` directory contains api configuration related files - - `extractors` directory contains anime streaming url extractor files -

+ - `controllers` directory contains all the controller logic + - `types` directory contains all types & interfaces used for this project + - `parsers` directory contains all the parsing aka scraping logic + - `routes` directory contains all the routers + - `utils` directory contains handy utility methods and properties + - `config` directory contains api configuration related files + - `extractors` directory contains anime streaming url extractor files +

- `test` directory contains all the tests that needs to be evaluated diff --git a/README.md b/README.md index e48a14a..1f16b59 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ #

Aniwatch API

- A free RESTful API serving anime information from hianime.to + A free RESTful API serving anime information from hianimez.to
@@ -56,7 +56,7 @@ > [!IMPORTANT] > > 1. [https://api-aniwatch.onrender.com](https://api-aniwatch.onrender.com/) is only meant to demo the API and has rate-limiting enabled to minimize bandwidth consumption. It is recommended to deploy your own instance for personal use by customizing the API as you need it to be. -> 2. This API is just an unofficial API for [hianime.to](https://hianime.to) and is in no other way officially related to the same. +> 2. This API is just an unofficial API for [hianimez.to](https://hianimez.to) and is in no other way officially related to the same. > 3. The content that this API provides is not mine, nor is it hosted by me. These belong to their respective owners. This API just demonstrates how to build an API that scrapes websites and uses their content. ## Table of Contents diff --git a/public/index.html b/public/index.html index ff7384f..c095416 100644 --- a/public/index.html +++ b/public/index.html @@ -2,276 +2,277 @@ - - Aniwatch API - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + .api-stats-container a { + flex-shrink: 0; + user-select: none; + } + + .contributors { + margin-top: 1rem; + text-align: center; + } + - - - + + + -
-

- Welcome to the unofficial - hianime.to - API - ⚔️ -

+
+

+ Welcome to the unofficial + hianimez.to + API + ⚔️ +

- + - -
-

Thanks to the following people for keeping this - project alive and - relevant.

+
+

Thanks to the following people for keeping this + project alive and + relevant.

- - Contributors - -
-
+ + Contributors + +
+ \ No newline at end of file diff --git a/src/config/variables.ts b/src/config/variables.ts index b58316f..dc90dcf 100644 --- a/src/config/variables.ts +++ b/src/config/variables.ts @@ -1,8 +1,8 @@ type CacheVariables = { - CACHE_CONFIG: { - key: string; - duration: number; - }; + CACHE_CONFIG: { + key: string; + duration: number; + }; }; -export type AniwatchAPIVariables = {} & CacheVariables; +export type AniwatchAPIVariables = CacheVariables & {}; diff --git a/test/animeAboutInfo.test.ts b/test/animeAboutInfo.test.ts index 82fe97d..8a6ed94 100644 --- a/test/animeAboutInfo.test.ts +++ b/test/animeAboutInfo.test.ts @@ -5,11 +5,11 @@ const animeId = "steinsgate-3"; // npx vitest run animeAboutInfo.test.ts test(`GET /api/v2/hianime/anime/${animeId}`, async () => { - const hianime = new HiAnime.Scraper(); - const data = await hianime.getInfo(animeId); + const hianime = new HiAnime.Scraper(); + const data = await hianime.getInfo(animeId); - expect(data.anime.info.name).not.toEqual(null); - expect(data.recommendedAnimes).not.toEqual([]); - expect(data.mostPopularAnimes).not.toEqual([]); - expect(Object.keys(data.anime.moreInfo)).not.toEqual([]); + expect(data.anime.info.name).not.toEqual(null); + expect(data.recommendedAnimes).not.toEqual([]); + expect(data.mostPopularAnimes).not.toEqual([]); + expect(Object.keys(data.anime.moreInfo)).not.toEqual([]); }); diff --git a/test/animeCategory.test.ts b/test/animeCategory.test.ts index 045b0f9..84d14e0 100644 --- a/test/animeCategory.test.ts +++ b/test/animeCategory.test.ts @@ -5,12 +5,12 @@ const category = "subbed-anime"; // npx vitest run animeCategory.test.ts test(`GET /api/v2/hianime/category/${category}`, async () => { - const hianime = new HiAnime.Scraper(); - const data = await hianime.getCategoryAnime(category); + const hianime = new HiAnime.Scraper(); + const data = await hianime.getCategoryAnime(category); - expect(data.animes).not.toEqual([]); - expect(data.genres).not.toEqual([]); - expect(data.top10Animes.today).not.toEqual([]); - expect(data.top10Animes.week).not.toEqual([]); - expect(data.top10Animes.month).not.toEqual([]); + expect(data.animes).not.toEqual([]); + expect(data.genres).not.toEqual([]); + expect(data.top10Animes.today).not.toEqual([]); + expect(data.top10Animes.week).not.toEqual([]); + expect(data.top10Animes.month).not.toEqual([]); }); diff --git a/test/animeEpisodeSrcs.test.ts b/test/animeEpisodeSrcs.test.ts index d8db0ed..752020c 100644 --- a/test/animeEpisodeSrcs.test.ts +++ b/test/animeEpisodeSrcs.test.ts @@ -7,12 +7,12 @@ const category = "sub"; // npx vitest run animeEpisodeSrcs.test.ts test(`GET /api/v2/hianime/episode/sources?animeEpisodeId=${animeEpisodeId}&server=${server}&category=${category}`, async () => { - const hianime = new HiAnime.Scraper(); - const data = await hianime.getEpisodeSources( - animeEpisodeId, - server, - category - ); + const hianime = new HiAnime.Scraper(); + const data = await hianime.getEpisodeSources( + animeEpisodeId, + server, + category + ); - expect(data.sources).not.toEqual([]); + expect(data.sources).not.toEqual([]); }); diff --git a/test/animeEpisodes.test.ts b/test/animeEpisodes.test.ts index fbe75ae..0762796 100644 --- a/test/animeEpisodes.test.ts +++ b/test/animeEpisodes.test.ts @@ -5,9 +5,9 @@ const animeId = "steinsgate-3"; // npx vitest run animeEpisodes.test.ts test(`GET /api/v2/hianime/anime/${animeId}/episodes`, async () => { - const hianime = new HiAnime.Scraper(); - const data = await hianime.getEpisodes(animeId); + const hianime = new HiAnime.Scraper(); + const data = await hianime.getEpisodes(animeId); - expect(data.totalEpisodes).not.toEqual(0); - expect(data.episodes).not.toEqual([]); + expect(data.totalEpisodes).not.toEqual(0); + expect(data.episodes).not.toEqual([]); }); diff --git a/test/animeGenre.test.ts b/test/animeGenre.test.ts index f08ad1b..f9cfca7 100644 --- a/test/animeGenre.test.ts +++ b/test/animeGenre.test.ts @@ -6,11 +6,11 @@ const page = 2; // npx vitest run animeGenre.test.ts test(`GET /api/v2/hianime/genre/${genreName}?page=${page}`, async () => { - const hianime = new HiAnime.Scraper(); + const hianime = new HiAnime.Scraper(); - const data = await hianime.getGenreAnime(genreName, page); + const data = await hianime.getGenreAnime(genreName, page); - expect(data.animes).not.toEqual([]); - expect(data.genres).not.toEqual([]); - expect(data.topAiringAnimes).not.toEqual([]); + expect(data.animes).not.toEqual([]); + expect(data.genres).not.toEqual([]); + expect(data.topAiringAnimes).not.toEqual([]); }); diff --git a/test/animeProducer.test.ts b/test/animeProducer.test.ts index 53f0d28..c687389 100644 --- a/test/animeProducer.test.ts +++ b/test/animeProducer.test.ts @@ -6,12 +6,12 @@ const page = 2; // npx vitest run animeProducer.test.ts test(`GET /api/v2/hianime/producer/${producerName}?page=${page}`, async () => { - const hianime = new HiAnime.Scraper(); - const data = await hianime.getProducerAnimes(producerName, page); + const hianime = new HiAnime.Scraper(); + const data = await hianime.getProducerAnimes(producerName, page); - expect(data.animes).not.toEqual([]); - expect(data.topAiringAnimes).not.toEqual([]); - expect(data.top10Animes.today).not.toEqual([]); - expect(data.top10Animes.week).not.toEqual([]); - expect(data.top10Animes.month).not.toEqual([]); + expect(data.animes).not.toEqual([]); + expect(data.topAiringAnimes).not.toEqual([]); + expect(data.top10Animes.today).not.toEqual([]); + expect(data.top10Animes.week).not.toEqual([]); + expect(data.top10Animes.month).not.toEqual([]); }); diff --git a/test/animeSearch.test.ts b/test/animeSearch.test.ts index dac66cb..30ad90b 100644 --- a/test/animeSearch.test.ts +++ b/test/animeSearch.test.ts @@ -4,14 +4,14 @@ import { HiAnime } from "aniwatch"; const query = "monster"; const page = 1; const filter: HiAnime.SearchFilters = { - genres: "seinen,psychological", + genres: "seinen,psychological", }; // npx vitest run animeSearch.test.ts test(`GET /api/v2/hianime/search?q=${query}&page=${page}&genres=${filter.genres}`, async () => { - const hianime = new HiAnime.Scraper(); - const data = await hianime.search(query, page, filter); + const hianime = new HiAnime.Scraper(); + const data = await hianime.search(query, page, filter); - expect(data.animes).not.toEqual([]); - expect(data.mostPopularAnimes).not.toEqual([]); + expect(data.animes).not.toEqual([]); + expect(data.mostPopularAnimes).not.toEqual([]); }); diff --git a/test/animeSearchSuggestion.test.ts b/test/animeSearchSuggestion.test.ts index 5957528..434c092 100644 --- a/test/animeSearchSuggestion.test.ts +++ b/test/animeSearchSuggestion.test.ts @@ -5,8 +5,8 @@ const query = "one piece"; // npx vitest run animeSearchSuggestion.test.ts test(`GET /api/v2/hianime/search/suggestion?q=${query}`, async () => { - const hianime = new HiAnime.Scraper(); - const data = await hianime.searchSuggestions(query); + const hianime = new HiAnime.Scraper(); + const data = await hianime.searchSuggestions(query); - expect(data.suggestions).not.toEqual([]); + expect(data.suggestions).not.toEqual([]); }); diff --git a/test/episodeServers.test.ts b/test/episodeServers.test.ts index c044e6c..2064761 100644 --- a/test/episodeServers.test.ts +++ b/test/episodeServers.test.ts @@ -5,11 +5,11 @@ const animeEpisodeId = "steinsgate-0-92?ep=2055"; // npx vitest run episodeServers.test.ts test(`GET /api/v2/hianime/episode/servers?animeEpisodeId=${animeEpisodeId}`, async () => { - const hianime = new HiAnime.Scraper(); - const data = await hianime.getEpisodeServers(animeEpisodeId); + const hianime = new HiAnime.Scraper(); + const data = await hianime.getEpisodeServers(animeEpisodeId); - expect(data.episodeId).not.toEqual(null); - expect(data.episodeNo).not.toEqual(0); - expect(data.sub).not.toEqual([]); - expect(data.dub).not.toEqual([]); + expect(data.episodeId).not.toEqual(null); + expect(data.episodeNo).not.toEqual(0); + expect(data.sub).not.toEqual([]); + expect(data.dub).not.toEqual([]); }); diff --git a/test/estimatedSchedule.test.ts b/test/estimatedSchedule.test.ts index 1e8e086..e714bbe 100644 --- a/test/estimatedSchedule.test.ts +++ b/test/estimatedSchedule.test.ts @@ -5,13 +5,13 @@ const padZero = (num: number) => (num < 10 ? `0${num}` : num.toString()); const d = new Date(); const date = `${d.getFullYear()}-${padZero(d.getMonth() + 1)}-${padZero( - d.getDate() + d.getDate() )}`; // npx vitest run estimatedSchedule.test.ts test(`GET /api/v2/hianime/schedule?date=${date}`, async () => { - const hianime = new HiAnime.Scraper(); - const data = await hianime.getEstimatedSchedule(date); + const hianime = new HiAnime.Scraper(); + const data = await hianime.getEstimatedSchedule(date); - expect(data.scheduledAnimes).not.toEqual([]); + expect(data.scheduledAnimes).not.toEqual([]); }); diff --git a/test/homePage.test.ts b/test/homePage.test.ts index a5f9e8f..d4fd97f 100644 --- a/test/homePage.test.ts +++ b/test/homePage.test.ts @@ -2,20 +2,20 @@ import { expect, test } from "vitest"; import { HiAnime } from "aniwatch"; test("GET /api/v2/hianime/home", async () => { - const hianime = new HiAnime.Scraper(); - const data = await hianime.getHomePage(); + const hianime = new HiAnime.Scraper(); + const data = await hianime.getHomePage(); - expect(data.spotlightAnimes).not.toEqual([]); - expect(data.trendingAnimes).not.toEqual([]); - expect(data.latestEpisodeAnimes).not.toEqual([]); - expect(data.topUpcomingAnimes).not.toEqual([]); - expect(data.topAiringAnimes).not.toEqual([]); - expect(data.mostPopularAnimes).not.toEqual([]); - expect(data.mostFavoriteAnimes).not.toEqual([]); - expect(data.latestCompletedAnimes).not.toEqual([]); - expect(data.genres).not.toEqual([]); + expect(data.spotlightAnimes).not.toEqual([]); + expect(data.trendingAnimes).not.toEqual([]); + expect(data.latestEpisodeAnimes).not.toEqual([]); + expect(data.topUpcomingAnimes).not.toEqual([]); + expect(data.topAiringAnimes).not.toEqual([]); + expect(data.mostPopularAnimes).not.toEqual([]); + expect(data.mostFavoriteAnimes).not.toEqual([]); + expect(data.latestCompletedAnimes).not.toEqual([]); + expect(data.genres).not.toEqual([]); - expect(data.top10Animes.today).not.toEqual([]); - expect(data.top10Animes.week).not.toEqual([]); - expect(data.top10Animes.month).not.toEqual([]); + expect(data.top10Animes.today).not.toEqual([]); + expect(data.top10Animes.week).not.toEqual([]); + expect(data.top10Animes.month).not.toEqual([]); }); diff --git a/vitest.config.ts b/vitest.config.ts index eb949e4..35991b6 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,9 +1,9 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ - test: { - name: "aniwatch-api", - environment: "node", - testTimeout: 15000, - }, + test: { + name: "aniwatch-api", + environment: "node", + testTimeout: 15000, + }, }); From 5880fe3db6425fe4c4da32301295a775c2f99892 Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:06:04 +0530 Subject: [PATCH 04/16] feat: add envalid for better env management --- src/config/env.ts | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/config/env.ts diff --git a/src/config/env.ts b/src/config/env.ts new file mode 100644 index 0000000..bfa0b0e --- /dev/null +++ b/src/config/env.ts @@ -0,0 +1,78 @@ +import { config } from "dotenv"; +import { cleanEnv, num, str, bool, url, port } from "envalid"; + +config(); + +export const env = cleanEnv(process.env, { + ANIWATCH_API_PORT: port({ + default: 4000, + desc: "Port number of the Aniwatch API.", + }), + + ANIWATCH_API_WINDOW_MS: num({ + // 60 mins if dev, else 30 mins + default: isDevEnv() ? 60 * 60 * 1000 : 30 * 60 * 1000, + desc: "Duration to track requests for rate limiting (in milliseconds).", + docs: "https://github.com/ghoshRitesh12/aniwatch-api/blob/main/.env.example#L9", + }), + + ANIWATCH_API_MAX_REQS: num({ + default: isDevEnv() ? 600 : 6, + desc: "Maximum number of requests in the `ANIWATCH_API_WINDOW_MS` time period.", + }), + + ANIWATCH_API_CORS_ALLOWED_ORIGINS: str({ + default: undefined, + example: + "https://your-production-domain.com,https://another-trusted-domain.com", + desc: "Allowed origins, separated by commas and no spaces in between (CSV).", + }), + + ANIWATCH_API_VERCEL_DEPLOYMENT: bool({ + default: false, + desc: "Required for distinguishing Vercel deployment from other ones; set it to true", + }), + + ANIWATCH_API_HOSTNAME: url({ + default: undefined, + example: "https://your-production-domain.com", + desc: "Set this to your api instance's hostname to enable rate limiting, don't have this value if you don't wish to rate limit.", + docs: "https://github.com/ghoshRitesh12/aniwatch-api/blob/main/.env.example#L18", + }), + + ANIWATCH_API_REDIS_CONN_URL: url({ + default: undefined, + example: + "rediss://default:your-secure-password@your-redis-instance-name.provider.com:6379", + desc: "This env is optional by default and can be set to utilize Redis caching functionality. It has to be a valid connection URL.", + }), + + ANIWATCH_API_S_MAXAGE: num({ + default: 60, + desc: "Specifies the maximum amount of time (in seconds) a resource is considered fresh when served by a CDN cache.", + }), + + ANIWATCH_API_STALE_WHILE_REVALIDATE: num({ + default: 30, + desc: "Specifies the amount of time (in seconds) a resource is served stale while a new one is fetched.", + }), + + NODE_ENV: str({ + default: "development", + choices: ["development", "production", "test", "staging"], + desc: "The environment in which the application is running.", + docs: "https://nodejs.org/en/learn/getting-started/nodejs-the-difference-between-development-and-production", + }), +}); + +function isDevEnv(): boolean { + return ( + !process.env.NODE_ENV || + process.env.NODE_ENV === "development" || + process.env.NODE_ENV === "test" + ); +} + +export function isEnvUndefined(envVar?: string): boolean { + return typeof envVar === "undefined" || envVar === "undefined"; +} From fc921944073ae165dd2909404519e9edaecd16ec Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:06:15 +0530 Subject: [PATCH 05/16] feat: add new husky script --- .husky/install.mjs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .husky/install.mjs diff --git a/.husky/install.mjs b/.husky/install.mjs new file mode 100644 index 0000000..941d817 --- /dev/null +++ b/.husky/install.mjs @@ -0,0 +1,6 @@ +// Skip Husky install in production and CI +if (process.env.NODE_ENV === "production" || process.env.CI === "true") { + process.exit(0); +} +const husky = (await import("husky")).default; +console.log(husky()); From 5e028771f0a008b65521385fd8fdbd694eba9f2a Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:07:04 +0530 Subject: [PATCH 06/16] feat: integrate use of ebvalidated envs --- src/config/cache.ts | 43 ++++++++++++++++++++++---------------- src/config/cors.ts | 22 +++++++++---------- src/config/errorHandler.ts | 25 +++++++++++----------- src/config/ratelimit.ts | 31 ++++++++++++++------------- 4 files changed, 64 insertions(+), 57 deletions(-) diff --git a/src/config/cache.ts b/src/config/cache.ts index 916047e..584ff69 100644 --- a/src/config/cache.ts +++ b/src/config/cache.ts @@ -1,47 +1,54 @@ -import { config } from "dotenv"; import { Redis } from "ioredis"; - -config(); +import { env } from "./env.js"; export class AniwatchAPICache { - private _client: Redis | null; + private static instance: AniwatchAPICache | null = null; + + private client: Redis | null; public isOptional: boolean = true; static DEFAULT_CACHE_EXPIRY_SECONDS = 60 as const; static CACHE_EXPIRY_HEADER_NAME = "X-ANIWATCH-CACHE-EXPIRY" as const; constructor() { - const redisConnURL = process.env?.ANIWATCH_API_REDIS_CONN_URL; + const redisConnURL = env.ANIWATCH_API_REDIS_CONN_URL; this.isOptional = !Boolean(redisConnURL); - this._client = this.isOptional ? null : new Redis(String(redisConnURL)); + this.client = this.isOptional ? null : new Redis(String(redisConnURL)); } - set(key: string | Buffer, value: string | Buffer | number) { - if (this.isOptional) return; - return this._client?.set(key, value); + static getInstance() { + if (!AniwatchAPICache.instance) { + AniwatchAPICache.instance = new AniwatchAPICache(); + } + return AniwatchAPICache.instance; } - get(key: string | Buffer) { - if (this.isOptional) return; - return this._client?.get(key); - } + // set(key: string | Buffer, value: string | Buffer | number) { + // if (this.isOptional) return; + // return this.client?.set(key, value); + // } + + // get(key: string | Buffer) { + // if (this.isOptional) return; + // return this.client?.get(key); + // } /** * @param expirySeconds set to 60 by default */ async getOrSet( - setCB: () => Promise, + dataGetter: () => Promise, key: string | Buffer, expirySeconds: number = AniwatchAPICache.DEFAULT_CACHE_EXPIRY_SECONDS ) { const cachedData = this.isOptional ? null - : (await this._client?.get(key)) || null; + : (await this.client?.get?.(key)) || null; let data = JSON.parse(String(cachedData)) as T; if (!data) { - data = await setCB(); - await this._client?.set( + data = await dataGetter(); + await this.client?.set?.( key, JSON.stringify(data), "EX", @@ -52,4 +59,4 @@ export class AniwatchAPICache { } } -export const cache = new AniwatchAPICache(); +export const cache = AniwatchAPICache.getInstance(); diff --git a/src/config/cors.ts b/src/config/cors.ts index 6d6e138..f6b5f25 100644 --- a/src/config/cors.ts +++ b/src/config/cors.ts @@ -1,17 +1,15 @@ -import { config } from "dotenv"; import { cors } from "hono/cors"; +import { env } from "./env.js"; -config(); +const DEFAULT_ALLOWED_ORIGINS = ["http://localhost:4000", "*"]; -const allowedOrigins = process.env.ANIWATCH_API_CORS_ALLOWED_ORIGINS - ? process.env.ANIWATCH_API_CORS_ALLOWED_ORIGINS.split(",") - : ["http://localhost:4000", "*"]; +const allowedOrigins = env.ANIWATCH_API_CORS_ALLOWED_ORIGINS + ? env.ANIWATCH_API_CORS_ALLOWED_ORIGINS.split(",") + : DEFAULT_ALLOWED_ORIGINS; -const corsConfig = cors({ - allowMethods: ["GET"], - maxAge: 600, - credentials: true, - origin: allowedOrigins, +export const corsConfig = cors({ + allowMethods: ["GET"], + maxAge: 600, + credentials: true, + origin: allowedOrigins, }); - -export default corsConfig; diff --git a/src/config/errorHandler.ts b/src/config/errorHandler.ts index d6f3193..36f57c3 100644 --- a/src/config/errorHandler.ts +++ b/src/config/errorHandler.ts @@ -1,27 +1,28 @@ import { HiAnimeError } from "aniwatch"; import type { ErrorHandler, NotFoundHandler } from "hono"; import type { ContentfulStatusCode } from "hono/utils/http-status"; +import { logger } from "./logger.js"; const errResp: { status: ContentfulStatusCode; message: string } = { - status: 500, - message: "Internal Server Error", + status: 500, + message: "Internal Server Error", }; export const errorHandler: ErrorHandler = (err, c) => { - console.error(err); + logger.error(JSON.stringify(err)); - if (err instanceof HiAnimeError) { - errResp.status = err.status as ContentfulStatusCode; - errResp.message = err.message; - } + if (err instanceof HiAnimeError) { + errResp.status = err.status as ContentfulStatusCode; + errResp.message = err.message; + } - return c.json(errResp, errResp.status); + return c.json(errResp, errResp.status); }; export const notFoundHandler: NotFoundHandler = (c) => { - errResp.status = 404; - errResp.message = "Not Found"; + errResp.status = 404; + errResp.message = "Not Found"; - console.error(errResp); - return c.json(errResp, errResp.status); + logger.error(JSON.stringify(errResp)); + return c.json(errResp, errResp.status); }; diff --git a/src/config/ratelimit.ts b/src/config/ratelimit.ts index afd6e54..8bfc308 100644 --- a/src/config/ratelimit.ts +++ b/src/config/ratelimit.ts @@ -1,21 +1,22 @@ -import { config } from "dotenv"; import { rateLimiter } from "hono-rate-limiter"; import { getConnInfo } from "@hono/node-server/conninfo"; - -config(); +import { env } from "./env.js"; export const ratelimit = rateLimiter({ - windowMs: Number(process.env.ANIWATCH_API_WINDOW_MS) || 30 * 60 * 1000, - limit: Number(process.env.ANIWATCH_API_MAX_REQS) || 6, - standardHeaders: "draft-7", - keyGenerator(c) { - const { remote } = getConnInfo(c); - const key = - `${String(remote.addressType)}_` + - `${String(remote.address)}:${String(remote.port)}`; + windowMs: env.ANIWATCH_API_WINDOW_MS, + limit: env.ANIWATCH_API_MAX_REQS, + standardHeaders: "draft-7", + keyGenerator(c) { + const { remote } = getConnInfo(c); + const key = + `${String(remote.addressType)}_` + + `${String(remote.address)}:${String(remote.port)}`; - return key; - }, - handler: (c) => - c.json({ status: 429, message: "Too Many Requests 😵" }, { status: 429 }), + return key; + }, + handler: (c) => + c.json( + { status: 429, message: "Too Many Requests 😵" }, + { status: 429 } + ), }); From b6288a02d85c108a5103ec1de15a1fa0b98d526e Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:07:17 +0530 Subject: [PATCH 07/16] feat: add custom pino logger --- src/config/logger.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/config/logger.ts diff --git a/src/config/logger.ts b/src/config/logger.ts new file mode 100644 index 0000000..990b32d --- /dev/null +++ b/src/config/logger.ts @@ -0,0 +1,26 @@ +import { env } from "./env.js"; +import { pino, type LoggerOptions } from "pino"; + +const loggerOptions: LoggerOptions = { + redact: env.isProduction ? ["hostname"] : [], + level: "info", + transport: env.isDev + ? { + target: "pino-pretty", + options: { + colorize: true, + translateTime: "SYS:standard", + }, + } + : undefined, + formatters: { + level(label) { + return { + level: label.toUpperCase(), + }; + }, + }, + timestamp: pino.stdTimeFunctions.isoTime, +}; + +export const logger = pino(loggerOptions); From 2054785565cc164ab2239b868816dbd17b36b7cc Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:07:43 +0530 Subject: [PATCH 08/16] feat: add new logging middleware that uses pino logger --- src/middleware/logging.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/middleware/logging.ts diff --git a/src/middleware/logging.ts b/src/middleware/logging.ts new file mode 100644 index 0000000..df25ac5 --- /dev/null +++ b/src/middleware/logging.ts @@ -0,0 +1,9 @@ +import type { MiddlewareHandler } from "hono"; +import { logger as honoLogger } from "hono/logger"; +import { logger } from "../config/logger.js"; + +export const logging: MiddlewareHandler = honoLogger( + (msg: string, ...rest: string[]) => { + logger.info(msg, ...rest); + } +); From e098e7cfcbbe2381a2ef5ca538e0a255cca0d8d7 Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:07:52 +0530 Subject: [PATCH 09/16] feat: integrate use of ebvalidated envs --- src/middleware/cache.ts | 45 +++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/middleware/cache.ts b/src/middleware/cache.ts index fc249c2..164f4a3 100644 --- a/src/middleware/cache.ts +++ b/src/middleware/cache.ts @@ -1,35 +1,32 @@ -import { config } from "dotenv"; -import { AniwatchAPICache } from "../config/cache.js"; import type { MiddlewareHandler } from "hono"; - -config(); +import { env } from "../config/env.js"; +import { AniwatchAPICache } from "../config/cache.js"; // Define middleware to add Cache-Control header -export const cacheControlMiddleware: MiddlewareHandler = async (c, next) => { - const sMaxAge = process.env.ANIWATCH_API_S_MAXAGE || "60"; - const staleWhileRevalidate = - process.env.ANIWATCH_API_STALE_WHILE_REVALIDATE || "30"; +export const cacheControl: MiddlewareHandler = async (c, next) => { + const sMaxAge = env.ANIWATCH_API_S_MAXAGE; + const staleWhileRevalidate = env.ANIWATCH_API_STALE_WHILE_REVALIDATE; - c.header( - "Cache-Control", - `s-maxage=${sMaxAge}, stale-while-revalidate=${staleWhileRevalidate}` - ); + c.header( + "Cache-Control", + `s-maxage=${sMaxAge}, stale-while-revalidate=${staleWhileRevalidate}` + ); - await next(); + await next(); }; export function cacheConfigSetter(keySliceIndex: number): MiddlewareHandler { - return async (c, next) => { - const { pathname, search } = new URL(c.req.url); + return async (c, next) => { + const { pathname, search } = new URL(c.req.url); - c.set("CACHE_CONFIG", { - key: `${pathname.slice(keySliceIndex) + search}`, - duration: Number( - c.req.header(AniwatchAPICache.CACHE_EXPIRY_HEADER_NAME) || - AniwatchAPICache.DEFAULT_CACHE_EXPIRY_SECONDS - ), - }); + c.set("CACHE_CONFIG", { + key: `${pathname.slice(keySliceIndex) + search}`, + duration: Number( + c.req.header(AniwatchAPICache.CACHE_EXPIRY_HEADER_NAME) || + AniwatchAPICache.DEFAULT_CACHE_EXPIRY_SECONDS + ), + }); - await next(); - }; + await next(); + }; } From 83076b672b0cdf48489e24ae00ee9b0f8edcc238 Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:08:12 +0530 Subject: [PATCH 10/16] feat: integrate custom logger and envalidated envs --- src/server.ts | 63 +++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/src/server.ts b/src/server.ts index 9164666..c218f03 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,40 +1,36 @@ import https from "https"; -import { config } from "dotenv"; - -import corsConfig from "./config/cors.js"; -import { ratelimit } from "./config/ratelimit.js"; - -import { - cacheConfigSetter, - cacheControlMiddleware, -} from "./middleware/cache.js"; -import { hianimeRouter } from "./routes/hianime.js"; import { Hono } from "hono"; -import { logger } from "hono/logger"; import { serve } from "@hono/node-server"; import { serveStatic } from "@hono/node-server/serve-static"; -import pkgJson from "../package.json" with { type: "json" }; +import { env } from "./config/env.js"; +import { logger } from "./config/logger.js"; +import { corsConfig } from "./config/cors.js"; +import { ratelimit } from "./config/ratelimit.js"; import { errorHandler, notFoundHandler } from "./config/errorHandler.js"; import type { AniwatchAPIVariables } from "./config/variables.js"; -config(); +import { hianimeRouter } from "./routes/hianime.js"; +import { logging } from "./middleware/logging.js"; +import { cacheConfigSetter, cacheControl } from "./middleware/cache.js"; +import pkgJson from "../package.json" with { type: "json" }; + +// const BASE_PATH = "/api/v2" as const; -const PORT: number = Number(process.env.ANIWATCH_API_PORT) || 4000; -const ANIWATCH_API_HOSTNAME = process.env?.ANIWATCH_API_HOSTNAME; const app = new Hono<{ Variables: AniwatchAPIVariables }>(); -app.use(logger()); +app.use(logging); app.use(corsConfig); -app.use(cacheControlMiddleware); +app.use(cacheControl); +// // CAUTION: For personal deployments, "refrain" from having an env // named "ANIWATCH_API_HOSTNAME". You may face rate limitting // or other issues if you do. -const ISNT_PERSONAL_DEPLOYMENT = Boolean(ANIWATCH_API_HOSTNAME); +const ISNT_PERSONAL_DEPLOYMENT = Boolean(env.ANIWATCH_API_HOSTNAME); if (ISNT_PERSONAL_DEPLOYMENT) { app.use(ratelimit); } @@ -43,7 +39,8 @@ app.use("/", serveStatic({ root: "public" })); app.get("/health", (c) => c.text("daijoubu", { status: 200 })); app.get("/v", async (c) => c.text( - `v${"version" in pkgJson && pkgJson?.version ? pkgJson.version : "-1"}` + `aniwatch-api: v${"version" in pkgJson && pkgJson?.version ? pkgJson.version : "-1"}\n` + + `aniwatch-package: v${"dependencies" in pkgJson && pkgJson?.dependencies?.aniwatch ? pkgJson?.dependencies?.aniwatch : "-1"}` ) ); @@ -57,18 +54,17 @@ app.basePath(BASE_PATH).get("/anicrush", (c) => app.notFound(notFoundHandler); app.onError(errorHandler); +// // NOTE: this env is "required" for vercel deployments -if (!Boolean(process.env?.ANIWATCH_API_VERCEL_DEPLOYMENT)) { +if (!env.ANIWATCH_API_VERCEL_DEPLOYMENT) { serve({ - port: PORT, + port: env.ANIWATCH_API_PORT, fetch: app.fetch, - }).addListener("listening", () => - console.info( - "\x1b[1;36m" + - `aniwatch-api at http://localhost:${PORT}` + - "\x1b[0m" - ) - ); + }).addListener("listening", () => { + logger.info( + `aniwatch-api RUNNING at http://localhost:${env.ANIWATCH_API_PORT}` + ); + }); // NOTE: remove the `if` block below for personal deployments if (ISNT_PERSONAL_DEPLOYMENT) { @@ -76,15 +72,12 @@ if (!Boolean(process.env?.ANIWATCH_API_VERCEL_DEPLOYMENT)) { // don't sleep setInterval(() => { - console.log( - "aniwatch-api HEALTH_CHECK at", - new Date().toISOString() + logger.info( + `aniwatch-api HEALTH_CHECK at ${new Date().toISOString()}` ); https - .get(`https://${ANIWATCH_API_HOSTNAME}/health`) - .on("error", (err) => { - console.error(err.message); - }); + .get(`https://${env.ANIWATCH_API_HOSTNAME}/health`) + .on("error", (err) => logger.error(err.message.trim())); }, interval); } } From e23383f4c49a94e9a24d446cee10b94be4d6513c Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:10:46 +0530 Subject: [PATCH 11/16] ci: update lint and format check workflow --- .github/workflows/lint-&-format-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-&-format-check.yml b/.github/workflows/lint-&-format-check.yml index 4995e30..e17ccb2 100644 --- a/.github/workflows/lint-&-format-check.yml +++ b/.github/workflows/lint-&-format-check.yml @@ -23,7 +23,7 @@ jobs: run: npm i - name: Check lint - run: npm run lint:ci + run: npm run lint - name: Check prettier run: npm run format:check From 36b4f8acfc508ffffb24c84a3e1a8f35abc78560 Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Sun, 11 May 2025 13:31:58 +0530 Subject: [PATCH 12/16] refactor: rename logger to log --- src/config/errorHandler.ts | 6 +++--- src/config/logger.ts | 2 +- src/middleware/logging.ts | 4 ++-- src/server.ts | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/config/errorHandler.ts b/src/config/errorHandler.ts index 36f57c3..140fa1f 100644 --- a/src/config/errorHandler.ts +++ b/src/config/errorHandler.ts @@ -1,7 +1,7 @@ import { HiAnimeError } from "aniwatch"; import type { ErrorHandler, NotFoundHandler } from "hono"; import type { ContentfulStatusCode } from "hono/utils/http-status"; -import { logger } from "./logger.js"; +import { log } from "./logger.js"; const errResp: { status: ContentfulStatusCode; message: string } = { status: 500, @@ -9,7 +9,7 @@ const errResp: { status: ContentfulStatusCode; message: string } = { }; export const errorHandler: ErrorHandler = (err, c) => { - logger.error(JSON.stringify(err)); + log.error(JSON.stringify(err)); if (err instanceof HiAnimeError) { errResp.status = err.status as ContentfulStatusCode; @@ -23,6 +23,6 @@ export const notFoundHandler: NotFoundHandler = (c) => { errResp.status = 404; errResp.message = "Not Found"; - logger.error(JSON.stringify(errResp)); + log.error(JSON.stringify(errResp)); return c.json(errResp, errResp.status); }; diff --git a/src/config/logger.ts b/src/config/logger.ts index 990b32d..2c20fa0 100644 --- a/src/config/logger.ts +++ b/src/config/logger.ts @@ -23,4 +23,4 @@ const loggerOptions: LoggerOptions = { timestamp: pino.stdTimeFunctions.isoTime, }; -export const logger = pino(loggerOptions); +export const log = pino(loggerOptions); diff --git a/src/middleware/logging.ts b/src/middleware/logging.ts index df25ac5..116ca83 100644 --- a/src/middleware/logging.ts +++ b/src/middleware/logging.ts @@ -1,9 +1,9 @@ import type { MiddlewareHandler } from "hono"; import { logger as honoLogger } from "hono/logger"; -import { logger } from "../config/logger.js"; +import { log } from "../config/logger.js"; export const logging: MiddlewareHandler = honoLogger( (msg: string, ...rest: string[]) => { - logger.info(msg, ...rest); + log.info(msg, ...rest); } ); diff --git a/src/server.ts b/src/server.ts index c218f03..15bc93e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,7 +5,7 @@ import { serve } from "@hono/node-server"; import { serveStatic } from "@hono/node-server/serve-static"; import { env } from "./config/env.js"; -import { logger } from "./config/logger.js"; +import { log } from "./config/logger.js"; import { corsConfig } from "./config/cors.js"; import { ratelimit } from "./config/ratelimit.js"; import { errorHandler, notFoundHandler } from "./config/errorHandler.js"; @@ -61,7 +61,7 @@ if (!env.ANIWATCH_API_VERCEL_DEPLOYMENT) { port: env.ANIWATCH_API_PORT, fetch: app.fetch, }).addListener("listening", () => { - logger.info( + log.info( `aniwatch-api RUNNING at http://localhost:${env.ANIWATCH_API_PORT}` ); }); @@ -72,12 +72,12 @@ if (!env.ANIWATCH_API_VERCEL_DEPLOYMENT) { // don't sleep setInterval(() => { - logger.info( + log.info( `aniwatch-api HEALTH_CHECK at ${new Date().toISOString()}` ); https .get(`https://${env.ANIWATCH_API_HOSTNAME}/health`) - .on("error", (err) => logger.error(err.message.trim())); + .on("error", (err) => log.error(err.message.trim())); }, interval); } } From a72f469f40a75acb80c69eb2c77f3050b53c61ad Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Mon, 12 May 2025 00:01:24 +0530 Subject: [PATCH 13/16] feat: update to the latest aniwatch pkg --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c55ade..b437cb3 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "license": "MIT", "dependencies": { "@hono/node-server": "^1.14.1", - "aniwatch": "^2.21.2", + "aniwatch": "^2.22.0", "dotenv": "^16.5.0", "envalid": "^8.0.0", "hono": "^4.7.9", From 468a6e3ef91a39acbf9d5dc3f762968b8e4f5f0b Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Mon, 12 May 2025 00:03:39 +0530 Subject: [PATCH 14/16] test: update tests directory --- {test => __tests__}/animeAboutInfo.test.ts | 0 {test => __tests__}/animeCategory.test.ts | 0 {test => __tests__}/animeEpisodeSrcs.test.ts | 0 {test => __tests__}/animeEpisodes.test.ts | 0 {test => __tests__}/animeGenre.test.ts | 0 {test => __tests__}/animeProducer.test.ts | 0 {test => __tests__}/animeSearch.test.ts | 0 {test => __tests__}/animeSearchSuggestion.test.ts | 0 {test => __tests__}/episodeServers.test.ts | 0 {test => __tests__}/estimatedSchedule.test.ts | 0 {test => __tests__}/homePage.test.ts | 0 {test => __tests__}/nextEpisodeSchedule.test.ts | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename {test => __tests__}/animeAboutInfo.test.ts (100%) rename {test => __tests__}/animeCategory.test.ts (100%) rename {test => __tests__}/animeEpisodeSrcs.test.ts (100%) rename {test => __tests__}/animeEpisodes.test.ts (100%) rename {test => __tests__}/animeGenre.test.ts (100%) rename {test => __tests__}/animeProducer.test.ts (100%) rename {test => __tests__}/animeSearch.test.ts (100%) rename {test => __tests__}/animeSearchSuggestion.test.ts (100%) rename {test => __tests__}/episodeServers.test.ts (100%) rename {test => __tests__}/estimatedSchedule.test.ts (100%) rename {test => __tests__}/homePage.test.ts (100%) rename {test => __tests__}/nextEpisodeSchedule.test.ts (100%) diff --git a/test/animeAboutInfo.test.ts b/__tests__/animeAboutInfo.test.ts similarity index 100% rename from test/animeAboutInfo.test.ts rename to __tests__/animeAboutInfo.test.ts diff --git a/test/animeCategory.test.ts b/__tests__/animeCategory.test.ts similarity index 100% rename from test/animeCategory.test.ts rename to __tests__/animeCategory.test.ts diff --git a/test/animeEpisodeSrcs.test.ts b/__tests__/animeEpisodeSrcs.test.ts similarity index 100% rename from test/animeEpisodeSrcs.test.ts rename to __tests__/animeEpisodeSrcs.test.ts diff --git a/test/animeEpisodes.test.ts b/__tests__/animeEpisodes.test.ts similarity index 100% rename from test/animeEpisodes.test.ts rename to __tests__/animeEpisodes.test.ts diff --git a/test/animeGenre.test.ts b/__tests__/animeGenre.test.ts similarity index 100% rename from test/animeGenre.test.ts rename to __tests__/animeGenre.test.ts diff --git a/test/animeProducer.test.ts b/__tests__/animeProducer.test.ts similarity index 100% rename from test/animeProducer.test.ts rename to __tests__/animeProducer.test.ts diff --git a/test/animeSearch.test.ts b/__tests__/animeSearch.test.ts similarity index 100% rename from test/animeSearch.test.ts rename to __tests__/animeSearch.test.ts diff --git a/test/animeSearchSuggestion.test.ts b/__tests__/animeSearchSuggestion.test.ts similarity index 100% rename from test/animeSearchSuggestion.test.ts rename to __tests__/animeSearchSuggestion.test.ts diff --git a/test/episodeServers.test.ts b/__tests__/episodeServers.test.ts similarity index 100% rename from test/episodeServers.test.ts rename to __tests__/episodeServers.test.ts diff --git a/test/estimatedSchedule.test.ts b/__tests__/estimatedSchedule.test.ts similarity index 100% rename from test/estimatedSchedule.test.ts rename to __tests__/estimatedSchedule.test.ts diff --git a/test/homePage.test.ts b/__tests__/homePage.test.ts similarity index 100% rename from test/homePage.test.ts rename to __tests__/homePage.test.ts diff --git a/test/nextEpisodeSchedule.test.ts b/__tests__/nextEpisodeSchedule.test.ts similarity index 100% rename from test/nextEpisodeSchedule.test.ts rename to __tests__/nextEpisodeSchedule.test.ts From 9e7b52ff9408ee55ff3533fedad1b3d2eeb08fe3 Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Mon, 12 May 2025 00:04:32 +0530 Subject: [PATCH 15/16] test: fix failing nextEpisodeSchedule test case --- __tests__/nextEpisodeSchedule.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/__tests__/nextEpisodeSchedule.test.ts b/__tests__/nextEpisodeSchedule.test.ts index 9abe1d3..7890aa9 100644 --- a/__tests__/nextEpisodeSchedule.test.ts +++ b/__tests__/nextEpisodeSchedule.test.ts @@ -1,11 +1,20 @@ import { expect, test } from "vitest"; import { HiAnime } from "aniwatch"; +function padZero(num: number) { + return num < 10 ? `0${num}` : num.toString(); +} + // npx vitest run nextEpisodeSchedule.test.ts test("returns anime next episode schedule", async () => { const hianime = new HiAnime.Scraper(); - const animeId = "one-piece-100"; + const d = new Date(); + const scheduleData = await hianime.getEstimatedSchedule( + `${d.getFullYear()}-${padZero(d.getMonth() + 1)}-${padZero(d.getDate())}` + ); + + const animeId = scheduleData.scheduledAnimes[0].id!; const data = await hianime.getNextEpisodeSchedule(animeId); expect(data.airingISOTimestamp).not.toEqual(null); From cedbd9392938c1d28e8f555712238d2c8de7ddb4 Mon Sep 17 00:00:00 2001 From: Ritesh Ghosh Date: Mon, 12 May 2025 00:06:55 +0530 Subject: [PATCH 16/16] test: add missing test cases --- __tests__/animeAZList.test.ts | 10 ++++++++++ __tests__/animeQtip.test.ts | 15 +++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 __tests__/animeAZList.test.ts create mode 100644 __tests__/animeQtip.test.ts diff --git a/__tests__/animeAZList.test.ts b/__tests__/animeAZList.test.ts new file mode 100644 index 0000000..799676c --- /dev/null +++ b/__tests__/animeAZList.test.ts @@ -0,0 +1,10 @@ +import { expect, test } from "vitest"; +import { HiAnime } from "aniwatch"; + +// npx vitest run animeAZList.test.ts +test("returns az list anime", async () => { + const hianime = new HiAnime.Scraper(); + const data = await hianime.getAZList("0-9", 1); + + expect(data.animes).not.toEqual([]); +}); diff --git a/__tests__/animeQtip.test.ts b/__tests__/animeQtip.test.ts new file mode 100644 index 0000000..9498f39 --- /dev/null +++ b/__tests__/animeQtip.test.ts @@ -0,0 +1,15 @@ +import { expect, test } from "vitest"; +import { HiAnime } from "aniwatch"; + +const animeId = "one-piece-100"; + +// npx vitest run animeQtip.test.ts +test(`returns ${animeId} anime qtip info`, async () => { + const hianime = new HiAnime.Scraper(); + const data = await hianime.getQtipInfo(animeId); + + expect(data.anime.id).not.toEqual(null); + expect(data.anime.name).not.toEqual(null); + expect(data.anime.description).not.toEqual(null); + expect(data.anime.genres).not.toEqual([]); +});