From 45946b797eefcf6f5b7af3ea7f02d879d04b500f Mon Sep 17 00:00:00 2001 From: Tyler G Date: Sat, 22 Aug 2026 22:35:51 -0700 Subject: [PATCH] feat: Connect QA checklist UI to modular Deno/Postgres API --- main.ts | 25 ++++++------------- src/db/connection.ts | 33 +++++++++++++++++++++++++ src/db/qa.ts | 22 +++++++++++++++++ src/routes/qa.ts | 28 +++++++++++++++++++++ viewer/index.html | 13 +++++----- viewer/qa.js | 59 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 156 insertions(+), 24 deletions(-) create mode 100644 src/db/connection.ts create mode 100644 src/db/qa.ts create mode 100644 src/routes/qa.ts create mode 100644 viewer/qa.js diff --git a/main.ts b/main.ts index 4c824a1..291d1e9 100644 --- a/main.ts +++ b/main.ts @@ -1,22 +1,16 @@ import { Hono } from "hono"; import { serveStatic } from "hono/deno"; -import postgres from "postgres"; +import { sql, initDb } from "./src/db/connection.ts"; +import { qaRoutes } from "./src/routes/qa.ts"; const app = new Hono(); -// Connect to Postgres using env vars (fallback for dev) -const sql = postgres({ - host: Deno.env.get("POSTGRES_HOST") || "db", - port: parseInt(Deno.env.get("POSTGRES_PORT") || "5432"), - database: Deno.env.get("POSTGRES_DB") || "nas_builder", - username: Deno.env.get("POSTGRES_USER") || "nasadmin", - password: Deno.env.get("POSTGRES_PASSWORD") || "your_secure_database_password_here", -}); +// Initialize DB schema on startup +await initDb(); // API Routes app.get("/api/status", async (c) => { try { - // Ping the database const result = await sql`SELECT version()`; return c.json({ status: "online", @@ -25,18 +19,15 @@ app.get("/api/status", async (c) => { timestamp: new Date().toISOString() }); } catch (error: any) { - return c.json({ - status: "online", - database: "disconnected", - error: error.message - }, 500); + return c.json({ status: "online", database: "disconnected", error: error.message }, 500); } }); +// Mount modular sub-routes +app.route("/api/qa", qaRoutes); + // Serve the 3D Viewer frontend app.use("/*", serveStatic({ root: "./" })); - -// Handle root redirect to viewer app.get("/", (c) => c.redirect("/viewer/index.html")); // Start the server diff --git a/src/db/connection.ts b/src/db/connection.ts new file mode 100644 index 0000000..fe67a2e --- /dev/null +++ b/src/db/connection.ts @@ -0,0 +1,33 @@ +import postgres from "postgres"; + +export const sql = postgres({ + host: Deno.env.get("POSTGRES_HOST") || "db", + port: parseInt(Deno.env.get("POSTGRES_PORT") || "5432"), + database: Deno.env.get("POSTGRES_DB") || "nas_builder", + username: Deno.env.get("POSTGRES_USER") || "nasadmin", + password: Deno.env.get("POSTGRES_PASSWORD") || "your_secure_database_password_here", +}); + +export async function initDb() { + await sql` + CREATE TABLE IF NOT EXISTS qa_checklists ( + id VARCHAR(50) PRIMARY KEY, + label TEXT NOT NULL, + is_completed BOOLEAN DEFAULT false, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `; + + // Seed some initial data if empty + const count = await sql`SELECT count(*) FROM qa_checklists`; + if (count[0].count === "0") { + await sql` + INSERT INTO qa_checklists (id, label, is_completed) VALUES + ('qa-cap', 'Supercap mounted & isolated', false), + ('qa-sas', 'Broadcom SAS seated in PCIe', false), + ('qa-cool', 'Waterblock pressure tested', false), + ('qa-zfs', 'ZFS pool scrub baseline passed', false) + `; + } + console.log("✅ Database schema initialized"); +} diff --git a/src/db/qa.ts b/src/db/qa.ts new file mode 100644 index 0000000..8be15b9 --- /dev/null +++ b/src/db/qa.ts @@ -0,0 +1,22 @@ +import { sql } from "./connection.ts"; + +export interface QaTask { + id: string; + label: string; + is_completed: boolean; +} + +export const getQaTasks = async (): Promise => { + const tasks = await sql`SELECT id, label, is_completed FROM qa_checklists ORDER BY id ASC`; + return tasks; +}; + +export const toggleQaTask = async (id: string, is_completed: boolean): Promise => { + const result = await sql` + UPDATE qa_checklists + SET is_completed = ${is_completed}, updated_at = CURRENT_TIMESTAMP + WHERE id = ${id} + RETURNING id, label, is_completed + `; + return result; +}; diff --git a/src/routes/qa.ts b/src/routes/qa.ts new file mode 100644 index 0000000..403e09a --- /dev/null +++ b/src/routes/qa.ts @@ -0,0 +1,28 @@ +import { Hono } from "hono"; +import { getQaTasks, toggleQaTask } from "../db/qa.ts"; + +export const qaRoutes = new Hono(); + +qaRoutes.get("/", async (c) => { + try { + const tasks = await getQaTasks(); + return c.json(tasks); + } catch (error: any) { + return c.json({ error: error.message }, 500); + } +}); + +qaRoutes.put("/:id", async (c) => { + const id = c.req.param("id"); + try { + const body = await c.req.json(); + if (typeof body.is_completed !== "boolean") { + return c.json({ error: "Invalid payload: is_completed must be a boolean" }, 400); + } + const updated = await toggleQaTask(id, body.is_completed); + if (updated.length === 0) return c.json({ error: "Task not found" }, 404); + return c.json(updated[0]); + } catch (error: any) { + return c.json({ error: error.message }, 500); + } +}); diff --git a/viewer/index.html b/viewer/index.html index f01257d..07d8d0e 100644 --- a/viewer/index.html +++ b/viewer/index.html @@ -862,13 +862,9 @@ 0% Complete
+
Fetching live tasks from Postgres...
-
-
-
-
-
-
+ @@ -926,7 +922,7 @@ -