Extract core from controller

This commit is contained in:
Adria Navarro 2025-04-10 17:16:24 +02:00
parent c8fc01fa0c
commit 7f41d085f7
6 changed files with 192 additions and 192 deletions

View File

@ -1,200 +1,10 @@
import * as uuid from "uuid"
import {
DocumentType,
FieldType,
GenerateTablesRequest,
GenerateTablesResponse,
SourceName,
Table,
TableSchema,
TableSourceType,
Upload,
UserCtx,
} from "@budibase/types"
import { ai } from "@budibase/pro"
import { context, objectStore, utils } from "@budibase/backend-core"
import sdk from "../../sdk"
import fs, { mkdirSync } from "fs"
import path, { join } from "path"
import { pipeline } from "stream"
import { promisify } from "util"
import fetch from "node-fetch"
import { ObjectStoreBuckets } from "../../constants"
import { uploadUrl } from "../../utilities"
import { uploadFile } from "../../utilities/fileUtils"
async function generateTablesDelegate(
tables: { name: string; primaryDisplay: string; schema: TableSchema }[]
) {
const count = (await sdk.datasources.fetch()).length
const { id: dsId } = await context.getAppDB().put({
_id: `${DocumentType.DATASOURCE}_bb_internal_${utils.newid()}`,
name: `Test ${count}`,
type: "budibase",
source: SourceName.BUDIBASE,
config: {},
})
const createdTables: GenerateTablesResponse["createdTables"] = []
const tableIds: Record<string, string> = {}
for (const table of tables) {
const createdTable = await sdk.tables.create({
...table,
sourceId: dsId,
schema: {},
primaryDisplay: undefined,
sourceType: TableSourceType.INTERNAL,
type: "table",
})
createdTables.push({ id: createdTable._id!, name: createdTable.name })
tableIds[table.name] = createdTable._id!
}
for (const table of tables) {
for (const field of Object.values(table.schema)) {
if (field.type === FieldType.LINK) {
const linkedTable = createdTables.find(t => t.name === field.tableId)
if (!linkedTable) {
throw `Table ${field.tableId} not found in the json response.`
}
field.tableId = linkedTable.id
} else if (field.type === FieldType.FORMULA) {
field.formula = `{{ js "${btoa(field.formula)}" }}`
}
}
}
for (const table of tables) {
const storedTable = await sdk.tables.getTable(tableIds[table.name])
await sdk.tables.update({
...storedTable,
schema: {
...storedTable.schema,
...table.schema,
},
primaryDisplay: table.primaryDisplay,
})
}
return createdTables
}
async function generateDataDelegate(
data: Record<string, Record<string, any>[]>,
userId: string,
tables: Record<string, Table>
) {
const createdData: Record<string, Record<string, string>> = {}
const toUpdateLinks: {
tableId: string
rowId: string
data: Record<string, { rowId: string[]; tableId: string }>
}[] = []
for (const tableName of Object.keys(data)) {
const table = tables[tableName]
const linksOverride: Record<string, null> = {}
for (const field of Object.values(table.schema).filter(
f => f.type === FieldType.LINK
)) {
linksOverride[field.name] = null
}
const attachmentColumns = Object.values(table.schema).filter(f =>
[FieldType.ATTACHMENTS, FieldType.ATTACHMENT_SINGLE].includes(f.type)
)
for (const entry of data[tableName]) {
const attachmentData: Record<string, any> = {}
for (const column of attachmentColumns) {
attachmentData[column.name] = []
if (!Array.isArray(entry[column.name])) {
entry[column.name] = [entry[column.name]]
}
for (const attachmentValue of entry[column.name]) {
let attachment
if (typeof attachmentValue === "object") {
attachment = await uploadUrl(attachmentValue)
} else {
attachment = await uploadFile(attachmentValue)
}
if (attachment) {
attachmentData[column.name].push(attachment)
}
}
if (column.type === FieldType.ATTACHMENT_SINGLE) {
attachmentData[column.name] = attachmentData[column.name][0]
}
}
const tableId = tables[tableName]._id!
const createdRow = await sdk.rows.save(
tableId,
{
...entry,
...linksOverride,
...attachmentData,
_id: undefined,
},
userId
)
createdData[tableId] ??= {}
createdData[tableId][entry._id] = createdRow.row._id!
const overridenLinks = Object.keys(linksOverride).reduce<
Record<string, { rowId: string[]; tableId: string }>
>((acc, l) => {
if (entry[l]) {
acc[l] = {
tableId: (table.schema[l] as any).tableId,
rowId: entry[l],
}
}
return acc
}, {})
if (Object.keys(overridenLinks).length) {
toUpdateLinks.push({
tableId: createdRow.table._id!,
rowId: createdRow.row._id!,
data: overridenLinks,
})
}
}
}
for (const data of toUpdateLinks) {
const persistedRow = await sdk.rows.find(data.tableId, data.rowId)
const updatedLinks = Object.keys(data.data).reduce<Record<string, any>>(
(acc, d) => {
acc[d] = [
...(persistedRow[d] || []),
...data.data[d].rowId.map(
rid => createdData[data.data[d].tableId][rid]
),
]
return acc
},
{}
)
await sdk.rows.save(
data.tableId,
{
...persistedRow,
...updatedLinks,
},
userId
)
}
}
export async function generateTables(
ctx: UserCtx<GenerateTablesRequest, GenerateTablesResponse>
@ -202,9 +12,9 @@ export async function generateTables(
const { prompt, addData } = ctx.request.body
const tableGenerator = new ai.TableGeneration({
generateTablesDelegate,
generateTablesDelegate: sdk.ai.helpers.generateTables,
getTablesDelegate: sdk.tables.getTables,
generateDataDelegate,
generateDataDelegate: sdk.ai.helpers.generateRows,
})
if (addData) {
tableGenerator.withData(ctx.user._id || "")

View File

@ -0,0 +1,2 @@
export * from "./table"
export * from "./rows"

View File

@ -0,0 +1,115 @@
import { FieldType, Table } from "@budibase/types"
import sdk from "../../.."
import { uploadFile, uploadUrl } from "../../../../utilities"
export async function generateRows(
data: Record<string, Record<string, any>[]>,
userId: string,
tables: Record<string, Table>
) {
const createdData: Record<string, Record<string, string>> = {}
const toUpdateLinks: {
tableId: string
rowId: string
data: Record<string, { rowId: string[]; tableId: string }>
}[] = []
for (const tableName of Object.keys(data)) {
const table = tables[tableName]
const linksOverride: Record<string, null> = {}
for (const field of Object.values(table.schema).filter(
f => f.type === FieldType.LINK
)) {
linksOverride[field.name] = null
}
const attachmentColumns = Object.values(table.schema).filter(f =>
[FieldType.ATTACHMENTS, FieldType.ATTACHMENT_SINGLE].includes(f.type)
)
for (const entry of data[tableName]) {
const attachmentData: Record<string, any> = {}
for (const column of attachmentColumns) {
attachmentData[column.name] = []
if (!Array.isArray(entry[column.name])) {
entry[column.name] = [entry[column.name]]
}
for (const attachmentValue of entry[column.name]) {
let attachment
if (typeof attachmentValue === "object") {
attachment = await uploadUrl(attachmentValue)
} else {
attachment = await uploadFile(attachmentValue)
}
if (attachment) {
attachmentData[column.name].push(attachment)
}
}
if (column.type === FieldType.ATTACHMENT_SINGLE) {
attachmentData[column.name] = attachmentData[column.name][0]
}
}
const tableId = tables[tableName]._id!
const createdRow = await sdk.rows.save(
tableId,
{
...entry,
...linksOverride,
...attachmentData,
_id: undefined,
},
userId
)
createdData[tableId] ??= {}
createdData[tableId][entry._id] = createdRow.row._id!
const overridenLinks = Object.keys(linksOverride).reduce<
Record<string, { rowId: string[]; tableId: string }>
>((acc, l) => {
if (entry[l]) {
acc[l] = {
tableId: (table.schema[l] as any).tableId,
rowId: entry[l],
}
}
return acc
}, {})
if (Object.keys(overridenLinks).length) {
toUpdateLinks.push({
tableId: createdRow.table._id!,
rowId: createdRow.row._id!,
data: overridenLinks,
})
}
}
}
for (const data of toUpdateLinks) {
const persistedRow = await sdk.rows.find(data.tableId, data.rowId)
const updatedLinks = Object.keys(data.data).reduce<Record<string, any>>(
(acc, d) => {
acc[d] = [
...(persistedRow[d] || []),
...data.data[d].rowId.map(
rid => createdData[data.data[d].tableId][rid]
),
]
return acc
},
{}
)
await sdk.rows.save(
data.tableId,
{
...persistedRow,
...updatedLinks,
},
userId
)
}
}

View File

@ -0,0 +1,70 @@
import {
DocumentType,
FieldType,
GenerateTablesResponse,
SourceName,
TableSchema,
TableSourceType,
} from "@budibase/types"
import sdk from "../../.."
import { context, utils } from "@budibase/backend-core"
export async function generateTables(
tables: { name: string; primaryDisplay: string; schema: TableSchema }[]
) {
const count = (await sdk.datasources.fetch()).length
const { id: dsId } = await context.getAppDB().put({
_id: `${DocumentType.DATASOURCE}_bb_internal_${utils.newid()}`,
name: `Test ${count}`,
type: "budibase",
source: SourceName.BUDIBASE,
config: {},
})
const createdTables: GenerateTablesResponse["createdTables"] = []
const tableIds: Record<string, string> = {}
for (const table of tables) {
const createdTable = await sdk.tables.create({
...table,
sourceId: dsId,
schema: {},
primaryDisplay: undefined,
sourceType: TableSourceType.INTERNAL,
type: "table",
})
createdTables.push({ id: createdTable._id!, name: createdTable.name })
tableIds[table.name] = createdTable._id!
}
for (const table of tables) {
for (const field of Object.values(table.schema)) {
if (field.type === FieldType.LINK) {
const linkedTable = createdTables.find(t => t.name === field.tableId)
if (!linkedTable) {
throw `Table ${field.tableId} not found in the json response.`
}
field.tableId = linkedTable.id
} else if (field.type === FieldType.FORMULA) {
field.formula = `{{ js "${btoa(field.formula)}" }}`
}
}
}
for (const table of tables) {
const storedTable = await sdk.tables.getTable(tableIds[table.name])
await sdk.tables.update({
...storedTable,
schema: {
...storedTable.schema,
...table.schema,
},
primaryDisplay: table.primaryDisplay,
})
}
return createdTables
}

View File

@ -0,0 +1 @@
export * as helpers from "./helpers"

View File

@ -14,6 +14,7 @@ import * as rowActions from "./app/rowActions"
import * as screens from "./app/screens"
import * as common from "./app/common"
import * as oauth2 from "./app/oauth2"
import * as ai from "./app/ai"
const sdk = {
backups,
@ -32,6 +33,7 @@ const sdk = {
rowActions,
common,
oauth2,
ai,
}
// default export for TS