developer and published apps usage quota migrations
This commit is contained in:
parent
0d64bed4b3
commit
714da96ee5
|
@ -16,11 +16,11 @@
|
||||||
easing: easing,
|
easing: easing,
|
||||||
})
|
})
|
||||||
|
|
||||||
$: if (value) $progress = value
|
$: if (value || value === 0) $progress = value
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class:spectrum-ProgressBar--indeterminate={!value}
|
class:spectrum-ProgressBar--indeterminate={!value && value !== 0}
|
||||||
class:spectrum-ProgressBar--sideLabel={sideLabel}
|
class:spectrum-ProgressBar--sideLabel={sideLabel}
|
||||||
class="spectrum-ProgressBar spectrum-ProgressBar--size{size}"
|
class="spectrum-ProgressBar spectrum-ProgressBar--size{size}"
|
||||||
value={$progress}
|
value={$progress}
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if value}
|
{#if value || value === 0}
|
||||||
<div
|
<div
|
||||||
class="spectrum-FieldLabel spectrum-ProgressBar-percentage spectrum-FieldLabel--size{size}"
|
class="spectrum-FieldLabel spectrum-ProgressBar-percentage spectrum-FieldLabel--size{size}"
|
||||||
>
|
>
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
<div class="spectrum-ProgressBar-track">
|
<div class="spectrum-ProgressBar-track">
|
||||||
<div
|
<div
|
||||||
class="spectrum-ProgressBar-fill"
|
class="spectrum-ProgressBar-fill"
|
||||||
style={value ? `width: ${$progress}%` : ""}
|
style={value || value === 0 ? `width: ${$progress}%` : ""}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="spectrum-ProgressBar-label" hidden="" />
|
<div class="spectrum-ProgressBar-label" hidden="" />
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
const { createUserBuildersView } = require("@budibase/backend-core/db")
|
const { createUserBuildersView } = require("@budibase/backend-core/db")
|
||||||
|
import * as syncDevelopers from "./usageQuotas/syncDevelopers"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date:
|
* Date:
|
||||||
* March 2022
|
* March 2022
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Create the builder users view.
|
* Create the builder users view and sync the developer count
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const run = async (db: any) => {
|
export const run = async (db: any) => {
|
||||||
await createUserBuildersView(db)
|
await createUserBuildersView(db)
|
||||||
|
await syncDevelopers.run()
|
||||||
}
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import * as syncPublishedApps from "./usageQuotas/syncPublishedApps"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date:
|
||||||
|
* March 2022
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Sync the published apps count
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const run = async (db: any) => {
|
||||||
|
await syncPublishedApps.run()
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { getTenantId } from "@budibase/backend-core/tenancy"
|
||||||
|
import { utils } from "@budibase/backend-core"
|
||||||
|
import * as Pro from "@budibase/pro"
|
||||||
|
|
||||||
|
export const run = async () => {
|
||||||
|
// get developer count
|
||||||
|
const developerCount = await utils.getBuildersCount()
|
||||||
|
|
||||||
|
// sync developer count
|
||||||
|
const tenantId = getTenantId()
|
||||||
|
console.log(
|
||||||
|
`[Tenant: ${tenantId}] Syncing developer count: ${developerCount}`
|
||||||
|
)
|
||||||
|
await Pro.Licensing.Quotas.setUsage(
|
||||||
|
developerCount,
|
||||||
|
Pro.StaticQuotaName.DEVELOPERS,
|
||||||
|
Pro.QuotaUsageType.STATIC
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { getTenantId } from "@budibase/backend-core/tenancy"
|
||||||
|
import { getAllApps } from "@budibase/backend-core/db"
|
||||||
|
import * as Pro from "@budibase/pro"
|
||||||
|
|
||||||
|
export const run = async () => {
|
||||||
|
// get app count
|
||||||
|
const opts: any = { dev: false }
|
||||||
|
const prodApps = await getAllApps(opts)
|
||||||
|
const prodAppCount = prodApps ? prodApps.length : 0
|
||||||
|
|
||||||
|
// sync app count
|
||||||
|
const tenantId = getTenantId()
|
||||||
|
console.log(
|
||||||
|
`[Tenant: ${tenantId}] Syncing published app count: ${prodAppCount}`
|
||||||
|
)
|
||||||
|
await Pro.Licensing.Quotas.setUsage(
|
||||||
|
prodAppCount,
|
||||||
|
Pro.StaticQuotaName.PUBLISHED_APPS,
|
||||||
|
Pro.QuotaUsageType.STATIC
|
||||||
|
)
|
||||||
|
}
|
|
@ -8,7 +8,8 @@ const {
|
||||||
import * as userEmailViewCasing from "./functions/userEmailViewCasing"
|
import * as userEmailViewCasing from "./functions/userEmailViewCasing"
|
||||||
import * as quota1 from "./functions/quotas1"
|
import * as quota1 from "./functions/quotas1"
|
||||||
import * as appUrls from "./functions/appUrls"
|
import * as appUrls from "./functions/appUrls"
|
||||||
import * as userBuildersView from "./functions/userBuildersView"
|
import * as developerQuota from "./functions/developerQuota"
|
||||||
|
import * as publishedAppsQuota from "./functions/publishedAppsQuota"
|
||||||
|
|
||||||
export interface Migration {
|
export interface Migration {
|
||||||
type: string
|
type: string
|
||||||
|
@ -52,8 +53,13 @@ export const MIGRATIONS: Migration[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: MIGRATION_TYPES.GLOBAL,
|
type: MIGRATION_TYPES.GLOBAL,
|
||||||
name: "user_builders_view",
|
name: "developer_quota",
|
||||||
fn: userBuildersView.run,
|
fn: developerQuota.run,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: MIGRATION_TYPES.GLOBAL,
|
||||||
|
name: "published_apps_quota",
|
||||||
|
fn: publishedAppsQuota.run,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
const {
|
const {
|
||||||
getGlobalUserParams,
|
getGlobalUserParams,
|
||||||
StaticDatabases,
|
StaticDatabases,
|
||||||
generateNewUsageQuotaDoc,
|
|
||||||
} = require("@budibase/backend-core/db")
|
} = require("@budibase/backend-core/db")
|
||||||
const {
|
const {
|
||||||
hash,
|
hash,
|
||||||
|
@ -9,9 +8,9 @@ const {
|
||||||
saveUser,
|
saveUser,
|
||||||
platformLogout,
|
platformLogout,
|
||||||
} = require("@budibase/backend-core/utils")
|
} = require("@budibase/backend-core/utils")
|
||||||
const { EmailTemplatePurpose } = require("../../../constants")
|
import { EmailTemplatePurpose } from "../../../constants"
|
||||||
const { checkInviteCode } = require("../../../utilities/redis")
|
import { checkInviteCode } from "../../../utilities/redis"
|
||||||
const { sendEmail } = require("../../../utilities/email")
|
import { sendEmail } from "../../../utilities/email"
|
||||||
const { user: userCache } = require("@budibase/backend-core/cache")
|
const { user: userCache } = require("@budibase/backend-core/cache")
|
||||||
const { invalidateSessions } = require("@budibase/backend-core/sessions")
|
const { invalidateSessions } = require("@budibase/backend-core/sessions")
|
||||||
const accounts = require("@budibase/backend-core/accounts")
|
const accounts = require("@budibase/backend-core/accounts")
|
||||||
|
@ -22,36 +21,37 @@ const {
|
||||||
doesTenantExist,
|
doesTenantExist,
|
||||||
} = require("@budibase/backend-core/tenancy")
|
} = require("@budibase/backend-core/tenancy")
|
||||||
const { removeUserFromInfoDB } = require("@budibase/backend-core/deprovision")
|
const { removeUserFromInfoDB } = require("@budibase/backend-core/deprovision")
|
||||||
const env = require("../../../environment")
|
import env from "../../../environment"
|
||||||
const { syncUserInApps } = require("../../../utilities/appService")
|
import { syncUserInApps } from "../../../utilities/appService"
|
||||||
const { errors } = require("@budibase/backend-core")
|
const { errors } = require("@budibase/backend-core")
|
||||||
|
import * as Pro from "@budibase/pro"
|
||||||
|
|
||||||
async function allUsers() {
|
const allUsers = async () => {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const response = await db.allDocs(
|
const response = await db.allDocs(
|
||||||
getGlobalUserParams(null, {
|
getGlobalUserParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
return response.rows.map(row => row.doc)
|
return response.rows.map((row: any) => row.doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async ctx => {
|
export const save = async (ctx: any) => {
|
||||||
try {
|
try {
|
||||||
const user = await saveUser(ctx.request.body, getTenantId())
|
const user = await saveUser(ctx.request.body, getTenantId())
|
||||||
// let server know to sync user
|
// let server know to sync user
|
||||||
await syncUserInApps(user._id)
|
await syncUserInApps(user._id)
|
||||||
ctx.body = user
|
ctx.body = user
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
ctx.throw(err.status || 400, err)
|
ctx.throw(err.status || 400, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseBooleanParam = param => {
|
const parseBooleanParam = (param: any) => {
|
||||||
return !(param && param === "false")
|
return !(param && param === "false")
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.adminUser = async ctx => {
|
export const adminUser = async (ctx: any) => {
|
||||||
const { email, password, tenantId } = ctx.request.body
|
const { email, password, tenantId } = ctx.request.body
|
||||||
|
|
||||||
// account portal sends a pre-hashed password - honour param to prevent double hashing
|
// account portal sends a pre-hashed password - honour param to prevent double hashing
|
||||||
|
@ -81,10 +81,10 @@ exports.adminUser = async ctx => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// don't worry about errors
|
// don't worry about errors
|
||||||
}
|
}
|
||||||
await db.put(generateNewUsageQuotaDoc())
|
await db.put(Pro.Licensing.Quotas.generateNewQuotaUsage())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.rows.some(row => row.doc.admin)) {
|
if (response.rows.some((row: any) => row.doc.admin)) {
|
||||||
ctx.throw(
|
ctx.throw(
|
||||||
403,
|
403,
|
||||||
"You cannot initialise once an global user has been created."
|
"You cannot initialise once an global user has been created."
|
||||||
|
@ -106,12 +106,12 @@ exports.adminUser = async ctx => {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ctx.body = await saveUser(user, tenantId, hashPassword, requirePassword)
|
ctx.body = await saveUser(user, tenantId, hashPassword, requirePassword)
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
ctx.throw(err.status || 400, err)
|
ctx.throw(err.status || 400, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async ctx => {
|
export const destroy = async (ctx: any) => {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const dbUser = await db.get(ctx.params.id)
|
const dbUser = await db.get(ctx.params.id)
|
||||||
|
|
||||||
|
@ -139,10 +139,10 @@ exports.destroy = async ctx => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.removeAppRole = async ctx => {
|
export const removeAppRole = async (ctx: any) => {
|
||||||
const { appId } = ctx.params
|
const { appId } = ctx.params
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const users = await allUsers(ctx)
|
const users = await allUsers()
|
||||||
const bulk = []
|
const bulk = []
|
||||||
const cacheInvalidations = []
|
const cacheInvalidations = []
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
|
@ -162,7 +162,7 @@ exports.removeAppRole = async ctx => {
|
||||||
/**
|
/**
|
||||||
* Add the attributes that are session based to the current user.
|
* Add the attributes that are session based to the current user.
|
||||||
*/
|
*/
|
||||||
const addSessionAttributesToUser = ctx => {
|
const addSessionAttributesToUser = (ctx: any) => {
|
||||||
ctx.body.account = ctx.user.account
|
ctx.body.account = ctx.user.account
|
||||||
ctx.body.license = ctx.user.license
|
ctx.body.license = ctx.user.license
|
||||||
ctx.body.budibaseAccess = ctx.user.budibaseAccess
|
ctx.body.budibaseAccess = ctx.user.budibaseAccess
|
||||||
|
@ -174,7 +174,7 @@ const addSessionAttributesToUser = ctx => {
|
||||||
* Remove the attributes that are session based from the current user,
|
* Remove the attributes that are session based from the current user,
|
||||||
* so that stale values are not written to the db
|
* so that stale values are not written to the db
|
||||||
*/
|
*/
|
||||||
const removeSessionAttributesFromUser = ctx => {
|
const removeSessionAttributesFromUser = (ctx: any) => {
|
||||||
delete ctx.request.body.csrfToken
|
delete ctx.request.body.csrfToken
|
||||||
delete ctx.request.body.account
|
delete ctx.request.body.account
|
||||||
delete ctx.request.body.accountPortalAccess
|
delete ctx.request.body.accountPortalAccess
|
||||||
|
@ -182,7 +182,7 @@ const removeSessionAttributesFromUser = ctx => {
|
||||||
delete ctx.request.body.license
|
delete ctx.request.body.license
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getSelf = async ctx => {
|
export const getSelf = async (ctx: any) => {
|
||||||
if (!ctx.user) {
|
if (!ctx.user) {
|
||||||
ctx.throw(403, "User not logged in")
|
ctx.throw(403, "User not logged in")
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,7 @@ exports.getSelf = async ctx => {
|
||||||
addSessionAttributesToUser(ctx)
|
addSessionAttributesToUser(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.updateSelf = async ctx => {
|
export const updateSelf = async (ctx: any) => {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const user = await db.get(ctx.user._id)
|
const user = await db.get(ctx.user._id)
|
||||||
if (ctx.request.body.password) {
|
if (ctx.request.body.password) {
|
||||||
|
@ -224,8 +224,8 @@ exports.updateSelf = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// called internally by app server user fetch
|
// called internally by app server user fetch
|
||||||
exports.fetch = async ctx => {
|
export const fetch = async (ctx: any) => {
|
||||||
const users = await allUsers(ctx)
|
const users = await allUsers()
|
||||||
// user hashed password shouldn't ever be returned
|
// user hashed password shouldn't ever be returned
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -236,7 +236,7 @@ exports.fetch = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// called internally by app server user find
|
// called internally by app server user find
|
||||||
exports.find = async ctx => {
|
export const find = async (ctx: any) => {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
let user
|
let user
|
||||||
try {
|
try {
|
||||||
|
@ -251,7 +251,7 @@ exports.find = async ctx => {
|
||||||
ctx.body = user
|
ctx.body = user
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.tenantUserLookup = async ctx => {
|
export const tenantUserLookup = async (ctx: any) => {
|
||||||
const id = ctx.params.id
|
const id = ctx.params.id
|
||||||
const user = await getTenantUser(id)
|
const user = await getTenantUser(id)
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -261,7 +261,7 @@ exports.tenantUserLookup = async ctx => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.invite = async ctx => {
|
export const invite = async (ctx: any) => {
|
||||||
let { email, userInfo } = ctx.request.body
|
let { email, userInfo } = ctx.request.body
|
||||||
const existing = await getGlobalUserByEmail(email)
|
const existing = await getGlobalUserByEmail(email)
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
@ -271,20 +271,21 @@ exports.invite = async ctx => {
|
||||||
userInfo = {}
|
userInfo = {}
|
||||||
}
|
}
|
||||||
userInfo.tenantId = getTenantId()
|
userInfo.tenantId = getTenantId()
|
||||||
await sendEmail(email, EmailTemplatePurpose.INVITATION, {
|
const opts: any = {
|
||||||
subject: "{{ company }} platform invitation",
|
subject: "{{ company }} platform invitation",
|
||||||
info: userInfo,
|
info: userInfo,
|
||||||
})
|
}
|
||||||
|
await sendEmail(email, EmailTemplatePurpose.INVITATION, opts)
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Invitation has been sent.",
|
message: "Invitation has been sent.",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.inviteAccept = async ctx => {
|
export const inviteAccept = async (ctx: any) => {
|
||||||
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
||||||
try {
|
try {
|
||||||
// info is an extension of the user object that was stored by global
|
// info is an extension of the user object that was stored by global
|
||||||
const { email, info } = await checkInviteCode(inviteCode)
|
const { email, info }: any = await checkInviteCode(inviteCode)
|
||||||
ctx.body = await saveUser(
|
ctx.body = await saveUser(
|
||||||
{
|
{
|
||||||
firstName,
|
firstName,
|
||||||
|
@ -295,7 +296,7 @@ exports.inviteAccept = async ctx => {
|
||||||
},
|
},
|
||||||
info.tenantId
|
info.tenantId
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
||||||
// explicitly re-throw limit exceeded errors
|
// explicitly re-throw limit exceeded errors
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
Loading…
Reference in New Issue