See how Wimble grew through invitations. Each branch connects a person to the people they invited.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273// @ts-check
import { renderHtml } from "./render-html.js";
/** @param {{ args: string[], accept: string[], host: { database: D1Database } }} context */export default async function network({ args, accept, host }) { if (args.includes("--help")) return "The Wimble invitation tree.\n\nUsage: run.js\n"; const tree = await invitationTree(host.database); for (const type of accept) { if (type === "text/html") { return new Response(renderHtml(tree), { headers: { "content-type": "text/html; charset=utf-8" } }); } if (type === "application/json") return tree; if (type === "text/plain") return renderText(tree); }}
/** @param {D1Database} database */async function invitationTree(database) { const [usersResult, systemResult, userResult] = await Promise.all([ database.prepare(` SELECT users.username FROM users JOIN computers ON computers.owner_account_id = users.id AND computers.status = 'active' WHERE users.status = 'active' ORDER BY users.username COLLATE NOCASE `).all(), database.prepare("SELECT accepted_username FROM system_invite_codes WHERE accepted_username IS NOT NULL ORDER BY accepted_at, code").all(), database.prepare(` SELECT inviter.username AS inviter, invited.username AS invited FROM invite_codes JOIN users inviter ON inviter.id = invite_codes.inviter_id JOIN users invited ON invited.id = invite_codes.accepted_user_id WHERE inviter.status = 'active' AND invited.status = 'active' ORDER BY invite_codes.accepted_at, invite_codes.code `).all(), ]); const usernames = /** @type {{ username: string }[]} */ (usersResult.results ?? []).map((row) => row.username); const active = new Set(usernames); const parentByUsername = new Map(); for (const row of /** @type {{ accepted_username: string }[]} */ (systemResult.results ?? [])) { if (active.has(row.accepted_username)) parentByUsername.set(row.accepted_username, "wimble"); } for (const row of /** @type {{ inviter: string, invited: string }[]} */ (userResult.results ?? [])) { if (active.has(row.invited) && active.has(row.inviter) && !parentByUsername.has(row.invited)) parentByUsername.set(row.invited, row.inviter); } const root = { label: "wimble", path: "/", children: /** @type {any[]} */ ([]) }; const nodes = new Map(usernames.map((username) => [username, { label: username, path: `/${username}`, children: /** @type {any[]} */ ([]), }])); for (const username of usernames) { const node = nodes.get(username); const parent = nodes.get(parentByUsername.get(username) ?? "") ?? root; if (node) parent.children.push(node); } return root;}
/** @param {{ label: string, children: any[] }} node @param {string} prefix @param {string} connector */function textLines(node, prefix = "", connector = "") { const lines = [`${prefix}${connector}${node.label}`]; const childPrefix = `${prefix}${connector === "├── " ? "│ " : connector === "└── " ? " " : ""}`; node.children.forEach((child, index) => lines.push(...textLines(child, childPrefix, index === node.children.length - 1 ? "└── " : "├── "))); return lines;}
/** @param {{ label: string, children: any[] }} tree */function renderText(tree) { return `${textLines(tree).join("\n")}\n`;}