Self Host <-> Licensing integration
This commit is contained in:
parent
eefe4ea2ad
commit
7698e9293c
|
@ -22,25 +22,3 @@ exports.getAccount = async email => {
|
||||||
|
|
||||||
return json[0]
|
return json[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replace with licensing key
|
|
||||||
exports.getLicense = async tenantId => {
|
|
||||||
const response = await api.get(`/api/license/${tenantId}`, {
|
|
||||||
headers: {
|
|
||||||
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.status === 404) {
|
|
||||||
// no license for the tenant
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status !== 200) {
|
|
||||||
const text = await response.text()
|
|
||||||
console.error("Error getting license: ", text)
|
|
||||||
throw new Error(`Error getting license for tenant ${tenantId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.json()
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ exports.Cookies = {
|
||||||
|
|
||||||
exports.Headers = {
|
exports.Headers = {
|
||||||
API_KEY: "x-budibase-api-key",
|
API_KEY: "x-budibase-api-key",
|
||||||
|
LICENSE_KEY: "x-budibase-license-key",
|
||||||
API_VER: "x-budibase-api-version",
|
API_VER: "x-budibase-api-version",
|
||||||
APP_ID: "x-budibase-app-id",
|
APP_ID: "x-budibase-app-id",
|
||||||
TYPE: "x-budibase-type",
|
TYPE: "x-budibase-type",
|
||||||
|
|
|
@ -22,6 +22,7 @@ exports.StaticDatabases = {
|
||||||
docs: {
|
docs: {
|
||||||
apiKeys: "apikeys",
|
apiKeys: "apikeys",
|
||||||
usageQuota: "usage_quota",
|
usageQuota: "usage_quota",
|
||||||
|
licenseInfo: "license_info",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// contains information about tenancy and so on
|
// contains information about tenancy and so on
|
||||||
|
|
|
@ -55,6 +55,10 @@
|
||||||
title: "Updates",
|
title: "Updates",
|
||||||
href: "/builder/portal/settings/update",
|
href: "/builder/portal/settings/update",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Upgrade",
|
||||||
|
href: "/builder/portal/settings/upgrade",
|
||||||
|
},
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Layout,
|
||||||
|
Heading,
|
||||||
|
Body,
|
||||||
|
Divider,
|
||||||
|
Link,
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Label,
|
||||||
|
notifications,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { auth, admin } from "stores/portal"
|
||||||
|
import { redirect } from "@roxi/routify"
|
||||||
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
|
import { API } from "api"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
$: license = $auth.user.license
|
||||||
|
$: upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade`
|
||||||
|
|
||||||
|
$: activateDisabled = !licenseKey || licenseKeyDisabled
|
||||||
|
|
||||||
|
let licenseInfo
|
||||||
|
|
||||||
|
let licenseKeyDisabled = false
|
||||||
|
let licenseKeyType = "text"
|
||||||
|
let licenseKey = ""
|
||||||
|
|
||||||
|
// Make sure page can't be visited directly in cloud
|
||||||
|
$: {
|
||||||
|
if ($admin.cloud) {
|
||||||
|
$redirect("../../portal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const activate = async () => {
|
||||||
|
await API.activateLicenseKey({ licenseKey })
|
||||||
|
await auth.getSelf()
|
||||||
|
await setLicenseInfo()
|
||||||
|
notifications.success("Successfully activated")
|
||||||
|
}
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
try {
|
||||||
|
await API.refreshLicense()
|
||||||
|
await auth.getSelf()
|
||||||
|
notifications.success("Refreshed license")
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
notifications.error("Error refreshing license")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deactivate the license key field if there is a license key set
|
||||||
|
$: {
|
||||||
|
if (licenseInfo?.licenseKey) {
|
||||||
|
licenseKey = "**********************************************"
|
||||||
|
licenseKeyType = "password"
|
||||||
|
licenseKeyDisabled = true
|
||||||
|
activateDisabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setLicenseInfo = async () => {
|
||||||
|
licenseInfo = await API.getLicenseInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await setLicenseInfo()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $auth.isAdmin}
|
||||||
|
<Layout noPadding>
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading size="M">Upgrade</Heading>
|
||||||
|
<Body size="M">
|
||||||
|
{#if license.plan.type === "free"}
|
||||||
|
Upgrade your budibase installation to unlock additional features. To
|
||||||
|
subscribe to a plan visit your <Link size="L" href={upgradeUrl}
|
||||||
|
>Account</Link
|
||||||
|
>.
|
||||||
|
{:else}
|
||||||
|
To manage your plan visit your <Link size="L" href={upgradeUrl}
|
||||||
|
>Account</Link
|
||||||
|
>.
|
||||||
|
{/if}
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
<Divider size="S" />
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading size="S">Activate</Heading>
|
||||||
|
<Body size="S">Enter your license key below to activate your plan</Body>
|
||||||
|
</Layout>
|
||||||
|
<Layout noPadding>
|
||||||
|
<div class="fields">
|
||||||
|
<div class="field">
|
||||||
|
<Label size="L">License Key</Label>
|
||||||
|
<Input
|
||||||
|
thin
|
||||||
|
bind:value={licenseKey}
|
||||||
|
type={licenseKeyType}
|
||||||
|
disabled={licenseKeyDisabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button cta on:click={activate} disabled={activateDisabled}
|
||||||
|
>Activate</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
<Divider size="S" />
|
||||||
|
<Layout gap="L" noPadding>
|
||||||
|
<Layout gap="S" noPadding>
|
||||||
|
<Heading size="S">Plan</Heading>
|
||||||
|
<Layout noPadding gap="XXS">
|
||||||
|
<Body size="S">You are currently on the {license.plan.type} plan</Body
|
||||||
|
>
|
||||||
|
<Body size="XS">
|
||||||
|
{processStringSync(
|
||||||
|
"Updated {{ duration time 'millisecond' }} ago",
|
||||||
|
{
|
||||||
|
time:
|
||||||
|
new Date().getTime() -
|
||||||
|
new Date(license.refreshedAt).getTime(),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
<div>
|
||||||
|
<Button secondary on:click={refresh}>Refresh</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fields {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.field {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 100px 1fr;
|
||||||
|
grid-gap: var(--spacing-l);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -21,6 +21,7 @@ import { buildTableEndpoints } from "./tables"
|
||||||
import { buildTemplateEndpoints } from "./templates"
|
import { buildTemplateEndpoints } from "./templates"
|
||||||
import { buildUserEndpoints } from "./user"
|
import { buildUserEndpoints } from "./user"
|
||||||
import { buildViewEndpoints } from "./views"
|
import { buildViewEndpoints } from "./views"
|
||||||
|
import { buildLicensingEndpoints } from "./licensing"
|
||||||
|
|
||||||
const defaultAPIClientConfig = {
|
const defaultAPIClientConfig = {
|
||||||
/**
|
/**
|
||||||
|
@ -231,5 +232,6 @@ export const createAPIClient = config => {
|
||||||
...buildTemplateEndpoints(API),
|
...buildTemplateEndpoints(API),
|
||||||
...buildUserEndpoints(API),
|
...buildUserEndpoints(API),
|
||||||
...buildViewEndpoints(API),
|
...buildViewEndpoints(API),
|
||||||
|
...buildLicensingEndpoints(API),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
export const buildLicensingEndpoints = API => ({
|
||||||
|
/**
|
||||||
|
* Activates a self hosted license key
|
||||||
|
*/
|
||||||
|
activateLicenseKey: async data => {
|
||||||
|
return API.post({
|
||||||
|
url: `/api/global/license/activate`,
|
||||||
|
body: data,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the license info - metadata about the license including the
|
||||||
|
* obfuscated license key.
|
||||||
|
*/
|
||||||
|
getLicenseInfo: async () => {
|
||||||
|
return API.get({
|
||||||
|
url: "/api/global/license/info",
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the license cache
|
||||||
|
*/
|
||||||
|
refreshLicense: async () => {
|
||||||
|
return API.post({
|
||||||
|
url: "/api/global/license/refresh",
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
|
@ -148,6 +148,7 @@
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
"@types/koa": "^2.13.3",
|
"@types/koa": "^2.13.3",
|
||||||
"@types/koa-router": "^7.4.2",
|
"@types/koa-router": "^7.4.2",
|
||||||
|
"@types/lodash": "^4.14.179",
|
||||||
"@types/node": "^15.12.4",
|
"@types/node": "^15.12.4",
|
||||||
"@types/oracledb": "^5.2.1",
|
"@types/oracledb": "^5.2.1",
|
||||||
"@typescript-eslint/parser": "5.12.0",
|
"@typescript-eslint/parser": "5.12.0",
|
||||||
|
|
|
@ -52,7 +52,7 @@ interface RunConfig {
|
||||||
|
|
||||||
module External {
|
module External {
|
||||||
function buildFilters(
|
function buildFilters(
|
||||||
id: string | undefined,
|
id: string | undefined | string[],
|
||||||
filters: SearchFilters,
|
filters: SearchFilters,
|
||||||
table: Table
|
table: Table
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
const linkRows = require("../../../db/linkedRows")
|
import { updateLinks, EventType } from "../../../db/linkedRows"
|
||||||
const { getRowParams, generateTableID } = require("../../../db/utils")
|
import { getRowParams, generateTableID } from "../../../db/utils"
|
||||||
const { FieldTypes } = require("../../../constants")
|
import { FieldTypes } from "../../../constants"
|
||||||
const {
|
import {
|
||||||
TableSaveFunctions,
|
TableSaveFunctions,
|
||||||
hasTypeChanged,
|
hasTypeChanged,
|
||||||
getTable,
|
getTable,
|
||||||
handleDataImport,
|
handleDataImport,
|
||||||
} = require("./utils")
|
} from "./utils"
|
||||||
const { quotas, StaticQuotaName, QuotaUsageType } = require("@budibase/pro")
|
|
||||||
const { getAppDB } = require("@budibase/backend-core/context")
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
const env = require("../../../environment")
|
import { isTest } from "../../../environment"
|
||||||
const { cleanupAttachments } = require("../../../utilities/rowProcessor")
|
import { cleanupAttachments } from "../../../utilities/rowProcessor"
|
||||||
const { runStaticFormulaChecks } = require("./bulkFormula")
|
import { runStaticFormulaChecks } from "./bulkFormula"
|
||||||
|
import * as Pro from "@budibase/pro"
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
export async function save(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const { dataImport, ...rest } = ctx.request.body
|
const { dataImport, ...rest } = ctx.request.body
|
||||||
let tableToSave = {
|
let tableToSave = {
|
||||||
|
@ -80,10 +80,8 @@ exports.save = async function (ctx) {
|
||||||
|
|
||||||
// update linked rows
|
// update linked rows
|
||||||
try {
|
try {
|
||||||
const linkResp = await linkRows.updateLinks({
|
const linkResp: any = await updateLinks({
|
||||||
eventType: oldTable
|
eventType: oldTable ? EventType.TABLE_UPDATED : EventType.TABLE_SAVE,
|
||||||
? linkRows.EventType.TABLE_UPDATED
|
|
||||||
: linkRows.EventType.TABLE_SAVE,
|
|
||||||
table: tableToSave,
|
table: tableToSave,
|
||||||
oldTable: oldTable,
|
oldTable: oldTable,
|
||||||
})
|
})
|
||||||
|
@ -105,11 +103,11 @@ exports.save = async function (ctx) {
|
||||||
|
|
||||||
tableToSave = await tableSaveFunctions.after(tableToSave)
|
tableToSave = await tableSaveFunctions.after(tableToSave)
|
||||||
// has to run after, make sure it has _id
|
// has to run after, make sure it has _id
|
||||||
await runStaticFormulaChecks(tableToSave, { oldTable })
|
await runStaticFormulaChecks(tableToSave, { oldTable, deletion: null })
|
||||||
return tableToSave
|
return tableToSave
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function (ctx) {
|
export async function destroy(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const tableToDelete = await db.get(ctx.params.tableId)
|
const tableToDelete = await db.get(ctx.params.tableId)
|
||||||
|
|
||||||
|
@ -119,16 +117,18 @@ exports.destroy = async function (ctx) {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
await db.bulkDocs(rows.rows.map(row => ({ ...row.doc, _deleted: true })))
|
await db.bulkDocs(
|
||||||
await quotas.updateUsage(
|
rows.rows.map((row: any) => ({ ...row.doc, _deleted: true }))
|
||||||
|
)
|
||||||
|
await Pro.Licensing.Quotas.updateUsage(
|
||||||
-rows.rows.length,
|
-rows.rows.length,
|
||||||
StaticQuotaName.ROWS,
|
Pro.StaticQuotaName.ROWS,
|
||||||
QuotaUsageType.STATIC
|
Pro.QuotaUsageType.STATIC
|
||||||
)
|
)
|
||||||
|
|
||||||
// update linked rows
|
// update linked rows
|
||||||
await linkRows.updateLinks({
|
await updateLinks({
|
||||||
eventType: linkRows.EventType.TABLE_DELETE,
|
eventType: EventType.TABLE_DELETE,
|
||||||
table: tableToDelete,
|
table: tableToDelete,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -136,10 +136,10 @@ exports.destroy = async function (ctx) {
|
||||||
await db.remove(tableToDelete)
|
await db.remove(tableToDelete)
|
||||||
|
|
||||||
// remove table search index
|
// remove table search index
|
||||||
if (!env.isTest()) {
|
if (!isTest()) {
|
||||||
const currentIndexes = await db.getIndexes()
|
const currentIndexes = await db.getIndexes()
|
||||||
const existingIndex = currentIndexes.indexes.find(
|
const existingIndex = currentIndexes.indexes.find(
|
||||||
existing => existing.name === `search:${ctx.params.tableId}`
|
(existing: any) => existing.name === `search:${ctx.params.tableId}`
|
||||||
)
|
)
|
||||||
if (existingIndex) {
|
if (existingIndex) {
|
||||||
await db.deleteIndex(existingIndex)
|
await db.deleteIndex(existingIndex)
|
||||||
|
@ -147,12 +147,15 @@ exports.destroy = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// has to run after, make sure it has _id
|
// has to run after, make sure it has _id
|
||||||
await runStaticFormulaChecks(tableToDelete, { deletion: true })
|
await runStaticFormulaChecks(tableToDelete, {
|
||||||
|
oldTable: null,
|
||||||
|
deletion: true,
|
||||||
|
})
|
||||||
await cleanupAttachments(tableToDelete, { rows })
|
await cleanupAttachments(tableToDelete, { rows })
|
||||||
return tableToDelete
|
return tableToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.bulkImport = async function (ctx) {
|
export async function bulkImport(ctx: any) {
|
||||||
const table = await getTable(ctx.params.tableId)
|
const table = await getTable(ctx.params.tableId)
|
||||||
const { dataImport } = ctx.request.body
|
const { dataImport } = ctx.request.body
|
||||||
await handleDataImport(ctx.user, table, dataImport)
|
await handleDataImport(ctx.user, table, dataImport)
|
|
@ -1,34 +1,34 @@
|
||||||
const csvParser = require("../../../utilities/csvParser")
|
import { transform } from "../../../utilities/csvParser"
|
||||||
const {
|
import {
|
||||||
getRowParams,
|
getRowParams,
|
||||||
generateRowID,
|
generateRowID,
|
||||||
InternalTables,
|
InternalTables,
|
||||||
getTableParams,
|
getTableParams,
|
||||||
BudibaseInternalDB,
|
BudibaseInternalDB,
|
||||||
} = require("../../../db/utils")
|
} from "../../../db/utils"
|
||||||
const { isEqual } = require("lodash")
|
import { isEqual } from "lodash"
|
||||||
const { AutoFieldSubTypes, FieldTypes } = require("../../../constants")
|
import { AutoFieldSubTypes, FieldTypes } from "../../../constants"
|
||||||
const {
|
import {
|
||||||
inputProcessing,
|
inputProcessing,
|
||||||
cleanupAttachments,
|
cleanupAttachments,
|
||||||
} = require("../../../utilities/rowProcessor")
|
} from "../../../utilities/rowProcessor"
|
||||||
const {
|
import {
|
||||||
USERS_TABLE_SCHEMA,
|
USERS_TABLE_SCHEMA,
|
||||||
SwitchableTypes,
|
SwitchableTypes,
|
||||||
CanSwitchTypes,
|
CanSwitchTypes,
|
||||||
} = require("../../../constants")
|
} from "../../../constants"
|
||||||
const {
|
import {
|
||||||
isExternalTable,
|
isExternalTable,
|
||||||
breakExternalTableId,
|
breakExternalTableId,
|
||||||
isSQL,
|
isSQL,
|
||||||
} = require("../../../integrations/utils")
|
} from "../../../integrations/utils"
|
||||||
const { getViews, saveView } = require("../view/utils")
|
import { getViews, saveView } from "../view/utils"
|
||||||
const viewTemplate = require("../view/viewBuilder")
|
import viewTemplate from "../view/viewBuilder"
|
||||||
const { quotas, StaticQuotaName, QuotaUsageType } = require("@budibase/pro")
|
|
||||||
const { getAppDB } = require("@budibase/backend-core/context")
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import * as Pro from "@budibase/pro"
|
||||||
|
|
||||||
exports.clearColumns = async (table, columnNames) => {
|
export async function clearColumns(table: any, columnNames: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const rows = await db.allDocs(
|
const rows = await db.allDocs(
|
||||||
getRowParams(table._id, null, {
|
getRowParams(table._id, null, {
|
||||||
|
@ -36,18 +36,18 @@ exports.clearColumns = async (table, columnNames) => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
return db.bulkDocs(
|
return db.bulkDocs(
|
||||||
rows.rows.map(({ doc }) => {
|
rows.rows.map(({ doc }: any) => {
|
||||||
columnNames.forEach(colName => delete doc[colName])
|
columnNames.forEach((colName: any) => delete doc[colName])
|
||||||
return doc
|
return doc
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.checkForColumnUpdates = async (oldTable, updatedTable) => {
|
export async function checkForColumnUpdates(oldTable: any, updatedTable: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
let updatedRows = []
|
let updatedRows = []
|
||||||
const rename = updatedTable._rename
|
const rename = updatedTable._rename
|
||||||
let deletedColumns = []
|
let deletedColumns: any = []
|
||||||
if (oldTable && oldTable.schema && updatedTable.schema) {
|
if (oldTable && oldTable.schema && updatedTable.schema) {
|
||||||
deletedColumns = Object.keys(oldTable.schema).filter(
|
deletedColumns = Object.keys(oldTable.schema).filter(
|
||||||
colName => updatedTable.schema[colName] == null
|
colName => updatedTable.schema[colName] == null
|
||||||
|
@ -61,14 +61,14 @@ exports.checkForColumnUpdates = async (oldTable, updatedTable) => {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const rawRows = rows.rows.map(({ doc }) => doc)
|
const rawRows = rows.rows.map(({ doc }: any) => doc)
|
||||||
updatedRows = rawRows.map(row => {
|
updatedRows = rawRows.map((row: any) => {
|
||||||
row = cloneDeep(row)
|
row = cloneDeep(row)
|
||||||
if (rename) {
|
if (rename) {
|
||||||
row[rename.updated] = row[rename.old]
|
row[rename.updated] = row[rename.old]
|
||||||
delete row[rename.old]
|
delete row[rename.old]
|
||||||
} else if (deletedColumns.length !== 0) {
|
} else if (deletedColumns.length !== 0) {
|
||||||
deletedColumns.forEach(colName => delete row[colName])
|
deletedColumns.forEach((colName: any) => delete row[colName])
|
||||||
}
|
}
|
||||||
return row
|
return row
|
||||||
})
|
})
|
||||||
|
@ -76,14 +76,14 @@ exports.checkForColumnUpdates = async (oldTable, updatedTable) => {
|
||||||
// cleanup any attachments from object storage for deleted attachment columns
|
// cleanup any attachments from object storage for deleted attachment columns
|
||||||
await cleanupAttachments(updatedTable, { oldTable, rows: rawRows })
|
await cleanupAttachments(updatedTable, { oldTable, rows: rawRows })
|
||||||
// Update views
|
// Update views
|
||||||
await exports.checkForViewUpdates(updatedTable, rename, deletedColumns)
|
await checkForViewUpdates(updatedTable, rename, deletedColumns)
|
||||||
delete updatedTable._rename
|
delete updatedTable._rename
|
||||||
}
|
}
|
||||||
return { rows: updatedRows, table: updatedTable }
|
return { rows: updatedRows, table: updatedTable }
|
||||||
}
|
}
|
||||||
|
|
||||||
// makes sure the passed in table isn't going to reset the auto ID
|
// makes sure the passed in table isn't going to reset the auto ID
|
||||||
exports.makeSureTableUpToDate = (table, tableToSave) => {
|
export function makeSureTableUpToDate(table: any, tableToSave: any) {
|
||||||
if (!table) {
|
if (!table) {
|
||||||
return tableToSave
|
return tableToSave
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,9 @@ exports.makeSureTableUpToDate = (table, tableToSave) => {
|
||||||
tableToSave._rev = table._rev
|
tableToSave._rev = table._rev
|
||||||
// make sure auto IDs are always updated - these are internal
|
// make sure auto IDs are always updated - these are internal
|
||||||
// so the client may not know they have changed
|
// so the client may not know they have changed
|
||||||
for (let [field, column] of Object.entries(table.schema)) {
|
let field: any
|
||||||
|
let column: any
|
||||||
|
for ([field, column] of Object.entries(table.schema)) {
|
||||||
if (
|
if (
|
||||||
column.autocolumn &&
|
column.autocolumn &&
|
||||||
column.subtype === AutoFieldSubTypes.AUTO_ID &&
|
column.subtype === AutoFieldSubTypes.AUTO_ID &&
|
||||||
|
@ -103,14 +105,14 @@ exports.makeSureTableUpToDate = (table, tableToSave) => {
|
||||||
return tableToSave
|
return tableToSave
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.handleDataImport = async (user, table, dataImport) => {
|
export async function handleDataImport(user: any, table: any, dataImport: any) {
|
||||||
if (!dataImport || !dataImport.csvString) {
|
if (!dataImport || !dataImport.csvString) {
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
// Populate the table with rows imported from CSV in a bulk update
|
// Populate the table with rows imported from CSV in a bulk update
|
||||||
const data = await csvParser.transform({
|
const data = await transform({
|
||||||
...dataImport,
|
...dataImport,
|
||||||
existingTable: table,
|
existingTable: table,
|
||||||
})
|
})
|
||||||
|
@ -120,13 +122,15 @@ exports.handleDataImport = async (user, table, dataImport) => {
|
||||||
let row = data[i]
|
let row = data[i]
|
||||||
row._id = generateRowID(table._id)
|
row._id = generateRowID(table._id)
|
||||||
row.tableId = table._id
|
row.tableId = table._id
|
||||||
const processed = inputProcessing(user, table, row, {
|
const processed: any = inputProcessing(user, table, row, {
|
||||||
noAutoRelationships: true,
|
noAutoRelationships: true,
|
||||||
})
|
})
|
||||||
table = processed.table
|
table = processed.table
|
||||||
row = processed.row
|
row = processed.row
|
||||||
|
|
||||||
for (let [fieldName, schema] of Object.entries(table.schema)) {
|
let fieldName: any
|
||||||
|
let schema: any
|
||||||
|
for ([fieldName, schema] of Object.entries(table.schema)) {
|
||||||
// check whether the options need to be updated for inclusion as part of the data import
|
// check whether the options need to be updated for inclusion as part of the data import
|
||||||
if (
|
if (
|
||||||
schema.type === FieldTypes.OPTIONS &&
|
schema.type === FieldTypes.OPTIONS &&
|
||||||
|
@ -143,26 +147,26 @@ exports.handleDataImport = async (user, table, dataImport) => {
|
||||||
finalData.push(row)
|
finalData.push(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
await quotas.updateUsage(
|
await Pro.Licensing.Quotas.updateUsage(
|
||||||
finalData.length,
|
finalData.length,
|
||||||
StaticQuotaName.ROWS,
|
Pro.StaticQuotaName.ROWS,
|
||||||
QuotaUsageType.STATIC,
|
Pro.QuotaUsageType.STATIC,
|
||||||
{
|
{
|
||||||
dryRun: true,
|
dryRun: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await db.bulkDocs(finalData)
|
await db.bulkDocs(finalData)
|
||||||
await quotas.updateUsage(
|
await Pro.Licensing.Quotas.updateUsage(
|
||||||
finalData.length,
|
finalData.length,
|
||||||
StaticQuotaName.ROWS,
|
Pro.StaticQuotaName.ROWS,
|
||||||
QuotaUsageType.STATIC
|
Pro.QuotaUsageType.STATIC
|
||||||
)
|
)
|
||||||
let response = await db.put(table)
|
let response = await db.put(table)
|
||||||
table._rev = response._rev
|
table._rev = response._rev
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.handleSearchIndexes = async table => {
|
export async function handleSearchIndexes(table: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
// create relevant search indexes
|
// create relevant search indexes
|
||||||
if (table.indexes && table.indexes.length > 0) {
|
if (table.indexes && table.indexes.length > 0) {
|
||||||
|
@ -170,12 +174,12 @@ exports.handleSearchIndexes = async table => {
|
||||||
const indexName = `search:${table._id}`
|
const indexName = `search:${table._id}`
|
||||||
|
|
||||||
const existingIndex = currentIndexes.indexes.find(
|
const existingIndex = currentIndexes.indexes.find(
|
||||||
existing => existing.name === indexName
|
(existing: any) => existing.name === indexName
|
||||||
)
|
)
|
||||||
|
|
||||||
if (existingIndex) {
|
if (existingIndex) {
|
||||||
const currentFields = existingIndex.def.fields.map(
|
const currentFields = existingIndex.def.fields.map(
|
||||||
field => Object.keys(field)[0]
|
(field: any) => Object.keys(field)[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
// if index fields have changed, delete the original index
|
// if index fields have changed, delete the original index
|
||||||
|
@ -206,7 +210,7 @@ exports.handleSearchIndexes = async table => {
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.checkStaticTables = table => {
|
export function checkStaticTables(table: any) {
|
||||||
// check user schema has all required elements
|
// check user schema has all required elements
|
||||||
if (table._id === InternalTables.USER_METADATA) {
|
if (table._id === InternalTables.USER_METADATA) {
|
||||||
for (let [key, schema] of Object.entries(USERS_TABLE_SCHEMA.schema)) {
|
for (let [key, schema] of Object.entries(USERS_TABLE_SCHEMA.schema)) {
|
||||||
|
@ -220,7 +224,13 @@ exports.checkStaticTables = table => {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TableSaveFunctions {
|
class TableSaveFunctions {
|
||||||
constructor({ user, oldTable, dataImport }) {
|
db: any
|
||||||
|
user: any
|
||||||
|
oldTable: any
|
||||||
|
dataImport: any
|
||||||
|
rows: any
|
||||||
|
|
||||||
|
constructor({ user, oldTable, dataImport }: any) {
|
||||||
this.db = getAppDB()
|
this.db = getAppDB()
|
||||||
this.user = user
|
this.user = user
|
||||||
this.oldTable = oldTable
|
this.oldTable = oldTable
|
||||||
|
@ -230,25 +240,25 @@ class TableSaveFunctions {
|
||||||
}
|
}
|
||||||
|
|
||||||
// before anything is done
|
// before anything is done
|
||||||
async before(table) {
|
async before(table: any) {
|
||||||
if (this.oldTable) {
|
if (this.oldTable) {
|
||||||
table = exports.makeSureTableUpToDate(this.oldTable, table)
|
table = makeSureTableUpToDate(this.oldTable, table)
|
||||||
}
|
}
|
||||||
table = exports.checkStaticTables(table)
|
table = checkStaticTables(table)
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
// when confirmed valid
|
// when confirmed valid
|
||||||
async mid(table) {
|
async mid(table: any) {
|
||||||
let response = await exports.checkForColumnUpdates(this.oldTable, table)
|
let response = await checkForColumnUpdates(this.oldTable, table)
|
||||||
this.rows = this.rows.concat(response.rows)
|
this.rows = this.rows.concat(response.rows)
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
// after saving
|
// after saving
|
||||||
async after(table) {
|
async after(table: any) {
|
||||||
table = await exports.handleSearchIndexes(table)
|
table = await handleSearchIndexes(table)
|
||||||
table = await exports.handleDataImport(this.user, table, this.dataImport)
|
table = await handleDataImport(this.user, table, this.dataImport)
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,21 +267,21 @@ class TableSaveFunctions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getAllInternalTables = async () => {
|
export async function getAllInternalTables() {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const internalTables = await db.allDocs(
|
const internalTables = await db.allDocs(
|
||||||
getTableParams(null, {
|
getTableParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
return internalTables.rows.map(tableDoc => ({
|
return internalTables.rows.map((tableDoc: any) => ({
|
||||||
...tableDoc.doc,
|
...tableDoc.doc,
|
||||||
type: "internal",
|
type: "internal",
|
||||||
sourceId: BudibaseInternalDB._id,
|
sourceId: BudibaseInternalDB._id,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getAllExternalTables = async datasourceId => {
|
export async function getAllExternalTables(datasourceId: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const datasource = await db.get(datasourceId)
|
const datasource = await db.get(datasourceId)
|
||||||
if (!datasource || !datasource.entities) {
|
if (!datasource || !datasource.entities) {
|
||||||
|
@ -280,24 +290,28 @@ exports.getAllExternalTables = async datasourceId => {
|
||||||
return datasource.entities
|
return datasource.entities
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getExternalTable = async (datasourceId, tableName) => {
|
export async function getExternalTable(datasourceId: any, tableName: any) {
|
||||||
const entities = await exports.getAllExternalTables(datasourceId)
|
const entities = await getAllExternalTables(datasourceId)
|
||||||
return entities[tableName]
|
return entities[tableName]
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getTable = async tableId => {
|
export async function getTable(tableId: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
if (isExternalTable(tableId)) {
|
if (isExternalTable(tableId)) {
|
||||||
let { datasourceId, tableName } = breakExternalTableId(tableId)
|
let { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||||
const datasource = await db.get(datasourceId)
|
const datasource = await db.get(datasourceId)
|
||||||
const table = await exports.getExternalTable(datasourceId, tableName)
|
const table = await getExternalTable(datasourceId, tableName)
|
||||||
return { ...table, sql: isSQL(datasource) }
|
return { ...table, sql: isSQL(datasource) }
|
||||||
} else {
|
} else {
|
||||||
return db.get(tableId)
|
return db.get(tableId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.checkForViewUpdates = async (table, rename, deletedColumns) => {
|
export async function checkForViewUpdates(
|
||||||
|
table: any,
|
||||||
|
rename: any,
|
||||||
|
deletedColumns: any
|
||||||
|
) {
|
||||||
const views = await getViews()
|
const views = await getViews()
|
||||||
const tableViews = views.filter(view => view.meta.tableId === table._id)
|
const tableViews = views.filter(view => view.meta.tableId === table._id)
|
||||||
|
|
||||||
|
@ -321,7 +335,7 @@ exports.checkForViewUpdates = async (table, rename, deletedColumns) => {
|
||||||
|
|
||||||
// Update filters if required
|
// Update filters if required
|
||||||
if (view.meta.filters) {
|
if (view.meta.filters) {
|
||||||
view.meta.filters.forEach(filter => {
|
view.meta.filters.forEach((filter: any) => {
|
||||||
if (filter.key === rename.old) {
|
if (filter.key === rename.old) {
|
||||||
filter.key = rename.updated
|
filter.key = rename.updated
|
||||||
needsUpdated = true
|
needsUpdated = true
|
||||||
|
@ -329,7 +343,7 @@ exports.checkForViewUpdates = async (table, rename, deletedColumns) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (deletedColumns) {
|
} else if (deletedColumns) {
|
||||||
deletedColumns.forEach(column => {
|
deletedColumns.forEach((column: any) => {
|
||||||
// Remove calculation statement if required
|
// Remove calculation statement if required
|
||||||
if (view.meta.field === column) {
|
if (view.meta.field === column) {
|
||||||
delete view.meta.field
|
delete view.meta.field
|
||||||
|
@ -347,7 +361,7 @@ exports.checkForViewUpdates = async (table, rename, deletedColumns) => {
|
||||||
// Remove filters referencing deleted field if required
|
// Remove filters referencing deleted field if required
|
||||||
if (view.meta.filters && view.meta.filters.length) {
|
if (view.meta.filters && view.meta.filters.length) {
|
||||||
const initialLength = view.meta.filters.length
|
const initialLength = view.meta.filters.length
|
||||||
view.meta.filters = view.meta.filters.filter(filter => {
|
view.meta.filters = view.meta.filters.filter((filter: any) => {
|
||||||
return filter.key !== column
|
return filter.key !== column
|
||||||
})
|
})
|
||||||
if (initialLength !== view.meta.filters.length) {
|
if (initialLength !== view.meta.filters.length) {
|
||||||
|
@ -369,16 +383,20 @@ exports.checkForViewUpdates = async (table, rename, deletedColumns) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.generateForeignKey = (column, relatedTable) => {
|
export function generateForeignKey(column: any, relatedTable: any) {
|
||||||
return `fk_${relatedTable.name}_${column.fieldName}`
|
return `fk_${relatedTable.name}_${column.fieldName}`
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.generateJunctionTableName = (column, table, relatedTable) => {
|
export function generateJunctionTableName(
|
||||||
|
column: any,
|
||||||
|
table: any,
|
||||||
|
relatedTable: any
|
||||||
|
) {
|
||||||
return `jt_${table.name}_${relatedTable.name}_${column.name}_${column.fieldName}`
|
return `jt_${table.name}_${relatedTable.name}_${column.name}_${column.fieldName}`
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.foreignKeyStructure = (keyName, meta = null) => {
|
export function foreignKeyStructure(keyName: any, meta = null) {
|
||||||
const structure = {
|
const structure: any = {
|
||||||
type: FieldTypes.NUMBER,
|
type: FieldTypes.NUMBER,
|
||||||
constraints: {},
|
constraints: {},
|
||||||
name: keyName,
|
name: keyName,
|
||||||
|
@ -389,7 +407,7 @@ exports.foreignKeyStructure = (keyName, meta = null) => {
|
||||||
return structure
|
return structure
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.areSwitchableTypes = (type1, type2) => {
|
export function areSwitchableTypes(type1: any, type2: any) {
|
||||||
if (
|
if (
|
||||||
SwitchableTypes.indexOf(type1) === -1 &&
|
SwitchableTypes.indexOf(type1) === -1 &&
|
||||||
SwitchableTypes.indexOf(type2) === -1
|
SwitchableTypes.indexOf(type2) === -1
|
||||||
|
@ -406,21 +424,24 @@ exports.areSwitchableTypes = (type1, type2) => {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.hasTypeChanged = (table, oldTable) => {
|
export function hasTypeChanged(table: any, oldTable: any) {
|
||||||
if (!oldTable) {
|
if (!oldTable) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for (let [key, field] of Object.entries(oldTable.schema)) {
|
let key: any
|
||||||
|
let field: any
|
||||||
|
for ([key, field] of Object.entries(oldTable.schema)) {
|
||||||
const oldType = field.type
|
const oldType = field.type
|
||||||
if (!table.schema[key]) {
|
if (!table.schema[key]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const newType = table.schema[key].type
|
const newType = table.schema[key].type
|
||||||
if (oldType !== newType && !exports.areSwitchableTypes(oldType, newType)) {
|
if (oldType !== newType && !areSwitchableTypes(oldType, newType)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.TableSaveFunctions = TableSaveFunctions
|
const _TableSaveFunctions = TableSaveFunctions
|
||||||
|
export { _TableSaveFunctions as TableSaveFunctions }
|
|
@ -11,7 +11,7 @@ const zlib = require("zlib")
|
||||||
const { mainRoutes, staticRoutes } = require("./routes")
|
const { mainRoutes, staticRoutes } = require("./routes")
|
||||||
const pkg = require("../../package.json")
|
const pkg = require("../../package.json")
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
const { middleware: licensing } = require("@budibase/pro")
|
const Pro = require("@budibase/pro")
|
||||||
|
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ router
|
||||||
.use(currentApp)
|
.use(currentApp)
|
||||||
// this middleware will try to use the app ID to determine the tenancy
|
// this middleware will try to use the app ID to determine the tenancy
|
||||||
.use(buildAppTenancyMiddleware())
|
.use(buildAppTenancyMiddleware())
|
||||||
.use(licensing())
|
.use(Pro.Middleware.Licensing())
|
||||||
.use(auditLog)
|
.use(auditLog)
|
||||||
|
|
||||||
// error handling middleware
|
// error handling middleware
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
const rowController = require("../../api/controllers/row")
|
import { save } from "../../api/controllers/row"
|
||||||
const automationUtils = require("../automationUtils")
|
import { cleanUpRow, getError } from "../automationUtils"
|
||||||
const { quotas, StaticQuotaName, QuotaUsageType } = require("@budibase/pro")
|
import * as Pro from "@budibase/pro"
|
||||||
const { buildCtx } = require("./utils")
|
import { buildCtx } from "./utils"
|
||||||
|
|
||||||
exports.definition = {
|
export const definition = {
|
||||||
name: "Create Row",
|
name: "Create Row",
|
||||||
tagline: "Create a {{inputs.enriched.table.name}} row",
|
tagline: "Create a {{inputs.enriched.table.name}} row",
|
||||||
icon: "TableRowAddBottom",
|
icon: "TableRowAddBottom",
|
||||||
|
@ -59,7 +59,7 @@ exports.definition = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.run = async function ({ inputs, appId, emitter }) {
|
export async function run({ inputs, appId, emitter }: any) {
|
||||||
if (inputs.row == null || inputs.row.tableId == null) {
|
if (inputs.row == null || inputs.row.tableId == null) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -69,7 +69,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// have to clean up the row, remove the table from it
|
// have to clean up the row, remove the table from it
|
||||||
const ctx = buildCtx(appId, emitter, {
|
const ctx: any = buildCtx(appId, emitter, {
|
||||||
body: inputs.row,
|
body: inputs.row,
|
||||||
params: {
|
params: {
|
||||||
tableId: inputs.row.tableId,
|
tableId: inputs.row.tableId,
|
||||||
|
@ -77,15 +77,21 @@ exports.run = async function ({ inputs, appId, emitter }) {
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
inputs.row = await automationUtils.cleanUpRow(
|
inputs.row = await cleanUpRow(inputs.row.tableId, inputs.row)
|
||||||
inputs.row.tableId,
|
await Pro.Licensing.Quotas.updateUsage(
|
||||||
inputs.row
|
1,
|
||||||
|
Pro.StaticQuotaName.ROWS,
|
||||||
|
Pro.QuotaUsageType.STATIC,
|
||||||
|
{
|
||||||
|
dryRun: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await save(ctx)
|
||||||
|
await Pro.Licensing.Quotas.updateUsage(
|
||||||
|
1,
|
||||||
|
Pro.StaticQuotaName.ROWS,
|
||||||
|
Pro.QuotaUsageType.STATIC
|
||||||
)
|
)
|
||||||
await quotas.updateUsage(1, StaticQuotaName.ROWS, QuotaUsageType.STATIC, {
|
|
||||||
dryRun: true,
|
|
||||||
})
|
|
||||||
await rowController.save(ctx)
|
|
||||||
await quotas.updateUsage(1, StaticQuotaName.ROWS, QuotaUsageType.STATIC)
|
|
||||||
return {
|
return {
|
||||||
row: inputs.row,
|
row: inputs.row,
|
||||||
response: ctx.body,
|
response: ctx.body,
|
||||||
|
@ -96,7 +102,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
response: automationUtils.getError(err),
|
response: getError(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
const rowController = require("../../api/controllers/row")
|
import { destroy } from "../../api/controllers/row"
|
||||||
const { quotas, StaticQuotaName, QuotaUsageType } = require("@budibase/pro")
|
import * as Pro from "@budibase/pro"
|
||||||
const { buildCtx } = require("./utils")
|
import { buildCtx } from "./utils"
|
||||||
const automationUtils = require("../automationUtils")
|
import { getError } from "../automationUtils"
|
||||||
|
|
||||||
exports.definition = {
|
export const definition = {
|
||||||
description: "Delete a row from your database",
|
description: "Delete a row from your database",
|
||||||
icon: "TableRowRemoveCenter",
|
icon: "TableRowRemoveCenter",
|
||||||
name: "Delete Row",
|
name: "Delete Row",
|
||||||
|
@ -52,7 +52,7 @@ exports.definition = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.run = async function ({ inputs, appId, emitter }) {
|
export async function run({ inputs, appId, emitter }: any) {
|
||||||
if (inputs.id == null || inputs.revision == null) {
|
if (inputs.id == null || inputs.revision == null) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -62,7 +62,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = buildCtx(appId, emitter, {
|
let ctx: any = buildCtx(appId, emitter, {
|
||||||
body: {
|
body: {
|
||||||
_id: inputs.id,
|
_id: inputs.id,
|
||||||
_rev: inputs.revision,
|
_rev: inputs.revision,
|
||||||
|
@ -73,8 +73,12 @@ exports.run = async function ({ inputs, appId, emitter }) {
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await quotas.updateUsage(-1, StaticQuotaName.ROWS, QuotaUsageType.STATIC)
|
await Pro.Licensing.Quotas.updateUsage(
|
||||||
await rowController.destroy(ctx)
|
-1,
|
||||||
|
Pro.StaticQuotaName.ROWS,
|
||||||
|
Pro.QuotaUsageType.STATIC
|
||||||
|
)
|
||||||
|
await destroy(ctx)
|
||||||
return {
|
return {
|
||||||
response: ctx.body,
|
response: ctx.body,
|
||||||
row: ctx.row,
|
row: ctx.row,
|
||||||
|
@ -83,7 +87,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
response: automationUtils.getError(err),
|
response: getError(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { quotas, StaticQuotaName, QuotaUsageType } from "@budibase/pro"
|
import * as Pro from "@budibase/pro"
|
||||||
const { getUniqueRows } = require("../utilities/usageQuota/rows")
|
const { getUniqueRows } = require("../utilities/usageQuota/rows")
|
||||||
const {
|
const {
|
||||||
isExternalTable,
|
isExternalTable,
|
||||||
|
@ -14,12 +14,12 @@ const METHOD_MAP: any = {
|
||||||
|
|
||||||
const DOMAIN_MAP: any = {
|
const DOMAIN_MAP: any = {
|
||||||
rows: {
|
rows: {
|
||||||
name: StaticQuotaName.ROWS,
|
name: Pro.StaticQuotaName.ROWS,
|
||||||
type: QuotaUsageType.STATIC,
|
type: Pro.QuotaUsageType.STATIC,
|
||||||
},
|
},
|
||||||
applications: {
|
applications: {
|
||||||
name: StaticQuotaName.APPS,
|
name: Pro.StaticQuotaName.APPS,
|
||||||
type: QuotaUsageType.STATIC,
|
type: Pro.QuotaUsageType.STATIC,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ function getQuotaInfo(url: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = async (ctx: any, next: any) => {
|
module.exports = async (ctx: any, next: any) => {
|
||||||
if (!quotas.useQuotas()) {
|
if (!Pro.Licensing.Quotas.useQuotas()) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ const performRequest = async (
|
||||||
const usageContext = {
|
const usageContext = {
|
||||||
skipNext: false,
|
skipNext: false,
|
||||||
skipUsage: false,
|
skipUsage: false,
|
||||||
[StaticQuotaName.APPS]: {},
|
[Pro.StaticQuotaName.APPS]: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const quotaName = quotaInfo.name
|
const quotaName = quotaInfo.name
|
||||||
|
@ -96,7 +96,9 @@ const performRequest = async (
|
||||||
|
|
||||||
// run the request
|
// run the request
|
||||||
if (!usageContext.skipNext) {
|
if (!usageContext.skipNext) {
|
||||||
await quotas.updateUsage(usage, quotaName, quotaInfo.type, { dryRun: true })
|
await Pro.Licensing.Quotas.updateUsage(usage, quotaName, quotaInfo.type, {
|
||||||
|
dryRun: true,
|
||||||
|
})
|
||||||
await next()
|
await next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +114,7 @@ const performRequest = async (
|
||||||
|
|
||||||
// update the usage
|
// update the usage
|
||||||
if (!usageContext.skipUsage) {
|
if (!usageContext.skipUsage) {
|
||||||
await quotas.updateUsage(usage, quotaName, quotaInfo.type)
|
await Pro.Licensing.Quotas.updateUsage(usage, quotaName, quotaInfo.type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,18 +128,18 @@ const appPreDelete = async (ctx: any, usageContext: any) => {
|
||||||
// store the row count to delete
|
// store the row count to delete
|
||||||
const rows = await getUniqueRows([ctx.appId])
|
const rows = await getUniqueRows([ctx.appId])
|
||||||
if (rows.length) {
|
if (rows.length) {
|
||||||
usageContext[StaticQuotaName.APPS] = { rowCount: rows.length }
|
usageContext[Pro.StaticQuotaName.APPS] = { rowCount: rows.length }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const appPostDelete = async (ctx: any, usageContext: any) => {
|
const appPostDelete = async (ctx: any, usageContext: any) => {
|
||||||
// delete the app rows from usage
|
// delete the app rows from usage
|
||||||
const rowCount = usageContext[StaticQuotaName.ROWS].rowCount
|
const rowCount = usageContext[Pro.StaticQuotaName.ROWS].rowCount
|
||||||
if (rowCount) {
|
if (rowCount) {
|
||||||
await quotas.updateUsage(
|
await Pro.Licensing.Quotas.updateUsage(
|
||||||
-rowCount,
|
-rowCount,
|
||||||
StaticQuotaName.ROWS,
|
Pro.StaticQuotaName.ROWS,
|
||||||
QuotaUsageType.STATIC
|
Pro.QuotaUsageType.STATIC
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,24 +149,24 @@ const appPostCreate = async (ctx: any) => {
|
||||||
if (ctx.request.body.useTemplate === "true") {
|
if (ctx.request.body.useTemplate === "true") {
|
||||||
const rows = await getUniqueRows([ctx.response.body.appId])
|
const rows = await getUniqueRows([ctx.response.body.appId])
|
||||||
const rowCount = rows ? rows.length : 0
|
const rowCount = rows ? rows.length : 0
|
||||||
await quotas.updateUsage(
|
await Pro.Licensing.Quotas.updateUsage(
|
||||||
rowCount,
|
rowCount,
|
||||||
StaticQuotaName.ROWS,
|
Pro.StaticQuotaName.ROWS,
|
||||||
QuotaUsageType.STATIC
|
Pro.QuotaUsageType.STATIC
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const PRE_DELETE: any = {
|
const PRE_DELETE: any = {
|
||||||
[StaticQuotaName.APPS]: appPreDelete,
|
[Pro.StaticQuotaName.APPS]: appPreDelete,
|
||||||
}
|
}
|
||||||
|
|
||||||
const POST_DELETE: any = {
|
const POST_DELETE: any = {
|
||||||
[StaticQuotaName.APPS]: appPostDelete,
|
[Pro.StaticQuotaName.APPS]: appPostDelete,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PRE_CREATE: any = {}
|
const PRE_CREATE: any = {}
|
||||||
|
|
||||||
const POST_CREATE: any = {
|
const POST_CREATE: any = {
|
||||||
[StaticQuotaName.APPS]: appPostCreate,
|
[Pro.StaticQuotaName.APPS]: appPostCreate,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { quotas } from "@budibase/pro"
|
import * as Pro from "@budibase/pro"
|
||||||
|
|
||||||
export const runQuotaMigration = async (migration: Function) => {
|
export const runQuotaMigration = async (migration: Function) => {
|
||||||
if (!quotas.useQuotas()) {
|
if (!Pro.Licensing.Quotas.useQuotas()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await migration()
|
await migration()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { getTenantId } from "@budibase/backend-core/tenancy"
|
import { getTenantId } from "@budibase/backend-core/tenancy"
|
||||||
import { getAllApps } from "@budibase/backend-core/db"
|
import { getAllApps } from "@budibase/backend-core/db"
|
||||||
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
|
import * as Pro from "@budibase/pro"
|
||||||
|
|
||||||
export const run = async () => {
|
export const run = async () => {
|
||||||
// get app count
|
// get app count
|
||||||
|
@ -11,5 +11,9 @@ export const run = async () => {
|
||||||
// sync app count
|
// sync app count
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
console.log(`[Tenant: ${tenantId}] Syncing app count: ${appCount}`)
|
console.log(`[Tenant: ${tenantId}] Syncing app count: ${appCount}`)
|
||||||
await quotas.setUsage(appCount, StaticQuotaName.APPS, QuotaUsageType.STATIC)
|
await Pro.Licensing.Quotas.setUsage(
|
||||||
|
appCount,
|
||||||
|
Pro.StaticQuotaName.APPS,
|
||||||
|
Pro.QuotaUsageType.STATIC
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { getTenantId } from "@budibase/backend-core/tenancy"
|
import { getTenantId } from "@budibase/backend-core/tenancy"
|
||||||
import { getAllApps } from "@budibase/backend-core/db"
|
import { getAllApps } from "@budibase/backend-core/db"
|
||||||
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
|
import * as Pro from "@budibase/pro"
|
||||||
import { getUniqueRows } from "../../../utilities/usageQuota/rows"
|
import { getUniqueRows } from "../../../utilities/usageQuota/rows"
|
||||||
|
|
||||||
export const run = async () => {
|
export const run = async () => {
|
||||||
|
@ -15,5 +15,9 @@ export const run = async () => {
|
||||||
// sync row count
|
// sync row count
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
console.log(`[Tenant: ${tenantId}] Syncing row count: ${rowCount}`)
|
console.log(`[Tenant: ${tenantId}] Syncing row count: ${rowCount}`)
|
||||||
await quotas.setUsage(rowCount, StaticQuotaName.ROWS, QuotaUsageType.STATIC)
|
await Pro.Licensing.Quotas.setUsage(
|
||||||
|
rowCount,
|
||||||
|
Pro.StaticQuotaName.ROWS,
|
||||||
|
Pro.QuotaUsageType.STATIC
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2438,6 +2438,11 @@
|
||||||
"@types/koa-compose" "*"
|
"@types/koa-compose" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/lodash@^4.14.179":
|
||||||
|
version "4.14.179"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.179.tgz#490ec3288088c91295780237d2497a3aa9dfb5c5"
|
||||||
|
integrity sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==
|
||||||
|
|
||||||
"@types/mime@^1":
|
"@types/mime@^1":
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"watch": ["src", "../backend-core"],
|
"watch": ["src", "../backend-core", "../../../budibase-pro/packages/pro"],
|
||||||
"ext": "js,ts,json",
|
"ext": "js,ts,json",
|
||||||
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js"],
|
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js"],
|
||||||
"exec": "ts-node src/index.ts"
|
"exec": "ts-node src/index.ts"
|
||||||
|
|
|
@ -66,6 +66,7 @@
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
"@types/koa": "^2.13.3",
|
"@types/koa": "^2.13.3",
|
||||||
"@types/koa-router": "^7.4.2",
|
"@types/koa-router": "^7.4.2",
|
||||||
|
"@types/koa__router": "^8.0.11",
|
||||||
"@types/node": "^15.12.4",
|
"@types/node": "^15.12.4",
|
||||||
"@typescript-eslint/parser": "5.12.0",
|
"@typescript-eslint/parser": "5.12.0",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import * as Pro from "@budibase/pro"
|
||||||
|
|
||||||
|
export const activate = async (ctx: any) => {
|
||||||
|
const { licenseKey } = ctx.request.body
|
||||||
|
if (!licenseKey) {
|
||||||
|
ctx.throw(400, "licenseKey is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
await Pro.Licensing.activateLicenseKey(licenseKey)
|
||||||
|
ctx.status = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
export const refresh = async (ctx: any) => {
|
||||||
|
await Pro.Licensing.Cache.refresh()
|
||||||
|
ctx.status = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getInfo = async (ctx: any) => {
|
||||||
|
const licenseInfo = await Pro.Licensing.getLicenseInfo()
|
||||||
|
if (licenseInfo) {
|
||||||
|
licenseInfo.licenseKey = "*"
|
||||||
|
ctx.body = licenseInfo
|
||||||
|
}
|
||||||
|
ctx.status = 200
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ const {
|
||||||
buildTenancyMiddleware,
|
buildTenancyMiddleware,
|
||||||
buildCsrfMiddleware,
|
buildCsrfMiddleware,
|
||||||
} = require("@budibase/backend-core/auth")
|
} = require("@budibase/backend-core/auth")
|
||||||
const { middleware: licensing } = require("@budibase/pro")
|
const Pro = require("@budibase/pro")
|
||||||
const { errors } = require("@budibase/backend-core")
|
const { errors } = require("@budibase/backend-core")
|
||||||
|
|
||||||
const PUBLIC_ENDPOINTS = [
|
const PUBLIC_ENDPOINTS = [
|
||||||
|
@ -93,7 +93,7 @@ router
|
||||||
.use(buildAuthMiddleware(PUBLIC_ENDPOINTS))
|
.use(buildAuthMiddleware(PUBLIC_ENDPOINTS))
|
||||||
.use(buildTenancyMiddleware(PUBLIC_ENDPOINTS, NO_TENANCY_ENDPOINTS))
|
.use(buildTenancyMiddleware(PUBLIC_ENDPOINTS, NO_TENANCY_ENDPOINTS))
|
||||||
.use(buildCsrfMiddleware({ noCsrfPatterns: NO_CSRF_ENDPOINTS }))
|
.use(buildCsrfMiddleware({ noCsrfPatterns: NO_CSRF_ENDPOINTS }))
|
||||||
.use(licensing())
|
.use(Pro.Middleware.Licensing())
|
||||||
// for now no public access is allowed to worker (bar health check)
|
// for now no public access is allowed to worker (bar health check)
|
||||||
.use((ctx, next) => {
|
.use((ctx, next) => {
|
||||||
if (ctx.publicEndpoint) {
|
if (ctx.publicEndpoint) {
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import Router from "@koa/router"
|
||||||
|
import * as controller from "../../controllers/global/license"
|
||||||
|
|
||||||
|
const router = new Router()
|
||||||
|
|
||||||
|
router
|
||||||
|
.post("/api/global/license/activate", controller.activate)
|
||||||
|
.post("/api/global/license/refresh", controller.refresh)
|
||||||
|
.get("/api/global/license/info", controller.getInfo)
|
||||||
|
|
||||||
|
export = router
|
|
@ -8,6 +8,7 @@ const roleRoutes = require("./global/roles")
|
||||||
const sessionRoutes = require("./global/sessions")
|
const sessionRoutes = require("./global/sessions")
|
||||||
const environmentRoutes = require("./system/environment")
|
const environmentRoutes = require("./system/environment")
|
||||||
const tenantsRoutes = require("./system/tenants")
|
const tenantsRoutes = require("./system/tenants")
|
||||||
|
const licenseRoutes = require("./global/license")
|
||||||
|
|
||||||
exports.routes = [
|
exports.routes = [
|
||||||
configRoutes,
|
configRoutes,
|
||||||
|
@ -20,4 +21,5 @@ exports.routes = [
|
||||||
sessionRoutes,
|
sessionRoutes,
|
||||||
roleRoutes,
|
roleRoutes,
|
||||||
environmentRoutes,
|
environmentRoutes,
|
||||||
|
licenseRoutes,
|
||||||
]
|
]
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue