diff --git a/bun.lockb b/bun.lockb index 46934e6..fb92a69 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 9c239f6..598c595 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "typescript": "^5.0.0" }, "dependencies": { + "@mrleebo/prisma-ast": "^0.10.1", "brotli-wasm": "^2.0.1", "exit-hook": "^4.0.0", "firebase-admin": "^11.11.0", diff --git a/pkgs/api/_dbs.ts b/pkgs/api/_dbs.ts index 76dc201..4f617e0 100644 --- a/pkgs/api/_dbs.ts +++ b/pkgs/api/_dbs.ts @@ -1,10 +1,9 @@ import { apiContext } from "service-srv"; import { execQuery } from "utils/query"; - const g = global as any; export const _ = { - url: "/_dbs/:tableName", - async api(tableName: any) { + url: "/_dbs/*", + async api() { const { req, res } = apiContext(this); if (typeof g.db !== "undefined") { const body = req.params; diff --git a/pkgs/server/create.ts b/pkgs/server/create.ts index 9917d9a..0e456a5 100644 --- a/pkgs/server/create.ts +++ b/pkgs/server/create.ts @@ -59,6 +59,12 @@ export const createServer = async () => { await scan(dir(`app/srv/api`)); await scan(dir(`pkgs/api`)); + g.createServer = (arg) => { + return async (site_id: string) => { + return arg; + }; + }; + g.server = Bun.serve({ port: g.port, maxRequestBodySize: 1024 * 1024 * 128, diff --git a/pkgs/utils/global.ts b/pkgs/utils/global.ts index 303b1c7..dc546dc 100644 --- a/pkgs/utils/global.ts +++ b/pkgs/utils/global.ts @@ -70,6 +70,9 @@ export const g = global as unknown as { br: Record; br_timeout: Set; }; + createServer: ( + arg: PrasiServer & { api: any; db: any } + ) => (site_id: string) => Promise; deploy: { init: boolean; raw: any; diff --git a/pkgs/utils/query.ts b/pkgs/utils/query.ts index 76d4801..3dce3b5 100644 --- a/pkgs/utils/query.ts +++ b/pkgs/utils/query.ts @@ -1,4 +1,7 @@ +import { createPrismaSchemaBuilder } from "@mrleebo/prisma-ast"; +import { readAsync } from "fs-jetpack"; import { Prisma } from "../../app/db/db"; +import { dir } from "./dir"; export type DBArg = { db: string; @@ -10,7 +13,125 @@ export type DBArg = { export const execQuery = async (args: DBArg, prisma: any) => { const { table, action, params } = args; - console.log(args); + if (action.startsWith("schema_")) { + const schema_path = dir("app/db/prisma/schema.prisma"); + const schema = createPrismaSchemaBuilder(await readAsync(schema_path)); + if (action === "schema_tables") { + const tables = schema.findAllByType("model", {}).map((e) => e?.name); + return tables || []; + } else { + const schema_table = schema.findByType("model", { name: table }); + const columns = {} as Record< + string, + { + is_pk: boolean; + type: string; + optional: boolean; + db_type: string; + default?: any + } + >; + const rels = {} as Record + if (schema_table) { + if (action === "schema_rels") { + for (const col of schema_table.properties) { + if ( + col.type === "field" && + (!!col.array || + col.attributes && + col.attributes?.length > 0) + ) { + if (col.array) { + if (typeof col.fieldType === 'string') { + const target = schema.findByType('model', { name: col.fieldType }) + + if (target) { + const field = target.properties.find(e => { + if (e.type === 'field' + && e.fieldType === table) { + return true; + } + }); + if (field && field.type === 'field') { + const rel = field.attributes?.find(e => e.kind === 'field'); + + if (rel && rel.args) { + const { field, ref } = getFieldAndRef(rel, target, table); + + if (target && ref) { + rels[col.name] = { + type: 'has-many', + to: field, + from: ref + } + } + + } + } + } + } + } else if (col.attributes) { + const rel = col.attributes.find(e => e.type === 'attribute' && e.name === 'relation'); + if (rel && typeof col.fieldType === 'string') { + const target = schema.findByType('model', { name: col.fieldType }); + const { field, ref } = getFieldAndRef(rel, target, table); + + rels[col.name] = { + type: 'has-one', + to: { + table: field.table, + fields: ref.fields + }, + from: { + table: ref.table, + fields: field.fields + } + } + } + } + } + } + return rels; + } else if (action === "schema_columns") { + for (const col of schema_table.properties) { + if ( + col.type === "field" && + !col.array && + col.attributes && + col.attributes?.length > 0 + ) { + const attr = col.attributes.find(e => e.name !== 'id' && e.name !== 'default'); + + const default_val = col.attributes.find(e => e.name === 'default'); + const is_pk = col.attributes.find(e => e.name === 'id'); + + if (attr && attr.name !== "relation") { + let type = "String"; + if (typeof col.fieldType === "string") type = col.fieldType; + + columns[col.name] = { + is_pk: !!is_pk, + type: type.toLowerCase(), + optional: !!col.optional, + db_type: attr.name, + default: default_val + }; + } + } + } + return columns; + } + } + } + } const tableInstance = prisma[table]; @@ -42,3 +163,29 @@ export const execQuery = async (args: DBArg, prisma: any) => { } } }; + +const getFieldAndRef = (rel: any, target: any, table: string) => { + let field = null as unknown as { table: string, fields: string[] }; + let ref = null as unknown as { table: string, fields: string[] }; + for (const e of rel.args) { + if (typeof e.value === 'object' + && !Array.isArray(e.value) + && e.value.type === 'keyValue' + && typeof e.value.value === 'object' + && !Array.isArray(e.value.value) + && e.value.value.type === 'array') { + if (e.value.key === 'fields') { + field = { + table: target.name, + fields: e.value.value.args + } + } else if (e.value.key === 'references') { + ref = { + table: table, + fields: e.value.value.args + } + } + } + } + return { field, ref }; +} \ No newline at end of file