feat(app): stats added in to check if build in last build and also if theres a pending file

This commit is contained in:
2025-09-26 10:44:41 -05:00
parent 86dea6083e
commit 58aedecd4d
25 changed files with 2113 additions and 42 deletions

View File

@@ -1,16 +1,13 @@
import type { Express, Request, Response } from "express";
import healthRoutes from "./routes/healthRoutes.js";
import { setupAuthRoutes } from "../auth/routes/routes.js";
import { setupAdminRoutes } from "../admin/routes.js";
import { setupSystemRoutes } from "../system/routes.js";
export const setupRoutes = (app: Express, basePath: string) => {
// Root / health check
app.use(basePath + "/api/system/health", healthRoutes);
// all routes
setupAuthRoutes(app, basePath);
setupAdminRoutes(app, basePath);
setupSystemRoutes(app, basePath);
// always try to go to the app weather we are in dev or in production.
app.get(basePath + "/", (req: Request, res: Response) => {

View File

@@ -1,10 +0,0 @@
import { Router } from "express";
const router = Router();
// GET /health
router.get("/", (req, res) => {
res.json({ status: "ok", uptime: process.uptime() });
});
export default router;

View File

@@ -0,0 +1,6 @@
import type { Express, Request, Response } from "express";
import stats from "./routes/stats.js";
export const setupSystemRoutes = (app: Express, basePath: string) => {
app.use(basePath + "/api/system/stats", stats);
};

View File

@@ -0,0 +1,34 @@
import { Router } from "express";
import { tryCatch } from "../../../pkg/utils/tryCatch.js";
import { db } from "../../../pkg/db/db.js";
import {
serverStats,
type ServerStats,
} from "../../../pkg/db/schema/serverstats.js";
import { eq } from "drizzle-orm";
import { format } from "date-fns-tz";
import { checkBuildUpdate } from "../utlis/checkForBuild.js";
const router = Router();
// GET /health
router.get("/", async (req, res) => {
const { data, error } = await tryCatch(
db.select().from(serverStats).where(eq(serverStats.id, "serverStats"))
);
if (error || !data) {
res.status(400).json({ error: error });
}
const statData = data as ServerStats[];
res.json({
status: "ok",
uptime: process.uptime(),
build: statData[0]?.build,
pendingUpdateFile: await checkBuildUpdate(["."]),
lastUpdate: format(statData[0].lastUpdate!, "MM/dd/yyyy HH:mm"),
});
});
export default router;

View File

@@ -0,0 +1,28 @@
import { readdir } from "fs/promises";
import { resolve, join } from "path";
/**
* Looks in one or more relative/absolute directories for a .zip file.
* Returns the **full path** to the first .zip file found, or null if none.
*/
export async function checkBuildUpdate(dirs: string[]): Promise<string | null> {
for (const inputDir of dirs) {
const dir = resolve(inputDir); // resolves "../../" relative to cwd
try {
const files = await readdir(dir);
const zipFile = files.find((file) =>
file.toLowerCase().endsWith(".zip")
);
if (zipFile) {
//return join(dir, zipFile);
return zipFile;
}
} catch (err) {
// Ignore if directory doesn't exist, allow search to continue
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
throw err;
}
}
}
return null;
}

View File

@@ -0,0 +1,12 @@
import type { InferInsertModel, InferSelectModel } from "drizzle-orm";
import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import type z from "zod";
export const serverStats = pgTable("serverStats", {
id: text("id").primaryKey().default("serverStats"),
build: integer("build").notNull().default(1),
lastUpdate: timestamp("lastUpdate").defaultNow(),
});
export type ServerStats = InferSelectModel<typeof serverStats>; // SELECT type
//export type NewServerStats = InferInsertModel<typeof serverStats, "insert">; // INSERT type