fix: some shit
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m55s

This commit is contained in:
max_richter 2024-12-20 13:40:14 +01:00
parent 221817fc16
commit 4ca36b324b
8 changed files with 400 additions and 38 deletions

View File

@ -44,7 +44,7 @@ for await (const dir of dirs) {
async function postNode(node: Node) { async function postNode(node: Node) {
const wasmContent = await Deno.readFile(node.path); const wasmContent = await Deno.readFile(node.path);
const url = `http://localhost:8000/v1/nodes`; const url = `http://localhost:8000/nodes`;
const res = await fetch(url, { const res = await fetch(url, {
method: "POST", method: "POST",
@ -55,7 +55,7 @@ async function postNode(node: Node) {
console.log(`Uploaded ${node.id}`); console.log(`Uploaded ${node.id}`);
} else { } else {
const text = await res.text(); const text = await res.text();
console.log(`Failed to upload ${node.id}: ${text}`); console.log(`Failed to upload ${node.id}: ${res.status} ${text}`);
} }
} }

View File

@ -0,0 +1 @@
ALTER TABLE "nodes" ADD COLUMN "createdAt" timestamp DEFAULT now();

View File

@ -0,0 +1,217 @@
{
"id": "080ee514-5516-4400-9286-295826df6f8a",
"prevId": "b5fc8bcf-82d4-4d2e-bcd1-89d5a238f5e2",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_name_unique": {
"name": "users_name_unique",
"nullsNotDistinct": false,
"columns": [
"name"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.nodes": {
"name": "nodes",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"userId": {
"name": "userId",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"systemId": {
"name": "systemId",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"nodeId": {
"name": "nodeId",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"content": {
"name": "content",
"type": "bytea",
"primaryKey": false,
"notNull": true
},
"definition": {
"name": "definition",
"type": "json",
"primaryKey": false,
"notNull": true
},
"hash": {
"name": "hash",
"type": "varchar(8)",
"primaryKey": false,
"notNull": true
},
"previous": {
"name": "previous",
"type": "varchar(8)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_id_idx": {
"name": "user_id_idx",
"columns": [
{
"expression": "userId",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"system_id_idx": {
"name": "system_id_idx",
"columns": [
{
"expression": "systemId",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"node_id_idx": {
"name": "node_id_idx",
"columns": [
{
"expression": "nodeId",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"hash_idx": {
"name": "hash_idx",
"columns": [
{
"expression": "hash",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"nodes_userId_users_name_fk": {
"name": "nodes_userId_users_name_fk",
"tableFrom": "nodes",
"tableTo": "users",
"columnsFrom": [
"userId"
],
"columnsTo": [
"name"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"node_previous_fk": {
"name": "node_previous_fk",
"tableFrom": "nodes",
"tableTo": "nodes",
"columnsFrom": [
"previous"
],
"columnsTo": [
"hash"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"nodes_hash_unique": {
"name": "nodes_hash_unique",
"nullsNotDistinct": false,
"columns": [
"hash"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -8,6 +8,13 @@
"when": 1734695353420, "when": 1734695353420,
"tag": "0000_steep_bromley", "tag": "0000_steep_bromley",
"breakpoints": true "breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1734696211359,
"tag": "0001_amazing_weapon_omega",
"breakpoints": true
} }
] ]
} }

File diff suppressed because one or more lines are too long

View File

@ -78,7 +78,7 @@ nodeRouter.openapi(getNodeCollectionRoute, async (c) => {
const getNodeDefinitionRoute = createRoute({ const getNodeDefinitionRoute = createRoute({
method: "get", method: "get",
path: "/{user}/{system}/{nodeId}{.+\\.json}", path: "/{user}/{system}/{nodeId}.json",
request: { request: {
params: ParamsSchema, params: ParamsSchema,
}, },
@ -111,7 +111,7 @@ nodeRouter.openapi(getNodeDefinitionRoute, async (c) => {
const getNodeWasmRoute = createRoute({ const getNodeWasmRoute = createRoute({
method: "get", method: "get",
path: "/{user}/{system}/{nodeId}{.+\\.wasm}", path: "/{user}/{system}/{nodeId}.wasm",
request: { request: {
params: ParamsSchema, params: ParamsSchema,
}, },
@ -140,6 +140,63 @@ nodeRouter.openapi(getNodeWasmRoute, async (c) => {
return c.body(wasmContent); return c.body(wasmContent);
}); });
const getNodeVersionRoute = createRoute({
method: "get",
path: "/{user}/{system}/{nodeId}@{hash}.json",
request: {
params: ParamsSchema,
},
responses: {
200: {
content: {
"application/json": {
schema: NodeDefinitionSchema,
},
},
description: "Create a single node",
},
},
});
nodeRouter.openapi(getNodeVersionRoute, async (c) => {
const { user, system, nodeId } = c.req.valid("param");
const nodes = await service.getNodeVersions(user, system, nodeId);
return c.json(nodes);
});
const getNodeVersionsRoute = createRoute({
method: "get",
path: "/{user}/{system}/{nodeId}/versions.json",
request: {
params: z.object({
user: SingleParam("user"),
system: SingleParam("system"),
nodeId: SingleParam("nodeId"),
hash: SingleParam("hash"),
}),
},
responses: {
200: {
content: {
"application/json": {
schema: NodeDefinitionSchema,
},
},
description: "Create a single node",
},
},
});
nodeRouter.openapi(getNodeVersionsRoute, async (c) => {
const { user, system, nodeId, hash } = c.req.valid("param");
const node = await service.getNodeVersion(user, system, nodeId, hash);
return c.json(node);
});
const createNodeRoute = createRoute({ const createNodeRoute = createRoute({
method: "post", method: "post",
path: "/", path: "/",
@ -165,8 +222,19 @@ const createNodeRoute = createRoute({
nodeRouter.openapi(createNodeRoute, async (c) => { nodeRouter.openapi(createNodeRoute, async (c) => {
const buffer = await c.req.arrayBuffer(); const buffer = await c.req.arrayBuffer();
const bytes = await (await c.req.blob()).bytes(); const bytes = await (await c.req.blob()).bytes();
const node = await service.createNode(buffer, bytes);
return c.json(node); try {
const node = await service.createNode(buffer, bytes);
return c.json(node);
} catch (error) {
if (error instanceof Error && "code" in error) {
switch (error.code) {
case "23505":
throw new HTTPException(409, { message: "node already exists" });
}
}
}
throw new HTTPException(500);
}); });
export { nodeRouter }; export { nodeRouter };

View File

@ -2,14 +2,14 @@ import {
customType, customType,
foreignKey, foreignKey,
index, index,
integer,
json, json,
pgTable, pgTable,
serial, serial,
timestamp,
varchar, varchar,
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm/relations";
import { usersTable } from "../user/user.schema.ts"; import { usersTable } from "../user/user.schema.ts";
import { NodeDefinition } from "./validations/types.ts";
const bytea = customType<{ const bytea = customType<{
data: ArrayBuffer; data: ArrayBuffer;
@ -23,12 +23,13 @@ const bytea = customType<{
export const nodeTable = pgTable("nodes", { export const nodeTable = pgTable("nodes", {
id: serial().primaryKey(), id: serial().primaryKey(),
userId: varchar().notNull().references(() => usersTable.name), userId: varchar().notNull().references(() => usersTable.name),
createdAt: timestamp().defaultNow(),
systemId: varchar().notNull(), systemId: varchar().notNull(),
nodeId: varchar().notNull(), nodeId: varchar().notNull(),
content: bytea().notNull(), content: bytea().notNull(),
definition: json().notNull(), definition: json().notNull().$type<NodeDefinition>(),
hash: varchar({ length: 8 }).notNull().unique(), hash: varchar({ length: 16 }).notNull().unique(),
previous: varchar({ length: 8 }), previous: varchar({ length: 16 }),
}, (table) => [ }, (table) => [
foreignKey({ foreignKey({
columns: [table.previous], columns: [table.previous],

View File

@ -1,7 +1,7 @@
import { db } from "../../db/db.ts"; import { db } from "../../db/db.ts";
import { nodeTable } from "./node.schema.ts"; import { nodeTable } from "./node.schema.ts";
import { NodeDefinition, NodeDefinitionSchema } from "./validations/types.ts"; import { NodeDefinition, NodeDefinitionSchema } from "./validations/types.ts";
import { and, eq } from "drizzle-orm"; import { and, asc, eq } from "drizzle-orm";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import { WorkerMessage } from "./worker/messages.ts"; import { WorkerMessage } from "./worker/messages.ts";
@ -15,7 +15,7 @@ export type CreateNodeDTO = {
function getNodeHash(content: Uint8Array) { function getNodeHash(content: Uint8Array) {
const hash = createHash("sha256"); const hash = createHash("sha256");
hash.update(content); hash.update(content);
return hash.digest("hex").slice(0, 8); return hash.digest("hex").slice(0, 16);
} }
function extractDefinition(content: ArrayBuffer): Promise<NodeDefinition> { function extractDefinition(content: ArrayBuffer): Promise<NodeDefinition> {
@ -40,7 +40,6 @@ function extractDefinition(content: ArrayBuffer): Promise<NodeDefinition> {
res(e.data.result); res(e.data.result);
break; break;
case "error": case "error":
console.log("Worker error", e.data.error);
rej(e.data.result); rej(e.data.result);
break; break;
default: default:
@ -54,36 +53,46 @@ export async function createNode(
wasmBuffer: ArrayBuffer, wasmBuffer: ArrayBuffer,
content: Uint8Array, content: Uint8Array,
): Promise<NodeDefinition> { ): Promise<NodeDefinition> {
try { const def = await extractDefinition(wasmBuffer);
const def = await extractDefinition(wasmBuffer);
const [userId, systemId, nodeId] = def.id.split("/"); const [userId, systemId, nodeId] = def.id.split("/");
const node: typeof nodeTable.$inferInsert = { const hash = getNodeHash(content);
userId,
systemId,
nodeId,
definition: def,
hash: getNodeHash(content),
content: content,
};
await db.insert(nodeTable).values(node); const node: typeof nodeTable.$inferInsert = {
console.log("new node created!"); userId,
return def; systemId,
} catch (error) { nodeId,
console.log("Creation Error", { error }); definition: def,
throw error; hash,
content: content,
};
const previousNode = await db
.select({ hash: nodeTable.hash })
.from(nodeTable)
.orderBy(asc(nodeTable.createdAt))
.limit(1);
if (previousNode[0]) {
node.previous = previousNode[0].hash;
} }
await db.insert(nodeTable).values(node);
return def;
} }
export function getNodeDefinitionsByUser(userName: string) { export async function getNodeDefinitionsByUser(userName: string) {
return db.select({ definition: nodeTable.definition }).from(nodeTable) const nodes = await db.select({ definition: nodeTable.definition }).from(
nodeTable,
)
.where( .where(
and( and(
eq(nodeTable.userId, userName), eq(nodeTable.userId, userName),
), ),
); );
return nodes.map((n) => n.definition);
} }
export async function getNodesBySystem( export async function getNodesBySystem(
@ -91,11 +100,14 @@ export async function getNodesBySystem(
systemId: string, systemId: string,
): Promise<NodeDefinition[]> { ): Promise<NodeDefinition[]> {
const nodes = await db const nodes = await db
.select() .selectDistinctOn(
[nodeTable.userId, nodeTable.systemId, nodeTable.nodeId],
{ definition: nodeTable.definition },
)
.from(nodeTable) .from(nodeTable)
.where( .where(
and(eq(nodeTable.systemId, systemId), eq(nodeTable.userId, username)), and(eq(nodeTable.systemId, systemId), eq(nodeTable.userId, username)),
); ).orderBy(nodeTable.userId, nodeTable.systemId, nodeTable.nodeId);
const definitions = nodes const definitions = nodes
.map((node) => NodeDefinitionSchema.safeParse(node.definition)) .map((node) => NodeDefinitionSchema.safeParse(node.definition))
@ -118,7 +130,9 @@ export async function getNodeWasmById(
eq(nodeTable.systemId, systemId), eq(nodeTable.systemId, systemId),
eq(nodeTable.nodeId, nodeId), eq(nodeTable.nodeId, nodeId),
), ),
).limit(1).execute(); )
.orderBy(asc(nodeTable.createdAt))
.limit(1);
console.log("Time to load wasm", performance.now() - a); console.log("Time to load wasm", performance.now() - a);
if (!node[0]) { if (!node[0]) {
@ -141,7 +155,9 @@ export async function getNodeDefinitionById(
eq(nodeTable.systemId, systemId), eq(nodeTable.systemId, systemId),
eq(nodeTable.nodeId, nodeId), eq(nodeTable.nodeId, nodeId),
), ),
).limit(1); )
.orderBy(asc(nodeTable.createdAt))
.limit(1);
if (!node[0]) { if (!node[0]) {
return; return;
@ -155,3 +171,55 @@ export async function getNodeDefinitionById(
return definition.data; return definition.data;
} }
export async function getNodeVersions(
user: string,
system: string,
nodeId: string,
) {
const nodes = await db.select({
definition: nodeTable.definition,
hash: nodeTable.hash,
}).from(
nodeTable,
).where(
and(
eq(nodeTable.userId, user),
eq(nodeTable.systemId, system),
eq(nodeTable.nodeId, nodeId),
),
)
.orderBy(asc(nodeTable.createdAt));
return nodes.map((node) => ({
...node.definition,
id: node.definition.id + "@" + node.hash,
}));
}
export async function getNodeVersion(
user: string,
system: string,
nodeId: string,
hash: string,
) {
const nodes = await db.select({
definition: nodeTable.definition,
hash: nodeTable.hash,
}).from(
nodeTable,
).where(
and(
eq(nodeTable.userId, user),
eq(nodeTable.systemId, system),
eq(nodeTable.nodeId, nodeId),
eq(nodeTable.hash, hash),
),
).limit(1);
if (nodes.length === 0) {
throw new Error("Node not found");
}
return nodes[0].definition;
}