Merge branch 'master' into feature/form-block-description

This commit is contained in:
Andrew Kingston 2023-10-24 08:56:14 +01:00 committed by GitHub
commit d82aec9f31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 94 additions and 252 deletions

View File

@ -54,6 +54,7 @@ jobs:
push: true
pull: true
platforms: linux/amd64,linux/arm64
build-args: BUDIBASE_VERSION=0.0.0+test
tags: budibase/budibase-test:test
file: ./hosting/single/Dockerfile.v2
cache-from: type=registry,ref=budibase/budibase-test:test
@ -64,6 +65,8 @@ jobs:
context: .
push: true
platforms: linux/amd64
build-args: TARGETBUILD=aas
build-args: |
TARGETBUILD=aas
BUDIBASE_VERSION=0.0.0+test
tags: budibase/budibase-test:aas
file: ./hosting/single/Dockerfile.v2

View File

@ -7,6 +7,8 @@ services:
build:
context: ..
dockerfile: packages/server/Dockerfile.v2
args:
- BUDIBASE_VERSION=0.0.0+dev-docker
container_name: build-bbapps
environment:
SELF_HOSTED: 1
@ -30,13 +32,13 @@ services:
depends_on:
- worker-service
- redis-service
# volumes:
# - /some/path/to/plugins:/plugins
worker-service:
build:
context: ..
dockerfile: packages/worker/Dockerfile.v2
args:
- BUDIBASE_VERSION=0.0.0+dev-docker
container_name: build-bbworker
environment:
SELF_HOSTED: 1

View File

@ -118,6 +118,10 @@ EXPOSE 443
EXPOSE 2222
VOLUME /data
ARG BUDIBASE_VERSION
# Ensuring the version argument is sent
RUN test -n "$BUDIBASE_VERSION"
ENV BUDIBASE_VERSION=$BUDIBASE_VERSION
HEALTHCHECK --interval=15s --timeout=15s --start-period=45s CMD "/healthcheck.sh"

View File

@ -1,5 +1,5 @@
{
"version": "2.11.41",
"version": "2.11.42",
"npmClient": "yarn",
"packages": [
"packages/*"

View File

@ -119,8 +119,8 @@ export class Writethrough {
this.writeRateMs = writeRateMs
}
async put(doc: any, writeRateMs: number = this.writeRateMs) {
return put(this.db, doc, writeRateMs)
async put(doc: any) {
return put(this.db, doc, this.writeRateMs)
}
async get(id: string) {

View File

@ -75,12 +75,12 @@ function getPackageJsonFields(): {
const content = readFileSync(packageJsonFile!, "utf-8")
const parsedContent = JSON.parse(content)
return {
VERSION: parsedContent.version,
VERSION: process.env.BUDIBASE_VERSION || parsedContent.version,
SERVICE_NAME: parsedContent.name,
}
} catch {
// throwing an error here is confusing/causes backend-core to be hard to import
return { VERSION: "", SERVICE_NAME: "" }
return { VERSION: process.env.BUDIBASE_VERSION || "", SERVICE_NAME: "" }
}
}

View File

@ -25,17 +25,12 @@ import {
import {
getAccountHolderFromUserIds,
isAdmin,
isCreator,
validateUniqueUser,
} from "./utils"
import { searchExistingEmails } from "./lookup"
import { hash } from "../utils"
type QuotaUpdateFn = (
change: number,
creatorsChange: number,
cb?: () => Promise<any>
) => Promise<any>
type QuotaUpdateFn = (change: number, cb?: () => Promise<any>) => Promise<any>
type GroupUpdateFn = (groupId: string, userIds: string[]) => Promise<any>
type FeatureFn = () => Promise<Boolean>
type GroupGetFn = (ids: string[]) => Promise<UserGroup[]>
@ -250,8 +245,7 @@ export class UserDB {
}
const change = dbUser ? 0 : 1 // no change if there is existing user
const creatorsChange = isCreator(dbUser) !== isCreator(user) ? 1 : 0
return UserDB.quotas.addUsers(change, creatorsChange, async () => {
return UserDB.quotas.addUsers(change, async () => {
await validateUniqueUser(email, tenantId)
let builtUser = await UserDB.buildUser(user, opts, tenantId, dbUser)
@ -313,7 +307,6 @@ export class UserDB {
let usersToSave: any[] = []
let newUsers: any[] = []
let newCreators: any[] = []
const emails = newUsersRequested.map((user: User) => user.email)
const existingEmails = await searchExistingEmails(emails)
@ -334,66 +327,59 @@ export class UserDB {
}
newUser.userGroups = groups
newUsers.push(newUser)
if (isCreator(newUser)) {
newCreators.push(newUser)
}
}
const account = await accountSdk.getAccountByTenantId(tenantId)
return UserDB.quotas.addUsers(
newUsers.length,
newCreators.length,
async () => {
// create the promises array that will be called by bulkDocs
newUsers.forEach((user: any) => {
usersToSave.push(
UserDB.buildUser(
user,
{
hashPassword: true,
requirePassword: user.requirePassword,
},
tenantId,
undefined, // no dbUser
account
)
return UserDB.quotas.addUsers(newUsers.length, async () => {
// create the promises array that will be called by bulkDocs
newUsers.forEach((user: any) => {
usersToSave.push(
UserDB.buildUser(
user,
{
hashPassword: true,
requirePassword: user.requirePassword,
},
tenantId,
undefined, // no dbUser
account
)
})
)
})
const usersToBulkSave = await Promise.all(usersToSave)
await usersCore.bulkUpdateGlobalUsers(usersToBulkSave)
const usersToBulkSave = await Promise.all(usersToSave)
await usersCore.bulkUpdateGlobalUsers(usersToBulkSave)
// Post-processing of bulk added users, e.g. events and cache operations
for (const user of usersToBulkSave) {
// TODO: Refactor to bulk insert users into the info db
// instead of relying on looping tenant creation
await platform.users.addUser(tenantId, user._id, user.email)
await eventHelpers.handleSaveEvents(user, undefined)
}
const saved = usersToBulkSave.map(user => {
return {
_id: user._id,
email: user.email,
}
})
// now update the groups
if (Array.isArray(saved) && groups) {
const groupPromises = []
const createdUserIds = saved.map(user => user._id)
for (let groupId of groups) {
groupPromises.push(UserDB.groups.addUsers(groupId, createdUserIds))
}
await Promise.all(groupPromises)
}
return {
successful: saved,
unsuccessful,
}
// Post-processing of bulk added users, e.g. events and cache operations
for (const user of usersToBulkSave) {
// TODO: Refactor to bulk insert users into the info db
// instead of relying on looping tenant creation
await platform.users.addUser(tenantId, user._id, user.email)
await eventHelpers.handleSaveEvents(user, undefined)
}
)
const saved = usersToBulkSave.map(user => {
return {
_id: user._id,
email: user.email,
}
})
// now update the groups
if (Array.isArray(saved) && groups) {
const groupPromises = []
const createdUserIds = saved.map(user => user._id)
for (let groupId of groups) {
groupPromises.push(UserDB.groups.addUsers(groupId, createdUserIds))
}
await Promise.all(groupPromises)
}
return {
successful: saved,
unsuccessful,
}
})
}
static async bulkDelete(userIds: string[]): Promise<BulkUserDeleted> {
@ -433,12 +419,11 @@ export class UserDB {
_deleted: true,
}))
const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete)
const creatorsToDelete = usersToDelete.filter(isCreator)
await UserDB.quotas.removeUsers(toDelete.length)
for (let user of usersToDelete) {
await bulkDeleteProcessing(user)
}
await UserDB.quotas.removeUsers(toDelete.length, creatorsToDelete.length)
// Build Response
// index users by id
@ -487,8 +472,7 @@ export class UserDB {
await db.remove(userId, dbUser._rev)
const creatorsToDelete = isCreator(dbUser) ? 1 : 0
await UserDB.quotas.removeUsers(1, creatorsToDelete)
await UserDB.quotas.removeUsers(1)
await eventHelpers.handleDeleteEvents(dbUser)
await cache.user.invalidateUser(userId)
await sessions.invalidateSessions(userId, { reason: "deletion" })

View File

@ -14,16 +14,15 @@ import {
} from "../db"
import {
BulkDocsResponse,
ContextUser,
SearchQuery,
SearchQueryOperators,
SearchUsersRequest,
User,
ContextUser,
DatabaseQueryOpts,
} from "@budibase/types"
import { getGlobalDB } from "../context"
import * as context from "../context"
import { isCreator } from "./utils"
import { getGlobalDB } from "../context"
type GetOpts = { cleanup?: boolean }
@ -287,19 +286,6 @@ export async function getUserCount() {
return response.total_rows
}
export async function getCreatorCount() {
let creators = 0
async function iterate(startPage?: string) {
const page = await paginatedUsers({ bookmark: startPage })
creators += page.data.filter(isCreator).length
if (page.hasNextPage) {
await iterate(page.nextPage)
}
}
await iterate()
return creators
}
// used to remove the builder/admin permissions, for processing the
// user as an app user (they may have some specific role/group
export function removePortalUserPermissions(user: User | ContextUser) {

View File

@ -10,7 +10,6 @@ import { getAccountByTenantId } from "../accounts"
// extract from shared-core to make easily accessible from backend-core
export const isBuilder = sdk.users.isBuilder
export const isAdmin = sdk.users.isAdmin
export const isCreator = sdk.users.isCreator
export const isGlobalBuilder = sdk.users.isGlobalBuilder
export const isAdminOrBuilder = sdk.users.isAdminOrBuilder
export const hasAdminPermissions = sdk.users.hasAdminPermissions

View File

@ -1,54 +0,0 @@
const _ = require('lodash/fp')
const {structures} = require("../../../tests")
jest.mock("../../../src/context")
jest.mock("../../../src/db")
const context = require("../../../src/context")
const db = require("../../../src/db")
const {getCreatorCount} = require('../../../src/users/users')
describe("Users", () => {
let getGlobalDBMock
let getGlobalUserParamsMock
let paginationMock
beforeEach(() => {
jest.resetAllMocks()
getGlobalDBMock = jest.spyOn(context, "getGlobalDB")
getGlobalUserParamsMock = jest.spyOn(db, "getGlobalUserParams")
paginationMock = jest.spyOn(db, "pagination")
})
it("Retrieves the number of creators", async () => {
const getUsers = (offset, limit, creators = false) => {
const range = _.range(offset, limit)
const opts = creators ? {builder: {global: true}} : undefined
return range.map(() => structures.users.user(opts))
}
const page1Data = getUsers(0, 8)
const page2Data = getUsers(8, 12, true)
getGlobalDBMock.mockImplementation(() => ({
name : "fake-db",
allDocs: () => ({
rows: [...page1Data, ...page2Data]
})
}))
paginationMock.mockImplementationOnce(() => ({
data: page1Data,
hasNextPage: true,
nextPage: "1"
}))
paginationMock.mockImplementation(() => ({
data: page2Data,
hasNextPage: false,
nextPage: undefined
}))
const creatorsCount = await getCreatorCount()
expect(creatorsCount).toBe(4)
expect(paginationMock).toHaveBeenCalledTimes(2)
})
})

View File

@ -72,11 +72,6 @@ export function quotas(): Quotas {
value: 1,
triggers: [],
},
creators: {
name: "Creators",
value: 1,
triggers: [],
},
userGroups: {
name: "User Groups",
value: 1,
@ -123,10 +118,6 @@ export function customer(): Customer {
export function subscription(): Subscription {
return {
amount: 10000,
amounts: {
user: 10000,
creator: 0,
},
cancelAt: undefined,
currency: "usd",
currentPeriodEnd: 0,
@ -135,10 +126,6 @@ export function subscription(): Subscription {
duration: PriceDuration.MONTHLY,
pastDueAt: undefined,
quantity: 0,
quantities: {
user: 0,
creator: 0,
},
status: "active",
}
}

View File

@ -1,6 +1,6 @@
import { MonthlyQuotaName, QuotaUsage } from "@budibase/types"
export const usage = (users: number = 0, creators: number = 0): QuotaUsage => {
export const usage = (): QuotaUsage => {
return {
_id: "usage_quota",
quotaReset: new Date().toISOString(),
@ -58,8 +58,7 @@ export const usage = (users: number = 0, creators: number = 0): QuotaUsage => {
usageQuota: {
apps: 0,
plugins: 0,
users,
creators,
users: 0,
userGroups: 0,
rows: 0,
triggers: {},

@ -1 +1 @@
Subproject commit 56d968bfe6998e1077d3fce4eb1c9e483d1d6fc9
Subproject commit f7e7cffe422086d9449c2075a74a378c16caff9d

View File

@ -67,6 +67,11 @@ COPY packages/server/docker_run.sh .
COPY packages/server/builder/ builder/
COPY packages/server/client/ client/
ARG BUDIBASE_VERSION
# Ensuring the version argument is sent
RUN test -n "$BUDIBASE_VERSION"
ENV BUDIBASE_VERSION=$BUDIBASE_VERSION
EXPOSE 4001
# have to add node environment production after install

View File

@ -18,7 +18,7 @@
"test": "bash scripts/test.sh",
"test:memory": "jest --maxWorkers=2 --logHeapUsage --forceExit",
"test:watch": "jest --watch",
"build:docker": "yarn build && docker build . -t app-service --label version=$BUDIBASE_RELEASE_VERSION",
"build:docker": "yarn build && docker build . -t app-service --label version=$BUDIBASE_RELEASE_VERSION --build-arg BUDIBASE_VERSION=$BUDIBASE_RELEASE_VERSION",
"run:docker": "node dist/index.js",
"run:docker:cluster": "pm2-runtime start pm2.config.js",
"dev:stack:up": "node scripts/dev/manage.js up",

View File

@ -47,6 +47,7 @@ async function init() {
TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR",
HTTP_MIGRATIONS: "0",
HTTP_LOGGING: "0",
VERSION: "0.0.0+local",
}
let envFile = ""
Object.keys(envFileJson).forEach(key => {

View File

@ -41,7 +41,7 @@ describe("/component", () => {
.expect("Content-Type", /json/)
.expect(200)
expect(res.body).toEqual({
budibaseVersion: "0.0.0",
budibaseVersion: "0.0.0+jest",
cpuArch: "arm64",
cpuCores: 1,
cpuInfo: "test",

View File

@ -1,6 +1,6 @@
const setup = require("./utilities")
const { events } = require("@budibase/backend-core")
const version = require("../../../../package.json").version
describe("/dev", () => {
let request = setup.getRequest()
@ -32,9 +32,9 @@ describe("/dev", () => {
.expect("Content-Type", /json/)
.expect(200)
expect(res.body.version).toBe(version)
expect(res.body.version).toBe('0.0.0+jest')
expect(events.installation.versionChecked).toBeCalledTimes(1)
expect(events.installation.versionChecked).toBeCalledWith(version)
expect(events.installation.versionChecked).toBeCalledWith('0.0.0+jest')
})
})
})

View File

@ -3,7 +3,6 @@ import * as syncApps from "./usageQuotas/syncApps"
import * as syncRows from "./usageQuotas/syncRows"
import * as syncPlugins from "./usageQuotas/syncPlugins"
import * as syncUsers from "./usageQuotas/syncUsers"
import * as syncCreators from "./usageQuotas/syncCreators"
/**
* Synchronise quotas to the state of the db.
@ -14,6 +13,5 @@ export const run = async () => {
await syncRows.run()
await syncPlugins.run()
await syncUsers.run()
await syncCreators.run()
})
}

View File

@ -1,13 +0,0 @@
import { users } from "@budibase/backend-core"
import { quotas } from "@budibase/pro"
import { QuotaUsageType, StaticQuotaName } from "@budibase/types"
export const run = async () => {
const creatorCount = await users.getCreatorCount()
console.log(`Syncing creator count: ${creatorCount}`)
await quotas.setUsage(
creatorCount,
StaticQuotaName.CREATORS,
QuotaUsageType.STATIC
)
}

View File

@ -1,26 +0,0 @@
import TestConfig from "../../../../tests/utilities/TestConfiguration"
import * as syncCreators from "../syncCreators"
import { quotas } from "@budibase/pro"
describe("syncCreators", () => {
let config = new TestConfig(false)
beforeEach(async () => {
await config.init()
})
afterAll(config.end)
it("syncs creators", async () => {
return config.doInContext(null, async () => {
await config.createUser({ admin: true })
await syncCreators.run()
const usageDoc = await quotas.getQuotaUsage()
// default + additional creator
const creatorsCount = 2
expect(usageDoc.usageQuota.creators).toBe(creatorsCount)
})
})
})

View File

@ -9,3 +9,4 @@ process.env.LOG_LEVEL = process.env.LOG_LEVEL || "error"
process.env.MOCK_REDIS = "1"
process.env.PLATFORM_URL = "http://localhost:10000"
process.env.REDIS_PASSWORD = "budibase"
process.env.BUDIBASE_VERSION = "0.0.0+jest"

View File

@ -6,7 +6,6 @@ import {
InternalTable,
} from "@budibase/types"
import { getProdAppID } from "./applications"
import * as _ from "lodash/fp"
// checks if a user is specifically a builder, given an app ID
export function isBuilder(user: User | ContextUser, appId?: string): boolean {
@ -59,18 +58,6 @@ export function hasAppBuilderPermissions(user?: User | ContextUser): boolean {
return !isGlobalBuilder && appLength != null && appLength > 0
}
export function hasAppCreatorPermissions(user?: User | ContextUser): boolean {
if (!user) {
return false
}
return _.flow(
_.get("roles"),
_.values,
_.find(x => ["CREATOR", "ADMIN"].includes(x)),
x => !!x
)(user)
}
// checks if a user is capable of building any app
export function hasBuilderPermissions(user?: User | ContextUser): boolean {
if (!user) {
@ -87,18 +74,6 @@ export function hasAdminPermissions(user?: User | ContextUser): boolean {
return !!user.admin?.global
}
export function isCreator(user?: User | ContextUser): boolean {
if (!user) {
return false
}
return (
isGlobalBuilder(user) ||
hasAdminPermissions(user) ||
hasAppBuilderPermissions(user) ||
hasAppCreatorPermissions(user)
)
}
export function getGlobalUserID(userId?: string): string | undefined {
if (typeof userId !== "string") {
return userId

View File

@ -32,7 +32,6 @@ export interface StaticUsage {
[StaticQuotaName.APPS]: number
[StaticQuotaName.PLUGINS]: number
[StaticQuotaName.USERS]: number
[StaticQuotaName.CREATORS]: number
[StaticQuotaName.USER_GROUPS]: number
[StaticQuotaName.ROWS]: number
triggers: {

View File

@ -1,8 +1,5 @@
export enum FeatureFlag {
LICENSING = "LICENSING",
// Feature IDs in Posthog
PER_CREATOR_PER_USER_PRICE = "18873",
PER_CREATOR_PER_USER_PRICE_ALERT = "18530",
}
export interface TenantFeatureFlags {

View File

@ -5,17 +5,10 @@ export interface Customer {
currency: string | null | undefined
}
export interface SubscriptionItems {
user: number | undefined
creator: number | undefined
}
export interface Subscription {
amount: number
amounts: SubscriptionItems | undefined
currency: string
quantity: number
quantities: SubscriptionItems | undefined
duration: PriceDuration
cancelAt: number | null | undefined
currentPeriodStart: number

View File

@ -4,9 +4,7 @@ export enum PlanType {
PRO = "pro",
/** @deprecated */
TEAM = "team",
/** @deprecated */
PREMIUM = "premium",
PREMIUM_PLUS = "premium_plus",
BUSINESS = "business",
ENTERPRISE = "enterprise",
}
@ -28,12 +26,10 @@ export interface AvailablePrice {
currency: string
duration: PriceDuration
priceId: string
type?: string
}
export enum PlanModel {
PER_USER = "perUser",
PER_CREATOR_PER_USER = "per_creator_per_user",
DAY_PASS = "dayPass",
}

View File

@ -14,7 +14,6 @@ export enum StaticQuotaName {
ROWS = "rows",
APPS = "apps",
USERS = "users",
CREATORS = "creators",
USER_GROUPS = "userGroups",
PLUGINS = "plugins",
}
@ -68,7 +67,6 @@ export type StaticQuotas = {
[StaticQuotaName.ROWS]: Quota
[StaticQuotaName.APPS]: Quota
[StaticQuotaName.USERS]: Quota
[StaticQuotaName.CREATORS]: Quota
[StaticQuotaName.USER_GROUPS]: Quota
[StaticQuotaName.PLUGINS]: Quota
}

View File

@ -50,4 +50,9 @@ ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR
ENV ACCOUNT_PORTAL_URL=https://account.budibase.app
ARG BUDIBASE_VERSION
# Ensuring the version argument is sent
RUN test -n "$BUDIBASE_VERSION"
ENV BUDIBASE_VERSION=$BUDIBASE_VERSION
CMD ["./docker_run.sh"]

View File

@ -20,7 +20,7 @@
"run:docker": "node dist/index.js",
"debug": "yarn build && node --expose-gc --inspect=9223 dist/index.js",
"run:docker:cluster": "pm2-runtime start pm2.config.js",
"build:docker": "yarn build && docker build . -t worker-service --label version=$BUDIBASE_RELEASE_VERSION",
"build:docker": "yarn build && docker build . -t worker-service --label version=$BUDIBASE_RELEASE_VERSION --build-arg BUDIBASE_VERSION=$BUDIBASE_RELEASE_VERSION",
"dev:stack:init": "node ./scripts/dev/manage.js init",
"dev:builder": "npm run dev:stack:init && nodemon",
"dev:built": "yarn run dev:stack:init && yarn run run:docker",

View File

@ -31,6 +31,7 @@ async function init() {
TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR",
ENABLE_EMAIL_TEST_MODE: 1,
HTTP_LOGGING: 0,
VERSION: "0.0.0+local",
}
let envFile = ""
Object.keys(envFileJson).forEach(key => {

View File

@ -10,3 +10,4 @@ process.env.PLATFORM_URL = "http://localhost:10000"
process.env.INTERNAL_API_KEY = "tet"
process.env.DISABLE_ACCOUNT_PORTAL = "0"
process.env.MOCK_REDIS = "1"
process.env.BUDIBASE_VERSION = "0.0.0+jest"

View File

@ -1,3 +1,4 @@
#!/bin/bash
yarn build --scope @budibase/server --scope @budibase/worker
docker build -f hosting/single/Dockerfile.v2 -t budibase:latest .
version=$(./scripts/getCurrentVersion.sh)
docker build -f hosting/single/Dockerfile.v2 -t budibase:latest --build-arg BUDIBASE_VERSION=$version .