fixes for google sheets, admin checklist, and deleting an app from API (#8846)
* fixes for google sheets, admin checklist, and deleting an app from API * code review * splitting unpublish endpoint, moving deploy endpoint to applications controller. Still to do public API work and move deployment controller into application controller * updating REST method for unpublish in API test * unpublish and publish endpoint on public API, delete endpoint unpublishes and deletes app * removing skip_setup from prodAppDb call * removing commented code * unit tests and open API spec updates * unpublish, publish unit tests - delete still in progress * remove line updating app name in API test * unit tests * v2.1.46 * Update pro version to 2.1.46 * v2.2.0 * Update pro version to 2.2.0 * Fix for budibase plugin skeleton, which utilises the old import style. * Fix side nav styles * v2.2.1 * Update pro version to 2.2.1 * using dist folder to allow importing constants for openAPI specs * v2.2.2 * Update pro version to 2.2.2 * Fix for user enrichment call (updating to @budibase/nano fork) (#9038) * Fix for #9029 - this should fix the issue users have been experiencing with user enrichment calls in apps, essentially it utilises a fork of the nano library we use to interact with CouchDB, which has been updated to use a POST request rather than a GET request as it supports a larger set of data being sent as query parameters. * Incrementing Nano version to attempt to fix yarn registry issues. * v2.2.3 * Update pro version to 2.2.3 * Fix SQL table `_id` filtering (#9030) * Re-add support for filtering on _id using external SQL tables and fix filter key prefixes not working with _id field * Remove like operator from internal tables and only allow basic operators on SQL table _id column * Update data section filtering to respect new rules * Update automation section filtering to respect new rules * Update dynamic filter component to respect new rules * v2.2.4 * Update pro version to 2.2.4 * lock changes (#9047) * v2.2.5 * Update pro version to 2.2.5 * Make looping arrow point in right direction (#9053) * v2.2.6 * Update pro version to 2.2.6 * Types/attaching license to account (#9065) * adding license type to account * removing planDuration * v2.2.7 * Update pro version to 2.2.7 * Environment variable type coercion fix (#9074) * Environment variable type coercion fix * Update .gitignore * v2.2.8 * Update pro version to 2.2.8 * tests passing * all tests passing, updates to public API response * update unpublish call to return 204, openAPI spec and unit * fixing API tests Co-authored-by: Budibase Release Bot <> Co-authored-by: mike12345567 <me@michaeldrury.co.uk> Co-authored-by: Andrew Kingston <andrew@kingston.dev> Co-authored-by: melohagan <101575380+melohagan@users.noreply.github.com> Co-authored-by: Rory Powell <rory.codes@gmail.com>
This commit is contained in:
parent
5e95e6060e
commit
84ab7862d1
|
@ -4,6 +4,7 @@ builder/*
|
|||
packages/server/runtime_apps/
|
||||
.idea/
|
||||
bb-airgapped.tar.gz
|
||||
*.iml
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
|
|
@ -15,4 +15,4 @@
|
|||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -79,4 +79,4 @@
|
|||
"typescript": "4.7.3"
|
||||
},
|
||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
||||
}
|
||||
}
|
|
@ -13,6 +13,18 @@ const getClient = async (type: LockType): Promise<Redlock> => {
|
|||
}
|
||||
return noRetryRedlock
|
||||
}
|
||||
case LockType.DEFAULT: {
|
||||
if (!noRetryRedlock) {
|
||||
noRetryRedlock = await newRedlock(OPTIONS.DEFAULT)
|
||||
}
|
||||
return noRetryRedlock
|
||||
}
|
||||
case LockType.DELAY_500: {
|
||||
if (!noRetryRedlock) {
|
||||
noRetryRedlock = await newRedlock(OPTIONS.DELAY_500)
|
||||
}
|
||||
return noRetryRedlock
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Could not get redlock client: ${type}`)
|
||||
}
|
||||
|
@ -41,6 +53,9 @@ export const OPTIONS = {
|
|||
// see https://www.awsarchitectureblog.com/2015/03/backoff.html
|
||||
retryJitter: 100, // time in ms
|
||||
},
|
||||
DELAY_500: {
|
||||
retryDelay: 500,
|
||||
},
|
||||
}
|
||||
|
||||
export const newRedlock = async (opts: Options = {}) => {
|
||||
|
@ -55,19 +70,17 @@ export const doWithLock = async (opts: LockOptions, task: any) => {
|
|||
let lock
|
||||
try {
|
||||
// aquire lock
|
||||
let name: string
|
||||
if (opts.systemLock) {
|
||||
name = opts.name
|
||||
} else {
|
||||
name = `${tenancy.getTenantId()}_${opts.name}`
|
||||
}
|
||||
let name: string = `lock:${tenancy.getTenantId()}_${opts.name}`
|
||||
if (opts.nameSuffix) {
|
||||
name = name + `_${opts.nameSuffix}`
|
||||
}
|
||||
lock = await redlock.lock(name, opts.ttl)
|
||||
// perform locked task
|
||||
return task()
|
||||
// need to await to ensure completion before unlocking
|
||||
const result = await task()
|
||||
return result
|
||||
} catch (e: any) {
|
||||
console.log("lock error")
|
||||
// lock limit exceeded
|
||||
if (e.name === "LockError") {
|
||||
if (opts.type === LockType.TRY_ONCE) {
|
||||
|
|
|
@ -89,4 +89,4 @@
|
|||
"loader-utils": "1.4.1"
|
||||
},
|
||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
||||
}
|
||||
}
|
|
@ -123,4 +123,4 @@
|
|||
"vite": "^3.0.8"
|
||||
},
|
||||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
||||
}
|
||||
}
|
|
@ -180,7 +180,7 @@
|
|||
onSelect(block)
|
||||
}}
|
||||
>
|
||||
<Icon name={showLooping ? "ChevronDown" : "ChevronUp"} />
|
||||
<Icon name={showLooping ? "ChevronUp" : "ChevronDown"} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
import { ProgressCircle } from "@budibase/bbui"
|
||||
import CopyInput from "components/common/inputs/CopyInput.svelte"
|
||||
|
||||
let feedbackModal
|
||||
let publishModal
|
||||
let asyncModal
|
||||
let publishCompleteModal
|
||||
|
@ -23,13 +22,13 @@
|
|||
|
||||
export let onOk
|
||||
|
||||
async function deployApp() {
|
||||
async function publishApp() {
|
||||
try {
|
||||
//In Progress
|
||||
asyncModal.show()
|
||||
publishModal.hide()
|
||||
|
||||
published = await API.deployAppChanges()
|
||||
published = await API.publishAppChanges($store.appId)
|
||||
|
||||
if (typeof onOk === "function") {
|
||||
await onOk()
|
||||
|
@ -56,20 +55,11 @@
|
|||
</script>
|
||||
|
||||
<Button cta on:click={publishModal.show}>Publish</Button>
|
||||
<Modal bind:this={feedbackModal}>
|
||||
<ModalContent
|
||||
title="Enjoying Budibase?"
|
||||
size="L"
|
||||
showConfirmButton={false}
|
||||
showCancelButton={false}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={publishModal}>
|
||||
<ModalContent
|
||||
title="Publish to Production"
|
||||
confirmText="Publish"
|
||||
onConfirm={deployApp}
|
||||
onConfirm={publishApp}
|
||||
dataCy={"deploy-app-modal"}
|
||||
>
|
||||
<span
|
||||
|
|
|
@ -186,7 +186,9 @@
|
|||
<span>{$organisation?.company || "Budibase"}</span>
|
||||
</div>
|
||||
<div class="onboarding">
|
||||
<ConfigChecklist />
|
||||
{#if $auth.user?.admin?.global}
|
||||
<ConfigChecklist />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu">
|
||||
|
|
|
@ -54,4 +54,4 @@
|
|||
"eslint": "^7.20.0",
|
||||
"renamer": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -63,4 +63,4 @@
|
|||
"loader-utils": "1.4.1"
|
||||
},
|
||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
||||
}
|
||||
}
|
|
@ -10,4 +10,4 @@
|
|||
"lodash": "^4.17.21",
|
||||
"svelte": "^3.46.2"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,11 +22,11 @@ export const buildAppEndpoints = API => ({
|
|||
},
|
||||
|
||||
/**
|
||||
* Deploys the current app.
|
||||
* Publishes the current app.
|
||||
*/
|
||||
deployAppChanges: async () => {
|
||||
publishAppChanges: async appId => {
|
||||
return await API.post({
|
||||
url: "/api/deploy",
|
||||
url: `/api/applications/${appId}/publish`,
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -98,8 +98,8 @@ export const buildAppEndpoints = API => ({
|
|||
* @param appId the production ID of the app to unpublish
|
||||
*/
|
||||
unpublishApp: async appId => {
|
||||
return await API.delete({
|
||||
url: `/api/applications/${appId}?unpublish=1`,
|
||||
return await API.post({
|
||||
url: `/api/applications/${appId}/unpublish`,
|
||||
})
|
||||
},
|
||||
|
||||
|
|
|
@ -20,4 +20,4 @@
|
|||
"rollup-plugin-polyfill-node": "^0.8.0",
|
||||
"rollup-plugin-terser": "^7.0.2"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -168,4 +168,4 @@
|
|||
"oracledb": "5.3.0"
|
||||
},
|
||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
||||
}
|
||||
}
|
|
@ -567,6 +567,40 @@
|
|||
"data"
|
||||
]
|
||||
},
|
||||
"deploymentOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_id": {
|
||||
"description": "The ID of the app.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status of the deployment, whether it succeeded or failed",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SUCCESS",
|
||||
"FAILURE"
|
||||
]
|
||||
},
|
||||
"appUrl": {
|
||||
"description": "The URL of the published app",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"_id",
|
||||
"status",
|
||||
"appUrl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"row": {
|
||||
"description": "The row to be created/updated, based on the table schema.",
|
||||
"type": "object",
|
||||
|
@ -1933,6 +1967,56 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/applications/{appId}/unpublish": {
|
||||
"post": {
|
||||
"operationId": "unpublish",
|
||||
"summary": "Unpublish an application",
|
||||
"tags": [
|
||||
"applications"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/appIdUrl"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "The app was published successfully."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/applications/{appId}/publish": {
|
||||
"post": {
|
||||
"operationId": "publish",
|
||||
"summary": "Unpublish an application",
|
||||
"tags": [
|
||||
"applications"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/appIdUrl"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Returns the deployment object.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/deploymentOutput"
|
||||
},
|
||||
"examples": {
|
||||
"deployment": {
|
||||
"$ref": "#/components/examples/deploymentOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/applications/search": {
|
||||
"post": {
|
||||
"operationId": "search",
|
||||
|
|
|
@ -411,6 +411,30 @@ components:
|
|||
- version
|
||||
required:
|
||||
- data
|
||||
deploymentOutput:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
_id:
|
||||
description: The ID of the app.
|
||||
type: string
|
||||
status:
|
||||
description: Status of the deployment, whether it succeeded or failed
|
||||
type: string
|
||||
enum:
|
||||
- SUCCESS
|
||||
- FAILURE
|
||||
appUrl:
|
||||
description: The URL of the published app
|
||||
type: string
|
||||
required:
|
||||
- _id
|
||||
- status
|
||||
- appUrl
|
||||
required:
|
||||
- data
|
||||
row:
|
||||
description: The row to be created/updated, based on the table schema.
|
||||
type: object
|
||||
|
@ -1453,6 +1477,35 @@ paths:
|
|||
examples:
|
||||
application:
|
||||
$ref: "#/components/examples/application"
|
||||
"/applications/{appId}/unpublish":
|
||||
post:
|
||||
operationId: unpublish
|
||||
summary: Unpublish an application
|
||||
tags:
|
||||
- applications
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/appIdUrl"
|
||||
responses:
|
||||
"204":
|
||||
description: The app was published successfully.
|
||||
"/applications/{appId}/publish":
|
||||
post:
|
||||
operationId: publish
|
||||
summary: Unpublish an application
|
||||
tags:
|
||||
- applications
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/appIdUrl"
|
||||
responses:
|
||||
"200":
|
||||
description: Returns the deployment object.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/deploymentOutput"
|
||||
examples:
|
||||
deployment:
|
||||
$ref: "#/components/examples/deploymentOutput"
|
||||
/applications/search:
|
||||
post:
|
||||
operationId: search
|
||||
|
|
|
@ -80,6 +80,22 @@ const applicationOutputSchema = object(
|
|||
}
|
||||
)
|
||||
|
||||
const deploymentOutputSchema = object({
|
||||
_id: {
|
||||
description: "The ID of the app.",
|
||||
type: "string",
|
||||
},
|
||||
status: {
|
||||
description: "Status of the deployment, whether it succeeded or failed",
|
||||
type: "string",
|
||||
enum: ["SUCCESS", "FAILURE"],
|
||||
},
|
||||
appUrl: {
|
||||
description: "The URL of the published app",
|
||||
type: "string",
|
||||
},
|
||||
})
|
||||
|
||||
module.exports = new Resource()
|
||||
.setExamples({
|
||||
application: {
|
||||
|
@ -104,4 +120,7 @@ module.exports = new Resource()
|
|||
items: applicationOutputSchema,
|
||||
},
|
||||
}),
|
||||
deploymentOutput: object({
|
||||
data: deploymentOutputSchema,
|
||||
}),
|
||||
})
|
||||
|
|
|
@ -50,6 +50,7 @@ import {
|
|||
} from "@budibase/types"
|
||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||
import sdk from "../../sdk"
|
||||
import { getDB } from "@budibase/backend-core/src/db"
|
||||
|
||||
// utility function, need to do away with this
|
||||
async function getLayouts() {
|
||||
|
@ -464,41 +465,47 @@ export async function revertClient(ctx: BBContext) {
|
|||
ctx.body = app
|
||||
}
|
||||
|
||||
async function destroyApp(ctx: BBContext) {
|
||||
const unpublishApp = async (ctx: any) => {
|
||||
let appId = ctx.params.appId
|
||||
let isUnpublish = ctx.query && ctx.query.unpublish
|
||||
appId = dbCore.getProdAppID(appId)
|
||||
|
||||
if (isUnpublish) {
|
||||
appId = dbCore.getProdAppID(appId)
|
||||
const devAppId = dbCore.getDevAppID(appId)
|
||||
// sync before removing the published app
|
||||
await sdk.applications.syncApp(devAppId)
|
||||
}
|
||||
|
||||
const db = isUnpublish ? context.getProdAppDB() : context.getAppDB()
|
||||
const app = await db.get(DocumentType.APP_METADATA)
|
||||
const db = context.getProdAppDB()
|
||||
const result = await db.destroy()
|
||||
|
||||
if (isUnpublish) {
|
||||
await events.app.unpublished(app)
|
||||
} else {
|
||||
await quotas.removeApp()
|
||||
await events.app.deleted(app)
|
||||
await events.app.unpublished({ appId } as App)
|
||||
|
||||
// automations only in production
|
||||
await cleanupAutomations(appId)
|
||||
|
||||
await cache.app.invalidateAppMetadata(appId)
|
||||
return result
|
||||
}
|
||||
|
||||
async function destroyApp(ctx: BBContext) {
|
||||
let appId = ctx.params.appId
|
||||
appId = dbCore.getProdAppID(appId)
|
||||
const devAppId = dbCore.getDevAppID(appId)
|
||||
|
||||
// check if we need to unpublish first
|
||||
if (await dbCore.dbExists(appId)) {
|
||||
// app is deployed, run through unpublish flow
|
||||
await sdk.applications.syncApp(devAppId)
|
||||
await unpublishApp(ctx)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!env.isTest() && !isUnpublish) {
|
||||
const db = dbCore.getDB(devAppId)
|
||||
// standard app deletion flow
|
||||
const app = await db.get(DocumentType.APP_METADATA)
|
||||
const result = await db.destroy()
|
||||
await quotas.removeApp()
|
||||
await events.app.deleted(app)
|
||||
|
||||
if (!env.isTest()) {
|
||||
await deleteApp(appId)
|
||||
}
|
||||
// automations only in production
|
||||
if (isUnpublish) {
|
||||
await cleanupAutomations(appId)
|
||||
}
|
||||
// remove app role when the dev app is deleted (no trace of app anymore)
|
||||
else {
|
||||
await removeAppFromUserRoles(ctx, appId)
|
||||
}
|
||||
await cache.app.invalidateAppMetadata(appId)
|
||||
|
||||
await removeAppFromUserRoles(ctx, appId)
|
||||
await cache.app.invalidateAppMetadata(devAppId)
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -523,6 +530,21 @@ export async function destroy(ctx: BBContext) {
|
|||
ctx.body = result
|
||||
}
|
||||
|
||||
export const unpublish = async (ctx: BBContext) => {
|
||||
const prodAppId = dbCore.getProdAppID(ctx.params.appId)
|
||||
const dbExists = await dbCore.dbExists(prodAppId)
|
||||
|
||||
// check app has been published
|
||||
if (!dbExists) {
|
||||
return ctx.throw(400, "App has not been published.")
|
||||
}
|
||||
|
||||
await preDestroyApp(ctx)
|
||||
await unpublishApp(ctx)
|
||||
await postDestroyApp(ctx)
|
||||
ctx.status = 204
|
||||
}
|
||||
|
||||
export async function sync(ctx: BBContext) {
|
||||
const appId = ctx.params.appId
|
||||
try {
|
||||
|
|
|
@ -82,7 +82,7 @@ export async function importApps(ctx: Ctx) {
|
|||
"Import file is required and environment must be fresh to import apps."
|
||||
)
|
||||
}
|
||||
const file = ctx.request.files.importFile
|
||||
const file = ctx.request.files.importFile as any
|
||||
if (Array.isArray(file)) {
|
||||
ctx.throw(400, "Single file is required")
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ export default class Deployment {
|
|||
verification: any
|
||||
status?: string
|
||||
err?: any
|
||||
appUrl?: string
|
||||
|
||||
constructor(id = null) {
|
||||
this._id = id || newid()
|
||||
|
|
|
@ -94,7 +94,44 @@ async function initDeployedApp(prodAppId: any) {
|
|||
})
|
||||
}
|
||||
|
||||
async function deployApp(deployment: any, userId: string) {
|
||||
export async function fetchDeployments(ctx: any) {
|
||||
try {
|
||||
const db = context.getAppDB()
|
||||
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
||||
const { updated, deployments } = await checkAllDeployments(deploymentDoc)
|
||||
if (updated) {
|
||||
await db.put(deployments)
|
||||
}
|
||||
ctx.body = Object.values(deployments.history).reverse()
|
||||
} catch (err) {
|
||||
ctx.body = []
|
||||
}
|
||||
}
|
||||
|
||||
export async function deploymentProgress(ctx: any) {
|
||||
try {
|
||||
const db = context.getAppDB()
|
||||
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
||||
ctx.body = deploymentDoc[ctx.params.deploymentId]
|
||||
} catch (err) {
|
||||
ctx.throw(
|
||||
500,
|
||||
`Error fetching data for deployment ${ctx.params.deploymentId}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const publishApp = async function (ctx: any) {
|
||||
let deployment = new Deployment()
|
||||
console.log("Deployment object created")
|
||||
deployment.setStatus(DeploymentStatus.PENDING)
|
||||
console.log("Deployment object set to pending")
|
||||
deployment = await storeDeploymentHistory(deployment)
|
||||
console.log("Stored deployment history")
|
||||
|
||||
console.log("Deploying app...")
|
||||
|
||||
let app
|
||||
let replication
|
||||
try {
|
||||
const appId = context.getAppId()!
|
||||
|
@ -108,7 +145,7 @@ async function deployApp(deployment: any, userId: string) {
|
|||
productionAppId,
|
||||
AppBackupTrigger.PUBLISH,
|
||||
{
|
||||
createdBy: userId,
|
||||
createdBy: ctx.user._id,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -147,7 +184,7 @@ async function deployApp(deployment: any, userId: string) {
|
|||
console.log("Deployed app initialised, setting deployment to successful")
|
||||
deployment.setStatus(DeploymentStatus.SUCCESS)
|
||||
await storeDeploymentHistory(deployment)
|
||||
return appDoc
|
||||
app = appDoc
|
||||
} catch (err: any) {
|
||||
deployment.setStatus(DeploymentStatus.FAILURE, err.message)
|
||||
await storeDeploymentHistory(deployment)
|
||||
|
@ -160,62 +197,7 @@ async function deployApp(deployment: any, userId: string) {
|
|||
await replication.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchDeployments(ctx: any) {
|
||||
try {
|
||||
const db = context.getAppDB()
|
||||
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
||||
const { updated, deployments } = await checkAllDeployments(deploymentDoc)
|
||||
if (updated) {
|
||||
await db.put(deployments)
|
||||
}
|
||||
ctx.body = Object.values(deployments.history).reverse()
|
||||
} catch (err) {
|
||||
ctx.body = []
|
||||
}
|
||||
}
|
||||
|
||||
export async function deploymentProgress(ctx: any) {
|
||||
try {
|
||||
const db = context.getAppDB()
|
||||
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
||||
ctx.body = deploymentDoc[ctx.params.deploymentId]
|
||||
} catch (err) {
|
||||
ctx.throw(
|
||||
500,
|
||||
`Error fetching data for deployment ${ctx.params.deploymentId}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const isFirstDeploy = async () => {
|
||||
try {
|
||||
const db = context.getProdAppDB()
|
||||
await db.get(DocumentType.APP_METADATA)
|
||||
} catch (e: any) {
|
||||
if (e.status === 404) {
|
||||
return true
|
||||
}
|
||||
throw e
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const _deployApp = async function (ctx: any) {
|
||||
let deployment = new Deployment()
|
||||
console.log("Deployment object created")
|
||||
deployment.setStatus(DeploymentStatus.PENDING)
|
||||
console.log("Deployment object set to pending")
|
||||
deployment = await storeDeploymentHistory(deployment)
|
||||
console.log("Stored deployment history")
|
||||
|
||||
console.log("Deploying app...")
|
||||
|
||||
let app = await deployApp(deployment, ctx.user._id)
|
||||
|
||||
await events.app.published(app)
|
||||
ctx.body = deployment
|
||||
}
|
||||
|
||||
export { _deployApp as deployApp }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { db as dbCore, context } from "@budibase/backend-core"
|
||||
import { search as stringSearch, addRev } from "./utils"
|
||||
import * as controller from "../application"
|
||||
import * as deployController from "../deploy"
|
||||
import { Application } from "../../../definitions/common"
|
||||
|
||||
function fixAppID(app: Application, params: any) {
|
||||
|
@ -74,10 +75,26 @@ export async function destroy(ctx: any, next: any) {
|
|||
})
|
||||
}
|
||||
|
||||
export async function unpublish(ctx: any, next: any) {
|
||||
await context.doInAppContext(ctx.params.appId, async () => {
|
||||
await controller.unpublish(ctx)
|
||||
await next()
|
||||
})
|
||||
}
|
||||
|
||||
export async function publish(ctx: any, next: any) {
|
||||
await context.doInAppContext(ctx.params.appId, async () => {
|
||||
await deployController.publishApp(ctx)
|
||||
await next()
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
create,
|
||||
update,
|
||||
read,
|
||||
destroy,
|
||||
search,
|
||||
publish,
|
||||
unpublish,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Router from "@koa/router"
|
||||
import * as controller from "../controllers/application"
|
||||
import * as deploymentController from "../controllers/deploy"
|
||||
import authorized from "../../middleware/authorized"
|
||||
import { permissions } from "@budibase/backend-core"
|
||||
import { applicationValidator } from "./utils/validators"
|
||||
|
@ -37,6 +38,16 @@ router
|
|||
authorized(permissions.BUILDER),
|
||||
controller.revertClient
|
||||
)
|
||||
.post(
|
||||
"/api/applications/:appId/publish",
|
||||
authorized(permissions.BUILDER),
|
||||
deploymentController.publishApp
|
||||
)
|
||||
.post(
|
||||
"/api/applications/:appId/unpublish",
|
||||
authorized(permissions.BUILDER),
|
||||
controller.unpublish
|
||||
)
|
||||
.delete(
|
||||
"/api/applications/:appId",
|
||||
authorized(permissions.BUILDER),
|
||||
|
|
|
@ -16,6 +16,5 @@ router
|
|||
authorized(permissions.BUILDER),
|
||||
controller.deploymentProgress
|
||||
)
|
||||
.post("/api/deploy", authorized(permissions.BUILDER), controller.deployApp)
|
||||
|
||||
export = router
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import controller from "../../controllers/public/applications"
|
||||
import Endpoint from "./utils/Endpoint"
|
||||
const { nameValidator, applicationValidator } = require("../utils/validators")
|
||||
import { db } from "@budibase/backend-core"
|
||||
|
||||
const read = [],
|
||||
write = []
|
||||
|
@ -94,6 +95,49 @@ write.push(
|
|||
*/
|
||||
write.push(new Endpoint("delete", "/applications/:appId", controller.destroy))
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /applications/{appId}/unpublish:
|
||||
* post:
|
||||
* operationId: unpublish
|
||||
* summary: Unpublish an application
|
||||
* tags:
|
||||
* - applications
|
||||
* parameters:
|
||||
* - $ref: '#/components/parameters/appIdUrl'
|
||||
* responses:
|
||||
* 204:
|
||||
* description: The app was published successfully.
|
||||
*/
|
||||
write.push(
|
||||
new Endpoint("post", "/applications/:appId/unpublish", controller.unpublish)
|
||||
)
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /applications/{appId}/publish:
|
||||
* post:
|
||||
* operationId: publish
|
||||
* summary: Unpublish an application
|
||||
* tags:
|
||||
* - applications
|
||||
* parameters:
|
||||
* - $ref: '#/components/parameters/appIdUrl'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Returns the deployment object.
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/deploymentOutput'
|
||||
* examples:
|
||||
* deployment:
|
||||
* $ref: '#/components/examples/deploymentOutput'
|
||||
*/
|
||||
write.push(
|
||||
new Endpoint("post", "/applications/:appId/publish", controller.publish)
|
||||
)
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /applications/{appId}:
|
||||
|
|
|
@ -54,9 +54,13 @@ function processQueries(ctx: any) {
|
|||
}
|
||||
|
||||
export default async (ctx: any, next: any) => {
|
||||
if (!ctx.body) {
|
||||
return await next()
|
||||
}
|
||||
let urlParts = ctx.url.split("/")
|
||||
urlParts = urlParts.slice(4, urlParts.length)
|
||||
let body = {}
|
||||
|
||||
switch (urlParts[0]) {
|
||||
case Resources.APPLICATION:
|
||||
body = processApplications(ctx)
|
||||
|
|
|
@ -11,7 +11,6 @@ jest.mock("../../../utilities/redis", () => ({
|
|||
checkDebounce: jest.fn(),
|
||||
shutdown: jest.fn(),
|
||||
}))
|
||||
|
||||
import { clearAllApps, checkBuilderEndpoint } from "./utilities/TestFunctions"
|
||||
import * as setup from "./utilities"
|
||||
import { AppStatus } from "../../../db/utils"
|
||||
|
@ -160,33 +159,30 @@ describe("/applications", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
it("should delete app", async () => {
|
||||
await config.createApp("to-delete")
|
||||
describe("publish", () => {
|
||||
it("should publish app with dev app ID", async () => {
|
||||
const appId = config.getAppId()
|
||||
await request
|
||||
.delete(`/api/applications/${appId}`)
|
||||
.post(`/api/applications/${appId}/publish`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(events.app.deleted).toBeCalledTimes(1)
|
||||
expect(events.app.published).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should unpublish app", async () => {
|
||||
await config.createApp("to-unpublish")
|
||||
it("should publish app with prod app ID", async () => {
|
||||
const appId = config.getProdAppId()
|
||||
await request
|
||||
.delete(`/api/applications/${appId}?unpublish=true`)
|
||||
.post(`/api/applications/${appId}/publish`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(events.app.unpublished).toBeCalledTimes(1)
|
||||
expect(events.app.published).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("manage client library version", () => {
|
||||
it("should be able to update the app client library version", async () => {
|
||||
console.log(config.getAppId())
|
||||
await request
|
||||
.post(`/api/applications/${config.getAppId()}/client/update`)
|
||||
.set(config.defaultHeaders())
|
||||
|
@ -194,6 +190,7 @@ describe("/applications", () => {
|
|||
.expect(200)
|
||||
expect(events.app.versionUpdated).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should be able to revert the app client library version", async () => {
|
||||
// We need to first update the version so that we can then revert
|
||||
await request
|
||||
|
@ -267,4 +264,50 @@ describe("/applications", () => {
|
|||
env._set("DISABLE_AUTO_PROD_APP_SYNC", false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("unpublish", () => {
|
||||
it("should unpublish app with dev app ID", async () => {
|
||||
const appId = config.getAppId()
|
||||
await request
|
||||
.post(`/api/applications/${appId}/unpublish`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect(204)
|
||||
expect(events.app.unpublished).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should unpublish app with prod app ID", async () => {
|
||||
const appId = config.getProdAppId()
|
||||
await request
|
||||
.post(`/api/applications/${appId}/unpublish`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect(204)
|
||||
expect(events.app.unpublished).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
it("should delete published app and dev apps with dev app ID", async () => {
|
||||
await config.createApp("to-delete")
|
||||
const appId = config.getAppId()
|
||||
await request
|
||||
.delete(`/api/applications/${appId}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(events.app.deleted).toBeCalledTimes(1)
|
||||
expect(events.app.unpublished).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should delete published app and dev app with prod app ID", async () => {
|
||||
await config.createApp("to-delete")
|
||||
const appId = config.getProdAppId()
|
||||
await request
|
||||
.delete(`/api/applications/${appId}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(events.app.deleted).toBeCalledTimes(1)
|
||||
expect(events.app.unpublished).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -23,14 +23,13 @@ describe("/cloud", () => {
|
|||
// first we need to delete any existing apps on the system so it looks clean otherwise the
|
||||
// import will not run
|
||||
await request
|
||||
.delete(
|
||||
.post(
|
||||
`/api/applications/${dbCore.getProdAppID(
|
||||
config.getAppId()
|
||||
)}?unpublish=true`
|
||||
)}/unpublish`
|
||||
)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
.expect(204)
|
||||
await request
|
||||
.delete(`/api/applications/${config.getAppId()}`)
|
||||
.set(config.defaultHeaders())
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import * as setup from "./utilities"
|
||||
import { events } from "@budibase/backend-core"
|
||||
|
||||
describe("/deployments", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("deploy", () => {
|
||||
it("should deploy the application", async () => {
|
||||
await request
|
||||
.post(`/api/deploy`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect((events.app.published as jest.Mock).mock.calls.length).toBe(1)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -92,7 +92,7 @@ describe("/permission", () => {
|
|||
describe("check public user allowed", () => {
|
||||
it("should be able to read the row", async () => {
|
||||
// replicate changes before checking permissions
|
||||
await config.deploy()
|
||||
await config.publish()
|
||||
|
||||
const res = await request
|
||||
.get(`/api/${table._id}/rows`)
|
||||
|
|
|
@ -25,7 +25,7 @@ describe("/routing", () => {
|
|||
screen2.routing.roleId = BUILTIN_ROLE_IDS.POWER
|
||||
screen2.routing.route = route
|
||||
screen2 = await config.createScreen(screen2)
|
||||
await config.deploy()
|
||||
await config.publish()
|
||||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
|
|
|
@ -113,7 +113,7 @@ describe("/webhooks", () => {
|
|||
describe("trigger", () => {
|
||||
it("should allow triggering from public", async () => {
|
||||
// replicate changes before checking webhook
|
||||
await config.deploy()
|
||||
await config.publish()
|
||||
|
||||
const res = await request
|
||||
.post(`/api/webhooks/trigger/${config.prodAppId}/${webhook._id}`)
|
||||
|
|
|
@ -102,6 +102,16 @@ export interface components {
|
|||
lockedBy?: { [key: string]: unknown };
|
||||
};
|
||||
};
|
||||
deploymentOutput: {
|
||||
data: {
|
||||
/** @description The ID of the deployment. */
|
||||
_id: string;
|
||||
/** @description The status of the deployment. */
|
||||
status: "SUCCESS" | "FAILURE";
|
||||
/** @description The URL by which the published app is accessed. */
|
||||
appUrl?: string;
|
||||
}
|
||||
};
|
||||
applicationSearch: {
|
||||
data: {
|
||||
/** @description The name of the app. */
|
||||
|
|
|
@ -107,7 +107,7 @@ const environment = {
|
|||
}
|
||||
|
||||
// threading can cause memory issues with node-ts in development
|
||||
if (isDev() && module.exports.DISABLE_THREADING == null) {
|
||||
if (isDev() && environment.DISABLE_THREADING == null) {
|
||||
environment._set("DISABLE_THREADING", "1")
|
||||
}
|
||||
|
||||
|
|
|
@ -284,7 +284,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|||
async createTable(name?: string) {
|
||||
try {
|
||||
await this.connect()
|
||||
return await this.client.addSheet({ title: name })
|
||||
return await this.client.addSheet({ title: name, headerValues: ["test"] })
|
||||
} catch (err) {
|
||||
console.error("Error creating new table in google sheets", err)
|
||||
throw err
|
||||
|
|
|
@ -360,7 +360,6 @@ class TestConfiguration {
|
|||
}
|
||||
|
||||
// APP
|
||||
|
||||
async createApp(appName: string) {
|
||||
// create dev app
|
||||
// clear any old app
|
||||
|
@ -373,7 +372,7 @@ class TestConfiguration {
|
|||
await context.updateAppId(this.appId)
|
||||
|
||||
// create production app
|
||||
this.prodApp = await this.deploy()
|
||||
this.prodApp = await this.publish()
|
||||
|
||||
this.allApps.push(this.prodApp)
|
||||
this.allApps.push(this.app)
|
||||
|
@ -381,8 +380,8 @@ class TestConfiguration {
|
|||
return this.app
|
||||
}
|
||||
|
||||
async deploy() {
|
||||
await this._req(null, null, controllers.deploy.deployApp)
|
||||
async publish() {
|
||||
await this._req(null, null, controllers.deploy.publishApp)
|
||||
// @ts-ignore
|
||||
const prodAppId = this.getAppId().replace("_dev", "")
|
||||
this.prodAppId = prodAppId
|
||||
|
@ -393,6 +392,17 @@ class TestConfiguration {
|
|||
})
|
||||
}
|
||||
|
||||
async unpublish() {
|
||||
const response = await this._req(
|
||||
null,
|
||||
{ appId: this.appId },
|
||||
controllers.app.unpublish
|
||||
)
|
||||
this.prodAppId = null
|
||||
this.prodApp = null
|
||||
return response
|
||||
}
|
||||
|
||||
// TABLE
|
||||
|
||||
async updateTable(config?: any) {
|
||||
|
|
|
@ -47,4 +47,4 @@
|
|||
"typescript": "4.7.3"
|
||||
},
|
||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
||||
}
|
||||
}
|
|
@ -14,12 +14,12 @@
|
|||
"jest": {},
|
||||
"devDependencies": {
|
||||
"@budibase/nano": "10.1.1",
|
||||
"@types/formidable": "^1.0.31",
|
||||
"@types/json5": "2.2.0",
|
||||
"@types/koa": "2.13.4",
|
||||
"@types/node": "14.18.20",
|
||||
"@types/pouchdb": "6.4.0",
|
||||
"koa-body": "4.2.0",
|
||||
"rimraf": "3.0.2",
|
||||
"typescript": "4.7.3"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
import {
|
||||
Feature,
|
||||
Hosting,
|
||||
License,
|
||||
MonthlyQuotaName,
|
||||
PlanType,
|
||||
PriceDuration,
|
||||
Quotas,
|
||||
StaticQuotaName,
|
||||
} from "../../sdk"
|
||||
|
@ -46,6 +48,7 @@ export interface Account extends CreateAccount {
|
|||
tier: string // deprecated
|
||||
planType?: PlanType
|
||||
planTier?: number
|
||||
license?: License
|
||||
stripeCustomerId?: string
|
||||
licenseKey?: string
|
||||
licenseKeyActivatedAt?: number
|
||||
|
|
|
@ -4,11 +4,14 @@ export enum LockType {
|
|||
* No retries will take place and no error will be thrown.
|
||||
*/
|
||||
TRY_ONCE = "try_once",
|
||||
DEFAULT = "default",
|
||||
DELAY_500 = "delay_500",
|
||||
}
|
||||
|
||||
export enum LockName {
|
||||
MIGRATIONS = "migrations",
|
||||
TRIGGER_QUOTA = "trigger_quota",
|
||||
SYNC_ACCOUNT_LICENSE = "sync_account_license",
|
||||
}
|
||||
|
||||
export interface LockOptions {
|
||||
|
|
|
@ -364,11 +364,6 @@ brace-expansion@^1.1.7:
|
|||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
bytes@3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
|
||||
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
|
||||
|
||||
call-bind@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
|
||||
|
@ -377,16 +372,6 @@ call-bind@^1.0.0:
|
|||
function-bind "^1.1.1"
|
||||
get-intrinsic "^1.0.2"
|
||||
|
||||
co-body@^5.1.1:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/co-body/-/co-body-5.2.0.tgz#5a0a658c46029131e0e3a306f67647302f71c124"
|
||||
integrity sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==
|
||||
dependencies:
|
||||
inflation "^2.0.0"
|
||||
qs "^6.4.0"
|
||||
raw-body "^2.2.0"
|
||||
type-is "^1.6.14"
|
||||
|
||||
combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
|
@ -411,11 +396,6 @@ delayed-stream@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||
|
||||
depd@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||
|
||||
follow-redirects@^1.15.0:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||
|
@ -430,11 +410,6 @@ form-data@^4.0.0:
|
|||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
formidable@^1.1.1:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168"
|
||||
integrity sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
|
@ -485,29 +460,6 @@ http-cookie-agent@^4.0.2:
|
|||
dependencies:
|
||||
agent-base "^6.0.2"
|
||||
|
||||
http-errors@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
|
||||
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
|
||||
dependencies:
|
||||
depd "2.0.0"
|
||||
inherits "2.0.4"
|
||||
setprototypeof "1.2.0"
|
||||
statuses "2.0.1"
|
||||
toidentifier "1.0.1"
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
inflation@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/inflation/-/inflation-2.0.0.tgz#8b417e47c28f925a45133d914ca1fd389107f30f"
|
||||
integrity sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw==
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
|
@ -516,7 +468,7 @@ inflight@^1.0.4:
|
|||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@2.0.4:
|
||||
inherits@2:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
@ -526,26 +478,12 @@ json5@*:
|
|||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
|
||||
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
|
||||
|
||||
koa-body@4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.2.0.tgz#37229208b820761aca5822d14c5fc55cee31b26f"
|
||||
integrity sha512-wdGu7b9amk4Fnk/ytH8GuWwfs4fsB5iNkY8kZPpgQVb04QZSv85T0M8reb+cJmvLE8cjPYvBzRikD3s6qz8OoA==
|
||||
dependencies:
|
||||
"@types/formidable" "^1.0.31"
|
||||
co-body "^5.1.1"
|
||||
formidable "^1.1.1"
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.24:
|
||||
mime-types@^2.1.12:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
|
@ -601,7 +539,7 @@ punycode@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
qs@^6.11.0, qs@^6.4.0:
|
||||
qs@^6.11.0:
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
|
||||
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
|
||||
|
@ -613,16 +551,6 @@ querystringify@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||
|
||||
raw-body@^2.2.0:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
|
||||
integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
|
||||
dependencies:
|
||||
bytes "3.1.2"
|
||||
http-errors "2.0.0"
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
requires-port@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
|
@ -635,16 +563,6 @@ rimraf@3.0.2:
|
|||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
setprototypeof@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
|
||||
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
|
||||
|
||||
side-channel@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||
|
@ -654,16 +572,6 @@ side-channel@^1.0.4:
|
|||
get-intrinsic "^1.0.2"
|
||||
object-inspect "^1.9.0"
|
||||
|
||||
statuses@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
|
||||
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
|
||||
|
||||
toidentifier@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
|
||||
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
|
||||
|
||||
tough-cookie@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874"
|
||||
|
@ -674,14 +582,6 @@ tough-cookie@^4.1.2:
|
|||
universalify "^0.2.0"
|
||||
url-parse "^1.5.3"
|
||||
|
||||
type-is@^1.6.14:
|
||||
version "1.6.18"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||
dependencies:
|
||||
media-typer "0.3.0"
|
||||
mime-types "~2.1.24"
|
||||
|
||||
typescript@4.7.3:
|
||||
version "4.7.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
|
||||
|
@ -692,11 +592,6 @@ universalify@^0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
|
||||
|
||||
unpipe@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
|
||||
|
||||
url-parse@^1.5.3:
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
|
||||
|
|
|
@ -96,4 +96,4 @@
|
|||
"update-dotenv": "1.1.1"
|
||||
},
|
||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
||||
}
|
||||
}
|
|
@ -305,7 +305,7 @@ export async function upload(ctx: UserCtx) {
|
|||
if (ctx.request.files == null || Array.isArray(ctx.request.files.file)) {
|
||||
ctx.throw(400, "One file must be uploaded.")
|
||||
}
|
||||
const file = ctx.request.files.file
|
||||
const file = ctx.request.files.file as any
|
||||
const { type, name } = ctx.params
|
||||
|
||||
let bucket = coreEnv.GLOBAL_BUCKET_NAME
|
||||
|
|
|
@ -47,15 +47,10 @@ export default class AppApi {
|
|||
return [response, json]
|
||||
}
|
||||
|
||||
async publish(appUrl: string): Promise<[Response, DeployConfig]> {
|
||||
const response = await this.api.post("/deploy")
|
||||
async publish(appId: string | undefined): Promise<[Response, DeployConfig]> {
|
||||
const response = await this.api.post(`/applications/${appId}/publish`)
|
||||
const json = await response.json()
|
||||
expect(response).toHaveStatusCode(200)
|
||||
expect(json).toEqual({
|
||||
_id: expect.any(String),
|
||||
appUrl: appUrl,
|
||||
status: "SUCCESS",
|
||||
})
|
||||
return [response, json]
|
||||
}
|
||||
|
||||
|
@ -152,13 +147,9 @@ export default class AppApi {
|
|||
return [response, json]
|
||||
}
|
||||
|
||||
async unpublish(appId: string): Promise<[Response, UnpublishAppResponse]> {
|
||||
const response = await this.api.del(`/applications/${appId}?unpublish=1`)
|
||||
expect(response).toHaveStatusCode(200)
|
||||
const json = await response.json()
|
||||
expect(json.data.ok).toBe(true)
|
||||
expect(json.ok).toBe(true)
|
||||
expect(json.status).toBe(200)
|
||||
return [response, json]
|
||||
async unpublish(appId: string): Promise<[Response]> {
|
||||
const response = await this.api.post(`/applications/${appId}/unpublish`)
|
||||
expect(response).toHaveStatusCode(204)
|
||||
return [response]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,4 +46,21 @@ export default class AppApi {
|
|||
const json = await response.json()
|
||||
return [response, json.data]
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<[Response, Application]> {
|
||||
const response = await this.api.del(`/applications/${id}`)
|
||||
const json = await response.json()
|
||||
return [response, json.data]
|
||||
}
|
||||
|
||||
async publish(id: string): Promise<[Response, any]> {
|
||||
const response = await this.api.post(`/applications/${id}/publish`)
|
||||
const json = await response.json()
|
||||
return [response, json.data]
|
||||
}
|
||||
|
||||
async unpublish(id: string): Promise<[Response]> {
|
||||
const response = await this.api.post(`/applications/${id}/unpublish`)
|
||||
return [response]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ describe("Internal API - Application creation, update, publish and delete", () =
|
|||
await config.applications.canRender()
|
||||
|
||||
// publish app
|
||||
await config.applications.publish(<string>app.url)
|
||||
await config.applications.publish(<string>app.appId)
|
||||
|
||||
// check published app renders
|
||||
config.applications.api.appId = db.getProdAppID(app.appId!)
|
||||
|
@ -94,7 +94,7 @@ describe("Internal API - Application creation, update, publish and delete", () =
|
|||
config.applications.api.appId = app.appId
|
||||
|
||||
// publish app
|
||||
await config.applications.publish(<string>app.url)
|
||||
await config.applications.publish(<string>app._id)
|
||||
|
||||
const [syncResponse, sync] = await config.applications.sync(
|
||||
<string>app.appId
|
||||
|
@ -126,7 +126,7 @@ describe("Internal API - Application creation, update, publish and delete", () =
|
|||
config.applications.api.appId = app.appId
|
||||
|
||||
// publish app
|
||||
await config.applications.publish(<string>app.url)
|
||||
await config.applications.publish(<string>app._id)
|
||||
|
||||
// Change/add component to the app
|
||||
await config.screen.create(generateScreen("BASIC"))
|
||||
|
|
|
@ -2,6 +2,7 @@ import TestConfiguration from "../../../config/public-api/TestConfiguration"
|
|||
import PublicAPIClient from "../../../config/public-api/TestConfiguration/PublicAPIClient"
|
||||
import generateApp from "../../../config/public-api/fixtures/applications"
|
||||
import { Application } from "@budibase/server/api/controllers/public/mapping/types"
|
||||
import { db as dbCore } from "@budibase/backend-core"
|
||||
|
||||
describe("Public API - /applications endpoints", () => {
|
||||
const api = new PublicAPIClient()
|
||||
|
@ -47,4 +48,50 @@ describe("Public API - /applications endpoints", () => {
|
|||
expect(app.updatedAt).not.toEqual(config.context.updatedAt)
|
||||
expect(app.name).toEqual(config.context.name)
|
||||
})
|
||||
|
||||
it("POST - publish an application", async () => {
|
||||
config.context.name = "UpdatedName"
|
||||
const [response, deployment] = await config.applications.publish(
|
||||
config.context._id
|
||||
)
|
||||
expect(response).toHaveStatusCode(200)
|
||||
expect(deployment).toEqual({
|
||||
status: "SUCCESS",
|
||||
})
|
||||
|
||||
// Verify publish
|
||||
const prodAppId = dbCore.getProdAppID(config.context._id)
|
||||
const [_, publishedApp] = await config.applications.read(prodAppId)
|
||||
expect(response).toHaveStatusCode(200)
|
||||
expect(publishedApp._id).toEqual(prodAppId)
|
||||
})
|
||||
|
||||
it("POST - unpublish a published application", async () => {
|
||||
await config.applications.publish(config.context._id)
|
||||
const [response] = await config.applications.unpublish(config.context._id)
|
||||
expect(response).toHaveStatusCode(204)
|
||||
})
|
||||
|
||||
it("POST - unpublish an unpublished application", async () => {
|
||||
const [response] = await config.applications.unpublish(
|
||||
config.context._id
|
||||
)
|
||||
expect(response).toHaveStatusCode(400)
|
||||
})
|
||||
|
||||
it("DELETE - delete a published application and the dev application", async () => {
|
||||
await config.applications.publish(config.context._id)
|
||||
const [response, deletion] = await config.applications.delete(config.context._id)
|
||||
expect(response).toHaveStatusCode(200)
|
||||
expect(deletion._id).toEqual(config.context._id)
|
||||
|
||||
// verify dev app deleted
|
||||
const [devAppResponse] = await config.applications.read(config.context._id)
|
||||
expect(devAppResponse).toHaveStatusCode(404)
|
||||
|
||||
// verify prod app deleted
|
||||
const prodAppId = dbCore.getProdAppID(config.context._id)
|
||||
const [publishedAppResponse] = await config.applications.read(prodAppId)
|
||||
expect(publishedAppResponse).toHaveStatusCode(404)
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue