Skip to content

Commit

Permalink
Merge branch 'main' into 4398-decouple-contacts-and-companies-creatio…
Browse files Browse the repository at this point in the history
…n-from-messages-import
  • Loading branch information
bosiraphael committed Mar 22, 2024
2 parents 646cee8 + 1a76326 commit 963e5d8
Show file tree
Hide file tree
Showing 176 changed files with 2,936 additions and 2,114 deletions.
6 changes: 4 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
server/node_modules/
server/.env
.git
.env
node_modules
.nx/cache
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@aws-sdk/client-s3": "^3.363.0",
"@aws-sdk/credential-providers": "^3.363.0",
"@blocknote/core": "^0.12.1",
"@blocknote/react": "^0.12.1",
"@blocknote/react": "^0.12.2",
"@chakra-ui/accordion": "^2.3.0",
"@chakra-ui/system": "^2.6.0",
"@codesandbox/sandpack-react": "^2.13.5",
Expand Down
6 changes: 6 additions & 0 deletions packages/twenty-docker/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ prod-docs-build:
prod-docs-run:
@docker run -d -p 3000:3000 --name twenty-docs twenty-docs

prod-build:
@cd ../.. && docker build -f ./packages/twenty-docker/prod/twenty/Dockerfile --tag twenty . && cd -

prod-run:
@docker run -d -p 3000:3000 --name twenty twenty

prod-front-build:
@cd ../.. && docker build -f ./packages/twenty-docker/prod/twenty-front/Dockerfile --tag twenty-front . && cd -

Expand Down
16 changes: 16 additions & 0 deletions packages/twenty-docker/prod/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
TAG=latest

POSTGRES_ADMIN_PASSWORD=replace_me_with_a_strong_password

PG_DATABASE_HOST=db:5432

SERVER_URL=http://localhost:3000
# Uncoment if you are serving your front on another server than the API (eg. bucket)
# FRONT_BASE_URL=http://localhost:3000

# Use openssl rand -base64 32 for each secret
# ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access
# LOGIN_TOKEN_SECRET=replace_me_with_a_random_string_login
# REFRESH_TOKEN_SECRET=replace_me_with_a_random_string_refresh

SIGN_IN_PREFILLED=true
52 changes: 52 additions & 0 deletions packages/twenty-docker/prod/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
version: '3.8'
name: twenty

services:
server:
image: twentycrm/twenty:${TAG}
volumes:
- server-local-data:/app/.local-storage
ports:
- "3000:3000"
environment:
PORT: 3000
PG_DATABASE_URL: postgres://twenty:twenty@${PG_DATABASE_HOST}/default
SERVER_URL: ${SERVER_URL}
FRONT_BASE_URL: ${FRONT_BASE_URL:-$SERVER_URL}

ENABLE_DB_MIGRATIONS: true

SIGN_IN_PREFILLED: ${SIGN_IN_PREFILLED}
STORAGE_TYPE: local
STORAGE_LOCAL_PATH: .local-storage
ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET}
LOGIN_TOKEN_SECRET: ${LOGIN_TOKEN_SECRET}
REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET}
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl --silent --fail http://localhost:3000/healthz | jq -e '.status == \"ok\"' > /dev/null || exit 1"]
interval: 5s
timeout: 5s
retries: 10
restart: always

db:
image: twentycrm/twenty-postgres:${TAG}
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${POSTGRES_ADMIN_PASSWORD}
#POSTGRES_USER: ${POSTGRES_USER}
#POSTGRES_DB: ${POSTGRES_DB}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U twenty -d default"]
interval: 5s
timeout: 5s
retries: 10
restart: always

volumes:
db-data:
server-local-data:
77 changes: 77 additions & 0 deletions packages/twenty-docker/prod/twenty/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Base image for common dependencies
FROM node:18.17.1-alpine as common-deps

WORKDIR /app

# Copy only the necessary files for dependency resolution
COPY ./package.json ./yarn.lock ./.yarnrc.yml ./tsconfig.base.json ./nx.json /app/
COPY ./.yarn/releases /app/.yarn/releases

COPY ./packages/twenty-emails/package.json /app/packages/twenty-emails/
COPY ./packages/twenty-server/package.json /app/packages/twenty-server/
COPY ./packages/twenty-server/patches /app/packages/twenty-server/patches
COPY ./packages/twenty-ui/package.json /app/packages/twenty-ui/
COPY ./packages/twenty-front/package.json /app/packages/twenty-front/

# Install all dependencies
RUN yarn && yarn cache clean && npx nx reset


# Build the back
FROM common-deps as twenty-server-build

# Copy sourcecode after installing dependences to accelerate subsequents builds
COPY ./packages/twenty-emails /app/packages/twenty-emails
COPY ./packages/twenty-server /app/packages/twenty-server

RUN npx nx run twenty-server:build && \
mv /app/packages/twenty-server/dist /app/packages/twenty-server/build && \
npx nx run twenty-server:build:packageJson && \
mv /app/packages/twenty-server/dist/package.json /app/packages/twenty-server/package.json && \
rm -rf /app/packages/twenty-server/dist && \
mv /app/packages/twenty-server/build /app/packages/twenty-server/dist

RUN yarn workspaces focus --production twenty-emails twenty-server


# Build the front
FROM common-deps as twenty-front-build

ARG REACT_APP_SERVER_BASE_URL

COPY ./packages/twenty-front /app/packages/twenty-front
COPY ./packages/twenty-ui /app/packages/twenty-ui
RUN yarn nx build twenty-front


# Final stage: Run the application
FROM node:18.17.1-alpine as twenty

# Used to run healthcheck in docker
RUN apk add --no-cache curl jq

COPY ./packages/twenty-docker/prod/twenty/entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh

WORKDIR /app/packages/twenty-server

ARG REACT_APP_SERVER_BASE_URL
ENV REACT_APP_SERVER_BASE_URL $REACT_APP_SERVER_BASE_URL

# Copy built applications from previous stages
COPY --chown=1000 --from=twenty-server-build /app /app
COPY --chown=1000 --from=twenty-server-build /app/packages/twenty-server /app/packages/twenty-server
COPY --chown=1000 --from=twenty-front-build /app/packages/twenty-front/build /app/packages/twenty-server/dist/front

# Set metadata and labels
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the backend and frontend, ensuring it deploys faster and runs the same way regardless of the deployment environment."

RUN mkdir /app/.local-storage
RUN chown -R 1000 /app

# Use non root user with uid 1000
USER 1000

CMD ["node", "dist/src/main"]
ENTRYPOINT ["/app/entrypoint.sh"]
17 changes: 17 additions & 0 deletions packages/twenty-docker/prod/twenty/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh

# Check if the initialization has already been done and that we enabled automatic migration
if [ "${ENABLE_DB_MIGRATIONS}" = "true" ] && [ ! -f /app/${STORAGE_LOCAL_PATH}/db_initialized ]; then
echo "Running database setup and migrations..."

# Run setup and migration scripts
npx ts-node ./scripts/setup-db.ts
yarn database:migrate:prod

# Mark initialization as done
echo "Successfuly migrated DB!"
touch /app/${STORAGE_LOCAL_PATH}/db_initialized
fi

# Continue with the original Docker command
exec "$@"
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ This should work out of the box with the eslint extension installed. If this doe
"source.fixAll.eslint": "explicit"
}
```

## Docker container build

To successfully build Docker images, ensure that your system has a minimum of 2GB of memory available. For users of Docker Desktop, please verify that you've allocated sufficient resources to Docker within the application's settings.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Complete step three and four with :

## Production docker containers

Prebuilt images for both Postgres, frontend, and back-end can be found on [docker hub](https://hub.docker.com/r/twentycrm/).
Prebuilt images for both Postgres, frontend, and back-end can be found on [docker hub](https://hub.docker.com/r/twentycrm/). Note that the Postgres container will not persist data if your server is not configured to be stateful (for example Heroku). You probably want to configure a special stateful resource for the database.

## Environment Variables

Expand Down
2 changes: 1 addition & 1 deletion packages/twenty-docs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twenty-docs",
"version": "0.3.2",
"version": "0.3.3",
"private": true,
"scripts": {
"nx": "NX_DEFAULT_PROJECT=twenty-docs node ../../node_modules/nx/bin/nx.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/twenty-emails/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twenty-emails",
"version": "0.3.2",
"version": "0.3.3",
"description": "",
"author": "",
"private": true,
Expand Down
4 changes: 2 additions & 2 deletions packages/twenty-front/nyc.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const globalCoverage = {
};

const modulesCoverage = {
statements: 75,
lines: 75,
statements: 70,
lines: 70,
functions: 70,
include: ['src/modules/**/*'],
exclude: ['src/**/*.ts'],
Expand Down
2 changes: 1 addition & 1 deletion packages/twenty-front/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twenty-front",
"version": "0.3.2",
"version": "0.3.3",
"private": true,
"type": "module",
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,16 @@ export const PageChangeEffect = () => {
) {
navigate(AppPath.CreateProfile);
} else if (
(onboardingStatus === OnboardingStatus.Completed ||
onboardingStatus === OnboardingStatus.CompletedWithoutSubscription) &&
onboardingStatus === OnboardingStatus.Completed &&
isMatchingOnboardingRoute
) {
navigate(AppPath.Index);
} else if (
onboardingStatus === OnboardingStatus.CompletedWithoutSubscription &&
isMatchingOnboardingRoute &&
!isMatchingLocation(AppPath.PlanRequired)
) {
navigate(AppPath.Index);
} else if (isMatchingLocation(AppPath.Invite)) {
const inviteHash =
matchPath({ path: '/invite/:workspaceInviteHash' }, location.pathname)
Expand Down
67 changes: 65 additions & 2 deletions packages/twenty-front/src/generated-metadata/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export type Billing = {
export type BillingSubscription = {
__typename?: 'BillingSubscription';
id: Scalars['ID']['output'];
interval?: Maybe<Scalars['String']['output']>;
status: Scalars['String']['output'];
};

Expand Down Expand Up @@ -263,7 +264,6 @@ export enum FieldMetadataType {
DateTime = 'DATE_TIME',
Email = 'EMAIL',
FullName = 'FULL_NAME',
Json = 'JSON',
Link = 'LINK',
MultiSelect = 'MULTI_SELECT',
Number = 'NUMBER',
Expand All @@ -272,6 +272,7 @@ export enum FieldMetadataType {
Position = 'POSITION',
Probability = 'PROBABILITY',
Rating = 'RATING',
RawJson = 'RAW_JSON',
Relation = 'RELATION',
Select = 'SELECT',
Text = 'TEXT',
Expand Down Expand Up @@ -341,6 +342,7 @@ export type Mutation = {
renewToken: AuthTokens;
signUp: LoginToken;
track: Analytics;
updateBillingSubscription: UpdateBillingEntity;
updateOneField: Field;
updateOneObject: Object;
updatePasswordViaResetToken: InvalidatePassword;
Expand Down Expand Up @@ -545,6 +547,8 @@ export type Query = {
fields: FieldConnection;
findWorkspaceFromInviteHash: Workspace;
getProductPrices: ProductPricesEntity;
getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal;
getTimelineCalendarEventsFromPersonId: TimelineCalendarEventsWithTotal;
getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal;
getTimelineThreadsFromPersonId: TimelineThreadsWithTotal;
object: Object;
Expand Down Expand Up @@ -591,6 +595,20 @@ export type QueryGetProductPricesArgs = {
};


export type QueryGetTimelineCalendarEventsFromCompanyIdArgs = {
companyId: Scalars['ID']['input'];
page: Scalars['Int']['input'];
pageSize: Scalars['Int']['input'];
};


export type QueryGetTimelineCalendarEventsFromPersonIdArgs = {
page: Scalars['Int']['input'];
pageSize: Scalars['Int']['input'];
personId: Scalars['ID']['input'];
};


export type QueryGetTimelineThreadsFromCompanyIdArgs = {
companyId: Scalars['ID']['input'];
page: Scalars['Int']['input'];
Expand Down Expand Up @@ -697,7 +715,7 @@ export type Sentry = {

export type SessionEntity = {
__typename?: 'SessionEntity';
url: Scalars['String']['output'];
url?: Maybe<Scalars['String']['output']>;
};

/** Sort Directions */
Expand All @@ -724,6 +742,45 @@ export type Telemetry = {
enabled: Scalars['Boolean']['output'];
};

export type TimelineCalendarEvent = {
__typename?: 'TimelineCalendarEvent';
attendees: Array<TimelineCalendarEventAttendee>;
conferenceSolution: Scalars['String']['output'];
conferenceUri: Scalars['String']['output'];
description: Scalars['String']['output'];
endsAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
isCanceled: Scalars['Boolean']['output'];
isFullDay: Scalars['Boolean']['output'];
location: Scalars['String']['output'];
startsAt: Scalars['DateTime']['output'];
title: Scalars['String']['output'];
visibility: TimelineCalendarEventVisibility;
};

export type TimelineCalendarEventAttendee = {
__typename?: 'TimelineCalendarEventAttendee';
avatarUrl: Scalars['String']['output'];
displayName: Scalars['String']['output'];
firstName: Scalars['String']['output'];
handle: Scalars['String']['output'];
lastName: Scalars['String']['output'];
personId?: Maybe<Scalars['ID']['output']>;
workspaceMemberId?: Maybe<Scalars['ID']['output']>;
};

/** Visibility of the calendar event */
export enum TimelineCalendarEventVisibility {
Metadata = 'METADATA',
ShareEverything = 'SHARE_EVERYTHING'
}

export type TimelineCalendarEventsWithTotal = {
__typename?: 'TimelineCalendarEventsWithTotal';
timelineCalendarEvents: Array<TimelineCalendarEvent>;
totalNumberOfCalendarEvents: Scalars['Int']['output'];
};

export type TimelineThread = {
__typename?: 'TimelineThread';
firstParticipant: TimelineThreadParticipant;
Expand Down Expand Up @@ -760,6 +817,12 @@ export type TransientToken = {
transientToken: AuthToken;
};

export type UpdateBillingEntity = {
__typename?: 'UpdateBillingEntity';
/** Boolean that confirms query was successful */
success: Scalars['Boolean']['output'];
};

export type UpdateFieldInput = {
defaultValue?: InputMaybe<Scalars['JSON']['input']>;
description?: InputMaybe<Scalars['String']['input']>;
Expand Down
Loading

0 comments on commit 963e5d8

Please sign in to comment.