Refactor client config (#529)

* Refactor client config

* Fix server tests

* Fix lint
This commit is contained in:
Charles Bochet
2023-07-07 11:10:42 -07:00
committed by GitHub
parent 11d18cc269
commit 26b033abc9
38 changed files with 386 additions and 180 deletions

2
front/.gitignore vendored
View File

@ -23,3 +23,5 @@ build-storybook.log
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
.nyc_output

View File

@ -29,6 +29,13 @@ export type Analytics = {
success: Scalars['Boolean']; success: Scalars['Boolean'];
}; };
export type AuthProviders = {
__typename?: 'AuthProviders';
google: Scalars['Boolean'];
magicLink: Scalars['Boolean'];
password: Scalars['Boolean'];
};
export type AuthToken = { export type AuthToken = {
__typename?: 'AuthToken'; __typename?: 'AuthToken';
expiresAt: Scalars['DateTime']; expiresAt: Scalars['DateTime'];
@ -57,8 +64,10 @@ export type BoolFilter = {
export type ClientConfig = { export type ClientConfig = {
__typename?: 'ClientConfig'; __typename?: 'ClientConfig';
display_google_login: Scalars['Boolean']; authProviders: AuthProviders;
prefill_login_with_seed: Scalars['Boolean']; debugMode: Scalars['Boolean'];
demoMode: Scalars['Boolean'];
telemetry: Telemetry;
}; };
export type Comment = { export type Comment = {
@ -756,11 +765,11 @@ export type CompanyOrderByRelationAggregateInput = {
export type CompanyOrderByWithRelationInput = { export type CompanyOrderByWithRelationInput = {
accountOwner?: InputMaybe<UserOrderByWithRelationInput>; accountOwner?: InputMaybe<UserOrderByWithRelationInput>;
accountOwnerId?: InputMaybe<SortOrderInput>; accountOwnerId?: InputMaybe<SortOrder>;
address?: InputMaybe<SortOrder>; address?: InputMaybe<SortOrder>;
createdAt?: InputMaybe<SortOrder>; createdAt?: InputMaybe<SortOrder>;
domainName?: InputMaybe<SortOrder>; domainName?: InputMaybe<SortOrder>;
employees?: InputMaybe<SortOrderInput>; employees?: InputMaybe<SortOrder>;
id?: InputMaybe<SortOrder>; id?: InputMaybe<SortOrder>;
name?: InputMaybe<SortOrder>; name?: InputMaybe<SortOrder>;
people?: InputMaybe<PersonOrderByRelationAggregateInput>; people?: InputMaybe<PersonOrderByRelationAggregateInput>;
@ -1247,11 +1256,6 @@ export type NullableStringFieldUpdateOperationsInput = {
set?: InputMaybe<Scalars['String']>; set?: InputMaybe<Scalars['String']>;
}; };
export enum NullsOrder {
First = 'first',
Last = 'last'
}
export type Person = { export type Person = {
__typename?: 'Person'; __typename?: 'Person';
_commentCount: Scalars['Int']; _commentCount: Scalars['Int'];
@ -1332,7 +1336,7 @@ export type PersonOrderByRelationAggregateInput = {
export type PersonOrderByWithRelationInput = { export type PersonOrderByWithRelationInput = {
city?: InputMaybe<SortOrder>; city?: InputMaybe<SortOrder>;
company?: InputMaybe<CompanyOrderByWithRelationInput>; company?: InputMaybe<CompanyOrderByWithRelationInput>;
companyId?: InputMaybe<SortOrderInput>; companyId?: InputMaybe<SortOrder>;
createdAt?: InputMaybe<SortOrder>; createdAt?: InputMaybe<SortOrder>;
email?: InputMaybe<SortOrder>; email?: InputMaybe<SortOrder>;
firstName?: InputMaybe<SortOrder>; firstName?: InputMaybe<SortOrder>;
@ -1567,6 +1571,7 @@ export type PipelineProgressCreateInput = {
export type PipelineProgressCreateManyPipelineInput = { export type PipelineProgressCreateManyPipelineInput = {
amount?: InputMaybe<Scalars['Int']>; amount?: InputMaybe<Scalars['Int']>;
closeDate?: InputMaybe<Scalars['DateTime']>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
pipelineStageId: Scalars['String']; pipelineStageId: Scalars['String'];
@ -1582,6 +1587,7 @@ export type PipelineProgressCreateManyPipelineInputEnvelope = {
export type PipelineProgressCreateManyPipelineStageInput = { export type PipelineProgressCreateManyPipelineStageInput = {
amount?: InputMaybe<Scalars['Int']>; amount?: InputMaybe<Scalars['Int']>;
closeDate?: InputMaybe<Scalars['DateTime']>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
pipelineId: Scalars['String']; pipelineId: Scalars['String'];
@ -1597,6 +1603,7 @@ export type PipelineProgressCreateManyPipelineStageInputEnvelope = {
export type PipelineProgressCreateManyWorkspaceInput = { export type PipelineProgressCreateManyWorkspaceInput = {
amount?: InputMaybe<Scalars['Int']>; amount?: InputMaybe<Scalars['Int']>;
closeDate?: InputMaybe<Scalars['DateTime']>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
pipelineId: Scalars['String']; pipelineId: Scalars['String'];
@ -1642,6 +1649,7 @@ export type PipelineProgressCreateOrConnectWithoutWorkspaceInput = {
export type PipelineProgressCreateWithoutPipelineInput = { export type PipelineProgressCreateWithoutPipelineInput = {
amount?: InputMaybe<Scalars['Int']>; amount?: InputMaybe<Scalars['Int']>;
closeDate?: InputMaybe<Scalars['DateTime']>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
pipelineStage: PipelineStageCreateNestedOneWithoutPipelineProgressesInput; pipelineStage: PipelineStageCreateNestedOneWithoutPipelineProgressesInput;
@ -1652,6 +1660,7 @@ export type PipelineProgressCreateWithoutPipelineInput = {
export type PipelineProgressCreateWithoutPipelineStageInput = { export type PipelineProgressCreateWithoutPipelineStageInput = {
amount?: InputMaybe<Scalars['Int']>; amount?: InputMaybe<Scalars['Int']>;
closeDate?: InputMaybe<Scalars['DateTime']>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
pipeline: PipelineCreateNestedOneWithoutPipelineProgressesInput; pipeline: PipelineCreateNestedOneWithoutPipelineProgressesInput;
@ -1662,6 +1671,7 @@ export type PipelineProgressCreateWithoutPipelineStageInput = {
export type PipelineProgressCreateWithoutWorkspaceInput = { export type PipelineProgressCreateWithoutWorkspaceInput = {
amount?: InputMaybe<Scalars['Int']>; amount?: InputMaybe<Scalars['Int']>;
closeDate?: InputMaybe<Scalars['DateTime']>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
pipeline: PipelineCreateNestedOneWithoutPipelineProgressesInput; pipeline: PipelineCreateNestedOneWithoutPipelineProgressesInput;
@ -1682,8 +1692,8 @@ export type PipelineProgressOrderByRelationAggregateInput = {
}; };
export type PipelineProgressOrderByWithRelationInput = { export type PipelineProgressOrderByWithRelationInput = {
amount?: InputMaybe<SortOrderInput>; amount?: InputMaybe<SortOrder>;
closeDate?: InputMaybe<SortOrderInput>; closeDate?: InputMaybe<SortOrder>;
createdAt?: InputMaybe<SortOrder>; createdAt?: InputMaybe<SortOrder>;
id?: InputMaybe<SortOrder>; id?: InputMaybe<SortOrder>;
pipeline?: InputMaybe<PipelineOrderByWithRelationInput>; pipeline?: InputMaybe<PipelineOrderByWithRelationInput>;
@ -1714,6 +1724,7 @@ export type PipelineProgressScalarWhereInput = {
NOT?: InputMaybe<Array<PipelineProgressScalarWhereInput>>; NOT?: InputMaybe<Array<PipelineProgressScalarWhereInput>>;
OR?: InputMaybe<Array<PipelineProgressScalarWhereInput>>; OR?: InputMaybe<Array<PipelineProgressScalarWhereInput>>;
amount?: InputMaybe<IntNullableFilter>; amount?: InputMaybe<IntNullableFilter>;
closeDate?: InputMaybe<DateTimeNullableFilter>;
createdAt?: InputMaybe<DateTimeFilter>; createdAt?: InputMaybe<DateTimeFilter>;
id?: InputMaybe<StringFilter>; id?: InputMaybe<StringFilter>;
pipelineId?: InputMaybe<StringFilter>; pipelineId?: InputMaybe<StringFilter>;
@ -1737,6 +1748,7 @@ export type PipelineProgressUpdateInput = {
export type PipelineProgressUpdateManyMutationInput = { export type PipelineProgressUpdateManyMutationInput = {
amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>; amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>;
closeDate?: InputMaybe<NullableDateTimeFieldUpdateOperationsInput>;
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>; createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
id?: InputMaybe<StringFieldUpdateOperationsInput>; id?: InputMaybe<StringFieldUpdateOperationsInput>;
progressableId?: InputMaybe<StringFieldUpdateOperationsInput>; progressableId?: InputMaybe<StringFieldUpdateOperationsInput>;
@ -1818,6 +1830,7 @@ export type PipelineProgressUpdateWithWhereUniqueWithoutWorkspaceInput = {
export type PipelineProgressUpdateWithoutPipelineInput = { export type PipelineProgressUpdateWithoutPipelineInput = {
amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>; amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>;
closeDate?: InputMaybe<NullableDateTimeFieldUpdateOperationsInput>;
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>; createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
id?: InputMaybe<StringFieldUpdateOperationsInput>; id?: InputMaybe<StringFieldUpdateOperationsInput>;
pipelineStage?: InputMaybe<PipelineStageUpdateOneRequiredWithoutPipelineProgressesNestedInput>; pipelineStage?: InputMaybe<PipelineStageUpdateOneRequiredWithoutPipelineProgressesNestedInput>;
@ -1828,6 +1841,7 @@ export type PipelineProgressUpdateWithoutPipelineInput = {
export type PipelineProgressUpdateWithoutPipelineStageInput = { export type PipelineProgressUpdateWithoutPipelineStageInput = {
amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>; amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>;
closeDate?: InputMaybe<NullableDateTimeFieldUpdateOperationsInput>;
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>; createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
id?: InputMaybe<StringFieldUpdateOperationsInput>; id?: InputMaybe<StringFieldUpdateOperationsInput>;
pipeline?: InputMaybe<PipelineUpdateOneRequiredWithoutPipelineProgressesNestedInput>; pipeline?: InputMaybe<PipelineUpdateOneRequiredWithoutPipelineProgressesNestedInput>;
@ -1838,6 +1852,7 @@ export type PipelineProgressUpdateWithoutPipelineStageInput = {
export type PipelineProgressUpdateWithoutWorkspaceInput = { export type PipelineProgressUpdateWithoutWorkspaceInput = {
amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>; amount?: InputMaybe<NullableIntFieldUpdateOperationsInput>;
closeDate?: InputMaybe<NullableDateTimeFieldUpdateOperationsInput>;
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>; createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
id?: InputMaybe<StringFieldUpdateOperationsInput>; id?: InputMaybe<StringFieldUpdateOperationsInput>;
pipeline?: InputMaybe<PipelineUpdateOneRequiredWithoutPipelineProgressesNestedInput>; pipeline?: InputMaybe<PipelineUpdateOneRequiredWithoutPipelineProgressesNestedInput>;
@ -2356,11 +2371,6 @@ export enum SortOrder {
Desc = 'desc' Desc = 'desc'
} }
export type SortOrderInput = {
nulls?: InputMaybe<NullsOrder>;
sort: SortOrder;
};
export type StringFieldUpdateOperationsInput = { export type StringFieldUpdateOperationsInput = {
set?: InputMaybe<Scalars['String']>; set?: InputMaybe<Scalars['String']>;
}; };
@ -2395,6 +2405,12 @@ export type StringNullableFilter = {
startsWith?: InputMaybe<Scalars['String']>; startsWith?: InputMaybe<Scalars['String']>;
}; };
export type Telemetry = {
__typename?: 'Telemetry';
anonymizationEnabled: Scalars['Boolean'];
enabled: Scalars['Boolean'];
};
export type User = { export type User = {
__typename?: 'User'; __typename?: 'User';
avatarUrl?: Maybe<Scalars['String']>; avatarUrl?: Maybe<Scalars['String']>;
@ -2476,7 +2492,7 @@ export type UserCreateWithoutWorkspaceMemberInput = {
}; };
export type UserOrderByWithRelationInput = { export type UserOrderByWithRelationInput = {
avatarUrl?: InputMaybe<SortOrderInput>; avatarUrl?: InputMaybe<SortOrder>;
comments?: InputMaybe<CommentOrderByRelationAggregateInput>; comments?: InputMaybe<CommentOrderByRelationAggregateInput>;
companies?: InputMaybe<CompanyOrderByRelationAggregateInput>; companies?: InputMaybe<CompanyOrderByRelationAggregateInput>;
createdAt?: InputMaybe<SortOrder>; createdAt?: InputMaybe<SortOrder>;
@ -2486,10 +2502,10 @@ export type UserOrderByWithRelationInput = {
firstName?: InputMaybe<SortOrder>; firstName?: InputMaybe<SortOrder>;
id?: InputMaybe<SortOrder>; id?: InputMaybe<SortOrder>;
lastName?: InputMaybe<SortOrder>; lastName?: InputMaybe<SortOrder>;
lastSeen?: InputMaybe<SortOrderInput>; lastSeen?: InputMaybe<SortOrder>;
locale?: InputMaybe<SortOrder>; locale?: InputMaybe<SortOrder>;
metadata?: InputMaybe<SortOrderInput>; metadata?: InputMaybe<SortOrder>;
phoneNumber?: InputMaybe<SortOrderInput>; phoneNumber?: InputMaybe<SortOrder>;
updatedAt?: InputMaybe<SortOrder>; updatedAt?: InputMaybe<SortOrder>;
}; };
@ -2766,11 +2782,6 @@ export type CreateEventMutationVariables = Exact<{
export type CreateEventMutation = { __typename?: 'Mutation', createEvent: { __typename?: 'Analytics', success: boolean } }; export type CreateEventMutation = { __typename?: 'Mutation', createEvent: { __typename?: 'Analytics', success: boolean } };
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', display_google_login: boolean, prefill_login_with_seed: boolean } };
export type ChallengeMutationVariables = Exact<{ export type ChallengeMutationVariables = Exact<{
email: Scalars['String']; email: Scalars['String'];
password: Scalars['String']; password: Scalars['String'];
@ -2793,6 +2804,11 @@ export type RenewTokenMutationVariables = Exact<{
export type RenewTokenMutation = { __typename?: 'Mutation', renewToken: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', expiresAt: string, token: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type RenewTokenMutation = { __typename?: 'Mutation', renewToken: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', expiresAt: string, token: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', demoMode: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean } } };
export type CreateCommentMutationVariables = Exact<{ export type CreateCommentMutationVariables = Exact<{
commentId: Scalars['String']; commentId: Scalars['String'];
commentText: Scalars['String']; commentText: Scalars['String'];
@ -2954,7 +2970,7 @@ export type UpdateOnePipelineProgressMutationVariables = Exact<{
}>; }>;
export type UpdateOnePipelineProgressMutation = { __typename?: 'Mutation', updateOnePipelineProgress?: { __typename?: 'PipelineProgress', id: string } | null }; export type UpdateOnePipelineProgressMutation = { __typename?: 'Mutation', updateOnePipelineProgress?: { __typename?: 'PipelineProgress', id: string, amount?: number | null, closeDate?: string | null } | null };
export type UpdateOnePipelineProgressStageMutationVariables = Exact<{ export type UpdateOnePipelineProgressStageMutationVariables = Exact<{
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
@ -3074,41 +3090,6 @@ export function useCreateEventMutation(baseOptions?: Apollo.MutationHookOptions<
export type CreateEventMutationHookResult = ReturnType<typeof useCreateEventMutation>; export type CreateEventMutationHookResult = ReturnType<typeof useCreateEventMutation>;
export type CreateEventMutationResult = Apollo.MutationResult<CreateEventMutation>; export type CreateEventMutationResult = Apollo.MutationResult<CreateEventMutation>;
export type CreateEventMutationOptions = Apollo.BaseMutationOptions<CreateEventMutation, CreateEventMutationVariables>; export type CreateEventMutationOptions = Apollo.BaseMutationOptions<CreateEventMutation, CreateEventMutationVariables>;
export const GetClientConfigDocument = gql`
query GetClientConfig {
clientConfig {
display_google_login
prefill_login_with_seed
}
}
`;
/**
* __useGetClientConfigQuery__
*
* To run a query within a React component, call `useGetClientConfigQuery` and pass it any options that fit your needs.
* When your component renders, `useGetClientConfigQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetClientConfigQuery({
* variables: {
* },
* });
*/
export function useGetClientConfigQuery(baseOptions?: Apollo.QueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
}
export function useGetClientConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
}
export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>;
export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>;
export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>;
export const ChallengeDocument = gql` export const ChallengeDocument = gql`
mutation Challenge($email: String!, $password: String!) { mutation Challenge($email: String!, $password: String!) {
challenge(email: $email, password: $password) { challenge(email: $email, password: $password) {
@ -3246,6 +3227,49 @@ export function useRenewTokenMutation(baseOptions?: Apollo.MutationHookOptions<R
export type RenewTokenMutationHookResult = ReturnType<typeof useRenewTokenMutation>; export type RenewTokenMutationHookResult = ReturnType<typeof useRenewTokenMutation>;
export type RenewTokenMutationResult = Apollo.MutationResult<RenewTokenMutation>; export type RenewTokenMutationResult = Apollo.MutationResult<RenewTokenMutation>;
export type RenewTokenMutationOptions = Apollo.BaseMutationOptions<RenewTokenMutation, RenewTokenMutationVariables>; export type RenewTokenMutationOptions = Apollo.BaseMutationOptions<RenewTokenMutation, RenewTokenMutationVariables>;
export const GetClientConfigDocument = gql`
query GetClientConfig {
clientConfig {
authProviders {
google
password
}
demoMode
debugMode
telemetry {
enabled
anonymizationEnabled
}
}
}
`;
/**
* __useGetClientConfigQuery__
*
* To run a query within a React component, call `useGetClientConfigQuery` and pass it any options that fit your needs.
* When your component renders, `useGetClientConfigQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetClientConfigQuery({
* variables: {
* },
* });
*/
export function useGetClientConfigQuery(baseOptions?: Apollo.QueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
}
export function useGetClientConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
}
export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>;
export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>;
export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>;
export const CreateCommentDocument = gql` export const CreateCommentDocument = gql`
mutation CreateComment($commentId: String!, $commentText: String!, $authorId: String!, $commentThreadId: String!, $createdAt: DateTime!) { mutation CreateComment($commentId: String!, $commentText: String!, $authorId: String!, $commentThreadId: String!, $createdAt: DateTime!) {
createOneComment( createOneComment(
@ -4016,6 +4040,8 @@ export const UpdateOnePipelineProgressDocument = gql`
data: {amount: {set: $amount}, closeDate: {set: $closeDate}} data: {amount: {set: $amount}, closeDate: {set: $closeDate}}
) { ) {
id id
amount
closeDate
} }
} }
`; `;

View File

@ -8,7 +8,7 @@ import { ThemeType } from '@/ui/themes/themes';
import '@emotion/react'; import '@emotion/react';
import { ApolloProvider } from './providers/apollo/ApolloProvider'; import { ApolloProvider } from './providers/apollo/ApolloProvider';
import { ClientConfigProvider } from './providers/clientConfig/ClientConfigProvider'; import { ClientConfigProvider } from './providers/client-config/ClientConfigProvider';
import { AppThemeProvider } from './providers/theme/AppThemeProvider'; import { AppThemeProvider } from './providers/theme/AppThemeProvider';
import { UserProvider } from './providers/user/UserProvider'; import { UserProvider } from './providers/user/UserProvider';
import { App } from './App'; import { App } from './App';
@ -26,11 +26,11 @@ root.render(
<AppThemeProvider> <AppThemeProvider>
<StrictMode> <StrictMode>
<UserProvider> <UserProvider>
<BrowserRouter> <ClientConfigProvider>
<ClientConfigProvider> <BrowserRouter>
<App /> <App />
</ClientConfigProvider> </BrowserRouter>
</BrowserRouter> </ClientConfigProvider>
</UserProvider> </UserProvider>
</StrictMode> </StrictMode>
</AppThemeProvider> </AppThemeProvider>

View File

@ -1,9 +1,9 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useRecoilState } from 'recoil';
import { telemetryState } from '@/client-config/states/telemetryState';
import { useCreateEventMutation } from '~/generated/graphql'; import { useCreateEventMutation } from '~/generated/graphql';
import { useIsTelemetryEnabled } from './useIsTelemetryEnabled';
interface EventLocation { interface EventLocation {
pathname: string; pathname: string;
} }
@ -13,12 +13,12 @@ export interface EventData {
} }
export function useEventTracker() { export function useEventTracker() {
const telemetryEnabled = useIsTelemetryEnabled(); const [telemetry] = useRecoilState(telemetryState);
const [createEventMutation] = useCreateEventMutation(); const [createEventMutation] = useCreateEventMutation();
return useCallback( return useCallback(
(eventType: string, eventData: EventData) => { (eventType: string, eventData: EventData) => {
if (telemetryEnabled) { if (telemetry.enabled) {
createEventMutation({ createEventMutation({
variables: { variables: {
type: eventType, type: eventType,
@ -27,6 +27,6 @@ export function useEventTracker() {
}); });
} }
}, },
[createEventMutation, telemetryEnabled], [createEventMutation, telemetry],
); );
} }

View File

@ -1,4 +0,0 @@
export function useIsTelemetryEnabled() {
// TODO: replace by clientConfig
return process.env.IS_TELEMETRY_ENABLED !== 'false';
}

View File

@ -8,6 +8,7 @@ import { useRecoilState } from 'recoil';
import { isMockModeState } from '@/auth/states/isMockModeState'; import { isMockModeState } from '@/auth/states/isMockModeState';
import { tokenPairState } from '@/auth/states/tokenPairState'; import { tokenPairState } from '@/auth/states/tokenPairState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { CommentThreadTarget } from '~/generated/graphql'; import { CommentThreadTarget } from '~/generated/graphql';
import { mockedCompaniesData } from '~/testing/mock-data/companies'; import { mockedCompaniesData } from '~/testing/mock-data/companies';
import { mockedUsersData } from '~/testing/mock-data/users'; import { mockedUsersData } from '~/testing/mock-data/users';
@ -16,6 +17,7 @@ import { ApolloFactory } from '../services/apollo.factory';
export function useApolloFactory() { export function useApolloFactory() {
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null); const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null);
const [isDebugMode] = useRecoilState(isDebugModeState);
const [tokenPair, setTokenPair] = useRecoilState(tokenPairState); const [tokenPair, setTokenPair] = useRecoilState(tokenPairState);
const [isMockMode] = useRecoilState(isMockModeState); const [isMockMode] = useRecoilState(isMockModeState);
@ -64,10 +66,11 @@ export function useApolloFactory() {
setTokenPair(null); setTokenPair(null);
}, },
extraLinks: isMockMode ? [mockLink] : [], extraLinks: isMockMode ? [mockLink] : [],
isDebugMode,
}); });
return apolloRef.current.getClient(); return apolloRef.current.getClient();
}, [isMockMode, setTokenPair]); }, [isMockMode, setTokenPair, isDebugMode]);
useEffect(() => { useEffect(() => {
if (apolloRef.current) { if (apolloRef.current) {

View File

@ -29,6 +29,7 @@ export interface Options<TCacheShape> extends ApolloClientOptions<TCacheShape> {
onTokenPairChange?: (tokenPair: AuthTokenPair) => void; onTokenPairChange?: (tokenPair: AuthTokenPair) => void;
onUnauthenticatedError?: () => void; onUnauthenticatedError?: () => void;
extraLinks?: ApolloLink[]; extraLinks?: ApolloLink[];
isDebugMode?: boolean;
} }
export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> { export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
@ -43,6 +44,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
onTokenPairChange, onTokenPairChange,
onUnauthenticatedError, onUnauthenticatedError,
extraLinks, extraLinks,
isDebugMode,
...options ...options
} = opts; } = opts;
@ -98,7 +100,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
return forward(operation); return forward(operation);
} }
default: default:
if (process.env.NODE_ENV === 'development') { if (isDebugMode) {
console.warn( console.warn(
`[GraphQL error]: Message: ${ `[GraphQL error]: Message: ${
graphQLError.message graphQLError.message
@ -114,7 +116,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
} }
if (networkError) { if (networkError) {
if (process.env.NODE_ENV === 'development') { if (isDebugMode) {
console.warn(`[Network error]: ${networkError}`); console.warn(`[Network error]: ${networkError}`);
} }
onNetworkError?.(networkError); onNetworkError?.(networkError);
@ -127,8 +129,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
errorLink, errorLink,
authLink, authLink,
...(extraLinks ? extraLinks : []), ...(extraLinks ? extraLinks : []),
// Only show logger in dev mode isDebugMode ? logger : null,
process.env.NODE_ENV !== 'production' ? logger : null,
retryLink, retryLink,
httpLink, httpLink,
].filter(assertNotNull), ].filter(assertNotNull),

View File

@ -1,2 +1 @@
export * from './select';
export * from './update'; export * from './update';

View File

@ -1,10 +0,0 @@
import { gql } from '@apollo/client';
export const GET_CLIENT_CONFIG = gql`
query GetClientConfig {
clientConfig {
display_google_login
prefill_login_with_seed
}
}
`;

View File

@ -1,6 +1,6 @@
import { atom } from 'recoil'; import { atom } from 'recoil';
export const authFlowUserEmailState = atom({ export const authFlowUserEmailState = atom<string>({
key: 'authFlowUserEmailState', key: 'authFlowUserEmailState',
default: process.env.NODE_ENV === 'development' ? 'tim@apple.dev' : '', default: '',
}); });

View File

@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const displayGoogleLogin = atom<boolean>({
key: 'displayGoogleLogin',
default: true,
});

View File

@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const prefillLoginWithSeed = atom<boolean>({
key: 'prefillLoginWithSeed',
default: true,
});

View File

@ -0,0 +1,18 @@
import { gql } from '@apollo/client';
export const GET_CLIENT_CONFIG = gql`
query GetClientConfig {
clientConfig {
authProviders {
google
password
}
demoMode
debugMode
telemetry {
enabled
anonymizationEnabled
}
}
}
`;

View File

@ -0,0 +1,8 @@
import { atom } from 'recoil';
import { AuthProviders } from '~/generated/graphql';
export const authProvidersState = atom<AuthProviders>({
key: 'authProvidersState',
default: { google: false, magicLink: false, password: true },
});

View File

@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const isDebugModeState = atom<boolean>({
key: 'isDebugModeState',
default: false,
});

View File

@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const isDemoModeState = atom<boolean>({
key: 'isDemoModeState',
default: false,
});

View File

@ -0,0 +1,8 @@
import { atom } from 'recoil';
import { Telemetry } from '~/generated/graphql';
export const telemetryState = atom<Telemetry>({
key: 'telemetryState',
default: { enabled: true, anonymizationEnabled: true },
});

View File

@ -12,6 +12,7 @@ import { Logo } from '@/auth/components/ui/Logo';
import { Title } from '@/auth/components/ui/Title'; import { Title } from '@/auth/components/ui/Title';
import { authFlowUserEmailState } from '@/auth/states/authFlowUserEmailState'; import { authFlowUserEmailState } from '@/auth/states/authFlowUserEmailState';
import { isMockModeState } from '@/auth/states/isMockModeState'; import { isMockModeState } from '@/auth/states/isMockModeState';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState'; import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
import { MainButton } from '@/ui/components/buttons/MainButton'; import { MainButton } from '@/ui/components/buttons/MainButton';
import { TextInput } from '@/ui/components/inputs/TextInput'; import { TextInput } from '@/ui/components/inputs/TextInput';
@ -36,6 +37,8 @@ export function Index() {
const navigate = useNavigate(); const navigate = useNavigate();
const theme = useTheme(); const theme = useTheme();
const [, setMockMode] = useRecoilState(isMockModeState); const [, setMockMode] = useRecoilState(isMockModeState);
const [authProviders] = useRecoilState(authProvidersState);
const [demoMode] = useRecoilState(authProvidersState);
const [authFlowUserEmail, setAuthFlowUserEmail] = useRecoilState( const [authFlowUserEmail, setAuthFlowUserEmail] = useRecoilState(
authFlowUserEmailState, authFlowUserEmailState,
@ -71,7 +74,14 @@ export function Index() {
useEffect(() => { useEffect(() => {
setMockMode(true); setMockMode(true);
setCaptureHotkeyTypeInFocus(true); setCaptureHotkeyTypeInFocus(true);
}, [navigate, setMockMode, setCaptureHotkeyTypeInFocus]); setAuthFlowUserEmail(demoMode ? 'tim@apple.dev' : '');
}, [
navigate,
setMockMode,
setCaptureHotkeyTypeInFocus,
setAuthFlowUserEmail,
demoMode,
]);
return ( return (
<> <>
@ -80,12 +90,14 @@ export function Index() {
</AnimatedEaseIn> </AnimatedEaseIn>
<Title animate>Welcome to Twenty</Title> <Title animate>Welcome to Twenty</Title>
<StyledContentContainer> <StyledContentContainer>
<MainButton {authProviders.google && (
icon={<IconBrandGoogle size={theme.icon.size.sm} stroke={4} />} <MainButton
title="Continue with Google" icon={<IconBrandGoogle size={theme.icon.size.sm} stroke={4} />}
onClick={onGoogleLoginClick} title="Continue with Google"
fullWidth onClick={onGoogleLoginClick}
/> fullWidth
/>
)}
{visible && ( {visible && (
<motion.div <motion.div
initial={{ opacity: 0, height: 0 }} initial={{ opacity: 0, height: 0 }}

View File

@ -11,6 +11,7 @@ import { Title } from '@/auth/components/ui/Title';
import { useAuth } from '@/auth/hooks/useAuth'; import { useAuth } from '@/auth/hooks/useAuth';
import { authFlowUserEmailState } from '@/auth/states/authFlowUserEmailState'; import { authFlowUserEmailState } from '@/auth/states/authFlowUserEmailState';
import { isMockModeState } from '@/auth/states/isMockModeState'; import { isMockModeState } from '@/auth/states/isMockModeState';
import { isDemoModeState } from '@/client-config/states/isDemoModeState';
import { MainButton } from '@/ui/components/buttons/MainButton'; import { MainButton } from '@/ui/components/buttons/MainButton';
import { TextInput } from '@/ui/components/inputs/TextInput'; import { TextInput } from '@/ui/components/inputs/TextInput';
import { SubSectionTitle } from '@/ui/components/section-titles/SubSectionTitle'; import { SubSectionTitle } from '@/ui/components/section-titles/SubSectionTitle';
@ -48,15 +49,15 @@ const StyledErrorContainer = styled.div`
export function PasswordLogin() { export function PasswordLogin() {
const navigate = useNavigate(); const navigate = useNavigate();
const [isDemoMode] = useRecoilState(isDemoModeState);
const prefillPassword =
process.env.NODE_ENV === 'development' ? 'Applecar2025' : '';
const [authFlowUserEmail, setAuthFlowUserEmail] = useRecoilState( const [authFlowUserEmail, setAuthFlowUserEmail] = useRecoilState(
authFlowUserEmailState, authFlowUserEmailState,
); );
const [, setMockMode] = useRecoilState(isMockModeState); const [, setMockMode] = useRecoilState(isMockModeState);
const [internalPassword, setInternalPassword] = useState(prefillPassword); const [internalPassword, setInternalPassword] = useState(
isDemoMode ? 'Applecar2025' : '',
);
const [formError, setFormError] = useState(''); const [formError, setFormError] = useState('');
const { login } = useAuth(); const { login } = useAuth();

View File

@ -0,0 +1,34 @@
import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { useFetchClientConfig } from '@/auth/hooks/useFetchClientConfig';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { isDemoModeState } from '@/client-config/states/isDemoModeState';
import { telemetryState } from '@/client-config/states/telemetryState';
export const ClientConfigProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const [, setAuthProviders] = useRecoilState(authProvidersState);
const [, setDebugMode] = useRecoilState(isDebugModeState);
const [, setDemoMode] = useRecoilState(isDemoModeState);
const [, setTelemetry] = useRecoilState(telemetryState);
const clientConfig = useFetchClientConfig();
useEffect(() => {
if (clientConfig) {
setAuthProviders({
google: clientConfig.authProviders.google,
password: clientConfig.authProviders.password,
magicLink: false,
});
setDebugMode(clientConfig.debugMode);
setDemoMode(clientConfig.demoMode);
setTelemetry(clientConfig.telemetry);
}
}, [clientConfig, setAuthProviders, setDebugMode, setDemoMode, setTelemetry]);
return <>{children}</>;
};

View File

@ -1,21 +0,0 @@
import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { useFetchClientConfig } from '@/auth/hooks/useFetchClientConfig';
import { displayGoogleLogin } from '@/auth/states/displayGoogleLogin';
import { prefillLoginWithSeed } from '@/auth/states/prefillLoginWithSeed';
export const ClientConfigProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const [, setDisplayGoogleLogin] = useRecoilState(displayGoogleLogin);
const [, setPrefillLoginWithSeed] = useRecoilState(prefillLoginWithSeed);
const clientConfig = useFetchClientConfig();
useEffect(() => {
setDisplayGoogleLogin(clientConfig?.display_google_login ?? true);
setPrefillLoginWithSeed(clientConfig?.prefill_login_with_seed ?? true);
}, [setDisplayGoogleLogin, setPrefillLoginWithSeed, clientConfig]);
return <>{children}</>;
};

View File

@ -1,5 +1,4 @@
DEBUG_MODE=false DEBUG_MODE=false
AUTH_GOOGLE_ENABLED=false
ACCESS_TOKEN_SECRET=secret_jwt ACCESS_TOKEN_SECRET=secret_jwt
ACCESS_TOKEN_EXPIRES_IN=5m ACCESS_TOKEN_EXPIRES_IN=5m
LOGIN_TOKEN_SECRET=secret_login_token LOGIN_TOKEN_SECRET=secret_login_token

View File

@ -1,13 +1,21 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { AnalyticsResolver } from './analytics.resolver'; import { AnalyticsResolver } from './analytics.resolver';
import { AnalyticsService } from './analytics.service'; import { AnalyticsService } from './analytics.service';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
describe('AnalyticsResolver', () => { describe('AnalyticsResolver', () => {
let resolver: AnalyticsResolver; let resolver: AnalyticsResolver;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [AnalyticsResolver, AnalyticsService], providers: [
AnalyticsResolver,
AnalyticsService,
{
provide: EnvironmentService,
useValue: {},
},
],
}).compile(); }).compile();
resolver = module.get<AnalyticsResolver>(AnalyticsResolver); resolver = module.get<AnalyticsResolver>(AnalyticsResolver);

View File

@ -1,12 +1,19 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { AnalyticsService } from './analytics.service'; import { AnalyticsService } from './analytics.service';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
describe('AnalyticsService', () => { describe('AnalyticsService', () => {
let service: AnalyticsService; let service: AnalyticsService;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [AnalyticsService], providers: [
AnalyticsService,
{
provide: EnvironmentService,
useValue: {},
},
],
}).compile(); }).compile();
service = module.get<AnalyticsService>(AnalyticsService); service = module.get<AnalyticsService>(AnalyticsService);

View File

@ -3,12 +3,13 @@ import { User, Workspace } from '@prisma/client';
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { CreateAnalyticsInput } from './dto/create-analytics.input'; import { CreateAnalyticsInput } from './dto/create-analytics.input';
import { anonymize } from 'src/utils/anonymize'; import { anonymize } from 'src/utils/anonymize';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
@Injectable() @Injectable()
export class AnalyticsService { export class AnalyticsService {
private readonly httpService: AxiosInstance; private readonly httpService: AxiosInstance;
constructor() { constructor(private readonly environmentService: EnvironmentService) {
this.httpService = axios.create({ this.httpService = axios.create({
baseURL: 'https://t.twenty.com/api/v1/s2s', baseURL: 'https://t.twenty.com/api/v1/s2s',
}); });
@ -19,15 +20,26 @@ export class AnalyticsService {
user: User | undefined, user: User | undefined,
workspace: Workspace | undefined, workspace: Workspace | undefined,
) { ) {
if (process.env.IS_TELEMETRY_ENABLED === 'false') { if (!this.environmentService.isTelemetryEnabled()) {
return; return { success: true };
} }
const anonymizationEnabled =
this.environmentService.isTelemetryAnonymizationEnabled();
const data = { const data = {
type: createEventInput.type, type: createEventInput.type,
data: { data: {
userUUID: user ? anonymize(user.id) : undefined, userUUID: user
workspaceUUID: workspace ? anonymize(workspace.id) : undefined, ? anonymizationEnabled
? anonymize(user.id)
: user.id
: undefined,
workspaceUUID: workspace
? anonymizationEnabled
? anonymize(workspace.id)
: workspace.id
: undefined,
workspaceDomain: workspace ? workspace.domainName : undefined, workspaceDomain: workspace ? workspace.domainName : undefined,
...createEventInput.data, ...createEventInput.data,
}, },

View File

@ -1,5 +1,5 @@
import { Args, Mutation, Resolver, Query } from '@nestjs/graphql'; import { Args, Mutation, Resolver, Query } from '@nestjs/graphql';
import { AuthTokens, ClientConfig } from './dto/token.entity'; import { AuthTokens } from './dto/token.entity';
import { TokenService } from './services/token.service'; import { TokenService } from './services/token.service';
import { RefreshTokenInput } from './dto/refresh-token.input'; import { RefreshTokenInput } from './dto/refresh-token.input';
import { BadRequestException } from '@nestjs/common'; import { BadRequestException } from '@nestjs/common';
@ -61,17 +61,4 @@ export class AuthResolver {
return { tokens: tokens }; return { tokens: tokens };
} }
@Query(() => ClientConfig)
async clientConfig(): Promise<ClientConfig> {
const displayGoogleLogin = process.env.AUTH_GOOGLE_CLIENT_ID !== undefined;
const prefillLoginWithSeed = process.env.NODE_ENV === 'development';
const clientConfig: ClientConfig = {
display_google_login: displayGoogleLogin,
prefill_login_with_seed: prefillLoginWithSeed,
};
return Promise.resolve(clientConfig);
}
} }

View File

@ -23,12 +23,3 @@ export class AuthTokens {
@Field(() => AuthTokenPair) @Field(() => AuthTokenPair)
tokens: AuthTokenPair; tokens: AuthTokenPair;
} }
@ObjectType()
export class ClientConfig {
@Field(() => Boolean)
display_google_login: boolean;
@Field(() => Boolean)
prefill_login_with_seed: boolean;
}

View File

@ -7,7 +7,7 @@ import { GoogleStrategy } from '../strategies/google.auth.strategy';
export class GoogleProviderEnabledGuard implements CanActivate { export class GoogleProviderEnabledGuard implements CanActivate {
constructor(private readonly environmentService: EnvironmentService) {} constructor(private readonly environmentService: EnvironmentService) {}
canActivate(): boolean | Promise<boolean> | Observable<boolean> { canActivate(): boolean | Promise<boolean> | Observable<boolean> {
if (!this.environmentService.getAuthGoogleEnabled()) { if (!this.environmentService.isAuthGoogleEnabled()) {
throw new NotFoundException('Google auth is not enabled'); throw new NotFoundException('Google auth is not enabled');
} }

View File

@ -0,0 +1,37 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
class AuthProviders {
@Field(() => Boolean)
google: boolean;
@Field(() => Boolean)
magicLink: boolean;
@Field(() => Boolean)
password: boolean;
}
@ObjectType()
class Telemetry {
@Field(() => Boolean)
enabled: boolean;
@Field(() => Boolean)
anonymizationEnabled: boolean;
}
@ObjectType()
export class ClientConfig {
@Field(() => AuthProviders, { nullable: false })
authProviders: AuthProviders;
@Field(() => Telemetry, { nullable: false })
telemetry: Telemetry;
@Field(() => Boolean)
demoMode: boolean;
@Field(() => Boolean)
debugMode: boolean;
}

View File

@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { ClientConfigResolver } from './client-config.resolver';
@Module({
providers: [ClientConfigResolver],
})
export class ClientConfigModule {}

View File

@ -0,0 +1,25 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ClientConfigResolver } from './client-config.resolver';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
describe('ClientConfigResolver', () => {
let resolver: ClientConfigResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ClientConfigResolver,
{
provide: EnvironmentService,
useValue: {},
},
],
}).compile();
resolver = module.get<ClientConfigResolver>(ClientConfigResolver);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -0,0 +1,28 @@
import { Resolver, Query } from '@nestjs/graphql';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
import { ClientConfig } from './client-config.entity';
@Resolver()
export class ClientConfigResolver {
constructor(private environmentService: EnvironmentService) {}
@Query(() => ClientConfig)
async clientConfig(): Promise<ClientConfig> {
const clientConfig: ClientConfig = {
authProviders: {
google: this.environmentService.isAuthGoogleEnabled() ?? false,
magicLink: false,
password: true,
},
telemetry: {
enabled: this.environmentService.isTelemetryEnabled() ?? false,
anonymizationEnabled:
this.environmentService.isTelemetryAnonymizationEnabled() ?? false,
},
demoMode: this.environmentService.isDemoMode() ?? false,
debugMode: this.environmentService.isDebugMode() ?? false,
};
return Promise.resolve(clientConfig);
}
}

View File

@ -8,6 +8,7 @@ import { AuthModule } from './auth/auth.module';
import { WorkspaceModule } from './workspace/workspace.module'; import { WorkspaceModule } from './workspace/workspace.module';
import { AnalyticsModule } from './analytics/analytics.module'; import { AnalyticsModule } from './analytics/analytics.module';
import { FileModule } from './file/file.module'; import { FileModule } from './file/file.module';
import { ClientConfigModule } from './client-config/client-config.module';
@Module({ @Module({
imports: [ imports: [
@ -20,6 +21,7 @@ import { FileModule } from './file/file.module';
WorkspaceModule, WorkspaceModule,
AnalyticsModule, AnalyticsModule,
FileModule, FileModule,
ClientConfigModule,
], ],
exports: [ exports: [
AuthModule, AuthModule,

View File

@ -19,7 +19,7 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
private readonly logger = new Logger(PrismaService.name); private readonly logger = new Logger(PrismaService.name);
constructor(private readonly environmentService: EnvironmentService) { constructor(private readonly environmentService: EnvironmentService) {
const debugMode = environmentService.getDebugMode(); const debugMode = environmentService.isDebugMode();
super({ super({
errorFormat: 'minimal', errorFormat: 'minimal',
log: debugMode log: debugMode

View File

@ -8,8 +8,22 @@ import { StorageType } from './interfaces/storage.interface';
export class EnvironmentService { export class EnvironmentService {
constructor(private configService: ConfigService) {} constructor(private configService: ConfigService) {}
getDebugMode(): boolean | undefined { isDebugMode(): boolean {
return this.configService.get<boolean>('DEBUG_MODE')!; return this.configService.get<boolean>('DEBUG_MODE') ?? false;
}
isDemoMode(): boolean {
return this.configService.get<boolean>('DEMO_MODE') ?? false;
}
isTelemetryEnabled(): boolean {
return this.configService.get<boolean>('TELEMETRY_ENABLED') ?? true;
}
isTelemetryAnonymizationEnabled(): boolean | undefined {
return (
this.configService.get<boolean>('TELEMETRY_ANONYMIZATION_ENABLED') ?? true
);
} }
getPGDatabaseUrl(): string { getPGDatabaseUrl(): string {
@ -44,7 +58,7 @@ export class EnvironmentService {
return this.configService.get<string>('FRONT_AUTH_CALLBACK_URL')!; return this.configService.get<string>('FRONT_AUTH_CALLBACK_URL')!;
} }
getAuthGoogleEnabled(): boolean | undefined { isAuthGoogleEnabled(): boolean | undefined {
return this.configService.get<boolean>('AUTH_GOOGLE_ENABLED'); return this.configService.get<boolean>('AUTH_GOOGLE_ENABLED');
} }

View File

@ -16,12 +16,27 @@ import { IsAWSRegion } from './decorators/is-aws-region.decorator';
import { CastToBoolean } from './decorators/cast-to-boolean.decorator'; import { CastToBoolean } from './decorators/cast-to-boolean.decorator';
export class EnvironmentVariables { export class EnvironmentVariables {
// Stage // Misc
@CastToBoolean() @CastToBoolean()
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
DEBUG_MODE?: boolean; DEBUG_MODE?: boolean;
@CastToBoolean()
@IsOptional()
@IsBoolean()
DEMO_MODE?: boolean;
@CastToBoolean()
@IsOptional()
@IsBoolean()
TELEMETRY_ENABLED?: boolean;
@CastToBoolean()
@IsOptional()
@IsBoolean()
TELEMETRY_ANONYMIZATION_ENABLED?: boolean;
// Database // Database
@IsUrl({ protocols: ['postgres'], require_tld: false }) @IsUrl({ protocols: ['postgres'], require_tld: false })
PG_DATABASE_URL: string; PG_DATABASE_URL: string;

View File

@ -1,4 +1,4 @@
import { Global, Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { fromNodeProviderChain } from '@aws-sdk/credential-providers'; import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { EnvironmentModule } from './environment/environment.module'; import { EnvironmentModule } from './environment/environment.module';
import { EnvironmentService } from './environment/environment.service'; import { EnvironmentService } from './environment/environment.service';

View File

@ -1,9 +1,6 @@
import crypto from 'crypto'; import crypto from 'crypto';
export function anonymize(input) { export function anonymize(input: string) {
if (process.env.IS_TELEMETRY_ANONYMIZATION_ENABLED === 'false') {
return input;
}
// md5 shorter than sha-256 and collisions are not a security risk in this use-case // md5 shorter than sha-256 and collisions are not a security risk in this use-case
return crypto.createHash('md5').update(input).digest('hex'); return crypto.createHash('md5').update(input).digest('hex');
} }