Kaynağa Gözat

Merge pull request #16 from Sv443/ver/1.3.1

Sven Fehler 2 yıl önce
ebeveyn
işleme
229a8e2bf7
10 değiştirilmiş dosya ile 66 ekleme ve 49 silme
  1. 5 0
      .env.template
  2. 21 29
      README.md
  3. 11 1
      changelog.md
  4. 2 2
      package-lock.json
  5. 1 1
      package.json
  6. 1 3
      src/index.ts
  7. 1 1
      src/routes/translations.ts
  8. 24 9
      src/server.ts
  9. 0 2
      src/types.d.ts
  10. 0 1
      src/utils.ts

+ 5 - 0
.env.template

@@ -1,8 +1,13 @@
+# Has to be either development or production
+NODE_ENV=development
+
 # Default is 8074
 # Default is 8074
 HTTP_PORT=8074
 HTTP_PORT=8074
 # Defaults to 0.0.0.0 (listen on all interfaces)
 # Defaults to 0.0.0.0 (listen on all interfaces)
 HTTP_HOST=
 HTTP_HOST=
+
 # Gotten from creating a client on https://genius.com/api-clients
 # Gotten from creating a client on https://genius.com/api-clients
 GENIUS_ACCESS_TOKEN=
 GENIUS_ACCESS_TOKEN=
+
 # Comma-separated list of HTTP bearer tokens that are excluded from rate limiting (on geniURL's side)
 # Comma-separated list of HTTP bearer tokens that are excluded from rate limiting (on geniURL's side)
 AUTH_TOKENS=
 AUTH_TOKENS=

+ 21 - 29
README.md

@@ -20,20 +20,24 @@ https://api.sv443.net/geniurl/
 ```
 ```
 
 
 <sub>
 <sub>
-Note that this instance is rate limited to 10 requests within 30 seconds per unique client.
+Note that this instance is rate limited to 5 requests within 15 seconds per unique client.
 </sub>
 </sub>
 
 
 <br><br>
 <br><br>
 
 
 ## Routes:
 ## Routes:
-All routes support gzip and deflate compression.
-
+All routes support gzip and deflate compression.  
+  
+Also all routes always return an `error` and `matches` property.  
+They can be used to determine whether a response has succeeded (error = false, matches > 0), is errored (error = true, matches = null) or just didn't yield any results (error = true, matches = 0).  
+  
+These are the available routes:
 - [Search](#get-search)
 - [Search](#get-search)
     - [Search (only top result)](#get-searchtop)
     - [Search (only top result)](#get-searchtop)
 - [Translations](#get-translationssongid)
 - [Translations](#get-translationssongid)
 - [Associated Album](#get-albumsongid)
 - [Associated Album](#get-albumsongid)
 
 
-<br>
+<br><br>
 
 
 > ### GET `/search`
 > ### GET `/search`
 >
 >
@@ -121,8 +125,7 @@ All routes support gzip and deflate compression.
 >         // This array contains up to 10 objects with the same structure as 'top', sorted best match first
 >         // This array contains up to 10 objects with the same structure as 'top', sorted best match first
 >         // The amount of objects in here is the same as the 'matches' property
 >         // The amount of objects in here is the same as the 'matches' property
 >         // The first object of this array is exactly the same as 'top'
 >         // The first object of this array is exactly the same as 'top'
->     ],
->     "timestamp": 1234567890123
+>     ]
 > }
 > }
 > ```
 > ```
 >
 >
@@ -134,8 +137,7 @@ All routes support gzip and deflate compression.
 > {
 > {
 >     "error": true,
 >     "error": true,
 >     "matches": null,
 >     "matches": null,
->     "message": "Something went wrong",
->     "timestamp": 1234567890123
+>     "message": "Something went wrong"
 > }
 > }
 > ```
 > ```
 >
 >
@@ -147,8 +149,7 @@ All routes support gzip and deflate compression.
 > {
 > {
 >     "error": true,
 >     "error": true,
 >     "matches": 0,
 >     "matches": 0,
->     "message": "Found no results matching your search query",
->     "timestamp": 1234567890123
+>     "message": "Found no results matching your search query"
 > }
 > }
 > ```
 > ```
 >
 >
@@ -235,8 +236,7 @@ All routes support gzip and deflate compression.
 >         "image": "https://images.genius.com/..."
 >         "image": "https://images.genius.com/..."
 >     },
 >     },
 >     "lyricsState": "complete",
 >     "lyricsState": "complete",
->     "id": 42069,
->     "timestamp": 1234567890123
+>     "id": 42069
 > }
 > }
 > ```
 > ```
 >
 >
@@ -248,8 +248,7 @@ All routes support gzip and deflate compression.
 > {
 > {
 >     "error": true,
 >     "error": true,
 >     "matches": null,
 >     "matches": null,
->     "message": "Something went wrong",
->     "timestamp": 1234567890123
+>     "message": "Something went wrong"
 > }
 > }
 > ```
 > ```
 >
 >
@@ -261,8 +260,7 @@ All routes support gzip and deflate compression.
 > {
 > {
 >     "error": true,
 >     "error": true,
 >     "matches": 0,
 >     "matches": 0,
->     "message": "Found no results matching your search query",
->     "timestamp": 1234567890123
+>     "message": "Found no results matching your search query"
 > }
 > }
 > ```
 > ```
 >
 >
@@ -306,8 +304,7 @@ All routes support gzip and deflate compression.
 >             "path": "/Genius-traducciones-al-espanol-artist-song-al-espanol-lyrics",
 >             "path": "/Genius-traducciones-al-espanol-artist-song-al-espanol-lyrics",
 >             "id": 6942
 >             "id": 6942
 >         }
 >         }
->     ],
->     "timestamp": 1234567890123
+>     ]
 > }
 > }
 > ```
 > ```
 >
 >
@@ -319,8 +316,7 @@ All routes support gzip and deflate compression.
 > {
 > {
 >     "error": true,
 >     "error": true,
 >     "matches": null,
 >     "matches": null,
->     "message": "Something went wrong",
->     "timestamp": 1234567890123
+>     "message": "Something went wrong"
 > }
 > }
 > ```
 > ```
 >
 >
@@ -330,10 +326,9 @@ All routes support gzip and deflate compression.
 >
 >
 > ```json
 > ```json
 > {
 > {
->     "error": false,
+>     "error": true,
 >     "matches": 0,
 >     "matches": 0,
->     "translations": [],
->     "timestamp": 1234567890123
+>     "translations": []
 > }
 > }
 > ```
 > ```
 >
 >
@@ -375,8 +370,7 @@ All routes support gzip and deflate compression.
 >             "image": "https://images.genius.com/...",
 >             "image": "https://images.genius.com/...",
 >             "headerImage": "https://images.genius.com/..."
 >             "headerImage": "https://images.genius.com/..."
 >         }
 >         }
->     },
->     "timestamp": 1234567890123
+>     }
 > }
 > }
 > ```
 > ```
 >
 >
@@ -388,8 +382,7 @@ All routes support gzip and deflate compression.
 > {
 > {
 >     "error": true,
 >     "error": true,
 >     "matches": null,
 >     "matches": null,
->     "message": "Something went wrong",
->     "timestamp": 1234567890123
+>     "message": "Something went wrong"
 > }
 > }
 > ```
 > ```
 >
 >
@@ -401,8 +394,7 @@ All routes support gzip and deflate compression.
 > {
 > {
 >     "error": true,
 >     "error": true,
 >     "matches": 0,
 >     "matches": 0,
->     "message": "Couldn't find any associated album for this song",
->     "timestamp": 1234567890123
+>     "message": "Couldn't find any associated album for this song"
 > }
 > }
 > ```
 > ```
 >
 >

+ 11 - 1
changelog.md

@@ -1,5 +1,6 @@
 ## Version History:
 ## Version History:
-- [**1.3.0**](#v130)
+- [**1.3.1**](#v131)
+- [1.3.0](#v130)
 - [1.2.0](#v120)
 - [1.2.0](#v120)
 - [1.1.1](#v111)
 - [1.1.1](#v111)
 - [1.1.0](#v110)
 - [1.1.0](#v110)
@@ -9,6 +10,15 @@
 
 
 <br><br>
 <br><br>
 
 
+### v1.3.1
+- Fixed inconsistent `error` property when no translations are found
+- Added support for preflight through an OPTIONS request
+- Improved rate-limit header consistency
+- Removed timestamp property to allow for better caching
+- Made documentation more clear
+
+<br>
+
 ### v1.3.0
 ### v1.3.0
 - Added route `/translations/:songId` to receive info about a song's translation pages
 - Added route `/translations/:songId` to receive info about a song's translation pages
 - Added route `/album/:songId` to get info about the album that the provided song is in
 - Added route `/album/:songId` to get info about the album that the provided song is in

+ 2 - 2
package-lock.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "geniurl",
   "name": "geniurl",
-  "version": "1.3.0",
+  "version": "1.3.1",
   "lockfileVersion": 2,
   "lockfileVersion": 2,
   "requires": true,
   "requires": true,
   "packages": {
   "packages": {
@@ -10777,4 +10777,4 @@
       "dev": true
       "dev": true
     }
     }
   }
   }
-}
+}

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "geniurl",
   "name": "geniurl",
-  "version": "1.3.0",
+  "version": "1.3.1",
   "description": "Simple JSON and XML REST API to search for song metadata, the lyrics URL and lyrics translations on genius.com",
   "description": "Simple JSON and XML REST API to search for song metadata, the lyrics URL and lyrics translations on genius.com",
   "main": "src/index.ts",
   "main": "src/index.ts",
   "scripts": {
   "scripts": {

+ 1 - 3
src/index.ts

@@ -1,6 +1,4 @@
-import dotenv from "dotenv";
-
-dotenv.config();
+import "dotenv/config";
 
 
 import * as server from "./server";
 import * as server from "./server";
 import { error } from "./error";
 import { error } from "./error";

+ 1 - 1
src/routes/translations.ts

@@ -24,7 +24,7 @@ export function initTranslationsRoutes(router: Router) {
 
 
             const translations = await getTranslations(Number(songId), { preferLang });
             const translations = await getTranslations(Number(songId), { preferLang });
 
 
-            if(!translations)
+            if(!translations || translations.length === 0)
                 return respond(res, "clientError", "Couldn't find translations for this song", format, 0);
                 return respond(res, "clientError", "Couldn't find translations for this song", format, 0);
 
 
             return respond(res, "success", { translations }, format, translations.length);
             return respond(res, "success", { translations }, format, translations.length);

+ 24 - 9
src/server.ts

@@ -11,26 +11,37 @@ import { error } from "./error";
 import { initRouter } from "./routes";
 import { initRouter } from "./routes";
 import { respond } from "./utils";
 import { respond } from "./utils";
 
 
+const { env } = process;
 const app = express();
 const app = express();
 
 
-app.use(cors({ methods: "GET,HEAD,OPTIONS", origin: "*" }));
-app.use(helmet());
+app.use(cors({
+    methods: "GET,HEAD,OPTIONS",
+    origin: "*",
+}));
+app.use(helmet({ 
+    dnsPrefetchControl: true,
+}));
+app.use(compression({
+    threshold: 256
+}));
 app.use(express.json());
 app.use(express.json());
-app.use(compression());
+
+if(env.NODE_ENV?.toLowerCase() === "production")
+    app.enable("trust proxy");
 
 
 app.disable("x-powered-by");
 app.disable("x-powered-by");
 
 
 const rateLimiter = new RateLimiterMemory({
 const rateLimiter = new RateLimiterMemory({
-    points: 10,
-    duration: 30,
+    points: 5,
+    duration: 15,
 });
 });
 
 
 const authTokens = getAuthTokens();
 const authTokens = getAuthTokens();
 
 
 export async function init()
 export async function init()
 {
 {
-    const port = parseInt(String(process.env.HTTP_PORT ?? "").trim());
-    const hostRaw = String(process.env.HTTP_HOST ?? "").trim();
+    const port = parseInt(String(env.HTTP_PORT ?? "").trim());
+    const hostRaw = String(env.HTTP_HOST ?? "").trim();
     const host = hostRaw.length < 1 ? "0.0.0.0" : hostRaw;
     const host = hostRaw.length < 1 ? "0.0.0.0" : hostRaw;
 
 
     if(await portUsed(port))
     if(await portUsed(port))
@@ -44,6 +55,9 @@ export async function init()
             return next();
             return next();
     });
     });
 
 
+    // preflight requests
+    app.options("*", cors());
+
     // rate limiting
     // rate limiting
     app.use(async (req, res, next) => {
     app.use(async (req, res, next) => {
         const fmt = req?.query?.format ? String(req.query.format) : undefined;
         const fmt = req?.query?.format ? String(req.query.format) : undefined;
@@ -56,7 +70,8 @@ export async function init()
             return next();
             return next();
 
 
         const setRateLimitHeaders = (rateLimiterRes: RateLimiterRes) => {
         const setRateLimitHeaders = (rateLimiterRes: RateLimiterRes) => {
-            res.setHeader("Retry-After", rateLimiterRes.msBeforeNext / 1000);
+            if(rateLimiterRes.remainingPoints === 0)
+                res.setHeader("Retry-After", Math.ceil(rateLimiterRes.msBeforeNext / 1000));
             res.setHeader("X-RateLimit-Limit", rateLimiter.points);
             res.setHeader("X-RateLimit-Limit", rateLimiter.points);
             res.setHeader("X-RateLimit-Remaining", rateLimiterRes.remainingPoints);
             res.setHeader("X-RateLimit-Remaining", rateLimiterRes.remainingPoints);
             res.setHeader("X-RateLimit-Reset", new Date(Date.now() + rateLimiterRes.msBeforeNext).toISOString());
             res.setHeader("X-RateLimit-Reset", new Date(Date.now() + rateLimiterRes.msBeforeNext).toISOString());
@@ -70,7 +85,7 @@ export async function init()
             .catch((err) => {
             .catch((err) => {
                 if(err instanceof RateLimiterRes) {
                 if(err instanceof RateLimiterRes) {
                     setRateLimitHeaders(err);
                     setRateLimitHeaders(err);
-                    return respond(res, 429, { message: "You are being rate limited. Please try again a little later." }, fmt);
+                    return respond(res, 429, { error: true, matches: null, message: "You are being rate limited. Refer to the Retry-After header for when to try again." }, fmt);
                 }
                 }
                 else return respond(res, 500, { message: "Encountered an internal error. Please try again a little later." }, fmt);
                 else return respond(res, 500, { message: "Encountered an internal error. Please try again a little later." }, fmt);
             });
             });

+ 0 - 2
src/types.d.ts

@@ -5,14 +5,12 @@ export type ServerResponse<T> = SuccessResponse<T> | ErrorResponse;
 export type SuccessResponse<T> = {
 export type SuccessResponse<T> = {
     error: false;
     error: false;
     matches: number;
     matches: number;
-    timestamp: number;
 } & T;
 } & T;
 
 
 export type ErrorResponse = {
 export type ErrorResponse = {
     error: true;
     error: true;
     matches: 0 | null;
     matches: 0 | null;
     message: string;
     message: string;
-    timestamp: number;
 }
 }
 
 
 //#SECTION meta
 //#SECTION meta

+ 0 - 1
src/utils.ts

@@ -62,7 +62,6 @@ export function respond(res: Response, type: ResponseType | number, data: String
         error,
         error,
         ...(matches === undefined ? {} : { matches }),
         ...(matches === undefined ? {} : { matches }),
         ...resData,
         ...resData,
-        timestamp: Date.now(),
     };
     };
 
 
     const finalData = format === "xml" ? jsonToXml("data", resData) : resData;
     const finalData = format === "xml" ? jsonToXml("data", resData) : resData;