Self Host <-> Licensing integration

This commit is contained in:
Rory Powell 2022-03-09 21:16:22 +00:00
parent eefe4ea2ad
commit 7698e9293c
26 changed files with 1605 additions and 191 deletions

View File

@ -22,25 +22,3 @@ exports.getAccount = async email => {
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()
}

View File

@ -13,6 +13,7 @@ exports.Cookies = {
exports.Headers = {
API_KEY: "x-budibase-api-key",
LICENSE_KEY: "x-budibase-license-key",
API_VER: "x-budibase-api-version",
APP_ID: "x-budibase-app-id",
TYPE: "x-budibase-type",

View File

@ -22,6 +22,7 @@ exports.StaticDatabases = {
docs: {
apiKeys: "apikeys",
usageQuota: "usage_quota",
licenseInfo: "license_info",
},
},
// contains information about tenancy and so on

View File

@ -55,6 +55,10 @@
title: "Updates",
href: "/builder/portal/settings/update",
},
{
title: "Upgrade",
href: "/builder/portal/settings/upgrade",
},
])
}
} else {

View File

@ -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>

View File

@ -21,6 +21,7 @@ import { buildTableEndpoints } from "./tables"
import { buildTemplateEndpoints } from "./templates"
import { buildUserEndpoints } from "./user"
import { buildViewEndpoints } from "./views"
import { buildLicensingEndpoints } from "./licensing"
const defaultAPIClientConfig = {
/**
@ -231,5 +232,6 @@ export const createAPIClient = config => {
...buildTemplateEndpoints(API),
...buildUserEndpoints(API),
...buildViewEndpoints(API),
...buildLicensingEndpoints(API),
}
}

View File

@ -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",
})
},
})

View File

@ -148,6 +148,7 @@
"@types/jest": "^26.0.23",
"@types/koa": "^2.13.3",
"@types/koa-router": "^7.4.2",
"@types/lodash": "^4.14.179",
"@types/node": "^15.12.4",
"@types/oracledb": "^5.2.1",
"@typescript-eslint/parser": "5.12.0",

View File

@ -52,7 +52,7 @@ interface RunConfig {
module External {
function buildFilters(
id: string | undefined,
id: string | undefined | string[],
filters: SearchFilters,
table: Table
) {

View File

@ -1,19 +1,19 @@
const linkRows = require("../../../db/linkedRows")
const { getRowParams, generateTableID } = require("../../../db/utils")
const { FieldTypes } = require("../../../constants")
const {
import { updateLinks, EventType } from "../../../db/linkedRows"
import { getRowParams, generateTableID } from "../../../db/utils"
import { FieldTypes } from "../../../constants"
import {
TableSaveFunctions,
hasTypeChanged,
getTable,
handleDataImport,
} = require("./utils")
const { quotas, StaticQuotaName, QuotaUsageType } = require("@budibase/pro")
} from "./utils"
const { getAppDB } = require("@budibase/backend-core/context")
const env = require("../../../environment")
const { cleanupAttachments } = require("../../../utilities/rowProcessor")
const { runStaticFormulaChecks } = require("./bulkFormula")
import { isTest } from "../../../environment"
import { cleanupAttachments } from "../../../utilities/rowProcessor"
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 { dataImport, ...rest } = ctx.request.body
let tableToSave = {
@ -80,10 +80,8 @@ exports.save = async function (ctx) {
// update linked rows
try {
const linkResp = await linkRows.updateLinks({
eventType: oldTable
? linkRows.EventType.TABLE_UPDATED
: linkRows.EventType.TABLE_SAVE,
const linkResp: any = await updateLinks({
eventType: oldTable ? EventType.TABLE_UPDATED : EventType.TABLE_SAVE,
table: tableToSave,
oldTable: oldTable,
})
@ -105,11 +103,11 @@ exports.save = async function (ctx) {
tableToSave = await tableSaveFunctions.after(tableToSave)
// has to run after, make sure it has _id
await runStaticFormulaChecks(tableToSave, { oldTable })
await runStaticFormulaChecks(tableToSave, { oldTable, deletion: null })
return tableToSave
}
exports.destroy = async function (ctx) {
export async function destroy(ctx: any) {
const db = getAppDB()
const tableToDelete = await db.get(ctx.params.tableId)
@ -119,16 +117,18 @@ exports.destroy = async function (ctx) {
include_docs: true,
})
)
await db.bulkDocs(rows.rows.map(row => ({ ...row.doc, _deleted: true })))
await quotas.updateUsage(
await db.bulkDocs(
rows.rows.map((row: any) => ({ ...row.doc, _deleted: true }))
)
await Pro.Licensing.Quotas.updateUsage(
-rows.rows.length,
StaticQuotaName.ROWS,
QuotaUsageType.STATIC
Pro.StaticQuotaName.ROWS,
Pro.QuotaUsageType.STATIC
)
// update linked rows
await linkRows.updateLinks({
eventType: linkRows.EventType.TABLE_DELETE,
await updateLinks({
eventType: EventType.TABLE_DELETE,
table: tableToDelete,
})
@ -136,10 +136,10 @@ exports.destroy = async function (ctx) {
await db.remove(tableToDelete)
// remove table search index
if (!env.isTest()) {
if (!isTest()) {
const currentIndexes = await db.getIndexes()
const existingIndex = currentIndexes.indexes.find(
existing => existing.name === `search:${ctx.params.tableId}`
(existing: any) => existing.name === `search:${ctx.params.tableId}`
)
if (existingIndex) {
await db.deleteIndex(existingIndex)
@ -147,12 +147,15 @@ exports.destroy = async function (ctx) {
}
// 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 })
return tableToDelete
}
exports.bulkImport = async function (ctx) {
export async function bulkImport(ctx: any) {
const table = await getTable(ctx.params.tableId)
const { dataImport } = ctx.request.body
await handleDataImport(ctx.user, table, dataImport)

View File

@ -1,34 +1,34 @@
const csvParser = require("../../../utilities/csvParser")
const {
import { transform } from "../../../utilities/csvParser"
import {
getRowParams,
generateRowID,
InternalTables,
getTableParams,
BudibaseInternalDB,
} = require("../../../db/utils")
const { isEqual } = require("lodash")
const { AutoFieldSubTypes, FieldTypes } = require("../../../constants")
const {
} from "../../../db/utils"
import { isEqual } from "lodash"
import { AutoFieldSubTypes, FieldTypes } from "../../../constants"
import {
inputProcessing,
cleanupAttachments,
} = require("../../../utilities/rowProcessor")
const {
} from "../../../utilities/rowProcessor"
import {
USERS_TABLE_SCHEMA,
SwitchableTypes,
CanSwitchTypes,
} = require("../../../constants")
const {
} from "../../../constants"
import {
isExternalTable,
breakExternalTableId,
isSQL,
} = require("../../../integrations/utils")
const { getViews, saveView } = require("../view/utils")
const viewTemplate = require("../view/viewBuilder")
const { quotas, StaticQuotaName, QuotaUsageType } = require("@budibase/pro")
} from "../../../integrations/utils"
import { getViews, saveView } from "../view/utils"
import viewTemplate from "../view/viewBuilder"
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 rows = await db.allDocs(
getRowParams(table._id, null, {
@ -36,18 +36,18 @@ exports.clearColumns = async (table, columnNames) => {
})
)
return db.bulkDocs(
rows.rows.map(({ doc }) => {
columnNames.forEach(colName => delete doc[colName])
rows.rows.map(({ doc }: any) => {
columnNames.forEach((colName: any) => delete doc[colName])
return doc
})
)
}
exports.checkForColumnUpdates = async (oldTable, updatedTable) => {
export async function checkForColumnUpdates(oldTable: any, updatedTable: any) {
const db = getAppDB()
let updatedRows = []
const rename = updatedTable._rename
let deletedColumns = []
let deletedColumns: any = []
if (oldTable && oldTable.schema && updatedTable.schema) {
deletedColumns = Object.keys(oldTable.schema).filter(
colName => updatedTable.schema[colName] == null
@ -61,14 +61,14 @@ exports.checkForColumnUpdates = async (oldTable, updatedTable) => {
include_docs: true,
})
)
const rawRows = rows.rows.map(({ doc }) => doc)
updatedRows = rawRows.map(row => {
const rawRows = rows.rows.map(({ doc }: any) => doc)
updatedRows = rawRows.map((row: any) => {
row = cloneDeep(row)
if (rename) {
row[rename.updated] = row[rename.old]
delete row[rename.old]
} else if (deletedColumns.length !== 0) {
deletedColumns.forEach(colName => delete row[colName])
deletedColumns.forEach((colName: any) => delete row[colName])
}
return row
})
@ -76,14 +76,14 @@ exports.checkForColumnUpdates = async (oldTable, updatedTable) => {
// cleanup any attachments from object storage for deleted attachment columns
await cleanupAttachments(updatedTable, { oldTable, rows: rawRows })
// Update views
await exports.checkForViewUpdates(updatedTable, rename, deletedColumns)
await checkForViewUpdates(updatedTable, rename, deletedColumns)
delete updatedTable._rename
}
return { rows: updatedRows, table: updatedTable }
}
// 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) {
return tableToSave
}
@ -91,7 +91,9 @@ exports.makeSureTableUpToDate = (table, tableToSave) => {
tableToSave._rev = table._rev
// make sure auto IDs are always updated - these are internal
// 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 (
column.autocolumn &&
column.subtype === AutoFieldSubTypes.AUTO_ID &&
@ -103,14 +105,14 @@ exports.makeSureTableUpToDate = (table, tableToSave) => {
return tableToSave
}
exports.handleDataImport = async (user, table, dataImport) => {
export async function handleDataImport(user: any, table: any, dataImport: any) {
if (!dataImport || !dataImport.csvString) {
return table
}
const db = getAppDB()
// Populate the table with rows imported from CSV in a bulk update
const data = await csvParser.transform({
const data = await transform({
...dataImport,
existingTable: table,
})
@ -120,13 +122,15 @@ exports.handleDataImport = async (user, table, dataImport) => {
let row = data[i]
row._id = generateRowID(table._id)
row.tableId = table._id
const processed = inputProcessing(user, table, row, {
const processed: any = inputProcessing(user, table, row, {
noAutoRelationships: true,
})
table = processed.table
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
if (
schema.type === FieldTypes.OPTIONS &&
@ -143,26 +147,26 @@ exports.handleDataImport = async (user, table, dataImport) => {
finalData.push(row)
}
await quotas.updateUsage(
await Pro.Licensing.Quotas.updateUsage(
finalData.length,
StaticQuotaName.ROWS,
QuotaUsageType.STATIC,
Pro.StaticQuotaName.ROWS,
Pro.QuotaUsageType.STATIC,
{
dryRun: true,
}
)
await db.bulkDocs(finalData)
await quotas.updateUsage(
await Pro.Licensing.Quotas.updateUsage(
finalData.length,
StaticQuotaName.ROWS,
QuotaUsageType.STATIC
Pro.StaticQuotaName.ROWS,
Pro.QuotaUsageType.STATIC
)
let response = await db.put(table)
table._rev = response._rev
return table
}
exports.handleSearchIndexes = async table => {
export async function handleSearchIndexes(table: any) {
const db = getAppDB()
// create relevant search indexes
if (table.indexes && table.indexes.length > 0) {
@ -170,12 +174,12 @@ exports.handleSearchIndexes = async table => {
const indexName = `search:${table._id}`
const existingIndex = currentIndexes.indexes.find(
existing => existing.name === indexName
(existing: any) => existing.name === indexName
)
if (existingIndex) {
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
@ -206,7 +210,7 @@ exports.handleSearchIndexes = async table => {
return table
}
exports.checkStaticTables = table => {
export function checkStaticTables(table: any) {
// check user schema has all required elements
if (table._id === InternalTables.USER_METADATA) {
for (let [key, schema] of Object.entries(USERS_TABLE_SCHEMA.schema)) {
@ -220,7 +224,13 @@ exports.checkStaticTables = table => {
}
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.user = user
this.oldTable = oldTable
@ -230,25 +240,25 @@ class TableSaveFunctions {
}
// before anything is done
async before(table) {
async before(table: any) {
if (this.oldTable) {
table = exports.makeSureTableUpToDate(this.oldTable, table)
table = makeSureTableUpToDate(this.oldTable, table)
}
table = exports.checkStaticTables(table)
table = checkStaticTables(table)
return table
}
// when confirmed valid
async mid(table) {
let response = await exports.checkForColumnUpdates(this.oldTable, table)
async mid(table: any) {
let response = await checkForColumnUpdates(this.oldTable, table)
this.rows = this.rows.concat(response.rows)
return table
}
// after saving
async after(table) {
table = await exports.handleSearchIndexes(table)
table = await exports.handleDataImport(this.user, table, this.dataImport)
async after(table: any) {
table = await handleSearchIndexes(table)
table = await handleDataImport(this.user, table, this.dataImport)
return table
}
@ -257,21 +267,21 @@ class TableSaveFunctions {
}
}
exports.getAllInternalTables = async () => {
export async function getAllInternalTables() {
const db = getAppDB()
const internalTables = await db.allDocs(
getTableParams(null, {
include_docs: true,
})
)
return internalTables.rows.map(tableDoc => ({
return internalTables.rows.map((tableDoc: any) => ({
...tableDoc.doc,
type: "internal",
sourceId: BudibaseInternalDB._id,
}))
}
exports.getAllExternalTables = async datasourceId => {
export async function getAllExternalTables(datasourceId: any) {
const db = getAppDB()
const datasource = await db.get(datasourceId)
if (!datasource || !datasource.entities) {
@ -280,24 +290,28 @@ exports.getAllExternalTables = async datasourceId => {
return datasource.entities
}
exports.getExternalTable = async (datasourceId, tableName) => {
const entities = await exports.getAllExternalTables(datasourceId)
export async function getExternalTable(datasourceId: any, tableName: any) {
const entities = await getAllExternalTables(datasourceId)
return entities[tableName]
}
exports.getTable = async tableId => {
export async function getTable(tableId: any) {
const db = getAppDB()
if (isExternalTable(tableId)) {
let { datasourceId, tableName } = breakExternalTableId(tableId)
const datasource = await db.get(datasourceId)
const table = await exports.getExternalTable(datasourceId, tableName)
const table = await getExternalTable(datasourceId, tableName)
return { ...table, sql: isSQL(datasource) }
} else {
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 tableViews = views.filter(view => view.meta.tableId === table._id)
@ -321,7 +335,7 @@ exports.checkForViewUpdates = async (table, rename, deletedColumns) => {
// Update filters if required
if (view.meta.filters) {
view.meta.filters.forEach(filter => {
view.meta.filters.forEach((filter: any) => {
if (filter.key === rename.old) {
filter.key = rename.updated
needsUpdated = true
@ -329,7 +343,7 @@ exports.checkForViewUpdates = async (table, rename, deletedColumns) => {
})
}
} else if (deletedColumns) {
deletedColumns.forEach(column => {
deletedColumns.forEach((column: any) => {
// Remove calculation statement if required
if (view.meta.field === column) {
delete view.meta.field
@ -347,7 +361,7 @@ exports.checkForViewUpdates = async (table, rename, deletedColumns) => {
// Remove filters referencing deleted field if required
if (view.meta.filters && 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
})
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}`
}
exports.generateJunctionTableName = (column, table, relatedTable) => {
export function generateJunctionTableName(
column: any,
table: any,
relatedTable: any
) {
return `jt_${table.name}_${relatedTable.name}_${column.name}_${column.fieldName}`
}
exports.foreignKeyStructure = (keyName, meta = null) => {
const structure = {
export function foreignKeyStructure(keyName: any, meta = null) {
const structure: any = {
type: FieldTypes.NUMBER,
constraints: {},
name: keyName,
@ -389,7 +407,7 @@ exports.foreignKeyStructure = (keyName, meta = null) => {
return structure
}
exports.areSwitchableTypes = (type1, type2) => {
export function areSwitchableTypes(type1: any, type2: any) {
if (
SwitchableTypes.indexOf(type1) === -1 &&
SwitchableTypes.indexOf(type2) === -1
@ -406,21 +424,24 @@ exports.areSwitchableTypes = (type1, type2) => {
return false
}
exports.hasTypeChanged = (table, oldTable) => {
export function hasTypeChanged(table: any, oldTable: any) {
if (!oldTable) {
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
if (!table.schema[key]) {
continue
}
const newType = table.schema[key].type
if (oldType !== newType && !exports.areSwitchableTypes(oldType, newType)) {
if (oldType !== newType && !areSwitchableTypes(oldType, newType)) {
return true
}
}
return false
}
exports.TableSaveFunctions = TableSaveFunctions
const _TableSaveFunctions = TableSaveFunctions
export { _TableSaveFunctions as TableSaveFunctions }

View File

@ -11,7 +11,7 @@ const zlib = require("zlib")
const { mainRoutes, staticRoutes } = require("./routes")
const pkg = require("../../package.json")
const env = require("../environment")
const { middleware: licensing } = require("@budibase/pro")
const Pro = require("@budibase/pro")
const router = new Router()
@ -55,7 +55,7 @@ router
.use(currentApp)
// this middleware will try to use the app ID to determine the tenancy
.use(buildAppTenancyMiddleware())
.use(licensing())
.use(Pro.Middleware.Licensing())
.use(auditLog)
// error handling middleware

View File

@ -1,9 +1,9 @@
const rowController = require("../../api/controllers/row")
const automationUtils = require("../automationUtils")
const { quotas, StaticQuotaName, QuotaUsageType } = require("@budibase/pro")
const { buildCtx } = require("./utils")
import { save } from "../../api/controllers/row"
import { cleanUpRow, getError } from "../automationUtils"
import * as Pro from "@budibase/pro"
import { buildCtx } from "./utils"
exports.definition = {
export const definition = {
name: "Create Row",
tagline: "Create a {{inputs.enriched.table.name}} row",
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) {
return {
success: false,
@ -69,7 +69,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
}
}
// 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,
params: {
tableId: inputs.row.tableId,
@ -77,15 +77,21 @@ exports.run = async function ({ inputs, appId, emitter }) {
})
try {
inputs.row = await automationUtils.cleanUpRow(
inputs.row.tableId,
inputs.row
inputs.row = await cleanUpRow(inputs.row.tableId, inputs.row)
await Pro.Licensing.Quotas.updateUsage(
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 {
row: inputs.row,
response: ctx.body,
@ -96,7 +102,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
} catch (err) {
return {
success: false,
response: automationUtils.getError(err),
response: getError(err),
}
}
}

View File

@ -1,9 +1,9 @@
const rowController = require("../../api/controllers/row")
const { quotas, StaticQuotaName, QuotaUsageType } = require("@budibase/pro")
const { buildCtx } = require("./utils")
const automationUtils = require("../automationUtils")
import { destroy } from "../../api/controllers/row"
import * as Pro from "@budibase/pro"
import { buildCtx } from "./utils"
import { getError } from "../automationUtils"
exports.definition = {
export const definition = {
description: "Delete a row from your database",
icon: "TableRowRemoveCenter",
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) {
return {
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: {
_id: inputs.id,
_rev: inputs.revision,
@ -73,8 +73,12 @@ exports.run = async function ({ inputs, appId, emitter }) {
})
try {
await quotas.updateUsage(-1, StaticQuotaName.ROWS, QuotaUsageType.STATIC)
await rowController.destroy(ctx)
await Pro.Licensing.Quotas.updateUsage(
-1,
Pro.StaticQuotaName.ROWS,
Pro.QuotaUsageType.STATIC
)
await destroy(ctx)
return {
response: ctx.body,
row: ctx.row,
@ -83,7 +87,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
} catch (err) {
return {
success: false,
response: automationUtils.getError(err),
response: getError(err),
}
}
}

View File

@ -1,4 +1,4 @@
import { quotas, StaticQuotaName, QuotaUsageType } from "@budibase/pro"
import * as Pro from "@budibase/pro"
const { getUniqueRows } = require("../utilities/usageQuota/rows")
const {
isExternalTable,
@ -14,12 +14,12 @@ const METHOD_MAP: any = {
const DOMAIN_MAP: any = {
rows: {
name: StaticQuotaName.ROWS,
type: QuotaUsageType.STATIC,
name: Pro.StaticQuotaName.ROWS,
type: Pro.QuotaUsageType.STATIC,
},
applications: {
name: StaticQuotaName.APPS,
type: QuotaUsageType.STATIC,
name: Pro.StaticQuotaName.APPS,
type: Pro.QuotaUsageType.STATIC,
},
}
@ -32,7 +32,7 @@ function getQuotaInfo(url: string) {
}
module.exports = async (ctx: any, next: any) => {
if (!quotas.useQuotas()) {
if (!Pro.Licensing.Quotas.useQuotas()) {
return next()
}
@ -79,7 +79,7 @@ const performRequest = async (
const usageContext = {
skipNext: false,
skipUsage: false,
[StaticQuotaName.APPS]: {},
[Pro.StaticQuotaName.APPS]: {},
}
const quotaName = quotaInfo.name
@ -96,7 +96,9 @@ const performRequest = async (
// run the request
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()
}
@ -112,7 +114,7 @@ const performRequest = async (
// update the usage
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
const rows = await getUniqueRows([ctx.appId])
if (rows.length) {
usageContext[StaticQuotaName.APPS] = { rowCount: rows.length }
usageContext[Pro.StaticQuotaName.APPS] = { rowCount: rows.length }
}
}
const appPostDelete = async (ctx: any, usageContext: any) => {
// delete the app rows from usage
const rowCount = usageContext[StaticQuotaName.ROWS].rowCount
const rowCount = usageContext[Pro.StaticQuotaName.ROWS].rowCount
if (rowCount) {
await quotas.updateUsage(
await Pro.Licensing.Quotas.updateUsage(
-rowCount,
StaticQuotaName.ROWS,
QuotaUsageType.STATIC
Pro.StaticQuotaName.ROWS,
Pro.QuotaUsageType.STATIC
)
}
}
@ -147,24 +149,24 @@ const appPostCreate = async (ctx: any) => {
if (ctx.request.body.useTemplate === "true") {
const rows = await getUniqueRows([ctx.response.body.appId])
const rowCount = rows ? rows.length : 0
await quotas.updateUsage(
await Pro.Licensing.Quotas.updateUsage(
rowCount,
StaticQuotaName.ROWS,
QuotaUsageType.STATIC
Pro.StaticQuotaName.ROWS,
Pro.QuotaUsageType.STATIC
)
}
}
const PRE_DELETE: any = {
[StaticQuotaName.APPS]: appPreDelete,
[Pro.StaticQuotaName.APPS]: appPreDelete,
}
const POST_DELETE: any = {
[StaticQuotaName.APPS]: appPostDelete,
[Pro.StaticQuotaName.APPS]: appPostDelete,
}
const PRE_CREATE: any = {}
const POST_CREATE: any = {
[StaticQuotaName.APPS]: appPostCreate,
[Pro.StaticQuotaName.APPS]: appPostCreate,
}

View File

@ -1,7 +1,7 @@
import { quotas } from "@budibase/pro"
import * as Pro from "@budibase/pro"
export const runQuotaMigration = async (migration: Function) => {
if (!quotas.useQuotas()) {
if (!Pro.Licensing.Quotas.useQuotas()) {
return
}
await migration()

View File

@ -1,6 +1,6 @@
import { getTenantId } from "@budibase/backend-core/tenancy"
import { getAllApps } from "@budibase/backend-core/db"
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
import * as Pro from "@budibase/pro"
export const run = async () => {
// get app count
@ -11,5 +11,9 @@ export const run = async () => {
// sync app count
const tenantId = getTenantId()
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
)
}

View File

@ -1,6 +1,6 @@
import { getTenantId } from "@budibase/backend-core/tenancy"
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"
export const run = async () => {
@ -15,5 +15,9 @@ export const run = async () => {
// sync row count
const tenantId = getTenantId()
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
)
}

View File

@ -2438,6 +2438,11 @@
"@types/koa-compose" "*"
"@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":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"

View File

@ -1,5 +1,5 @@
{
"watch": ["src", "../backend-core"],
"watch": ["src", "../backend-core", "../../../budibase-pro/packages/pro"],
"ext": "js,ts,json",
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js"],
"exec": "ts-node src/index.ts"

View File

@ -66,6 +66,7 @@
"@types/jest": "^26.0.23",
"@types/koa": "^2.13.3",
"@types/koa-router": "^7.4.2",
"@types/koa__router": "^8.0.11",
"@types/node": "^15.12.4",
"@typescript-eslint/parser": "5.12.0",
"copyfiles": "^2.4.1",

View File

@ -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
}

View File

@ -8,7 +8,7 @@ const {
buildTenancyMiddleware,
buildCsrfMiddleware,
} = require("@budibase/backend-core/auth")
const { middleware: licensing } = require("@budibase/pro")
const Pro = require("@budibase/pro")
const { errors } = require("@budibase/backend-core")
const PUBLIC_ENDPOINTS = [
@ -93,7 +93,7 @@ router
.use(buildAuthMiddleware(PUBLIC_ENDPOINTS))
.use(buildTenancyMiddleware(PUBLIC_ENDPOINTS, NO_TENANCY_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)
.use((ctx, next) => {
if (ctx.publicEndpoint) {

View File

@ -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

View File

@ -8,6 +8,7 @@ const roleRoutes = require("./global/roles")
const sessionRoutes = require("./global/sessions")
const environmentRoutes = require("./system/environment")
const tenantsRoutes = require("./system/tenants")
const licenseRoutes = require("./global/license")
exports.routes = [
configRoutes,
@ -20,4 +21,5 @@ exports.routes = [
sessionRoutes,
roleRoutes,
environmentRoutes,
licenseRoutes,
]

File diff suppressed because it is too large Load Diff