2022-08-11 07:01:48 +12:00
|
|
|
import { ObjectStoreBuckets } from "../../constants"
|
2022-09-07 03:28:35 +12:00
|
|
|
import { loadJSFile } from "../../utilities/fileSystem"
|
2022-08-31 08:37:08 +12:00
|
|
|
import {
|
2022-09-07 03:28:35 +12:00
|
|
|
uploadedNpmPlugin,
|
|
|
|
uploadedUrlPlugin,
|
|
|
|
uploadedGithubPlugin,
|
|
|
|
uploadedFilePlugin,
|
|
|
|
} from "./plugin/utils"
|
2022-08-11 07:01:48 +12:00
|
|
|
import { getGlobalDB } from "@budibase/backend-core/tenancy"
|
2022-08-12 02:29:51 +12:00
|
|
|
import { generatePluginID, getPluginParams } from "../../db/utils"
|
2022-09-05 21:13:55 +12:00
|
|
|
import {
|
|
|
|
uploadDirectory,
|
|
|
|
deleteFolder,
|
|
|
|
} from "@budibase/backend-core/objectStore"
|
2022-08-16 09:23:45 +12:00
|
|
|
import { PluginType, FileType } from "@budibase/types"
|
2022-09-03 06:32:15 +12:00
|
|
|
import env from "../../environment"
|
2022-08-13 04:03:06 +12:00
|
|
|
|
|
|
|
export async function getPlugins(type?: PluginType) {
|
|
|
|
const db = getGlobalDB()
|
|
|
|
const response = await db.allDocs(
|
|
|
|
getPluginParams(null, {
|
|
|
|
include_docs: true,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
const plugins = response.rows.map((row: any) => row.doc)
|
|
|
|
if (type) {
|
|
|
|
return plugins.filter((plugin: any) => plugin.schema?.type === type)
|
|
|
|
} else {
|
|
|
|
return plugins
|
|
|
|
}
|
|
|
|
}
|
2022-08-11 07:01:48 +12:00
|
|
|
|
|
|
|
export async function upload(ctx: any) {
|
2022-08-16 09:23:45 +12:00
|
|
|
const plugins: FileType[] =
|
2022-08-11 07:01:48 +12:00
|
|
|
ctx.request.files.file.length > 1
|
|
|
|
? Array.from(ctx.request.files.file)
|
|
|
|
: [ctx.request.files.file]
|
|
|
|
try {
|
2022-08-11 21:37:57 +12:00
|
|
|
let docs = []
|
2022-08-11 07:01:48 +12:00
|
|
|
// can do single or multiple plugins
|
|
|
|
for (let plugin of plugins) {
|
2022-09-08 00:51:14 +12:00
|
|
|
const doc = await processPlugin(
|
|
|
|
plugin,
|
|
|
|
ctx.request.body.source,
|
|
|
|
ctx.request.body.update
|
|
|
|
)
|
2022-08-16 09:23:45 +12:00
|
|
|
docs.push(doc)
|
2022-08-11 07:01:48 +12:00
|
|
|
}
|
2022-08-11 21:37:57 +12:00
|
|
|
ctx.body = {
|
|
|
|
message: "Plugin(s) uploaded successfully",
|
|
|
|
plugins: docs,
|
|
|
|
}
|
2022-08-11 07:01:48 +12:00
|
|
|
} catch (err: any) {
|
|
|
|
const errMsg = err?.message ? err?.message : err
|
2022-08-31 08:37:08 +12:00
|
|
|
|
2022-08-11 07:01:48 +12:00
|
|
|
ctx.throw(400, `Failed to import plugin: ${errMsg}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-31 08:37:08 +12:00
|
|
|
export async function create(ctx: any) {
|
2022-09-05 21:28:09 +12:00
|
|
|
const { type, source, url, headers, githubToken } = ctx.request.body
|
2022-08-31 08:37:08 +12:00
|
|
|
|
2022-09-07 03:28:35 +12:00
|
|
|
if (!env.SELF_HOSTED) {
|
2022-09-07 03:37:37 +12:00
|
|
|
ctx.throw(400, "Plugins not supported outside of self-host.")
|
2022-09-07 03:28:35 +12:00
|
|
|
}
|
|
|
|
|
2022-09-07 03:37:37 +12:00
|
|
|
try {
|
|
|
|
let metadata
|
|
|
|
let directory
|
|
|
|
// Generating random name as a backup and needed for url
|
|
|
|
let name = "PLUGIN_" + Math.floor(100000 + Math.random() * 900000)
|
|
|
|
|
|
|
|
switch (source) {
|
|
|
|
case "npm":
|
2022-09-07 03:28:35 +12:00
|
|
|
const { metadata: metadataNpm, directory: directoryNpm } =
|
|
|
|
await uploadedNpmPlugin(url, name)
|
|
|
|
metadata = metadataNpm
|
|
|
|
directory = directoryNpm
|
2022-09-07 03:37:37 +12:00
|
|
|
break
|
|
|
|
case "github":
|
|
|
|
const { metadata: metadataGithub, directory: directoryGithub } =
|
|
|
|
await uploadedGithubPlugin(ctx, url, name, githubToken)
|
|
|
|
metadata = metadataGithub
|
|
|
|
directory = directoryGithub
|
|
|
|
break
|
|
|
|
case "url":
|
|
|
|
const { metadata: metadataUrl, directory: directoryUrl } =
|
|
|
|
await uploadedUrlPlugin(url, name, headers)
|
|
|
|
metadata = metadataUrl
|
|
|
|
directory = directoryUrl
|
|
|
|
break
|
|
|
|
}
|
2022-08-31 08:37:08 +12:00
|
|
|
|
2022-09-01 04:53:00 +12:00
|
|
|
const doc = await storePlugin(metadata, directory, source)
|
2022-09-01 03:09:47 +12:00
|
|
|
|
|
|
|
ctx.body = {
|
|
|
|
message: "Plugin uploaded successfully",
|
2022-09-01 04:53:00 +12:00
|
|
|
plugins: [doc],
|
2022-09-01 03:09:47 +12:00
|
|
|
}
|
|
|
|
} catch (err: any) {
|
|
|
|
const errMsg = err?.message ? err?.message : err
|
|
|
|
|
|
|
|
ctx.throw(400, `Failed to import plugin: ${errMsg}`)
|
|
|
|
}
|
2022-08-31 08:37:08 +12:00
|
|
|
ctx.status = 200
|
|
|
|
}
|
|
|
|
|
2022-08-12 02:29:51 +12:00
|
|
|
export async function fetch(ctx: any) {
|
2022-08-13 04:03:06 +12:00
|
|
|
ctx.body = await getPlugins()
|
2022-08-12 02:29:51 +12:00
|
|
|
}
|
2022-08-11 07:01:48 +12:00
|
|
|
|
2022-08-30 21:49:19 +12:00
|
|
|
export async function destroy(ctx: any) {
|
|
|
|
const db = getGlobalDB()
|
2022-09-05 21:13:55 +12:00
|
|
|
const { pluginId, pluginRev } = ctx.params
|
|
|
|
|
|
|
|
try {
|
|
|
|
const plugin = await db.get(pluginId)
|
|
|
|
const bucketPath = `${plugin.name}/`
|
|
|
|
await deleteFolder(ObjectStoreBuckets.PLUGINS, bucketPath)
|
|
|
|
|
|
|
|
await db.remove(pluginId, pluginRev)
|
|
|
|
} catch (err: any) {
|
|
|
|
const errMsg = err?.message ? err?.message : err
|
|
|
|
|
|
|
|
ctx.throw(400, `Failed to delete plugin: ${errMsg}`)
|
|
|
|
}
|
|
|
|
|
2022-08-30 21:49:19 +12:00
|
|
|
ctx.message = `Plugin ${ctx.params.pluginId} deleted.`
|
|
|
|
ctx.status = 200
|
|
|
|
}
|
2022-08-16 09:23:45 +12:00
|
|
|
|
2022-08-31 08:37:08 +12:00
|
|
|
export async function storePlugin(
|
|
|
|
metadata: any,
|
|
|
|
directory: any,
|
2022-09-08 00:51:14 +12:00
|
|
|
source?: string,
|
|
|
|
update?: boolean
|
2022-08-31 08:37:08 +12:00
|
|
|
) {
|
2022-08-16 09:23:45 +12:00
|
|
|
const db = getGlobalDB()
|
|
|
|
const version = metadata.package.version,
|
|
|
|
name = metadata.package.name,
|
|
|
|
description = metadata.package.description
|
|
|
|
|
|
|
|
// first open the tarball into tmp directory
|
2022-09-03 06:32:15 +12:00
|
|
|
const bucketPath = `${name}/`
|
2022-08-16 09:23:45 +12:00
|
|
|
const files = await uploadDirectory(
|
|
|
|
ObjectStoreBuckets.PLUGINS,
|
|
|
|
directory,
|
|
|
|
bucketPath
|
|
|
|
)
|
|
|
|
const jsFile = files.find((file: any) => file.name.endsWith(".js"))
|
|
|
|
if (!jsFile) {
|
|
|
|
throw new Error(`Plugin missing .js file.`)
|
|
|
|
}
|
2022-09-03 06:32:15 +12:00
|
|
|
// validate the JS for a datasource
|
|
|
|
if (metadata.schema.type === PluginType.DATASOURCE) {
|
|
|
|
const js = loadJSFile(directory, jsFile.name)
|
|
|
|
// TODO: this isn't safe - but we need full node environment
|
|
|
|
// in future we should do this in a thread for safety
|
|
|
|
try {
|
2022-09-06 02:04:26 +12:00
|
|
|
eval(js)
|
2022-09-03 06:32:15 +12:00
|
|
|
} catch (err: any) {
|
|
|
|
const message = err?.message ? err.message : JSON.stringify(err)
|
|
|
|
throw new Error(`JS invalid: ${message}`)
|
|
|
|
}
|
|
|
|
}
|
2022-08-16 09:23:45 +12:00
|
|
|
const jsFileName = jsFile.name
|
2022-09-03 06:32:15 +12:00
|
|
|
const pluginId = generatePluginID(name)
|
2022-08-16 09:23:45 +12:00
|
|
|
|
|
|
|
// overwrite existing docs entirely if they exist
|
|
|
|
let rev
|
|
|
|
try {
|
|
|
|
const existing = await db.get(pluginId)
|
|
|
|
rev = existing._rev
|
|
|
|
} catch (err) {
|
2022-09-08 00:51:14 +12:00
|
|
|
if (update) {
|
|
|
|
throw new Error("Unable to update. Plugin does not exist")
|
|
|
|
}
|
2022-08-16 09:23:45 +12:00
|
|
|
rev = undefined
|
|
|
|
}
|
2022-09-06 21:37:49 +12:00
|
|
|
let doc = {
|
2022-08-16 09:23:45 +12:00
|
|
|
_id: pluginId,
|
|
|
|
_rev: rev,
|
2022-09-03 06:32:15 +12:00
|
|
|
...metadata,
|
2022-08-16 09:23:45 +12:00
|
|
|
name,
|
|
|
|
version,
|
|
|
|
description,
|
|
|
|
jsUrl: `${bucketPath}${jsFileName}`,
|
|
|
|
}
|
2022-09-06 21:37:49 +12:00
|
|
|
|
|
|
|
if (source) {
|
|
|
|
doc = {
|
|
|
|
...doc,
|
|
|
|
source,
|
|
|
|
}
|
|
|
|
}
|
2022-09-08 00:51:14 +12:00
|
|
|
|
2022-08-16 09:23:45 +12:00
|
|
|
const response = await db.put(doc)
|
|
|
|
return {
|
|
|
|
...doc,
|
|
|
|
_rev: response.rev,
|
|
|
|
}
|
|
|
|
}
|
2022-08-31 08:37:08 +12:00
|
|
|
|
2022-09-08 00:51:14 +12:00
|
|
|
export async function processPlugin(
|
|
|
|
plugin: FileType,
|
|
|
|
source?: string,
|
|
|
|
update?: boolean
|
|
|
|
) {
|
2022-09-05 20:38:24 +12:00
|
|
|
if (!env.SELF_HOSTED) {
|
|
|
|
throw new Error("Plugins not supported outside of self-host.")
|
|
|
|
}
|
|
|
|
|
2022-09-07 03:28:35 +12:00
|
|
|
const { metadata, directory } = await uploadedFilePlugin(plugin)
|
2022-09-08 00:51:14 +12:00
|
|
|
return await storePlugin(metadata, directory, source, update)
|
2022-08-31 08:37:08 +12:00
|
|
|
}
|