From dde70ee3b0fb71b0ef64b3e60e939c4060f8f7fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Mon, 24 Feb 2025 21:38:41 +0100 Subject: [PATCH] Add fields for admin panel access and workspace version (#10451) Prepare for better version upgrade system + split admin panel into two permissions + fix GraphQL generation detection --------- Co-authored-by: ehconitin --- .github/workflows/ci-server.yaml | 17 ++++++------- .../src/generated-metadata/graphql.ts | 4 ++- .../twenty-front/src/generated/graphql.tsx | 7 ++++-- .../src/modules/app/components/AppRouter.tsx | 4 ++- .../modules/auth/hooks/__mocks__/useAuth.ts | 1 + .../modules/auth/states/currentUserState.ts | 1 + .../hooks/__mocks__/useFieldMetadataItem.ts | 2 ++ .../components/SettingsAdminGeneral.tsx | 21 ++++++++++++++-- .../useSettingsNavigationItems.test.tsx | 1 + .../hooks/useSettingsNavigationItems.tsx | 4 ++- .../graphql/fragments/userQueryFragment.ts | 1 + .../src/testing/mock-data/users.ts | 3 +++ .../src/database/typeorm-seeds/core/users.ts | 4 +++ ...addAccessToFullAdminAndWorkspaceVersion.ts | 25 +++++++++++++++++++ .../auth/services/sign-in-up.service.ts | 3 +++ .../engine/core-modules/user/user.entity.ts | 4 +++ .../workspace/workspace.entity.ts | 6 ++++- 17 files changed, 91 insertions(+), 17 deletions(-) create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1740415309924-addAccessToFullAdminAndWorkspaceVersion.ts diff --git a/.github/workflows/ci-server.yaml b/.github/workflows/ci-server.yaml index 6e12499d9..bc282fb55 100644 --- a/.github/workflows/ci-server.yaml +++ b/.github/workflows/ci-server.yaml @@ -108,15 +108,14 @@ jobs: - name: GraphQL / Check for Pending Generation if: steps.changed-files.outputs.any_changed == 'true' run: | - GRAPHQL_GENERATE_OUTPUT=$(npx nx run twenty-front:graphql:generate || true) - GRAPHQL_METADATA_OUTPUT=$(npx nx run twenty-front:graphql:generate --configuration=metadata || true) - if [[ $GRAPHQL_GENERATE_OUTPUT == *"No changes detected"* && $GRAPHQL_METADATA_OUTPUT == *"No changes detected"* ]]; then - echo "GraphQL generation check passed." - else - echo "::error::Unexpected GraphQL changes detected. Please run the required commands and commit the changes." - echo "$GRAPHQL_GENERATE_OUTPUT" - echo "$GRAPHQL_METADATA_OUTPUT" - exit 1 + # Run GraphQL generation commands + npx nx run twenty-front:graphql:generate + npx nx run twenty-front:graphql:generate --configuration=metadata + + # Check if any files were modified + if ! git diff --quiet; then + echo "::error::GraphQL schema changes detected. Please run 'npx nx run twenty-front:graphql:generate' and 'npx nx run twenty-front:graphql:generate --configuration=metadata' and commit the changes." + exit 1 fi - name: Save server setup uses: ./.github/workflows/actions/save-cache diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 26d07753a..f79f37cf7 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1815,7 +1815,7 @@ export enum SettingsPermissions { ROLES = 'ROLES', SECURITY = 'SECURITY', WORKSPACE = 'WORKSPACE', - WORKSPACE_USERS = 'WORKSPACE_USERS' + WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS' } export type SetupOidcSsoInput = { @@ -2058,6 +2058,7 @@ export type UpdateWorkspaceInput = { export type User = { __typename?: 'User'; analyticsTinybirdJwts?: Maybe; + canAccessFullAdminPanel: Scalars['Boolean']['output']; canImpersonate: Scalars['Boolean']['output']; createdAt: Scalars['DateTime']['output']; currentUserWorkspace?: Maybe; @@ -2211,6 +2212,7 @@ export type Workspace = { metadataVersion: Scalars['Float']['output']; subdomain: Scalars['String']['output']; updatedAt: Scalars['DateTime']['output']; + version?: Maybe; workspaceMembersCount?: Maybe; workspaceUrls: WorkspaceUrls; }; diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 08c627f04..601cc686a 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -1846,6 +1846,7 @@ export type UpdateWorkspaceInput = { export type User = { __typename?: 'User'; analyticsTinybirdJwts?: Maybe; + canAccessFullAdminPanel: Scalars['Boolean']; canImpersonate: Scalars['Boolean']; createdAt: Scalars['DateTime']; currentUserWorkspace?: Maybe; @@ -1989,6 +1990,7 @@ export type Workspace = { metadataVersion: Scalars['Float']; subdomain: Scalars['String']; updatedAt: Scalars['DateTime']; + version?: Maybe; workspaceMembersCount?: Maybe; workspaceUrls: WorkspaceUrls; }; @@ -2434,7 +2436,7 @@ export type GetSsoIdentityProvidersQueryVariables = Exact<{ [key: string]: never export type GetSsoIdentityProvidersQuery = { __typename?: 'Query', getSSOIdentityProviders: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> }; -export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array | null, objectRecordsPermissions?: Array | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> }; +export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array | null, objectRecordsPermissions?: Array | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> }; export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>; @@ -2451,7 +2453,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array | null, objectRecordsPermissions?: Array | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } }; +export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array | null, objectRecordsPermissions?: Array | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } }; export type ActivateWorkflowVersionMutationVariables = Exact<{ workflowVersionId: Scalars['String']; @@ -2709,6 +2711,7 @@ export const UserQueryFragmentFragmentDoc = gql` firstName lastName email + canAccessFullAdminPanel canImpersonate supportUserHash analyticsTinybirdJwts { diff --git a/packages/twenty-front/src/modules/app/components/AppRouter.tsx b/packages/twenty-front/src/modules/app/components/AppRouter.tsx index 0fd81abd4..c5ebc2815 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouter.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouter.tsx @@ -9,7 +9,9 @@ export const AppRouter = () => { const currentUser = useRecoilValue(currentUserState); - const isAdminPageEnabled = currentUser?.canImpersonate; + const isAdminPageEnabled = + (currentUser?.canImpersonate || currentUser?.canAccessFullAdminPanel) ?? + false; return ( theme.spacing(4)} 0; `; +const StyledErrorMessage = styled.div` + color: ${({ theme }) => theme.color.red}; + margin-top: ${({ theme }) => theme.spacing(2)}; +`; + export const SettingsAdminGeneral = () => { const [userIdentifier, setUserIdentifier] = useState(''); const { enqueueSnackBar } = useSnackBar(); @@ -67,6 +73,10 @@ export const SettingsAdminGeneral = () => { const [userLookup] = useUserLookupAdminPanelMutation(); + const currentUser = useRecoilValue(currentUserState); + + const canImpersonate = currentUser?.canImpersonate; + const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState); const handleSearch = async () => { @@ -154,9 +164,16 @@ export const SettingsAdminGeneral = () => { accent="blue" title={t`Search`} onClick={handleSearch} - disabled={!userIdentifier.trim() || isUserLookupLoading} + disabled={ + !userIdentifier.trim() || isUserLookupLoading || !canImpersonate + } /> + {!canImpersonate && ( + + {t`You do not have access to impersonate users.`} + + )} {isDefined(userLookupResult) && ( diff --git a/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx b/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx index 1de3e9a6e..84f3d47a7 100644 --- a/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx @@ -21,6 +21,7 @@ const mockCurrentUser = { email: 'fake@email.com', supportUserHash: null, analyticsTinybirdJwts: null, + canAccessFullAdminPanel: false, canImpersonate: false, onboardingStatus: OnboardingStatus.COMPLETED, userVars: {}, diff --git a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx index 437817080..249dc8420 100644 --- a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx @@ -56,7 +56,9 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { const isFunctionSettingsEnabled = false; const isBillingEnabled = billing?.isBillingEnabled ?? false; const currentUser = useRecoilValue(currentUserState); - const isAdminEnabled = currentUser?.canImpersonate ?? false; + const isAdminEnabled = + (currentUser?.canImpersonate || currentUser?.canAccessFullAdminPanel) ?? + false; const labPublicFeatureFlags = useRecoilValue(labPublicFeatureFlagsState); const featureFlags = useFeatureFlagsMap(); diff --git a/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts b/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts index ded855dc9..cf4e31f13 100644 --- a/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts +++ b/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts @@ -7,6 +7,7 @@ export const USER_QUERY_FRAGMENT = gql` firstName lastName email + canAccessFullAdminPanel canImpersonate supportUserHash analyticsTinybirdJwts { diff --git a/packages/twenty-front/src/testing/mock-data/users.ts b/packages/twenty-front/src/testing/mock-data/users.ts index 5d98a6737..8003f42b2 100644 --- a/packages/twenty-front/src/testing/mock-data/users.ts +++ b/packages/twenty-front/src/testing/mock-data/users.ts @@ -19,6 +19,7 @@ type MockedUser = Pick< | 'email' | 'firstName' | 'lastName' + | 'canAccessFullAdminPanel' | 'canImpersonate' | '__typename' | 'supportUserHash' @@ -123,6 +124,7 @@ export const mockedUserData: MockedUser = { email: 'charles@test.com', firstName: 'Charles', lastName: 'Test', + canAccessFullAdminPanel: false, canImpersonate: false, supportUserHash: 'a95afad9ff6f0b364e2a3fd3e246a1a852c22b6e55a3ca33745a86c201f9c10d', @@ -148,6 +150,7 @@ export const mockedOnboardingUserData = ( email: 'workspace-onboarding@test.com', firstName: '', lastName: '', + canAccessFullAdminPanel: false, canImpersonate: false, supportUserHash: '4fb61d34ed3a4aeda2476d4b308b5162db9e1809b2b8277e6fdc6efc4a609254', diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/users.ts b/packages/twenty-server/src/database/typeorm-seeds/core/users.ts index 5f5f4d281..5e687bc87 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/core/users.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/core/users.ts @@ -22,6 +22,7 @@ export const seedUsers = async ( 'email', 'passwordHash', 'canImpersonate', + 'canAccessFullAdminPanel', ]) .orIgnore() .values([ @@ -33,6 +34,7 @@ export const seedUsers = async ( passwordHash: '$2b$10$66d.6DuQExxnrfI9rMqOg.U1XIYpagr6Lv05uoWLYbYmtK0HDIvS6', // Applecar2025 canImpersonate: true, + canAccessFullAdminPanel: true, }, { id: DEV_SEED_USER_IDS.JONY, @@ -42,6 +44,7 @@ export const seedUsers = async ( passwordHash: '$2b$10$66d.6DuQExxnrfI9rMqOg.U1XIYpagr6Lv05uoWLYbYmtK0HDIvS6', // Applecar2025 canImpersonate: true, + canAccessFullAdminPanel: true, }, { id: DEV_SEED_USER_IDS.PHIL, @@ -51,6 +54,7 @@ export const seedUsers = async ( passwordHash: '$2b$10$66d.6DuQExxnrfI9rMqOg.U1XIYpagr6Lv05uoWLYbYmtK0HDIvS6', // Applecar2025 canImpersonate: true, + canAccessFullAdminPanel: true, }, ]) .execute(); diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1740415309924-addAccessToFullAdminAndWorkspaceVersion.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1740415309924-addAccessToFullAdminAndWorkspaceVersion.ts new file mode 100644 index 000000000..747839eb5 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1740415309924-addAccessToFullAdminAndWorkspaceVersion.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAccessToFullAdminAndWorkspaceVersion1740415309924 + implements MigrationInterface +{ + name = 'AddAccessToFullAdminAndWorkspaceVersion1740415309924'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."user" ADD "canAccessFullAdminPanel" boolean NOT NULL DEFAULT false`, + ); + await queryRunner.query( + `ALTER TABLE "core"."workspace" ADD "version" character varying`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."workspace" DROP COLUMN "version"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."user" DROP COLUMN "canAccessFullAdminPanel"`, + ); + } +} 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 477f48c67..3c0efac36 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 @@ -207,6 +207,7 @@ export class SignInUpService { const userToCreate = this.userRepository.create({ ...newUser, defaultAvatarUrl: imagePath, + canAccessFullAdminPanel: false, canImpersonate: false, } as Partial); @@ -289,6 +290,7 @@ export class SignInUpService { const user: PartialUserWithPicture = { ...partialUserWithPicture, canImpersonate: false, + canAccessFullAdminPanel: false, }; if (!user.email) { @@ -303,6 +305,7 @@ export class SignInUpService { // if the workspace doesn't exist it means it's the first user of the workspace user.canImpersonate = true; + user.canAccessFullAdminPanel = true; // let the creation of the first workspace if (workspacesCount > 0) { diff --git a/packages/twenty-server/src/engine/core-modules/user/user.entity.ts b/packages/twenty-server/src/engine/core-modules/user/user.entity.ts index 58e0ee479..ac10d4a1f 100644 --- a/packages/twenty-server/src/engine/core-modules/user/user.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/user/user.entity.ts @@ -69,6 +69,10 @@ export class User { @Column({ default: false }) canImpersonate: boolean; + @Field() + @Column({ default: false }) + canAccessFullAdminPanel: boolean; + @Field() @CreateDateColumn({ type: 'timestamptz' }) createdAt: Date; 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 6a6b041b5..a192a3993 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 @@ -15,12 +15,12 @@ import { import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity'; +import { ApprovedAccessDomain } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity'; import { PostgresCredentials } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.entity'; import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; -import { ApprovedAccessDomain } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity'; registerEnumType(WorkspaceActivationStatus, { name: 'WorkspaceActivationStatus', @@ -150,4 +150,8 @@ export class Workspace { @Field() @Column({ default: false }) isCustomDomainEnabled: boolean; + + @Field(() => String, { nullable: true }) + @Column({ type: 'varchar', nullable: true }) + version: string | null; }