save other attributes
This commit is contained in:
parent
d4b3b74b5f
commit
f5b9494d26
7 changed files with 120 additions and 21 deletions
|
@ -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 = {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,15 +35,18 @@ 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);
|
||||||
|
|
||||||
// load code
|
|
||||||
const codePath = join(path, manifest.code);
|
|
||||||
const codeModule = await import(codePath);
|
|
||||||
|
|
||||||
// wire up routes
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
for (const endpoint of manifest.endpoints || []) {
|
|
||||||
const func = codeModule[endpoint.handler];
|
// load code
|
||||||
router.all(endpoint.route, func);
|
if (manifest.code) {
|
||||||
|
const codePath = join(path, manifest.code);
|
||||||
|
const codeModule = await import(codePath);
|
||||||
|
|
||||||
|
// wire up routes
|
||||||
|
for (const endpoint of manifest.endpoints || []) {
|
||||||
|
const func = codeModule[endpoint.handler];
|
||||||
|
router.all(endpoint.route, func);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await dataSource.transaction(async (em) => {
|
await dataSource.transaction(async (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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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/*"],
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue