save other attributes

This commit is contained in:
Michael Zhang 2024-07-10 03:11:19 -05:00
parent d4b3b74b5f
commit f5b9494d26
7 changed files with 120 additions and 21 deletions

View file

@ -1,11 +1,20 @@
import type { Context } from "koa"; import type { Context } from "koa";
import type {} from "@koa/bodyparser";
export async function createHeartbeats(ctx: Context) { export async function createHeartbeats(ctx: Context) {
const results = []; const results = [];
for (const heartbeat of ctx.request.body) { for (const heartbeat of ctx.request.body) {
console.log("heartbeat", heartbeat); console.log("heartbeat", heartbeat);
const time = new Date(heartbeat.time * 1000.0);
const resp = await fetch("http://localhost:3000/node", { const resp = await fetch("http://localhost:3000/node", {
method: "PUT", method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
attributes: [
["panorama::time/start", time.toISOString()],
["panorama/codetrack::project", heartbeat.project],
],
}),
}); });
const data = await resp.json(); const data = await resp.json();
results.push({ results.push({
@ -13,6 +22,6 @@ export async function createHeartbeats(ctx: Context) {
}); });
} }
ctx.status = 400; ctx.status = 400;
console.log("results", results); // console.log("results", results);
ctx.body = {}; ctx.body = {};
} }

View file

@ -12,7 +12,7 @@ attributes:
- panorama::time/start - panorama::time/start
- name: project - name: project
type: option<string> type: string
endpoints: endpoints:
- route: /api/v1/users/current/heartbeats.bulk - route: /api/v1/users/current/heartbeats.bulk

View file

@ -17,7 +17,10 @@ export function sanitizeName(name: string): string {
export async function loadApps(): Promise<Map<string, CustomApp>> { export async function loadApps(): Promise<Map<string, CustomApp>> {
const apps = new Map(); const apps = new Map();
const paths = ["/Users/michael/Projects/panorama/apps/codetrack"]; const paths = [
"/Users/michael/Projects/panorama/apps/std",
"/Users/michael/Projects/panorama/apps/codetrack",
];
for (const path of paths) { for (const path of paths) {
const app = await loadApp(path); const app = await loadApp(path);
@ -32,16 +35,19 @@ export async function loadApp(path: string): Promise<CustomApp> {
const manifest = manifestSchema.parse(manifestRaw); const manifest = manifestSchema.parse(manifestRaw);
const sanitizedName = sanitizeName(manifest.name); const sanitizedName = sanitizeName(manifest.name);
const router = new Router();
// load code // load code
if (manifest.code) {
const codePath = join(path, manifest.code); const codePath = join(path, manifest.code);
const codeModule = await import(codePath); const codeModule = await import(codePath);
// wire up routes // wire up routes
const router = new Router();
for (const endpoint of manifest.endpoints || []) { for (const endpoint of manifest.endpoints || []) {
const func = codeModule[endpoint.handler]; const func = codeModule[endpoint.handler];
router.all(endpoint.route, func); router.all(endpoint.route, func);
} }
}
await dataSource.transaction(async (em) => { await dataSource.transaction(async (em) => {
const app = await em const app = await em
@ -52,8 +58,6 @@ export async function loadApp(path: string): Promise<CustomApp> {
.getOne(); .getOne();
let appId = app?.id; let appId = app?.id;
console.log("app id", appId);
if (!appId) { if (!appId) {
const result = await em.getRepository(App).insert({ const result = await em.getRepository(App).insert({
id: sanitizedName, id: sanitizedName,
@ -63,6 +67,8 @@ export async function loadApp(path: string): Promise<CustomApp> {
appId = result.identifiers[0].id; appId = result.identifiers[0].id;
} }
if (!appId) throw new Error("could not initialize");
// register all the attributes // register all the attributes
for (const attribute of manifest.attributes || []) { for (const attribute of manifest.attributes || []) {
await em await em

View file

@ -2,7 +2,7 @@ import { z } from "zod";
export const manifestSchema = z.object({ export const manifestSchema = z.object({
name: z.string(), name: z.string(),
code: z.string(), code: z.string().optional(),
attributes: z attributes: z
.array( .array(

View file

@ -7,7 +7,7 @@ const AppDataSource = new DataSource({
entities: [PNode, App, Attribute, NodeHasAttribute], entities: [PNode, App, Attribute, NodeHasAttribute],
synchronize: true, synchronize: true,
// logging: true, logging: true,
migrationsTableName: "migrations", migrationsTableName: "migrations",
migrations: ["migrations/*"], migrations: ["migrations/*"],

View file

@ -1,9 +1,26 @@
import { Column, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm"; import {
Column,
Entity,
Index,
ManyToOne,
OneToMany,
PrimaryColumn,
UpdateDateColumn,
} from "typeorm";
@Entity({ name: "node" }) @Entity({ name: "node" })
export class PNode { export class PNode {
@PrimaryColumn() @PrimaryColumn()
id!: string; id!: string;
@UpdateDateColumn()
lastUpdated!: Date;
@OneToMany(
() => NodeHasAttribute,
(hasAttr) => hasAttr.nodeId,
)
attributes!: NodeHasAttribute[];
} }
@Entity() @Entity()
@ -11,6 +28,7 @@ export class App {
@PrimaryColumn() @PrimaryColumn()
id!: string; id!: string;
@Column()
name!: string; name!: string;
@OneToMany( @OneToMany(
@ -33,19 +51,24 @@ export class Attribute {
name!: string; name!: string;
@Column() @Column()
@Index({})
type!: string; type!: string;
} }
@Entity() @Entity()
export class NodeHasAttribute { export class NodeHasAttribute {
@PrimaryColumn() @PrimaryColumn()
@ManyToOne(
() => PNode,
(node) => node.id,
)
nodeId!: string; nodeId!: string;
@PrimaryColumn() @PrimaryColumn()
appId!: string; appId!: string;
@PrimaryColumn() @PrimaryColumn()
name!: string; attrName!: string;
@Column({ nullable: true }) @Column({ nullable: true })
nodeRef: number | undefined; nodeRef: number | undefined;
@ -57,6 +80,4 @@ export class NodeHasAttribute {
boolean: boolean | undefined; boolean: boolean | undefined;
@Column({ nullable: true }) @Column({ nullable: true })
instant: Date | undefined; instant: Date | undefined;
@Column({ nullable: true })
url: string | undefined;
} }

View file

@ -1,5 +1,5 @@
import Router from "@koa/router"; import Router from "@koa/router";
import { PNode } from "../models"; import { App, Attribute, NodeHasAttribute, PNode } from "../models";
import { uuidv7 } from "uuidv7"; import { uuidv7 } from "uuidv7";
import { dataSource } from "../db"; import { dataSource } from "../db";
@ -8,7 +8,70 @@ export const nodeRouter = new Router();
nodeRouter.put("/", async (ctx) => { nodeRouter.put("/", async (ctx) => {
const id = uuidv7(); const id = uuidv7();
const body = ctx.request.body; const body = ctx.request.body;
await dataSource.getRepository(PNode).insert({ id }); await dataSource.transaction(async (em) => {
await em.getRepository(PNode).insert({ id });
const attributes: [string, any][] = body?.attributes ?? [];
const attributeNames = attributes
.map(([name, _]) => name)
.map((name) => name.split("::"));
const appNames = new Set(attributeNames.map(([app, _]) => app));
const attrNames = new Set(attributeNames.map(([_, attr]) => attr));
console.log("attribute names", appNames);
const result = await em
.createQueryBuilder(App, "app")
.where("app.name IN (:...appNames)", { appNames: [...appNames] })
.getMany();
const appIdMappingList: [string, string][] = result.map((app) => [
app.name,
app.id,
]);
const appIds = new Set([...appIdMappingList.map(([_, id]) => id)]);
const appIdMapping = new Map([...appIdMappingList]);
const result2 = await em
.createQueryBuilder(Attribute, "attr")
.where("attr.appId IN (:...appIds)", { appIds: [...appIds] })
.andWhere("attr.name IN (:...attrNames)", { attrNames: [...attrNames] })
.getMany();
const attributeTypesList: [string, string][] = result2.map((attr) => [
`${attr.appId}::${attr.name}`,
attr.type,
]);
const attributeTypes = new Map(attributeTypesList);
console.log("attribute types", attributeTypes);
const convertValue = (
type: string,
value: string,
): Partial<NodeHasAttribute> => {
switch (type) {
case "string":
return { string: value };
case "datetime":
return { instant: new Date(value) };
default:
console.error(`fuck, unknown type ${type}`);
return {};
}
};
const attributesMapped: Partial<NodeHasAttribute>[] = attributes.map(
([name, value]) => {
const [app, attrName] = name.split("::");
const appId = appIdMapping.get(app)!;
const type = attributeTypes.get(`${appId}::${attrName}`)!;
console.log("converting type", type, value);
return { nodeId: id, appId, attrName, ...convertValue(type, value) };
},
);
console.log("mappped", attributesMapped);
const result3 = await em.insert(NodeHasAttribute, attributesMapped);
});
ctx.body = { id }; ctx.body = { id };
}); });