Browse Source

ref: cache stuff and mime type map constant

Sv443 4 months ago
parent
commit
a21e74b9cf
4 changed files with 46 additions and 41 deletions
  1. 2 2
      .vscode/settings.json
  2. 32 15
      src/constants.ts
  3. 0 13
      src/routes/search.ts
  4. 12 11
      src/utils.ts

+ 2 - 2
.vscode/settings.json

@@ -20,9 +20,9 @@
       "overviewRulerColor": "#ed0",
     },
     "((//\\s*|/\\*\\s*)?#region ([^\\S\\r\\n]*[\\(\\)\\w,.\\-_&+#*'\"/:]+)*)": { //#region test: (abc):
-      "backgroundColor": "#5df",
+      "backgroundColor": "#35b5d0",
       "color": "#000",
-      "overviewRulerColor": "#5df",
+      "overviewRulerColor": "#35b5d0",
     },
     "((<!--\\s*)?</?\\{\\{[A-Z_-]+\\}\\}>(\\s*-->)?)": { // <!-- <{{FOO}}> --> and <!-- </{{FOO}}> --> or <{{BAR}}> and </{{BAR}}>
       "backgroundColor": "#9af",

+ 32 - 15
src/constants.ts

@@ -1,23 +1,47 @@
 import { resolve } from "node:path";
 import type { IRateLimiterOptions } from "rate-limiter-flexible";
+import type { ResponseFormat } from "./types.js";
 import packageJson from "../package.json" with { type: "json" };
 
+//#region rate limiting
+
+/** Options for the rate limiter */
+export const rateLimitOptions: IRateLimiterOptions = {
+  points: 20,
+  duration: 30,
+};
+
+/** Any requests to paths starting with one of these will not be subject to rate limiting */
+export const rlIgnorePaths = [
+  "/docs",
+];
+
+//#region docs
+
+/** Path to the VuePress build output folder - this is what gets served as the docs by the API if the `HOST_WEBSITE` env var is set to `true` */
+export const docsPath = resolve("./www/.vuepress/dist");
+
+/** Max age of the docs in milliseconds */
+export const docsMaxAge = 1000 * 60 * 60 * 24 * 2; // 2 days
+
+//#region misc
+
 /** Max amount of results that geniURL can serve */
 export const maxResultsAmt = 10;
 
+//#region other
+
 /** The version from package.json, split into a tuple of major, minor, and patch number */
 export const splitVersion = packageJson.version.split(".").map(v => Number(v)) as [major: number, minor: number, patch: number];
 
+/** Major, minor, and patch version numbers */
 export const [verMajor, verMinor, verPatch] = splitVersion;
 
-/** Options for the rate limiter */
-export const rateLimitOptions: IRateLimiterOptions = {
-  points: 20,
-  duration: 30,
-};
-
-/** Set of all supported [ISO 639-1 language codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) */
-export const langCodes = new Set<string>(["aa","ab","ae","af","ak","am","an","ar","as","av","ay","az","ba","be","bg","bh","bi","bm","bn","bo","br","bs","ca","ce","ch","co","cr","cs","cu","cv","cy","da","de","dv","dz","ee","el","en","eo","es","et","eu","fa","ff","fi","fj","fo","fr","fy","ga","gd","gl","gn","gu","gv","ha","he","hi","ho","hr","ht","hu","hy","hz","ia","id","ie","ig","ii","ik","io","is","it","iu","ja","jv","ka","kg","ki","kj","kk","kl","km","kn","ko","kr","ks","ku","kv","kw","ky","la","lb","lg","li","ln","lo","lt","lu","lv","mg","mh","mi","mk","ml","mn","mr","ms","mt","my","na","nb","nd","ne","ng","nl","nn","no","nr","nv","ny","oc","oj","om","or","os","pa","pi","pl","ps","pt","qu","rm","rn","ro","ru","rw","sa","sc","sd","se","sg","si","sk","sl","sm","sn","so","sq","sr","ss","st","su","sv","sw","ta","te","tg","th","ti","tk","tl","tn","to","tr","ts","tt","tw","ty","ug","uk","ur","uz","ve","vi","vo","wa","wo","xh","yi","yo","za","zh","zu"]);
+/** Map of response formats and their corresponding MIME types */
+export const mimeTypeMap = {
+  json: "application/json",
+  xml: "application/xml",
+} as const satisfies Record<ResponseFormat, string>;
 
 /** Map of unicode variant characters and replacements used in normalizing strings served by the genius API */
 export const charReplacements = new Map<string, string>([
@@ -27,10 +51,3 @@ export const charReplacements = new Map<string, string>([
   ["—─ ", "-"],
   ["     ", " "],
 ]);
-
-/** Any requests to paths starting with one of these will not be subject to rate limiting */
-export const rlIgnorePaths = [
-  "/docs",
-];
-
-export const docsPath = resolve("./www/.vuepress/dist");

+ 0 - 13
src/routes/search.ts

@@ -68,17 +68,4 @@ export function initSearchRoutes(router: Router) {
       return respond(res, "serverError", `Encountered an internal server error${err instanceof Error ? err.message : ""}`, "json");
     }
   });
-
-  // TODO: adjust
-  //#region /search/manual
-  router.get("/search/manual", (_req, res) => {
-    res.sendFile("index.html", {
-      root: "public",
-      dotfiles: "deny",
-      headers: {
-        "Content-Type": "text/html; charset=utf-8",
-      },
-      maxAge: 1000 * 60 * 60 * 24 * 7,
-    });
-  });
 }

+ 12 - 11
src/utils.ts

@@ -2,8 +2,8 @@ import { createHash, type BinaryToTextEncoding } from "node:crypto";
 import { Response } from "express";
 import { parse as jsonToXml } from "js2xmlparser";
 import type { Stringifiable } from "svcorelib";
-import { verMajor } from "@src/constants.js";
-import type { ResponseType } from "@src/types.js";
+import { docsMaxAge, mimeTypeMap, verMajor } from "@src/constants.js";
+import type { ResponseFormat, ResponseType } from "@src/types.js";
 
 /** Checks if the value of a passed URL parameter is a string with length > 0 */
 export function paramValid(val: unknown): boolean {
@@ -15,24 +15,23 @@ export function paramValid(val: unknown): boolean {
 /**
  * Responds to a request in a uniform way
  * @param res Express response object
- * @param type Type of response or status code
+ * @param typeOrStatusCode Type of response or status code
  * @param data The data to send in the response body
  * @param format Response format "json" or "xml"
  * @param matchesAmt Amount of matches / datasets returned in this response
  */
-export function respond(res: Response, type: ResponseType | number, data: Stringifiable | Record<string, unknown>, format = "json", matchesAmt?: number) {
+export function respond(res: Response, typeOrStatusCode: ResponseType | number, data: Stringifiable | Record<string, unknown>, format: ResponseFormat | string = "json", matchesAmt?: number) {
   let statusCode = 500;
   let error = true;
   let matches = null;
 
   let resData = {};
 
-  format = format?.toLowerCase();
-
-  if(typeof format !== "string" || !["json", "xml"].includes(format))
+  if(!(format in mimeTypeMap))
     format = "json";
+  format = format.toLowerCase();
 
-  switch(type) {
+  switch(typeOrStatusCode) {
   case "success":
     error = false;
     matches = matchesAmt;
@@ -44,6 +43,7 @@ export function respond(res: Response, type: ResponseType | number, data: String
     matches = matchesAmt ?? 0;
     statusCode = 200;
     resData = data;
+    break;
   case "clientError":
     error = true;
     matches = matchesAmt ?? null;
@@ -57,10 +57,10 @@ export function respond(res: Response, type: ResponseType | number, data: String
     resData = { message: data };
     break;
   default:
-    if(typeof type === "number") {
+    if(typeof typeOrStatusCode === "number") {
       error = false;
       matches = matchesAmt ?? 0;
-      statusCode = type;
+      statusCode = typeOrStatusCode;
       resData = data;
     }
     break;
@@ -75,13 +75,14 @@ export function respond(res: Response, type: ResponseType | number, data: String
   const finalData = format === "xml" ? jsonToXml("data", resData) : resData;
   const contentLen = getByteLength(typeof finalData === "string" ? finalData : JSON.stringify(finalData));
 
-  res.setHeader("Content-Type", format === "xml" ? "application/xml" : "application/json");
+  res.setHeader("Content-Type", `${mimeTypeMap[format as ResponseFormat] ?? "text/plain"}; charset=utf-8`);
   contentLen > -1 && res.setHeader("Content-Length", contentLen);
   res.status(statusCode).send(finalData);
 }
 
 /** Redirects to the documentation page at the given relative path (homepage by default) */
 export function redirectToDocs(res: Response, path?: string) {
+  res.setHeader("Cache-Control", `private, max-age=${docsMaxAge}, immutable`);
   res.redirect(`/v${verMajor}/docs/${path ? path.replace(/^\//, "") : ""}`);
 }