Add S3 presigned request generation and file upload capabilities

This commit is contained in:
Andrew Kingston 2022-01-12 14:59:33 +00:00
parent 4f7499cafe
commit 159c951262
8 changed files with 79 additions and 6 deletions

View File

@ -3335,13 +3335,18 @@
{ {
"type": "dataSource/s3", "type": "dataSource/s3",
"label": "S3 Datasource", "label": "S3 Datasource",
"key": "datasource" "key": "datasourceId"
}, },
{ {
"type": "text", "type": "text",
"label": "Bucket", "label": "Bucket",
"key": "bucket" "key": "bucket"
}, },
{
"type": "text",
"label": "File Name",
"key": "key"
},
{ {
"type": "boolean", "type": "boolean",
"label": "Disabled", "label": "Disabled",

View File

@ -36,7 +36,11 @@ const makeApiCall = async ({ method, url, body, json = true }) => {
}) })
switch (response.status) { switch (response.status) {
case 200: case 200:
return response.json() try {
return await response.json()
} catch (error) {
return null
}
case 401: case 401:
notificationStore.actions.error("Invalid credentials") notificationStore.actions.error("Invalid credentials")
return handleError(`Invalid credentials`) return handleError(`Invalid credentials`)
@ -82,14 +86,15 @@ const makeCachedApiCall = async params => {
* Constructs an API call function for a particular HTTP method. * Constructs an API call function for a particular HTTP method.
*/ */
const requestApiCall = method => async params => { const requestApiCall = method => async params => {
const { url, cache = false } = params const { external = false, url, cache = false } = params
const fixedUrl = `/${url}`.replace("//", "/") const fixedUrl = external ? url : `/${url}`.replace("//", "/")
const enrichedParams = { ...params, method, url: fixedUrl } const enrichedParams = { ...params, method, url: fixedUrl }
return await (cache ? makeCachedApiCall : makeApiCall)(enrichedParams) return await (cache ? makeCachedApiCall : makeApiCall)(enrichedParams)
} }
export default { export default {
post: requestApiCall("POST"), post: requestApiCall("POST"),
put: requestApiCall("PUT"),
get: requestApiCall("GET"), get: requestApiCall("GET"),
patch: requestApiCall("PATCH"), patch: requestApiCall("PATCH"),
del: requestApiCall("DELETE"), del: requestApiCall("DELETE"),

View File

@ -10,3 +10,12 @@ export const uploadAttachment = async (data, tableId = "") => {
json: false, json: false,
}) })
} }
export const uploadToS3 = async (signedUrl, data) => {
await API.put({
url: signedUrl,
body: data,
json: false,
external: true,
})
}

View File

@ -8,6 +8,7 @@ import {
convertJSONSchemaToTableSchema, convertJSONSchemaToTableSchema,
getJSONArrayDatasourceSchema, getJSONArrayDatasourceSchema,
} from "builder/src/builderStore/jsonUtils" } from "builder/src/builderStore/jsonUtils"
import API from "./api.js"
/** /**
* Fetches all rows for a particular Budibase data source. * Fetches all rows for a particular Budibase data source.
@ -131,3 +132,14 @@ export const fetchDatasourceSchema = async dataSource => {
}) })
return { ...schema, ...jsonAdditions } return { ...schema, ...jsonAdditions }
} }
export const getSignedS3URL = async (datasourceId, bucket, key) => {
if (!datasourceId) {
return null
}
const res = await API.post({
url: `/api/datasources/s3/getSignedS3URL`,
body: { datasourceId, bucket, key },
})
return res?.signedUrl
}

View File

@ -2,9 +2,11 @@
import Field from "./Field.svelte" import Field from "./Field.svelte"
import { CoreDropzone } from "@budibase/bbui" import { CoreDropzone } from "@budibase/bbui"
import { getContext, onMount, onDestroy } from "svelte" import { getContext, onMount, onDestroy } from "svelte"
import { getSignedS3URL } from "../../../api/index.js"
export let dataSource export let datasourceId
export let bucket export let bucket
export let key
export let field export let field
export let label export let label
export let disabled = false export let disabled = false
@ -40,7 +42,8 @@
} }
const upload = async () => { const upload = async () => {
console.log("UPLOADING!!!") const url = await API.getSignedS3URL(datasourceId, bucket, key)
await API.uploadToS3(url, file)
} }
onMount(() => { onMount(() => {

View File

@ -6,6 +6,7 @@ import {
authStore, authStore,
stateStore, stateStore,
uploadStore, uploadStore,
notificationStore,
} from "stores" } from "stores"
import { saveRow, deleteRow, executeQuery, triggerAutomation } from "api" import { saveRow, deleteRow, executeQuery, triggerAutomation } from "api"
import { ActionTypes } from "constants" import { ActionTypes } from "constants"
@ -164,6 +165,7 @@ const s3UploadHandler = async action => {
return return
} }
await uploadStore.actions.processFileUpload(componentId) await uploadStore.actions.processFileUpload(componentId)
notificationStore.actions.success("File uploaded successfully")
} }
const handlerMap = { const handlerMap = {

View File

@ -10,6 +10,9 @@ const {
const { BuildSchemaErrors, InvalidColumns } = require("../../constants") const { BuildSchemaErrors, InvalidColumns } = require("../../constants")
const { integrations } = require("../../integrations") const { integrations } = require("../../integrations")
const { getDatasourceAndQuery } = require("./row/utils") const { getDatasourceAndQuery } = require("./row/utils")
const AWS = require("aws-sdk")
const env = require("../../environment")
const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1"
exports.fetch = async function (ctx) { exports.fetch = async function (ctx) {
const database = new CouchDB(ctx.appId) const database = new CouchDB(ctx.appId)
@ -152,6 +155,35 @@ exports.query = async function (ctx) {
} }
} }
exports.getSignedS3URL = async function (ctx) {
const { datasourceId, bucket, key } = ctx.request.body || {}
if (!datasourceId || !bucket || !key) {
ctx.throw(400, "datasourceId, bucket and key must be specified")
return
}
const database = new CouchDB(ctx.appId)
const datasource = await database.get(datasourceId)
if (!datasource) {
ctx.throw(400, "The specified datasource could not be found")
return
}
let signedUrl
try {
const s3 = new AWS.S3({
region: AWS_REGION,
accessKeyId: datasource?.config?.accessKeyId,
secretAccessKey: datasource?.config?.secretAccessKey,
apiVersion: "2006-03-01",
signatureVersion: "v4",
})
const params = { Bucket: bucket, Key: key }
signedUrl = s3.getSignedUrl("putObject", params)
} catch (error) {
ctx.throw(400, error)
}
ctx.body = { signedUrl }
}
function getErrorTables(errors, errorType) { function getErrorTables(errors, errorType) {
return Object.entries(errors) return Object.entries(errors)
.filter(entry => entry[1] === errorType) .filter(entry => entry[1] === errorType)

View File

@ -93,5 +93,10 @@ router
authorized(BUILDER), authorized(BUILDER),
datasourceController.destroy datasourceController.destroy
) )
.post(
"/api/datasources/s3/getSignedS3URL",
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
datasourceController.getSignedS3URL
)
module.exports = router module.exports = router