Process quota emails in account-portal

This commit is contained in:
Rory Powell 2022-10-06 16:03:47 +01:00
parent 46f9f14e3d
commit af24f72166
14 changed files with 190 additions and 87 deletions

View File

@ -62,6 +62,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@types/chance": "1.1.3",
"@types/jest": "27.5.1", "@types/jest": "27.5.1",
"@types/koa": "2.0.52", "@types/koa": "2.0.52",
"@types/lodash": "4.14.180", "@types/lodash": "4.14.180",
@ -73,6 +74,7 @@
"@types/tar-fs": "2.0.1", "@types/tar-fs": "2.0.1",
"@types/uuid": "8.3.4", "@types/uuid": "8.3.4",
"ioredis-mock": "5.8.0", "ioredis-mock": "5.8.0",
"chance": "1.1.3",
"jest": "27.5.1", "jest": "27.5.1",
"koa": "2.7.0", "koa": "2.7.0",
"nodemon": "2.0.16", "nodemon": "2.0.16",

View File

@ -0,0 +1,23 @@
import { generator, uuid } from "."
import { AuthType, CloudAccount, Hosting } from "@budibase/types"
import * as db from "../../../src/db/utils"
export const cloudAccount = (): CloudAccount => {
return {
accountId: uuid(),
createdAt: Date.now(),
verified: true,
verificationSent: true,
tier: "",
email: generator.email(),
tenantId: generator.word(),
hosting: Hosting.CLOUD,
authType: AuthType.PASSWORD,
password: generator.word(),
tenantName: generator.word(),
name: generator.name(),
size: "10+",
profession: "Software Engineer",
budibaseUserId: db.generateGlobalUserID(),
}
}

View File

@ -0,0 +1 @@
export { v4 as uuid } from "uuid"

View File

@ -1 +1,8 @@
export * from "./common"
import Chance from "chance"
export const generator = new Chance()
export * as koa from "./koa" export * as koa from "./koa"
export * as accounts from "./accounts"
export * as licenses from "./licenses"

View File

@ -0,0 +1,18 @@
import { AccountPlan, License, PlanType, Quotas } from "@budibase/types"
const newPlan = (type: PlanType = PlanType.FREE): AccountPlan => {
return {
type,
}
}
export const newLicense = (opts: {
quotas: Quotas
planType?: PlanType
}): License => {
return {
features: [],
quotas: opts.quotas,
plan: newPlan(opts.planType),
}
}

View File

@ -663,6 +663,11 @@
"@types/connect" "*" "@types/connect" "*"
"@types/node" "*" "@types/node" "*"
"@types/chance@1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.1.3.tgz#d19fe9391288d60fdccd87632bfc9ab2b4523fea"
integrity sha512-X6c6ghhe4/sQh4XzcZWSFaTAUOda38GQHmq9BUanYkOE/EO7ZrkazwKmtsj3xzTjkLWmwULE++23g3d3CCWaWw==
"@types/connect@*": "@types/connect@*":
version "3.4.35" version "3.4.35"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
@ -1555,6 +1560,11 @@ chalk@^4.0.0, chalk@^4.1.0:
ansi-styles "^4.1.0" ansi-styles "^4.1.0"
supports-color "^7.1.0" supports-color "^7.1.0"
chance@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.3.tgz#414f08634ee479c7a316b569050ea20751b82dd3"
integrity sha512-XeJsdoVAzDb1WRPRuMBesRSiWpW1uNTo5Fd7mYxPJsAfgX71+jfuCOHOdbyBz2uAUZ8TwKcXgWk3DMedFfJkbg==
char-regex@^1.0.2: char-regex@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"

View File

@ -17,7 +17,6 @@ import {
getProdAppDB, getProdAppDB,
getDevAppDB, getDevAppDB,
} from "@budibase/backend-core/context" } from "@budibase/backend-core/context"
import { quotas } from "@budibase/pro"
import { events } from "@budibase/backend-core" import { events } from "@budibase/backend-core"
// the max time we can wait for an invalidation to complete before considering it failed // the max time we can wait for an invalidation to complete before considering it failed

View File

@ -3,9 +3,3 @@ import { QuotaUsage } from "../../documents"
export interface GetLicenseRequest { export interface GetLicenseRequest {
quotaUsage: QuotaUsage quotaUsage: QuotaUsage
} }
export interface QuotaUsageTriggeredRequest {
percentage: number
name: string
resetDate: string
}

View File

@ -1,5 +1,12 @@
import { Feature, Hosting, PlanType, Quotas } from "../../sdk" import {
import { QuotaUsage } from "../global" Feature,
Hosting,
MonthlyQuotaName,
PlanType,
Quotas,
StaticQuotaName,
} from "../../sdk"
import { MonthlyUsage, QuotaUsage, StaticUsage } from "../global"
export interface CreateAccount { export interface CreateAccount {
email: string email: string
@ -43,7 +50,7 @@ export interface Account extends CreateAccount {
licenseKey?: string licenseKey?: string
licenseKeyActivatedAt?: number licenseKeyActivatedAt?: number
licenseOverrides?: LicenseOverrides licenseOverrides?: LicenseOverrides
quotaUsage?: QuotaUsage quotaUsage?: AccountQuotaUsage
} }
export interface PasswordAccount extends Account { export interface PasswordAccount extends Account {
@ -86,3 +93,26 @@ export interface OAuthTokens {
accessToken: string accessToken: string
refreshToken: string refreshToken: string
} }
export type QuotaTriggers = {
[key: string]: string | null
}
export interface AccountStaticUsage extends StaticUsage {
triggers?: {
[key in StaticQuotaName]?: QuotaTriggers
}
}
export interface AccountMonthlyUsage extends MonthlyUsage {
triggers?: {
[key in MonthlyQuotaName]?: QuotaTriggers
}
}
export interface AccountQuotaUsage extends QuotaUsage {
usageQuota: AccountStaticUsage
monthly: {
[key: string]: AccountMonthlyUsage
}
}

View File

@ -24,7 +24,14 @@ export interface UsageBreakdown {
} }
} }
export type MonthlyUsage = { export interface StaticUsage {
[StaticQuotaName.APPS]: number
[StaticQuotaName.PLUGINS]: number
[StaticQuotaName.USER_GROUPS]: number
[StaticQuotaName.ROWS]: number
}
export interface MonthlyUsage {
[MonthlyQuotaName.QUERIES]: number [MonthlyQuotaName.QUERIES]: number
[MonthlyQuotaName.AUTOMATIONS]: number [MonthlyQuotaName.AUTOMATIONS]: number
[MonthlyQuotaName.DAY_PASSES]: number [MonthlyQuotaName.DAY_PASSES]: number
@ -34,9 +41,7 @@ export type MonthlyUsage = {
} }
export interface BaseQuotaUsage { export interface BaseQuotaUsage {
usageQuota: { usageQuota: StaticUsage
[key in StaticQuotaName]: number
}
monthly: { monthly: {
[key: string]: MonthlyUsage [key: string]: MonthlyUsage
} }

View File

@ -61,26 +61,33 @@ export type PlanQuotas = {
[PlanType.ENTERPRISE]: Quotas [PlanType.ENTERPRISE]: Quotas
} }
export type Quotas = { export type MonthlyQuotas = {
[QuotaType.USAGE]: {
[QuotaUsageType.MONTHLY]: {
[MonthlyQuotaName.QUERIES]: Quota [MonthlyQuotaName.QUERIES]: Quota
[MonthlyQuotaName.AUTOMATIONS]: Quota [MonthlyQuotaName.AUTOMATIONS]: Quota
[MonthlyQuotaName.DAY_PASSES]: Quota [MonthlyQuotaName.DAY_PASSES]: Quota
} }
[QuotaUsageType.STATIC]: {
export type StaticQuotas = {
[StaticQuotaName.ROWS]: Quota [StaticQuotaName.ROWS]: Quota
[StaticQuotaName.APPS]: Quota [StaticQuotaName.APPS]: Quota
[StaticQuotaName.USER_GROUPS]: Quota [StaticQuotaName.USER_GROUPS]: Quota
[StaticQuotaName.PLUGINS]: Quota [StaticQuotaName.PLUGINS]: Quota
} }
}
[QuotaType.CONSTANT]: { export type ConstantQuotas = {
[ConstantQuotaName.AUTOMATION_LOG_RETENTION_DAYS]: Quota [ConstantQuotaName.AUTOMATION_LOG_RETENTION_DAYS]: Quota
} }
export type Quotas = {
[QuotaType.USAGE]: {
[QuotaUsageType.MONTHLY]: MonthlyQuotas
[QuotaUsageType.STATIC]: StaticQuotas
}
[QuotaType.CONSTANT]: ConstantQuotas
} }
export interface Quota { export interface Quota {
name: string name: string
value: number value: number
triggers: number[]
} }

View File

@ -1,58 +0,0 @@
const { StaticDatabases, doWithDB } = require("@budibase/backend-core/db")
const { getTenantId } = require("@budibase/backend-core/tenancy")
const { deleteTenant } = require("@budibase/backend-core/deprovision")
const { quotas } = require("@budibase/pro")
exports.exists = async ctx => {
const tenantId = ctx.request.params
ctx.body = {
exists: await doWithDB(StaticDatabases.PLATFORM_INFO.name, async db => {
let exists = false
try {
const tenantsDoc = await db.get(
StaticDatabases.PLATFORM_INFO.docs.tenants
)
if (tenantsDoc) {
exists = tenantsDoc.tenantIds.indexOf(tenantId) !== -1
}
} catch (err) {
// if error it doesn't exist
}
return exists
}),
}
}
exports.fetch = async ctx => {
ctx.body = await doWithDB(StaticDatabases.PLATFORM_INFO.name, async db => {
let tenants = []
try {
const tenantsDoc = await db.get(
StaticDatabases.PLATFORM_INFO.docs.tenants
)
if (tenantsDoc) {
tenants = tenantsDoc.tenantIds
}
} catch (err) {
// if error it doesn't exist
}
return tenants
})
}
exports.delete = async ctx => {
const tenantId = getTenantId()
if (ctx.params.tenantId !== tenantId) {
ctx.throw(403, "Unauthorized")
}
try {
await deleteTenant(tenantId)
await quotas.bustCache()
ctx.status = 204
} catch (err) {
ctx.log.error(err)
throw err
}
}

View File

@ -0,0 +1,66 @@
const { StaticDatabases, doWithDB } = require("@budibase/backend-core/db")
const { getTenantId } = require("@budibase/backend-core/tenancy")
const { deleteTenant } = require("@budibase/backend-core/deprovision")
import { quotas } from "@budibase/pro"
export const exists = async (ctx: any) => {
const tenantId = ctx.request.params
ctx.body = {
exists: await doWithDB(
StaticDatabases.PLATFORM_INFO.name,
async (db: any) => {
let exists = false
try {
const tenantsDoc = await db.get(
StaticDatabases.PLATFORM_INFO.docs.tenants
)
if (tenantsDoc) {
exists = tenantsDoc.tenantIds.indexOf(tenantId) !== -1
}
} catch (err) {
// if error it doesn't exist
}
return exists
}
),
}
}
export const fetch = async (ctx: any) => {
ctx.body = await doWithDB(
StaticDatabases.PLATFORM_INFO.name,
async (db: any) => {
let tenants = []
try {
const tenantsDoc = await db.get(
StaticDatabases.PLATFORM_INFO.docs.tenants
)
if (tenantsDoc) {
tenants = tenantsDoc.tenantIds
}
} catch (err) {
// if error it doesn't exist
}
return tenants
}
)
}
const _delete = async (ctx: any) => {
const tenantId = getTenantId()
if (ctx.params.tenantId !== tenantId) {
ctx.throw(403, "Unauthorized")
}
try {
await deleteTenant(tenantId)
await quotas.bustCache()
ctx.status = 204
} catch (err) {
ctx.log.error(err)
throw err
}
}
export { _delete as delete }

View File

@ -1,7 +1,6 @@
import env from "../../environment" import env from "../../environment"
import { events, accounts, tenancy } from "@budibase/backend-core" import { events, accounts, tenancy } from "@budibase/backend-core"
import { User, UserRoles, CloudAccount } from "@budibase/types" import { User, UserRoles, CloudAccount } from "@budibase/types"
import { users as pro } from "@budibase/pro"
export const handleDeleteEvents = async (user: any) => { export const handleDeleteEvents = async (user: any) => {
await events.user.deleted(user) await events.user.deleted(user)