Merge pull request #10900 from Budibase/budi-7010/export_controller_as_post

BUDI-7010 - Export controller as post
This commit is contained in:
Adria Navarro 2023-06-14 14:38:01 +01:00 committed by GitHub
commit 672804c150
4 changed files with 78 additions and 13 deletions

View File

@ -1,5 +1,11 @@
<script> <script>
import { ModalContent, Toggle, Body, InlineAlert } from "@budibase/bbui" import {
ModalContent,
Toggle,
Body,
InlineAlert,
notifications,
} from "@budibase/bbui"
export let app export let app
export let published export let published
@ -8,10 +14,45 @@
$: title = published ? "Export published app" : "Export latest app" $: title = published ? "Export published app" : "Export latest app"
$: confirmText = published ? "Export published" : "Export latest" $: confirmText = published ? "Export published" : "Export latest"
const exportApp = () => { const exportApp = async () => {
const id = published ? app.prodId : app.devId const id = published ? app.prodId : app.devId
const appName = encodeURIComponent(app.name) const url = `/api/backups/export?appId=${id}`
window.location = `/api/backups/export?appId=${id}&appname=${appName}&excludeRows=${excludeRows}` await downloadFile(url, { excludeRows })
}
async function downloadFile(url, body) {
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
})
if (response.ok) {
const contentDisposition = response.headers.get("Content-Disposition")
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(
contentDisposition
)
const filename = matches[1].replace(/['"]/g, "")
const url = URL.createObjectURL(await response.blob())
const link = document.createElement("a")
link.href = url
link.download = filename
link.click()
URL.revokeObjectURL(url)
} else {
notifications.error("Error exporting the app.")
}
} catch (error) {
notifications.error(error.message || "Error downloading the exported app")
}
} }
</script> </script>

View File

@ -1,14 +1,22 @@
import sdk from "../../sdk" import sdk from "../../sdk"
import { events, context } from "@budibase/backend-core" import { events, context, db } from "@budibase/backend-core"
import { DocumentType } from "../../db/utils" import { DocumentType } from "../../db/utils"
import { isQsTrue } from "../../utilities" import { Ctx } from "@budibase/types"
interface ExportAppDumpRequest {
excludeRows: boolean
}
export async function exportAppDump(ctx: Ctx<ExportAppDumpRequest>) {
const { appId } = ctx.query as any
const { excludeRows } = ctx.request.body
const [app] = await db.getAppsByIDs([appId])
const appName = app.name
export async function exportAppDump(ctx: any) {
let { appId, excludeRows } = ctx.query
// remove the 120 second limit for the request // remove the 120 second limit for the request
ctx.req.setTimeout(0) ctx.req.setTimeout(0)
const appName = decodeURI(ctx.query.appname)
excludeRows = isQsTrue(excludeRows)
const backupIdentifier = `${appName}-export-${new Date().getTime()}.tar.gz` const backupIdentifier = `${appName}-export-${new Date().getTime()}.tar.gz`
ctx.attachment(backupIdentifier) ctx.attachment(backupIdentifier)
ctx.body = await sdk.backups.streamExportApp(appId, excludeRows) ctx.body = await sdk.backups.streamExportApp(appId, excludeRows)

View File

@ -5,7 +5,7 @@ import { permissions } from "@budibase/backend-core"
const router: Router = new Router() const router: Router = new Router()
router.get( router.post(
"/api/backups/export", "/api/backups/export",
authorized(permissions.BUILDER), authorized(permissions.BUILDER),
controller.exportAppDump controller.exportAppDump

View File

@ -1,7 +1,9 @@
import tk from "timekeeper"
import * as setup from "./utilities" import * as setup from "./utilities"
import { events } from "@budibase/backend-core" import { events } from "@budibase/backend-core"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import { checkBuilderEndpoint } from "./utilities/TestFunctions" import { checkBuilderEndpoint } from "./utilities/TestFunctions"
import { mocks } from "@budibase/backend-core/tests"
describe("/backups", () => { describe("/backups", () => {
let request = setup.getRequest() let request = setup.getRequest()
@ -16,7 +18,7 @@ describe("/backups", () => {
describe("exportAppDump", () => { describe("exportAppDump", () => {
it("should be able to export app", async () => { it("should be able to export app", async () => {
const res = await request const res = await request
.get(`/api/backups/export?appId=${config.getAppId()}&appname=test`) .post(`/api/backups/export?appId=${config.getAppId()}`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect(200) .expect(200)
expect(res.headers["content-type"]).toEqual("application/gzip") expect(res.headers["content-type"]).toEqual("application/gzip")
@ -26,10 +28,24 @@ describe("/backups", () => {
it("should apply authorization to endpoint", async () => { it("should apply authorization to endpoint", async () => {
await checkBuilderEndpoint({ await checkBuilderEndpoint({
config, config,
method: "GET", method: "POST",
url: `/api/backups/export?appId=${config.getAppId()}`, url: `/api/backups/export?appId=${config.getAppId()}`,
}) })
}) })
it("should infer the app name from the app", async () => {
tk.freeze(mocks.date.MOCK_DATE)
const res = await request
.post(`/api/backups/export?appId=${config.getAppId()}`)
.set(config.defaultHeaders())
expect(res.headers["content-disposition"]).toEqual(
`attachment; filename="${
config.getApp()!.name
}-export-${mocks.date.MOCK_DATE.getTime()}.tar.gz"`
)
})
}) })
describe("calculateBackupStats", () => { describe("calculateBackupStats", () => {