From 9f5ec4fff2bb5ec3bd2cbc6a89eff7aa0426a5e3 Mon Sep 17 00:00:00 2001 From: blake Date: Thu, 5 Feb 2026 20:45:38 -0500 Subject: [PATCH] initial ws setup --- package-lock.json | 1 + package.json | 1 + src/index.js | 36 ++++++++++++++++++++++++------------ src/routes/matches.route.js | 20 ++++++++++++-------- src/ws/server.js | 37 +++++++++++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 20 deletions(-) create mode 100644 src/ws/server.js diff --git a/package-lock.json b/package-lock.json index 7c4dbfb..cfa742c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "express": "^5.2.1", "pg": "^8.18.0", "postgres": "^3.4.8", + "ws": "^8.19.0", "wscat": "^6.1.0", "zod": "^4.3.6" }, diff --git a/package.json b/package.json index 500a66e..a60001c 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "express": "^5.2.1", "pg": "^8.18.0", "postgres": "^3.4.8", + "ws": "^8.19.0", "wscat": "^6.1.0", "zod": "^4.3.6" }, diff --git a/src/index.js b/src/index.js index 54a7a90..59508df 100644 --- a/src/index.js +++ b/src/index.js @@ -1,22 +1,34 @@ -import express from 'express' +import express from "express"; +import http from "http"; // routes -import { matchRouter } from './routes/matches.route.js' +import { matchRouter } from "./routes/matches.route.js"; +import { attachWebsocketServer } from "./ws/server.js"; -const app = express() +const PORT = process.env.PORT || 8081; +const HOST = process.env.HOST || "0.0.0.0"; -const port = process.env.PORT || 8081 +const app = express(); -app.use(express.json()) +const server = http.createServer(app); -app.get('/',(_,res)=>{ - res.send('Hello from express server!') -}) +app.use(express.json()); -app.use('/matches', matchRouter) +app.get("/", (_, res) => { + res.send("Hello from express server!"); +}); -app.listen(port, ()=>{ - console.info(`Listening on port ${port}`) -}) +app.use("/matches", matchRouter); +const { broadcastMatchCreated } = attachWebsocketServer(server); +app.locals.broadcastMatchCreated = broadcastMatchCreated; + +server.listen(PORT, HOST, () => { + const baseURL = + HOST === "0.0.0.0" ? `http://localhost:${PORT}` : `http://${HOST}:${PORT}`; + console.info(`Server running on ${baseURL}`); + console.info( + `Websocket server running on ${baseURL.replace("http", "ws")}/ws`, + ); +}); diff --git a/src/routes/matches.route.js b/src/routes/matches.route.js index 7db1b1c..72849e4 100644 --- a/src/routes/matches.route.js +++ b/src/routes/matches.route.js @@ -17,7 +17,7 @@ matchRouter.get("/", async (req, res) => { if (!parsed.success) { return res.status(400).json({ error: "Invalid query", - details: JSON.stringify(parsed.error), + details: parsed.error.issues, }); } @@ -34,24 +34,24 @@ matchRouter.get("/", async (req, res) => { } catch (e) { return res .status(500) - .json({ error: "Failed to list matchs.", details: JSON.stringify(e) }); + .json({ error: "Failed to list matchs.", details: e.issues }); } }); matchRouter.post("/", async (req, res) => { const parsed = createMatchSchema.safeParse(req.body); - const { - data: { startTime, endTime, homeScore, awayScore }, - } = parsed; - if (!parsed.success) { return res.status(400).json({ error: "Invalid payload", - details: JSON.stringify(parsed.error), + details: parsed.error.issues, }); } + const { + data: { startTime, endTime, homeScore, awayScore }, + } = parsed; + try { const [event] = await db .insert(matches) @@ -65,10 +65,14 @@ matchRouter.post("/", async (req, res) => { }) .returning(); + // broadcast this to all clients + if (res.app.locals.broadcastMatchCreated) { + res.app.locals.broadcastMatchCreated(event); + } res.status(201).json({ data: event }); } catch (e) { return res .status(500) - .json({ error: "Failed to create match.", details: JSON.stringify(e) }); + .json({ error: "Failed to create match.", details: e.issues }); } }); diff --git a/src/ws/server.js b/src/ws/server.js new file mode 100644 index 0000000..c16e827 --- /dev/null +++ b/src/ws/server.js @@ -0,0 +1,37 @@ +import { WebSocket, WebSocketServer } from "ws"; + +const sendJson = (socket, payload) => { + if (socket.readyState !== WebSocket.OPEN) { + return; + } + + socket.send(JSON.stringify(payload)); +}; + +const broadcast = (wss, payload) => { + for (const client of wss.clients) { + if (client.readyState !== WebSocket.OPEN) return; + + client.send(JSON.stringify(payload)); + } +}; + +export const attachWebsocketServer = (server) => { + const wss = new WebSocketServer({ + server, + path: "/ws", + maxPayload: 1024 * 1024, // 1mb + }); + + wss.on("connection", (socket) => { + sendJson(socket, { type: "welcome" }); + + socket.on("error", console.error); + }); + + function broadcastMatchCreated(match) { + broadcast(wss, { type: "match_created", data: match }); + } + + return { broadcastMatchCreated }; +};