All files middleware.ts

42.22% Statements 38/90
80% Branches 4/5
25% Functions 2/8
42.22% Lines 38/90

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 1091x 1x   1x     1x 1x 1x   1x 8x 8x 8x 8x 8x 8x 8x   8x 8x 8x 8x   8x 4x 4x 4x 4x 4x 4x 4x 8x     8x 8x                                                                                                                 1x             1x 1x 1x 1x 1x 1x 1x 1x  
import type express from "express";
import expressRateLimit from "express-rate-limit";
import type { Kysely } from "kysely";
import morgan from "morgan";
import type { Config } from "./config";
import type { GenericDatabase } from "./database";
import { fileRouter as plainFileRouter } from "./file-router";
import { randomId } from "./id";
import { getLogger } from "./log";
 
export function forceWWW(): express.RequestHandler {
  return function forceWWW(
    req: express.Request,
    res: express.Response,
    next: express.NextFunction,
  ) {
    const log = getLogger("www");
    const host = req.header("host");
 
    if (host) {
      const wwwRegex = /^www\./i;
      const isWww = wwwRegex.test(host);
      const isSubdomain = host.split(".").length > 2;
 
      if (!isWww && !isSubdomain) {
        const lowercaseHost = host.toLowerCase(); // Convert host to lowercase
        const newUrl = `https://www.${lowercaseHost}${req.url}`;
        log.info("redirecting to", newUrl);
        res.redirect(301, newUrl);
      } else {
        next();
      }
    } else {
      next();
    }
  };
}
 
function logging(): express.RequestHandler {
  const logger = getLogger();
  return morgan(
    ":method :url :status :res[content-length] - :response-time ms",
    {
      stream: {
        write: (message) => logger.info(message.trim()),
      },
    },
  );
}
 
function rateLimit(opts?: {
  rateLimit?: { windowMs?: number; limit?: number; message?: string };
}): express.RequestHandler {
  const rateLimit = opts?.rateLimit;
  return expressRateLimit({
    windowMs: rateLimit?.windowMs ?? 60 * 1000,
    limit: rateLimit?.limit ?? 60,
    message:
      rateLimit?.message ??
      "Too many requests, please try again in a few seconds",
  });
}
 
function database({
  database,
}: {
  database: GenericDatabase;
}): express.RequestHandler {
  return (req, res, next) => {
    const log = getLogger("middleware");
    log.debug("attach database to request");
    res.locals.database = database;
    next();
  };
}
 
async function fileRouter({
  dir,
}: {
  dir: string;
}): Promise<express.RequestHandler> {
  const { router } = await plainFileRouter({ cwd: dir });
  return router;
}
 
// TODO use async local storage
function id(): express.RequestHandler {
  return (req, res, next) => {
    res.locals.id = randomId("req");
    next();
  };
}
 
export function defineHttp(
  handler: (config: Config) => Promise<express.Application>,
) {
  return handler;
}
 
/** A collection of built-in express middleware. */
export const middleware = {
  forceWWW,
  logging,
  rateLimit,
  database,
  fileRouter,
  id,
};