Final typescript conversions for server.

This commit is contained in:
mike12345567 2022-11-26 16:24:37 +00:00
parent 3aed49778f
commit 93cb1c52de
5 changed files with 732 additions and 744 deletions

View File

@ -18,11 +18,7 @@ import {
convertRowId, convertRowId,
} from "../../../integrations/utils" } from "../../../integrations/utils"
import { getDatasourceAndQuery } from "./utils" import { getDatasourceAndQuery } from "./utils"
import { import { FieldTypes, RelationshipTypes } from "../../../constants"
DataSourceOperation,
FieldTypes,
RelationshipTypes,
} from "../../../constants"
import { breakExternalTableId, isSQL } from "../../../integrations/utils" import { breakExternalTableId, isSQL } from "../../../integrations/utils"
import { processObjectSync } from "@budibase/string-templates" import { processObjectSync } from "@budibase/string-templates"
// @ts-ignore // @ts-ignore
@ -30,7 +26,7 @@ import { cloneDeep } from "lodash/fp"
import { processFormulas, processDates } from "../../../utilities/rowProcessor" import { processFormulas, processDates } from "../../../utilities/rowProcessor"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
interface ManyRelationship { export interface ManyRelationship {
tableId?: string tableId?: string
id?: string id?: string
isUpdate?: boolean isUpdate?: boolean
@ -38,21 +34,22 @@ interface ManyRelationship {
[key: string]: any [key: string]: any
} }
interface RunConfig { export interface RunConfig {
id?: string id?: any[]
filters?: SearchFilters filters?: SearchFilters
sort?: SortJson sort?: SortJson
paginate?: PaginationJson paginate?: PaginationJson
datasource?: Datasource
row?: Row row?: Row
rows?: Row[] rows?: Row[]
tables?: Record<string, Table>
} }
module External { function buildFilters(
function buildFilters(
id: string | undefined | string[], id: string | undefined | string[],
filters: SearchFilters, filters: SearchFilters,
table: Table table: Table
) { ) {
const primary = table.primary const primary = table.primary
// if passed in array need to copy for shifting etc // if passed in array need to copy for shifting etc
let idCopy: undefined | string | any[] = cloneDeep(id) let idCopy: undefined | string | any[] = cloneDeep(id)
@ -87,9 +84,9 @@ module External {
return { return {
equal, equal,
} }
} }
/** /**
* This function checks the incoming parameters to make sure all the inputs are * This function checks the incoming parameters to make sure all the inputs are
* valid based on on the table schema. The main thing this is looking for is when a * valid based on on the table schema. The main thing this is looking for is when a
* user has made use of the _id field of a row for a foreign key or a search parameter. * user has made use of the _id field of a row for a foreign key or a search parameter.
@ -97,7 +94,7 @@ module External {
* simplify it down to the requirements. This function is quite complex as we try to be * simplify it down to the requirements. This function is quite complex as we try to be
* relatively restrictive over what types of columns we will perform this action for. * relatively restrictive over what types of columns we will perform this action for.
*/ */
function cleanupConfig(config: RunConfig, table: Table): RunConfig { function cleanupConfig(config: RunConfig, table: Table): RunConfig {
const primaryOptions = [ const primaryOptions = [
FieldTypes.STRING, FieldTypes.STRING,
FieldTypes.LONGFORM, FieldTypes.LONGFORM,
@ -134,9 +131,9 @@ module External {
} }
return config return config
} }
function generateIdForRow(row: Row | undefined, table: Table): string { function generateIdForRow(row: Row | undefined, table: Table): string {
const primary = table.primary const primary = table.primary
if (!row || !primary) { if (!row || !primary) {
return "" return ""
@ -154,9 +151,9 @@ module External {
return "" return ""
} }
return generateRowIdField(idParts) return generateRowIdField(idParts)
} }
function getEndpoint(tableId: string | undefined, operation: string) { function getEndpoint(tableId: string | undefined, operation: string) {
if (!tableId) { if (!tableId) {
return {} return {}
} }
@ -166,9 +163,9 @@ module External {
entityId: tableName, entityId: tableName,
operation, operation,
} }
} }
function basicProcessing(row: Row, table: Table): Row { function basicProcessing(row: Row, table: Table): Row {
const thisRow: Row = {} const thisRow: Row = {}
// filter the row down to what is actually the row (not joined) // filter the row down to what is actually the row (not joined)
for (let fieldName of Object.keys(table.schema)) { for (let fieldName of Object.keys(table.schema)) {
@ -183,9 +180,9 @@ module External {
thisRow.tableId = table._id thisRow.tableId = table._id
thisRow._rev = "rev" thisRow._rev = "rev"
return processFormulas(table, thisRow) return processFormulas(table, thisRow)
} }
function fixArrayTypes(row: Row, table: Table) { function fixArrayTypes(row: Row, table: Table) {
for (let [fieldName, schema] of Object.entries(table.schema)) { for (let [fieldName, schema] of Object.entries(table.schema)) {
if ( if (
schema.type === FieldTypes.ARRAY && schema.type === FieldTypes.ARRAY &&
@ -200,25 +197,21 @@ module External {
} }
} }
return row return row
} }
function isOneSide(field: FieldSchema) { function isOneSide(field: FieldSchema) {
return ( return (
field.relationshipType && field.relationshipType.split("-")[0] === "one" field.relationshipType && field.relationshipType.split("-")[0] === "one"
) )
} }
class ExternalRequest { export class ExternalRequest {
private operation: DataSourceOperation private operation: Operation
private tableId: string private tableId: string
private datasource: Datasource private datasource?: Datasource
private tables: { [key: string]: Table } = {} private tables: { [key: string]: Table } = {}
constructor( constructor(operation: Operation, tableId: string, datasource?: Datasource) {
operation: DataSourceOperation,
tableId: string,
datasource: Datasource
) {
this.operation = operation this.operation = operation
this.tableId = tableId this.tableId = tableId
this.datasource = datasource this.datasource = datasource
@ -270,9 +263,7 @@ module External {
newRow[key] = row[key] newRow[key] = row[key]
continue continue
} }
const { tableName: linkTableName } = breakExternalTableId( const { tableName: linkTableName } = breakExternalTableId(field?.tableId)
field?.tableId
)
// table has to exist for many to many // table has to exist for many to many
if (!linkTableName || !this.tables[linkTableName]) { if (!linkTableName || !this.tables[linkTableName]) {
continue continue
@ -530,7 +521,7 @@ module External {
continue continue
} }
const response = await getDatasourceAndQuery({ const response = await getDatasourceAndQuery({
endpoint: getEndpoint(tableId, DataSourceOperation.READ), endpoint: getEndpoint(tableId, Operation.READ),
filters: { filters: {
equal: { equal: {
[fieldName]: row[lookupField], [fieldName]: row[lookupField],
@ -578,9 +569,7 @@ module External {
row[linkPrimary] === relationship.id || row[linkPrimary] === relationship.id ||
row[linkPrimary] === body?.[linkPrimary] row[linkPrimary] === body?.[linkPrimary]
) )
const operation = isUpdate const operation = isUpdate ? Operation.UPDATE : Operation.CREATE
? DataSourceOperation.UPDATE
: DataSourceOperation.CREATE
if (!found) { if (!found) {
promises.push( promises.push(
getDatasourceAndQuery({ getDatasourceAndQuery({
@ -596,24 +585,17 @@ module External {
} }
} }
// finally cleanup anything that needs to be removed // finally cleanup anything that needs to be removed
for (let [colName, { isMany, rows, tableId }] of Object.entries( for (let [colName, { isMany, rows, tableId }] of Object.entries(related)) {
related
)) {
const table: Table | undefined = this.getTable(tableId) const table: Table | undefined = this.getTable(tableId)
// if its not the foreign key skip it, nothing to do // if its not the foreign key skip it, nothing to do
if ( if (!table || (table.primary && table.primary.indexOf(colName) !== -1)) {
!table ||
(table.primary && table.primary.indexOf(colName) !== -1)
) {
continue continue
} }
for (let row of rows) { for (let row of rows) {
const filters = buildFilters(generateIdForRow(row, table), {}, table) const filters = buildFilters(generateIdForRow(row, table), {}, table)
// safety check, if there are no filters on deletion bad things happen // safety check, if there are no filters on deletion bad things happen
if (Object.keys(filters).length !== 0) { if (Object.keys(filters).length !== 0) {
const op = isMany const op = isMany ? Operation.DELETE : Operation.UPDATE
? DataSourceOperation.DELETE
: DataSourceOperation.UPDATE
const body = isMany ? null : { [colName]: null } const body = isMany ? null : { [colName]: null }
promises.push( promises.push(
getDatasourceAndQuery({ getDatasourceAndQuery({
@ -696,7 +678,7 @@ module External {
const processed = this.inputProcessing(row, table) const processed = this.inputProcessing(row, table)
row = processed.row row = processed.row
if ( if (
operation === DataSourceOperation.DELETE && operation === Operation.DELETE &&
(filters == null || Object.keys(filters).length === 0) (filters == null || Object.keys(filters).length === 0)
) { ) {
throw "Deletion must be filtered" throw "Deletion must be filtered"
@ -727,10 +709,7 @@ module External {
// can't really use response right now // can't really use response right now
const response = await getDatasourceAndQuery(json) const response = await getDatasourceAndQuery(json)
// handle many to many relationships now if we know the ID (could be auto increment) // handle many to many relationships now if we know the ID (could be auto increment)
if ( if (operation !== Operation.READ && processed.manyRelationships) {
operation !== DataSourceOperation.READ &&
processed.manyRelationships
) {
await this.handleManyRelationships( await this.handleManyRelationships(
table._id || "", table._id || "",
response[0], response[0],
@ -739,11 +718,8 @@ module External {
} }
const output = this.outputProcessing(response, table, relationships) const output = this.outputProcessing(response, table, relationships)
// if reading it'll just be an array of rows, return whole thing // if reading it'll just be an array of rows, return whole thing
return operation === DataSourceOperation.READ && Array.isArray(response) return operation === Operation.READ && Array.isArray(response)
? output ? output
: { row: output[0], table } : { row: output[0], table }
} }
}
module.exports = ExternalRequest
} }

View File

@ -1,104 +1,117 @@
const { import {
DataSourceOperation,
SortDirection, SortDirection,
FieldTypes, FieldTypes,
NoEmptyFilterStrings, NoEmptyFilterStrings,
} = require("../../../constants") } from "../../../constants"
const { import {
breakExternalTableId, breakExternalTableId,
breakRowIdField, breakRowIdField,
} = require("../../../integrations/utils") } from "../../../integrations/utils"
const ExternalRequest = require("./ExternalRequest") import { ExternalRequest, RunConfig } from "./ExternalRequest"
const { context } = require("@budibase/backend-core") import { context } from "@budibase/backend-core"
const exporters = require("../view/exporters") import * as exporters from "../view/exporters"
const { apiFileReturn } = require("../../../utilities/fileSystem") import { apiFileReturn } from "../../../utilities/fileSystem"
import {
Operation,
BBContext,
Row,
PaginationJson,
Table,
Datasource,
} from "@budibase/types"
async function handleRequest(operation, tableId, opts = {}) { export async function handleRequest(
operation: Operation,
tableId: string,
opts?: RunConfig
) {
// make sure the filters are cleaned up, no empty strings for equals, fuzzy or string // make sure the filters are cleaned up, no empty strings for equals, fuzzy or string
if (opts && opts.filters) { if (opts && opts.filters) {
for (let filterField of NoEmptyFilterStrings) { for (let filterField of NoEmptyFilterStrings) {
if (!opts.filters[filterField]) { if (!opts.filters[filterField]) {
continue continue
} }
// @ts-ignore
for (let [key, value] of Object.entries(opts.filters[filterField])) { for (let [key, value] of Object.entries(opts.filters[filterField])) {
if (!value || value === "") { if (!value || value === "") {
// @ts-ignore
delete opts.filters[filterField][key] delete opts.filters[filterField][key]
} }
} }
} }
} }
return new ExternalRequest(operation, tableId, opts.datasource).run(opts) return new ExternalRequest(operation, tableId, opts?.datasource).run(
opts || {}
)
} }
exports.handleRequest = handleRequest export async function patch(ctx: BBContext) {
exports.patch = async ctx => {
const inputs = ctx.request.body const inputs = ctx.request.body
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
const id = inputs._id const id = inputs._id
// don't save the ID to db // don't save the ID to db
delete inputs._id delete inputs._id
return handleRequest(DataSourceOperation.UPDATE, tableId, { return handleRequest(Operation.UPDATE, tableId, {
id: breakRowIdField(id), id: breakRowIdField(id),
row: inputs, row: inputs,
}) })
} }
exports.save = async ctx => { export async function save(ctx: BBContext) {
const inputs = ctx.request.body const inputs = ctx.request.body
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
return handleRequest(DataSourceOperation.CREATE, tableId, { return handleRequest(Operation.CREATE, tableId, {
row: inputs, row: inputs,
}) })
} }
exports.fetchView = async ctx => { export async function fetchView(ctx: BBContext) {
// there are no views in external datasources, shouldn't ever be called // there are no views in external datasources, shouldn't ever be called
// for now just fetch // for now just fetch
const split = ctx.params.viewName.split("all_") const split = ctx.params.viewName.split("all_")
ctx.params.tableId = split[1] ? split[1] : split[0] ctx.params.tableId = split[1] ? split[1] : split[0]
return exports.fetch(ctx) return fetch(ctx)
} }
exports.fetch = async ctx => { export async function fetch(ctx: BBContext) {
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
return handleRequest(DataSourceOperation.READ, tableId) return handleRequest(Operation.READ, tableId)
} }
exports.find = async ctx => { export async function find(ctx: BBContext) {
const id = ctx.params.rowId const id = ctx.params.rowId
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
const response = await handleRequest(DataSourceOperation.READ, tableId, { const response = (await handleRequest(Operation.READ, tableId, {
id: breakRowIdField(id), id: breakRowIdField(id),
}) })) as Row[]
return response ? response[0] : response return response ? response[0] : response
} }
exports.destroy = async ctx => { export async function destroy(ctx: BBContext) {
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
const id = ctx.request.body._id const id = ctx.request.body._id
const { row } = await handleRequest(DataSourceOperation.DELETE, tableId, { const { row } = (await handleRequest(Operation.DELETE, tableId, {
id: breakRowIdField(id), id: breakRowIdField(id),
}) })) as { row: Row }
return { response: { ok: true }, row } return { response: { ok: true }, row }
} }
exports.bulkDestroy = async ctx => { export async function bulkDestroy(ctx: BBContext) {
const { rows } = ctx.request.body const { rows } = ctx.request.body
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
let promises = [] let promises = []
for (let row of rows) { for (let row of rows) {
promises.push( promises.push(
handleRequest(DataSourceOperation.DELETE, tableId, { handleRequest(Operation.DELETE, tableId, {
id: breakRowIdField(row._id), id: breakRowIdField(row._id),
}) })
) )
} }
const responses = await Promise.all(promises) const responses = (await Promise.all(promises)) as { row: Row }[]
return { response: { ok: true }, rows: responses.map(resp => resp.row) } return { response: { ok: true }, rows: responses.map(resp => resp.row) }
} }
exports.search = async ctx => { export async function search(ctx: BBContext) {
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
const { paginate, query, ...params } = ctx.request.body const { paginate, query, ...params } = ctx.request.body
let { bookmark, limit } = params let { bookmark, limit } = params
@ -129,26 +142,26 @@ exports.search = async ctx => {
} }
} }
try { try {
const rows = await handleRequest(DataSourceOperation.READ, tableId, { const rows = (await handleRequest(Operation.READ, tableId, {
filters: query, filters: query,
sort, sort,
paginate: paginateObj, paginate: paginateObj as PaginationJson,
}) })) as Row[]
let hasNextPage = false let hasNextPage = false
if (paginate && rows.length === limit) { if (paginate && rows.length === limit) {
const nextRows = await handleRequest(DataSourceOperation.READ, tableId, { const nextRows = (await handleRequest(Operation.READ, tableId, {
filters: query, filters: query,
sort, sort,
paginate: { paginate: {
limit: 1, limit: 1,
page: bookmark * limit + 1, page: bookmark * limit + 1,
}, },
}) })) as Row[]
hasNextPage = nextRows.length > 0 hasNextPage = nextRows.length > 0
} }
// need wrapper object for bookmarks etc when paginating // need wrapper object for bookmarks etc when paginating
return { rows, hasNextPage, bookmark: bookmark + 1 } return { rows, hasNextPage, bookmark: bookmark + 1 }
} catch (err) { } catch (err: any) {
if (err.message && err.message.includes("does not exist")) { if (err.message && err.message.includes("does not exist")) {
throw new Error( throw new Error(
`Table updated externally, please re-fetch - ${err.message}` `Table updated externally, please re-fetch - ${err.message}`
@ -159,12 +172,12 @@ exports.search = async ctx => {
} }
} }
exports.validate = async () => { export async function validate(ctx: BBContext) {
// can't validate external right now - maybe in future // can't validate external right now - maybe in future
return { valid: true } return { valid: true }
} }
exports.exportRows = async ctx => { export async function exportRows(ctx: BBContext) {
const { datasourceId } = breakExternalTableId(ctx.params.tableId) const { datasourceId } = breakExternalTableId(ctx.params.tableId)
const db = context.getAppDB() const db = context.getAppDB()
const format = ctx.query.format const format = ctx.query.format
@ -176,13 +189,15 @@ exports.exportRows = async ctx => {
ctx.request.body = { ctx.request.body = {
query: { query: {
oneOf: { oneOf: {
_id: ctx.request.body.rows.map(row => JSON.parse(decodeURI(row))[0]), _id: ctx.request.body.rows.map(
(row: string) => JSON.parse(decodeURI(row))[0]
),
}, },
}, },
} }
let result = await exports.search(ctx) let result = await search(ctx)
let rows = [] let rows: Row[] = []
// Filter data to only specified columns if required // Filter data to only specified columns if required
if (columns && columns.length) { if (columns && columns.length) {
@ -197,6 +212,7 @@ exports.exportRows = async ctx => {
} }
let headers = Object.keys(rows[0]) let headers = Object.keys(rows[0])
// @ts-ignore
const exporter = exporters[format] const exporter = exporters[format]
const filename = `export.${format}` const filename = `export.${format}`
@ -205,21 +221,24 @@ exports.exportRows = async ctx => {
return apiFileReturn(exporter(headers, rows)) return apiFileReturn(exporter(headers, rows))
} }
exports.fetchEnrichedRow = async ctx => { export async function fetchEnrichedRow(ctx: BBContext) {
const id = ctx.params.rowId const id = ctx.params.rowId
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
const { datasourceId, tableName } = breakExternalTableId(tableId) const { datasourceId, tableName } = breakExternalTableId(tableId)
const db = context.getAppDB() const db = context.getAppDB()
const datasource = await db.get(datasourceId) const datasource: Datasource = await db.get(datasourceId)
if (!tableName) {
ctx.throw(400, "Unable to find table.")
}
if (!datasource || !datasource.entities) { if (!datasource || !datasource.entities) {
ctx.throw(400, "Datasource has not been configured for plus API.") ctx.throw(400, "Datasource has not been configured for plus API.")
} }
const tables = datasource.entities const tables = datasource.entities
const response = await handleRequest(DataSourceOperation.READ, tableId, { const response = (await handleRequest(Operation.READ, tableId, {
id, id,
datasource, datasource,
}) })) as Row[]
const table = tables[tableName] const table: Table = tables[tableName]
const row = response[0] const row = response[0]
// this seems like a lot of work, but basically we need to dig deeper for the enrich // this seems like a lot of work, but basically we need to dig deeper for the enrich
// for a single row, there is probably a better way to do this with some smart multi-layer joins // for a single row, there is probably a better way to do this with some smart multi-layer joins
@ -233,21 +252,19 @@ exports.fetchEnrichedRow = async ctx => {
} }
const links = row[fieldName] const links = row[fieldName]
const linkedTableId = field.tableId const linkedTableId = field.tableId
const linkedTable = tables[breakExternalTableId(linkedTableId).tableName] const linkedTableName = breakExternalTableId(linkedTableId).tableName!
const linkedTable = tables[linkedTableName]
// don't support composite keys right now // don't support composite keys right now
const linkedIds = links.map(link => breakRowIdField(link._id)[0]) const linkedIds = links.map((link: Row) => breakRowIdField(link._id!)[0])
row[fieldName] = await handleRequest( const primaryLink = linkedTable.primary?.[0] as string
DataSourceOperation.READ, row[fieldName] = await handleRequest(Operation.READ, linkedTableId!, {
linkedTableId,
{
tables, tables,
filters: { filters: {
oneOf: { oneOf: {
[linkedTable.primary]: linkedIds, [primaryLink]: linkedIds,
}, },
}, },
} })
)
} }
return row return row
} }

View File

@ -191,7 +191,7 @@ export async function fetchView(ctx: BBContext) {
// if this is a table view being looked for just transfer to that // if this is a table view being looked for just transfer to that
if (viewName.startsWith(DocumentType.TABLE)) { if (viewName.startsWith(DocumentType.TABLE)) {
ctx.params.tableId = viewName ctx.params.tableId = viewName
return exports.fetch(ctx) return fetch(ctx)
} }
const db = context.getAppDB() const db = context.getAppDB()
@ -347,7 +347,7 @@ export async function bulkDestroy(ctx: BBContext) {
export async function search(ctx: BBContext) { export async function search(ctx: BBContext) {
// Fetch the whole table when running in cypress, as search doesn't work // Fetch the whole table when running in cypress, as search doesn't work
if (!env.COUCH_DB_URL && env.isCypress()) { if (!env.COUCH_DB_URL && env.isCypress()) {
return { rows: await exports.fetch(ctx) } return { rows: await fetch(ctx) }
} }
const { tableId } = ctx.params const { tableId } = ctx.params

View File

@ -8,11 +8,7 @@ import {
foreignKeyStructure, foreignKeyStructure,
hasTypeChanged, hasTypeChanged,
} from "./utils" } from "./utils"
import { import { FieldTypes, RelationshipTypes } from "../../../constants"
DataSourceOperation,
FieldTypes,
RelationshipTypes,
} from "../../../constants"
import { makeExternalQuery } from "../../../integrations/base/query" import { makeExternalQuery } from "../../../integrations/base/query"
import * as csvParser from "../../../utilities/csvParser" import * as csvParser from "../../../utilities/csvParser"
import { handleRequest } from "../row/external" import { handleRequest } from "../row/external"
@ -347,7 +343,7 @@ export async function bulkImport(ctx: BBContext) {
...dataImport, ...dataImport,
existingTable: table, existingTable: table,
}) })
await handleRequest(DataSourceOperation.BULK_CREATE, table._id, { await handleRequest(Operation.BULK_CREATE, table._id!, {
rows, rows,
}) })
await events.rows.imported(table, "csv", rows.length) await events.rows.imported(table, "csv", rows.length)

View File

@ -3,7 +3,6 @@
"target": "es6", "target": "es6",
"module": "commonjs", "module": "commonjs",
"lib": ["es2020"], "lib": ["es2020"],
"allowJs": true,
"strict": true, "strict": true,
"noImplicitAny": true, "noImplicitAny": true,
"esModuleInterop": true, "esModuleInterop": true,