db.ts and server.ts work confirmed by test.js. moving to cards.

This commit is contained in:
2025-11-28 23:15:04 -06:00
parent 6502e8d5a7
commit 80d02737c0
13 changed files with 378 additions and 0 deletions
+6
View File
@@ -10,9 +10,11 @@
"bun-plugin-tailwind": "^0.1.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"kysely": "^0.28.8",
"lucide-react": "^0.545.0",
"react": "^19",
"react-dom": "^19",
"sqlite": "^5.1.1",
"tailwind-merge": "^3.3.1",
},
"devDependencies": {
@@ -135,6 +137,8 @@
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
"kysely": ["kysely@0.28.8", "", {}, "sha512-QUOgl5ZrS9IRuhq5FvOKFSsD/3+IA6MLE81/bOOTRA/YQpKDza2sFdN5g6JCB9BOpqMJDGefLCQ9F12hRS13TA=="],
"lucide-react": ["lucide-react@0.545.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-7r1/yUuflQDSt4f1bpn5ZAocyIxcTyVyBBChSVtBKn5M+392cPmI5YJMWOJKk/HUWGm5wg83chlAZtCcGbEZtw=="],
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
@@ -149,6 +153,8 @@
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"sqlite": ["sqlite@5.1.1", "", {}, "sha512-oBkezXa2hnkfuJwUo44Hl9hS3er+YFtueifoajrgidvqsJRQFpc5fKoAkAor1O5ZnLoa28GBScfHXs8j0K358Q=="],
"tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="],
"tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
BIN
View File
Binary file not shown.
+2
View File
@@ -15,9 +15,11 @@
"bun-plugin-tailwind": "^0.1.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"kysely": "^0.28.8",
"lucide-react": "^0.545.0",
"react": "^19",
"react-dom": "^19",
"sqlite": "^5.1.1",
"tailwind-merge": "^3.3.1"
},
"devDependencies": {
View File
+179
View File
@@ -0,0 +1,179 @@
// db.ts
import { Database } from "bun:sqlite";
// --- Initialize Bun SQLite ---
const db = new Database("./flow.db");
// --- Helper functions to bypass TS binding issues ---
function getOne<T>(sql: string, ...params: any[]): T | null {
const row = db.query(sql).get(...params as any);
return row ? (row as T) : null;
}
function getAll<T>(sql: string, ...params: any[]): T[] {
const rows = db.query(sql).all(...params as any);
return rows.map(r => r as T);
}
function run(sql: string, ...params: any[]) {
db.run(sql, ...params as any);
}
// --- Create tables if they do not exist ---
run(`
CREATE TABLE IF NOT EXISTS flow_steps (
id TEXT PRIMARY KEY,
stepNumber TEXT,
caseCode TEXT,
subStep TEXT,
stage TEXT,
role TEXT,
handoffTo TEXT,
handoffChannel TEXT,
description TEXT,
createdAt TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
run(`
CREATE TABLE IF NOT EXISTS extended_descriptions (
stepId TEXT PRIMARY KEY REFERENCES flow_steps(id),
content TEXT
)
`);
run(`
CREATE TABLE IF NOT EXISTS questions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
stepId TEXT REFERENCES flow_steps(id),
question TEXT,
followUp TEXT,
createdAt TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
run(`
CREATE TABLE IF NOT EXISTS concerns (
stepId TEXT PRIMARY KEY REFERENCES flow_steps(id),
flagged INTEGER DEFAULT 0,
comment TEXT,
createdAt TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
// --- Types ---
export type FlowStep = {
id: string;
stepNumber: string;
caseCode: string;
subStep: string;
stage: string;
role: string;
handoffTo: string;
handoffChannel: string;
description: string;
createdAt: string;
};
export type ExtendedDescription = {
stepId: string;
content: string;
};
export type Question = {
id: number;
stepId: string;
question: string;
followUp: string | null;
createdAt: string;
};
export type Concern = {
stepId: string;
flagged: boolean;
comment: string;
createdAt: string;
};
// --- Flow Steps ---
export function createStep(step: Omit<FlowStep, "createdAt">) {
run(
`INSERT OR IGNORE INTO flow_steps
(id, stepNumber, caseCode, subStep, stage, role, handoffTo, handoffChannel, description)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
step.id,
step.stepNumber,
step.caseCode,
step.subStep,
step.stage,
step.role,
step.handoffTo,
step.handoffChannel,
step.description
);
}
export function getStep(id: string): FlowStep | null {
return getOne<FlowStep>("SELECT * FROM flow_steps WHERE id = ?", id);
}
export function getAllSteps(): FlowStep[] {
return getAll<FlowStep>("SELECT * FROM flow_steps");
}
// --- Extended Descriptions ---
export function saveExtendedDescription(stepId: string, content: string) {
run(
`INSERT OR REPLACE INTO extended_descriptions (stepId, content) VALUES (?, ?)`,
stepId,
content
);
}
export function getExtendedDescription(stepId: string): string | null {
const row = getOne<{ content: string }>(
"SELECT content FROM extended_descriptions WHERE stepId = ?",
stepId
);
return row ? row.content : null;
}
// --- Questions ---
export function addQuestion(stepId: string, question: string, followUp?: string | null) {
run(
`INSERT INTO questions (stepId, question, followUp) VALUES (?, ?, ?)`,
stepId,
question,
followUp ?? null
);
}
export function getQuestions(stepId: string): Question[] {
return getAll<Question>("SELECT * FROM questions WHERE stepId = ?", stepId);
}
// --- Concerns ---
export function flagConcern(stepId: string, flagged: boolean, comment: string) {
run(
`INSERT OR REPLACE INTO concerns (stepId, flagged, comment) VALUES (?, ?, ?)`,
stepId,
flagged ? 1 : 0,
comment
);
}
export function getConcern(stepId: string): Concern | null {
const row = getOne<{ stepId: string; flagged: number; comment: string; createdAt: string }>(
"SELECT * FROM concerns WHERE stepId = ?",
stepId
);
if (!row) return null;
return {
stepId: row.stepId,
flagged: row.flagged === 1,
comment: row.comment,
createdAt: row.createdAt
};
}
//export default db;
+105
View File
@@ -0,0 +1,105 @@
// src/server.ts
import { serve } from "bun";
import {
createStep,
getStep,
getAllSteps,
saveExtendedDescription,
getExtendedDescription,
addQuestion,
getQuestions,
flagConcern,
getConcern
} from "./db.ts";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
async function getJSON(req: Request) {
try {
return await req.json();
} catch {
return {};
}
}
serve({
port: 3000,
fetch: async (req: Request) => {
const { method } = req;
const path = new URL(req.url).pathname;
if (method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
// --- Steps ---
if (path === "/steps" && method === "GET") {
return new Response(JSON.stringify(getAllSteps()), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
if (path.startsWith("/steps/") && method === "GET") {
const stepId = String(path.split("/")[2]);
return new Response(JSON.stringify(getStep(stepId)), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
if (path === "/steps" && method === "POST") {
const data = await getJSON(req);
createStep({
id: String(data.id),
stepNumber: String(data.stepNumber),
caseCode: String(data.caseCode),
subStep: String(data.subStep),
stage: String(data.stage),
role: String(data.role),
handoffTo: String(data.handoffTo),
handoffChannel: String(data.handoffChannel),
description: String(data.description),
});
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
// --- Extended Descriptions ---
if (path === "/descriptions" && method === "POST") {
const { stepId, content } = await getJSON(req);
saveExtendedDescription(String(stepId), String(content));
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
if (path.startsWith("/descriptions/") && method === "GET") {
const stepId = String(path.split("/")[2]);
return new Response(JSON.stringify({ stepId, content: getExtendedDescription(stepId) }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
// --- Questions ---
if (path === "/questions" && method === "POST") {
const { stepId, question, followUp } = await getJSON(req);
addQuestion(String(stepId), String(question), followUp ? String(followUp) : null);
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
if (path.startsWith("/questions/") && method === "GET") {
const stepId = String(path.split("/")[2]);
return new Response(JSON.stringify(getQuestions(stepId)), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
// --- Concerns ---
if (path === "/concerns" && method === "POST") {
const { stepId, flagged, comment } = await getJSON(req);
flagConcern(String(stepId), Boolean(flagged), String(comment));
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
if (path.startsWith("/concerns/") && method === "GET") {
const stepId = String(path.split("/")[2]);
return new Response(JSON.stringify(getConcern(stepId)), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
return new Response("Not Found", { status: 404 });
}
});
console.log("Server running at http://localhost:3000");
View File
View File
+81
View File
@@ -0,0 +1,81 @@
// test-server.ts
async function test() {
const base = "http://localhost:3000";
// --- Create a step ---
const stepData = {
id: "step1",
stepNumber: "0.1",
caseCode: "caseA",
subStep: "a",
stage: "Pre-Intake",
role: "Estimator",
handoffTo: "Office Manager",
handoffChannel: "Phone",
description: "Initial ITB received",
};
console.log("Creating step...");
let res = await fetch(`${base}/steps`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(stepData),
});
console.log(await res.json());
// --- Get the step ---
console.log("Getting step...");
res = await fetch(`${base}/steps/${stepData.id}`);
console.log(await res.json());
// --- Add extended description ---
console.log("Adding extended description...");
res = await fetch(`${base}/descriptions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ stepId: stepData.id, content: "Extended info about step1" }),
});
console.log(await res.json());
// --- Get extended description ---
console.log("Getting extended description...");
res = await fetch(`${base}/descriptions/${stepData.id}`);
console.log(await res.json());
// --- Add a question ---
console.log("Adding question...");
res = await fetch(`${base}/questions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ stepId: stepData.id, question: "Follow up question?", followUp: "Yes, clarify details" }),
});
console.log(await res.json());
// --- Get questions ---
console.log("Getting questions...");
res = await fetch(`${base}/questions/${stepData.id}`);
console.log(await res.json());
// --- Add a concern ---
console.log("Adding concern...");
res = await fetch(`${base}/concerns`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ stepId: stepData.id, flagged: true, comment: "Check this step for errors" }),
});
console.log(await res.json());
// --- Get concern ---
console.log("Getting concern...");
res = await fetch(`${base}/concerns/${stepData.id}`);
console.log(await res.json());
// --- Get all steps ---
console.log("Getting all steps...");
res = await fetch(`${base}/steps`);
console.log(await res.json());
console.log("Test complete.");
}
test();
+5
View File
@@ -0,0 +1,5 @@
import workflow from "./workflow.json";
export function useWorkflow() {
return workflow;
}
View File
View File
BIN
View File
Binary file not shown.