Scratch space for learning atproto app development

more tidy

dholms 19149b18 d6ebb4ef

Changed files
+39 -134
src
-45
src/common/__tests__/errorHandler.test.ts
···
-
import express, { type Express } from "express";
-
import { StatusCodes } from "http-status-codes";
-
import request from "supertest";
-
-
import errorHandler from "#/common/middleware/errorHandler";
-
-
describe("Error Handler Middleware", () => {
-
let app: Express;
-
-
beforeAll(() => {
-
app = express();
-
-
app.get("/error", () => {
-
throw new Error("Test error");
-
});
-
app.get("/next-error", (_req, _res, next) => {
-
const error = new Error("Error passed to next()");
-
next(error);
-
});
-
-
app.use(errorHandler());
-
app.use("*", (req, res) => res.status(StatusCodes.NOT_FOUND).send("Not Found"));
-
});
-
-
describe("Handling unknown routes", () => {
-
it("returns 404 for unknown routes", async () => {
-
const response = await request(app).get("/this-route-does-not-exist");
-
expect(response.status).toBe(StatusCodes.NOT_FOUND);
-
});
-
});
-
-
describe("Handling thrown errors", () => {
-
it("handles thrown errors with a 500 status code", async () => {
-
const response = await request(app).get("/error");
-
expect(response.status).toBe(StatusCodes.INTERNAL_SERVER_ERROR);
-
});
-
});
-
-
describe("Handling errors passed to next()", () => {
-
it("handles errors passed to next() with a 500 status code", async () => {
-
const response = await request(app).get("/next-error");
-
expect(response.status).toBe(StatusCodes.INTERNAL_SERVER_ERROR);
-
});
-
});
-
});
-52
src/common/__tests__/requestLogger.test.ts
···
-
import express from "express";
-
import { StatusCodes } from "http-status-codes";
-
import request from "supertest";
-
-
import errorHandler from "#/common/middleware/errorHandler";
-
import requestLogger from "#/common/middleware/requestLogger";
-
-
describe("Request Logger Middleware", () => {
-
const app = express();
-
-
beforeAll(() => {
-
app.use(requestLogger);
-
app.get("/success", (req, res) => res.status(StatusCodes.OK).send("Success"));
-
app.get("/redirect", (req, res) => res.redirect("/success"));
-
app.get("/error", () => {
-
throw new Error("Test error");
-
});
-
app.use(errorHandler());
-
});
-
-
describe("Successful requests", () => {
-
it("logs successful requests", async () => {
-
const response = await request(app).get("/success");
-
expect(response.status).toBe(StatusCodes.OK);
-
});
-
-
it("checks existing request id", async () => {
-
const requestId = "test-request-id";
-
const response = await request(app).get("/success").set("X-Request-Id", requestId);
-
expect(response.status).toBe(StatusCodes.OK);
-
});
-
});
-
-
describe("Re-directions", () => {
-
it("logs re-directions correctly", async () => {
-
const response = await request(app).get("/redirect");
-
expect(response.status).toBe(StatusCodes.MOVED_TEMPORARILY);
-
});
-
});
-
-
describe("Error handling", () => {
-
it("logs thrown errors with a 500 status code", async () => {
-
const response = await request(app).get("/error");
-
expect(response.status).toBe(StatusCodes.INTERNAL_SERVER_ERROR);
-
});
-
-
it("logs 404 for unknown routes", async () => {
-
const response = await request(app).get("/unknown");
-
expect(response.status).toBe(StatusCodes.NOT_FOUND);
-
});
-
});
-
});
-15
src/common/middleware/rateLimiter.ts
···
-
import type { Request } from "express";
-
import { rateLimit } from "express-rate-limit";
-
-
import { env } from "#/common/utils/envConfig";
-
-
const rateLimiter = rateLimit({
-
legacyHeaders: true,
-
limit: env.COMMON_RATE_LIMIT_MAX_REQUESTS,
-
message: "Too many requests, please try again later.",
-
standardHeaders: true,
-
windowMs: 15 * 60 * env.COMMON_RATE_LIMIT_WINDOW_MS,
-
keyGenerator: (req: Request) => req.ip as string,
-
});
-
-
export default rateLimiter;
-19
src/router.ts
···
-
import express from "express";
-
import type { AppContext } from "#/config";
-
-
export const createRouter = (ctx: AppContext) => {
-
const router = express.Router();
-
-
router.get("/", async (req, res) => {
-
const posts = await ctx.db
-
.selectFrom("post")
-
.selectAll()
-
.orderBy("indexedAt", "desc")
-
.limit(10)
-
.execute();
-
const postTexts = posts.map((row) => row.text);
-
res.json(postTexts);
-
});
-
-
return router;
-
};
+23
src/routes/index.ts
···
+
import express from "express";
+
import type { AppContext } from "#/config";
+
import { handler } from "./util";
+
+
export const createRouter = (ctx: AppContext) => {
+
const router = express.Router();
+
+
router.get(
+
"/",
+
handler(async (req, res) => {
+
const posts = await ctx.db
+
.selectFrom("post")
+
.selectAll()
+
.orderBy("indexedAt", "desc")
+
.limit(10)
+
.execute();
+
const postTexts = posts.map((row) => row.text);
+
res.json(postTexts);
+
})
+
);
+
+
return router;
+
};
+15
src/routes/util.ts
···
+
import type express from "express";
+
+
export const handler =
+
(fn: express.Handler) =>
+
async (
+
req: express.Request,
+
res: express.Response,
+
next: express.NextFunction
+
) => {
+
try {
+
await fn(req, res, next);
+
} catch (err) {
+
next(err);
+
}
+
};
+1 -3
src/server.ts
···
import { pino } from "pino";
import errorHandler from "#/common/middleware/errorHandler";
-
import rateLimiter from "#/common/middleware/rateLimiter";
import requestLogger from "#/common/middleware/requestLogger";
import { env } from "#/common/utils/envConfig";
import { createDb, migrateToLatest } from "#/db";
import { Firehose } from "#/firehose";
-
import { createRouter } from "#/router";
+
import { createRouter } from "#/routes";
import type { AppContext } from "./config";
export class Server {
···
app.use(express.urlencoded({ extended: true }));
app.use(cors({ origin: env.CORS_ORIGIN, credentials: true }));
app.use(helmet());
-
app.use(rateLimiter);
// Request logging
app.use(requestLogger);