refactor(workspace, users, billing): remove default workspace + rename (#9360)

Replaced user-based parameterization with workspace-focused logic across
seed scripts, mocks, and billing services. Removed redundant `user`
references and standardized to `workspace` to align with updated
business rules. Adjusted mock data and tests to reflect these changes.

Fix https://github.com/twentyhq/twenty/issues/9295
This commit is contained in:
Antoine Moreaux
2025-01-06 12:33:57 +01:00
committed by GitHub
parent 25083e5405
commit 3eb7ec909e
12 changed files with 35 additions and 51 deletions

View File

@ -10,7 +10,7 @@ import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { getCompaniesMock } from '~/testing/mock-data/companies'; import { getCompaniesMock } from '~/testing/mock-data/companies';
import { import {
mockDefaultWorkspace, mockCurrentWorkspace,
mockedWorkspaceMemberData, mockedWorkspaceMemberData,
} from '~/testing/mock-data/users'; } from '~/testing/mock-data/users';
import { sleep } from '~/utils/sleep'; import { sleep } from '~/utils/sleep';
@ -54,7 +54,7 @@ const meta: Meta<typeof CommandMenu> = {
isCommandMenuOpenedState, isCommandMenuOpenedState,
); );
setCurrentWorkspace(mockDefaultWorkspace); setCurrentWorkspace(mockCurrentWorkspace);
setCurrentWorkspaceMember(mockedWorkspaceMemberData); setCurrentWorkspaceMember(mockedWorkspaceMemberData);
setIsCommandMenuOpened(true); setIsCommandMenuOpened(true);

View File

@ -13,7 +13,7 @@ import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadat
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { import {
mockDefaultWorkspace, mockCurrentWorkspace,
mockedWorkspaceMemberData, mockedWorkspaceMemberData,
} from '~/testing/mock-data/users'; } from '~/testing/mock-data/users';
@ -26,7 +26,7 @@ const RelationWorkspaceSetterEffect = () => {
); );
useEffect(() => { useEffect(() => {
setCurrentWorkspace(mockDefaultWorkspace); setCurrentWorkspace(mockCurrentWorkspace);
setCurrentWorkspaceMember(mockedWorkspaceMemberData); setCurrentWorkspaceMember(mockedWorkspaceMemberData);
}, [setCurrentWorkspace, setCurrentWorkspaceMember]); }, [setCurrentWorkspace, setCurrentWorkspaceMember]);

View File

@ -13,7 +13,7 @@ import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadat
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { import {
mockDefaultWorkspace, mockCurrentWorkspace,
mockedWorkspaceMemberData, mockedWorkspaceMemberData,
} from '~/testing/mock-data/users'; } from '~/testing/mock-data/users';
@ -32,7 +32,7 @@ const RelationWorkspaceSetterEffect = () => {
); );
useEffect(() => { useEffect(() => {
setCurrentWorkspace(mockDefaultWorkspace); setCurrentWorkspace(mockCurrentWorkspace);
setCurrentWorkspaceMember(mockedWorkspaceMemberData); setCurrentWorkspaceMember(mockedWorkspaceMemberData);
}, [setCurrentWorkspace, setCurrentWorkspaceMember]); }, [setCurrentWorkspace, setCurrentWorkspaceMember]);

View File

@ -7,7 +7,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus'; import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { OnboardingStatus, SubscriptionStatus } from '~/generated/graphql'; import { OnboardingStatus, SubscriptionStatus } from '~/generated/graphql';
import { import {
mockDefaultWorkspace, mockCurrentWorkspace,
mockedUserData, mockedUserData,
} from '~/testing/mock-data/users'; } from '~/testing/mock-data/users';
@ -35,7 +35,7 @@ const renderHooks = (
act(() => { act(() => {
result.current.setCurrentUser({ ...mockedUserData, onboardingStatus }); result.current.setCurrentUser({ ...mockedUserData, onboardingStatus });
result.current.setCurrentWorkspace({ result.current.setCurrentWorkspace({
...mockDefaultWorkspace, ...mockCurrentWorkspace,
currentBillingSubscription: withCurrentBillingSubscription currentBillingSubscription: withCurrentBillingSubscription
? { id: v4(), status: SubscriptionStatus.Active } ? { id: v4(), status: SubscriptionStatus.Active }
: undefined, : undefined,

View File

@ -9,7 +9,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { supportChatState } from '@/client-config/states/supportChatState'; import { supportChatState } from '@/client-config/states/supportChatState';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { import {
mockDefaultWorkspace, mockCurrentWorkspace,
mockedUserData, mockedUserData,
mockedWorkspaceMemberData, mockedWorkspaceMemberData,
} from '~/testing/mock-data/users'; } from '~/testing/mock-data/users';
@ -29,7 +29,7 @@ const meta: Meta<typeof SupportDropdown> = {
currentWorkspaceMemberState, currentWorkspaceMemberState,
); );
setCurrentWorkspace(mockDefaultWorkspace); setCurrentWorkspace(mockCurrentWorkspace);
setCurrentWorkspaceMember(mockedWorkspaceMemberData); setCurrentWorkspaceMember(mockedWorkspaceMemberData);
setCurrentUser(mockedUserData); setCurrentUser(mockedUserData);
setSupportChat({ supportDriver: 'front', supportFrontChatId: '1234' }); setSupportChat({ supportDriver: 'front', supportFrontChatId: '1234' });

View File

@ -36,7 +36,7 @@ export const avatarUrl =
export const workspaceLogoUrl = export const workspaceLogoUrl =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACb0lEQVR4nO2VO4taQRTHr3AblbjxEVlwCwVhg7BoqqCIjy/gAyyFWNlYBOxsfH0KuxgQGwXRUkGuL2S7i1barGAgiwbdW93SnGOc4BonPiKahf3DwXFmuP/fPM4ZlvmlTxAhCBdzHnEQWYiv7Mr4C3NeuVYhQYDPzOUUQgDLBQGcLHNhvQK8DACPx8PTxiqVyvISG43GbyaT6Qfpn06n0m63e/tPAPF4vJ1MJu8kEsnWTCkWi1yr1RKGw+GDRqPBOTfr44vFQvD7/Q/lcpmaaVQAr9fLp1IpO22c47hGOBz+MB6PH+Vy+VYDAL8qlUoGtVotzOfzq4MAgsHgE/6KojiQyWR/bKVSqbSszHFM8Pl8z1YK48JsNltCOBwOnrYLO+8AAIjb+nHbycoTiUQfDJ7tFq4YAHiVSmXBxcD41u8flQU8z7fhzO0r83atVns3Go3u9Xr9x0O/RQXo9/tsIBBg6vX606a52Wz+bZ7P5/WwG29gxSJzhKgA6XTaDoFNF+krFAocmC//4yWEcSf2wTm7mCO19xFgSsKOLI16vV7b7XY7mRNoLwA0JymJ5uQIzgIAuX5PzDElT2m+E8BqtQ4ymcx7Yq7T6a6ZE4sKgOadTucaCwkxp1UzlEKh0GDxIXOwDWHAdi6Xe3swQDQa/Q7mywoolUpvsaptymazDWKxmBHTlWXZm405BFZoNpuGgwEmk4mE2SGtVivii4f1AO7J3ZopkQCQj7Ar1FeRChCJRJzVapX6DKNIfSc1Ax+wtQWQ55h6bH8FWDfYV4fO3wlwDr0C/BcADYiTPCxHqIEA2QsCZAkAKnRGkMbKN/sTX5YHPQ1e7SkAAAAASUVORK5CYII='; 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACb0lEQVR4nO2VO4taQRTHr3AblbjxEVlwCwVhg7BoqqCIjy/gAyyFWNlYBOxsfH0KuxgQGwXRUkGuL2S7i1barGAgiwbdW93SnGOc4BonPiKahf3DwXFmuP/fPM4ZlvmlTxAhCBdzHnEQWYiv7Mr4C3NeuVYhQYDPzOUUQgDLBQGcLHNhvQK8DACPx8PTxiqVyvISG43GbyaT6Qfpn06n0m63e/tPAPF4vJ1MJu8kEsnWTCkWi1yr1RKGw+GDRqPBOTfr44vFQvD7/Q/lcpmaaVQAr9fLp1IpO22c47hGOBz+MB6PH+Vy+VYDAL8qlUoGtVotzOfzq4MAgsHgE/6KojiQyWR/bKVSqbSszHFM8Pl8z1YK48JsNltCOBwOnrYLO+8AAIjb+nHbycoTiUQfDJ7tFq4YAHiVSmXBxcD41u8flQU8z7fhzO0r83atVns3Go3u9Xr9x0O/RQXo9/tsIBBg6vX606a52Wz+bZ7P5/WwG29gxSJzhKgA6XTaDoFNF+krFAocmC//4yWEcSf2wTm7mCO19xFgSsKOLI16vV7b7XY7mRNoLwA0JymJ5uQIzgIAuX5PzDElT2m+E8BqtQ4ymcx7Yq7T6a6ZE4sKgOadTucaCwkxp1UzlEKh0GDxIXOwDWHAdi6Xe3swQDQa/Q7mywoolUpvsaptymazDWKxmBHTlWXZm405BFZoNpuGgwEmk4mE2SGtVivii4f1AO7J3ZopkQCQj7Ar1FeRChCJRJzVapX6DKNIfSc1Ax+wtQWQ55h6bH8FWDfYV4fO3wlwDr0C/BcADYiTPCxHqIEA2QsCZAkAKnRGkMbKN/sTX5YHPQ1e7SkAAAAASUVORK5CYII=';
export const mockDefaultWorkspace: Workspace = { export const mockCurrentWorkspace: Workspace = {
subdomain: 'acme.twenty.com', subdomain: 'acme.twenty.com',
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6w', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6w',
displayName: 'Twenty', displayName: 'Twenty',
@ -107,9 +107,9 @@ export const mockedUserData: MockedUser = {
supportUserHash: supportUserHash:
'a95afad9ff6f0b364e2a3fd3e246a1a852c22b6e55a3ca33745a86c201f9c10d', 'a95afad9ff6f0b364e2a3fd3e246a1a852c22b6e55a3ca33745a86c201f9c10d',
workspaceMember: mockedWorkspaceMemberData, workspaceMember: mockedWorkspaceMemberData,
currentWorkspace: mockDefaultWorkspace, currentWorkspace: mockCurrentWorkspace,
locale: 'en', locale: 'en',
workspaces: [{ workspace: mockDefaultWorkspace }], workspaces: [{ workspace: mockCurrentWorkspace }],
workspaceMembers: [mockedWorkspaceMemberData], workspaceMembers: [mockedWorkspaceMemberData],
onboardingStatus: OnboardingStatus.Completed, onboardingStatus: OnboardingStatus.Completed,
userVars: {}, userVars: {},
@ -129,9 +129,9 @@ export const mockedOnboardingUserData = (
supportUserHash: supportUserHash:
'4fb61d34ed3a4aeda2476d4b308b5162db9e1809b2b8277e6fdc6efc4a609254', '4fb61d34ed3a4aeda2476d4b308b5162db9e1809b2b8277e6fdc6efc4a609254',
workspaceMember: null, workspaceMember: null,
currentWorkspace: mockDefaultWorkspace, currentWorkspace: mockCurrentWorkspace,
locale: 'en', locale: 'en',
workspaces: [{ workspace: mockDefaultWorkspace }], workspaces: [{ workspace: mockCurrentWorkspace }],
onboardingStatus: onboardingStatus || null, onboardingStatus: onboardingStatus || null,
}; };
}; };

View File

@ -21,7 +21,7 @@ export const seedCoreSchema = async (
const schemaName = 'core'; const schemaName = 'core';
await seedWorkspaces(workspaceDataSource, schemaName, workspaceId); await seedWorkspaces(workspaceDataSource, schemaName, workspaceId);
await seedUsers(workspaceDataSource, schemaName, workspaceId); await seedUsers(workspaceDataSource, schemaName);
await seedUserWorkspaces(workspaceDataSource, schemaName, workspaceId); await seedUserWorkspaces(workspaceDataSource, schemaName, workspaceId);
}; };

View File

@ -13,7 +13,6 @@ export const DEMO_SEED_USER_IDS = {
export const seedUsers = async ( export const seedUsers = async (
workspaceDataSource: DataSource, workspaceDataSource: DataSource,
schemaName: string, schemaName: string,
workspaceId: string,
) => { ) => {
await workspaceDataSource await workspaceDataSource
.createQueryBuilder() .createQueryBuilder()
@ -56,16 +55,22 @@ export const seedUsers = async (
}; };
export const deleteUsersByWorkspace = async ( export const deleteUsersByWorkspace = async (
workspaceDataSource: DataSource, dataSource: DataSource,
schemaName: string, schemaName: string,
workspaceId: string, workspaceId: string,
) => { ) => {
await workspaceDataSource const user = await dataSource
.createQueryBuilder(`${schemaName}.${tableName}`, 'user')
.leftJoinAndSelect('user.workspaces', 'userWorkspace')
.where(`userWorkspace."workspaceId" = :workspaceId`, {
workspaceId,
})
.getMany();
await dataSource
.createQueryBuilder() .createQueryBuilder()
.delete() .delete()
.from(`${schemaName}.${tableName}`) .from(`${schemaName}.${tableName}`)
.where(`"${tableName}"."defaultWorkspaceId" = :workspaceId`, { .where(`"${tableName}"."id" IN (:...ids)`, { ids: user.map((u) => u.id) })
workspaceId,
})
.execute(); .execute();
}; };

View File

@ -12,7 +12,7 @@ export const seedCoreSchema = async (
const schemaName = 'core'; const schemaName = 'core';
await seedWorkspaces(workspaceDataSource, schemaName, workspaceId); await seedWorkspaces(workspaceDataSource, schemaName, workspaceId);
await seedUsers(workspaceDataSource, schemaName, workspaceId); await seedUsers(workspaceDataSource, schemaName);
await seedUserWorkspaces(workspaceDataSource, schemaName, workspaceId); await seedUserWorkspaces(workspaceDataSource, schemaName, workspaceId);
await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId); await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId);
}; };

View File

@ -1,7 +1,5 @@
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
// import { SeedWorkspaceId } from 'src/database/typeorm-seeds/core/workspaces';
const tableName = 'user'; const tableName = 'user';
export const DEV_SEED_USER_IDS = { export const DEV_SEED_USER_IDS = {
@ -13,7 +11,6 @@ export const DEV_SEED_USER_IDS = {
export const seedUsers = async ( export const seedUsers = async (
workspaceDataSource: DataSource, workspaceDataSource: DataSource,
schemaName: string, schemaName: string,
workspaceId: string,
) => { ) => {
await workspaceDataSource await workspaceDataSource
.createQueryBuilder() .createQueryBuilder()
@ -54,18 +51,3 @@ export const seedUsers = async (
]) ])
.execute(); .execute();
}; };
export const deleteUsersByWorkspace = async (
workspaceDataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
.createQueryBuilder()
.delete()
.from(`${schemaName}.${tableName}`)
.where(`"${tableName}"."defaultWorkspaceId" = :workspaceId`, {
workspaceId,
})
.execute();
};

View File

@ -37,9 +37,8 @@ export class BillingResolver {
} }
@Query(() => SessionEntity) @Query(() => SessionEntity)
@UseGuards(WorkspaceAuthGuard, UserAuthGuard) @UseGuards(WorkspaceAuthGuard)
async billingPortalSession( async billingPortalSession(
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace, @AuthWorkspace() workspace: Workspace,
@Args() { returnUrlPath }: BillingSessionInput, @Args() { returnUrlPath }: BillingSessionInput,
) { ) {
@ -89,8 +88,8 @@ export class BillingResolver {
@Mutation(() => UpdateBillingEntity) @Mutation(() => UpdateBillingEntity)
@UseGuards(WorkspaceAuthGuard) @UseGuards(WorkspaceAuthGuard)
async updateBillingSubscription(@AuthUser() user: User) { async updateBillingSubscription(@AuthWorkspace() workspace: Workspace) {
await this.billingSubscriptionService.applyBillingSubscription(user); await this.billingSubscriptionService.applyBillingSubscription(workspace);
return { success: true }; return { success: true };
} }

View File

@ -3,7 +3,6 @@ import { InjectRepository } from '@nestjs/typeorm';
import assert from 'assert'; import assert from 'assert';
import { User } from '@sentry/node';
import Stripe from 'stripe'; import Stripe from 'stripe';
import { Not, Repository } from 'typeorm'; import { Not, Repository } from 'typeorm';
@ -15,6 +14,7 @@ import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/bill
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum'; import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum';
import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service'; import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@Injectable() @Injectable()
export class BillingSubscriptionService { export class BillingSubscriptionService {
@ -114,9 +114,9 @@ export class BillingSubscriptionService {
return entitlement.value; return entitlement.value;
} }
async applyBillingSubscription(user: User) { async applyBillingSubscription(workspace: Workspace) {
const billingSubscription = await this.getCurrentBillingSubscriptionOrThrow( const billingSubscription = await this.getCurrentBillingSubscriptionOrThrow(
{ workspaceId: user.defaultWorkspaceId }, { workspaceId: workspace.id },
); );
const newInterval = const newInterval =
@ -125,9 +125,7 @@ export class BillingSubscriptionService {
: SubscriptionInterval.Year; : SubscriptionInterval.Year;
const billingSubscriptionItem = const billingSubscriptionItem =
await this.getCurrentBillingSubscriptionItemOrThrow( await this.getCurrentBillingSubscriptionItemOrThrow(workspace.id);
user.defaultWorkspaceId,
);
const productPrice = await this.stripeService.getStripePrice( const productPrice = await this.stripeService.getStripePrice(
AvailableProduct.BasePlan, AvailableProduct.BasePlan,