mirror of
https://github.com/lingble/twenty.git
synced 2025-11-03 14:17:58 +00:00
Add deploy buttons and clean environment variables (#974)
* add render.yaml * Clean environment variables --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@@ -5,15 +5,11 @@ services:
|
|||||||
context: ..
|
context: ..
|
||||||
dockerfile: ./infra/prod/front/Dockerfile
|
dockerfile: ./infra/prod/front/Dockerfile
|
||||||
args:
|
args:
|
||||||
REACT_APP_API_URL: "http://localhost:3000/graphql"
|
REACT_APP_SERVER_BASE_URL: "http://localhost:3000"
|
||||||
REACT_APP_AUTH_URL: "http://localhost:3000/auth"
|
|
||||||
REACT_APP_FILES_URL: "http://localhost:3000/files"
|
|
||||||
ports:
|
ports:
|
||||||
- "3001:3000"
|
- "3001:3000"
|
||||||
labels:
|
labels:
|
||||||
dev.ergomake.env.replace-arg.REACT_APP_API_URL: "https://{{ services.server.url }}/graphql"
|
dev.ergomake.env.replace-arg.REACT_APP_SERVER_BASE_URL: "https://{{ services.server.url }}"
|
||||||
dev.ergomake.env.replace-arg.REACT_APP_AUTH_URL: "https://{{ services.server.url }}/auth"
|
|
||||||
dev.ergomake.env.replace-arg.REACT_APP_FILES_URL: "https://{{ services.server.url }}/files"
|
|
||||||
server:
|
server:
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
@@ -23,19 +19,14 @@ services:
|
|||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
environment:
|
environment:
|
||||||
DEBUG_MODE: false
|
DEBUG_MODE: false
|
||||||
DEMO_MODE: true
|
IS_SIGN_IN_PREFILLED: true
|
||||||
ACCESS_TOKEN_SECRET: "secret_jwt"
|
ACCESS_TOKEN_SECRET: "secret_jwt"
|
||||||
ACCESS_TOKEN_EXPIRES_IN: "30m"
|
|
||||||
LOGIN_TOKEN_SECRET: "secret_login_token"
|
LOGIN_TOKEN_SECRET: "secret_login_token"
|
||||||
LOGIN_TOKEN_EXPIRES_IN: "15m"
|
|
||||||
REFRESH_TOKEN_SECRET: "secret_refresh_token"
|
REFRESH_TOKEN_SECRET: "secret_refresh_token"
|
||||||
REFRESH_TOKEN_EXPIRES_IN: "90d"
|
|
||||||
PG_DATABASE_URL: "postgres://postgres:postgrespassword@postgres:5432/default?connection_limit=1"
|
PG_DATABASE_URL: "postgres://postgres:postgrespassword@postgres:5432/default?connection_limit=1"
|
||||||
FRONT_AUTH_CALLBACK_URL: "http://localhost:3000/verify"
|
FRONT_BASE_URL: "http://localhost:3000"
|
||||||
STORAGE_TYPE: "local"
|
|
||||||
STORAGE_LOCAL_PATH: ".local-storage"
|
|
||||||
labels:
|
labels:
|
||||||
dev.ergomake.env.replace-env.FRONT_AUTH_CALLBACK_URL: "https://{{ services.server.url }}/verify"
|
dev.ergomake.env.replace-env.FRONT_BASE_URL: "https://{{ services.server.url }}"
|
||||||
postgres:
|
postgres:
|
||||||
build: ../infra/dev/postgres
|
build: ../infra/dev/postgres
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
8
.github/workflows/ci-chromatic.yaml
vendored
8
.github/workflows/ci-chromatic.yaml
vendored
@@ -11,9 +11,7 @@ jobs:
|
|||||||
if: ${{ contains(github.event.*.labels.*.name, 'run-chromatic') }} || github.event_name == 'push' }}
|
if: ${{ contains(github.event.*.labels.*.name, 'run-chromatic') }} || github.event_name == 'push' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
REACT_APP_API_URL: http://127.0.0.1:3000/graphql
|
REACT_APP_SERVER_BASE_URL: http://127.0.0.1:3000
|
||||||
REACT_APP_AUTH_URL: http://127.0.0.1:3000/auth
|
|
||||||
REACT_APP_FILES_URL: http://127.0.0.1:3000/files
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
@@ -33,9 +31,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd front
|
cd front
|
||||||
touch .env
|
touch .env
|
||||||
echo "REACT_APP_API_URL: $REACT_APP_API_URL" >> .env
|
echo "REACT_APP_SERVER_BASE_URL: $REACT_APP_SERVER_BASE_URL" >> .env
|
||||||
echo "REACT_APP_AUTH_URL: $REACT_APP_AUTH_URL" >> .env
|
|
||||||
echo "REACT_APP_FILES_URL: $REACT_APP_FILES_URL" >> .env
|
|
||||||
- name: Front / Install Dependencies
|
- name: Front / Install Dependencies
|
||||||
run: cd front && yarn
|
run: cd front && yarn
|
||||||
- name: Publish to Chromatic
|
- name: Publish to Chromatic
|
||||||
|
|||||||
4
.github/workflows/ci-front.yaml
vendored
4
.github/workflows/ci-front.yaml
vendored
@@ -8,9 +8,7 @@ jobs:
|
|||||||
front-test:
|
front-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
REACT_APP_API_URL: http://localhost:3000/graphql
|
REACT_APP_SERVER_BASE_URL: http://localhost:3000
|
||||||
REACT_APP_AUTH_URL: http://localhost:3000/auth
|
|
||||||
REACT_APP_FILES_URL: http://localhost:3000/files
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
sidebar_custom_props:
|
sidebar_custom_props:
|
||||||
icon: TbArrowBigRight
|
icon: TbDeviceDesktop
|
||||||
---
|
---
|
||||||
|
|
||||||
# Local Setup
|
# Local Setup
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ You will find these in the [infra/prod/front/Dockerfile](https://github.com/twen
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg REACT_APP_API_URL=REPLACE_ME \
|
--build-arg REACT_APP_SERVER_BASE_URL=REPLACE_ME \
|
||||||
--build-arg REACT_APP_AUTH_URL=REPLACE_ME \
|
|
||||||
--build-arg REACT_APP_FILES_URL=REPLACE_ME \
|
|
||||||
-t twenty-front:latest \
|
-t twenty-front:latest \
|
||||||
-f ./infra/prod/front/Dockerfile .
|
-f ./infra/prod/front/Dockerfile .
|
||||||
```
|
```
|
||||||
@@ -36,10 +34,19 @@ docker build \
|
|||||||
-f ./infra/prod/server/Dockerfile .
|
-f ./infra/prod/server/Dockerfile .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Render
|
||||||
|
|
||||||
|
[](https://render.com/deploy?repo=https://github.com/twentyhq/twenty)
|
||||||
|
|
||||||
|
|
||||||
## AWS Elastic Beanstalk (soon)
|
## AWS Elastic Beanstalk (soon)
|
||||||
|
|
||||||
We are working on providing a joint Docker image - containing both the Twenty frontend and server - that you can deploy using [AWS Elastic Beanstalk](https://aws.amazon.com/elasticbeanstalk/).
|
We are working on providing a joint Docker image - containing both the Twenty frontend and server - that you can deploy using [AWS Elastic Beanstalk](https://aws.amazon.com/elasticbeanstalk/).
|
||||||
|
|
||||||
## Railway (soon)
|
|
||||||
|
|
||||||
[Railway](https://railway.app) is an infrastructure platform that lets you deploy to the cloud in one click. We are currently working on making it available.
|
<!--
|
||||||
|
|
||||||
|
## Railway
|
||||||
|
[](https://railway.app/template/YWGqza?referralCode=3CLObs)
|
||||||
|
|
||||||
|
-->
|
||||||
@@ -4,6 +4,6 @@ sidebar_custom_props:
|
|||||||
icon: TbTerminal2
|
icon: TbTerminal2
|
||||||
---
|
---
|
||||||
|
|
||||||
# CLI (soon)
|
# CLI
|
||||||
|
|
||||||
Available soon!
|
Available soon!
|
||||||
@@ -6,7 +6,7 @@ import Link from '@docusaurus/Link';
|
|||||||
import isInternalUrl from '@docusaurus/isInternalUrl';
|
import isInternalUrl from '@docusaurus/isInternalUrl';
|
||||||
import IconExternalLink from '@theme/Icon/ExternalLink';
|
import IconExternalLink from '@theme/Icon/ExternalLink';
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
import { TbFaceIdError, TbTerminal2, TbCloud, TbServer, TbBolt, TbApps, TbTopologyStar, TbChartDots, TbBug, TbVocabulary, TbArrowBigRight } from "react-icons/tb";
|
import { TbFaceIdError, TbTerminal2, TbCloud, TbServer, TbBolt, TbApps, TbTopologyStar, TbChartDots, TbBug, TbVocabulary, TbArrowBigRight, TbDeviceDesktop } from "react-icons/tb";
|
||||||
|
|
||||||
|
|
||||||
export default function DocSidebarItemLink({
|
export default function DocSidebarItemLink({
|
||||||
@@ -30,7 +30,8 @@ export default function DocSidebarItemLink({
|
|||||||
'TbTopologyStar': TbTopologyStar,
|
'TbTopologyStar': TbTopologyStar,
|
||||||
'TbChartDots': TbChartDots,
|
'TbChartDots': TbChartDots,
|
||||||
'TbBug': TbBug,
|
'TbBug': TbBug,
|
||||||
'TbVocabulary': TbVocabulary
|
'TbVocabulary': TbVocabulary,
|
||||||
|
'TbDeviceDesktop': TbDeviceDesktop,
|
||||||
};
|
};
|
||||||
|
|
||||||
let IconComponent = customProps && customProps.icon ? icons[customProps.icon] : TbFaceIdError;
|
let IconComponent = customProps && customProps.icon ? icons[customProps.icon] : TbFaceIdError;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
REACT_APP_API_URL=http://localhost:3000/graphql
|
REACT_APP_SERVER_BASE_URL=http://localhost:3000
|
||||||
REACT_APP_AUTH_URL=http://localhost:3000/auth
|
|
||||||
REACT_APP_FILES_URL=http://localhost:3000/files
|
|
||||||
|
|
||||||
CHROMATIC_PROJECT_TOKEN=REPLACE_ME
|
# ———————— Optional ————————
|
||||||
|
# REACT_APP_SERVER_AUTH_URL=http://localhost:3000/auth
|
||||||
|
# REACT_APP_SERVER_FILES_URL=http://localhost:3000/files
|
||||||
|
# CHROMATIC_PROJECT_TOKEN=
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
schema: process.env.REACT_APP_API_URL,
|
schema: process.env.REACT_APP_SERVER_BASE_URL + "/graphql",
|
||||||
documents: ['./src/**/*.tsx', './src/**/*.ts'],
|
documents: ['./src/**/*.tsx', './src/**/*.ts'],
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
generates: {
|
generates: {
|
||||||
|
|||||||
@@ -547,7 +547,7 @@ export type ClientConfig = {
|
|||||||
__typename?: 'ClientConfig';
|
__typename?: 'ClientConfig';
|
||||||
authProviders: AuthProviders;
|
authProviders: AuthProviders;
|
||||||
debugMode: Scalars['Boolean'];
|
debugMode: Scalars['Boolean'];
|
||||||
demoMode: Scalars['Boolean'];
|
signInPrefilled: Scalars['Boolean'];
|
||||||
telemetry: Telemetry;
|
telemetry: Telemetry;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2333,7 +2333,7 @@ export type RenewTokenMutation = { __typename?: 'Mutation', renewToken: { __type
|
|||||||
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
|
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 GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean } } };
|
||||||
|
|
||||||
export type GetCompaniesQueryVariables = Exact<{
|
export type GetCompaniesQueryVariables = Exact<{
|
||||||
orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput> | CompanyOrderByWithRelationInput>;
|
orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput> | CompanyOrderByWithRelationInput>;
|
||||||
@@ -3369,7 +3369,7 @@ export const GetClientConfigDocument = gql`
|
|||||||
google
|
google
|
||||||
password
|
password
|
||||||
}
|
}
|
||||||
demoMode
|
signInPrefilled
|
||||||
debugMode
|
debugMode
|
||||||
telemetry {
|
telemetry {
|
||||||
enabled
|
enabled
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function useApolloFactory() {
|
|||||||
|
|
||||||
const apolloClient = useMemo(() => {
|
const apolloClient = useMemo(() => {
|
||||||
apolloRef.current = new ApolloFactory({
|
apolloRef.current = new ApolloFactory({
|
||||||
uri: `${process.env.REACT_APP_API_URL}`,
|
uri: `${process.env.REACT_APP_SERVER_BASE_URL}/graphql`,
|
||||||
cache: new InMemoryCache({
|
cache: new InMemoryCache({
|
||||||
typePolicies: {
|
typePolicies: {
|
||||||
Activity: {
|
Activity: {
|
||||||
|
|||||||
@@ -118,8 +118,11 @@ export function useAuth() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleGoogleLogin = useCallback((workspaceInviteHash?: string) => {
|
const handleGoogleLogin = useCallback((workspaceInviteHash?: string) => {
|
||||||
|
const authServerUrl =
|
||||||
|
process.env.REACT_APP_SERVER_AUTH_URL ??
|
||||||
|
process.env.REACT_APP_SERVER_BASE_URL + '/auth';
|
||||||
window.location.href =
|
window.location.href =
|
||||||
`${process.env.REACT_APP_AUTH_URL}/google/${
|
`${authServerUrl}/google/${
|
||||||
workspaceInviteHash ? '?inviteHash=' + workspaceInviteHash : ''
|
workspaceInviteHash ? '?inviteHash=' + workspaceInviteHash : ''
|
||||||
}` || '';
|
}` || '';
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||||
import { isDemoModeState } from '@/client-config/states/isDemoModeState';
|
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||||
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
||||||
@@ -45,7 +45,7 @@ export function useSignInUp() {
|
|||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
const isMatchingLocation = useIsMatchingLocation();
|
const isMatchingLocation = useIsMatchingLocation();
|
||||||
const [authProviders] = useRecoilState(authProvidersState);
|
const [authProviders] = useRecoilState(authProvidersState);
|
||||||
const isDemoMode = useRecoilValue(isDemoModeState);
|
const isSignInPrefilled = useRecoilValue(isSignInPrefilledState);
|
||||||
const workspaceInviteHash = useParams().workspaceInviteHash;
|
const workspaceInviteHash = useParams().workspaceInviteHash;
|
||||||
const [signInUpStep, setSignInUpStep] = useState<SignInUpStep>(
|
const [signInUpStep, setSignInUpStep] = useState<SignInUpStep>(
|
||||||
SignInUpStep.Init,
|
SignInUpStep.Init,
|
||||||
@@ -61,8 +61,8 @@ export function useSignInUp() {
|
|||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
exist: false,
|
exist: false,
|
||||||
email: isDemoMode ? 'tim@apple.dev' : '',
|
email: isSignInPrefilled ? 'tim@apple.dev' : '',
|
||||||
password: isDemoMode ? 'Applecar2025' : '',
|
password: isSignInPrefilled ? 'Applecar2025' : '',
|
||||||
},
|
},
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useRecoilState } from 'recoil';
|
|||||||
|
|
||||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||||
import { isDemoModeState } from '@/client-config/states/isDemoModeState';
|
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
|
||||||
import { telemetryState } from '@/client-config/states/telemetryState';
|
import { telemetryState } from '@/client-config/states/telemetryState';
|
||||||
import { useGetClientConfigQuery } from '~/generated/graphql';
|
import { useGetClientConfigQuery } from '~/generated/graphql';
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ export const ClientConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [, setAuthProviders] = useRecoilState(authProvidersState);
|
const [, setAuthProviders] = useRecoilState(authProvidersState);
|
||||||
const [, setDebugMode] = useRecoilState(isDebugModeState);
|
const [, setDebugMode] = useRecoilState(isDebugModeState);
|
||||||
const [, setDemoMode] = useRecoilState(isDemoModeState);
|
const [, setSignInPrefilled] = useRecoilState(isSignInPrefilledState);
|
||||||
const [, setTelemetry] = useRecoilState(telemetryState);
|
const [, setTelemetry] = useRecoilState(telemetryState);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
@@ -29,14 +29,14 @@ export const ClientConfigProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
magicLink: false,
|
magicLink: false,
|
||||||
});
|
});
|
||||||
setDebugMode(data?.clientConfig.debugMode);
|
setDebugMode(data?.clientConfig.debugMode);
|
||||||
setDemoMode(data?.clientConfig.demoMode);
|
setSignInPrefilled(data?.clientConfig.signInPrefilled);
|
||||||
setTelemetry(data?.clientConfig.telemetry);
|
setTelemetry(data?.clientConfig.telemetry);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
data,
|
data,
|
||||||
setAuthProviders,
|
setAuthProviders,
|
||||||
setDebugMode,
|
setDebugMode,
|
||||||
setDemoMode,
|
setSignInPrefilled,
|
||||||
setTelemetry,
|
setTelemetry,
|
||||||
setIsLoading,
|
setIsLoading,
|
||||||
loading,
|
loading,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const GET_CLIENT_CONFIG = gql`
|
|||||||
google
|
google
|
||||||
password
|
password
|
||||||
}
|
}
|
||||||
demoMode
|
signInPrefilled
|
||||||
debugMode
|
debugMode
|
||||||
telemetry {
|
telemetry {
|
||||||
enabled
|
enabled
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import { atom } from 'recoil';
|
|
||||||
|
|
||||||
export const isDemoModeState = atom<boolean>({
|
|
||||||
key: 'isDemoModeState',
|
|
||||||
default: false,
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const isSignInPrefilledState = atom<boolean>({
|
||||||
|
key: 'isSignInPrefilledState',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
@@ -11,5 +11,9 @@ export function getImageAbsoluteURIOrBase64(imageUrl?: string | null) {
|
|||||||
return imageUrl;
|
return imageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${process.env.REACT_APP_FILES_URL}/${imageUrl}`;
|
const serverFilesUrl =
|
||||||
|
process.env.REACT_APP_SERVER_FILES_URL ??
|
||||||
|
process.env.REACT_APP_SERVER_BASE_URL + '/files';
|
||||||
|
|
||||||
|
return `${serverFilesUrl}/${imageUrl}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ export const graphqlMocks = [
|
|||||||
return res(
|
return res(
|
||||||
ctx.data({
|
ctx.data({
|
||||||
clientConfig: {
|
clientConfig: {
|
||||||
demoMode: true,
|
signInPrefilled: true,
|
||||||
debugMode: false,
|
debugMode: false,
|
||||||
authProviders: { google: true, password: true, magicLink: false },
|
authProviders: { google: true, password: true, magicLink: false },
|
||||||
telemetry: { enabled: false, anonymizationEnabled: true },
|
telemetry: { enabled: false, anonymizationEnabled: true },
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ApolloClient, InMemoryCache } from '@apollo/client';
|
import { ApolloClient, InMemoryCache } from '@apollo/client';
|
||||||
|
|
||||||
export const mockedClient = new ApolloClient({
|
export const mockedClient = new ApolloClient({
|
||||||
uri: process.env.REACT_APP_API_URL,
|
uri: process.env.REACT_APP_SERVER_BASE_URL + '/graphql',
|
||||||
cache: new InMemoryCache(),
|
cache: new InMemoryCache(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
FROM node:18.16.0-alpine as build
|
FROM node:18.16.0-alpine as build
|
||||||
|
|
||||||
ARG REACT_APP_API_URL
|
|
||||||
ARG REACT_APP_AUTH_URL
|
ARG REACT_APP_SERVER_BASE_URL
|
||||||
ARG REACT_APP_FILES_URL
|
ARG REACT_APP_SERVER_AUTH_URL
|
||||||
|
ARG REACT_APP_SERVER_FILES_URL
|
||||||
|
|
||||||
COPY ./packages/ /app/packages
|
COPY ./packages/ /app/packages
|
||||||
|
|
||||||
|
|||||||
42
render.yaml
Normal file
42
render.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
services:
|
||||||
|
- type: web
|
||||||
|
name: front
|
||||||
|
env: docker
|
||||||
|
dockerfilePath: ./infra/prod/front/Dockerfile
|
||||||
|
autoDeploy: false
|
||||||
|
envVars:
|
||||||
|
- key: REACT_APP_SERVER_BASE_URL
|
||||||
|
fromService:
|
||||||
|
name: server
|
||||||
|
type: web
|
||||||
|
envVarKey: RENDER_EXTERNAL_URL
|
||||||
|
- type: web
|
||||||
|
name: server
|
||||||
|
env: docker
|
||||||
|
dockerfilePath: ./infra/prod/server/Dockerfile
|
||||||
|
dockerCommand: "sh -c yarn prisma:migrate && node dist/src/main"
|
||||||
|
autoDeploy: false
|
||||||
|
envVars:
|
||||||
|
- key: ACCESS_TOKEN_SECRET
|
||||||
|
generateValue: true
|
||||||
|
- key: LOGIN_TOKEN_SECRET
|
||||||
|
generateValue: true
|
||||||
|
- key: REFRESH_TOKEN_SECRET
|
||||||
|
generateValue: true
|
||||||
|
- key: PG_DATABASE_URL
|
||||||
|
fromDatabase:
|
||||||
|
name: twenty-db
|
||||||
|
property: connectionString
|
||||||
|
- key: FRONT_BASE_URL
|
||||||
|
fromService:
|
||||||
|
name: front
|
||||||
|
type: web
|
||||||
|
envVarKey: RENDER_EXTERNAL_URL
|
||||||
|
disk:
|
||||||
|
name: twenty-disk
|
||||||
|
mountPath: /.local-storage
|
||||||
|
sizeGB: 5
|
||||||
|
|
||||||
|
databases:
|
||||||
|
- name: twenty-db
|
||||||
|
plan: starter
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
DEBUG_MODE=false
|
|
||||||
DEMO_MODE=true
|
|
||||||
ACCESS_TOKEN_SECRET=secret_jwt
|
|
||||||
ACCESS_TOKEN_EXPIRES_IN=30m
|
|
||||||
LOGIN_TOKEN_SECRET=secret_login_token
|
|
||||||
LOGIN_TOKEN_EXPIRES_IN=15m
|
|
||||||
REFRESH_TOKEN_SECRET=secret_refresh_token
|
|
||||||
REFRESH_TOKEN_EXPIRES_IN=90d
|
|
||||||
PG_DATABASE_URL=postgres://postgres:postgrespassword@localhost:5432/default?connection_limit=1
|
PG_DATABASE_URL=postgres://postgres:postgrespassword@localhost:5432/default?connection_limit=1
|
||||||
FRONT_AUTH_CALLBACK_URL=http://localhost:3001/verify
|
FRONT_BASE_URL=http://localhost:3001
|
||||||
STORAGE_TYPE=local
|
ACCESS_TOKEN_SECRET=replace_me_with_a_random_string
|
||||||
STORAGE_LOCAL_PATH=.local-storage
|
LOGIN_TOKEN_SECRET=replace_me_with_a_random_string
|
||||||
|
REFRESH_TOKEN_SECRET=replace_me_with_a_random_string
|
||||||
|
IS_SIGN_IN_PREFILLED=true
|
||||||
|
|
||||||
|
# ———————— Optional ————————
|
||||||
|
# DEBUG_MODE=true
|
||||||
|
# ACCESS_TOKEN_EXPIRES_IN=30m
|
||||||
|
# LOGIN_TOKEN_EXPIRES_IN=15m
|
||||||
|
# REFRESH_TOKEN_EXPIRES_IN=90d
|
||||||
|
# FRONT_AUTH_CALLBACK_URL=http://localhost:3001/verify
|
||||||
|
# AUTH_GOOGLE_ENABLED=false
|
||||||
|
# STORAGE_TYPE=local
|
||||||
|
# STORAGE_LOCAL_PATH=.local-storage
|
||||||
@@ -1,12 +1,20 @@
|
|||||||
DEBUG_MODE=true
|
DEBUG_MODE=true
|
||||||
AUTH_GOOGLE_ENABLED=false
|
|
||||||
ACCESS_TOKEN_SECRET=secret_jwt
|
|
||||||
ACCESS_TOKEN_EXPIRES_IN=1d
|
|
||||||
REFRESH_TOKEN_SECRET=secret_refresh_token
|
|
||||||
REFRESH_TOKEN_EXPIRES_IN=30d
|
|
||||||
LOGIN_TOKEN_SECRET=secret_login_token
|
|
||||||
LOGIN_TOKEN_EXPIRES_IN=15m
|
|
||||||
FRONT_AUTH_CALLBACK_URL=http://localhost:3001/auth/callback
|
|
||||||
PG_DATABASE_URL=postgres://postgres:postgrespassword@localhost:5432/test?connection_limit=1
|
PG_DATABASE_URL=postgres://postgres:postgrespassword@localhost:5432/test?connection_limit=1
|
||||||
STORAGE_TYPE=local
|
# the URL of the front-end app
|
||||||
STORAGE_LOCAL_PATH=.local-storage
|
FRONT_BASE_URL=http://localhost:3001
|
||||||
|
# random keys used to generate JWT tokens
|
||||||
|
ACCESS_TOKEN_SECRET=secret_jwt
|
||||||
|
LOGIN_TOKEN_SECRET=secret_login_tokens
|
||||||
|
REFRESH_TOKEN_SECRET=secret_refresh_token
|
||||||
|
|
||||||
|
|
||||||
|
# ———————— Optional ————————
|
||||||
|
# DEBUG_MODE=false
|
||||||
|
# IS_SIGN_IN_PREFILLED=false
|
||||||
|
# ACCESS_TOKEN_EXPIRES_IN=30m
|
||||||
|
# LOGIN_TOKEN_EXPIRES_IN=15m
|
||||||
|
# REFRESH_TOKEN_EXPIRES_IN=90d
|
||||||
|
# FRONT_AUTH_CALLBACK_URL=http://localhost:3001/verify
|
||||||
|
# AUTH_GOOGLE_ENABLED=false
|
||||||
|
# STORAGE_TYPE=local
|
||||||
|
# STORAGE_LOCAL_PATH=.local-storage
|
||||||
@@ -11,7 +11,14 @@ ENV_PATH="${SCRIPT_DIR}/../.env.test"
|
|||||||
if [ -f "${ENV_PATH}" ]; then
|
if [ -f "${ENV_PATH}" ]; then
|
||||||
echo "🔵 - Loading environment variables from "${ENV_PATH}"..."
|
echo "🔵 - Loading environment variables from "${ENV_PATH}"..."
|
||||||
# Export env vars
|
# Export env vars
|
||||||
export $(grep -v '^#' ${ENV_PATH} | xargs)
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
|
if echo "$line" | grep -F = &>/dev/null
|
||||||
|
then
|
||||||
|
varname=$(echo "$line" | cut -d '=' -f 1)
|
||||||
|
varvalue=$(echo "$line" | cut -d '=' -f 2- | cut -d '#' -f 1)
|
||||||
|
export "$varname"="$varvalue"
|
||||||
|
fi
|
||||||
|
done < <(grep -v '^#' "${ENV_PATH}")
|
||||||
else
|
else
|
||||||
echo "Error: ${ENV_PATH} does not exist."
|
echo "Error: ${ENV_PATH} does not exist."
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ export class UpdateActivityTargetAbilityHandler implements IAbilityHandler {
|
|||||||
async handle(ability: AppAbility, context: ExecutionContext) {
|
async handle(ability: AppAbility, context: ExecutionContext) {
|
||||||
const gqlContext = GqlExecutionContext.create(context);
|
const gqlContext = GqlExecutionContext.create(context);
|
||||||
const args = gqlContext.getArgs<ActivityTargetArgs>();
|
const args = gqlContext.getArgs<ActivityTargetArgs>();
|
||||||
const ActivityTarget = await this.prismaService.client.activityTarget.findFirst({
|
const ActivityTarget =
|
||||||
|
await this.prismaService.client.activityTarget.findFirst({
|
||||||
where: args.where,
|
where: args.where,
|
||||||
});
|
});
|
||||||
assert(ActivityTarget, '', NotFoundException);
|
assert(ActivityTarget, '', NotFoundException);
|
||||||
@@ -66,7 +67,8 @@ export class DeleteActivityTargetAbilityHandler implements IAbilityHandler {
|
|||||||
async handle(ability: AppAbility, context: ExecutionContext) {
|
async handle(ability: AppAbility, context: ExecutionContext) {
|
||||||
const gqlContext = GqlExecutionContext.create(context);
|
const gqlContext = GqlExecutionContext.create(context);
|
||||||
const args = gqlContext.getArgs<ActivityTargetArgs>();
|
const args = gqlContext.getArgs<ActivityTargetArgs>();
|
||||||
const ActivityTarget = await this.prismaService.client.activityTarget.findFirst({
|
const ActivityTarget =
|
||||||
|
await this.prismaService.client.activityTarget.findFirst({
|
||||||
where: args.where,
|
where: args.where,
|
||||||
});
|
});
|
||||||
assert(ActivityTarget, '', NotFoundException);
|
assert(ActivityTarget, '', NotFoundException);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class ClientConfig {
|
|||||||
telemetry: Telemetry;
|
telemetry: Telemetry;
|
||||||
|
|
||||||
@Field(() => Boolean)
|
@Field(() => Boolean)
|
||||||
demoMode: boolean;
|
signInPrefilled: boolean;
|
||||||
|
|
||||||
@Field(() => Boolean)
|
@Field(() => Boolean)
|
||||||
debugMode: boolean;
|
debugMode: boolean;
|
||||||
|
|||||||
@@ -12,17 +12,17 @@ export class ClientConfigResolver {
|
|||||||
async clientConfig(): Promise<ClientConfig> {
|
async clientConfig(): Promise<ClientConfig> {
|
||||||
const clientConfig: ClientConfig = {
|
const clientConfig: ClientConfig = {
|
||||||
authProviders: {
|
authProviders: {
|
||||||
google: this.environmentService.isAuthGoogleEnabled() ?? false,
|
google: this.environmentService.isAuthGoogleEnabled(),
|
||||||
magicLink: false,
|
magicLink: false,
|
||||||
password: true,
|
password: true,
|
||||||
},
|
},
|
||||||
telemetry: {
|
telemetry: {
|
||||||
enabled: this.environmentService.isTelemetryEnabled() ?? false,
|
enabled: this.environmentService.isTelemetryEnabled(),
|
||||||
anonymizationEnabled:
|
anonymizationEnabled:
|
||||||
this.environmentService.isTelemetryAnonymizationEnabled() ?? false,
|
this.environmentService.isTelemetryAnonymizationEnabled(),
|
||||||
},
|
},
|
||||||
demoMode: this.environmentService.isDemoMode() ?? false,
|
signInPrefilled: this.environmentService.isSignInPrefilled(),
|
||||||
debugMode: this.environmentService.isDebugMode() ?? false,
|
debugMode: this.environmentService.isDebugMode(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return Promise.resolve(clientConfig);
|
return Promise.resolve(clientConfig);
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ export class EnvironmentService {
|
|||||||
return this.configService.get<boolean>('DEBUG_MODE') ?? false;
|
return this.configService.get<boolean>('DEBUG_MODE') ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
isDemoMode(): boolean {
|
isSignInPrefilled(): boolean {
|
||||||
return this.configService.get<boolean>('DEMO_MODE') ?? false;
|
return this.configService.get<boolean>('IS_SIGN_IN_PREFILLED') ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
isTelemetryEnabled(): boolean {
|
isTelemetryEnabled(): boolean {
|
||||||
return this.configService.get<boolean>('TELEMETRY_ENABLED') ?? true;
|
return this.configService.get<boolean>('TELEMETRY_ENABLED') ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
isTelemetryAnonymizationEnabled(): boolean | undefined {
|
isTelemetryAnonymizationEnabled(): boolean {
|
||||||
return (
|
return (
|
||||||
this.configService.get<boolean>('TELEMETRY_ANONYMIZATION_ENABLED') ?? true
|
this.configService.get<boolean>('TELEMETRY_ANONYMIZATION_ENABLED') ?? true
|
||||||
);
|
);
|
||||||
@@ -31,12 +31,16 @@ export class EnvironmentService {
|
|||||||
return this.configService.get<string>('PG_DATABASE_URL')!;
|
return this.configService.get<string>('PG_DATABASE_URL')!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFrontBaseUrl(): string {
|
||||||
|
return this.configService.get<string>('FRONT_BASE_URL')!;
|
||||||
|
}
|
||||||
|
|
||||||
getAccessTokenSecret(): string {
|
getAccessTokenSecret(): string {
|
||||||
return this.configService.get<string>('ACCESS_TOKEN_SECRET')!;
|
return this.configService.get<string>('ACCESS_TOKEN_SECRET')!;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccessTokenExpiresIn(): string {
|
getAccessTokenExpiresIn(): string {
|
||||||
return this.configService.get<string>('ACCESS_TOKEN_EXPIRES_IN')!;
|
return this.configService.get<string>('ACCESS_TOKEN_EXPIRES_IN') ?? '30m';
|
||||||
}
|
}
|
||||||
|
|
||||||
getRefreshTokenSecret(): string {
|
getRefreshTokenSecret(): string {
|
||||||
@@ -44,7 +48,7 @@ export class EnvironmentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRefreshTokenExpiresIn(): string {
|
getRefreshTokenExpiresIn(): string {
|
||||||
return this.configService.get<string>('REFRESH_TOKEN_EXPIRES_IN')!;
|
return this.configService.get<string>('REFRESH_TOKEN_EXPIRES_IN') ?? '90d';
|
||||||
}
|
}
|
||||||
|
|
||||||
getLoginTokenSecret(): string {
|
getLoginTokenSecret(): string {
|
||||||
@@ -52,15 +56,18 @@ export class EnvironmentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getLoginTokenExpiresIn(): string {
|
getLoginTokenExpiresIn(): string {
|
||||||
return this.configService.get<string>('LOGIN_TOKEN_EXPIRES_IN')!;
|
return this.configService.get<string>('LOGIN_TOKEN_EXPIRES_IN') ?? '15m';
|
||||||
}
|
}
|
||||||
|
|
||||||
getFrontAuthCallbackUrl(): string {
|
getFrontAuthCallbackUrl(): string {
|
||||||
return this.configService.get<string>('FRONT_AUTH_CALLBACK_URL')!;
|
return (
|
||||||
|
this.configService.get<string>('FRONT_AUTH_CALLBACK_URL') ??
|
||||||
|
this.getFrontBaseUrl() + '/auth/callback'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
isAuthGoogleEnabled(): boolean | undefined {
|
isAuthGoogleEnabled(): boolean {
|
||||||
return this.configService.get<boolean>('AUTH_GOOGLE_ENABLED');
|
return this.configService.get<boolean>('AUTH_GOOGLE_ENABLED') ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthGoogleClientId(): string | undefined {
|
getAuthGoogleClientId(): string | undefined {
|
||||||
@@ -75,8 +82,10 @@ export class EnvironmentService {
|
|||||||
return this.configService.get<string>('AUTH_GOOGLE_CALLBACK_URL');
|
return this.configService.get<string>('AUTH_GOOGLE_CALLBACK_URL');
|
||||||
}
|
}
|
||||||
|
|
||||||
getStorageType(): StorageType | undefined {
|
getStorageType(): StorageType {
|
||||||
return this.configService.get<StorageType>('STORAGE_TYPE');
|
return (
|
||||||
|
this.configService.get<StorageType>('STORAGE_TYPE') ?? StorageType.Local
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getStorageS3Region(): AwsRegion | undefined {
|
getStorageS3Region(): AwsRegion | undefined {
|
||||||
@@ -87,7 +96,9 @@ export class EnvironmentService {
|
|||||||
return this.configService.get<AwsRegion>('STORAGE_S3_NAME');
|
return this.configService.get<AwsRegion>('STORAGE_S3_NAME');
|
||||||
}
|
}
|
||||||
|
|
||||||
getStorageLocalPath(): string | undefined {
|
getStorageLocalPath(): string {
|
||||||
return this.configService.get<string>('STORAGE_LOCAL_PATH')!;
|
return (
|
||||||
|
this.configService.get<string>('STORAGE_LOCAL_PATH') ?? '.local-storage'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class EnvironmentVariables {
|
|||||||
@CastToBoolean()
|
@CastToBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
DEMO_MODE?: boolean;
|
IS_SIGN_IN_PREFILLED?: boolean;
|
||||||
|
|
||||||
@CastToBoolean()
|
@CastToBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -43,24 +43,32 @@ export class EnvironmentVariables {
|
|||||||
@IsUrl({ protocols: ['postgres'], require_tld: false })
|
@IsUrl({ protocols: ['postgres'], require_tld: false })
|
||||||
PG_DATABASE_URL: string;
|
PG_DATABASE_URL: string;
|
||||||
|
|
||||||
|
// Frontend URL
|
||||||
|
@IsUrl({ require_tld: false })
|
||||||
|
FRONT_BASE_URL: string;
|
||||||
|
|
||||||
// Json Web Token
|
// Json Web Token
|
||||||
@IsString()
|
@IsString()
|
||||||
ACCESS_TOKEN_SECRET: string;
|
ACCESS_TOKEN_SECRET: string;
|
||||||
@IsDuration()
|
@IsDuration()
|
||||||
|
@IsOptional()
|
||||||
ACCESS_TOKEN_EXPIRES_IN: string;
|
ACCESS_TOKEN_EXPIRES_IN: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
REFRESH_TOKEN_SECRET: string;
|
REFRESH_TOKEN_SECRET: string;
|
||||||
@IsDuration()
|
@IsDuration()
|
||||||
|
@IsOptional()
|
||||||
REFRESH_TOKEN_EXPIRES_IN: string;
|
REFRESH_TOKEN_EXPIRES_IN: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
LOGIN_TOKEN_SECRET: string;
|
LOGIN_TOKEN_SECRET: string;
|
||||||
@IsDuration()
|
@IsDuration()
|
||||||
|
@IsOptional()
|
||||||
LOGIN_TOKEN_EXPIRES_IN: string;
|
LOGIN_TOKEN_EXPIRES_IN: string;
|
||||||
|
|
||||||
// Auth
|
// Auth
|
||||||
@IsUrl({ require_tld: false })
|
@IsUrl({ require_tld: false })
|
||||||
|
@IsOptional()
|
||||||
FRONT_AUTH_CALLBACK_URL: string;
|
FRONT_AUTH_CALLBACK_URL: string;
|
||||||
|
|
||||||
@CastToBoolean()
|
@CastToBoolean()
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ const fileStorageModuleFactory = async (
|
|||||||
const type = environmentService.getStorageType();
|
const type = environmentService.getStorageType();
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case undefined:
|
|
||||||
case StorageType.Local: {
|
case StorageType.Local: {
|
||||||
const storagePath = environmentService.getStorageLocalPath();
|
const storagePath = environmentService.getStorageLocalPath();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user