From 648410348d2c2f0c9a3ce1e2d2a34b0ca4f5f80e Mon Sep 17 00:00:00 2001 From: jvcalderon Date: Mon, 23 Oct 2023 13:27:59 +0200 Subject: [PATCH 1/4] [Revert] [Added] Per user per creator changes --- .../backend-core/src/cache/writethrough.ts | 4 +- packages/backend-core/src/users/db.ts | 116 ++++++++---------- packages/backend-core/src/users/users.ts | 13 -- packages/backend-core/src/users/utils.ts | 1 - .../tests/core/users/users.spec.js | 54 -------- .../core/utilities/structures/licenses.ts | 13 -- .../tests/core/utilities/structures/quotas.ts | 5 +- packages/pro | 2 +- .../src/migrations/functions/syncQuotas.ts | 2 - .../functions/usageQuotas/syncCreators.ts | 13 -- .../usageQuotas/tests/syncCreators.spec.ts | 26 ---- .../shared-core/src/sdk/documents/users.ts | 25 ---- packages/types/src/documents/global/quotas.ts | 1 - packages/types/src/sdk/featureFlag.ts | 3 - packages/types/src/sdk/licensing/billing.ts | 7 -- packages/types/src/sdk/licensing/plan.ts | 4 - packages/types/src/sdk/licensing/quota.ts | 2 - 17 files changed, 55 insertions(+), 236 deletions(-) delete mode 100644 packages/backend-core/tests/core/users/users.spec.js delete mode 100644 packages/server/src/migrations/functions/usageQuotas/syncCreators.ts delete mode 100644 packages/server/src/migrations/functions/usageQuotas/tests/syncCreators.spec.ts diff --git a/packages/backend-core/src/cache/writethrough.ts b/packages/backend-core/src/cache/writethrough.ts index c331d791a6..e64c116663 100644 --- a/packages/backend-core/src/cache/writethrough.ts +++ b/packages/backend-core/src/cache/writethrough.ts @@ -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) { diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts index daa09bee6f..a2539e836e 100644 --- a/packages/backend-core/src/users/db.ts +++ b/packages/backend-core/src/users/db.ts @@ -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 -) => Promise +type QuotaUpdateFn = (change: number, cb?: () => Promise) => Promise type GroupUpdateFn = (groupId: string, userIds: string[]) => Promise type FeatureFn = () => Promise type GroupGetFn = (ids: string[]) => Promise @@ -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 { @@ -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" }) diff --git a/packages/backend-core/src/users/users.ts b/packages/backend-core/src/users/users.ts index bad108ab84..30b9e0a45f 100644 --- a/packages/backend-core/src/users/users.ts +++ b/packages/backend-core/src/users/users.ts @@ -287,19 +287,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) { diff --git a/packages/backend-core/src/users/utils.ts b/packages/backend-core/src/users/utils.ts index 0ef4b77998..af0e8e10c7 100644 --- a/packages/backend-core/src/users/utils.ts +++ b/packages/backend-core/src/users/utils.ts @@ -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 diff --git a/packages/backend-core/tests/core/users/users.spec.js b/packages/backend-core/tests/core/users/users.spec.js deleted file mode 100644 index ae7109344a..0000000000 --- a/packages/backend-core/tests/core/users/users.spec.js +++ /dev/null @@ -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) - }) -}) diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts index bb452f9ad5..5cce84edfd 100644 --- a/packages/backend-core/tests/core/utilities/structures/licenses.ts +++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts @@ -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", } } diff --git a/packages/backend-core/tests/core/utilities/structures/quotas.ts b/packages/backend-core/tests/core/utilities/structures/quotas.ts index 8d0b05fe1e..e82117053f 100644 --- a/packages/backend-core/tests/core/utilities/structures/quotas.ts +++ b/packages/backend-core/tests/core/utilities/structures/quotas.ts @@ -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: {}, diff --git a/packages/pro b/packages/pro index 56d968bfe6..1e43861b7e 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 56d968bfe6998e1077d3fce4eb1c9e483d1d6fc9 +Subproject commit 1e43861b7ed7141284ab63a0cf5bd494ec3ab2d1 diff --git a/packages/server/src/migrations/functions/syncQuotas.ts b/packages/server/src/migrations/functions/syncQuotas.ts index 83a7670e78..67f38ba929 100644 --- a/packages/server/src/migrations/functions/syncQuotas.ts +++ b/packages/server/src/migrations/functions/syncQuotas.ts @@ -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() }) } diff --git a/packages/server/src/migrations/functions/usageQuotas/syncCreators.ts b/packages/server/src/migrations/functions/usageQuotas/syncCreators.ts deleted file mode 100644 index ce53be925a..0000000000 --- a/packages/server/src/migrations/functions/usageQuotas/syncCreators.ts +++ /dev/null @@ -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 - ) -} diff --git a/packages/server/src/migrations/functions/usageQuotas/tests/syncCreators.spec.ts b/packages/server/src/migrations/functions/usageQuotas/tests/syncCreators.spec.ts deleted file mode 100644 index 75fa9f217e..0000000000 --- a/packages/server/src/migrations/functions/usageQuotas/tests/syncCreators.spec.ts +++ /dev/null @@ -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) - }) - }) -}) diff --git a/packages/shared-core/src/sdk/documents/users.ts b/packages/shared-core/src/sdk/documents/users.ts index b58994aa46..03d86daa85 100644 --- a/packages/shared-core/src/sdk/documents/users.ts +++ b/packages/shared-core/src/sdk/documents/users.ts @@ -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 diff --git a/packages/types/src/documents/global/quotas.ts b/packages/types/src/documents/global/quotas.ts index 4eb1168f7d..61410f7435 100644 --- a/packages/types/src/documents/global/quotas.ts +++ b/packages/types/src/documents/global/quotas.ts @@ -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: { diff --git a/packages/types/src/sdk/featureFlag.ts b/packages/types/src/sdk/featureFlag.ts index e3935bc7ee..53aa4842c4 100644 --- a/packages/types/src/sdk/featureFlag.ts +++ b/packages/types/src/sdk/featureFlag.ts @@ -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 { diff --git a/packages/types/src/sdk/licensing/billing.ts b/packages/types/src/sdk/licensing/billing.ts index bcbc7abd18..35f366c811 100644 --- a/packages/types/src/sdk/licensing/billing.ts +++ b/packages/types/src/sdk/licensing/billing.ts @@ -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 diff --git a/packages/types/src/sdk/licensing/plan.ts b/packages/types/src/sdk/licensing/plan.ts index 1604dfb8af..3e214a01ff 100644 --- a/packages/types/src/sdk/licensing/plan.ts +++ b/packages/types/src/sdk/licensing/plan.ts @@ -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", } diff --git a/packages/types/src/sdk/licensing/quota.ts b/packages/types/src/sdk/licensing/quota.ts index 85700f167b..73afa1ed05 100644 --- a/packages/types/src/sdk/licensing/quota.ts +++ b/packages/types/src/sdk/licensing/quota.ts @@ -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 } From a285fb1d4027ca70d77a953ba16bcb74179ba8e1 Mon Sep 17 00:00:00 2001 From: jvcalderon Date: Mon, 23 Oct 2023 13:33:27 +0200 Subject: [PATCH 2/4] Missed code in merge --- packages/backend-core/src/users/users.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/backend-core/src/users/users.ts b/packages/backend-core/src/users/users.ts index 30b9e0a45f..d259d5b560 100644 --- a/packages/backend-core/src/users/users.ts +++ b/packages/backend-core/src/users/users.ts @@ -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 } From a22aea63a372f32a9a2089fd783cd72ced667378 Mon Sep 17 00:00:00 2001 From: jvcalderon Date: Mon, 23 Oct 2023 13:33:56 +0200 Subject: [PATCH 3/4] Update pro submodule --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 1e43861b7e..ee222c4694 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 1e43861b7ed7141284ab63a0cf5bd494ec3ab2d1 +Subproject commit ee222c4694c71716eeb1257f37467fc2231ee166 From bc4dd8e1c99fafb335dc79daa78e47ded56e9498 Mon Sep 17 00:00:00 2001 From: jvcalderon Date: Mon, 23 Oct 2023 15:43:44 +0200 Subject: [PATCH 4/4] Update pro submodule --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index ee222c4694..f7e7cffe42 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit ee222c4694c71716eeb1257f37467fc2231ee166 +Subproject commit f7e7cffe422086d9449c2075a74a378c16caff9d