diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts
index 9e7935792..dd87ef14a 100644
--- a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts
+++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts
@@ -102,6 +102,7 @@ export const useGenerateCombinedFindManyRecordsQuery = ({
}
pageInfo {
hasNextPage
+ hasPreviousPage
startCursor
endCursor
}
diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx
index 4db2eefa8..cd1851625 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx
@@ -29,8 +29,6 @@ import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
-import { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
-import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import {
FieldMetadataType,
@@ -302,27 +300,25 @@ export const RecordShowContainer = ({
);
return (
-
-
-
- {!isMobile && summaryCard}
- {!isMobile && fieldsBox}
-
- >}
- fieldsBox={fieldsBox}
- loading={isPrefetchLoading || loading || recordLoading}
- />
-
-
+
+
+ {!isMobile && summaryCard}
+ {!isMobile && fieldsBox}
+
+ >}
+ fieldsBox={fieldsBox}
+ loading={isPrefetchLoading || loading || recordLoading}
+ />
+
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts
index d252e80ca..cea8c47db 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts
+++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts
@@ -5,7 +5,6 @@ import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
-import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
import { useRecordIdsFromFindManyCacheRootQuery } from '@/object-record/record-show/hooks/useRecordIdsFromFindManyCacheRootQuery';
@@ -37,10 +36,6 @@ export const useRecordShowPagePagination = (
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
- const recordGqlFields = generateDepthOneRecordGqlFields({
- objectMetadataItem,
- });
-
const { filter, orderBy } =
useQueryVariablesFromActiveFieldsOfViewOrDefaultView({
objectMetadataItem,
@@ -55,7 +50,7 @@ export const useRecordShowPagePagination = (
orderBy,
limit: 1,
objectNameSingular,
- recordGqlFields,
+ recordGqlFields: { id: true },
});
const cursorFromRequest = currentRecordsPageInfo?.endCursor;
@@ -77,7 +72,7 @@ export const useRecordShowPagePagination = (
}
: undefined,
objectNameSingular,
- recordGqlFields,
+ recordGqlFields: { id: true },
onCompleted: (_, pagination) => {
setTotalCountBefore(pagination?.totalCount ?? 0);
},
@@ -97,7 +92,7 @@ export const useRecordShowPagePagination = (
}
: undefined,
objectNameSingular,
- recordGqlFields,
+ recordGqlFields: { id: true },
onCompleted: (_, pagination) => {
setTotalCountAfter(pagination?.totalCount ?? 0);
},
diff --git a/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx
index 981afe71f..ffad33185 100644
--- a/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx
+++ b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx
@@ -3,10 +3,10 @@ import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { Favorite } from '@/favorites/types/Favorite';
+import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords';
+import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery';
-import { FIND_ALL_FAVORITES_OPERATION_SIGNATURE } from '@/prefetch/query-keys/FindAllFavoritesOperationSignature';
-import { FIND_ALL_VIEWS_OPERATION_SIGNATURE } from '@/prefetch/query-keys/FindAllViewsOperationSignature';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
import { isDefined } from '~/utils/isDefined';
@@ -24,11 +24,20 @@ export const PrefetchRunQueriesEffect = () => {
prefetchKey: PrefetchKey.AllFavorites,
});
+ const { objectMetadataItems } = useObjectMetadataItems();
+
+ const operationSignatures = Object.values(PREFETCH_CONFIG).map(
+ ({ objectNameSingular, operationSignatureFactory }) => {
+ const objectMetadataItem = objectMetadataItems.find(
+ (item) => item.nameSingular === objectNameSingular,
+ );
+
+ return operationSignatureFactory({ objectMetadataItem });
+ },
+ );
+
const { result } = useCombinedFindManyRecords({
- operationSignatures: [
- FIND_ALL_VIEWS_OPERATION_SIGNATURE,
- FIND_ALL_FAVORITES_OPERATION_SIGNATURE,
- ],
+ operationSignatures,
skip: !currentUser,
});
diff --git a/packages/twenty-front/src/modules/prefetch/constants/PrefetchConfig.ts b/packages/twenty-front/src/modules/prefetch/constants/PrefetchConfig.ts
index e23d51f68..f76ba6637 100644
--- a/packages/twenty-front/src/modules/prefetch/constants/PrefetchConfig.ts
+++ b/packages/twenty-front/src/modules/prefetch/constants/PrefetchConfig.ts
@@ -1,10 +1,22 @@
-import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
-import { FIND_ALL_FAVORITES_OPERATION_SIGNATURE } from '@/prefetch/query-keys/FindAllFavoritesOperationSignature';
-import { FIND_ALL_VIEWS_OPERATION_SIGNATURE } from '@/prefetch/query-keys/FindAllViewsOperationSignature';
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
+import { findAllFavoritesOperationSignatureFactory } from '@/prefetch/operation-signatures/factories/findAllFavoritesOperationSignatureFactory';
+import { findAllViewsOperationSignatureFactory } from '@/prefetch/operation-signatures/factories/findAllViewsOperationSignatureFactory';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
-export const PREFETCH_CONFIG: Record =
+export const PREFETCH_CONFIG: Record<
+ PrefetchKey,
{
- ALL_VIEWS: FIND_ALL_VIEWS_OPERATION_SIGNATURE,
- ALL_FAVORITES: FIND_ALL_FAVORITES_OPERATION_SIGNATURE,
- };
+ objectNameSingular: CoreObjectNameSingular;
+ operationSignatureFactory: RecordGqlOperationSignatureFactory;
+ }
+> = {
+ ALL_VIEWS: {
+ objectNameSingular: CoreObjectNameSingular.View,
+ operationSignatureFactory: findAllViewsOperationSignatureFactory,
+ },
+ ALL_FAVORITES: {
+ objectNameSingular: CoreObjectNameSingular.Favorite,
+ operationSignatureFactory: findAllFavoritesOperationSignatureFactory,
+ },
+};
diff --git a/packages/twenty-front/src/modules/prefetch/hooks/internal/usePrefetchRunQuery.ts b/packages/twenty-front/src/modules/prefetch/hooks/internal/usePrefetchRunQuery.ts
index 855b337ea..a2b18f3ca 100644
--- a/packages/twenty-front/src/modules/prefetch/hooks/internal/usePrefetchRunQuery.ts
+++ b/packages/twenty-front/src/modules/prefetch/hooks/internal/usePrefetchRunQuery.ts
@@ -2,7 +2,6 @@ import { useSetRecoilState } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
-import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState';
@@ -18,10 +17,16 @@ export const usePrefetchRunQuery = ({
const setPrefetchDataIsLoaded = useSetRecoilState(
prefetchIsLoadedFamilyState(prefetchKey),
);
+
+ const { operationSignatureFactory, objectNameSingular } =
+ PREFETCH_CONFIG[prefetchKey];
+
const { objectMetadataItem } = useObjectMetadataItem({
- objectNameSingular: PREFETCH_CONFIG[prefetchKey].objectNameSingular,
+ objectNameSingular,
});
+ const operationSignature = operationSignatureFactory({ objectMetadataItem });
+
const { upsertFindManyRecordsQueryInCache } =
useUpsertFindManyRecordsQueryInCache({
objectMetadataItem: objectMetadataItem,
@@ -30,10 +35,8 @@ export const usePrefetchRunQuery = ({
const upsertRecordsInCache = (records: T[]) => {
setPrefetchDataIsLoaded(false);
upsertFindManyRecordsQueryInCache({
- queryVariables: PREFETCH_CONFIG[prefetchKey].variables,
- recordGqlFields:
- PREFETCH_CONFIG[prefetchKey].fields ??
- generateDepthOneRecordGqlFields({ objectMetadataItem }),
+ queryVariables: operationSignature.variables,
+ recordGqlFields: operationSignature.fields,
objectRecordsToOverwrite: records,
computeReferences: false,
});
diff --git a/packages/twenty-front/src/modules/prefetch/hooks/usePrefetchedData.ts b/packages/twenty-front/src/modules/prefetch/hooks/usePrefetchedData.ts
index 5db8f0d07..b951dc417 100644
--- a/packages/twenty-front/src/modules/prefetch/hooks/usePrefetchedData.ts
+++ b/packages/twenty-front/src/modules/prefetch/hooks/usePrefetchedData.ts
@@ -2,7 +2,6 @@ import { useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
-import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
@@ -17,21 +16,18 @@ export const usePrefetchedData = (
prefetchIsLoadedFamilyState(prefetchKey),
);
- const prefetchQueryKey = PREFETCH_CONFIG[prefetchKey];
+ const { operationSignatureFactory, objectNameSingular } =
+ PREFETCH_CONFIG[prefetchKey];
const { objectMetadataItem } = useObjectMetadataItem({
- objectNameSingular: prefetchQueryKey.objectNameSingular,
+ objectNameSingular,
});
const { records } = useFindManyRecords({
skip: !isDataPrefetched,
- objectNameSingular: prefetchQueryKey.objectNameSingular,
+ objectNameSingular: objectNameSingular,
recordGqlFields:
- prefetchQueryKey.fields ??
- generateDepthOneRecordGqlFields({
- objectMetadataItem,
- }),
- filter,
+ operationSignatureFactory({ objectMetadataItem }).fields ?? filter,
});
return {
diff --git a/packages/twenty-front/src/modules/prefetch/operation-signatures/factories/findAllFavoritesOperationSignatureFactory.ts b/packages/twenty-front/src/modules/prefetch/operation-signatures/factories/findAllFavoritesOperationSignatureFactory.ts
new file mode 100644
index 000000000..a763710a0
--- /dev/null
+++ b/packages/twenty-front/src/modules/prefetch/operation-signatures/factories/findAllFavoritesOperationSignatureFactory.ts
@@ -0,0 +1,15 @@
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
+import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
+
+export const findAllFavoritesOperationSignatureFactory: RecordGqlOperationSignatureFactory =
+ ({ objectMetadataItem }: { objectMetadataItem: ObjectMetadataItem }) => ({
+ objectNameSingular: CoreObjectNameSingular.Favorite,
+ variables: {},
+ fields: {
+ ...generateDepthOneRecordGqlFields({
+ objectMetadataItem,
+ }),
+ },
+ });
diff --git a/packages/twenty-front/src/modules/prefetch/operation-signatures/factories/findAllViewsOperationSignatureFactory.ts b/packages/twenty-front/src/modules/prefetch/operation-signatures/factories/findAllViewsOperationSignatureFactory.ts
new file mode 100644
index 000000000..3a4430a2c
--- /dev/null
+++ b/packages/twenty-front/src/modules/prefetch/operation-signatures/factories/findAllViewsOperationSignatureFactory.ts
@@ -0,0 +1,24 @@
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
+
+export const findAllViewsOperationSignatureFactory: RecordGqlOperationSignatureFactory =
+ () => ({
+ objectNameSingular: CoreObjectNameSingular.View,
+ variables: {},
+ fields: {
+ id: true,
+ createdAt: true,
+ updatedAt: true,
+ isCompact: true,
+ objectMetadataId: true,
+ position: true,
+ type: true,
+ kanbanFieldMetadataId: true,
+ name: true,
+ icon: true,
+ key: true,
+ viewFilters: true,
+ viewSorts: true,
+ viewFields: true,
+ },
+ });
diff --git a/packages/twenty-front/src/modules/prefetch/query-keys/FindAllFavoritesOperationSignature.ts b/packages/twenty-front/src/modules/prefetch/query-keys/FindAllFavoritesOperationSignature.ts
deleted file mode 100644
index 32364260b..000000000
--- a/packages/twenty-front/src/modules/prefetch/query-keys/FindAllFavoritesOperationSignature.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
-import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
-
-export const FIND_ALL_FAVORITES_OPERATION_SIGNATURE: RecordGqlOperationSignature =
- {
- objectNameSingular: CoreObjectNameSingular.Favorite,
- variables: {},
- };
diff --git a/packages/twenty-front/src/modules/prefetch/query-keys/FindAllViewsOperationSignature.ts b/packages/twenty-front/src/modules/prefetch/query-keys/FindAllViewsOperationSignature.ts
deleted file mode 100644
index a0a14f3d5..000000000
--- a/packages/twenty-front/src/modules/prefetch/query-keys/FindAllViewsOperationSignature.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
-import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
-
-export const FIND_ALL_VIEWS_OPERATION_SIGNATURE: RecordGqlOperationSignature = {
- objectNameSingular: CoreObjectNameSingular.View,
- variables: {},
- fields: {
- id: true,
- createdAt: true,
- updatedAt: true,
- isCompact: true,
- objectMetadataId: true,
- position: true,
- type: true,
- kanbanFieldMetadataId: true,
- name: true,
- icon: true,
- key: true,
- viewFilters: true,
- viewSorts: true,
- viewFields: true,
- },
-};
diff --git a/packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts b/packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts
index b06cf94a6..38069ee05 100644
--- a/packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts
+++ b/packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts
@@ -6,5 +6,5 @@ export const prefetchIsLoadedFamilyState = createFamilyState<
PrefetchKey
>({
key: 'prefetchIsLoadedFamilyState',
- defaultValue: true,
+ defaultValue: false,
});
diff --git a/packages/twenty-front/src/modules/ui/layout/states/ShowPageRecoilScopeContext.ts b/packages/twenty-front/src/modules/ui/layout/states/ShowPageRecoilScopeContext.ts
deleted file mode 100644
index f7d666788..000000000
--- a/packages/twenty-front/src/modules/ui/layout/states/ShowPageRecoilScopeContext.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { createContext } from 'react';
-
-/* istanbul ignore next */
-export const ShowPageRecoilScopeContext = createContext(null);
diff --git a/packages/twenty-server/src/command/command-logger.ts b/packages/twenty-server/src/command/command-logger.ts
index 5936c6421..f05a4e930 100644
--- a/packages/twenty-server/src/command/command-logger.ts
+++ b/packages/twenty-server/src/command/command-logger.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
import { Injectable } from '@nestjs/common';
import { existsSync } from 'fs';
diff --git a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts
index b701093e7..af786fd2b 100644
--- a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts
+++ b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts
@@ -1,3 +1,5 @@
+import { Logger } from '@nestjs/common';
+
import { Command, CommandRunner } from 'nest-commander';
import { EntityManager } from 'typeorm';
@@ -43,6 +45,7 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works
})
export class DataSeedWorkspaceCommand extends CommandRunner {
workspaceIds = [SEED_APPLE_WORKSPACE_ID, SEED_TWENTY_WORKSPACE_ID];
+ private readonly logger = new Logger(DataSeedWorkspaceCommand.name);
constructor(
private readonly dataSourceService: DataSourceService,
@@ -86,7 +89,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
});
}
} catch (error) {
- console.error(error);
+ this.logger.error(error);
return;
}
@@ -197,7 +200,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
},
);
} catch (error) {
- console.error(error);
+ this.logger.error(error);
}
await this.typeORMService.disconnectFromDataSource(dataSourceMetadata.id);
diff --git a/packages/twenty-server/src/database/commands/database-command.module.ts b/packages/twenty-server/src/database/commands/database-command.module.ts
index c12d651e5..b06b92e6c 100644
--- a/packages/twenty-server/src/database/commands/database-command.module.ts
+++ b/packages/twenty-server/src/database/commands/database-command.module.ts
@@ -9,7 +9,6 @@ import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-wo
import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question';
import { UpgradeTo0_23CommandModule } from 'src/database/commands/upgrade-version/0-23/0-23-upgrade-version.module';
import { UpgradeVersionModule } from 'src/database/commands/upgrade-version/upgrade-version.module';
-import { WorkspaceAddTotalCountCommand } from 'src/database/commands/workspace-add-total-count.command';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
@@ -51,7 +50,6 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
providers: [
DataSeedWorkspaceCommand,
DataSeedDemoWorkspaceCommand,
- WorkspaceAddTotalCountCommand,
ConfirmationQuestion,
StartDataSeedDemoWorkspaceCronCommand,
StopDataSeedDemoWorkspaceCronCommand,
diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-backfill-new-onboarding-user-vars.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-backfill-new-onboarding-user-vars.ts
index 2d38b76a3..7b44ddf54 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-backfill-new-onboarding-user-vars.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-backfill-new-onboarding-user-vars.ts
@@ -5,7 +5,6 @@ import chalk from 'chalk';
import { Command, CommandRunner, Option } from 'nest-commander';
import { Repository } from 'typeorm';
-import { UpdateFileFolderStructureCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-file-folder-structure.command';
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
import {
Workspace,
@@ -21,7 +20,9 @@ interface BackfillNewOnboardingUserVarsCommandOptions {
description: 'Backfill new onboarding user vars for existing workspaces',
})
export class BackfillNewOnboardingUserVarsCommand extends CommandRunner {
- private readonly logger = new Logger(UpdateFileFolderStructureCommand.name);
+ private readonly logger = new Logger(
+ BackfillNewOnboardingUserVarsCommand.name,
+ );
constructor(
@InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository,
@@ -43,22 +44,13 @@ export class BackfillNewOnboardingUserVarsCommand extends CommandRunner {
_passedParam: string[],
options: BackfillNewOnboardingUserVarsCommandOptions,
): Promise {
- let workspaces;
-
- if (options.workspaceId) {
- workspaces = await this.workspaceRepository.find({
- where: {
- activationStatus: WorkspaceActivationStatus.ACTIVE,
- id: options.workspaceId,
- },
- relations: ['users'],
- });
- } else {
- workspaces = await this.workspaceRepository.find({
- where: { activationStatus: WorkspaceActivationStatus.ACTIVE },
- relations: ['users'],
- });
- }
+ const workspaces = await this.workspaceRepository.find({
+ where: {
+ activationStatus: WorkspaceActivationStatus.PENDING_CREATION,
+ ...(options.workspaceId && { id: options.workspaceId }),
+ },
+ relations: ['users'],
+ });
if (!workspaces.length) {
this.logger.log(chalk.yellow('No workspace found'));
@@ -75,19 +67,19 @@ export class BackfillNewOnboardingUserVarsCommand extends CommandRunner {
chalk.green(`Running command on workspace ${workspace.id}`),
);
- await this.onboardingService.toggleOnboardingInviteTeamCompletion({
+ await this.onboardingService.setOnboardingInviteTeamPending({
workspaceId: workspace.id,
value: true,
});
for (const user of workspace.users) {
- await this.onboardingService.toggleOnboardingConnectAccountCompletion({
+ await this.onboardingService.setOnboardingCreateProfileCompletion({
userId: user.id,
workspaceId: workspace.id,
value: true,
});
- await this.onboardingService.toggleOnboardingCreateProfileCompletion({
+ await this.onboardingService.setOnboardingConnectAccountPending({
userId: user.id,
workspaceId: workspace.id,
value: true,
diff --git a/packages/twenty-server/src/database/commands/workspace-add-total-count.command.ts b/packages/twenty-server/src/database/commands/workspace-add-total-count.command.ts
deleted file mode 100644
index cdf8aa293..000000000
--- a/packages/twenty-server/src/database/commands/workspace-add-total-count.command.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Command, CommandRunner } from 'nest-commander';
-import chalk from 'chalk';
-
-import { TypeORMService } from 'src/database/typeorm/typeorm.service';
-
-@Command({
- name: 'workspace:add-total-count',
- description: 'Add pg_graphql total count directive to all workspace tables',
-})
-export class WorkspaceAddTotalCountCommand extends CommandRunner {
- constructor(private readonly typeORMService: TypeORMService) {
- super();
- }
-
- async run(): Promise {
- const mainDataSource = this.typeORMService.getMainDataSource();
-
- try {
- await mainDataSource.query(`
- DO $$
- DECLARE
- schema_cursor CURSOR FOR SELECT schema_name FROM information_schema.schemata WHERE schema_name LIKE 'workspace_%';
- schema_name text;
- table_rec record;
- BEGIN
- OPEN schema_cursor;
- LOOP
- FETCH schema_cursor INTO schema_name;
- EXIT WHEN NOT FOUND;
-
- FOR table_rec IN SELECT t.table_name FROM information_schema.tables t WHERE t.table_schema = schema_name
- LOOP
- EXECUTE 'COMMENT ON TABLE ' || quote_ident(schema_name) || '.' || quote_ident(table_rec.table_name) || ' IS e''@graphql({"totalCount": {"enabled": true}})'';';
- END LOOP;
- END LOOP;
- CLOSE schema_cursor;
- END $$;
- `);
-
- console.log(
- chalk.green('Total count directive added to all workspace tables'),
- );
- } catch (error) {
- console.log(
- chalk.red('Error adding total count directive to all workspace tables'),
- );
- }
- }
-}
diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/1722256203540-updateActivationStatusEnum.ts b/packages/twenty-server/src/database/typeorm/core/migrations/1722256203540-updateActivationStatusEnum copy.ts
similarity index 100%
rename from packages/twenty-server/src/database/typeorm/core/migrations/1722256203540-updateActivationStatusEnum.ts
rename to packages/twenty-server/src/database/typeorm/core/migrations/1722256203540-updateActivationStatusEnum copy.ts
diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/1722256203541-updateActivationStatusEnumPendingCreation.ts b/packages/twenty-server/src/database/typeorm/core/migrations/1722256203541-updateActivationStatusEnumPendingCreation.ts
new file mode 100644
index 000000000..dea97eb77
--- /dev/null
+++ b/packages/twenty-server/src/database/typeorm/core/migrations/1722256203541-updateActivationStatusEnumPendingCreation.ts
@@ -0,0 +1,69 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class UpdateActivationStatusEnumPendingCreationStatus1722256203541
+ implements MigrationInterface
+{
+ name = 'UpdateActivationStatusEnumPendingCreationStatus1722256203541';
+
+ public async up(queryRunner: QueryRunner): Promise {
+ // Set current column as text
+ await queryRunner.query(
+ `ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DATA TYPE text USING "activationStatus"::text`,
+ );
+
+ // Drop default value
+ await queryRunner.query(
+ `ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" DROP DEFAULT`,
+ );
+
+ // Drop the old enum type
+ await queryRunner.query(
+ `DROP TYPE "core"."workspace_activationStatus_enum"`,
+ );
+
+ await queryRunner.query(
+ `CREATE TYPE "core"."workspace_activationStatus_enum" AS ENUM('PENDING_CREATION', 'ONGOING_CREATION', 'ACTIVE', 'INACTIVE')`,
+ );
+
+ // Re-apply the enum type
+ await queryRunner.query(
+ `ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DATA TYPE "core"."workspace_activationStatus_enum" USING "activationStatus"::"core"."workspace_activationStatus_enum"`,
+ );
+
+ // Update default value
+ await queryRunner.query(
+ `ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DEFAULT 'INACTIVE'`,
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ // Set current column as text
+ await queryRunner.query(
+ `ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DATA TYPE text USING "activationStatus"::text`,
+ );
+
+ // Drop default value
+ await queryRunner.query(
+ `ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" DROP DEFAULT`,
+ );
+
+ // Drop the old enum type
+ await queryRunner.query(
+ `DROP TYPE "core"."workspace_activationStatus_enum"`,
+ );
+
+ await queryRunner.query(
+ `CREATE TYPE "core"."workspace_activationStatus_enum" AS ENUM('PENDING_CREATION', 'ACTIVE', 'INACTIVE')`,
+ );
+
+ // Re-apply the enum type
+ await queryRunner.query(
+ `ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DATA TYPE "core"."workspace_activationStatus_enum" USING "activationStatus"::"core"."workspace_activationStatus_enum"`,
+ );
+
+ // Update default value
+ await queryRunner.query(
+ `ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DEFAULT 'INACTIVE'`,
+ );
+ }
+}
diff --git a/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.service.ts b/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.service.ts
index 465f9a0a4..f487b3b25 100644
--- a/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.service.ts
@@ -1,22 +1,22 @@
import { Injectable, Logger } from '@nestjs/common';
-import { RunnableSequence } from '@langchain/core/runnables';
import { StructuredOutputParser } from '@langchain/core/output_parsers';
+import { RunnableSequence } from '@langchain/core/runnables';
+import groupBy from 'lodash.groupby';
import { DataSource, QueryFailedError } from 'typeorm';
+import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
-import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
-import groupBy from 'lodash.groupby';
-import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
+import { sqlGenerationPromptTemplate } from 'src/engine/core-modules/ai-sql-query/ai-sql-query.prompt-templates';
+import { AISQLQueryResult } from 'src/engine/core-modules/ai-sql-query/dtos/ai-sql-query-result.dto';
import { LLMChatModelService } from 'src/engine/integrations/llm-chat-model/llm-chat-model.service';
import { LLMTracingService } from 'src/engine/integrations/llm-tracing/llm-tracing.service';
-import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { DEFAULT_LABEL_IDENTIFIER_FIELD_NAME } from 'src/engine/metadata-modules/object-metadata/object-metadata.constants';
+import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
+import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { StandardObjectFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory';
-import { AISQLQueryResult } from 'src/engine/core-modules/ai-sql-query/dtos/ai-sql-query-result.dto';
-import { sqlGenerationPromptTemplate } from 'src/engine/core-modules/ai-sql-query/ai-sql-query.prompt-templates';
@Injectable()
export class AISQLQueryService {
@@ -31,9 +31,9 @@ export class AISQLQueryService {
private getLabelIdentifierName(
objectMetadata: ObjectMetadataEntity,
- dataSourceId,
- workspaceId,
- workspaceFeatureFlagsMap,
+ _dataSourceId,
+ _workspaceId,
+ _workspaceFeatureFlagsMap,
): string | undefined {
const customObjectLabelIdentifierFieldMetadata = objectMetadata.fields.find(
(fieldMetadata) =>
diff --git a/packages/twenty-server/src/engine/core-modules/auth/controllers/google-apis-auth.controller.ts b/packages/twenty-server/src/engine/core-modules/auth/controllers/google-apis-auth.controller.ts
index 3527d6652..2dff70629 100644
--- a/packages/twenty-server/src/engine/core-modules/auth/controllers/google-apis-auth.controller.ts
+++ b/packages/twenty-server/src/engine/core-modules/auth/controllers/google-apis-auth.controller.ts
@@ -93,10 +93,10 @@ export class GoogleAPIsAuthController {
workspaceId,
);
- await onboardingServiceInstance.toggleOnboardingConnectAccountCompletion({
+ await onboardingServiceInstance.setOnboardingConnectAccountPending({
userId,
workspaceId,
- value: true,
+ value: false,
});
}
diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts
index 7313ebf0f..e3bb7af39 100644
--- a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts
+++ b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts
@@ -1,15 +1,14 @@
+import { HttpService } from '@nestjs/axios';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
-import { HttpService } from '@nestjs/axios';
-import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
-import { User } from 'src/engine/core-modules/user/user.entity';
-import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
-import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
-import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
+import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
+import { User } from 'src/engine/core-modules/user/user.entity';
+import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
+import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
describe('SignInUpService', () => {
let service: SignInUpService;
diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts
index 0cc3f2f03..839a69427 100644
--- a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts
@@ -175,6 +175,18 @@ export class SignInUpService {
await this.userWorkspaceService.create(user.id, workspace.id);
await this.userWorkspaceService.createWorkspaceMember(workspace.id, user);
+ await this.onboardingService.setOnboardingConnectAccountPending({
+ userId: user.id,
+ workspaceId: workspace.id,
+ value: true,
+ });
+
+ await this.onboardingService.setOnboardingCreateProfileCompletion({
+ userId: user.id,
+ workspaceId: workspace.id,
+ value: true,
+ });
+
return user;
}
@@ -222,13 +234,22 @@ export class SignInUpService {
await this.userWorkspaceService.create(user.id, workspace.id);
- if (user.firstName !== '' || user.lastName === '') {
- await this.onboardingService.toggleOnboardingCreateProfileCompletion({
- userId: user.id,
- workspaceId: workspace.id,
- value: true,
- });
- }
+ await this.onboardingService.setOnboardingConnectAccountPending({
+ userId: user.id,
+ workspaceId: workspace.id,
+ value: true,
+ });
+
+ await this.onboardingService.setOnboardingCreateProfileCompletion({
+ userId: user.id,
+ workspaceId: workspace.id,
+ value: true,
+ });
+
+ await this.onboardingService.setOnboardingInviteTeamPending({
+ workspaceId: workspace.id,
+ value: true,
+ });
return user;
}
diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.module.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.module.ts
index cb66d9109..c1c6760df 100644
--- a/packages/twenty-server/src/engine/core-modules/billing/billing.module.ts
+++ b/packages/twenty-server/src/engine/core-modules/billing/billing.module.ts
@@ -9,13 +9,16 @@ import { BillingWorkspaceMemberListener } from 'src/engine/core-modules/billing/
import { BillingPortalWorkspaceService } from 'src/engine/core-modules/billing/services/billing-portal.workspace-service';
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
import { BillingWebhookService } from 'src/engine/core-modules/billing/services/billing-webhook.service';
+import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
import { StripeModule } from 'src/engine/core-modules/billing/stripe/stripe.module';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
+import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@Module({
imports: [
+ FeatureFlagModule,
StripeModule,
UserWorkspaceModule,
TypeOrmModule.forFeature(
@@ -35,11 +38,13 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
BillingPortalWorkspaceService,
BillingResolver,
BillingWorkspaceMemberListener,
+ BillingService,
],
exports: [
BillingSubscriptionService,
BillingPortalWorkspaceService,
BillingWebhookService,
+ BillingService,
],
})
export class BillingModule {}
diff --git a/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription.entity.ts b/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription.entity.ts
index 509d5250b..5199137d7 100644
--- a/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription.entity.ts
+++ b/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription.entity.ts
@@ -1,5 +1,7 @@
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
+import { IDField } from '@ptc-org/nestjs-query-graphql';
+import Stripe from 'stripe';
import {
Column,
CreateDateColumn,
@@ -11,12 +13,10 @@ import {
Relation,
UpdateDateColumn,
} from 'typeorm';
-import Stripe from 'stripe';
-import { IDField } from '@ptc-org/nestjs-query-graphql';
-import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
-import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
+import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
+import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
export enum SubscriptionStatus {
Active = 'active',
@@ -76,7 +76,7 @@ export class BillingSubscription {
enum: Object.values(SubscriptionStatus),
nullable: false,
})
- status: Stripe.Subscription.Status;
+ status: SubscriptionStatus;
@Field(() => SubscriptionInterval, { nullable: true })
@Column({
diff --git a/packages/twenty-server/src/engine/core-modules/billing/jobs/update-subscription.job.ts b/packages/twenty-server/src/engine/core-modules/billing/jobs/update-subscription.job.ts
index 14929639f..45d723329 100644
--- a/packages/twenty-server/src/engine/core-modules/billing/jobs/update-subscription.job.ts
+++ b/packages/twenty-server/src/engine/core-modules/billing/jobs/update-subscription.job.ts
@@ -33,7 +33,7 @@ export class UpdateSubscriptionJob {
try {
const billingSubscriptionItem =
- await this.billingSubscriptionService.getCurrentBillingSubscriptionItem(
+ await this.billingSubscriptionService.getCurrentBillingSubscriptionItemOrThrow(
data.workspaceId,
);
diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts
index 506a97dd7..ca4685f07 100644
--- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts
+++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts
@@ -70,12 +70,18 @@ export class BillingPortalWorkspaceService {
returnUrlPath?: string,
) {
const currentSubscriptionItem =
- await this.billingSubscriptionService.getCurrentBillingSubscription({
- workspaceId,
- });
+ await this.billingSubscriptionService.getCurrentBillingSubscriptionOrThrow(
+ {
+ workspaceId,
+ },
+ );
const stripeCustomerId = currentSubscriptionItem.stripeCustomerId;
+ if (!stripeCustomerId) {
+ throw new Error('Error: missing stripeCustomerId');
+ }
+
const frontBaseUrl = this.environmentService.get('FRONT_BASE_URL');
const returnUrl = returnUrlPath
? frontBaseUrl + returnUrlPath
diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts
index 9b0575e87..15fb1df71 100644
--- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts
@@ -79,7 +79,7 @@ export class BillingSubscriptionService {
);
}
- async getCurrentBillingSubscription(criteria: {
+ async getCurrentBillingSubscriptionOrThrow(criteria: {
workspaceId?: string;
stripeCustomerId?: string;
}) {
@@ -97,21 +97,15 @@ export class BillingSubscriptionService {
return notCanceledSubscriptions?.[0];
}
- async getCurrentBillingSubscriptionItem(
+ async getCurrentBillingSubscriptionItemOrThrow(
workspaceId: string,
stripeProductId = this.environmentService.get(
'BILLING_STRIPE_BASE_PLAN_PRODUCT_ID',
),
) {
- const billingSubscription = await this.getCurrentBillingSubscription({
- workspaceId,
- });
-
- if (!billingSubscription) {
- throw new Error(
- `Cannot find billingSubscriptionItem for product ${stripeProductId} for workspace ${workspaceId}`,
- );
- }
+ const billingSubscription = await this.getCurrentBillingSubscriptionOrThrow(
+ { workspaceId },
+ );
const billingSubscriptionItem =
billingSubscription.billingSubscriptionItems.filter(
@@ -129,9 +123,10 @@ export class BillingSubscriptionService {
}
async deleteSubscription(workspaceId: string) {
- const subscriptionToCancel = await this.getCurrentBillingSubscription({
- workspaceId,
- });
+ const subscriptionToCancel =
+ await this.getCurrentBillingSubscriptionOrThrow({
+ workspaceId,
+ });
if (subscriptionToCancel) {
await this.stripeService.cancelSubscription(
@@ -142,9 +137,9 @@ export class BillingSubscriptionService {
}
async handleUnpaidInvoices(data: Stripe.SetupIntentSucceededEvent.Data) {
- const billingSubscription = await this.getCurrentBillingSubscription({
- stripeCustomerId: data.object.customer as string,
- });
+ const billingSubscription = await this.getCurrentBillingSubscriptionOrThrow(
+ { stripeCustomerId: data.object.customer as string },
+ );
if (billingSubscription?.status === 'unpaid') {
await this.stripeService.collectLastInvoice(
@@ -154,9 +149,9 @@ export class BillingSubscriptionService {
}
async applyBillingSubscription(user: User) {
- const billingSubscription = await this.getCurrentBillingSubscription({
- workspaceId: user.defaultWorkspaceId,
- });
+ const billingSubscription = await this.getCurrentBillingSubscriptionOrThrow(
+ { workspaceId: user.defaultWorkspaceId },
+ );
const newInterval =
billingSubscription?.interval === SubscriptionInterval.Year
@@ -164,7 +159,9 @@ export class BillingSubscriptionService {
: SubscriptionInterval.Year;
const billingSubscriptionItem =
- await this.getCurrentBillingSubscriptionItem(user.defaultWorkspaceId);
+ await this.getCurrentBillingSubscriptionItemOrThrow(
+ user.defaultWorkspaceId,
+ );
const productPrice = await this.stripeService.getStripePrice(
AvailableProduct.BasePlan,
diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook.service.ts
index 347eea6de..7d1b5df5a 100644
--- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook.service.ts
@@ -46,7 +46,7 @@ export class BillingWebhookService {
workspaceId: workspaceId,
stripeCustomerId: data.object.customer as string,
stripeSubscriptionId: data.object.id,
- status: data.object.status,
+ status: data.object.status as SubscriptionStatus,
interval: data.object.items.data[0].plan.interval,
},
{
diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing.service.ts
new file mode 100644
index 000000000..e86895f9f
--- /dev/null
+++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing.service.ts
@@ -0,0 +1,55 @@
+import { Injectable, Logger } from '@nestjs/common';
+
+import { isDefined } from 'class-validator';
+
+import { SubscriptionStatus } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
+import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
+import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
+import { IsFeatureEnabledService } from 'src/engine/core-modules/feature-flag/services/is-feature-enabled.service';
+import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
+
+@Injectable()
+export class BillingService {
+ protected readonly logger = new Logger(BillingService.name);
+ constructor(
+ private readonly environmentService: EnvironmentService,
+ private readonly billingSubscriptionService: BillingSubscriptionService,
+ private readonly isFeatureEnabledService: IsFeatureEnabledService,
+ ) {}
+
+ isBillingEnabled() {
+ return this.environmentService.get('IS_BILLING_ENABLED');
+ }
+
+ async hasWorkspaceActiveSubscriptionOrFreeAccess(workspaceId: string) {
+ const isBillingEnabled = this.isBillingEnabled();
+
+ if (!isBillingEnabled) {
+ return true;
+ }
+
+ const isFreeAccessEnabled =
+ await this.isFeatureEnabledService.isFeatureEnabled(
+ FeatureFlagKey.IsFreeAccessEnabled,
+ workspaceId,
+ );
+
+ if (isFreeAccessEnabled) {
+ return true;
+ }
+
+ const currentBillingSubscription =
+ await this.billingSubscriptionService.getCurrentBillingSubscriptionOrThrow(
+ { workspaceId },
+ );
+
+ return (
+ isDefined(currentBillingSubscription) &&
+ [
+ SubscriptionStatus.Active,
+ SubscriptionStatus.Trialing,
+ SubscriptionStatus.PastDue,
+ ].includes(currentBillingSubscription.status)
+ );
+ }
+}
diff --git a/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts b/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts
index c98e8c53b..7dba53b26 100644
--- a/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts
@@ -141,28 +141,30 @@ export class StripeService {
);
}
- formatProductPrices(prices: Stripe.Price[]) {
- const result: Record = {};
+ formatProductPrices(prices: Stripe.Price[]): ProductPriceEntity[] {
+ const productPrices: ProductPriceEntity[] = Object.values(
+ prices
+ .filter((item) => item.recurring?.interval && item.unit_amount)
+ .reduce((acc, item: Stripe.Price) => {
+ const interval = item.recurring?.interval;
- prices.forEach((item) => {
- const interval = item.recurring?.interval;
+ if (!interval || !item.unit_amount) {
+ return acc;
+ }
- if (!interval || !item.unit_amount) {
- return;
- }
- if (
- !result[interval] ||
- item.created > (result[interval]?.created || 0)
- ) {
- result[interval] = {
- unitAmount: item.unit_amount,
- recurringInterval: interval,
- created: item.created,
- stripePriceId: item.id,
- };
- }
- });
+ if (!acc[interval] || item.created > acc[interval].created) {
+ acc[interval] = {
+ unitAmount: item.unit_amount,
+ recurringInterval: interval,
+ created: item.created,
+ stripePriceId: item.id,
+ };
+ }
- return Object.values(result).sort((a, b) => a.unitAmount - b.unitAmount);
+ return acc satisfies Record;
+ }, {}),
+ );
+
+ return productPrices.sort((a, b) => a.unitAmount - b.unitAmount);
}
}
diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts
index b17f04fef..585551291 100644
--- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts
+++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts
@@ -1,16 +1,13 @@
import { UseGuards } from '@nestjs/common';
-import { Query, Args, ArgsType, Field, Int, Resolver } from '@nestjs/graphql';
+import { Args, ArgsType, Field, Int, Query, Resolver } from '@nestjs/graphql';
import { Max } from 'class-validator';
-import { User } from 'src/engine/core-modules/user/user.entity';
-import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
-import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
+import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants';
import { TimelineCalendarEventsWithTotal } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
import { TimelineCalendarEventService } from 'src/engine/core-modules/calendar/timeline-calendar-event.service';
-import { UserService } from 'src/engine/core-modules/user/services/user.service';
-import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
+import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
@ArgsType()
class GetTimelineCalendarEventsFromPersonIdArgs {
@@ -43,12 +40,10 @@ class GetTimelineCalendarEventsFromCompanyIdArgs {
export class TimelineCalendarEventResolver {
constructor(
private readonly timelineCalendarEventService: TimelineCalendarEventService,
- private readonly userService: UserService,
) {}
@Query(() => TimelineCalendarEventsWithTotal)
async getTimelineCalendarEventsFromPersonId(
- @AuthUser() user: User,
@Args()
{ personId, page, pageSize }: GetTimelineCalendarEventsFromPersonIdArgs,
) {
@@ -64,7 +59,6 @@ export class TimelineCalendarEventResolver {
@Query(() => TimelineCalendarEventsWithTotal)
async getTimelineCalendarEventsFromCompanyId(
- @AuthUser() user: User,
@Args()
{ companyId, page, pageSize }: GetTimelineCalendarEventsFromCompanyIdArgs,
) {
diff --git a/packages/twenty-server/src/engine/core-modules/jwt/services/jwt-wrapper.service.ts b/packages/twenty-server/src/engine/core-modules/jwt/services/jwt-wrapper.service.ts
index 43a4ea2cb..79ebee2c8 100644
--- a/packages/twenty-server/src/engine/core-modules/jwt/services/jwt-wrapper.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/jwt/services/jwt-wrapper.service.ts
@@ -7,7 +7,12 @@ import * as jwt from 'jsonwebtoken';
export class JwtWrapperService {
constructor(private readonly jwtService: JwtService) {}
- sign(payload: string, options?: JwtSignOptions): string {
+ sign(payload: string | object, options?: JwtSignOptions): string {
+ // Typescript does not handle well the overloads of the sign method, helping it a little bit
+ if (typeof payload === 'object') {
+ return this.jwtService.sign(payload, options);
+ }
+
return this.jwtService.sign(payload, options);
}
diff --git a/packages/twenty-server/src/engine/core-modules/onboarding/onboarding.resolver.ts b/packages/twenty-server/src/engine/core-modules/onboarding/onboarding.resolver.ts
index 97fe28e50..fcc0a89a7 100644
--- a/packages/twenty-server/src/engine/core-modules/onboarding/onboarding.resolver.ts
+++ b/packages/twenty-server/src/engine/core-modules/onboarding/onboarding.resolver.ts
@@ -19,10 +19,10 @@ export class OnboardingResolver {
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
): Promise {
- await this.onboardingService.toggleOnboardingConnectAccountCompletion({
+ await this.onboardingService.setOnboardingConnectAccountPending({
userId: user.id,
workspaceId: workspace.id,
- value: true,
+ value: false,
});
return { success: true };
diff --git a/packages/twenty-server/src/engine/core-modules/onboarding/onboarding.service.ts b/packages/twenty-server/src/engine/core-modules/onboarding/onboarding.service.ts
index 7c2fa2406..cd4adb8e0 100644
--- a/packages/twenty-server/src/engine/core-modules/onboarding/onboarding.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/onboarding/onboarding.service.ts
@@ -1,66 +1,40 @@
import { Injectable } from '@nestjs/common';
-import { SubscriptionStatus } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
-import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
-import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
-import { IsFeatureEnabledService } from 'src/engine/core-modules/feature-flag/services/is-feature-enabled.service';
+import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
import { OnboardingStatus } from 'src/engine/core-modules/onboarding/enums/onboarding-status.enum';
import { UserVarsService } from 'src/engine/core-modules/user/user-vars/services/user-vars.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceActivationStatus } from 'src/engine/core-modules/workspace/workspace.entity';
-import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
-import { isDefined } from 'src/utils/is-defined';
export enum OnboardingStepKeys {
- ONBOARDING_CONNECT_ACCOUNT_COMPLETE = 'ONBOARDING_CONNECT_ACCOUNT_COMPLETE',
- ONBOARDING_INVITE_TEAM_COMPLETE = 'ONBOARDING_INVITE_TEAM_COMPLETE',
- ONBOARDING_CREATE_PROFILE_COMPLETE = 'ONBOARDING_CREATE_PROFILE_COMPLETE',
+ ONBOARDING_CONNECT_ACCOUNT_PENDING = 'ONBOARDING_CONNECT_ACCOUNT_PENDING',
+ ONBOARDING_INVITE_TEAM_PENDING = 'ONBOARDING_INVITE_TEAM_PENDING',
+ ONBOARDING_CREATE_PROFILE_PENDING = 'ONBOARDING_CREATE_PROFILE_PENDING',
}
export type OnboardingKeyValueTypeMap = {
- [OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_COMPLETE]: boolean;
- [OnboardingStepKeys.ONBOARDING_INVITE_TEAM_COMPLETE]: boolean;
- [OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_COMPLETE]: boolean;
+ [OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_PENDING]: boolean;
+ [OnboardingStepKeys.ONBOARDING_INVITE_TEAM_PENDING]: boolean;
+ [OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_PENDING]: boolean;
};
@Injectable()
export class OnboardingService {
constructor(
- private readonly billingSubscriptionService: BillingSubscriptionService,
- private readonly environmentService: EnvironmentService,
- private readonly isFeatureEnabledService: IsFeatureEnabledService,
+ private readonly billingService: BillingService,
private readonly userVarsService: UserVarsService,
) {}
private async isSubscriptionIncompleteOnboardingStatus(user: User) {
- const isBillingEnabled = this.environmentService.get('IS_BILLING_ENABLED');
-
- if (!isBillingEnabled) {
- return false;
- }
-
- const isFreeAccessEnabled =
- await this.isFeatureEnabledService.isFeatureEnabled(
- FeatureFlagKey.IsFreeAccessEnabled,
+ const hasSubscription =
+ await this.billingService.hasWorkspaceActiveSubscriptionOrFreeAccess(
user.defaultWorkspaceId,
);
- if (isFreeAccessEnabled) {
- return false;
- }
-
- const currentBillingSubscription =
- await this.billingSubscriptionService.getCurrentBillingSubscription({
- workspaceId: user.defaultWorkspaceId,
- });
-
- return (
- !isDefined(currentBillingSubscription) ||
- currentBillingSubscription?.status === SubscriptionStatus.Incomplete
- );
+ return !hasSubscription;
}
- private async isWorkspaceActivationOnboardingStatus(user: User) {
+ private isWorkspaceActivationPending(user: User) {
return (
user.defaultWorkspace.activationStatus ===
WorkspaceActivationStatus.PENDING_CREATION
@@ -72,7 +46,7 @@ export class OnboardingService {
return OnboardingStatus.PLAN_REQUIRED;
}
- if (await this.isWorkspaceActivationOnboardingStatus(user)) {
+ if (this.isWorkspaceActivationPending(user)) {
return OnboardingStatus.WORKSPACE_ACTIVATION;
}
@@ -81,33 +55,33 @@ export class OnboardingService {
workspaceId: user.defaultWorkspaceId,
});
- const isProfileCreationComplete =
- userVars.get(OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_COMPLETE) ===
+ const isProfileCreationPending =
+ userVars.get(OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_PENDING) ===
true;
- const isConnectAccountComplete =
- userVars.get(OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_COMPLETE) ===
+ const isConnectAccountPending =
+ userVars.get(OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_PENDING) ===
true;
- const isInviteTeamComplete =
- userVars.get(OnboardingStepKeys.ONBOARDING_INVITE_TEAM_COMPLETE) === true;
+ const isInviteTeamPending =
+ userVars.get(OnboardingStepKeys.ONBOARDING_INVITE_TEAM_PENDING) === true;
- if (!isProfileCreationComplete) {
+ if (isProfileCreationPending) {
return OnboardingStatus.PROFILE_CREATION;
}
- if (!isConnectAccountComplete) {
+ if (isConnectAccountPending) {
return OnboardingStatus.SYNC_EMAIL;
}
- if (!isInviteTeamComplete) {
+ if (isInviteTeamPending) {
return OnboardingStatus.INVITE_TEAM;
}
return OnboardingStatus.COMPLETED;
}
- async toggleOnboardingConnectAccountCompletion({
+ async setOnboardingConnectAccountPending({
userId,
workspaceId,
value,
@@ -116,29 +90,48 @@ export class OnboardingService {
workspaceId: string;
value: boolean;
}) {
+ if (!value) {
+ await this.userVarsService.delete({
+ userId,
+ workspaceId,
+ key: OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_PENDING,
+ });
+
+ return;
+ }
+
await this.userVarsService.set({
userId,
workspaceId: workspaceId,
- key: OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_COMPLETE,
- value,
+ key: OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_PENDING,
+ value: true,
});
}
- async toggleOnboardingInviteTeamCompletion({
+ async setOnboardingInviteTeamPending({
workspaceId,
value,
}: {
workspaceId: string;
value: boolean;
}) {
+ if (!value) {
+ await this.userVarsService.delete({
+ workspaceId,
+ key: OnboardingStepKeys.ONBOARDING_INVITE_TEAM_PENDING,
+ });
+
+ return;
+ }
+
await this.userVarsService.set({
workspaceId,
- key: OnboardingStepKeys.ONBOARDING_INVITE_TEAM_COMPLETE,
- value,
+ key: OnboardingStepKeys.ONBOARDING_INVITE_TEAM_PENDING,
+ value: true,
});
}
- async toggleOnboardingCreateProfileCompletion({
+ async setOnboardingCreateProfileCompletion({
userId,
workspaceId,
value,
@@ -147,11 +140,21 @@ export class OnboardingService {
workspaceId: string;
value: boolean;
}) {
+ if (!value) {
+ await this.userVarsService.delete({
+ userId,
+ workspaceId,
+ key: OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_PENDING,
+ });
+
+ return;
+ }
+
await this.userVarsService.set({
userId,
workspaceId,
- key: OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_COMPLETE,
- value,
+ key: OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_PENDING,
+ value: true,
});
}
}
diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts
index b31b8a2f9..4e57e1d2b 100644
--- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts
@@ -47,6 +47,32 @@ export class WorkspaceService extends TypeOrmQueryService {
throw new BadRequestException("'displayName' not provided");
}
+ const existingWorkspace = await this.workspaceRepository.findOneBy({
+ id: user.defaultWorkspace.id,
+ });
+
+ if (!existingWorkspace) {
+ throw new Error('Workspace not found');
+ }
+
+ if (
+ existingWorkspace.activationStatus ===
+ WorkspaceActivationStatus.ONGOING_CREATION
+ ) {
+ throw new Error('Workspace is already being created');
+ }
+
+ if (
+ existingWorkspace.activationStatus !==
+ WorkspaceActivationStatus.PENDING_CREATION
+ ) {
+ throw new Error('Worspace is not pending creation');
+ }
+
+ await this.workspaceRepository.update(user.defaultWorkspace.id, {
+ activationStatus: WorkspaceActivationStatus.ONGOING_CREATION,
+ });
+
await this.workspaceManagerService.init(user.defaultWorkspace.id);
await this.userWorkspaceService.createWorkspaceMember(
user.defaultWorkspace.id,
@@ -142,9 +168,9 @@ export class WorkspaceService extends TypeOrmQueryService {
});
}
- await this.onboardingService.toggleOnboardingInviteTeamCompletion({
+ await this.onboardingService.setOnboardingInviteTeamPending({
workspaceId: workspace.id,
- value: true,
+ value: false,
});
return { success: true };
diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace-workspace-member.listener.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace-workspace-member.listener.ts
index 324c0d929..fb8412d32 100644
--- a/packages/twenty-server/src/engine/core-modules/workspace/workspace-workspace-member.listener.ts
+++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace-workspace-member.listener.ts
@@ -25,9 +25,6 @@ export class WorkspaceWorkspaceMemberListener {
async handleUpdateEvent(
payload: ObjectRecordUpdateEvent,
) {
- const { firstName: firstNameBefore, lastName: lastNameBefore } =
- payload.properties.before.name;
-
const { firstName: firstNameAfter, lastName: lastNameAfter } =
payload.properties.after.name;
@@ -39,10 +36,10 @@ export class WorkspaceWorkspaceMemberListener {
return;
}
- await this.onboardingService.toggleOnboardingCreateProfileCompletion({
+ await this.onboardingService.setOnboardingCreateProfileCompletion({
userId: payload.userId,
workspaceId: payload.workspaceId,
- value: true,
+ value: false,
});
}
diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts
index 88a3bf7c8..0b4a3e048 100644
--- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts
+++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts
@@ -21,6 +21,7 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
import { User } from 'src/engine/core-modules/user/user.entity';
export enum WorkspaceActivationStatus {
+ ONGOING_CREATION = 'ONGOING_CREATION',
PENDING_CREATION = 'PENDING_CREATION',
ACTIVE = 'ACTIVE',
INACTIVE = 'INACTIVE',
diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts
index cc390dfa9..1bb019344 100644
--- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts
+++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts
@@ -118,9 +118,9 @@ export class WorkspaceResolver {
async currentBillingSubscription(
@Parent() workspace: Workspace,
): Promise {
- return this.billingSubscriptionService.getCurrentBillingSubscription({
- workspaceId: workspace.id,
- });
+ return this.billingSubscriptionService.getCurrentBillingSubscriptionOrThrow(
+ { workspaceId: workspace.id },
+ );
}
@ResolveField(() => Number)
diff --git a/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/console.driver.ts b/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/console.driver.ts
index 47e126324..6c1ae4015 100644
--- a/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/console.driver.ts
+++ b/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/console.driver.ts
@@ -1,6 +1,7 @@
+/* eslint-disable no-console */
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
-import { ConsoleCallbackHandler } from '@langchain/core/tracers/console';
import { Run } from '@langchain/core/tracers/base';
+import { ConsoleCallbackHandler } from '@langchain/core/tracers/console';
import { LLMTracingDriver } from 'src/engine/integrations/llm-tracing/drivers/interfaces/llm-tracing-driver.interface';
diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.service.ts
index e62b56ef9..0b8bdf7cf 100644
--- a/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.service.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.service.ts
@@ -1,30 +1,32 @@
-import { Injectable, NotFoundException } from '@nestjs/common';
+import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
+import { WorkspaceHealthFixKind } from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-health-fix-kind.interface';
import { WorkspaceHealthIssue } from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-health-issue.interface';
import {
WorkspaceHealthMode,
WorkspaceHealthOptions,
} from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-health-options.interface';
-import { WorkspaceHealthFixKind } from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-health-fix-kind.interface';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
-import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
-import { ObjectMetadataHealthService } from 'src/engine/workspace-manager/workspace-health/services/object-metadata-health.service';
-import { FieldMetadataHealthService } from 'src/engine/workspace-manager/workspace-health/services/field-metadata-health.service';
-import { RelationMetadataHealthService } from 'src/engine/workspace-manager/workspace-health/services/relation-metadata.health.service';
-import { DatabaseStructureService } from 'src/engine/workspace-manager/workspace-health/services/database-structure.service';
-import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
-import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
+import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
+import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
+import { DatabaseStructureService } from 'src/engine/workspace-manager/workspace-health/services/database-structure.service';
+import { FieldMetadataHealthService } from 'src/engine/workspace-manager/workspace-health/services/field-metadata-health.service';
+import { ObjectMetadataHealthService } from 'src/engine/workspace-manager/workspace-health/services/object-metadata-health.service';
+import { RelationMetadataHealthService } from 'src/engine/workspace-manager/workspace-health/services/relation-metadata.health.service';
import { WorkspaceFixService } from 'src/engine/workspace-manager/workspace-health/services/workspace-fix.service';
+import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
@Injectable()
export class WorkspaceHealthService {
+ private readonly logger = new Logger(WorkspaceHealthService.name);
+
constructor(
@InjectDataSource('metadata')
private readonly metadataDataSource: DataSource,
@@ -188,7 +190,7 @@ export class WorkspaceHealthService {
);
} catch (error) {
await queryRunner.rollbackTransaction();
- console.error('Fix of issues failed with:', error);
+ this.logger.error('Fix of issues failed with:', error);
} finally {
await queryRunner.release();
}
diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts
index e09a41d3a..d05ff13ea 100644
--- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts
@@ -1,4 +1,4 @@
-import { Injectable } from '@nestjs/common';
+import { Injectable, Logger } from '@nestjs/common';
import {
QueryRunner,
@@ -32,6 +32,8 @@ import { customTableDefaultColumns } from './utils/custom-table-default-column.u
@Injectable()
export class WorkspaceMigrationRunnerService {
+ private readonly logger = new Logger(WorkspaceMigrationRunnerService.name);
+
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private readonly workspaceMigrationService: WorkspaceMigrationService,
@@ -87,7 +89,7 @@ export class WorkspaceMigrationRunnerService {
await queryRunner.commitTransaction();
} catch (error) {
- console.error('Error executing migration', error);
+ this.logger.error('Error executing migration', error);
await queryRunner.rollbackTransaction();
throw error;
} finally {
diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook.ts
index 64616dd13..dc2a92dd7 100644
--- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook.ts
+++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook.ts
@@ -4,10 +4,10 @@ import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-que
import { FindOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
+import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { CanAccessCalendarEventService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-event.service';
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity';
-import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
@WorkspaceQueryHook({
key: `calendarEvent.findOne`,
@@ -39,13 +39,12 @@ export class CalendarEventFindOnePreQueryHook
'calendarChannelEventAssociation',
);
- // TODO: Re-implement this using twenty ORM
const calendarChannelCalendarEventAssociations =
await calendarChannelEventAssociationRepository.find({
where: {
calendarEventId: payload?.filter?.id?.eq,
},
- relations: ['calendarChannel.connectedAccount'],
+ relations: ['calendarChannel', 'calendarChannel.connectedAccount'],
});
if (calendarChannelCalendarEventAssociations.length === 0) {
diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-event.service.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-event.service.ts
index 1b083eaa4..041c3a648 100644
--- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-event.service.ts
+++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-event.service.ts
@@ -1,15 +1,11 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import groupBy from 'lodash.groupby';
-import { Any } from 'typeorm';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity';
-import {
- CalendarChannelVisibility,
- CalendarChannelWorkspaceEntity,
-} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
+import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
@@ -30,20 +26,9 @@ export class CanAccessCalendarEventService {
workspaceId: string,
calendarChannelCalendarEventAssociations: CalendarChannelEventAssociationWorkspaceEntity[],
) {
- const calendarRepository =
- await this.twentyORMManager.getRepository(
- 'calendarChannel',
- );
-
- const calendarChannels = await calendarRepository.find({
- where: {
- id: Any(
- calendarChannelCalendarEventAssociations.map(
- (association) => association.calendarChannel.id,
- ),
- ),
- },
- });
+ const calendarChannels = calendarChannelCalendarEventAssociations.map(
+ (association) => association.calendarChannel,
+ );
const calendarChannelsGroupByVisibility = groupBy(
calendarChannels,
diff --git a/packages/twenty-server/src/modules/messaging/message-participant-manager/listeners/message-participant-workspace-member.listener.ts b/packages/twenty-server/src/modules/messaging/message-participant-manager/listeners/message-participant-workspace-member.listener.ts
index a7dde2e18..506bf3d5a 100644
--- a/packages/twenty-server/src/modules/messaging/message-participant-manager/listeners/message-participant-workspace-member.listener.ts
+++ b/packages/twenty-server/src/modules/messaging/message-participant-manager/listeners/message-participant-workspace-member.listener.ts
@@ -1,6 +1,13 @@
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+
+import {
+ Workspace,
+ WorkspaceActivationStatus,
+} from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event';
import { ObjectRecordUpdateEvent } from 'src/engine/integrations/event-emitter/types/object-record-update.event';
import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/engine/integrations/event-emitter/utils/object-record-changed-properties.util';
@@ -8,12 +15,12 @@ import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decora
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service';
import {
- MessageParticipantMatchParticipantJobData,
MessageParticipantMatchParticipantJob,
+ MessageParticipantMatchParticipantJobData,
} from 'src/modules/messaging/message-participant-manager/jobs/message-participant-match-participant.job';
import {
- MessageParticipantUnmatchParticipantJobData,
MessageParticipantUnmatchParticipantJob,
+ MessageParticipantUnmatchParticipantJobData,
} from 'src/modules/messaging/message-participant-manager/jobs/message-participant-unmatch-participant.job';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@@ -22,12 +29,25 @@ export class MessageParticipantWorkspaceMemberListener {
constructor(
@InjectMessageQueue(MessageQueue.messagingQueue)
private readonly messageQueueService: MessageQueueService,
+ @InjectRepository(Workspace, 'core')
+ private readonly workspaceRepository: Repository,
) {}
@OnEvent('workspaceMember.created')
async handleCreatedEvent(
payload: ObjectRecordCreateEvent,
) {
+ const workspace = await this.workspaceRepository.findOneBy({
+ id: payload.workspaceId,
+ });
+
+ if (
+ !workspace ||
+ workspace.activationStatus !== WorkspaceActivationStatus.ACTIVE
+ ) {
+ return;
+ }
+
if (payload.properties.after.userEmail === null) {
return;
}
diff --git a/packages/twenty-server/src/modules/messaging/message-participant-manager/message-participant-manager.module.ts b/packages/twenty-server/src/modules/messaging/message-participant-manager/message-participant-manager.module.ts
index d905c3d15..d31454791 100644
--- a/packages/twenty-server/src/modules/messaging/message-participant-manager/message-participant-manager.module.ts
+++ b/packages/twenty-server/src/modules/messaging/message-participant-manager/message-participant-manager.module.ts
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
+import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
@@ -22,7 +23,7 @@ import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-o
@Module({
imports: [
- TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
+ TypeOrmModule.forFeature([FeatureFlagEntity, Workspace], 'core'),
AnalyticsModule,
ContactCreationManagerModule,
WorkspaceDataSourceModule,
diff --git a/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts b/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts
index 548fbea14..142b000b9 100644
--- a/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts
+++ b/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts
@@ -13,6 +13,7 @@ import {
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
+import { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator';
import { WorkspaceIsDeprecated } from 'src/engine/twenty-orm/decorators/workspace-is-deprecated.decorator';
import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator';
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
@@ -90,6 +91,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
],
defaultValue: "'NEW'",
})
+ @WorkspaceIndex()
stage: string;
@WorkspaceField({
diff --git a/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts b/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts
index 1285f8ed7..159fb8622 100644
--- a/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts
+++ b/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts
@@ -1,4 +1,4 @@
-import { Global, Module } from '@nestjs/common';
+import { Module } from '@nestjs/common';
import { WorkflowCommonService } from 'src/modules/workflow/common/workflow-common.services';