From 202f5af3ed789b1792312e186e307e3411834960 Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Thu, 25 Sep 2025 12:26:58 -0500 Subject: [PATCH] feat(apihits): added in api hits to monitor and assist for issues --- app/main.ts | 4 ++++ app/src/pkg/db/schema/apiHits.ts | 35 +++++++++++++++++++++++++++++++ app/src/pkg/middleware/apiHits.ts | 30 ++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 app/src/pkg/db/schema/apiHits.ts create mode 100644 app/src/pkg/middleware/apiHits.ts diff --git a/app/main.ts b/app/main.ts index 51ab9c7..9221323 100644 --- a/app/main.ts +++ b/app/main.ts @@ -17,6 +17,7 @@ import cors from "cors"; import { sendNotify } from "./src/pkg/utils/notify.js"; import { toNodeHandler } from "better-auth/node"; import { auth } from "./src/pkg/auth/auth.js"; +import { apiHitMiddleware } from "./src/pkg/middleware/apiHits.js"; const main = async () => { const env = validateEnv(process.env); @@ -74,6 +75,8 @@ const main = async () => { } // global middleware + app.set("trust proxy", true); + app.use(apiHitMiddleware); app.all(basePath + "/api/auth/*splat", toNodeHandler(auth)); // sign-in sign-out app.use(express.json()); @@ -81,6 +84,7 @@ const main = async () => { "http://localhost:5173", // lstV2 dev "http://localhost:5500", // lst dev "http://localhost:4200", // express + "http://localhost:4000", // prod port env.BETTER_AUTH_URL, // prod ]; diff --git a/app/src/pkg/db/schema/apiHits.ts b/app/src/pkg/db/schema/apiHits.ts new file mode 100644 index 0000000..0593147 --- /dev/null +++ b/app/src/pkg/db/schema/apiHits.ts @@ -0,0 +1,35 @@ +import { + integer, + jsonb, + pgTable, + text, + timestamp, + uniqueIndex, + uuid, +} from "drizzle-orm/pg-core"; +import { createSelectSchema } from "drizzle-zod"; + +export const apiHits = pgTable( + "apiHits", + { + apiHit_id: uuid("apiHit_id").defaultRandom().primaryKey(), + method: text("method").notNull(), + path: text("path").notNull(), + body: jsonb("body"), + status: integer("status"), + ip: text("ip"), + duration: integer("duration"), + createdAt: timestamp("createdAt").defaultNow().notNull(), + }, + (table) => [ + // uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`), + // uniqueIndex("endpoint").on(table.endpoint, table.ip), + ] +); + +// Schema for inserting a user - can be used to validate API requests +// export const insertRolesSchema = createInsertSchema(roles, { +// name: z.string().min(3, {message: "Role name must be more than 3 letters"}), +// }); +// Schema for selecting a Expenses - can be used to validate API responses +export const selectRolesSchema = createSelectSchema(apiHits); diff --git a/app/src/pkg/middleware/apiHits.ts b/app/src/pkg/middleware/apiHits.ts new file mode 100644 index 0000000..dd23774 --- /dev/null +++ b/app/src/pkg/middleware/apiHits.ts @@ -0,0 +1,30 @@ +import type { Request, Response, NextFunction } from "express"; +import { db } from "../db/db.js"; +import { apiHits } from "../db/schema/apiHits.js"; + +export function apiHitMiddleware( + req: Request, + res: Response, + next: NextFunction +) { + const start = Date.now(); + + // When response is done sending, log the request + response info + res.on("finish", async () => { + try { + await db.insert(apiHits).values({ + method: req.method, + path: req.originalUrl, + body: JSON.stringify(req.body ?? {}), + status: res.statusCode, + ip: req.ip, + duration: Date.now() - start, + createdAt: new Date(), + }); + } catch (err) { + console.error("Failed to insert apiTracking log:", err); + } + }); + + next(); +}