more logging stuff

This commit is contained in:
2026-03-18 12:22:00 -05:00
parent 2846b9cb0d
commit e67e9e6d72
15 changed files with 287 additions and 45 deletions

View File

@@ -1,3 +1,4 @@
import { desc } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { logs } from "../db/schema/logs.schema.js";
import type { RoomId } from "./types.socket.js";
@@ -6,6 +7,11 @@ type RoomDefinition<T = unknown> = {
seed: (limit: number) => Promise<T[]>;
};
export const protectedRooms: any = {
logs: { requiresAuth: true, role: "admin" },
admin: { requiresAuth: true, role: "admin" },
};
export const roomDefinition: Record<RoomId, RoomDefinition> = {
logs: {
seed: async (limit) => {
@@ -13,7 +19,7 @@ export const roomDefinition: Record<RoomId, RoomDefinition> = {
const rows = await db
.select()
.from(logs)
.orderBy(logs.createdAt)
.orderBy(desc(logs.createdAt))
.limit(limit);
return rows; //.reverse();

View File

@@ -3,6 +3,7 @@ import type { Server as HttpServer } from "node:http";
//import { fileURLToPath } from "node:url";
import { instrument } from "@socket.io/admin-ui";
import { Server } from "socket.io";
import { auth } from "utils/auth.utils.js";
import { createLogger } from "../logger/logger.controller.js";
import { allowedOrigins } from "../utils/cors.utils.js";
import { registerEmitter } from "./roomEmitter.socket.js";
@@ -12,6 +13,16 @@ import { createRoomEmitter, preseedRoom } from "./roomService.socket.js";
//const __dirname = dirname(__filename);
const log = createLogger({ module: "socket.io", subModule: "setup" });
//import type { Session, User } from "better-auth"; // adjust if needed
import { protectedRooms } from "./roomDefinitions.socket.js";
// declare module "socket.io" {
// interface Socket {
// user?: User | any;
// session?: Session;
// }
// }
export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
const io = new Server(server, {
path: `${baseUrl}/api/socket.io`,
@@ -25,6 +36,38 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
const { addDataToRoom } = createRoomEmitter(io);
registerEmitter(addDataToRoom);
io.use(async (socket, next) => {
try {
//const cookieHeader = socket.handshake.headers.cookie;
const headers = new Headers();
for (const [key, value] of Object.entries(socket.request.headers)) {
if (typeof value === "string") {
headers.set(key, value);
} else if (Array.isArray(value)) {
headers.set(key, value.join(", "));
}
}
const session = await auth.api.getSession({
headers,
});
if (!session) {
return next(); // allow connection, but no auth
}
if (session) {
socket.user = session.user;
socket.session = session as any;
}
next();
} catch (err) {
next();
}
});
io.on("connection", (s) => {
log.info({}, `User connected: ${s.id}`);
@@ -35,6 +78,21 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
});
s.on("join-room", async (rn) => {
const config = protectedRooms[rn];
if (config?.requiresAuth && !s.user) {
return s.emit("room-error", {
room: rn,
message: "Authentication required",
});
}
if (config?.role && s.user?.role !== config.role) {
return s.emit("room-error", {
room: rn,
message: `Not authorized to be in room: ${rn}`,
});
}
s.join(rn);
// get room seeded

9
backend/types/socket.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
import "socket.io";
import type { Session, User } from "better-auth"; // adjust if needed
declare module "socket.io" {
interface Socket {
user?: User | any;
session?: Session;
}
}

View File

@@ -32,15 +32,15 @@ export const auth = betterAuth({
schema,
}),
trustedOrigins: allowedOrigins,
// user: {
// additionalFields: {
// role: {
// type: "string",
// //required: false,
// input: false,
// },
// },
// },
user: {
additionalFields: {
role: {
type: "string",
//required: false,
input: false,
},
},
},
plugins: [
jwt({ jwt: { expirationTime: "1h" } }),
//apiKey(),
@@ -137,3 +137,5 @@ export const auth = betterAuth({
// },
},
});
type Session = typeof auth.$Infer.Session;

View File

@@ -1,3 +1,3 @@
vars {
url: http://uslim1vms006:3100/lst
url: http://localhost:3000/lst
}

View File

@@ -12,8 +12,8 @@ patch {
body:json {
{
"value" : "0",
"active": "false"
"value" : "1",
"active": "true"
}
}

View File

@@ -10,9 +10,11 @@
"dependencies": {
"@fontsource-variable/inter": "^5.2.8",
"@tailwindcss/vite": "^4.2.1",
"@tanstack/react-form": "^1.28.5",
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-router": "^1.166.7",
"@tanstack/react-router-devtools": "^1.166.7",
"@tanstack/react-table": "^8.21.3",
"axios": "^1.13.6",
"better-auth": "^1.5.5",
"class-variance-authority": "^0.7.1",
@@ -3994,6 +3996,37 @@
"vite": "^5.2.0 || ^6 || ^7"
}
},
"node_modules/@tanstack/devtools-event-client": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@tanstack/devtools-event-client/-/devtools-event-client-0.4.3.tgz",
"integrity": "sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw==",
"license": "MIT",
"bin": {
"intent": "bin/intent.js"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/form-core": {
"version": "1.28.5",
"resolved": "https://registry.npmjs.org/@tanstack/form-core/-/form-core-1.28.5.tgz",
"integrity": "sha512-8lYnduHHfP6uaXF9+2OLnh3Fo27tH4TdtekWLG2b/Bp26ynbrWG6L4qhBgEb7VcvTpJw/RjvJF/JyFhZkG3pfQ==",
"license": "MIT",
"dependencies": {
"@tanstack/devtools-event-client": "^0.4.1",
"@tanstack/pacer-lite": "^0.1.1",
"@tanstack/store": "^0.9.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/history": {
"version": "1.161.6",
"resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.161.6.tgz",
@@ -4007,6 +4040,19 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/pacer-lite": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@tanstack/pacer-lite/-/pacer-lite-0.1.1.tgz",
"integrity": "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/query-core": {
"version": "5.90.20",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz",
@@ -4017,6 +4063,28 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-form": {
"version": "1.28.5",
"resolved": "https://registry.npmjs.org/@tanstack/react-form/-/react-form-1.28.5.tgz",
"integrity": "sha512-CL8IeWkeXnEEDsHt5wBuIOZvSYrKiLRtsC9ca0LzfJJ22SYCma9cBmh1UX1EBX0o3gH2U21PmUf+y5f9OJNoEQ==",
"license": "MIT",
"dependencies": {
"@tanstack/form-core": "1.28.5",
"@tanstack/react-store": "^0.9.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@tanstack/react-start": {
"optional": true
}
}
},
"node_modules/@tanstack/react-query": {
"version": "5.90.21",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz",
@@ -4103,6 +4171,26 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@tanstack/react-table": {
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz",
"integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==",
"license": "MIT",
"dependencies": {
"@tanstack/table-core": "8.21.3"
},
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/@tanstack/router-core": {
"version": "1.167.3",
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.167.3.tgz",
@@ -4284,6 +4372,19 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/table-core": {
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
"integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/virtual-file-routes": {
"version": "1.161.6",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.161.6.tgz",

View File

@@ -12,9 +12,11 @@
"dependencies": {
"@fontsource-variable/inter": "^5.2.8",
"@tailwindcss/vite": "^4.2.1",
"@tanstack/react-form": "^1.28.5",
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-router": "^1.166.7",
"@tanstack/react-router-devtools": "^1.166.7",
"@tanstack/react-table": "^8.21.3",
"axios": "^1.13.6",
"better-auth": "^1.5.5",
"class-variance-authority": "^0.7.1",

View File

@@ -0,0 +1,46 @@
import { useEffect, useState } from "react";
import socket from "@/lib/socket.io";
export function useSocketRoom<T>(roomId: string) {
const [data, setData] = useState<T[]>([]);
const [info, setInfo] = useState(
"No data yet — join the room to start receiving",
);
useEffect(() => {
function handleConnect() {
socket.emit("join-room", roomId);
}
function handleUpdate(payload: any) {
setData((prev) => [...payload.payloads, ...prev]);
}
function handleError(err: any) {
setInfo(err.message ?? "Room error");
}
if (!socket.connected) {
socket.connect();
}
socket.on("connect", handleConnect);
socket.on("room-update", handleUpdate);
socket.on("room-error", handleError);
// If already connected, join immediately
if (socket.connected) {
socket.emit("join-room", roomId);
}
return () => {
socket.emit("leave-room", roomId);
socket.off("connect", handleConnect);
socket.off("room-update", handleUpdate);
socket.off("room-error", handleError);
};
}, [roomId]);
return { data, info };
}

View File

@@ -3,6 +3,7 @@ import { io } from "socket.io-client";
// Connect to your Socket.io server
const socket = io(`${window.location.host}`, {
path: "/lst/api/socket.io",
withCredentials: true,
// autoConnect: false, // connect manually
// reconnection: true,
// reconnectionAttempts: 5,

View File

@@ -1,7 +1,6 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
import { useEffect, useState } from "react";
import { useSocketRoom } from "@/hooks/socket.io.hook";
import { authClient } from "@/lib/auth-client";
import socket from "@/lib/socket.io";
export const Route = createFileRoute("/admin/logs")({
beforeLoad: async ({ location }) => {
@@ -60,40 +59,49 @@ function LevelBadge({ level }: { level: number }) {
}
function RouteComponent() {
const { data: logs, info: logsInfo } = useSocketRoom<LogEntry>("logs");
//const { user } = Route.useRouteContext();
//const router = useRouter();
const [logs, setLogs] = useState<LogEntry[]>([]);
// const [logs, setLogs] = useState<LogEntry[]>([]);
// const [logsInfo, setLogInfo] = useState(
// "No logs yet — join the room to start receiving",
// );
useEffect(() => {
// Connect if not already connected
if (!socket.connected) {
socket.connect();
}
// useEffect(() => {
// // Connect if not already connected
// if (!socket.connected) {
// socket.connect();
// }
socket.on("connect", () => {
socket.emit("join-room", "logs");
});
// socket.on("connect", () => {
// socket.emit("join-room", "logs");
// });
socket.emit("join-room", "logs");
socket.on(
"room-update",
(data: { payloads: LogEntry[]; roomId: string }) => {
setLogs((prev) => [...data.payloads, ...prev]);
},
);
// socket.emit("join-room", "logs");
// socket.on(
// "room-update",
// (data: { payloads: LogEntry[]; roomId: string }) => {
// setLogs((prev) => [...data.payloads, ...prev]);
// },
// );
// socket.on("logs", (data) => {
// console.log(data);
// setLogs((prev) => [...data.payloads, ...prev]);
// });
// socket.on("room-error", (data) => {
// setLogInfo(data.message);
// });
// Cleanup listeners on unmount
return () => {
socket.emit("leave-room", "logs");
socket.off("room-update");
socket.off("logs");
};
}, []);
// // socket.on("logs", (data) => {
// // console.log(data);
// // setLogs((prev) => [...data.payloads, ...prev]);
// // });
// // Cleanup listeners on unmount
// return () => {
// socket.emit("leave-room", "logs");
// socket.off("room-update");
// socket.off("room-error");
// socket.off("logs");
// };
// }, []);
return (
<div>
{/* Log Table */}
@@ -115,7 +123,7 @@ function RouteComponent() {
colSpan={6}
className="text-center py-6 text-muted-foreground"
>
No logs yet join the room to start receiving
{logsInfo}
</td>
</tr>
) : (

View File

@@ -27,5 +27,5 @@
"@/*": ["src/*"]
}
},
"include": ["src"]
"include": ["src", "types"]
}

9
frontend/types/socket.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
import "socket.io";
import type { Session, User } from "better-auth"; // adjust if needed
declare module "socket.io" {
interface Socket {
user?: User;
session?: Session;
}
}

View File

@@ -27,7 +27,7 @@
//"allowImportingTsExtensions": true,
"noEmit": false
},
"include": ["backend/**/*"],
"include": ["backend/**/*", "types"],
"exclude": [
"node_modules",
"frontend",