Browse Source

ref: env util functions

Sv443 4 months ago
parent
commit
930e9228e0
5 changed files with 67 additions and 28 deletions
  1. 5 4
      src/axios.ts
  2. 3 3
      src/constants.ts
  3. 50 0
      src/env.ts
  4. 2 1
      src/routes/index.ts
  5. 7 20
      src/server.ts

+ 5 - 4
src/axios.ts

@@ -1,15 +1,16 @@
-import { default as _axios } from "axios";
-import { axiosTimeout } from "./constants.js";
+import _axios, { type AxiosRequestConfig } from "axios";
+import { axiosTimeout } from "@src/constants.js";
+import { getEnvVar } from "@src/env.js";
 
 export const axios = _axios.create({
   timeout: axiosTimeout,
 });
 
 export function baseAxiosOpts() {
-  const authToken = process.env.GENIUS_ACCESS_TOKEN?.trim();
+  const authToken = getEnvVar("GENIUS_ACCESS_TOKEN");
   return authToken && authToken.length > 0 ? {
     headers: {
       "Authorization": `Bearer ${authToken}`,
     },
-  } : {};
+  } satisfies AxiosRequestConfig : {};
 }

+ 3 - 3
src/constants.ts

@@ -1,8 +1,8 @@
 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" };
-import type { axios } from "./axios.js";
+import type { axios } from "@src/axios.js";
+import type { ResponseFormat } from "@src/types.js";
+import packageJson from "@root/package.json" with { type: "json" };
 
 // for @linkcode in tsdoc comments
 void [{} as typeof axios];

+ 50 - 0
src/env.ts

@@ -0,0 +1,50 @@
+import "dotenv/config";
+import type { Stringifiable } from "svcorelib";
+
+export type EnvVarConvType = "string" | "number" | "boolean" | "stringArray" | "numberArray";
+
+/** Resolves the env var with the given {@linkcode name} as a string */
+export function getEnvVar(name: string, type?: "string"): string
+/** Resolves the env var with the given {@linkcode name} as a number */
+export function getEnvVar(name: string, type: "number"): number
+/** Resolves the env var with the given {@linkcode name} as a boolean */
+export function getEnvVar(name: string, type: "boolean"): boolean
+/** Resolves the env var with the given {@linkcode name} as a string array by splitting the string at commas and semicolons */
+export function getEnvVar(name: string, type: "stringArray"): string[]
+/** Resolves the env var with the given {@linkcode name} as a number array by splitting the string at commas and semicolons and converting each element to a number */
+export function getEnvVar(name: string, type: "numberArray"): number[]
+/** Resolves the env var with the given {@linkcode name} as a string, number, boolean, string array or number array - string by default */
+export function getEnvVar(name: string, type: EnvVarConvType = "string"): string | number | boolean | string[] | number[] {
+  const val = process.env[name];
+  if(val === undefined)
+    throw new Error(`Environment variable "${name}" not set`);
+
+  switch(type) {
+  case "string":
+    return String(val).trim();
+  case "number": {
+    const num = Number(val);
+    if(isNaN(num))
+      throw new Error(`Environment variable "${name}" is not a number`);
+    return num;
+  }
+  case "boolean":
+    if(String(val).trim().toLowerCase() === "true")
+      return true;
+    if(String(val).trim().toLowerCase() === "false")
+      return false;
+    throw new Error(`Environment variable "${name}" is not a boolean`);
+  case "stringArray":
+    return String(val).split(/[,;]/g);
+  case "numberArray":
+    return String(val).split(/[,;]/g).map(Number);
+  }
+}
+
+/** Checks if the env var with the given {@linkcode name} equals the given {@linkcode value}, converted to a string */
+export function envVarEquals(name: string, value: Stringifiable): boolean {
+  return (typeof value === "boolean"
+    ? getEnvVar(name).toLowerCase()
+    : getEnvVar(name)
+  ) === String(value);
+}

+ 2 - 1
src/routes/index.ts

@@ -1,12 +1,13 @@
 import express, { type Application, Router } from "express";
 import { docsPath, verMajor } from "@src/constants.js";
 import { redirectToDocs } from "@src/utils.js";
+import { envVarEquals } from "@src/env.js";
 
 import { initSearchRoutes } from "@routes/search.js";
 import { initTranslationsRoutes } from "@routes/translations.js";
 import { initAlbumRoutes } from "@routes/album.js";
 
-const hostHomepage = process.env.HOST_HOMEPAGE?.toLowerCase() !== "false";
+const hostHomepage = !envVarEquals("HOST_HOMEPAGE", false);
 
 const routeFuncs: ((router: Router) => unknown)[] = [
   initSearchRoutes,

+ 7 - 20
src/server.ts

@@ -7,13 +7,13 @@ import k from "kleur";
 import cors from "cors";
 import { getClientIp } from "request-ip";
 
-import packageJson from "../package.json" with { type: "json" };
 import { error } from "@src/error.js";
 import { hashStr, respond } from "@src/utils.js";
+import { envVarEquals, getEnvVar } from "@src/env.js";
 import { rateLimitOptions, rlIgnorePaths } from "@src/constants.js";
 import { initRouter } from "@routes/index.js";
+import packageJson from "@root/package.json" with { type: "json" };
 
-const { env } = process;
 const app = express();
 
 app.use(cors({
@@ -31,19 +31,19 @@ app.use(compression({
 
 app.use(express.json());
 
-if(env.TRUST_PROXY?.toLowerCase() === "true")
+if(envVarEquals("TRUST_PROXY", true))
   app.enable("trust proxy");
 
 app.disable("x-powered-by");
 
 const rateLimiter = new RateLimiterMemory(rateLimitOptions);
 
-const authTokens = getAuthTokens();
+const toks = getEnvVar("AUTH_TOKENS", "stringArray");
+const authTokens = new Set<string>(Array.isArray(toks) ? toks : []);
 
 export async function init() {
-  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 port = getEnvVar("HTTP_PORT", "number"),
+    host = getEnvVar("HTTP_HOST").length < 1 ? "0.0.0.0" : getEnvVar("HTTP_HOST");
 
   if(await portUsed(port))
     return error(`TCP port ${port} is already used or invalid`, undefined, true);
@@ -127,19 +127,6 @@ function registerRoutes() {
   }
 }
 
-/** Returns all auth tokens as a set of strings */
-function getAuthTokens() {
-  const envVal = env.AUTH_TOKENS;
-  let tokens: string[] = [];
-
-  if(!envVal || envVal.length === 0)
-    tokens = [];
-  else
-    tokens = envVal.split(/,/g);
-
-  return new Set<string>(tokens);
-}
-
 /** Sets all rate-limiting related headers on a response given a RateLimiterRes object */
 function setRateLimitHeaders(res: Response, rateLimiterRes: RateLimiterRes) {
   if(rateLimiterRes.remainingPoints === 0)