diff --git a/.deploy/api/Dockerfile b/.deploy/api/Dockerfile index 146a0911709..a4451d9c6f3 100644 --- a/.deploy/api/Dockerfile +++ b/.deploy/api/Dockerfile @@ -75,6 +75,15 @@ ARG GAUZY_GITHUB_APP_ID ARG GAUZY_GITHUB_APP_NAME ARG GAUZY_GITHUB_POST_INSTALL_URL ARG GAUZY_GITHUB_APP_PRIVATE_KEY +ARG MAGIC_CODE_EXPIRATION_TIME +ARG APP_NAME +ARG APP_LOGO +ARG APP_SIGNATURE +ARG APP_LINK +ARG APP_EMAIL_CONFIRMATION_URL +ARG APP_MAGIC_SIGN_URL +ARG COMPANY_LINK +ARG COMPANY_NAME FROM node:18-alpine3.17 AS dependencies @@ -314,6 +323,15 @@ ENV GAUZY_GITHUB_POST_INSTALL_URL=${GAUZY_GITHUB_POST_INSTALL_URL} ENV GAUZY_GITHUB_OAUTH_CLIENT_ID=${GAUZY_GITHUB_OAUTH_CLIENT_ID} ENV GAUZY_GITHUB_OAUTH_CLIENT_SECRET=${GAUZY_GITHUB_OAUTH_CLIENT_SECRET} ENV GAUZY_GITHUB_OAUTH_CALLBACK_URL=${GAUZY_GITHUB_OAUTH_CALLBACK_URL} +ENV MAGIC_CODE_EXPIRATION_TIME=${MAGIC_CODE_EXPIRATION_TIME} +ENV APP_NAME=${APP_NAME} +ENV APP_LOGO=${APP_LOGO} +ENV APP_SIGNATURE=${APP_SIGNATURE} +ENV APP_LINK=${APP_LINK} +ENV APP_EMAIL_CONFIRMATION_URL=${APP_EMAIL_CONFIRMATION_URL} +ENV APP_MAGIC_SIGN_URL=${APP_MAGIC_SIGN_URL} +ENV COMPANY_LINK=${COMPANY_LINK} +ENV COMPANY_NAME=${COMPANY_NAME} EXPOSE ${API_PORT} diff --git a/.deploy/k8s/k8s-manifest.civo.prod.yaml b/.deploy/k8s/k8s-manifest.civo.prod.yaml index 8261556924f..99a5e21a249 100644 --- a/.deploy/k8s/k8s-manifest.civo.prod.yaml +++ b/.deploy/k8s/k8s-manifest.civo.prod.yaml @@ -195,6 +195,24 @@ spec: value: '$GAUZY_GITHUB_OAUTH_CLIENT_SECRET' - name: GAUZY_GITHUB_OAUTH_CALLBACK_URL value: '$GAUZY_GITHUB_OAUTH_CALLBACK_URL' + - name: MAGIC_CODE_EXPIRATION_TIME + value: '$MAGIC_CODE_EXPIRATION_TIME' + - name: APP_NAME + value: '$APP_NAME' + - name: APP_LOGO + value: '$APP_LOGO' + - name: APP_SIGNATURE + value: '$APP_SIGNATURE' + - name: APP_LINK + value: '$APP_LINK' + - name: APP_EMAIL_CONFIRMATION_URL + value: '$APP_EMAIL_CONFIRMATION_URL' + - name: APP_MAGIC_SIGN_URL + value: '$APP_MAGIC_SIGN_URL' + - name: COMPANY_LINK + value: '$COMPANY_LINK' + - name: COMPANY_NAME + value: '$COMPANY_NAME' ports: - containerPort: 3000 diff --git a/.deploy/k8s/k8s-manifest.civo.stage.yaml b/.deploy/k8s/k8s-manifest.civo.stage.yaml index 4f85cfaa2d1..b7c1ac2ed02 100644 --- a/.deploy/k8s/k8s-manifest.civo.stage.yaml +++ b/.deploy/k8s/k8s-manifest.civo.stage.yaml @@ -189,6 +189,24 @@ spec: value: '$GAUZY_GITHUB_OAUTH_CLIENT_SECRET' - name: GAUZY_GITHUB_OAUTH_CALLBACK_URL value: '$GAUZY_GITHUB_OAUTH_CALLBACK_URL' + - name: MAGIC_CODE_EXPIRATION_TIME + value: '$MAGIC_CODE_EXPIRATION_TIME' + - name: APP_NAME + value: '$APP_NAME' + - name: APP_LOGO + value: '$APP_LOGO' + - name: APP_SIGNATURE + value: '$APP_SIGNATURE' + - name: APP_LINK + value: '$APP_LINK' + - name: APP_EMAIL_CONFIRMATION_URL + value: '$APP_EMAIL_CONFIRMATION_URL' + - name: APP_MAGIC_SIGN_URL + value: '$APP_MAGIC_SIGN_URL' + - name: COMPANY_LINK + value: '$COMPANY_LINK' + - name: COMPANY_NAME + value: '$COMPANY_NAME' ports: - containerPort: 3000 diff --git a/.deploy/k8s/k8s-manifest.cw.prod.yaml b/.deploy/k8s/k8s-manifest.cw.prod.yaml index 964a9f3593a..bfc8790ee77 100644 --- a/.deploy/k8s/k8s-manifest.cw.prod.yaml +++ b/.deploy/k8s/k8s-manifest.cw.prod.yaml @@ -221,6 +221,24 @@ spec: value: '$GAUZY_GITHUB_OAUTH_CLIENT_SECRET' - name: GAUZY_GITHUB_OAUTH_CALLBACK_URL value: '$GAUZY_GITHUB_OAUTH_CALLBACK_URL' + - name: MAGIC_CODE_EXPIRATION_TIME + value: '$MAGIC_CODE_EXPIRATION_TIME' + - name: APP_NAME + value: '$APP_NAME' + - name: APP_LOGO + value: '$APP_LOGO' + - name: APP_SIGNATURE + value: '$APP_SIGNATURE' + - name: APP_LINK + value: '$APP_LINK' + - name: APP_EMAIL_CONFIRMATION_URL + value: '$APP_EMAIL_CONFIRMATION_URL' + - name: APP_MAGIC_SIGN_URL + value: '$APP_MAGIC_SIGN_URL' + - name: COMPANY_LINK + value: '$COMPANY_LINK' + - name: COMPANY_NAME + value: '$COMPANY_NAME' ports: - containerPort: 3000 diff --git a/.deploy/k8s/k8s-manifest.cw.stage.yaml b/.deploy/k8s/k8s-manifest.cw.stage.yaml index 1d85930b7b7..07bf6cda2f3 100644 --- a/.deploy/k8s/k8s-manifest.cw.stage.yaml +++ b/.deploy/k8s/k8s-manifest.cw.stage.yaml @@ -215,6 +215,24 @@ spec: value: '$GAUZY_GITHUB_OAUTH_CLIENT_SECRET' - name: GAUZY_GITHUB_OAUTH_CALLBACK_URL value: '$GAUZY_GITHUB_OAUTH_CALLBACK_URL' + - name: MAGIC_CODE_EXPIRATION_TIME + value: '$MAGIC_CODE_EXPIRATION_TIME' + - name: APP_NAME + value: '$APP_NAME' + - name: APP_LOGO + value: '$APP_LOGO' + - name: APP_SIGNATURE + value: '$APP_SIGNATURE' + - name: APP_LINK + value: '$APP_LINK' + - name: APP_EMAIL_CONFIRMATION_URL + value: '$APP_EMAIL_CONFIRMATION_URL' + - name: APP_MAGIC_SIGN_URL + value: '$APP_MAGIC_SIGN_URL' + - name: COMPANY_LINK + value: '$COMPANY_LINK' + - name: COMPANY_NAME + value: '$COMPANY_NAME' ports: - containerPort: 3000 diff --git a/.deploy/k8s/k8s-manifest.prod.yaml b/.deploy/k8s/k8s-manifest.prod.yaml index 6c8284fcc25..68d57c65952 100644 --- a/.deploy/k8s/k8s-manifest.prod.yaml +++ b/.deploy/k8s/k8s-manifest.prod.yaml @@ -213,6 +213,24 @@ spec: value: '$GAUZY_GITHUB_OAUTH_CLIENT_SECRET' - name: GAUZY_GITHUB_OAUTH_CALLBACK_URL value: '$GAUZY_GITHUB_OAUTH_CALLBACK_URL' + - name: MAGIC_CODE_EXPIRATION_TIME + value: '$MAGIC_CODE_EXPIRATION_TIME' + - name: APP_NAME + value: '$APP_NAME' + - name: APP_LOGO + value: '$APP_LOGO' + - name: APP_SIGNATURE + value: '$APP_SIGNATURE' + - name: APP_LINK + value: '$APP_LINK' + - name: APP_EMAIL_CONFIRMATION_URL + value: '$APP_EMAIL_CONFIRMATION_URL' + - name: APP_MAGIC_SIGN_URL + value: '$APP_MAGIC_SIGN_URL' + - name: COMPANY_LINK + value: '$COMPANY_LINK' + - name: COMPANY_NAME + value: '$COMPANY_NAME' ports: - containerPort: 3000 diff --git a/.deploy/k8s/k8s-manifest.stage.yaml b/.deploy/k8s/k8s-manifest.stage.yaml index a6f9084985f..b14e808816f 100644 --- a/.deploy/k8s/k8s-manifest.stage.yaml +++ b/.deploy/k8s/k8s-manifest.stage.yaml @@ -207,6 +207,24 @@ spec: value: '$GAUZY_GITHUB_OAUTH_CLIENT_SECRET' - name: GAUZY_GITHUB_OAUTH_CALLBACK_URL value: '$GAUZY_GITHUB_OAUTH_CALLBACK_URL' + - name: MAGIC_CODE_EXPIRATION_TIME + value: '$MAGIC_CODE_EXPIRATION_TIME' + - name: APP_NAME + value: '$APP_NAME' + - name: APP_LOGO + value: '$APP_LOGO' + - name: APP_SIGNATURE + value: '$APP_SIGNATURE' + - name: APP_LINK + value: '$APP_LINK' + - name: APP_EMAIL_CONFIRMATION_URL + value: '$APP_EMAIL_CONFIRMATION_URL' + - name: APP_MAGIC_SIGN_URL + value: '$APP_MAGIC_SIGN_URL' + - name: COMPANY_LINK + value: '$COMPANY_LINK' + - name: COMPANY_NAME + value: '$COMPANY_NAME' ports: - containerPort: 3000 diff --git a/.env.sample b/.env.sample index da82b5a50ec..942e875a7ef 100644 --- a/.env.sample +++ b/.env.sample @@ -16,21 +16,21 @@ APP_EMAIL_CONFIRMATION_URL="http://localhost:4200/#/auth/confirm-email" # The URL for magic sign-in in the application. APP_MAGIC_SIGN_URL="http://localhost:4200/#/auth/magic-sign-in" -# set true if running inside Docker container +# Set true if running inside Docker container IS_DOCKER=false -# set true if running as a Demo +# Set true if running as a Demo DEMO=false ALLOW_SUPER_ADMIN_ROLE=true -# set to Gauzy API base URL +# Set to Gauzy API base URL API_BASE_URL=http://localhost:3000 -# set to Gauzy UI base URL +# Set to Gauzy UI base URL CLIENT_BASE_URL=http://localhost:4200 -#set to Website Platform +# Set to Website Platform PLATFORM_WEBSITE_URL=https://gauzy.co PLATFORM_WEBSITE_DOWNLOAD_URL=https://gauzy.co/downloads diff --git a/.github/workflows/deploy-civo-prod.yml b/.github/workflows/deploy-civo-prod.yml index 00a213fb917..59546f54ec6 100644 --- a/.github/workflows/deploy-civo-prod.yml +++ b/.github/workflows/deploy-civo-prod.yml @@ -1,125 +1,134 @@ name: Deploy to Civo Prod on: - workflow_run: - workflows: ['Build and Publish Docker Images Prod'] - branches: [master] - types: - - completed + workflow_run: + workflows: ['Build and Publish Docker Images Prod'] + branches: [master] + types: + - completed jobs: - deploy-prod: - runs-on: ubuntu-latest + deploy-prod: + runs-on: ubuntu-latest - environment: prod + environment: prod - steps: - - name: Checkout - uses: actions/checkout@v3 + steps: + - name: Checkout + uses: actions/checkout@v3 - - name: Create kubeconfig - run: | - mkdir ${HOME}/.kube - echo ${{ secrets.CIVO_KUBECONFIG }} | base64 --decode > ${HOME}/.kube/config + - name: Create kubeconfig + run: | + mkdir ${HOME}/.kube + echo ${{ secrets.CIVO_KUBECONFIG }} | base64 --decode > ${HOME}/.kube/config - - name: Generate TLS Secrets for DemoCW and APIDemoCW - run: | - rm -f ${HOME}/ingress.api.crt ${HOME}/ingress.api.key ${HOME}/ingress.webapp.crt ${HOME}/ingress.webapp.key - echo ${{ secrets.INGRESS_API_CERT }} | base64 --decode > ${HOME}/ingress.api.crt - echo ${{ secrets.INGRESS_API_CERT_KEY }} | base64 --decode > ${HOME}/ingress.api.key - echo ${{ secrets.INGRESS_WEBAPP_CERT }} | base64 --decode > ${HOME}/ingress.webapp.crt - echo ${{ secrets.INGRESS_WEBAPP_CERT_KEY }} | base64 --decode > ${HOME}/ingress.webapp.key - kubectl create secret tls apicivo.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.api.crt --key=${HOME}/ingress.api.key -o yaml | kubectl apply -f - - kubectl create secret tls appcivo.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.webapp.crt --key=${HOME}/ingress.webapp.key -o yaml | kubectl apply -f - + - name: Generate TLS Secrets for DemoCW and APIDemoCW + run: | + rm -f ${HOME}/ingress.api.crt ${HOME}/ingress.api.key ${HOME}/ingress.webapp.crt ${HOME}/ingress.webapp.key + echo ${{ secrets.INGRESS_API_CERT }} | base64 --decode > ${HOME}/ingress.api.crt + echo ${{ secrets.INGRESS_API_CERT_KEY }} | base64 --decode > ${HOME}/ingress.api.key + echo ${{ secrets.INGRESS_WEBAPP_CERT }} | base64 --decode > ${HOME}/ingress.webapp.crt + echo ${{ secrets.INGRESS_WEBAPP_CERT_KEY }} | base64 --decode > ${HOME}/ingress.webapp.key + kubectl create secret tls apicivo.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.api.crt --key=${HOME}/ingress.api.key -o yaml | kubectl apply -f - + kubectl create secret tls appcivo.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.webapp.crt --key=${HOME}/ingress.webapp.key -o yaml | kubectl apply -f - - - name: Write PostgreSQL Certificate file - run: | - echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt - env: - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + - name: Write PostgreSQL Certificate file + run: | + echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt + env: + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) - run: | - envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.civo.prod.yaml | kubectl --context ever apply -f - - env: - # below we are using GitHub secrets for both frontend and backend - DB_TYPE: '${{ secrets.DB_TYPE }}' - DB_URI: '${{ secrets.DB_URI }}' - # Note: for now we are using DB in different provider, so we have to use public hostname - DB_HOST: '${{ secrets.DB_HOST_PUBLIC }}' - DB_USER: '${{ secrets.DB_USER }}' - DB_PASS: '${{ secrets.DB_PASS }}' - DB_NAME: 'gauzy-pool' - DB_PORT: '${{ secrets.DB_PORT }}' - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' - SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' - SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' - SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' - SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' - AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' - AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' - AWS_REGION: '${{ secrets.AWS_REGION }}' - AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' - WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' - WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' - WASABI_REGION: '${{ secrets.WASABI_REGION }}' - WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' - WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' - EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' - JWT_SECRET: '${{ secrets.JWT_SECRET }}' - JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' - JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' - CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' - CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' - CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' - MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' - MAIL_HOST: '${{ secrets.MAIL_HOST }}' - MAIL_PORT: '${{ secrets.MAIL_PORT }}' - MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' - MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' - ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' - GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' - GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' - GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' - FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' - FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' - FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' - FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' - INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' - UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' - FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' - GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' - GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' - UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' - UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' - UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' - UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' - UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' - UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' - PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' - PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' - PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' - JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' - JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' - GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' - GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' - GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' - GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' - GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' - GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' - GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' - GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' - GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' - GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' - JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' - JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) + run: | + envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.civo.prod.yaml | kubectl --context ever apply -f - + env: + # below we are using GitHub secrets for both frontend and backend + DB_TYPE: '${{ secrets.DB_TYPE }}' + DB_URI: '${{ secrets.DB_URI }}' + # Note: for now we are using DB in different provider, so we have to use public hostname + DB_HOST: '${{ secrets.DB_HOST_PUBLIC }}' + DB_USER: '${{ secrets.DB_USER }}' + DB_PASS: '${{ secrets.DB_PASS }}' + DB_NAME: 'gauzy-pool' + DB_PORT: '${{ secrets.DB_PORT }}' + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' + SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' + SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' + SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' + SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' + AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' + AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' + AWS_REGION: '${{ secrets.AWS_REGION }}' + AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' + WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' + WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' + WASABI_REGION: '${{ secrets.WASABI_REGION }}' + WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' + WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' + EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' + JWT_SECRET: '${{ secrets.JWT_SECRET }}' + JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' + JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' + CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' + CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' + CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' + MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' + MAIL_HOST: '${{ secrets.MAIL_HOST }}' + MAIL_PORT: '${{ secrets.MAIL_PORT }}' + MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' + MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' + ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' + GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' + GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' + GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' + FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' + FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' + FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' + FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' + INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' + UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' + FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' + GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' + GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' + UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' + UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' + UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' + UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' + UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' + UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' + PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' + PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' + PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' + JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' + JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' + GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' + GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' + GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' + GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' + GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' + GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' + GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' + GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' + GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' + JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' + JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + MAGIC_CODE_EXPIRATION_TIME: '${{ secrets.MAGIC_CODE_EXPIRATION_TIME }}' + APP_NAME: '${{ secrets.APP_NAME }}' + APP_LOGO: '${{ secrets.APP_LOGO }}' + APP_SIGNATURE: '${{ secrets.APP_SIGNATURE }}' + APP_LINK: '${{ secrets.APP_LINK }}' + APP_EMAIL_CONFIRMATION_URL: '${{ secrets.APP_EMAIL_CONFIRMATION_URL }}' + APP_MAGIC_SIGN_URL: '${{ secrets.APP_MAGIC_SIGN_URL }}' + COMPANY_LINK: '${{ secrets.COMPANY_LINK }}' + COMPANY_NAME: '${{ secrets.COMPANY_NAME }}' - # we need this step because for now we just use :latest tag - # note: for production we will use different strategy later - - name: Restart Pods to pick up :latest tag version - run: | - kubectl --context ever rollout restart deployment/gauzy-prod-api - kubectl --context ever rollout restart deployment/gauzy-prod-webapp + # we need this step because for now we just use :latest tag + # note: for production we will use different strategy later + - name: Restart Pods to pick up :latest tag version + run: | + kubectl --context ever rollout restart deployment/gauzy-prod-api + kubectl --context ever rollout restart deployment/gauzy-prod-webapp diff --git a/.github/workflows/deploy-civo-stage.yml b/.github/workflows/deploy-civo-stage.yml index 95f47ea111f..5fdd71d251a 100644 --- a/.github/workflows/deploy-civo-stage.yml +++ b/.github/workflows/deploy-civo-stage.yml @@ -1,126 +1,135 @@ name: Deploy to Civo Stage on: - workflow_run: - workflows: ['Build and Publish Docker Images Stage'] - branches: [stage] - types: - - completed + workflow_run: + workflows: ['Build and Publish Docker Images Stage'] + branches: [stage] + types: + - completed jobs: - deploy-stage: - runs-on: ubuntu-latest + deploy-stage: + runs-on: ubuntu-latest - environment: stage + environment: stage - steps: - - name: Checkout - uses: actions/checkout@v3 + steps: + - name: Checkout + uses: actions/checkout@v3 - - name: Create kubeconfig - run: | - mkdir ${HOME}/.kube - echo ${{ secrets.CIVO_KUBECONFIG }} | base64 --decode > ${HOME}/.kube/config + - name: Create kubeconfig + run: | + mkdir ${HOME}/.kube + echo ${{ secrets.CIVO_KUBECONFIG }} | base64 --decode > ${HOME}/.kube/config - - name: Generate TLS Secrets for DemoCW and APIDemoCW - run: | - rm -f ${HOME}/ingress.api.crt ${HOME}/ingress.api.key ${HOME}/ingress.webapp.crt ${HOME}/ingress.webapp.key - echo ${{ secrets.INGRESS_API_CERT }} | base64 --decode > ${HOME}/ingress.api.crt - echo ${{ secrets.INGRESS_API_CERT_KEY }} | base64 --decode > ${HOME}/ingress.api.key - echo ${{ secrets.INGRESS_WEBAPP_CERT }} | base64 --decode > ${HOME}/ingress.webapp.crt - echo ${{ secrets.INGRESS_WEBAPP_CERT_KEY }} | base64 --decode > ${HOME}/ingress.webapp.key - kubectl create secret tls apistagecivo.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.api.crt --key=${HOME}/ingress.api.key -o yaml | kubectl apply -f - - kubectl create secret tls stagecivo.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.webapp.crt --key=${HOME}/ingress.webapp.key -o yaml | kubectl apply -f - + - name: Generate TLS Secrets for DemoCW and APIDemoCW + run: | + rm -f ${HOME}/ingress.api.crt ${HOME}/ingress.api.key ${HOME}/ingress.webapp.crt ${HOME}/ingress.webapp.key + echo ${{ secrets.INGRESS_API_CERT }} | base64 --decode > ${HOME}/ingress.api.crt + echo ${{ secrets.INGRESS_API_CERT_KEY }} | base64 --decode > ${HOME}/ingress.api.key + echo ${{ secrets.INGRESS_WEBAPP_CERT }} | base64 --decode > ${HOME}/ingress.webapp.crt + echo ${{ secrets.INGRESS_WEBAPP_CERT_KEY }} | base64 --decode > ${HOME}/ingress.webapp.key + kubectl create secret tls apistagecivo.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.api.crt --key=${HOME}/ingress.api.key -o yaml | kubectl apply -f - + kubectl create secret tls stagecivo.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.webapp.crt --key=${HOME}/ingress.webapp.key -o yaml | kubectl apply -f - - - name: Write PostgreSQL Certificate file - run: | - echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt - env: - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + - name: Write PostgreSQL Certificate file + run: | + echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt + env: + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) - run: | - envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.civo.stage.yaml | kubectl --context ever apply -f - - env: - # below we are using GitHub secrets for both frontend and backend - DB_TYPE: '${{ secrets.DB_TYPE }}' - DB_URI: '${{ secrets.DB_URI }}' - # Note: for now we are using DB in different provider, so we have to use public hostname - DB_HOST: '${{ secrets.DB_HOST_PUBLIC }}' - DB_USER: '${{ secrets.DB_USER }}' - DB_PASS: '${{ secrets.DB_PASS }}' - # Note that for staging we are using for now same server, just different DB name - DB_NAME: 'gauzy-stage-pool' - DB_PORT: '${{ secrets.DB_PORT }}' - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' - SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' - SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' - SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' - SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' - AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' - AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' - AWS_REGION: '${{ secrets.AWS_REGION }}' - AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' - WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' - WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' - WASABI_REGION: '${{ secrets.WASABI_REGION }}' - WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' - WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' - EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' - JWT_SECRET: '${{ secrets.JWT_SECRET }}' - JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' - JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' - CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' - CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' - CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' - MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' - MAIL_HOST: '${{ secrets.MAIL_HOST }}' - MAIL_PORT: '${{ secrets.MAIL_PORT }}' - MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' - MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' - ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' - GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' - GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' - GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' - FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' - FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' - FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' - FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' - INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' - UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' - FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' - GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' - GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' - UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' - UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' - UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' - UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' - UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' - UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' - PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' - PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' - PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' - JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' - JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' - GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' - GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' - GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' - GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' - GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' - GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' - GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' - GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' - GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' - GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' - JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' - JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) + run: | + envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.civo.stage.yaml | kubectl --context ever apply -f - + env: + # below we are using GitHub secrets for both frontend and backend + DB_TYPE: '${{ secrets.DB_TYPE }}' + DB_URI: '${{ secrets.DB_URI }}' + # Note: for now we are using DB in different provider, so we have to use public hostname + DB_HOST: '${{ secrets.DB_HOST_PUBLIC }}' + DB_USER: '${{ secrets.DB_USER }}' + DB_PASS: '${{ secrets.DB_PASS }}' + # Note that for staging we are using for now same server, just different DB name + DB_NAME: 'gauzy-stage-pool' + DB_PORT: '${{ secrets.DB_PORT }}' + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' + SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' + SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' + SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' + SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' + AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' + AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' + AWS_REGION: '${{ secrets.AWS_REGION }}' + AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' + WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' + WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' + WASABI_REGION: '${{ secrets.WASABI_REGION }}' + WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' + WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' + EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' + JWT_SECRET: '${{ secrets.JWT_SECRET }}' + JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' + JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' + CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' + CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' + CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' + MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' + MAIL_HOST: '${{ secrets.MAIL_HOST }}' + MAIL_PORT: '${{ secrets.MAIL_PORT }}' + MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' + MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' + ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' + GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' + GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' + GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' + FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' + FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' + FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' + FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' + INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' + UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' + FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' + GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' + GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' + UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' + UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' + UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' + UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' + UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' + UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' + PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' + PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' + PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' + JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' + JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' + GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' + GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' + GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' + GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' + GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' + GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' + GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' + GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' + GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' + JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' + JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + MAGIC_CODE_EXPIRATION_TIME: '${{ secrets.MAGIC_CODE_EXPIRATION_TIME }}' + APP_NAME: '${{ secrets.APP_NAME }}' + APP_LOGO: '${{ secrets.APP_LOGO }}' + APP_SIGNATURE: '${{ secrets.APP_SIGNATURE }}' + APP_LINK: '${{ secrets.APP_LINK }}' + APP_EMAIL_CONFIRMATION_URL: '${{ secrets.APP_EMAIL_CONFIRMATION_URL }}' + APP_MAGIC_SIGN_URL: '${{ secrets.APP_MAGIC_SIGN_URL }}' + COMPANY_LINK: '${{ secrets.COMPANY_LINK }}' + COMPANY_NAME: '${{ secrets.COMPANY_NAME }}' - # we need this step because for now we just use :latest tag - # note: for production we will use different strategy later - - name: Restart Pods to pick up :latest tag version - run: | - kubectl --context ever rollout restart deployment/gauzy-stage-api - kubectl --context ever rollout restart deployment/gauzy-stage-webapp + # we need this step because for now we just use :latest tag + # note: for production we will use different strategy later + - name: Restart Pods to pick up :latest tag version + run: | + kubectl --context ever rollout restart deployment/gauzy-stage-api + kubectl --context ever rollout restart deployment/gauzy-stage-webapp diff --git a/.github/workflows/deploy-cw-prod.yml b/.github/workflows/deploy-cw-prod.yml index a0f3a2b4da5..33ad468be03 100644 --- a/.github/workflows/deploy-cw-prod.yml +++ b/.github/workflows/deploy-cw-prod.yml @@ -1,125 +1,134 @@ name: Deploy to CoreWeave Prod on: - workflow_run: - workflows: ['Build and Publish Docker Images Prod'] - branches: [master] - types: - - completed + workflow_run: + workflows: ['Build and Publish Docker Images Prod'] + branches: [master] + types: + - completed jobs: - deploy-prod: - runs-on: ubuntu-latest + deploy-prod: + runs-on: ubuntu-latest - environment: prod + environment: prod - steps: - - name: Checkout - uses: actions/checkout@v3 + steps: + - name: Checkout + uses: actions/checkout@v3 - - name: Create kubeconfig - run: | - mkdir ${HOME}/.kube - echo ${{ secrets.CW_KUBECONFIG }} | base64 --decode > ${HOME}/.kube/config + - name: Create kubeconfig + run: | + mkdir ${HOME}/.kube + echo ${{ secrets.CW_KUBECONFIG }} | base64 --decode > ${HOME}/.kube/config - - name: Generate TLS Secrets for DemoCW and APIDemoCW - run: | - rm -f ${HOME}/ingress.api.crt ${HOME}/ingress.api.key ${HOME}/ingress.webapp.crt ${HOME}/ingress.webapp.key - echo ${{ secrets.INGRESS_API_CERT }} | base64 --decode > ${HOME}/ingress.api.crt - echo ${{ secrets.INGRESS_API_CERT_KEY }} | base64 --decode > ${HOME}/ingress.api.key - echo ${{ secrets.INGRESS_WEBAPP_CERT }} | base64 --decode > ${HOME}/ingress.webapp.crt - echo ${{ secrets.INGRESS_WEBAPP_CERT_KEY }} | base64 --decode > ${HOME}/ingress.webapp.key - kubectl create secret tls apicw.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.api.crt --key=${HOME}/ingress.api.key -o yaml | kubectl --context coreweave apply -f - - kubectl create secret tls appcw.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.webapp.crt --key=${HOME}/ingress.webapp.key -o yaml | kubectl --context coreweave apply -f - + - name: Generate TLS Secrets for DemoCW and APIDemoCW + run: | + rm -f ${HOME}/ingress.api.crt ${HOME}/ingress.api.key ${HOME}/ingress.webapp.crt ${HOME}/ingress.webapp.key + echo ${{ secrets.INGRESS_API_CERT }} | base64 --decode > ${HOME}/ingress.api.crt + echo ${{ secrets.INGRESS_API_CERT_KEY }} | base64 --decode > ${HOME}/ingress.api.key + echo ${{ secrets.INGRESS_WEBAPP_CERT }} | base64 --decode > ${HOME}/ingress.webapp.crt + echo ${{ secrets.INGRESS_WEBAPP_CERT_KEY }} | base64 --decode > ${HOME}/ingress.webapp.key + kubectl create secret tls apicw.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.api.crt --key=${HOME}/ingress.api.key -o yaml | kubectl --context coreweave apply -f - + kubectl create secret tls appcw.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.webapp.crt --key=${HOME}/ingress.webapp.key -o yaml | kubectl --context coreweave apply -f - - - name: Write PostgreSQL Certificate file - run: | - echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt - env: - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + - name: Write PostgreSQL Certificate file + run: | + echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt + env: + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) - run: | - envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.cw.prod.yaml | kubectl --context coreweave apply -f - - env: - # below we are using GitHub secrets for both frontend and backend - DB_TYPE: '${{ secrets.DB_TYPE }}' - DB_URI: '${{ secrets.DB_URI }}' - # Note: for now we are using DB in different provider, so we have to use public hostname - DB_HOST: '${{ secrets.DB_HOST_PUBLIC }}' - DB_USER: '${{ secrets.DB_USER }}' - DB_PASS: '${{ secrets.DB_PASS }}' - DB_NAME: 'gauzy-pool' - DB_PORT: '${{ secrets.DB_PORT }}' - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' - SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' - SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' - SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' - SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' - AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' - AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' - AWS_REGION: '${{ secrets.AWS_REGION }}' - AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' - WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' - WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' - WASABI_REGION: '${{ secrets.WASABI_REGION }}' - WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' - WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' - EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' - JWT_SECRET: '${{ secrets.JWT_SECRET }}' - JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' - JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' - CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' - CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' - CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' - MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' - MAIL_HOST: '${{ secrets.MAIL_HOST }}' - MAIL_PORT: '${{ secrets.MAIL_PORT }}' - MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' - MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' - ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' - GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' - GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' - GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' - FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' - FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' - FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' - FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' - INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' - UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' - FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' - GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' - GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' - UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' - UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' - UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' - UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' - UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' - UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' - PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' - PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' - PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' - JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' - JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' - GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' - GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' - GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' - GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' - GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' - GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' - GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' - GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' - GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' - GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' - JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' - JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) + run: | + envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.cw.prod.yaml | kubectl --context coreweave apply -f - + env: + # below we are using GitHub secrets for both frontend and backend + DB_TYPE: '${{ secrets.DB_TYPE }}' + DB_URI: '${{ secrets.DB_URI }}' + # Note: for now we are using DB in different provider, so we have to use public hostname + DB_HOST: '${{ secrets.DB_HOST_PUBLIC }}' + DB_USER: '${{ secrets.DB_USER }}' + DB_PASS: '${{ secrets.DB_PASS }}' + DB_NAME: 'gauzy-pool' + DB_PORT: '${{ secrets.DB_PORT }}' + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' + SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' + SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' + SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' + SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' + AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' + AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' + AWS_REGION: '${{ secrets.AWS_REGION }}' + AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' + WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' + WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' + WASABI_REGION: '${{ secrets.WASABI_REGION }}' + WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' + WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' + EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' + JWT_SECRET: '${{ secrets.JWT_SECRET }}' + JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' + JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' + CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' + CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' + CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' + MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' + MAIL_HOST: '${{ secrets.MAIL_HOST }}' + MAIL_PORT: '${{ secrets.MAIL_PORT }}' + MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' + MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' + ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' + GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' + GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' + GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' + FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' + FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' + FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' + FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' + INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' + UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' + FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' + GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' + GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' + UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' + UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' + UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' + UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' + UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' + UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' + PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' + PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' + PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' + JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' + JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' + GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' + GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' + GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' + GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' + GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' + GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' + GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' + GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' + GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' + JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' + JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + MAGIC_CODE_EXPIRATION_TIME: '${{ secrets.MAGIC_CODE_EXPIRATION_TIME }}' + APP_NAME: '${{ secrets.APP_NAME }}' + APP_LOGO: '${{ secrets.APP_LOGO }}' + APP_SIGNATURE: '${{ secrets.APP_SIGNATURE }}' + APP_LINK: '${{ secrets.APP_LINK }}' + APP_EMAIL_CONFIRMATION_URL: '${{ secrets.APP_EMAIL_CONFIRMATION_URL }}' + APP_MAGIC_SIGN_URL: '${{ secrets.APP_MAGIC_SIGN_URL }}' + COMPANY_LINK: '${{ secrets.COMPANY_LINK }}' + COMPANY_NAME: '${{ secrets.COMPANY_NAME }}' - # we need this step because for now we just use :latest tag - # note: for production we will use different strategy later - - name: Restart Pods to pick up :latest tag version - run: | - kubectl --context coreweave rollout restart deployment/gauzy-prod-api - kubectl --context coreweave rollout restart deployment/gauzy-prod-webapp + # we need this step because for now we just use :latest tag + # note: for production we will use different strategy later + - name: Restart Pods to pick up :latest tag version + run: | + kubectl --context coreweave rollout restart deployment/gauzy-prod-api + kubectl --context coreweave rollout restart deployment/gauzy-prod-webapp diff --git a/.github/workflows/deploy-cw-stage.yml b/.github/workflows/deploy-cw-stage.yml index 0144c18dc8c..c2e2a73b1b7 100644 --- a/.github/workflows/deploy-cw-stage.yml +++ b/.github/workflows/deploy-cw-stage.yml @@ -1,126 +1,135 @@ name: Deploy to CoreWeave Stage on: - workflow_run: - workflows: ['Build and Publish Docker Images Stage'] - branches: [stage] - types: - - completed + workflow_run: + workflows: ['Build and Publish Docker Images Stage'] + branches: [stage] + types: + - completed jobs: - deploy-stage: - runs-on: ubuntu-latest + deploy-stage: + runs-on: ubuntu-latest - environment: stage + environment: stage - steps: - - name: Checkout - uses: actions/checkout@v3 + steps: + - name: Checkout + uses: actions/checkout@v3 - - name: Create kubeconfig - run: | - mkdir ${HOME}/.kube - echo ${{ secrets.CW_KUBECONFIG }} | base64 --decode > ${HOME}/.kube/config + - name: Create kubeconfig + run: | + mkdir ${HOME}/.kube + echo ${{ secrets.CW_KUBECONFIG }} | base64 --decode > ${HOME}/.kube/config - - name: Generate TLS Secrets for DemoCW and APIDemoCW - run: | - rm -f ${HOME}/ingress.api.crt ${HOME}/ingress.api.key ${HOME}/ingress.webapp.crt ${HOME}/ingress.webapp.key - echo ${{ secrets.INGRESS_API_CERT }} | base64 --decode > ${HOME}/ingress.api.crt - echo ${{ secrets.INGRESS_API_CERT_KEY }} | base64 --decode > ${HOME}/ingress.api.key - echo ${{ secrets.INGRESS_WEBAPP_CERT }} | base64 --decode > ${HOME}/ingress.webapp.crt - echo ${{ secrets.INGRESS_WEBAPP_CERT_KEY }} | base64 --decode > ${HOME}/ingress.webapp.key - kubectl create secret tls apistagecw.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.api.crt --key=${HOME}/ingress.api.key -o yaml | kubectl --context coreweave apply -f - - kubectl create secret tls stagecw.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.webapp.crt --key=${HOME}/ingress.webapp.key -o yaml | kubectl --context coreweave apply -f - + - name: Generate TLS Secrets for DemoCW and APIDemoCW + run: | + rm -f ${HOME}/ingress.api.crt ${HOME}/ingress.api.key ${HOME}/ingress.webapp.crt ${HOME}/ingress.webapp.key + echo ${{ secrets.INGRESS_API_CERT }} | base64 --decode > ${HOME}/ingress.api.crt + echo ${{ secrets.INGRESS_API_CERT_KEY }} | base64 --decode > ${HOME}/ingress.api.key + echo ${{ secrets.INGRESS_WEBAPP_CERT }} | base64 --decode > ${HOME}/ingress.webapp.crt + echo ${{ secrets.INGRESS_WEBAPP_CERT_KEY }} | base64 --decode > ${HOME}/ingress.webapp.key + kubectl create secret tls apistagecw.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.api.crt --key=${HOME}/ingress.api.key -o yaml | kubectl --context coreweave apply -f - + kubectl create secret tls stagecw.gauzy.co-tls --save-config --dry-run=client --cert=${HOME}/ingress.webapp.crt --key=${HOME}/ingress.webapp.key -o yaml | kubectl --context coreweave apply -f - - - name: Write PostgreSQL Certificate file - run: | - echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt - env: - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + - name: Write PostgreSQL Certificate file + run: | + echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt + env: + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) - run: | - envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.cw.stage.yaml | kubectl --context coreweave apply -f - - env: - # below we are using GitHub secrets for both frontend and backend - DB_TYPE: '${{ secrets.DB_TYPE }}' - DB_URI: '${{ secrets.DB_URI }}' - # Note: for now we are using DB in different provider, so we have to use public hostname - DB_HOST: '${{ secrets.DB_HOST_PUBLIC }}' - DB_USER: '${{ secrets.DB_USER }}' - DB_PASS: '${{ secrets.DB_PASS }}' - # Note that for staging we are using for now same server, just different DB name - DB_NAME: 'gauzy-stage-pool' - DB_PORT: '${{ secrets.DB_PORT }}' - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' - SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' - SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' - SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' - SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' - AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' - AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' - AWS_REGION: '${{ secrets.AWS_REGION }}' - AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' - WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' - WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' - WASABI_REGION: '${{ secrets.WASABI_REGION }}' - WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' - WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' - EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' - JWT_SECRET: '${{ secrets.JWT_SECRET }}' - JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' - JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' - CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' - CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' - CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' - MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' - MAIL_HOST: '${{ secrets.MAIL_HOST }}' - MAIL_PORT: '${{ secrets.MAIL_PORT }}' - MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' - MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' - ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' - GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' - GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' - GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' - FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' - FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' - FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' - FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' - INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' - UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' - FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' - GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' - GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' - UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' - UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' - UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' - UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' - UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' - UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' - PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' - PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' - PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' - JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' - JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' - GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' - GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' - GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' - GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' - GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' - GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' - GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' - GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' - GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' - GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' - JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' - JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) + run: | + envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.cw.stage.yaml | kubectl --context coreweave apply -f - + env: + # below we are using GitHub secrets for both frontend and backend + DB_TYPE: '${{ secrets.DB_TYPE }}' + DB_URI: '${{ secrets.DB_URI }}' + # Note: for now we are using DB in different provider, so we have to use public hostname + DB_HOST: '${{ secrets.DB_HOST_PUBLIC }}' + DB_USER: '${{ secrets.DB_USER }}' + DB_PASS: '${{ secrets.DB_PASS }}' + # Note that for staging we are using for now same server, just different DB name + DB_NAME: 'gauzy-stage-pool' + DB_PORT: '${{ secrets.DB_PORT }}' + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' + SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' + SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' + SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' + SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' + AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' + AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' + AWS_REGION: '${{ secrets.AWS_REGION }}' + AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' + WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' + WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' + WASABI_REGION: '${{ secrets.WASABI_REGION }}' + WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' + WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' + EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' + JWT_SECRET: '${{ secrets.JWT_SECRET }}' + JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' + JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' + CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' + CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' + CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' + MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' + MAIL_HOST: '${{ secrets.MAIL_HOST }}' + MAIL_PORT: '${{ secrets.MAIL_PORT }}' + MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' + MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' + ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' + GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' + GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' + GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' + FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' + FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' + FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' + FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' + INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' + UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' + FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' + GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' + GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' + UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' + UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' + UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' + UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' + UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' + UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' + PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' + PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' + PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' + JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' + JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' + GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' + GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' + GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' + GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' + GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' + GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' + GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' + GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' + GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' + JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' + JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + MAGIC_CODE_EXPIRATION_TIME: '${{ secrets.MAGIC_CODE_EXPIRATION_TIME }}' + APP_NAME: '${{ secrets.APP_NAME }}' + APP_LOGO: '${{ secrets.APP_LOGO }}' + APP_SIGNATURE: '${{ secrets.APP_SIGNATURE }}' + APP_LINK: '${{ secrets.APP_LINK }}' + APP_EMAIL_CONFIRMATION_URL: '${{ secrets.APP_EMAIL_CONFIRMATION_URL }}' + APP_MAGIC_SIGN_URL: '${{ secrets.APP_MAGIC_SIGN_URL }}' + COMPANY_LINK: '${{ secrets.COMPANY_LINK }}' + COMPANY_NAME: '${{ secrets.COMPANY_NAME }}' - # we need this step because for now we just use :latest tag - # note: for production we will use different strategy later - - name: Restart Pods to pick up :latest tag version - run: | - kubectl --context coreweave rollout restart deployment/gauzy-stage-api - kubectl --context coreweave rollout restart deployment/gauzy-stage-webapp + # we need this step because for now we just use :latest tag + # note: for production we will use different strategy later + - name: Restart Pods to pick up :latest tag version + run: | + kubectl --context coreweave rollout restart deployment/gauzy-stage-api + kubectl --context coreweave rollout restart deployment/gauzy-stage-webapp diff --git a/.github/workflows/deploy-do-prod.yml b/.github/workflows/deploy-do-prod.yml index 9188f82e59d..33da003cd26 100644 --- a/.github/workflows/deploy-do-prod.yml +++ b/.github/workflows/deploy-do-prod.yml @@ -1,120 +1,129 @@ name: Deploy to DigitalOcean Prod on: - workflow_run: - workflows: ['Build and Publish Docker Images Prod'] - branches: [master] - types: - - completed + workflow_run: + workflows: ['Build and Publish Docker Images Prod'] + branches: [master] + types: + - completed jobs: - deploy-prod: - runs-on: ubuntu-latest + deploy-prod: + runs-on: ubuntu-latest - environment: prod + environment: prod - steps: - - name: Checkout - uses: actions/checkout@v3 + steps: + - name: Checkout + uses: actions/checkout@v3 - - name: Install doctl - uses: digitalocean/action-doctl@v2 - with: - token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - - name: Log in to DigitalOcean Container Registry with short-lived credentials - run: doctl registry login --expiry-seconds 600 + - name: Log in to DigitalOcean Container Registry with short-lived credentials + run: doctl registry login --expiry-seconds 600 - - name: Save DigitalOcean kubeconfig with short-lived credentials - run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 k8s-gauzy + - name: Save DigitalOcean kubeconfig with short-lived credentials + run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 k8s-gauzy - - name: Write PostgreSQL Certificate file - run: | - echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt - env: - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + - name: Write PostgreSQL Certificate file + run: | + echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt + env: + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) - run: | - envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.prod.yaml | kubectl --context do-sfo2-k8s-gauzy apply -f - - env: - # below we are using GitHub secrets for both frontend and backend - DB_TYPE: '${{ secrets.DB_TYPE }}' - DB_URI: '${{ secrets.DB_URI }}' - DB_HOST: '${{ secrets.DB_HOST }}' - DB_USER: '${{ secrets.DB_USER }}' - DB_PASS: '${{ secrets.DB_PASS }}' - DB_NAME: 'gauzy-pool' - DB_PORT: '${{ secrets.DB_PORT }}' - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' - SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' - SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' - SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' - SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' - AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' - AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' - AWS_REGION: '${{ secrets.AWS_REGION }}' - AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' - WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' - WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' - WASABI_REGION: '${{ secrets.WASABI_REGION }}' - WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' - WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' - EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' - JWT_SECRET: '${{ secrets.JWT_SECRET }}' - JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' - JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' - CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' - CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' - CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' - MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' - MAIL_HOST: '${{ secrets.MAIL_HOST }}' - MAIL_PORT: '${{ secrets.MAIL_PORT }}' - MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' - MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' - ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' - GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' - GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' - GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' - FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' - FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' - FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' - FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' - INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' - UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' - FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' - GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' - GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' - UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' - UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' - UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' - UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' - UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' - UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' - PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' - PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' - PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' - JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' - JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' - GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' - GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' - GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' - GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' - GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' - GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' - GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' - GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' - GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' - GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' - JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' - JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) + run: | + envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.prod.yaml | kubectl --context do-sfo2-k8s-gauzy apply -f - + env: + # below we are using GitHub secrets for both frontend and backend + DB_TYPE: '${{ secrets.DB_TYPE }}' + DB_URI: '${{ secrets.DB_URI }}' + DB_HOST: '${{ secrets.DB_HOST }}' + DB_USER: '${{ secrets.DB_USER }}' + DB_PASS: '${{ secrets.DB_PASS }}' + DB_NAME: 'gauzy-pool' + DB_PORT: '${{ secrets.DB_PORT }}' + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' + SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' + SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' + SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' + SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' + AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' + AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' + AWS_REGION: '${{ secrets.AWS_REGION }}' + AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' + WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' + WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' + WASABI_REGION: '${{ secrets.WASABI_REGION }}' + WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' + WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' + EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' + JWT_SECRET: '${{ secrets.JWT_SECRET }}' + JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' + JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' + CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' + CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' + CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' + MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' + MAIL_HOST: '${{ secrets.MAIL_HOST }}' + MAIL_PORT: '${{ secrets.MAIL_PORT }}' + MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' + MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' + ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' + GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' + GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' + GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' + FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' + FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' + FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' + FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' + INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' + UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' + FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' + GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' + GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' + UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' + UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' + UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' + UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' + UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' + UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' + PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' + PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' + PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' + JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' + JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' + GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' + GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' + GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' + GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' + GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' + GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' + GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' + GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' + GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' + JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' + JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + MAGIC_CODE_EXPIRATION_TIME: '${{ secrets.MAGIC_CODE_EXPIRATION_TIME }}' + APP_NAME: '${{ secrets.APP_NAME }}' + APP_LOGO: '${{ secrets.APP_LOGO }}' + APP_SIGNATURE: '${{ secrets.APP_SIGNATURE }}' + APP_LINK: '${{ secrets.APP_LINK }}' + APP_EMAIL_CONFIRMATION_URL: '${{ secrets.APP_EMAIL_CONFIRMATION_URL }}' + APP_MAGIC_SIGN_URL: '${{ secrets.APP_MAGIC_SIGN_URL }}' + COMPANY_LINK: '${{ secrets.COMPANY_LINK }}' + COMPANY_NAME: '${{ secrets.COMPANY_NAME }}' - # we need this step because for now we just use :latest tag - # note: for production we will use different strategy later - - name: Restart Pods to pick up :latest tag version - run: | - kubectl --context do-sfo2-k8s-gauzy rollout restart deployment/gauzy-prod-api - kubectl --context do-sfo2-k8s-gauzy rollout restart deployment/gauzy-prod-webapp + # we need this step because for now we just use :latest tag + # note: for production we will use different strategy later + - name: Restart Pods to pick up :latest tag version + run: | + kubectl --context do-sfo2-k8s-gauzy rollout restart deployment/gauzy-prod-api + kubectl --context do-sfo2-k8s-gauzy rollout restart deployment/gauzy-prod-webapp diff --git a/.github/workflows/deploy-do-stage.yml b/.github/workflows/deploy-do-stage.yml index 3dda4e6ea50..7c94fae6ca2 100644 --- a/.github/workflows/deploy-do-stage.yml +++ b/.github/workflows/deploy-do-stage.yml @@ -1,121 +1,130 @@ name: Deploy to DigitalOcean Stage on: - workflow_run: - workflows: ['Build and Publish Docker Images Stage'] - branches: [stage] - types: - - completed + workflow_run: + workflows: ['Build and Publish Docker Images Stage'] + branches: [stage] + types: + - completed jobs: - deploy-stage: - runs-on: ubuntu-latest + deploy-stage: + runs-on: ubuntu-latest - environment: stage + environment: stage - steps: - - name: Checkout - uses: actions/checkout@v3 + steps: + - name: Checkout + uses: actions/checkout@v3 - - name: Install doctl - uses: digitalocean/action-doctl@v2 - with: - token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - - name: Log in to DigitalOcean Container Registry with short-lived credentials - run: doctl registry login --expiry-seconds 600 + - name: Log in to DigitalOcean Container Registry with short-lived credentials + run: doctl registry login --expiry-seconds 600 - - name: Save DigitalOcean kubeconfig with short-lived credentials - run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 k8s-gauzy + - name: Save DigitalOcean kubeconfig with short-lived credentials + run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 k8s-gauzy - - name: Write PostgreSQL Certificate file - run: | - echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt - env: - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + - name: Write PostgreSQL Certificate file + run: | + echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt + env: + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) - run: | - envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.stage.yaml | kubectl --context do-sfo2-k8s-gauzy apply -f - - env: - # below we are using GitHub secrets for both frontend and backend - DB_TYPE: '${{ secrets.DB_TYPE }}' - DB_URI: '${{ secrets.DB_URI }}' - DB_HOST: '${{ secrets.DB_HOST }}' - DB_USER: '${{ secrets.DB_USER }}' - DB_PASS: '${{ secrets.DB_PASS }}' - # Note that for staging we are using for now same server, just different DB name - DB_NAME: 'gauzy-stage-pool' - DB_PORT: '${{ secrets.DB_PORT }}' - DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' - DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' - SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' - SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' - SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' - SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' - AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' - AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' - AWS_REGION: '${{ secrets.AWS_REGION }}' - AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' - WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' - WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' - WASABI_REGION: '${{ secrets.WASABI_REGION }}' - WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' - WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' - EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' - JWT_SECRET: '${{ secrets.JWT_SECRET }}' - JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' - JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' - CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' - CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' - CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' - MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' - MAIL_HOST: '${{ secrets.MAIL_HOST }}' - MAIL_PORT: '${{ secrets.MAIL_PORT }}' - MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' - MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' - ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' - GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' - GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' - GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' - FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' - FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' - FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' - FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' - INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' - UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' - FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' - GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' - GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' - UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' - UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' - UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' - UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' - UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' - UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' - PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' - PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' - PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' - JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' - JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' - GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' - GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' - GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' - GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' - GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' - GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' - GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' - GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' - GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' - GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' - GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' - JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' - JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) + run: | + envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.stage.yaml | kubectl --context do-sfo2-k8s-gauzy apply -f - + env: + # below we are using GitHub secrets for both frontend and backend + DB_TYPE: '${{ secrets.DB_TYPE }}' + DB_URI: '${{ secrets.DB_URI }}' + DB_HOST: '${{ secrets.DB_HOST }}' + DB_USER: '${{ secrets.DB_USER }}' + DB_PASS: '${{ secrets.DB_PASS }}' + # Note that for staging we are using for now same server, just different DB name + DB_NAME: 'gauzy-stage-pool' + DB_PORT: '${{ secrets.DB_PORT }}' + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + DB_SSL_MODE: '${{ secrets.DB_SSL_MODE }}' + SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' + SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' + SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' + SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' + AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' + AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' + AWS_REGION: '${{ secrets.AWS_REGION }}' + AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' + WASABI_ACCESS_KEY_ID: '${{ secrets.WASABI_ACCESS_KEY_ID }}' + WASABI_SECRET_ACCESS_KEY: '${{ secrets.WASABI_SECRET_ACCESS_KEY }}' + WASABI_REGION: '${{ secrets.WASABI_REGION }}' + WASABI_SERVICE_URL: '${{ secrets.WASABI_SERVICE_URL }}' + WASABI_S3_BUCKET: '${{ secrets.WASABI_S3_BUCKET }}' + EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' + JWT_SECRET: '${{ secrets.JWT_SECRET }}' + JWT_REFRESH_TOKEN_SECRET: '${{ secrets.JWT_REFRESH_TOKEN_SECRET }}' + JWT_REFRESH_TOKEN_EXPIRATION_TIME: '${{ secrets.JWT_REFRESH_TOKEN_EXPIRATION_TIME }}' + CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' + CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' + CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' + MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' + MAIL_HOST: '${{ secrets.MAIL_HOST }}' + MAIL_PORT: '${{ secrets.MAIL_PORT }}' + MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' + MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' + ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' + GOOGLE_CLIENT_ID: '${{ secrets.GOOGLE_CLIENT_ID }}' + GOOGLE_CLIENT_SECRET: '${{ secrets.GOOGLE_CLIENT_SECRET }}' + GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' + FACEBOOK_CLIENT_ID: '${{ secrets.FACEBOOK_CLIENT_ID }}' + FACEBOOK_CLIENT_SECRET: '${{ secrets.FACEBOOK_CLIENT_SECRET }}' + FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' + FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' + INTEGRATED_USER_DEFAULT_PASS: '${{ secrets.INTEGRATED_USER_DEFAULT_PASS }}' + UPWORK_REDIRECT_URL: '${{ secrets.UPWORK_REDIRECT_URL }}' + FILE_PROVIDER: '${{ secrets.FILE_PROVIDER }}' + GAUZY_AI_GRAPHQL_ENDPOINT: '${{ secrets.GAUZY_AI_GRAPHQL_ENDPOINT }}' + GAUZY_AI_REST_ENDPOINT: '${{ secrets.GAUZY_AI_REST_ENDPOINT }}' + UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' + UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' + UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' + UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' + UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' + UNLEASH_API_KEY: '${{ secrets.UNLEASH_API_KEY }}' + PM2_MACHINE_NAME: '${{ secrets.PM2_MACHINE_NAME }}' + PM2_SECRET_KEY: '${{ secrets.PM2_SECRET_KEY }}' + PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' + JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' + JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' + GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' + GAUZY_GITHUB_APP_PRIVATE_KEY: '${{ secrets.GAUZY_GITHUB_APP_PRIVATE_KEY }}' + GAUZY_GITHUB_WEBHOOK_URL: '${{ secrets.GAUZY_GITHUB_WEBHOOK_URL }}' + GAUZY_GITHUB_WEBHOOK_SECRET: '${{ secrets.GAUZY_GITHUB_WEBHOOK_SECRET }}' + GAUZY_GITHUB_APP_NAME: '${{ secrets.GAUZY_GITHUB_APP_NAME }}' + GAUZY_GITHUB_REDIRECT_URL: '${{ secrets.GAUZY_GITHUB_REDIRECT_URL }}' + GAUZY_GITHUB_POST_INSTALL_URL: '${{ secrets.GAUZY_GITHUB_POST_INSTALL_URL }}' + GAUZY_GITHUB_APP_ID: '${{ secrets.GAUZY_GITHUB_APP_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_ID }}' + GAUZY_GITHUB_OAUTH_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_OAUTH_CLIENT_SECRET }}' + GAUZY_GITHUB_OAUTH_CALLBACK_URL: '${{ secrets.GAUZY_GITHUB_OAUTH_CALLBACK_URL }}' + JITSU_BROWSER_URL: '${{ secrets.JITSU_BROWSER_URL }}' + JITSU_BROWSER_WRITE_KEY: '${{ secrets.JITSU_BROWSER_WRITE_KEY }}' + MAGIC_CODE_EXPIRATION_TIME: '${{ secrets.MAGIC_CODE_EXPIRATION_TIME }}' + APP_NAME: '${{ secrets.APP_NAME }}' + APP_LOGO: '${{ secrets.APP_LOGO }}' + APP_SIGNATURE: '${{ secrets.APP_SIGNATURE }}' + APP_LINK: '${{ secrets.APP_LINK }}' + APP_EMAIL_CONFIRMATION_URL: '${{ secrets.APP_EMAIL_CONFIRMATION_URL }}' + APP_MAGIC_SIGN_URL: '${{ secrets.APP_MAGIC_SIGN_URL }}' + COMPANY_LINK: '${{ secrets.COMPANY_LINK }}' + COMPANY_NAME: '${{ secrets.COMPANY_NAME }}' - # we need this step because for now we just use :latest tag - # note: for production we will use different strategy later - - name: Restart Pods to pick up :latest tag version - run: | - kubectl --context do-sfo2-k8s-gauzy rollout restart deployment/gauzy-stage-api - kubectl --context do-sfo2-k8s-gauzy rollout restart deployment/gauzy-stage-webapp + # we need this step because for now we just use :latest tag + # note: for production we will use different strategy later + - name: Restart Pods to pick up :latest tag version + run: | + kubectl --context do-sfo2-k8s-gauzy rollout restart deployment/gauzy-stage-api + kubectl --context do-sfo2-k8s-gauzy rollout restart deployment/gauzy-stage-webapp diff --git a/apps/gauzy/src/app/@shared/timesheet/edit-time-log-modal/edit-time-log-modal.component.ts b/apps/gauzy/src/app/@shared/timesheet/edit-time-log-modal/edit-time-log-modal.component.ts index f1979588510..68a62d25938 100644 --- a/apps/gauzy/src/app/@shared/timesheet/edit-time-log-modal/edit-time-log-modal.component.ts +++ b/apps/gauzy/src/app/@shared/timesheet/edit-time-log-modal/edit-time-log-modal.component.ts @@ -33,9 +33,8 @@ import { Store, ToastrService } from '../../../@core/services'; templateUrl: './edit-time-log-modal.component.html', styleUrls: ['./edit-time-log-modal.component.scss'] }) -export class EditTimeLogModalComponent - implements OnInit, AfterViewInit, OnDestroy -{ +export class EditTimeLogModalComponent implements OnInit, AfterViewInit, OnDestroy { + PermissionsEnum = PermissionsEnum; today: Date = new Date(); mode: 'create' | 'update' = 'create'; @@ -251,7 +250,7 @@ export class EditTimeLogModalComponent ); } } - timeLog.overlapDuration = overlapDuration; + timeLog['overlapDuration'] = overlapDuration; return timeLog; }); } catch (error) { @@ -332,5 +331,5 @@ export class EditTimeLogModalComponent return this.form.get(control).value; } - ngOnDestroy(): void {} + ngOnDestroy(): void { } } diff --git a/apps/gauzy/src/app/pages/candidates/candidates.component.ts b/apps/gauzy/src/app/pages/candidates/candidates.component.ts index 6bb6e805f5b..85edb0e68c0 100644 --- a/apps/gauzy/src/app/pages/candidates/candidates.component.ts +++ b/apps/gauzy/src/app/pages/candidates/candidates.component.ts @@ -237,7 +237,7 @@ export class CandidatesComponent extends PaginationFilterBaseComponent } const candidateId = this.selectedCandidate.id; - this.router.navigate(['/pages/employees/candidates/edit/', candidateId, '/profile']); + this.router.navigate(['/pages/employees/candidates/edit', candidateId, 'profile']); } async archive(selectedItem?: ICandidateViewModel) { diff --git a/apps/gauzy/src/app/pages/employees/timesheet/approvals/approvals/approvals.component.ts b/apps/gauzy/src/app/pages/employees/timesheet/approvals/approvals/approvals.component.ts index 734aa3dbc3f..bc69c08839b 100644 --- a/apps/gauzy/src/app/pages/employees/timesheet/approvals/approvals/approvals.component.ts +++ b/apps/gauzy/src/app/pages/employees/timesheet/approvals/approvals/approvals.component.ts @@ -95,8 +95,8 @@ export class ApprovalsComponent extends BaseSelectorFilterComponent implements this.store.hasPermission( PermissionsEnum.CHANGE_SELECTED_EMPLOYEE ) - ? ['employee', 'employee.user'] - : [] + ? ['employee', 'employee.user'] + : [] ) ] }); @@ -317,40 +317,48 @@ export class ApprovalsComponent extends BaseSelectorFilterComponent implements */ userRowSelect(timesheet: ITimesheet) { // if row is already selected, deselect it. - if (timesheet.isSelected) { - timesheet.isSelected = false; + if (timesheet['isSelected']) { + timesheet['isSelected'] = false; this.selectTimesheet({ - isSelected: timesheet.isSelected, + isSelected: timesheet['isSelected'], data: null }); } else { // find the row which was previously selected. const isRowSelected = this.timesheets.find( - (item: ITimesheet) => item.isSelected === true + (item: ITimesheet) => item['isSelected'] === true ); if (!!isRowSelected) { // if row found successfully, mark that row as deselected - isRowSelected.isSelected = false; + isRowSelected['isSelected'] = false; } // mark new row as selected - timesheet.isSelected = true; + timesheet['isSelected'] = true; this.selectTimesheet({ - isSelected: timesheet.isSelected, + isSelected: timesheet['isSelected'], data: timesheet }); } } - isRowSelected() { - return !!this.timesheets.find((t: ITimesheet) => t.isSelected); + /** + * Checks if at least one timesheet in the list is selected. + * @returns True if a timesheet is selected, otherwise false. + */ + isRowSelected(): boolean { + return !!this.timesheets.find((timesheet: ITimesheet) => timesheet['isSelected'] === true); } - isCheckboxSelected() { - return this.timesheets.find((t: ITimesheet) => t.checked); + /** + * Checks if at least one timesheet in the list has its checkbox selected. + * @returns True if a timesheet's checkbox is selected, otherwise false. + */ + isCheckboxSelected(): boolean { + return !!this.timesheets.find((timesheet: ITimesheet) => timesheet['checked'] === true); } /** - * Prapare timesheets ids payload + * Prepare timesheets ids payload * * @param timesheetIds * @returns @@ -359,5 +367,5 @@ export class ApprovalsComponent extends BaseSelectorFilterComponent implements return (typeof timesheetIds === 'string') ? [timesheetIds] : timesheetIds; } - ngOnDestroy(): void {} + ngOnDestroy(): void { } } diff --git a/apps/gauzy/src/app/pages/employees/timesheet/daily/daily/daily.component.ts b/apps/gauzy/src/app/pages/employees/timesheet/daily/daily/daily.component.ts index 08a2e39b484..bcd9b944ab8 100644 --- a/apps/gauzy/src/app/pages/employees/timesheet/daily/daily/daily.component.ts +++ b/apps/gauzy/src/app/pages/employees/timesheet/daily/daily/daily.component.ts @@ -415,34 +415,42 @@ export class DailyComponent extends BaseSelectorFilterComponent */ userRowSelect(timeLog: ITimeLog) { // if row is already selected, deselect it. - if (timeLog.isSelected) { - timeLog.isSelected = false; + if (timeLog['isSelected']) { + timeLog['isSelected'] = false; this.selectTimeLog({ - isSelected: timeLog.isSelected, + isSelected: timeLog['isSelected'], data: null }); } else { // find the row which was previously selected. - const isRowSelected = this.timeLogs.find((item) => item.isSelected === true); + const isRowSelected = this.timeLogs.find((item: ITimeLog) => item['isSelected'] === true); if (!!isRowSelected) { // if row found successfully, mark that row as deselected - isRowSelected.isSelected = false; + isRowSelected['isSelected'] = false; } // mark new row as selected - timeLog.isSelected = true; + timeLog['isSelected'] = true; this.selectTimeLog({ - isSelected: timeLog.isSelected, + isSelected: timeLog['isSelected'], data: timeLog }); } } - isRowSelected() { - return !!this.timeLogs.find((t: ITimeLog) => t.isSelected); + /** + * Checks if at least one time log in the list is selected. + * @returns True if a time log is selected, otherwise false. + */ + isRowSelected(): boolean { + return !!this.timeLogs.find((log: ITimeLog) => log['isSelected'] === true); } - isCheckboxSelected() { - return this.timeLogs.find((t: ITimeLog) => t.checked); + /** + * Checks if at least one time log in the list has its checkbox selected. + * @returns True if a time log's checkbox is selected, otherwise false. + */ + isCheckboxSelected(): boolean { + return !!this.timeLogs.find((log: ITimeLog) => log['checked'] === true); } ngOnDestroy(): void { } diff --git a/apps/gauzy/src/assets/images/logos/ever512x512.jpg b/apps/gauzy/src/assets/images/logos/ever512x512.jpg new file mode 100644 index 00000000000..e2264f98c4f Binary files /dev/null and b/apps/gauzy/src/assets/images/logos/ever512x512.jpg differ diff --git a/apps/gauzy/src/assets/images/logos/logo_Gauzy_512x512.png b/apps/gauzy/src/assets/images/logos/logo_Gauzy_512x512.png new file mode 100644 index 00000000000..5b77c8b5892 Binary files /dev/null and b/apps/gauzy/src/assets/images/logos/logo_Gauzy_512x512.png differ diff --git a/apps/gauzy/src/assets/images/logos/logo_Gauzy_Old.svg b/apps/gauzy/src/assets/images/logos/logo_Gauzy_Old.svg deleted file mode 100644 index 45e72a0af4f..00000000000 --- a/apps/gauzy/src/assets/images/logos/logo_Gauzy_Old.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - Group - Created with Sketch. - - - - - - - - - - - - - - \ No newline at end of file diff --git a/apps/gauzy/src/assets/images/logos/logo_Gauzy_large.png b/apps/gauzy/src/assets/images/logos/logo_Gauzy_large.png new file mode 100644 index 00000000000..ef6f3c0f74b Binary files /dev/null and b/apps/gauzy/src/assets/images/logos/logo_Gauzy_large.png differ diff --git a/apps/gauzy/src/assets/images/logos/logo_Gauzy_white.png b/apps/gauzy/src/assets/images/logos/logo_Gauzy_white.png deleted file mode 100644 index 03848490d8c..00000000000 Binary files a/apps/gauzy/src/assets/images/logos/logo_Gauzy_white.png and /dev/null differ diff --git a/docker-compose.demo.yml b/docker-compose.demo.yml index 739503eb70f..163b6c99dc4 100644 --- a/docker-compose.demo.yml +++ b/docker-compose.demo.yml @@ -1,341 +1,350 @@ version: '3.8' services: - db: - image: postgres:15-alpine - container_name: db - restart: always - environment: - POSTGRES_DB: ${DB_NAME:-gauzy} - POSTGRES_USER: ${DB_USER:-postgres} - POSTGRES_PASSWORD: ${DB_PASS:-gauzy_password} - healthcheck: - test: - [ - 'CMD-SHELL', - 'psql postgres://$${POSTGRES_USER}:$${POSTGRES_PASSWORD}@localhost:5432/$${POSTGRES_DB} || exit 1', - ] - volumes: - - postgres_data:/var/lib/postgresql/data/ - - ./.deploy/db/init-user-db.sh:/docker-entrypoint-initdb.d/init-user-db.sh - ports: - - '5432:5432' - networks: - - overlay + db: + image: postgres:15-alpine + container_name: db + restart: always + environment: + POSTGRES_DB: ${DB_NAME:-gauzy} + POSTGRES_USER: ${DB_USER:-postgres} + POSTGRES_PASSWORD: ${DB_PASS:-gauzy_password} + healthcheck: + test: + [ + 'CMD-SHELL', + 'psql postgres://$${POSTGRES_USER}:$${POSTGRES_PASSWORD}@localhost:5432/$${POSTGRES_DB} || exit 1' + ] + volumes: + - postgres_data:/var/lib/postgresql/data/ + - ./.deploy/db/init-user-db.sh:/docker-entrypoint-initdb.d/init-user-db.sh + ports: + - '5432:5432' + networks: + - overlay - cube: - image: cubejs/cube:latest - container_name: cube - ports: - - '4000:4000' # Cube Playground - - '5430:5430' # Port for Cube SQL - environment: - CUBEJS_DEV_MODE: 'true' - CUBEJS_DB_TYPE: postgres - CUBEJS_DB_HOST: db - CUBEJS_DB_PORT: 5432 - CUBEJS_DB_NAME: ${DB_NAME:-gauzy} - CUBEJS_DB_USER: ${DB_USER:-postgres} - CUBEJS_DB_PASS: ${DB_PASS:-gauzy_password} - # Credentials to connect to Cube SQL APIs - CUBEJS_PG_SQL_PORT: 5430 - CUBEJS_SQL_USER: ${CUBE_USER:-cube_user} - CUBEJS_SQL_PASSWORD: ${CUBE_PASS:-cube_pass} - volumes: - - 'cube_data:/cube/conf' - links: - - db - networks: - - overlay + cube: + image: cubejs/cube:latest + container_name: cube + ports: + - '4000:4000' # Cube Playground + - '5430:5430' # Port for Cube SQL + environment: + CUBEJS_DEV_MODE: 'true' + CUBEJS_DB_TYPE: postgres + CUBEJS_DB_HOST: db + CUBEJS_DB_PORT: 5432 + CUBEJS_DB_NAME: ${DB_NAME:-gauzy} + CUBEJS_DB_USER: ${DB_USER:-postgres} + CUBEJS_DB_PASS: ${DB_PASS:-gauzy_password} + # Credentials to connect to Cube SQL APIs + CUBEJS_PG_SQL_PORT: 5430 + CUBEJS_SQL_USER: ${CUBE_USER:-cube_user} + CUBEJS_SQL_PASSWORD: ${CUBE_PASS:-cube_pass} + volumes: + - 'cube_data:/cube/conf' + links: + - db + networks: + - overlay - jitsu: - container_name: jitsu - image: jitsucom/jitsu:latest - extra_hosts: - - 'host.docker.internal:host-gateway' - environment: - - REDIS_URL=redis://redis:6379 - # Retroactive users recognition can affect RAM significant. - # Read more about the solution https://jitsu.com/docs/other-features/retroactive-user-recognition - - USER_RECOGNITION_ENABLED=true - - USER_RECOGNITION_REDIS_URL=redis://jitsu_redis_users_recognition:6380 - - TERM=xterm-256color - depends_on: - redis: - condition: service_healthy - jitsu_redis_users_recognition: - condition: service_healthy - volumes: - - ./.deploy/jitsu/configurator/data/logs:/home/configurator/data/logs - - ./.deploy/jitsu/server/data/logs:/home/eventnative/data/logs - - ./.deploy/jitsu/server/data/logs/events:/home/eventnative/data/logs/events - - /var/run/docker.sock:/var/run/docker.sock - - jitsu_workspace:/home/eventnative/data/airbyte - restart: always - ports: - - '8000:8000' - networks: - - overlay + jitsu: + container_name: jitsu + image: jitsucom/jitsu:latest + extra_hosts: + - 'host.docker.internal:host-gateway' + environment: + - REDIS_URL=redis://redis:6379 + # Retroactive users recognition can affect RAM significant. + # Read more about the solution https://jitsu.com/docs/other-features/retroactive-user-recognition + - USER_RECOGNITION_ENABLED=true + - USER_RECOGNITION_REDIS_URL=redis://jitsu_redis_users_recognition:6380 + - TERM=xterm-256color + depends_on: + redis: + condition: service_healthy + jitsu_redis_users_recognition: + condition: service_healthy + volumes: + - ./.deploy/jitsu/configurator/data/logs:/home/configurator/data/logs + - ./.deploy/jitsu/server/data/logs:/home/eventnative/data/logs + - ./.deploy/jitsu/server/data/logs/events:/home/eventnative/data/logs/events + - /var/run/docker.sock:/var/run/docker.sock + - jitsu_workspace:/home/eventnative/data/airbyte + restart: always + ports: + - '8000:8000' + networks: + - overlay - elasticsearch: - image: 'elasticsearch:7.17.7' - container_name: elasticsearch - volumes: - - elasticsearch_data:/usr/share/elasticsearch/data - environment: - ES_JAVA_OPTS: -Xms512m -Xmx1024m - discovery.type: single-node - http.port: 9200 - http.cors.enabled: 'true' - http.cors.allow-origin: http://localhost:3000,http://127.0.0.1:3000,http://localhost:1358,http://127.0.0.1:1358 - http.cors.allow-headers: X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization - http.cors.allow-credentials: 'true' - bootstrap.memory_lock: 'true' - xpack.security.enabled: 'false' - ports: - - '9200' - - '9300' - ulimits: - memlock: - soft: -1 - hard: -1 - healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:9200/_cat/health'] - interval: 5s - timeout: 5s - retries: 10 - start_period: 20s - networks: - - overlay + elasticsearch: + image: 'elasticsearch:7.17.7' + container_name: elasticsearch + volumes: + - elasticsearch_data:/usr/share/elasticsearch/data + environment: + ES_JAVA_OPTS: -Xms512m -Xmx1024m + discovery.type: single-node + http.port: 9200 + http.cors.enabled: 'true' + http.cors.allow-origin: http://localhost:3000,http://127.0.0.1:3000,http://localhost:1358,http://127.0.0.1:1358 + http.cors.allow-headers: X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization + http.cors.allow-credentials: 'true' + bootstrap.memory_lock: 'true' + xpack.security.enabled: 'false' + ports: + - '9200' + - '9300' + ulimits: + memlock: + soft: -1 + hard: -1 + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:9200/_cat/health'] + interval: 5s + timeout: 5s + retries: 10 + start_period: 20s + networks: + - overlay - # Elasticsearch Management UI - dejavu: - image: appbaseio/dejavu:3.6.0 - container_name: dejavu - ports: - - '1358:1358' - links: - - elasticsearch - networks: - - overlay + # Elasticsearch Management UI + dejavu: + image: appbaseio/dejavu:3.6.0 + container_name: dejavu + ports: + - '1358:1358' + links: + - elasticsearch + networks: + - overlay - # TODO: For now used in Jitsu, but we will need to create another one dedicated for Jitsu later - redis: - image: 'redis:7.0.2-alpine' - container_name: redis - restart: unless-stopped - healthcheck: - test: ['CMD-SHELL', 'redis-cli -h localhost -p 6379 PING'] - interval: 1s - timeout: 30s - ports: - - '6379' - volumes: - - ./.deploy/redis/data:/data - networks: - - overlay + # TODO: For now used in Jitsu, but we will need to create another one dedicated for Jitsu later + redis: + image: 'redis:7.0.2-alpine' + container_name: redis + restart: unless-stopped + healthcheck: + test: ['CMD-SHELL', 'redis-cli -h localhost -p 6379 PING'] + interval: 1s + timeout: 30s + ports: + - '6379' + volumes: + - ./.deploy/redis/data:/data + networks: + - overlay - jitsu_redis_users_recognition: - image: 'redis:7.0.2-alpine' - container_name: jitsu_redis_users_recognition - command: redis-server /usr/local/etc/redis/redis.conf - restart: unless-stopped - healthcheck: - test: ['CMD-SHELL', 'redis-cli -h localhost -p 6380 PING'] - interval: 1s - timeout: 30s - ports: - - '6380' - volumes: - - ./.deploy/redis/jitsu_users_recognition/data:/data - - ./.deploy/redis/jitsu_users_recognition/redis.conf:/usr/local/etc/redis/redis.conf - networks: - - overlay + jitsu_redis_users_recognition: + image: 'redis:7.0.2-alpine' + container_name: jitsu_redis_users_recognition + command: redis-server /usr/local/etc/redis/redis.conf + restart: unless-stopped + healthcheck: + test: ['CMD-SHELL', 'redis-cli -h localhost -p 6380 PING'] + interval: 1s + timeout: 30s + ports: + - '6380' + volumes: + - ./.deploy/redis/jitsu_users_recognition/data:/data + - ./.deploy/redis/jitsu_users_recognition/redis.conf:/usr/local/etc/redis/redis.conf + networks: + - overlay - minio: - restart: unless-stopped - image: quay.io/minio/minio:latest - container_name: minio - volumes: - - minio_data:/data - environment: - MINIO_ROOT_USER: ever-gauzy-access-key - MINIO_ROOT_PASSWORD: ever-gauzy-secret-key - command: server /data --address :9000 --console-address ":9001" - ports: - - 9000:9000 - - 9001:9001 - networks: - - overlay + minio: + restart: unless-stopped + image: quay.io/minio/minio:latest + container_name: minio + volumes: + - minio_data:/data + environment: + MINIO_ROOT_USER: ever-gauzy-access-key + MINIO_ROOT_PASSWORD: ever-gauzy-secret-key + command: server /data --address :9000 --console-address ":9001" + ports: + - 9000:9000 + - 9001:9001 + networks: + - overlay - minio_create_buckets: - image: minio/mc - environment: - MINIO_ROOT_USER: ever-gauzy-access-key - MINIO_ROOT_PASSWORD: ever-gauzy-secret-key - entrypoint: - - '/bin/sh' - - '-c' - command: - - "until (/usr/bin/mc alias set minio http://minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD) do - echo 'Waiting to start minio...' && sleep 1; - done; - /usr/bin/mc mb minio/ever-gauzy --region=eu-north-1; - exit 0;" - depends_on: - - minio - networks: - - overlay + minio_create_buckets: + image: minio/mc + environment: + MINIO_ROOT_USER: ever-gauzy-access-key + MINIO_ROOT_PASSWORD: ever-gauzy-secret-key + entrypoint: + - '/bin/sh' + - '-c' + command: + - "until (/usr/bin/mc alias set minio http://minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD) do + echo 'Waiting to start minio...' && sleep 1; + done; + /usr/bin/mc mb minio/ever-gauzy --region=eu-north-1; + exit 0;" + depends_on: + - minio + networks: + - overlay - pgweb: - image: sosedoff/pgweb - container_name: pgweb - restart: always - depends_on: - - db - links: - - db:${DB_HOST:-db} - environment: - POSTGRES_DB: ${DB_NAME:-gauzy} - POSTGRES_USER: ${DB_USER:-postgres} - POSTGRES_PASSWORD: ${DB_PASS:-gauzy_password} - PGWEB_DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASS:-gauzy_password}@${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-gauzy}?sslmode=disable - ports: - - '8081:8081' - networks: - - overlay + pgweb: + image: sosedoff/pgweb + container_name: pgweb + restart: always + depends_on: + - db + links: + - db:${DB_HOST:-db} + environment: + POSTGRES_DB: ${DB_NAME:-gauzy} + POSTGRES_USER: ${DB_USER:-postgres} + POSTGRES_PASSWORD: ${DB_PASS:-gauzy_password} + PGWEB_DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASS:-gauzy_password}@${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-gauzy}?sslmode=disable + ports: + - '8081:8081' + networks: + - overlay - api: - container_name: api - image: ghcr.io/ever-co/gauzy-api:latest - environment: - API_HOST: ${API_HOST:-api} - API_PORT: ${API_PORT:-3000} - NODE_ENV: ${NODE_ENV:-development} - DB_HOST: db - API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} - CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} - SENTRY_DSN: ${SENTRY_DSN:-} - SENTRY_HTTP_TRACING_ENABLED: ${SENTRY_HTTP_TRACING_ENABLED:-} - SENTRY_POSTGRES_TRACKING_ENABLED: ${SENTRY_POSTGRES_TRACKING_ENABLED:-} - JITSU_SERVER_URL: ${JITSU_SERVER_URL:-} - JITSU_SERVER_WRITE_KEY: ${JITSU_SERVER_WRITE_KEY:-} - GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} - GAUZY_GITHUB_CLIENT_SECRET: ${GAUZY_GITHUB_CLIENT_SECRET:-} - GAUZY_GITHUB_WEBHOOK_URL: ${GAUZY_GITHUB_WEBHOOK_URL:-} - GAUZY_GITHUB_WEBHOOK_SECRET: ${GAUZY_GITHUB_WEBHOOK_SECRET:-} - GAUZY_GITHUB_APP_PRIVATE_KEY: ${GAUZY_GITHUB_APP_PRIVATE_KEY:-} - GAUZY_GITHUB_APP_ID: ${GAUZY_GITHUB_APP_ID:-} - GAUZY_GITHUB_APP_NAME: ${GAUZY_GITHUB_APP_NAME:-} - GAUZY_GITHUB_POST_INSTALL_URL: ${GAUZY_GITHUB_POST_INSTALL_URL:-} - GAUZY_GITHUB_OAUTH_CLIENT_ID: ${GAUZY_GITHUB_OAUTH_CLIENT_ID:-} - GAUZY_GITHUB_OAUTH_CLIENT_SECRET: ${GAUZY_GITHUB_OAUTH_CLIENT_SECRET:-} - GAUZY_GITHUB_OAUTH_CALLBACK_URL: ${GAUZY_GITHUB_OAUTH_CALLBACK_URL:-} + api: + container_name: api + image: ghcr.io/ever-co/gauzy-api:latest + environment: + API_HOST: ${API_HOST:-api} + API_PORT: ${API_PORT:-3000} + NODE_ENV: ${NODE_ENV:-development} + DB_HOST: db + API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} + CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} + SENTRY_DSN: ${SENTRY_DSN:-} + SENTRY_HTTP_TRACING_ENABLED: ${SENTRY_HTTP_TRACING_ENABLED:-} + SENTRY_POSTGRES_TRACKING_ENABLED: ${SENTRY_POSTGRES_TRACKING_ENABLED:-} + JITSU_SERVER_URL: ${JITSU_SERVER_URL:-} + JITSU_SERVER_WRITE_KEY: ${JITSU_SERVER_WRITE_KEY:-} + GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} + GAUZY_GITHUB_CLIENT_SECRET: ${GAUZY_GITHUB_CLIENT_SECRET:-} + GAUZY_GITHUB_WEBHOOK_URL: ${GAUZY_GITHUB_WEBHOOK_URL:-} + GAUZY_GITHUB_WEBHOOK_SECRET: ${GAUZY_GITHUB_WEBHOOK_SECRET:-} + GAUZY_GITHUB_APP_PRIVATE_KEY: ${GAUZY_GITHUB_APP_PRIVATE_KEY:-} + GAUZY_GITHUB_APP_ID: ${GAUZY_GITHUB_APP_ID:-} + GAUZY_GITHUB_APP_NAME: ${GAUZY_GITHUB_APP_NAME:-} + GAUZY_GITHUB_POST_INSTALL_URL: ${GAUZY_GITHUB_POST_INSTALL_URL:-} + GAUZY_GITHUB_OAUTH_CLIENT_ID: ${GAUZY_GITHUB_OAUTH_CLIENT_ID:-} + GAUZY_GITHUB_OAUTH_CLIENT_SECRET: ${GAUZY_GITHUB_OAUTH_CLIENT_SECRET:-} + GAUZY_GITHUB_OAUTH_CALLBACK_URL: ${GAUZY_GITHUB_OAUTH_CALLBACK_URL:-} + MAGIC_CODE_EXPIRATION_TIME: ${MAGIC_CODE_EXPIRATION_TIME:-} + APP_NAME: ${APP_NAME:-} + APP_LOGO: ${APP_LOGO:-} + APP_SIGNATURE: ${APP_SIGNATURE:-} + APP_LINK: ${APP_LINK:-} + APP_EMAIL_CONFIRMATION_URL: ${APP_EMAIL_CONFIRMATION_URL:-} + APP_MAGIC_SIGN_URL: ${APP_MAGIC_SIGN_URL:-} + COMPANY_LINK: ${COMPANY_LINK:-} + COMPANY_NAME: ${COMPANY_NAME:-} - env_file: - - .env.compose - entrypoint: './entrypoint.compose.sh' - command: ['node', 'main.js'] - restart: on-failure - depends_on: - db: - condition: service_healthy - redis: - condition: service_started - minio: - condition: service_started - minio_create_buckets: - condition: service_started - elasticsearch: - condition: service_healthy - cube: - condition: service_started - links: - - db:${DB_HOST:-db} - - cube:${CUBE_HOST:-cube} - - redis:${REDIS_HOST:-redis} - - minio:${MINIO_HOST:-minio} - - elasticsearch:${ES_HOST:-elasticsearch} - # volumes: - # - webapp_node_modules:/srv/gauzy/node_modules - # - api_node_modules:/srv/gauzy/apps/api/node_modules - ports: - - '3000:${API_PORT:-3000}' - networks: - - overlay + env_file: + - .env.compose + entrypoint: './entrypoint.compose.sh' + command: ['node', 'main.js'] + restart: on-failure + depends_on: + db: + condition: service_healthy + redis: + condition: service_started + minio: + condition: service_started + minio_create_buckets: + condition: service_started + elasticsearch: + condition: service_healthy + cube: + condition: service_started + links: + - db:${DB_HOST:-db} + - cube:${CUBE_HOST:-cube} + - redis:${REDIS_HOST:-redis} + - minio:${MINIO_HOST:-minio} + - elasticsearch:${ES_HOST:-elasticsearch} + # volumes: + # - webapp_node_modules:/srv/gauzy/node_modules + # - api_node_modules:/srv/gauzy/apps/api/node_modules + ports: + - '3000:${API_PORT:-3000}' + networks: + - overlay - webapp: - container_name: webapp - image: ghcr.io/ever-co/gauzy-webapp:latest - environment: - WEB_HOST: ${WEB_HOST:-webapp} - WEB_PORT: ${WEB_PORT:-4200} - NODE_ENV: ${NODE_ENV:-development} - API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} - CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} - SENTRY_DSN: ${SENTRY_DSN:-} - SENTRY_TRACES_SAMPLE_RATE: ${SENTRY_TRACES_SAMPLE_RATE:-0.1} - CHATWOOT_SDK_TOKEN: ${CHATWOOT_SDK_TOKEN:-} - CLOUDINARY_CLOUD_NAME: ${CLOUDINARY_CLOUD_NAME:-} - CLOUDINARY_API_KEY: ${CLOUDINARY_API_KEY:-} - GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY:-} - GOOGLE_PLACE_AUTOCOMPLETE: ${GOOGLE_PLACE_AUTOCOMPLETE:-false} - DEFAULT_LATITUDE: ${DEFAULT_LATITUDE:-42.6459136} - DEFAULT_LONGITUDE: ${DEFAULT_LONGITUDE:-23.3332736} - DEFAULT_CURRENCY: ${DEFAULT_CURRENCY:-USD} - GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} - GAUZY_GITHUB_APP_NAME: ${GAUZY_GITHUB_APP_NAME:-} - GAUZY_GITHUB_REDIRECT_URL: ${GAUZY_GITHUB_REDIRECT_URL:-} - GAUZY_GITHUB_POST_INSTALL_URL: ${GAUZY_GITHUB_POST_INSTALL_URL:-} - GAUZY_GITHUB_APP_ID: ${GAUZY_GITHUB_APP_ID:-} - JITSU_BROWSER_URL: ${JITSU_BROWSER_URL:-} - JITSU_BROWSER_WRITE_KEY: ${JITSU_BROWSER_WRITE_KEY:-} - DEMO: 'true' - API_HOST: ${API_HOST:-api} - API_PORT: ${API_PORT:-3000} - entrypoint: './entrypoint.compose.sh' - command: ['nginx', '-g', 'daemon off;'] - env_file: - - .env.compose - restart: on-failure - links: - - db:${DB_HOST:-db} - - api:${API_HOST:-api} - - cube:${CUBE_HOST:-cube} - - redis:${REDIS_HOST:-redis} - - minio:${MINIO_HOST:-minio} - - elasticsearch:${ES_HOST:-elasticsearch} - depends_on: - db: - condition: service_healthy - redis: - condition: service_started - minio: - condition: service_started - minio_create_buckets: - condition: service_started - elasticsearch: - condition: service_healthy - api: - condition: service_started - # volumes: - # - webapp_node_modules:/srv/gauzy/node_modules - ports: - - '4200:${UI_PORT:-4200}' - networks: - - overlay + webapp: + container_name: webapp + image: ghcr.io/ever-co/gauzy-webapp:latest + environment: + WEB_HOST: ${WEB_HOST:-webapp} + WEB_PORT: ${WEB_PORT:-4200} + NODE_ENV: ${NODE_ENV:-development} + API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} + CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} + SENTRY_DSN: ${SENTRY_DSN:-} + SENTRY_TRACES_SAMPLE_RATE: ${SENTRY_TRACES_SAMPLE_RATE:-0.1} + CHATWOOT_SDK_TOKEN: ${CHATWOOT_SDK_TOKEN:-} + CLOUDINARY_CLOUD_NAME: ${CLOUDINARY_CLOUD_NAME:-} + CLOUDINARY_API_KEY: ${CLOUDINARY_API_KEY:-} + GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY:-} + GOOGLE_PLACE_AUTOCOMPLETE: ${GOOGLE_PLACE_AUTOCOMPLETE:-false} + DEFAULT_LATITUDE: ${DEFAULT_LATITUDE:-42.6459136} + DEFAULT_LONGITUDE: ${DEFAULT_LONGITUDE:-23.3332736} + DEFAULT_CURRENCY: ${DEFAULT_CURRENCY:-USD} + GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} + GAUZY_GITHUB_APP_NAME: ${GAUZY_GITHUB_APP_NAME:-} + GAUZY_GITHUB_REDIRECT_URL: ${GAUZY_GITHUB_REDIRECT_URL:-} + GAUZY_GITHUB_POST_INSTALL_URL: ${GAUZY_GITHUB_POST_INSTALL_URL:-} + GAUZY_GITHUB_APP_ID: ${GAUZY_GITHUB_APP_ID:-} + JITSU_BROWSER_URL: ${JITSU_BROWSER_URL:-} + JITSU_BROWSER_WRITE_KEY: ${JITSU_BROWSER_WRITE_KEY:-} + DEMO: 'true' + API_HOST: ${API_HOST:-api} + API_PORT: ${API_PORT:-3000} + entrypoint: './entrypoint.compose.sh' + command: ['nginx', '-g', 'daemon off;'] + env_file: + - .env.compose + restart: on-failure + links: + - db:${DB_HOST:-db} + - api:${API_HOST:-api} + - cube:${CUBE_HOST:-cube} + - redis:${REDIS_HOST:-redis} + - minio:${MINIO_HOST:-minio} + - elasticsearch:${ES_HOST:-elasticsearch} + depends_on: + db: + condition: service_healthy + redis: + condition: service_started + minio: + condition: service_started + minio_create_buckets: + condition: service_started + elasticsearch: + condition: service_healthy + api: + condition: service_started + # volumes: + # - webapp_node_modules:/srv/gauzy/node_modules + ports: + - '4200:${UI_PORT:-4200}' + networks: + - overlay volumes: - # webapp_node_modules: - # api_node_modules: - redis_data: {} - postgres_data: {} - elasticsearch_data: {} - minio_data: {} - cube_data: {} - certificates: {} - jitsu_workspace: {} + # webapp_node_modules: + # api_node_modules: + redis_data: {} + postgres_data: {} + elasticsearch_data: {} + minio_data: {} + cube_data: {} + certificates: {} + jitsu_workspace: {} networks: - overlay: - driver: bridge + overlay: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml index 68ab8e70d22..0772e830cf4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,375 +1,384 @@ version: '3.8' services: - db: - image: postgres:15-alpine - container_name: db - restart: always - environment: - POSTGRES_DB: ${DB_NAME:-gauzy} - POSTGRES_USER: ${DB_USER:-postgres} - POSTGRES_PASSWORD: ${DB_PASS:-gauzy_password} - healthcheck: - test: - [ - 'CMD-SHELL', - 'psql postgres://$${POSTGRES_USER}:$${POSTGRES_PASSWORD}@localhost:5432/$${POSTGRES_DB} || exit 1', - ] - volumes: - - postgres_data:/var/lib/postgresql/data/ - - ./.deploy/db/init-user-db.sh:/docker-entrypoint-initdb.d/init-user-db.sh - ports: - - '5432:5432' - networks: - - overlay + db: + image: postgres:15-alpine + container_name: db + restart: always + environment: + POSTGRES_DB: ${DB_NAME:-gauzy} + POSTGRES_USER: ${DB_USER:-postgres} + POSTGRES_PASSWORD: ${DB_PASS:-gauzy_password} + healthcheck: + test: + [ + 'CMD-SHELL', + 'psql postgres://$${POSTGRES_USER}:$${POSTGRES_PASSWORD}@localhost:5432/$${POSTGRES_DB} || exit 1' + ] + volumes: + - postgres_data:/var/lib/postgresql/data/ + - ./.deploy/db/init-user-db.sh:/docker-entrypoint-initdb.d/init-user-db.sh + ports: + - '5432:5432' + networks: + - overlay - cube: - image: cubejs/cube:latest - container_name: cube - ports: - - '4000:4000' # Cube Playground - - '5430:5430' # Port for Cube SQL - environment: - CUBEJS_DEV_MODE: 'true' - CUBEJS_DB_TYPE: postgres - CUBEJS_DB_HOST: db - CUBEJS_DB_PORT: 5432 - CUBEJS_DB_NAME: ${DB_NAME:-gauzy} - CUBEJS_DB_USER: ${DB_USER:-postgres} - CUBEJS_DB_PASS: ${DB_PASS:-gauzy_password} - # Credentials to connect to Cube SQL APIs - CUBEJS_PG_SQL_PORT: 5430 - CUBEJS_SQL_USER: ${CUBE_USER:-cube_user} - CUBEJS_SQL_PASSWORD: ${CUBE_PASS:-cube_pass} - volumes: - - 'cube_data:/cube/conf' - links: - - db - networks: - - overlay + cube: + image: cubejs/cube:latest + container_name: cube + ports: + - '4000:4000' # Cube Playground + - '5430:5430' # Port for Cube SQL + environment: + CUBEJS_DEV_MODE: 'true' + CUBEJS_DB_TYPE: postgres + CUBEJS_DB_HOST: db + CUBEJS_DB_PORT: 5432 + CUBEJS_DB_NAME: ${DB_NAME:-gauzy} + CUBEJS_DB_USER: ${DB_USER:-postgres} + CUBEJS_DB_PASS: ${DB_PASS:-gauzy_password} + # Credentials to connect to Cube SQL APIs + CUBEJS_PG_SQL_PORT: 5430 + CUBEJS_SQL_USER: ${CUBE_USER:-cube_user} + CUBEJS_SQL_PASSWORD: ${CUBE_PASS:-cube_pass} + volumes: + - 'cube_data:/cube/conf' + links: + - db + networks: + - overlay - jitsu: - container_name: jitsu - image: jitsucom/jitsu:latest - extra_hosts: - - 'host.docker.internal:host-gateway' - environment: - - REDIS_URL=redis://redis:6379 - # Retroactive users recognition can affect RAM significant. - # Read more about the solution https://jitsu.com/docs/other-features/retroactive-user-recognition - - USER_RECOGNITION_ENABLED=true - - USER_RECOGNITION_REDIS_URL=redis://jitsu_redis_users_recognition:6380 - - TERM=xterm-256color - depends_on: - redis: - condition: service_healthy - jitsu_redis_users_recognition: - condition: service_healthy - volumes: - - ./.deploy/jitsu/configurator/data/logs:/home/configurator/data/logs - - ./.deploy/jitsu/server/data/logs:/home/eventnative/data/logs - - ./.deploy/jitsu/server/data/logs/events:/home/eventnative/data/logs/events - - /var/run/docker.sock:/var/run/docker.sock - - jitsu_workspace:/home/eventnative/data/airbyte - restart: always - ports: - - '8000:8000' - networks: - - overlay + jitsu: + container_name: jitsu + image: jitsucom/jitsu:latest + extra_hosts: + - 'host.docker.internal:host-gateway' + environment: + - REDIS_URL=redis://redis:6379 + # Retroactive users recognition can affect RAM significant. + # Read more about the solution https://jitsu.com/docs/other-features/retroactive-user-recognition + - USER_RECOGNITION_ENABLED=true + - USER_RECOGNITION_REDIS_URL=redis://jitsu_redis_users_recognition:6380 + - TERM=xterm-256color + depends_on: + redis: + condition: service_healthy + jitsu_redis_users_recognition: + condition: service_healthy + volumes: + - ./.deploy/jitsu/configurator/data/logs:/home/configurator/data/logs + - ./.deploy/jitsu/server/data/logs:/home/eventnative/data/logs + - ./.deploy/jitsu/server/data/logs/events:/home/eventnative/data/logs/events + - /var/run/docker.sock:/var/run/docker.sock + - jitsu_workspace:/home/eventnative/data/airbyte + restart: always + ports: + - '8000:8000' + networks: + - overlay - elasticsearch: - image: 'elasticsearch:7.17.7' - container_name: elasticsearch - volumes: - - elasticsearch_data:/usr/share/elasticsearch/data - environment: - ES_JAVA_OPTS: -Xms512m -Xmx1024m - discovery.type: single-node - http.port: 9200 - http.cors.enabled: 'true' - http.cors.allow-origin: http://localhost:3000,http://127.0.0.1:3000,http://localhost:1358,http://127.0.0.1:1358 - http.cors.allow-headers: X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization - http.cors.allow-credentials: 'true' - bootstrap.memory_lock: 'true' - xpack.security.enabled: 'false' - ports: - - '9200' - - '9300' - ulimits: - memlock: - soft: -1 - hard: -1 - healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:9200/_cat/health'] - interval: 5s - timeout: 5s - retries: 10 - start_period: 20s - networks: - - overlay + elasticsearch: + image: 'elasticsearch:7.17.7' + container_name: elasticsearch + volumes: + - elasticsearch_data:/usr/share/elasticsearch/data + environment: + ES_JAVA_OPTS: -Xms512m -Xmx1024m + discovery.type: single-node + http.port: 9200 + http.cors.enabled: 'true' + http.cors.allow-origin: http://localhost:3000,http://127.0.0.1:3000,http://localhost:1358,http://127.0.0.1:1358 + http.cors.allow-headers: X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization + http.cors.allow-credentials: 'true' + bootstrap.memory_lock: 'true' + xpack.security.enabled: 'false' + ports: + - '9200' + - '9300' + ulimits: + memlock: + soft: -1 + hard: -1 + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:9200/_cat/health'] + interval: 5s + timeout: 5s + retries: 10 + start_period: 20s + networks: + - overlay - # Elasticsearch Management UI - dejavu: - image: appbaseio/dejavu:3.6.0 - container_name: dejavu - ports: - - '1358:1358' - links: - - elasticsearch - networks: - - overlay + # Elasticsearch Management UI + dejavu: + image: appbaseio/dejavu:3.6.0 + container_name: dejavu + ports: + - '1358:1358' + links: + - elasticsearch + networks: + - overlay - # TODO: For now used in Jitsu, but we will need to create another one dedicated for Jitsu later - redis: - image: 'redis:7.0.2-alpine' - container_name: redis - restart: unless-stopped - healthcheck: - test: ['CMD-SHELL', 'redis-cli -h localhost -p 6379 PING'] - interval: 1s - timeout: 30s - ports: - - '6379' - volumes: - - ./.deploy/redis/data:/data - networks: - - overlay + # TODO: For now used in Jitsu, but we will need to create another one dedicated for Jitsu later + redis: + image: 'redis:7.0.2-alpine' + container_name: redis + restart: unless-stopped + healthcheck: + test: ['CMD-SHELL', 'redis-cli -h localhost -p 6379 PING'] + interval: 1s + timeout: 30s + ports: + - '6379' + volumes: + - ./.deploy/redis/data:/data + networks: + - overlay - jitsu_redis_users_recognition: - image: 'redis:7.0.2-alpine' - container_name: jitsu_redis_users_recognition - command: redis-server /usr/local/etc/redis/redis.conf - restart: unless-stopped - healthcheck: - test: ['CMD-SHELL', 'redis-cli -h localhost -p 6380 PING'] - interval: 1s - timeout: 30s - ports: - - '6380' - volumes: - - ./.deploy/redis/jitsu_users_recognition/data:/data - - ./.deploy/redis/jitsu_users_recognition/redis.conf:/usr/local/etc/redis/redis.conf - networks: - - overlay + jitsu_redis_users_recognition: + image: 'redis:7.0.2-alpine' + container_name: jitsu_redis_users_recognition + command: redis-server /usr/local/etc/redis/redis.conf + restart: unless-stopped + healthcheck: + test: ['CMD-SHELL', 'redis-cli -h localhost -p 6380 PING'] + interval: 1s + timeout: 30s + ports: + - '6380' + volumes: + - ./.deploy/redis/jitsu_users_recognition/data:/data + - ./.deploy/redis/jitsu_users_recognition/redis.conf:/usr/local/etc/redis/redis.conf + networks: + - overlay - minio: - restart: unless-stopped - image: quay.io/minio/minio:latest - container_name: minio - volumes: - - minio_data:/data - environment: - MINIO_ROOT_USER: ever-gauzy-access-key - MINIO_ROOT_PASSWORD: ever-gauzy-secret-key - command: server /data --address :9000 --console-address ":9001" - ports: - - 9000:9000 - - 9001:9001 - networks: - - overlay + minio: + restart: unless-stopped + image: quay.io/minio/minio:latest + container_name: minio + volumes: + - minio_data:/data + environment: + MINIO_ROOT_USER: ever-gauzy-access-key + MINIO_ROOT_PASSWORD: ever-gauzy-secret-key + command: server /data --address :9000 --console-address ":9001" + ports: + - 9000:9000 + - 9001:9001 + networks: + - overlay - minio_create_buckets: - image: minio/mc - environment: - MINIO_ROOT_USER: ever-gauzy-access-key - MINIO_ROOT_PASSWORD: ever-gauzy-secret-key - entrypoint: - - '/bin/sh' - - '-c' - command: - - "until (/usr/bin/mc alias set minio http://minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD) do - echo 'Waiting to start minio...' && sleep 1; - done; - /usr/bin/mc mb minio/ever-gauzy --region=eu-north-1; - exit 0;" - depends_on: - - minio - networks: - - overlay + minio_create_buckets: + image: minio/mc + environment: + MINIO_ROOT_USER: ever-gauzy-access-key + MINIO_ROOT_PASSWORD: ever-gauzy-secret-key + entrypoint: + - '/bin/sh' + - '-c' + command: + - "until (/usr/bin/mc alias set minio http://minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD) do + echo 'Waiting to start minio...' && sleep 1; + done; + /usr/bin/mc mb minio/ever-gauzy --region=eu-north-1; + exit 0;" + depends_on: + - minio + networks: + - overlay - pgweb: - image: sosedoff/pgweb - container_name: pgweb - restart: always - depends_on: - - db - links: - - db:${DB_HOST:-db} - environment: - POSTGRES_DB: ${DB_NAME:-gauzy} - POSTGRES_USER: ${DB_USER:-postgres} - POSTGRES_PASSWORD: ${DB_PASS:-gauzy_password} - PGWEB_DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASS:-gauzy_password}@${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-gauzy}?sslmode=disable - ports: - - '8081:8081' - networks: - - overlay + pgweb: + image: sosedoff/pgweb + container_name: pgweb + restart: always + depends_on: + - db + links: + - db:${DB_HOST:-db} + environment: + POSTGRES_DB: ${DB_NAME:-gauzy} + POSTGRES_USER: ${DB_USER:-postgres} + POSTGRES_PASSWORD: ${DB_PASS:-gauzy_password} + PGWEB_DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASS:-gauzy_password}@${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-gauzy}?sslmode=disable + ports: + - '8081:8081' + networks: + - overlay - api: - container_name: api - image: gauzy-api:latest - build: - context: . - dockerfile: .deploy/api/Dockerfile - args: - NODE_ENV: ${NODE_ENV:-development} - API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} - CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} - environment: - API_HOST: ${API_HOST:-api} - API_PORT: ${API_PORT:-3000} - NODE_ENV: ${NODE_ENV:-development} - DB_HOST: db - API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} - CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} - SENTRY_DSN: ${SENTRY_DSN:-} - SENTRY_HTTP_TRACING_ENABLED: ${SENTRY_HTTP_TRACING_ENABLED:-} - SENTRY_POSTGRES_TRACKING_ENABLED: ${SENTRY_POSTGRES_TRACKING_ENABLED:-} - JITSU_SERVER_URL: ${JITSU_SERVER_URL:-} - JITSU_SERVER_WRITE_KEY: ${JITSU_SERVER_WRITE_KEY:-} - GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} - GAUZY_GITHUB_CLIENT_SECRET: ${GAUZY_GITHUB_CLIENT_SECRET:-} - GAUZY_GITHUB_WEBHOOK_URL: ${GAUZY_GITHUB_WEBHOOK_URL:-} - GAUZY_GITHUB_WEBHOOK_SECRET: ${GAUZY_GITHUB_WEBHOOK_SECRET:-} - GAUZY_GITHUB_APP_PRIVATE_KEY: ${GAUZY_GITHUB_APP_PRIVATE_KEY:-} - GAUZY_GITHUB_APP_ID: ${GAUZY_GITHUB_APP_ID:-} - GAUZY_GITHUB_APP_NAME: ${GAUZY_GITHUB_APP_NAME:-} - GAUZY_GITHUB_POST_INSTALL_URL: ${GAUZY_GITHUB_POST_INSTALL_URL:-} - GAUZY_GITHUB_OAUTH_CLIENT_ID: ${GAUZY_GITHUB_OAUTH_CLIENT_ID:-} - GAUZY_GITHUB_OAUTH_CLIENT_SECRET: ${GAUZY_GITHUB_OAUTH_CLIENT_SECRET:-} - GAUZY_GITHUB_OAUTH_CALLBACK_URL: ${GAUZY_GITHUB_OAUTH_CALLBACK_URL:-} + api: + container_name: api + image: gauzy-api:latest + build: + context: . + dockerfile: .deploy/api/Dockerfile + args: + NODE_ENV: ${NODE_ENV:-development} + API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} + CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} + environment: + API_HOST: ${API_HOST:-api} + API_PORT: ${API_PORT:-3000} + NODE_ENV: ${NODE_ENV:-development} + DB_HOST: db + API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} + CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} + SENTRY_DSN: ${SENTRY_DSN:-} + SENTRY_HTTP_TRACING_ENABLED: ${SENTRY_HTTP_TRACING_ENABLED:-} + SENTRY_POSTGRES_TRACKING_ENABLED: ${SENTRY_POSTGRES_TRACKING_ENABLED:-} + JITSU_SERVER_URL: ${JITSU_SERVER_URL:-} + JITSU_SERVER_WRITE_KEY: ${JITSU_SERVER_WRITE_KEY:-} + GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} + GAUZY_GITHUB_CLIENT_SECRET: ${GAUZY_GITHUB_CLIENT_SECRET:-} + GAUZY_GITHUB_WEBHOOK_URL: ${GAUZY_GITHUB_WEBHOOK_URL:-} + GAUZY_GITHUB_WEBHOOK_SECRET: ${GAUZY_GITHUB_WEBHOOK_SECRET:-} + GAUZY_GITHUB_APP_PRIVATE_KEY: ${GAUZY_GITHUB_APP_PRIVATE_KEY:-} + GAUZY_GITHUB_APP_ID: ${GAUZY_GITHUB_APP_ID:-} + GAUZY_GITHUB_APP_NAME: ${GAUZY_GITHUB_APP_NAME:-} + GAUZY_GITHUB_POST_INSTALL_URL: ${GAUZY_GITHUB_POST_INSTALL_URL:-} + GAUZY_GITHUB_OAUTH_CLIENT_ID: ${GAUZY_GITHUB_OAUTH_CLIENT_ID:-} + GAUZY_GITHUB_OAUTH_CLIENT_SECRET: ${GAUZY_GITHUB_OAUTH_CLIENT_SECRET:-} + GAUZY_GITHUB_OAUTH_CALLBACK_URL: ${GAUZY_GITHUB_OAUTH_CALLBACK_URL:-} + MAGIC_CODE_EXPIRATION_TIME: ${MAGIC_CODE_EXPIRATION_TIME:-} + APP_NAME: ${APP_NAME:-} + APP_LOGO: ${APP_LOGO:-} + APP_SIGNATURE: ${APP_SIGNATURE:-} + APP_LINK: ${APP_LINK:-} + APP_EMAIL_CONFIRMATION_URL: ${APP_EMAIL_CONFIRMATION_URL:-} + APP_MAGIC_SIGN_URL: ${APP_MAGIC_SIGN_URL:-} + COMPANY_LINK: ${COMPANY_LINK:-} + COMPANY_NAME: ${COMPANY_NAME:-} - env_file: - - .env.compose - entrypoint: './entrypoint.compose.sh' - command: ['node', 'main.js'] - restart: on-failure - depends_on: - db: - condition: service_healthy - redis: - condition: service_started - minio: - condition: service_started - minio_create_buckets: - condition: service_started - elasticsearch: - condition: service_healthy - cube: - condition: service_started - links: - - db:${DB_HOST:-db} - - cube:${CUBE_HOST:-cube} - - redis:${REDIS_HOST:-redis} - - minio:${MINIO_HOST:-minio} - - elasticsearch:${ES_HOST:-elasticsearch} - # volumes: - # - webapp_node_modules:/srv/gauzy/node_modules - # - api_node_modules:/srv/gauzy/apps/api/node_modules - ports: - - '3000:${API_PORT:-3000}' - networks: - - overlay + env_file: + - .env.compose + entrypoint: './entrypoint.compose.sh' + command: ['node', 'main.js'] + restart: on-failure + depends_on: + db: + condition: service_healthy + redis: + condition: service_started + minio: + condition: service_started + minio_create_buckets: + condition: service_started + elasticsearch: + condition: service_healthy + cube: + condition: service_started + links: + - db:${DB_HOST:-db} + - cube:${CUBE_HOST:-cube} + - redis:${REDIS_HOST:-redis} + - minio:${MINIO_HOST:-minio} + - elasticsearch:${ES_HOST:-elasticsearch} + # volumes: + # - webapp_node_modules:/srv/gauzy/node_modules + # - api_node_modules:/srv/gauzy/apps/api/node_modules + ports: + - '3000:${API_PORT:-3000}' + networks: + - overlay - webapp: - container_name: webapp - image: gauzy-webapp:latest - build: - context: . - dockerfile: .deploy/webapp/Dockerfile - args: - NODE_ENV: ${NODE_ENV:-development} - API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} - CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} - SENTRY_DSN: ${SENTRY_DSN:-} - SENTRY_TRACES_SAMPLE_RATE: ${SENTRY_TRACES_SAMPLE_RATE:-0.1} - CHATWOOT_SDK_TOKEN: ${CHATWOOT_SDK_TOKEN:-} - CLOUDINARY_CLOUD_NAME: ${CLOUDINARY_CLOUD_NAME:-} - CLOUDINARY_API_KEY: ${CLOUDINARY_API_KEY:-} - GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY:-} - GOOGLE_PLACE_AUTOCOMPLETE: ${GOOGLE_PLACE_AUTOCOMPLETE:-false} - DEFAULT_LATITUDE: ${DEFAULT_LATITUDE:-42.6459136} - DEFAULT_LONGITUDE: ${DEFAULT_LONGITUDE:-23.3332736} - DEFAULT_CURRENCY: ${DEFAULT_CURRENCY:-USD} - GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} - GAUZY_GITHUB_APP_NAME: ${GAUZY_GITHUB_APP_NAME:-} - GAUZY_GITHUB_REDIRECT_URL: ${GAUZY_GITHUB_REDIRECT_URL:-} - GAUZY_GITHUB_POST_INSTALL_URL: ${GAUZY_GITHUB_POST_INSTALL_URL:-} - GAUZY_GITHUB_APP_ID: ${GAUZY_GITHUB_APP_ID:-} - JITSU_BROWSER_URL: ${JITSU_BROWSER_URL:-} - JITSU_BROWSER_WRITE_KEY: ${JITSU_BROWSER_WRITE_KEY:-} - DEMO: 'true' - API_HOST: ${API_HOST:-api} - API_PORT: ${API_PORT:-3000} - environment: - WEB_HOST: ${WEB_HOST:-webapp} - WEB_PORT: ${WEB_PORT:-4200} - NODE_ENV: ${NODE_ENV:-development} - API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} - CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} - SENTRY_DSN: ${SENTRY_DSN:-} - SENTRY_TRACES_SAMPLE_RATE: ${SENTRY_TRACES_SAMPLE_RATE:-0.1} - CHATWOOT_SDK_TOKEN: ${CHATWOOT_SDK_TOKEN:-} - CLOUDINARY_CLOUD_NAME: ${CLOUDINARY_CLOUD_NAME:-} - CLOUDINARY_API_KEY: ${CLOUDINARY_API_KEY:-} - GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY:-} - GOOGLE_PLACE_AUTOCOMPLETE: ${GOOGLE_PLACE_AUTOCOMPLETE:-false} - DEFAULT_LATITUDE: ${DEFAULT_LATITUDE:-42.6459136} - DEFAULT_LONGITUDE: ${DEFAULT_LONGITUDE:-23.3332736} - DEFAULT_CURRENCY: ${DEFAULT_CURRENCY:-USD} - GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} - GAUZY_GITHUB_APP_NAME: ${GAUZY_GITHUB_APP_NAME:-} - GAUZY_GITHUB_REDIRECT_URL: ${GAUZY_GITHUB_REDIRECT_URL:-} - GAUZY_GITHUB_POST_INSTALL_URL: ${GAUZY_GITHUB_POST_INSTALL_URL:-} - GAUZY_GITHUB_APP_ID: ${GAUZY_GITHUB_APP_ID:-} - JITSU_BROWSER_URL: ${JITSU_BROWSER_URL:-} - JITSU_BROWSER_WRITE_KEY: ${JITSU_BROWSER_WRITE_KEY:-} - DEMO: 'true' - API_HOST: ${API_HOST:-api} - API_PORT: ${API_PORT:-3000} - entrypoint: './entrypoint.compose.sh' - command: ['nginx', '-g', 'daemon off;'] - env_file: - - .env.compose - restart: on-failure - links: - - db:${DB_HOST:-db} - - api:${API_HOST:-api} - - cube:${CUBE_HOST:-cube} - - redis:${REDIS_HOST:-redis} - - minio:${MINIO_HOST:-minio} - - elasticsearch:${ES_HOST:-elasticsearch} - depends_on: - db: - condition: service_healthy - redis: - condition: service_started - minio: - condition: service_started - minio_create_buckets: - condition: service_started - elasticsearch: - condition: service_healthy - api: - condition: service_started - # volumes: - # - webapp_node_modules:/srv/gauzy/node_modules - ports: - - '4200:${UI_PORT:-4200}' - networks: - - overlay + webapp: + container_name: webapp + image: gauzy-webapp:latest + build: + context: . + dockerfile: .deploy/webapp/Dockerfile + args: + NODE_ENV: ${NODE_ENV:-development} + API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} + CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} + SENTRY_DSN: ${SENTRY_DSN:-} + SENTRY_TRACES_SAMPLE_RATE: ${SENTRY_TRACES_SAMPLE_RATE:-0.1} + CHATWOOT_SDK_TOKEN: ${CHATWOOT_SDK_TOKEN:-} + CLOUDINARY_CLOUD_NAME: ${CLOUDINARY_CLOUD_NAME:-} + CLOUDINARY_API_KEY: ${CLOUDINARY_API_KEY:-} + GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY:-} + GOOGLE_PLACE_AUTOCOMPLETE: ${GOOGLE_PLACE_AUTOCOMPLETE:-false} + DEFAULT_LATITUDE: ${DEFAULT_LATITUDE:-42.6459136} + DEFAULT_LONGITUDE: ${DEFAULT_LONGITUDE:-23.3332736} + DEFAULT_CURRENCY: ${DEFAULT_CURRENCY:-USD} + GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} + GAUZY_GITHUB_APP_NAME: ${GAUZY_GITHUB_APP_NAME:-} + GAUZY_GITHUB_REDIRECT_URL: ${GAUZY_GITHUB_REDIRECT_URL:-} + GAUZY_GITHUB_POST_INSTALL_URL: ${GAUZY_GITHUB_POST_INSTALL_URL:-} + GAUZY_GITHUB_APP_ID: ${GAUZY_GITHUB_APP_ID:-} + JITSU_BROWSER_URL: ${JITSU_BROWSER_URL:-} + JITSU_BROWSER_WRITE_KEY: ${JITSU_BROWSER_WRITE_KEY:-} + DEMO: 'true' + API_HOST: ${API_HOST:-api} + API_PORT: ${API_PORT:-3000} + environment: + WEB_HOST: ${WEB_HOST:-webapp} + WEB_PORT: ${WEB_PORT:-4200} + NODE_ENV: ${NODE_ENV:-development} + API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} + CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} + SENTRY_DSN: ${SENTRY_DSN:-} + SENTRY_TRACES_SAMPLE_RATE: ${SENTRY_TRACES_SAMPLE_RATE:-0.1} + CHATWOOT_SDK_TOKEN: ${CHATWOOT_SDK_TOKEN:-} + CLOUDINARY_CLOUD_NAME: ${CLOUDINARY_CLOUD_NAME:-} + CLOUDINARY_API_KEY: ${CLOUDINARY_API_KEY:-} + GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY:-} + GOOGLE_PLACE_AUTOCOMPLETE: ${GOOGLE_PLACE_AUTOCOMPLETE:-false} + DEFAULT_LATITUDE: ${DEFAULT_LATITUDE:-42.6459136} + DEFAULT_LONGITUDE: ${DEFAULT_LONGITUDE:-23.3332736} + DEFAULT_CURRENCY: ${DEFAULT_CURRENCY:-USD} + GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} + GAUZY_GITHUB_APP_NAME: ${GAUZY_GITHUB_APP_NAME:-} + GAUZY_GITHUB_REDIRECT_URL: ${GAUZY_GITHUB_REDIRECT_URL:-} + GAUZY_GITHUB_POST_INSTALL_URL: ${GAUZY_GITHUB_POST_INSTALL_URL:-} + GAUZY_GITHUB_APP_ID: ${GAUZY_GITHUB_APP_ID:-} + JITSU_BROWSER_URL: ${JITSU_BROWSER_URL:-} + JITSU_BROWSER_WRITE_KEY: ${JITSU_BROWSER_WRITE_KEY:-} + DEMO: 'true' + API_HOST: ${API_HOST:-api} + API_PORT: ${API_PORT:-3000} + entrypoint: './entrypoint.compose.sh' + command: ['nginx', '-g', 'daemon off;'] + env_file: + - .env.compose + restart: on-failure + links: + - db:${DB_HOST:-db} + - api:${API_HOST:-api} + - cube:${CUBE_HOST:-cube} + - redis:${REDIS_HOST:-redis} + - minio:${MINIO_HOST:-minio} + - elasticsearch:${ES_HOST:-elasticsearch} + depends_on: + db: + condition: service_healthy + redis: + condition: service_started + minio: + condition: service_started + minio_create_buckets: + condition: service_started + elasticsearch: + condition: service_healthy + api: + condition: service_started + # volumes: + # - webapp_node_modules:/srv/gauzy/node_modules + ports: + - '4200:${UI_PORT:-4200}' + networks: + - overlay volumes: - # webapp_node_modules: - # api_node_modules: - redis_data: {} - postgres_data: {} - elasticsearch_data: {} - minio_data: {} - cube_data: {} - certificates: {} - jitsu_workspace: {} + # webapp_node_modules: + # api_node_modules: + redis_data: {} + postgres_data: {} + elasticsearch_data: {} + minio_data: {} + cube_data: {} + certificates: {} + jitsu_workspace: {} networks: - overlay: - driver: bridge + overlay: + driver: bridge diff --git a/packages/config/src/environments/environment.prod.ts b/packages/config/src/environments/environment.prod.ts index 9ec7a5230ab..eaa62afae9d 100644 --- a/packages/config/src/environments/environment.prod.ts +++ b/packages/config/src/environments/environment.prod.ts @@ -17,35 +17,28 @@ export const environment: IEnvironment = { envName: 'prod', env: { - LOG_LEVEL: 'debug', + LOG_LEVEL: 'debug' }, EXPRESS_SESSION_SECRET: process.env.EXPRESS_SESSION_SECRET || 'gauzy', USER_PASSWORD_BCRYPT_SALT_ROUNDS: 12, JWT_SECRET: process.env.JWT_SECRET || 'secretKey', - JWT_TOKEN_EXPIRATION_TIME: - parseInt(process.env.JWT_TOKEN_EXPIRATION_TIME) || 86400 * 1, // default JWT token expire time (1 day) + JWT_TOKEN_EXPIRATION_TIME: parseInt(process.env.JWT_TOKEN_EXPIRATION_TIME) || 86400 * 1, // default JWT token expire time (1 day) - JWT_REFRESH_TOKEN_SECRET: - process.env.JWT_REFRESH_TOKEN_SECRET || 'refreshSecretKey', - JWT_REFRESH_TOKEN_EXPIRATION_TIME: - parseInt(process.env.JWT_REFRESH_TOKEN_EXPIRATION_TIME) || 86400 * 7, // default JWT refresh token expire time (7 days) + JWT_REFRESH_TOKEN_SECRET: process.env.JWT_REFRESH_TOKEN_SECRET || 'refreshSecretKey', + JWT_REFRESH_TOKEN_EXPIRATION_TIME: parseInt(process.env.JWT_REFRESH_TOKEN_EXPIRATION_TIME) || 86400 * 7, // default JWT refresh token expire time (7 days) /** * Email verification options */ - JWT_VERIFICATION_TOKEN_SECRET: - process.env.JWT_VERIFICATION_TOKEN_SECRET || 'verificationSecretKey', - JWT_VERIFICATION_TOKEN_EXPIRATION_TIME: - parseInt(process.env.JWT_VERIFICATION_TOKEN_EXPIRATION_TIME) || - 86400 * 7, // default verification expire token time (7 days) + JWT_VERIFICATION_TOKEN_SECRET: process.env.JWT_VERIFICATION_TOKEN_SECRET || 'verificationSecretKey', + JWT_VERIFICATION_TOKEN_EXPIRATION_TIME: parseInt(process.env.JWT_VERIFICATION_TOKEN_EXPIRATION_TIME) || 86400 * 7, // default verification expire token time (7 days) /** * Email Reset */ - EMAIL_RESET_EXPIRATION_TIME: - parseInt(process.env.EMAIL_RESET_EXPIRATION_TIME) || 1800, // default email reset expiration time (30 minutes) + EMAIL_RESET_EXPIRATION_TIME: parseInt(process.env.EMAIL_RESET_EXPIRATION_TIME) || 1800, // default email reset expiration time (30 minutes) /** * Password Less Authentication Configuration @@ -53,8 +46,7 @@ export const environment: IEnvironment = { MAGIC_CODE_EXPIRATION_TIME: parseInt(process.env.MAGIC_CODE_EXPIRATION_TIME) || 60 * 30, // default magic code expire time (30 minutes) /** Organization Team Join Request Configuration **/ - TEAM_JOIN_REQUEST_EXPIRATION_TIME: - parseInt(process.env.TEAM_JOIN_REQUEST_EXPIRATION_TIME) || 60 * 60 * 24, // default code expire time (1 day) + TEAM_JOIN_REQUEST_EXPIRATION_TIME: parseInt(process.env.TEAM_JOIN_REQUEST_EXPIRATION_TIME) || 60 * 60 * 24, // default code expire time (1 day) /** * Throttler (Rate Limiting) Options @@ -69,14 +61,11 @@ export const environment: IEnvironment = { serverHost: process.env.JITSU_SERVER_URL, serverWriteKey: process.env.JITSU_SERVER_WRITE_KEY, debug: process.env.JITSU_SERVER_DEBUG === 'true' ? true : false, - echoEvents: - process.env.JITSU_SERVER_ECHO_EVENTS === 'true' ? true : false, + echoEvents: process.env.JITSU_SERVER_ECHO_EVENTS === 'true' ? true : false }, fileSystem: { - name: - (process.env.FILE_PROVIDER as FileStorageProviderEnum) || - FileStorageProviderEnum.LOCAL, + name: (process.env.FILE_PROVIDER as FileStorageProviderEnum) || FileStorageProviderEnum.LOCAL }, awsConfig: { @@ -84,8 +73,8 @@ export const environment: IEnvironment = { secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, region: process.env.AWS_REGION || 'us-east-1', s3: { - bucket: process.env.AWS_S3_BUCKET || 'gauzy', - }, + bucket: process.env.AWS_S3_BUCKET || 'gauzy' + } }, wasabiConfig: { @@ -94,8 +83,8 @@ export const environment: IEnvironment = { region: process.env.WASABI_REGION || 'us-east-1', serviceUrl: process.env.WASABI_SERVICE_URL || 's3.wasabisys.com', s3: { - bucket: process.env.WASABI_S3_BUCKET || 'gauzy', - }, + bucket: process.env.WASABI_S3_BUCKET || 'gauzy' + } }, /** @@ -106,8 +95,7 @@ export const environment: IEnvironment = { api_key: process.env.CLOUDINARY_API_KEY, api_secret: process.env.CLOUDINARY_API_SECRET, secure: process.env.CLOUDINARY_API_SECURE === 'false' ? false : true, - delivery_url: - process.env.CLOUDINARY_CDN_URL || 'https://res.cloudinary.com', + delivery_url: process.env.CLOUDINARY_CDN_URL || 'https://res.cloudinary.com' }, github: { @@ -117,23 +105,22 @@ export const environment: IEnvironment = { appId: process.env.GAUZY_GITHUB_APP_ID, appName: process.env.GAUZY_GITHUB_APP_NAME, appPrivateKey: process.env.GAUZY_GITHUB_APP_PRIVATE_KEY - ? Buffer.from( - process.env.GAUZY_GITHUB_APP_PRIVATE_KEY, - 'base64' - ).toString('ascii') + ? Buffer.from(process.env.GAUZY_GITHUB_APP_PRIVATE_KEY, 'base64').toString('ascii') : '', /** Github App Post Install Configuration */ - postInstallUrl: process.env.GAUZY_GITHUB_POST_INSTALL_URL || `${process.env.CLIENT_BASE_URL}/#/pages/integrations/github/setup/installation`, + postInstallUrl: + process.env.GAUZY_GITHUB_POST_INSTALL_URL || + `${process.env.CLIENT_BASE_URL}/#/pages/integrations/github/setup/installation`, /** Github Webhook Configuration */ webhookSecret: process.env.GAUZY_GITHUB_WEBHOOK_SECRET, - webhookUrl: process.env.GAUZY_GITHUB_WEBHOOK_URL || `${process.env.API_BASE_URL}/api/integration/github/webhook`, + webhookUrl: process.env.GAUZY_GITHUB_WEBHOOK_URL || `${process.env.API_BASE_URL}/api/integration/github/webhook` }, fiverrConfig: { clientId: process.env.FIVERR_CLIENT_ID, - clientSecret: process.env.FIVERR_CLIENT_SECRET, + clientSecret: process.env.FIVERR_CLIENT_SECRET }, keycloakConfig: { @@ -141,31 +128,27 @@ export const environment: IEnvironment = { clientId: process.env.KEYCLOAK_CLIENT_ID, secret: process.env.KEYCLOAK_SECRET, authServerUrl: process.env.KEYCLOAK_AUTH_SERVER_URL, - cookieKey: process.env.KEYCLOAK_COOKIE_KEY, + cookieKey: process.env.KEYCLOAK_COOKIE_KEY }, auth0Config: { clientID: process.env.AUTH0_CLIENT_ID, clientSecret: process.env.AUTH0_CLIENT_SECRET, - domain: process.env.AUTH0_DOMAIN, + domain: process.env.AUTH0_DOMAIN }, sentry: { - dsn: process.env.SENTRY_DSN, + dsn: process.env.SENTRY_DSN }, - defaultIntegratedUserPass: - process.env.INTEGRATED_USER_DEFAULT_PASS || '123456', + defaultIntegratedUserPass: process.env.INTEGRATED_USER_DEFAULT_PASS || '123456', upwork: { apiKey: process.env.UPWORK_API_KEY, apiSecret: process.env.UPWORK_API_SECRET, - callbackUrl: - process.env.UPWORK_REDIRECT_URL || - `${process.env.API_BASE_URL}/api/integrations/upwork/callback`, + callbackUrl: process.env.UPWORK_REDIRECT_URL || `${process.env.API_BASE_URL}/api/integrations/upwork/callback`, postInstallUrl: - process.env.UPWORK_POST_INSTALL_URL || - `${process.env.CLIENT_BASE_URL}/#/pages/integrations/upwork`, + process.env.UPWORK_POST_INSTALL_URL || `${process.env.CLIENT_BASE_URL}/#/pages/integrations/upwork` }, hubstaff: { @@ -174,14 +157,12 @@ export const environment: IEnvironment = { clientSecret: process.env.HUBSTAFF_CLIENT_SECRET, /** Hubstaff Integration Post Install URL */ postInstallUrl: - process.env.HUBSTAFF_POST_INSTALL_URL || - `${process.env.CLIENT_BASE_URL}/#/pages/integrations/hubstaff`, + process.env.HUBSTAFF_POST_INSTALL_URL || `${process.env.CLIENT_BASE_URL}/#/pages/integrations/hubstaff` }, isElectron: process.env.IS_ELECTRON === 'true' ? true : false, gauzyUserPath: process.env.GAUZY_USER_PATH, - allowSuperAdminRole: - process.env.ALLOW_SUPER_ADMIN_ROLE === 'false' ? false : true, + allowSuperAdminRole: process.env.ALLOW_SUPER_ADMIN_ROLE === 'false' ? false : true, /** * Endpoint for Gauzy AI API (optional), e.g.: http://localhost:3005/graphql @@ -201,25 +182,21 @@ export const environment: IEnvironment = { secure: process.env.MAIL_PORT === '465' ? true : false, // true for 465, false for other ports auth: { user: process.env.MAIL_USERNAME, - pass: process.env.MAIL_PASSWORD, + pass: process.env.MAIL_PASSWORD }, - fromAddress: process.env.MAIL_FROM_ADDRESS, + fromAddress: process.env.MAIL_FROM_ADDRESS }, defaultCurrency: process.env.DEFAULT_CURRENCY || 'USD', unleashConfig: { // if UNLEASH_API_URL is not set / empty or it's a spaces only, we consider UNLEASH disabled - url: process.env.UNLEASH_API_URL - ? process.env.UNLEASH_API_URL.trim() - : '', + url: process.env.UNLEASH_API_URL ? process.env.UNLEASH_API_URL.trim() : '', appName: process.env.UNLEASH_APP_NAME, environment: 'production', instanceId: process.env.UNLEASH_INSTANCE_ID, - refreshInterval: - parseInt(process.env.UNLEASH_REFRESH_INTERVAL) || 15000, - metricsInterval: - parseInt(process.env.UNLEASH_METRICS_INTERVAL) || 60000, - apiKey: process.env.UNLEASH_API_KEY, + refreshInterval: parseInt(process.env.UNLEASH_REFRESH_INTERVAL) || 15000, + metricsInterval: parseInt(process.env.UNLEASH_METRICS_INTERVAL) || 60000, + apiKey: process.env.UNLEASH_API_KEY }, /** @@ -243,119 +220,71 @@ export const environment: IEnvironment = { adminEmail: process.env.DEMO_ADMIN_EMAIL || `local.admin@ever.co`, adminPassword: process.env.DEMO_ADMIN_PASSWORD || `admin`, employeeEmail: process.env.DEMO_EMPLOYEE_EMAIL || `employee@ever.co`, - employeePassword: process.env.DEMO_EMPLOYEE_PASSWORD || `123456`, - }, + employeePassword: process.env.DEMO_EMPLOYEE_PASSWORD || `123456` + } }; export const gauzyToggleFeatures: IGauzyFeatures = { FEATURE_DASHBOARD: process.env.FEATURE_DASHBOARD === 'false' ? false : true, - FEATURE_TIME_TRACKING: - process.env.FEATURE_TIME_TRACKING === 'false' ? false : true, + FEATURE_TIME_TRACKING: process.env.FEATURE_TIME_TRACKING === 'false' ? false : true, FEATURE_ESTIMATE: process.env.FEATURE_ESTIMATE === 'false' ? false : true, - FEATURE_ESTIMATE_RECEIVED: - process.env.FEATURE_ESTIMATE_RECEIVED === 'false' ? false : true, + FEATURE_ESTIMATE_RECEIVED: process.env.FEATURE_ESTIMATE_RECEIVED === 'false' ? false : true, FEATURE_INVOICE: process.env.FEATURE_INVOICE === 'false' ? false : true, - FEATURE_INVOICE_RECURRING: - process.env.FEATURE_INVOICE_RECURRING === 'false' ? false : true, - FEATURE_INVOICE_RECEIVED: - process.env.FEATURE_INVOICE_RECEIVED === 'false' ? false : true, + FEATURE_INVOICE_RECURRING: process.env.FEATURE_INVOICE_RECURRING === 'false' ? false : true, + FEATURE_INVOICE_RECEIVED: process.env.FEATURE_INVOICE_RECEIVED === 'false' ? false : true, FEATURE_INCOME: process.env.FEATURE_INCOME === 'false' ? false : true, FEATURE_EXPENSE: process.env.FEATURE_EXPENSE === 'false' ? false : true, FEATURE_PAYMENT: process.env.FEATURE_PAYMENT === 'false' ? false : true, FEATURE_PROPOSAL: process.env.FEATURE_PROPOSAL === 'false' ? false : true, - FEATURE_PROPOSAL_TEMPLATE: - process.env.FEATURE_PROPOSAL_TEMPLATE === 'false' ? false : true, + FEATURE_PROPOSAL_TEMPLATE: process.env.FEATURE_PROPOSAL_TEMPLATE === 'false' ? false : true, FEATURE_PIPELINE: process.env.FEATURE_PIPELINE === 'false' ? false : true, - FEATURE_PIPELINE_DEAL: - process.env.FEATURE_PIPELINE_DEAL === 'false' ? false : true, - FEATURE_DASHBOARD_TASK: - process.env.FEATURE_DASHBOARD_TASK === 'false' ? false : true, + FEATURE_PIPELINE_DEAL: process.env.FEATURE_PIPELINE_DEAL === 'false' ? false : true, + FEATURE_DASHBOARD_TASK: process.env.FEATURE_DASHBOARD_TASK === 'false' ? false : true, FEATURE_TEAM_TASK: process.env.FEATURE_TEAM_TASK === 'false' ? false : true, FEATURE_MY_TASK: process.env.FEATURE_MY_TASK === 'false' ? false : true, FEATURE_JOB: process.env.FEATURE_JOB === 'false' ? false : true, FEATURE_EMPLOYEES: process.env.FEATURE_EMPLOYEES === 'false' ? false : true, - FEATURE_EMPLOYEE_TIME_ACTIVITY: - process.env.FEATURE_EMPLOYEE_TIME_ACTIVITY === 'false' ? false : true, - FEATURE_EMPLOYEE_TIMESHEETS: - process.env.FEATURE_EMPLOYEE_TIMESHEETS === 'false' ? false : true, - FEATURE_EMPLOYEE_APPOINTMENT: - process.env.FEATURE_EMPLOYEE_APPOINTMENT === 'false' ? false : true, - FEATURE_EMPLOYEE_APPROVAL: - process.env.FEATURE_EMPLOYEE_APPROVAL === 'false' ? false : true, - FEATURE_EMPLOYEE_APPROVAL_POLICY: - process.env.FEATURE_EMPLOYEE_APPROVAL_POLICY === 'false' ? false : true, - FEATURE_EMPLOYEE_LEVEL: - process.env.FEATURE_EMPLOYEE_LEVEL === 'false' ? false : true, - FEATURE_EMPLOYEE_POSITION: - process.env.FEATURE_EMPLOYEE_POSITION === 'false' ? false : true, - FEATURE_EMPLOYEE_TIMEOFF: - process.env.FEATURE_EMPLOYEE_TIMEOFF === 'false' ? false : true, - FEATURE_EMPLOYEE_RECURRING_EXPENSE: - process.env.FEATURE_EMPLOYEE_RECURRING_EXPENSE === 'false' - ? false - : true, - FEATURE_EMPLOYEE_CANDIDATE: - process.env.FEATURE_EMPLOYEE_CANDIDATE === 'false' ? false : true, - FEATURE_MANAGE_INTERVIEW: - process.env.FEATURE_MANAGE_INTERVIEW === 'false' ? false : true, - FEATURE_MANAGE_INVITE: - process.env.FEATURE_MANAGE_INVITE === 'false' ? false : true, - FEATURE_ORGANIZATION: - process.env.FEATURE_ORGANIZATION === 'false' ? false : true, - FEATURE_ORGANIZATION_EQUIPMENT: - process.env.FEATURE_ORGANIZATION_EQUIPMENT === 'false' ? false : true, - FEATURE_ORGANIZATION_INVENTORY: - process.env.FEATURE_ORGANIZATION_INVENTORY === 'false' ? false : true, - FEATURE_ORGANIZATION_TAG: - process.env.FEATURE_ORGANIZATION_TAG === 'false' ? false : true, - FEATURE_ORGANIZATION_VENDOR: - process.env.FEATURE_ORGANIZATION_VENDOR === 'false' ? false : true, - FEATURE_ORGANIZATION_PROJECT: - process.env.FEATURE_ORGANIZATION_PROJECT === 'false' ? false : true, - FEATURE_ORGANIZATION_DEPARTMENT: - process.env.FEATURE_ORGANIZATION_DEPARTMENT === 'false' ? false : true, - FEATURE_ORGANIZATION_TEAM: - process.env.FEATURE_ORGANIZATION_TEAM === 'false' ? false : true, - FEATURE_ORGANIZATION_DOCUMENT: - process.env.FEATURE_ORGANIZATION_DOCUMENT === 'false' ? false : true, - FEATURE_ORGANIZATION_EMPLOYMENT_TYPE: - process.env.FEATURE_ORGANIZATION_EMPLOYMENT_TYPE === 'false' - ? false - : true, + FEATURE_EMPLOYEE_TIME_ACTIVITY: process.env.FEATURE_EMPLOYEE_TIME_ACTIVITY === 'false' ? false : true, + FEATURE_EMPLOYEE_TIMESHEETS: process.env.FEATURE_EMPLOYEE_TIMESHEETS === 'false' ? false : true, + FEATURE_EMPLOYEE_APPOINTMENT: process.env.FEATURE_EMPLOYEE_APPOINTMENT === 'false' ? false : true, + FEATURE_EMPLOYEE_APPROVAL: process.env.FEATURE_EMPLOYEE_APPROVAL === 'false' ? false : true, + FEATURE_EMPLOYEE_APPROVAL_POLICY: process.env.FEATURE_EMPLOYEE_APPROVAL_POLICY === 'false' ? false : true, + FEATURE_EMPLOYEE_LEVEL: process.env.FEATURE_EMPLOYEE_LEVEL === 'false' ? false : true, + FEATURE_EMPLOYEE_POSITION: process.env.FEATURE_EMPLOYEE_POSITION === 'false' ? false : true, + FEATURE_EMPLOYEE_TIMEOFF: process.env.FEATURE_EMPLOYEE_TIMEOFF === 'false' ? false : true, + FEATURE_EMPLOYEE_RECURRING_EXPENSE: process.env.FEATURE_EMPLOYEE_RECURRING_EXPENSE === 'false' ? false : true, + FEATURE_EMPLOYEE_CANDIDATE: process.env.FEATURE_EMPLOYEE_CANDIDATE === 'false' ? false : true, + FEATURE_MANAGE_INTERVIEW: process.env.FEATURE_MANAGE_INTERVIEW === 'false' ? false : true, + FEATURE_MANAGE_INVITE: process.env.FEATURE_MANAGE_INVITE === 'false' ? false : true, + FEATURE_ORGANIZATION: process.env.FEATURE_ORGANIZATION === 'false' ? false : true, + FEATURE_ORGANIZATION_EQUIPMENT: process.env.FEATURE_ORGANIZATION_EQUIPMENT === 'false' ? false : true, + FEATURE_ORGANIZATION_INVENTORY: process.env.FEATURE_ORGANIZATION_INVENTORY === 'false' ? false : true, + FEATURE_ORGANIZATION_TAG: process.env.FEATURE_ORGANIZATION_TAG === 'false' ? false : true, + FEATURE_ORGANIZATION_VENDOR: process.env.FEATURE_ORGANIZATION_VENDOR === 'false' ? false : true, + FEATURE_ORGANIZATION_PROJECT: process.env.FEATURE_ORGANIZATION_PROJECT === 'false' ? false : true, + FEATURE_ORGANIZATION_DEPARTMENT: process.env.FEATURE_ORGANIZATION_DEPARTMENT === 'false' ? false : true, + FEATURE_ORGANIZATION_TEAM: process.env.FEATURE_ORGANIZATION_TEAM === 'false' ? false : true, + FEATURE_ORGANIZATION_DOCUMENT: process.env.FEATURE_ORGANIZATION_DOCUMENT === 'false' ? false : true, + FEATURE_ORGANIZATION_EMPLOYMENT_TYPE: process.env.FEATURE_ORGANIZATION_EMPLOYMENT_TYPE === 'false' ? false : true, FEATURE_ORGANIZATION_RECURRING_EXPENSE: - process.env.FEATURE_ORGANIZATION_RECURRING_EXPENSE === 'false' - ? false - : true, - FEATURE_ORGANIZATION_HELP_CENTER: - process.env.FEATURE_ORGANIZATION_HELP_CENTER === 'false' ? false : true, + process.env.FEATURE_ORGANIZATION_RECURRING_EXPENSE === 'false' ? false : true, + FEATURE_ORGANIZATION_HELP_CENTER: process.env.FEATURE_ORGANIZATION_HELP_CENTER === 'false' ? false : true, FEATURE_CONTACT: process.env.FEATURE_CONTACT === 'false' ? false : true, FEATURE_GOAL: process.env.FEATURE_GOAL === 'false' ? false : true, - FEATURE_GOAL_REPORT: - process.env.FEATURE_GOAL_REPORT === 'false' ? false : true, - FEATURE_GOAL_SETTING: - process.env.FEATURE_GOAL_SETTING === 'false' ? false : true, + FEATURE_GOAL_REPORT: process.env.FEATURE_GOAL_REPORT === 'false' ? false : true, + FEATURE_GOAL_SETTING: process.env.FEATURE_GOAL_SETTING === 'false' ? false : true, FEATURE_REPORT: process.env.FEATURE_REPORT === 'false' ? false : true, FEATURE_USER: process.env.FEATURE_USER === 'false' ? false : true, - FEATURE_ORGANIZATIONS: - process.env.FEATURE_ORGANIZATIONS === 'false' ? false : true, - FEATURE_APP_INTEGRATION: - process.env.FEATURE_APP_INTEGRATION === 'false' ? false : true, + FEATURE_ORGANIZATIONS: process.env.FEATURE_ORGANIZATIONS === 'false' ? false : true, + FEATURE_APP_INTEGRATION: process.env.FEATURE_APP_INTEGRATION === 'false' ? false : true, FEATURE_SETTING: process.env.FEATURE_SETTING === 'false' ? false : true, - FEATURE_EMAIL_HISTORY: - process.env.FEATURE_EMAIL_HISTORY === 'false' ? false : true, - FEATURE_EMAIL_TEMPLATE: - process.env.FEATURE_EMAIL_TEMPLATE === 'false' ? false : true, - FEATURE_IMPORT_EXPORT: - process.env.FEATURE_IMPORT_EXPORT === 'false' ? false : true, - FEATURE_FILE_STORAGE: - process.env.FEATURE_FILE_STORAGE === 'false' ? false : true, - FEATURE_PAYMENT_GATEWAY: - process.env.FEATURE_PAYMENT_GATEWAY === 'false' ? false : true, - FEATURE_SMS_GATEWAY: - process.env.FEATURE_SMS_GATEWAY === 'false' ? false : true, + FEATURE_EMAIL_HISTORY: process.env.FEATURE_EMAIL_HISTORY === 'false' ? false : true, + FEATURE_EMAIL_TEMPLATE: process.env.FEATURE_EMAIL_TEMPLATE === 'false' ? false : true, + FEATURE_IMPORT_EXPORT: process.env.FEATURE_IMPORT_EXPORT === 'false' ? false : true, + FEATURE_FILE_STORAGE: process.env.FEATURE_FILE_STORAGE === 'false' ? false : true, + FEATURE_PAYMENT_GATEWAY: process.env.FEATURE_PAYMENT_GATEWAY === 'false' ? false : true, + FEATURE_SMS_GATEWAY: process.env.FEATURE_SMS_GATEWAY === 'false' ? false : true, FEATURE_SMTP: process.env.FEATURE_SMTP === 'false' ? false : true, - FEATURE_ROLES_PERMISSION: - process.env.FEATURE_ROLES_PERMISSION === 'false' ? false : true, - FEATURE_EMAIL_VERIFICATION: - process.env.FEATURE_EMAIL_VERIFICATION === 'false' ? false : true, + FEATURE_ROLES_PERMISSION: process.env.FEATURE_ROLES_PERMISSION === 'false' ? false : true, + FEATURE_EMAIL_VERIFICATION: process.env.FEATURE_EMAIL_VERIFICATION === 'false' ? false : true }; diff --git a/packages/config/src/environments/environment.ts b/packages/config/src/environments/environment.ts index f47b122ad7d..7892a2cf40a 100644 --- a/packages/config/src/environments/environment.ts +++ b/packages/config/src/environments/environment.ts @@ -21,35 +21,28 @@ export const environment: IEnvironment = { envName: 'dev', env: { - LOG_LEVEL: 'debug', + LOG_LEVEL: 'debug' }, EXPRESS_SESSION_SECRET: process.env.EXPRESS_SESSION_SECRET || 'gauzy', USER_PASSWORD_BCRYPT_SALT_ROUNDS: 12, JWT_SECRET: process.env.JWT_SECRET || 'secretKey', - JWT_TOKEN_EXPIRATION_TIME: - parseInt(process.env.JWT_TOKEN_EXPIRATION_TIME) || 86400 * 1, // default JWT token expire time (1 day) + JWT_TOKEN_EXPIRATION_TIME: parseInt(process.env.JWT_TOKEN_EXPIRATION_TIME) || 86400 * 1, // default JWT token expire time (1 day) - JWT_REFRESH_TOKEN_SECRET: - process.env.JWT_REFRESH_TOKEN_SECRET || 'refreshSecretKey', - JWT_REFRESH_TOKEN_EXPIRATION_TIME: - parseInt(process.env.JWT_REFRESH_TOKEN_EXPIRATION_TIME) || 86400 * 7, // default JWT refresh token expire time (7 days) + JWT_REFRESH_TOKEN_SECRET: process.env.JWT_REFRESH_TOKEN_SECRET || 'refreshSecretKey', + JWT_REFRESH_TOKEN_EXPIRATION_TIME: parseInt(process.env.JWT_REFRESH_TOKEN_EXPIRATION_TIME) || 86400 * 7, // default JWT refresh token expire time (7 days) /** * Email verification options */ - JWT_VERIFICATION_TOKEN_SECRET: - process.env.JWT_VERIFICATION_TOKEN_SECRET || 'verificationSecretKey', - JWT_VERIFICATION_TOKEN_EXPIRATION_TIME: - parseInt(process.env.JWT_VERIFICATION_TOKEN_EXPIRATION_TIME) || - 86400 * 7, // default verification expire token time (7 days) + JWT_VERIFICATION_TOKEN_SECRET: process.env.JWT_VERIFICATION_TOKEN_SECRET || 'verificationSecretKey', + JWT_VERIFICATION_TOKEN_EXPIRATION_TIME: parseInt(process.env.JWT_VERIFICATION_TOKEN_EXPIRATION_TIME) || 86400 * 7, // default verification expire token time (7 days) /** * Email Reset */ - EMAIL_RESET_EXPIRATION_TIME: - parseInt(process.env.EMAIL_RESET_EXPIRATION_TIME) || 1800, // default email reset expiration time (30 minutes) + EMAIL_RESET_EXPIRATION_TIME: parseInt(process.env.EMAIL_RESET_EXPIRATION_TIME) || 1800, // default email reset expiration time (30 minutes) /** * Password Less Authentication Configuration @@ -57,8 +50,7 @@ export const environment: IEnvironment = { MAGIC_CODE_EXPIRATION_TIME: parseInt(process.env.MAGIC_CODE_EXPIRATION_TIME) || 60 * 30, // default magic code expire time (30 minutes) /** Organization Team Join Request Configuration **/ - TEAM_JOIN_REQUEST_EXPIRATION_TIME: - parseInt(process.env.TEAM_JOIN_REQUEST_EXPIRATION_TIME) || 60 * 60 * 24, // default code expire time (1 day) + TEAM_JOIN_REQUEST_EXPIRATION_TIME: parseInt(process.env.TEAM_JOIN_REQUEST_EXPIRATION_TIME) || 60 * 60 * 24, // default code expire time (1 day) /** * Throttler (Rate Limiting) Options @@ -73,14 +65,11 @@ export const environment: IEnvironment = { serverHost: process.env.JITSU_SERVER_URL, serverWriteKey: process.env.JITSU_SERVER_WRITE_KEY, debug: process.env.JITSU_SERVER_DEBUG === 'true' ? true : false, - echoEvents: - process.env.JITSU_SERVER_ECHO_EVENTS === 'true' ? true : false, + echoEvents: process.env.JITSU_SERVER_ECHO_EVENTS === 'true' ? true : false }, fileSystem: { - name: - (process.env.FILE_PROVIDER as FileStorageProviderEnum) || - FileStorageProviderEnum.LOCAL, + name: (process.env.FILE_PROVIDER as FileStorageProviderEnum) || FileStorageProviderEnum.LOCAL }, awsConfig: { @@ -88,8 +77,8 @@ export const environment: IEnvironment = { secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, region: process.env.AWS_REGION || 'us-east-1', s3: { - bucket: process.env.AWS_S3_BUCKET || 'gauzy', - }, + bucket: process.env.AWS_S3_BUCKET || 'gauzy' + } }, wasabiConfig: { @@ -98,8 +87,8 @@ export const environment: IEnvironment = { region: process.env.WASABI_REGION || 'us-east-1', serviceUrl: process.env.WASABI_SERVICE_URL || 's3.wasabisys.com', s3: { - bucket: process.env.WASABI_S3_BUCKET || 'gauzy', - }, + bucket: process.env.WASABI_S3_BUCKET || 'gauzy' + } }, /** @@ -110,8 +99,7 @@ export const environment: IEnvironment = { api_key: process.env.CLOUDINARY_API_KEY, api_secret: process.env.CLOUDINARY_API_SECRET, secure: process.env.CLOUDINARY_API_SECURE === 'false' ? false : true, - delivery_url: - process.env.CLOUDINARY_CDN_URL || `https://res.cloudinary.com`, + delivery_url: process.env.CLOUDINARY_CDN_URL || `https://res.cloudinary.com` }, github: { @@ -121,23 +109,22 @@ export const environment: IEnvironment = { appId: process.env.GAUZY_GITHUB_APP_ID, appName: process.env.GAUZY_GITHUB_APP_NAME, appPrivateKey: process.env.GAUZY_GITHUB_APP_PRIVATE_KEY - ? Buffer.from( - process.env.GAUZY_GITHUB_APP_PRIVATE_KEY, - 'base64' - ).toString('ascii') + ? Buffer.from(process.env.GAUZY_GITHUB_APP_PRIVATE_KEY, 'base64').toString('ascii') : '', /** Github App Post Install Configuration */ - postInstallUrl: process.env.GAUZY_GITHUB_POST_INSTALL_URL || `${process.env.CLIENT_BASE_URL}/#/pages/integrations/github/setup/installation`, + postInstallUrl: + process.env.GAUZY_GITHUB_POST_INSTALL_URL || + `${process.env.CLIENT_BASE_URL}/#/pages/integrations/github/setup/installation`, /** Github Webhook Configuration */ webhookSecret: process.env.GAUZY_GITHUB_WEBHOOK_SECRET, - webhookUrl: process.env.GAUZY_GITHUB_WEBHOOK_URL || `${process.env.API_BASE_URL}/api/integration/github/webhook`, + webhookUrl: process.env.GAUZY_GITHUB_WEBHOOK_URL || `${process.env.API_BASE_URL}/api/integration/github/webhook` }, fiverrConfig: { clientId: process.env.FIVERR_CLIENT_ID, - clientSecret: process.env.FIVERR_CLIENT_SECRET, + clientSecret: process.env.FIVERR_CLIENT_SECRET }, keycloakConfig: { @@ -145,31 +132,27 @@ export const environment: IEnvironment = { clientId: process.env.KEYCLOAK_CLIENT_ID, secret: process.env.KEYCLOAK_SECRET, authServerUrl: process.env.KEYCLOAK_AUTH_SERVER_URL, - cookieKey: process.env.KEYCLOAK_COOKIE_KEY, + cookieKey: process.env.KEYCLOAK_COOKIE_KEY }, auth0Config: { clientID: process.env.AUTH0_CLIENT_ID, clientSecret: process.env.AUTH0_CLIENT_SECRET, - domain: process.env.AUTH0_DOMAIN, + domain: process.env.AUTH0_DOMAIN }, sentry: { - dsn: process.env.SENTRY_DSN, + dsn: process.env.SENTRY_DSN }, - defaultIntegratedUserPass: - process.env.INTEGRATED_USER_DEFAULT_PASS || '123456', + defaultIntegratedUserPass: process.env.INTEGRATED_USER_DEFAULT_PASS || '123456', upwork: { apiKey: process.env.UPWORK_API_KEY, apiSecret: process.env.UPWORK_API_SECRET, - callbackUrl: - process.env.UPWORK_REDIRECT_URL || - `${process.env.API_BASE_URL}/api/integrations/upwork/callback`, + callbackUrl: process.env.UPWORK_REDIRECT_URL || `${process.env.API_BASE_URL}/api/integrations/upwork/callback`, postInstallUrl: - process.env.UPWORK_POST_INSTALL_URL || - `${process.env.CLIENT_BASE_URL}/#/pages/integrations/upwork`, + process.env.UPWORK_POST_INSTALL_URL || `${process.env.CLIENT_BASE_URL}/#/pages/integrations/upwork` }, hubstaff: { @@ -178,14 +161,12 @@ export const environment: IEnvironment = { clientSecret: process.env.HUBSTAFF_CLIENT_SECRET, /** Hubstaff Integration Post Install URL */ postInstallUrl: - process.env.HUBSTAFF_POST_INSTALL_URL || - `${process.env.CLIENT_BASE_URL}/#/pages/integrations/hubstaff`, + process.env.HUBSTAFF_POST_INSTALL_URL || `${process.env.CLIENT_BASE_URL}/#/pages/integrations/hubstaff` }, isElectron: process.env.IS_ELECTRON === 'true' ? true : false, gauzyUserPath: process.env.GAUZY_USER_PATH, - allowSuperAdminRole: - process.env.ALLOW_SUPER_ADMIN_ROLE === 'false' ? false : true, + allowSuperAdminRole: process.env.ALLOW_SUPER_ADMIN_ROLE === 'false' ? false : true, /** * Endpoint for Gauzy AI API (optional), e.g.: http://localhost:3005/graphql @@ -205,25 +186,21 @@ export const environment: IEnvironment = { secure: process.env.MAIL_PORT === '465' ? true : false, // true for 465, false for other ports auth: { user: process.env.MAIL_USERNAME, - pass: process.env.MAIL_PASSWORD, + pass: process.env.MAIL_PASSWORD }, - fromAddress: process.env.MAIL_FROM_ADDRESS, + fromAddress: process.env.MAIL_FROM_ADDRESS }, defaultCurrency: process.env.DEFAULT_CURRENCY || 'USD', unleashConfig: { // if UNLEASH_API_URL is not set / empty or it's a spaces only, we consider UNLEASH disabled - url: process.env.UNLEASH_API_URL - ? process.env.UNLEASH_API_URL.trim() - : '', + url: process.env.UNLEASH_API_URL ? process.env.UNLEASH_API_URL.trim() : '', appName: process.env.UNLEASH_APP_NAME, environment: 'development', instanceId: process.env.UNLEASH_INSTANCE_ID, - refreshInterval: - parseInt(process.env.UNLEASH_REFRESH_INTERVAL) || 15000, - metricsInterval: - parseInt(process.env.UNLEASH_METRICS_INTERVAL) || 60000, - apiKey: process.env.UNLEASH_API_KEY, + refreshInterval: parseInt(process.env.UNLEASH_REFRESH_INTERVAL) || 15000, + metricsInterval: parseInt(process.env.UNLEASH_METRICS_INTERVAL) || 60000, + apiKey: process.env.UNLEASH_API_KEY }, /** @@ -247,119 +224,71 @@ export const environment: IEnvironment = { adminEmail: process.env.DEMO_ADMIN_EMAIL || `local.admin@ever.co`, adminPassword: process.env.DEMO_ADMIN_PASSWORD || `admin`, employeeEmail: process.env.DEMO_EMPLOYEE_EMAIL || `employee@ever.co`, - employeePassword: process.env.DEMO_EMPLOYEE_PASSWORD || `123456`, - }, + employeePassword: process.env.DEMO_EMPLOYEE_PASSWORD || `123456` + } }; export const gauzyToggleFeatures: IGauzyFeatures = { FEATURE_DASHBOARD: process.env.FEATURE_DASHBOARD === 'false' ? false : true, - FEATURE_TIME_TRACKING: - process.env.FEATURE_TIME_TRACKING === 'false' ? false : true, + FEATURE_TIME_TRACKING: process.env.FEATURE_TIME_TRACKING === 'false' ? false : true, FEATURE_ESTIMATE: process.env.FEATURE_ESTIMATE === 'false' ? false : true, - FEATURE_ESTIMATE_RECEIVED: - process.env.FEATURE_ESTIMATE_RECEIVED === 'false' ? false : true, + FEATURE_ESTIMATE_RECEIVED: process.env.FEATURE_ESTIMATE_RECEIVED === 'false' ? false : true, FEATURE_INVOICE: process.env.FEATURE_INVOICE === 'false' ? false : true, - FEATURE_INVOICE_RECURRING: - process.env.FEATURE_INVOICE_RECURRING === 'false' ? false : true, - FEATURE_INVOICE_RECEIVED: - process.env.FEATURE_INVOICE_RECEIVED === 'false' ? false : true, + FEATURE_INVOICE_RECURRING: process.env.FEATURE_INVOICE_RECURRING === 'false' ? false : true, + FEATURE_INVOICE_RECEIVED: process.env.FEATURE_INVOICE_RECEIVED === 'false' ? false : true, FEATURE_INCOME: process.env.FEATURE_INCOME === 'false' ? false : true, FEATURE_EXPENSE: process.env.FEATURE_EXPENSE === 'false' ? false : true, FEATURE_PAYMENT: process.env.FEATURE_PAYMENT === 'false' ? false : true, FEATURE_PROPOSAL: process.env.FEATURE_PROPOSAL === 'false' ? false : true, - FEATURE_PROPOSAL_TEMPLATE: - process.env.FEATURE_PROPOSAL_TEMPLATE === 'false' ? false : true, + FEATURE_PROPOSAL_TEMPLATE: process.env.FEATURE_PROPOSAL_TEMPLATE === 'false' ? false : true, FEATURE_PIPELINE: process.env.FEATURE_PIPELINE === 'false' ? false : true, - FEATURE_PIPELINE_DEAL: - process.env.FEATURE_PIPELINE_DEAL === 'false' ? false : true, - FEATURE_DASHBOARD_TASK: - process.env.FEATURE_DASHBOARD_TASK === 'false' ? false : true, + FEATURE_PIPELINE_DEAL: process.env.FEATURE_PIPELINE_DEAL === 'false' ? false : true, + FEATURE_DASHBOARD_TASK: process.env.FEATURE_DASHBOARD_TASK === 'false' ? false : true, FEATURE_TEAM_TASK: process.env.FEATURE_TEAM_TASK === 'false' ? false : true, FEATURE_MY_TASK: process.env.FEATURE_MY_TASK === 'false' ? false : true, FEATURE_JOB: process.env.FEATURE_JOB === 'false' ? false : true, FEATURE_EMPLOYEES: process.env.FEATURE_EMPLOYEES === 'false' ? false : true, - FEATURE_EMPLOYEE_TIME_ACTIVITY: - process.env.FEATURE_EMPLOYEE_TIME_ACTIVITY === 'false' ? false : true, - FEATURE_EMPLOYEE_TIMESHEETS: - process.env.FEATURE_EMPLOYEE_TIMESHEETS === 'false' ? false : true, - FEATURE_EMPLOYEE_APPOINTMENT: - process.env.FEATURE_EMPLOYEE_APPOINTMENT === 'false' ? false : true, - FEATURE_EMPLOYEE_APPROVAL: - process.env.FEATURE_EMPLOYEE_APPROVAL === 'false' ? false : true, - FEATURE_EMPLOYEE_APPROVAL_POLICY: - process.env.FEATURE_EMPLOYEE_APPROVAL_POLICY === 'false' ? false : true, - FEATURE_EMPLOYEE_LEVEL: - process.env.FEATURE_EMPLOYEE_LEVEL === 'false' ? false : true, - FEATURE_EMPLOYEE_POSITION: - process.env.FEATURE_EMPLOYEE_POSITION === 'false' ? false : true, - FEATURE_EMPLOYEE_TIMEOFF: - process.env.FEATURE_EMPLOYEE_TIMEOFF === 'false' ? false : true, - FEATURE_EMPLOYEE_RECURRING_EXPENSE: - process.env.FEATURE_EMPLOYEE_RECURRING_EXPENSE === 'false' - ? false - : true, - FEATURE_EMPLOYEE_CANDIDATE: - process.env.FEATURE_EMPLOYEE_CANDIDATE === 'false' ? false : true, - FEATURE_MANAGE_INTERVIEW: - process.env.FEATURE_MANAGE_INTERVIEW === 'false' ? false : true, - FEATURE_MANAGE_INVITE: - process.env.FEATURE_MANAGE_INVITE === 'false' ? false : true, - FEATURE_ORGANIZATION: - process.env.FEATURE_ORGANIZATION === 'false' ? false : true, - FEATURE_ORGANIZATION_EQUIPMENT: - process.env.FEATURE_ORGANIZATION_EQUIPMENT === 'false' ? false : true, - FEATURE_ORGANIZATION_INVENTORY: - process.env.FEATURE_ORGANIZATION_INVENTORY === 'false' ? false : true, - FEATURE_ORGANIZATION_TAG: - process.env.FEATURE_ORGANIZATION_TAG === 'false' ? false : true, - FEATURE_ORGANIZATION_VENDOR: - process.env.FEATURE_ORGANIZATION_VENDOR === 'false' ? false : true, - FEATURE_ORGANIZATION_PROJECT: - process.env.FEATURE_ORGANIZATION_PROJECT === 'false' ? false : true, - FEATURE_ORGANIZATION_DEPARTMENT: - process.env.FEATURE_ORGANIZATION_DEPARTMENT === 'false' ? false : true, - FEATURE_ORGANIZATION_TEAM: - process.env.FEATURE_ORGANIZATION_TEAM === 'false' ? false : true, - FEATURE_ORGANIZATION_DOCUMENT: - process.env.FEATURE_ORGANIZATION_DOCUMENT === 'false' ? false : true, - FEATURE_ORGANIZATION_EMPLOYMENT_TYPE: - process.env.FEATURE_ORGANIZATION_EMPLOYMENT_TYPE === 'false' - ? false - : true, + FEATURE_EMPLOYEE_TIME_ACTIVITY: process.env.FEATURE_EMPLOYEE_TIME_ACTIVITY === 'false' ? false : true, + FEATURE_EMPLOYEE_TIMESHEETS: process.env.FEATURE_EMPLOYEE_TIMESHEETS === 'false' ? false : true, + FEATURE_EMPLOYEE_APPOINTMENT: process.env.FEATURE_EMPLOYEE_APPOINTMENT === 'false' ? false : true, + FEATURE_EMPLOYEE_APPROVAL: process.env.FEATURE_EMPLOYEE_APPROVAL === 'false' ? false : true, + FEATURE_EMPLOYEE_APPROVAL_POLICY: process.env.FEATURE_EMPLOYEE_APPROVAL_POLICY === 'false' ? false : true, + FEATURE_EMPLOYEE_LEVEL: process.env.FEATURE_EMPLOYEE_LEVEL === 'false' ? false : true, + FEATURE_EMPLOYEE_POSITION: process.env.FEATURE_EMPLOYEE_POSITION === 'false' ? false : true, + FEATURE_EMPLOYEE_TIMEOFF: process.env.FEATURE_EMPLOYEE_TIMEOFF === 'false' ? false : true, + FEATURE_EMPLOYEE_RECURRING_EXPENSE: process.env.FEATURE_EMPLOYEE_RECURRING_EXPENSE === 'false' ? false : true, + FEATURE_EMPLOYEE_CANDIDATE: process.env.FEATURE_EMPLOYEE_CANDIDATE === 'false' ? false : true, + FEATURE_MANAGE_INTERVIEW: process.env.FEATURE_MANAGE_INTERVIEW === 'false' ? false : true, + FEATURE_MANAGE_INVITE: process.env.FEATURE_MANAGE_INVITE === 'false' ? false : true, + FEATURE_ORGANIZATION: process.env.FEATURE_ORGANIZATION === 'false' ? false : true, + FEATURE_ORGANIZATION_EQUIPMENT: process.env.FEATURE_ORGANIZATION_EQUIPMENT === 'false' ? false : true, + FEATURE_ORGANIZATION_INVENTORY: process.env.FEATURE_ORGANIZATION_INVENTORY === 'false' ? false : true, + FEATURE_ORGANIZATION_TAG: process.env.FEATURE_ORGANIZATION_TAG === 'false' ? false : true, + FEATURE_ORGANIZATION_VENDOR: process.env.FEATURE_ORGANIZATION_VENDOR === 'false' ? false : true, + FEATURE_ORGANIZATION_PROJECT: process.env.FEATURE_ORGANIZATION_PROJECT === 'false' ? false : true, + FEATURE_ORGANIZATION_DEPARTMENT: process.env.FEATURE_ORGANIZATION_DEPARTMENT === 'false' ? false : true, + FEATURE_ORGANIZATION_TEAM: process.env.FEATURE_ORGANIZATION_TEAM === 'false' ? false : true, + FEATURE_ORGANIZATION_DOCUMENT: process.env.FEATURE_ORGANIZATION_DOCUMENT === 'false' ? false : true, + FEATURE_ORGANIZATION_EMPLOYMENT_TYPE: process.env.FEATURE_ORGANIZATION_EMPLOYMENT_TYPE === 'false' ? false : true, FEATURE_ORGANIZATION_RECURRING_EXPENSE: - process.env.FEATURE_ORGANIZATION_RECURRING_EXPENSE === 'false' - ? false - : true, - FEATURE_ORGANIZATION_HELP_CENTER: - process.env.FEATURE_ORGANIZATION_HELP_CENTER === 'false' ? false : true, + process.env.FEATURE_ORGANIZATION_RECURRING_EXPENSE === 'false' ? false : true, + FEATURE_ORGANIZATION_HELP_CENTER: process.env.FEATURE_ORGANIZATION_HELP_CENTER === 'false' ? false : true, FEATURE_CONTACT: process.env.FEATURE_CONTACT === 'false' ? false : true, FEATURE_GOAL: process.env.FEATURE_GOAL === 'false' ? false : true, - FEATURE_GOAL_REPORT: - process.env.FEATURE_GOAL_REPORT === 'false' ? false : true, - FEATURE_GOAL_SETTING: - process.env.FEATURE_GOAL_SETTING === 'false' ? false : true, + FEATURE_GOAL_REPORT: process.env.FEATURE_GOAL_REPORT === 'false' ? false : true, + FEATURE_GOAL_SETTING: process.env.FEATURE_GOAL_SETTING === 'false' ? false : true, FEATURE_REPORT: process.env.FEATURE_REPORT === 'false' ? false : true, FEATURE_USER: process.env.FEATURE_USER === 'false' ? false : true, - FEATURE_ORGANIZATIONS: - process.env.FEATURE_ORGANIZATIONS === 'false' ? false : true, - FEATURE_APP_INTEGRATION: - process.env.FEATURE_APP_INTEGRATION === 'false' ? false : true, + FEATURE_ORGANIZATIONS: process.env.FEATURE_ORGANIZATIONS === 'false' ? false : true, + FEATURE_APP_INTEGRATION: process.env.FEATURE_APP_INTEGRATION === 'false' ? false : true, FEATURE_SETTING: process.env.FEATURE_SETTING === 'false' ? false : true, - FEATURE_EMAIL_HISTORY: - process.env.FEATURE_EMAIL_HISTORY === 'false' ? false : true, - FEATURE_EMAIL_TEMPLATE: - process.env.FEATURE_EMAIL_TEMPLATE === 'false' ? false : true, - FEATURE_IMPORT_EXPORT: - process.env.FEATURE_IMPORT_EXPORT === 'false' ? false : true, - FEATURE_FILE_STORAGE: - process.env.FEATURE_FILE_STORAGE === 'false' ? false : true, - FEATURE_PAYMENT_GATEWAY: - process.env.FEATURE_PAYMENT_GATEWAY === 'false' ? false : true, - FEATURE_SMS_GATEWAY: - process.env.FEATURE_SMS_GATEWAY === 'false' ? false : true, + FEATURE_EMAIL_HISTORY: process.env.FEATURE_EMAIL_HISTORY === 'false' ? false : true, + FEATURE_EMAIL_TEMPLATE: process.env.FEATURE_EMAIL_TEMPLATE === 'false' ? false : true, + FEATURE_IMPORT_EXPORT: process.env.FEATURE_IMPORT_EXPORT === 'false' ? false : true, + FEATURE_FILE_STORAGE: process.env.FEATURE_FILE_STORAGE === 'false' ? false : true, + FEATURE_PAYMENT_GATEWAY: process.env.FEATURE_PAYMENT_GATEWAY === 'false' ? false : true, + FEATURE_SMS_GATEWAY: process.env.FEATURE_SMS_GATEWAY === 'false' ? false : true, FEATURE_SMTP: process.env.FEATURE_SMTP === 'false' ? false : true, - FEATURE_ROLES_PERMISSION: - process.env.FEATURE_ROLES_PERMISSION === 'false' ? false : true, - FEATURE_EMAIL_VERIFICATION: - process.env.FEATURE_EMAIL_VERIFICATION === 'false' ? false : true, + FEATURE_ROLES_PERMISSION: process.env.FEATURE_ROLES_PERMISSION === 'false' ? false : true, + FEATURE_EMAIL_VERIFICATION: process.env.FEATURE_EMAIL_VERIFICATION === 'false' ? false : true }; diff --git a/packages/contracts/src/timesheet.model.ts b/packages/contracts/src/timesheet.model.ts index 5a7cba7f3e7..6673f574fa3 100644 --- a/packages/contracts/src/timesheet.model.ts +++ b/packages/contracts/src/timesheet.model.ts @@ -25,10 +25,10 @@ import { IRelationalUser, IUser } from './user.model'; import { IRelationalOrganizationTeam } from './organization-team.model'; export interface ITimesheet extends IBasePerTenantAndOrganizationEntityModel { - [x: string]: any; employee: IEmployee; - employeeId?: string; + employeeId?: IEmployee['id']; approvedBy?: IUser; + approvedById?: IUser['id']; timeLogs?: ITimeLog[]; duration?: number; keyboard?: number; @@ -39,12 +39,13 @@ export interface ITimesheet extends IBasePerTenantAndOrganizationEntityModel { approvedAt?: Date; submittedAt?: Date; lockedAt?: Date; + editedAt?: Date; isBilled?: boolean; status: string; + isEdited?: boolean; } -export interface ITimesheetCreateInput - extends IBasePerTenantAndOrganizationEntityModel { +export interface ITimesheetCreateInput extends IBasePerTenantAndOrganizationEntityModel { employeeId: string; approvedById?: string; duration: number; @@ -60,8 +61,7 @@ export interface ITimesheetCreateInput status?: string; } -export interface ITimeSheetFindInput - extends IBasePerTenantAndOrganizationEntityModel { +export interface ITimeSheetFindInput extends IBasePerTenantAndOrganizationEntityModel { employeeId: string; approvedById?: string; employee: IEmployeeFindInput; @@ -107,28 +107,28 @@ export interface IDateRange { start: Date; end: Date; } -export interface ITimeLog - extends IBasePerTenantAndOrganizationEntityModel, - IRelationalOrganizationProject, - IRelationalOrganizationTeam { - [x: string]: any; +export interface ITimeLog extends IBasePerTenantAndOrganizationEntityModel, IRelationalOrganizationProject, IRelationalOrganizationTeam { employee: IEmployee; + employeeId: IEmployee['id']; timesheet?: ITimesheet; + timesheetId?: ITimesheet['id']; task?: ITask; + taskId?: ITask['id']; timeSlots?: ITimeSlot[]; project?: IOrganizationProject; + projectId?: IOrganizationProject['id']; organizationContact?: IOrganizationContact; + organizationContactId?: IOrganizationContact['id']; source?: TimeLogSourceEnum; startedAt?: Date; stoppedAt?: Date; + /** Edited At* */ + editedAt?: Date; logType: TimeLogType; description?: string; reason?: string; duration: number; isBillable: boolean; - employeeId: string; - organizationContactId?: string; - taskId?: string; tags?: string[]; isRunning?: boolean; isEdited?: boolean; @@ -389,8 +389,7 @@ export interface ITimerToggleInput stoppedAt?: Date; } -export interface IManualTimeInput - extends IBasePerTenantAndOrganizationEntityModel { +export interface IManualTimeInput extends IBasePerTenantAndOrganizationEntityModel { id?: string; employeeId?: string; projectId?: string; @@ -401,6 +400,7 @@ export interface IManualTimeInput reason?: string; startedAt?: Date; stoppedAt?: Date; + editedAt?: Date; tags?: string[]; isBillable?: boolean; } diff --git a/packages/core/src/database/migrations/1701067699290-AddedEditedAtFeatureToTheTimesheetAndTimeLogTables.ts b/packages/core/src/database/migrations/1701067699290-AddedEditedAtFeatureToTheTimesheetAndTimeLogTables.ts new file mode 100644 index 00000000000..5ac7884fad9 --- /dev/null +++ b/packages/core/src/database/migrations/1701067699290-AddedEditedAtFeatureToTheTimesheetAndTimeLogTables.ts @@ -0,0 +1,216 @@ + +import { MigrationInterface, QueryRunner } from "typeorm"; +import * as chalk from "chalk"; + +export class AddedEditedAtFeatureToTheTimesheetAndTimeLogTables1701067699290 implements MigrationInterface { + + name = 'AddedEditedAtFeatureToTheTimesheetAndTimeLogTables1701067699290'; + + /** + * Up Migration + * + * @param queryRunner + */ + public async up(queryRunner: QueryRunner): Promise { + console.log(chalk.yellow(`${this.name} start running!`)); + + if (['sqlite', 'better-sqlite3'].includes(queryRunner.connection.options.type)) { + await this.sqliteUpQueryRunner(queryRunner); + } else { + await this.postgresUpQueryRunner(queryRunner); + } + } + + /** + * Down Migration + * + * @param queryRunner + */ + public async down(queryRunner: QueryRunner): Promise { + if (['sqlite', 'better-sqlite3'].includes(queryRunner.connection.options.type)) { + await this.sqliteDownQueryRunner(queryRunner); + } else { + await this.postgresDownQueryRunner(queryRunner); + } + } + + /** + * PostgresDB Up Migration + * + * @param queryRunner + */ + public async postgresUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "time_log" ADD "editedAt" TIMESTAMP`); + await queryRunner.query(`ALTER TABLE "timesheet" ADD "editedAt" TIMESTAMP`); + await queryRunner.query(`CREATE INDEX "IDX_154e9120e2acb632d8bd9b91ff" ON "time_log" ("editedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_ea81b5247ecdf5d82cf71fa096" ON "timesheet" ("editedAt") `); + } + + /** + * PostgresDB Down Migration + * + * @param queryRunner + */ + public async postgresDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "public"."IDX_ea81b5247ecdf5d82cf71fa096"`); + await queryRunner.query(`DROP INDEX "public"."IDX_154e9120e2acb632d8bd9b91ff"`); + await queryRunner.query(`ALTER TABLE "timesheet" DROP COLUMN "editedAt"`); + await queryRunner.query(`ALTER TABLE "time_log" DROP COLUMN "editedAt"`); + } + + /** + * SqliteDB and BetterSQlite3DB Up Migration + * + * @param queryRunner + */ + public async sqliteUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_91a64228fbbe1516730a0cab5d"`); + await queryRunner.query(`DROP INDEX "IDX_a1910a76044b971609b75ea165"`); + await queryRunner.query(`DROP INDEX "IDX_79001d281ecb766005b3d331c1"`); + await queryRunner.query(`DROP INDEX "IDX_f447474d185cd70b3015853874"`); + await queryRunner.query(`DROP INDEX "IDX_722b9cb3a991c964d86396b6bc"`); + await queryRunner.query(`DROP INDEX "IDX_402290e7045e0c10ef97d9f982"`); + await queryRunner.query(`DROP INDEX "IDX_e80fb588b1086ce2a4f2244814"`); + await queryRunner.query(`DROP INDEX "IDX_a1f8fcd70164d915fe7dd4a1ec"`); + await queryRunner.query(`DROP INDEX "IDX_189b79acd611870aba62b3594e"`); + await queryRunner.query(`DROP INDEX "IDX_fa9018cb248ea0f3b2b30ef143"`); + await queryRunner.query(`DROP INDEX "IDX_aed2d5cc5680fba9d387c7f931"`); + await queryRunner.query(`DROP INDEX "IDX_a89a849957e005bafb8e4220bc"`); + await queryRunner.query(`DROP INDEX "IDX_e65393bb52aa8275b1392c73f7"`); + await queryRunner.query(`DROP INDEX "IDX_54776f6f5fd3c13c3bc1fbfac5"`); + await queryRunner.query(`DROP INDEX "IDX_1ddf2da35e34378fd845d80a18"`); + await queryRunner.query(`DROP INDEX "IDX_d1e8f22c02c5e949453dde7f2d"`); + await queryRunner.query(`DROP INDEX "IDX_18dcdf754396f0cb0308dc91f4"`); + await queryRunner.query(`CREATE TABLE "temporary_time_log" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "startedAt" datetime, "stoppedAt" datetime, "logType" varchar NOT NULL DEFAULT ('TRACKED'), "source" varchar NOT NULL DEFAULT ('BROWSER'), "description" varchar, "reason" varchar, "isBillable" boolean NOT NULL DEFAULT (0), "deletedAt" datetime, "employeeId" varchar NOT NULL, "timesheetId" varchar, "projectId" varchar, "taskId" varchar, "organizationContactId" varchar, "isRunning" boolean, "version" varchar, "organizationTeamId" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "editedAt" datetime, CONSTRAINT "FK_a89a849957e005bafb8e4220bc7" FOREIGN KEY ("employeeId") REFERENCES "employee" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_fa9018cb248ea0f3b2b30ef143b" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_aed2d5cc5680fba9d387c7f931d" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_e65393bb52aa8275b1392c73f72" FOREIGN KEY ("timesheetId") REFERENCES "timesheet" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_54776f6f5fd3c13c3bc1fbfac5b" FOREIGN KEY ("projectId") REFERENCES "organization_project" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_1ddf2da35e34378fd845d80a18b" FOREIGN KEY ("taskId") REFERENCES "task" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_d1e8f22c02c5e949453dde7f2d1" FOREIGN KEY ("organizationContactId") REFERENCES "organization_contact" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_18dcdf754396f0cb0308dc91f4c" FOREIGN KEY ("organizationTeamId") REFERENCES "organization_team" ("id") ON DELETE SET NULL ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_time_log"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "startedAt", "stoppedAt", "logType", "source", "description", "reason", "isBillable", "deletedAt", "employeeId", "timesheetId", "projectId", "taskId", "organizationContactId", "isRunning", "version", "organizationTeamId", "isActive", "isArchived") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "startedAt", "stoppedAt", "logType", "source", "description", "reason", "isBillable", "deletedAt", "employeeId", "timesheetId", "projectId", "taskId", "organizationContactId", "isRunning", "version", "organizationTeamId", "isActive", "isArchived" FROM "time_log"`); + await queryRunner.query(`DROP TABLE "time_log"`); + await queryRunner.query(`ALTER TABLE "temporary_time_log" RENAME TO "time_log"`); + await queryRunner.query(`CREATE INDEX "IDX_91a64228fbbe1516730a0cab5d" ON "time_log" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_a1910a76044b971609b75ea165" ON "time_log" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_79001d281ecb766005b3d331c1" ON "time_log" ("version") `); + await queryRunner.query(`CREATE INDEX "IDX_f447474d185cd70b3015853874" ON "time_log" ("isRunning") `); + await queryRunner.query(`CREATE INDEX "IDX_722b9cb3a991c964d86396b6bc" ON "time_log" ("isBillable") `); + await queryRunner.query(`CREATE INDEX "IDX_402290e7045e0c10ef97d9f982" ON "time_log" ("source") `); + await queryRunner.query(`CREATE INDEX "IDX_e80fb588b1086ce2a4f2244814" ON "time_log" ("logType") `); + await queryRunner.query(`CREATE INDEX "IDX_a1f8fcd70164d915fe7dd4a1ec" ON "time_log" ("stoppedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_189b79acd611870aba62b3594e" ON "time_log" ("startedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_fa9018cb248ea0f3b2b30ef143" ON "time_log" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_aed2d5cc5680fba9d387c7f931" ON "time_log" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_a89a849957e005bafb8e4220bc" ON "time_log" ("employeeId") `); + await queryRunner.query(`CREATE INDEX "IDX_e65393bb52aa8275b1392c73f7" ON "time_log" ("timesheetId") `); + await queryRunner.query(`CREATE INDEX "IDX_54776f6f5fd3c13c3bc1fbfac5" ON "time_log" ("projectId") `); + await queryRunner.query(`CREATE INDEX "IDX_1ddf2da35e34378fd845d80a18" ON "time_log" ("taskId") `); + await queryRunner.query(`CREATE INDEX "IDX_d1e8f22c02c5e949453dde7f2d" ON "time_log" ("organizationContactId") `); + await queryRunner.query(`CREATE INDEX "IDX_18dcdf754396f0cb0308dc91f4" ON "time_log" ("organizationTeamId") `); + await queryRunner.query(`DROP INDEX "IDX_f2d4cd3a7e839bfc7cb6b993ff"`); + await queryRunner.query(`DROP INDEX "IDX_42205a9e6af108364e5cc62dd4"`); + await queryRunner.query(`DROP INDEX "IDX_6c1f81934a3f597b3b1a17f562"`); + await queryRunner.query(`DROP INDEX "IDX_8c8f821cb0fe0dd387491ea7d9"`); + await queryRunner.query(`DROP INDEX "IDX_aca65a79fe0c1ec9e6a59022c5"`); + await queryRunner.query(`DROP INDEX "IDX_25b8df69c9b7f5752c6a6a6ef7"`); + await queryRunner.query(`DROP INDEX "IDX_930e2b28de9ecb1ea689d5a97a"`); + await queryRunner.query(`DROP INDEX "IDX_f6558fbb3158ab90da1c41d943"`); + await queryRunner.query(`DROP INDEX "IDX_6a79eb7534066b11f59243ede1"`); + await queryRunner.query(`DROP INDEX "IDX_3f8fc4b5718fcaa913f9438e27"`); + await queryRunner.query(`DROP INDEX "IDX_3502c60f98a7cda58dea75bcb5"`); + await queryRunner.query(`DROP INDEX "IDX_c828facbb4250117f83416d9f7"`); + await queryRunner.query(`DROP INDEX "IDX_23fdffa8369387d87101090684"`); + await queryRunner.query(`CREATE TABLE "temporary_timesheet" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "duration" integer NOT NULL DEFAULT (0), "keyboard" integer NOT NULL DEFAULT (0), "mouse" integer NOT NULL DEFAULT (0), "overall" integer NOT NULL DEFAULT (0), "startedAt" datetime, "stoppedAt" datetime, "approvedAt" datetime, "submittedAt" datetime, "lockedAt" datetime, "isBilled" boolean NOT NULL DEFAULT (0), "status" varchar NOT NULL DEFAULT ('PENDING'), "deletedAt" datetime, "employeeId" varchar NOT NULL, "approvedById" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "editedAt" datetime, CONSTRAINT "FK_8c8f821cb0fe0dd387491ea7d9e" FOREIGN KEY ("employeeId") REFERENCES "employee" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_aca65a79fe0c1ec9e6a59022c54" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_25b8df69c9b7f5752c6a6a6ef7f" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_6c1f81934a3f597b3b1a17f5623" FOREIGN KEY ("approvedById") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_timesheet"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "duration", "keyboard", "mouse", "overall", "startedAt", "stoppedAt", "approvedAt", "submittedAt", "lockedAt", "isBilled", "status", "deletedAt", "employeeId", "approvedById", "isActive", "isArchived") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "duration", "keyboard", "mouse", "overall", "startedAt", "stoppedAt", "approvedAt", "submittedAt", "lockedAt", "isBilled", "status", "deletedAt", "employeeId", "approvedById", "isActive", "isArchived" FROM "timesheet"`); + await queryRunner.query(`DROP TABLE "timesheet"`); + await queryRunner.query(`ALTER TABLE "temporary_timesheet" RENAME TO "timesheet"`); + await queryRunner.query(`CREATE INDEX "IDX_f2d4cd3a7e839bfc7cb6b993ff" ON "timesheet" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_42205a9e6af108364e5cc62dd4" ON "timesheet" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_6c1f81934a3f597b3b1a17f562" ON "timesheet" ("approvedById") `); + await queryRunner.query(`CREATE INDEX "IDX_8c8f821cb0fe0dd387491ea7d9" ON "timesheet" ("employeeId") `); + await queryRunner.query(`CREATE INDEX "IDX_aca65a79fe0c1ec9e6a59022c5" ON "timesheet" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_25b8df69c9b7f5752c6a6a6ef7" ON "timesheet" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_930e2b28de9ecb1ea689d5a97a" ON "timesheet" ("startedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_f6558fbb3158ab90da1c41d943" ON "timesheet" ("stoppedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_6a79eb7534066b11f59243ede1" ON "timesheet" ("approvedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_3f8fc4b5718fcaa913f9438e27" ON "timesheet" ("submittedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_3502c60f98a7cda58dea75bcb5" ON "timesheet" ("lockedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_c828facbb4250117f83416d9f7" ON "timesheet" ("isBilled") `); + await queryRunner.query(`CREATE INDEX "IDX_23fdffa8369387d87101090684" ON "timesheet" ("status") `); + await queryRunner.query(`CREATE INDEX "IDX_154e9120e2acb632d8bd9b91ff" ON "time_log" ("editedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_ea81b5247ecdf5d82cf71fa096" ON "timesheet" ("editedAt") `); + } + + /** + * SqliteDB and BetterSQlite3DB Down Migration + * + * @param queryRunner + */ + public async sqliteDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_ea81b5247ecdf5d82cf71fa096"`); + await queryRunner.query(`DROP INDEX "IDX_154e9120e2acb632d8bd9b91ff"`); + await queryRunner.query(`DROP INDEX "IDX_23fdffa8369387d87101090684"`); + await queryRunner.query(`DROP INDEX "IDX_c828facbb4250117f83416d9f7"`); + await queryRunner.query(`DROP INDEX "IDX_3502c60f98a7cda58dea75bcb5"`); + await queryRunner.query(`DROP INDEX "IDX_3f8fc4b5718fcaa913f9438e27"`); + await queryRunner.query(`DROP INDEX "IDX_6a79eb7534066b11f59243ede1"`); + await queryRunner.query(`DROP INDEX "IDX_f6558fbb3158ab90da1c41d943"`); + await queryRunner.query(`DROP INDEX "IDX_930e2b28de9ecb1ea689d5a97a"`); + await queryRunner.query(`DROP INDEX "IDX_25b8df69c9b7f5752c6a6a6ef7"`); + await queryRunner.query(`DROP INDEX "IDX_aca65a79fe0c1ec9e6a59022c5"`); + await queryRunner.query(`DROP INDEX "IDX_8c8f821cb0fe0dd387491ea7d9"`); + await queryRunner.query(`DROP INDEX "IDX_6c1f81934a3f597b3b1a17f562"`); + await queryRunner.query(`DROP INDEX "IDX_42205a9e6af108364e5cc62dd4"`); + await queryRunner.query(`DROP INDEX "IDX_f2d4cd3a7e839bfc7cb6b993ff"`); + await queryRunner.query(`ALTER TABLE "timesheet" RENAME TO "temporary_timesheet"`); + await queryRunner.query(`CREATE TABLE "timesheet" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "duration" integer NOT NULL DEFAULT (0), "keyboard" integer NOT NULL DEFAULT (0), "mouse" integer NOT NULL DEFAULT (0), "overall" integer NOT NULL DEFAULT (0), "startedAt" datetime, "stoppedAt" datetime, "approvedAt" datetime, "submittedAt" datetime, "lockedAt" datetime, "isBilled" boolean NOT NULL DEFAULT (0), "status" varchar NOT NULL DEFAULT ('PENDING'), "deletedAt" datetime, "employeeId" varchar NOT NULL, "approvedById" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), CONSTRAINT "FK_8c8f821cb0fe0dd387491ea7d9e" FOREIGN KEY ("employeeId") REFERENCES "employee" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_aca65a79fe0c1ec9e6a59022c54" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_25b8df69c9b7f5752c6a6a6ef7f" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_6c1f81934a3f597b3b1a17f5623" FOREIGN KEY ("approvedById") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "timesheet"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "duration", "keyboard", "mouse", "overall", "startedAt", "stoppedAt", "approvedAt", "submittedAt", "lockedAt", "isBilled", "status", "deletedAt", "employeeId", "approvedById", "isActive", "isArchived") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "duration", "keyboard", "mouse", "overall", "startedAt", "stoppedAt", "approvedAt", "submittedAt", "lockedAt", "isBilled", "status", "deletedAt", "employeeId", "approvedById", "isActive", "isArchived" FROM "temporary_timesheet"`); + await queryRunner.query(`DROP TABLE "temporary_timesheet"`); + await queryRunner.query(`CREATE INDEX "IDX_23fdffa8369387d87101090684" ON "timesheet" ("status") `); + await queryRunner.query(`CREATE INDEX "IDX_c828facbb4250117f83416d9f7" ON "timesheet" ("isBilled") `); + await queryRunner.query(`CREATE INDEX "IDX_3502c60f98a7cda58dea75bcb5" ON "timesheet" ("lockedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_3f8fc4b5718fcaa913f9438e27" ON "timesheet" ("submittedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_6a79eb7534066b11f59243ede1" ON "timesheet" ("approvedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_f6558fbb3158ab90da1c41d943" ON "timesheet" ("stoppedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_930e2b28de9ecb1ea689d5a97a" ON "timesheet" ("startedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_25b8df69c9b7f5752c6a6a6ef7" ON "timesheet" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_aca65a79fe0c1ec9e6a59022c5" ON "timesheet" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_8c8f821cb0fe0dd387491ea7d9" ON "timesheet" ("employeeId") `); + await queryRunner.query(`CREATE INDEX "IDX_6c1f81934a3f597b3b1a17f562" ON "timesheet" ("approvedById") `); + await queryRunner.query(`CREATE INDEX "IDX_42205a9e6af108364e5cc62dd4" ON "timesheet" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_f2d4cd3a7e839bfc7cb6b993ff" ON "timesheet" ("isArchived") `); + await queryRunner.query(`DROP INDEX "IDX_18dcdf754396f0cb0308dc91f4"`); + await queryRunner.query(`DROP INDEX "IDX_d1e8f22c02c5e949453dde7f2d"`); + await queryRunner.query(`DROP INDEX "IDX_1ddf2da35e34378fd845d80a18"`); + await queryRunner.query(`DROP INDEX "IDX_54776f6f5fd3c13c3bc1fbfac5"`); + await queryRunner.query(`DROP INDEX "IDX_e65393bb52aa8275b1392c73f7"`); + await queryRunner.query(`DROP INDEX "IDX_a89a849957e005bafb8e4220bc"`); + await queryRunner.query(`DROP INDEX "IDX_aed2d5cc5680fba9d387c7f931"`); + await queryRunner.query(`DROP INDEX "IDX_fa9018cb248ea0f3b2b30ef143"`); + await queryRunner.query(`DROP INDEX "IDX_189b79acd611870aba62b3594e"`); + await queryRunner.query(`DROP INDEX "IDX_a1f8fcd70164d915fe7dd4a1ec"`); + await queryRunner.query(`DROP INDEX "IDX_e80fb588b1086ce2a4f2244814"`); + await queryRunner.query(`DROP INDEX "IDX_402290e7045e0c10ef97d9f982"`); + await queryRunner.query(`DROP INDEX "IDX_722b9cb3a991c964d86396b6bc"`); + await queryRunner.query(`DROP INDEX "IDX_f447474d185cd70b3015853874"`); + await queryRunner.query(`DROP INDEX "IDX_79001d281ecb766005b3d331c1"`); + await queryRunner.query(`DROP INDEX "IDX_a1910a76044b971609b75ea165"`); + await queryRunner.query(`DROP INDEX "IDX_91a64228fbbe1516730a0cab5d"`); + await queryRunner.query(`ALTER TABLE "time_log" RENAME TO "temporary_time_log"`); + await queryRunner.query(`CREATE TABLE "time_log" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "startedAt" datetime, "stoppedAt" datetime, "logType" varchar NOT NULL DEFAULT ('TRACKED'), "source" varchar NOT NULL DEFAULT ('BROWSER'), "description" varchar, "reason" varchar, "isBillable" boolean NOT NULL DEFAULT (0), "deletedAt" datetime, "employeeId" varchar NOT NULL, "timesheetId" varchar, "projectId" varchar, "taskId" varchar, "organizationContactId" varchar, "isRunning" boolean, "version" varchar, "organizationTeamId" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), CONSTRAINT "FK_a89a849957e005bafb8e4220bc7" FOREIGN KEY ("employeeId") REFERENCES "employee" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_fa9018cb248ea0f3b2b30ef143b" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_aed2d5cc5680fba9d387c7f931d" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_e65393bb52aa8275b1392c73f72" FOREIGN KEY ("timesheetId") REFERENCES "timesheet" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_54776f6f5fd3c13c3bc1fbfac5b" FOREIGN KEY ("projectId") REFERENCES "organization_project" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_1ddf2da35e34378fd845d80a18b" FOREIGN KEY ("taskId") REFERENCES "task" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_d1e8f22c02c5e949453dde7f2d1" FOREIGN KEY ("organizationContactId") REFERENCES "organization_contact" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_18dcdf754396f0cb0308dc91f4c" FOREIGN KEY ("organizationTeamId") REFERENCES "organization_team" ("id") ON DELETE SET NULL ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "time_log"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "startedAt", "stoppedAt", "logType", "source", "description", "reason", "isBillable", "deletedAt", "employeeId", "timesheetId", "projectId", "taskId", "organizationContactId", "isRunning", "version", "organizationTeamId", "isActive", "isArchived") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "startedAt", "stoppedAt", "logType", "source", "description", "reason", "isBillable", "deletedAt", "employeeId", "timesheetId", "projectId", "taskId", "organizationContactId", "isRunning", "version", "organizationTeamId", "isActive", "isArchived" FROM "temporary_time_log"`); + await queryRunner.query(`DROP TABLE "temporary_time_log"`); + await queryRunner.query(`CREATE INDEX "IDX_18dcdf754396f0cb0308dc91f4" ON "time_log" ("organizationTeamId") `); + await queryRunner.query(`CREATE INDEX "IDX_d1e8f22c02c5e949453dde7f2d" ON "time_log" ("organizationContactId") `); + await queryRunner.query(`CREATE INDEX "IDX_1ddf2da35e34378fd845d80a18" ON "time_log" ("taskId") `); + await queryRunner.query(`CREATE INDEX "IDX_54776f6f5fd3c13c3bc1fbfac5" ON "time_log" ("projectId") `); + await queryRunner.query(`CREATE INDEX "IDX_e65393bb52aa8275b1392c73f7" ON "time_log" ("timesheetId") `); + await queryRunner.query(`CREATE INDEX "IDX_a89a849957e005bafb8e4220bc" ON "time_log" ("employeeId") `); + await queryRunner.query(`CREATE INDEX "IDX_aed2d5cc5680fba9d387c7f931" ON "time_log" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_fa9018cb248ea0f3b2b30ef143" ON "time_log" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_189b79acd611870aba62b3594e" ON "time_log" ("startedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_a1f8fcd70164d915fe7dd4a1ec" ON "time_log" ("stoppedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_e80fb588b1086ce2a4f2244814" ON "time_log" ("logType") `); + await queryRunner.query(`CREATE INDEX "IDX_402290e7045e0c10ef97d9f982" ON "time_log" ("source") `); + await queryRunner.query(`CREATE INDEX "IDX_722b9cb3a991c964d86396b6bc" ON "time_log" ("isBillable") `); + await queryRunner.query(`CREATE INDEX "IDX_f447474d185cd70b3015853874" ON "time_log" ("isRunning") `); + await queryRunner.query(`CREATE INDEX "IDX_79001d281ecb766005b3d331c1" ON "time_log" ("version") `); + await queryRunner.query(`CREATE INDEX "IDX_a1910a76044b971609b75ea165" ON "time_log" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_91a64228fbbe1516730a0cab5d" ON "time_log" ("isArchived") `); + } +} diff --git a/packages/core/src/tasks/task.entity.ts b/packages/core/src/tasks/task.entity.ts index c47f736a8e6..e0a2c78e816 100644 --- a/packages/core/src/tasks/task.entity.ts +++ b/packages/core/src/tasks/task.entity.ts @@ -321,7 +321,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { /** * TimeLog */ - @OneToMany(() => TimeLog, (timeLog) => timeLog.task) + @OneToMany(() => TimeLog, (it) => it.task) @JoinColumn() timeLogs?: ITimeLog[]; diff --git a/packages/core/src/time-tracking/time-log/dto/create-time-log.dto.ts b/packages/core/src/time-tracking/time-log/dto/create-time-log.dto.ts index a6a1eb092ef..b428c34b18d 100644 --- a/packages/core/src/time-tracking/time-log/dto/create-time-log.dto.ts +++ b/packages/core/src/time-tracking/time-log/dto/create-time-log.dto.ts @@ -5,13 +5,22 @@ import { IsEnum } from "class-validator"; import { IManualTimeInput, TimeLogSourceEnum, TimeLogType } from "@gauzy/contracts"; import { ManualTimeLogDTO } from "./manual-time-log.dto"; +/** + * DTO for creating manual time logs. + * Extends ManualTimeLogDTO and implements IManualTimeInput. + */ export class CreateManualTimeLogDTO extends ManualTimeLogDTO implements IManualTimeInput { - + /** + * Type of the time log (e.g., MANUAL). + */ @ApiProperty({ type: () => String, enum: TimeLogType }) @IsEnum(TimeLogType) @Transform(() => TimeLogType.MANUAL) logType: TimeLogType; + /** + * Source of the time log (e.g., WEB_TIMER). + */ @ApiProperty({ type: () => String, enum: TimeLogSourceEnum }) @IsEnum(TimeLogSourceEnum) @Transform(() => TimeLogSourceEnum.WEB_TIMER) diff --git a/packages/core/src/time-tracking/time-log/dto/manual-time-log.dto.ts b/packages/core/src/time-tracking/time-log/dto/manual-time-log.dto.ts index 7d6823159d7..86718b7ff3d 100644 --- a/packages/core/src/time-tracking/time-log/dto/manual-time-log.dto.ts +++ b/packages/core/src/time-tracking/time-log/dto/manual-time-log.dto.ts @@ -4,23 +4,31 @@ import { IEmployee, IManualTimeInput } from "@gauzy/contracts"; import { IsBeforeDate } from "./../../../shared/validators"; import { TenantOrganizationBaseDTO } from "./../../../core/dto"; +/** + * Data transfer object for creating or updating ManualTimeLog entities. + */ export class ManualTimeLogDTO extends TenantOrganizationBaseDTO implements IManualTimeInput { + /** + * The start date and time of the manual time log. + */ @ApiProperty({ type: () => Date }) - @IsNotEmpty({ - message: "Started date should not be empty" - }) + @IsNotEmpty({ message: "Started date should not be empty" }) @IsBeforeDate(ManualTimeLogDTO, (it) => it.stoppedAt, { message: "Started date must be before stopped date" }) startedAt: Date; + /** + * The end date and time of the manual time log. + */ @ApiProperty({ type: () => Date }) - @IsNotEmpty({ - message: "Stopped date should not be empty" - }) + @IsNotEmpty({ message: "Stopped date should not be empty" }) stoppedAt: Date; + /** + * The ID of the employee associated with the manual time log. + */ @ApiProperty({ type: () => String }) @IsNotEmpty() @IsUUID() diff --git a/packages/core/src/time-tracking/time-log/dto/update-time-log.dto.ts b/packages/core/src/time-tracking/time-log/dto/update-time-log.dto.ts index 4221e3bb2d0..6ca66095e19 100644 --- a/packages/core/src/time-tracking/time-log/dto/update-time-log.dto.ts +++ b/packages/core/src/time-tracking/time-log/dto/update-time-log.dto.ts @@ -1,5 +1,4 @@ import { IManualTimeInput } from "@gauzy/contracts"; import { ManualTimeLogDTO } from "./manual-time-log.dto"; -export class UpdateManualTimeLogDTO extends ManualTimeLogDTO - implements IManualTimeInput { } \ No newline at end of file +export class UpdateManualTimeLogDTO extends ManualTimeLogDTO implements IManualTimeInput { } diff --git a/packages/core/src/time-tracking/time-log/time-log.controller.ts b/packages/core/src/time-tracking/time-log/time-log.controller.ts index e4f635b71d9..127409a4e58 100644 --- a/packages/core/src/time-tracking/time-log/time-log.controller.ts +++ b/packages/core/src/time-tracking/time-log/time-log.controller.ts @@ -41,11 +41,20 @@ export class TimeLogController { private readonly timeLogService: TimeLogService ) { } + /** + * Get conflicting timer logs based on the provided entity. + * @param entity The entity with information for checking conflicts. + * @returns An array of conflicting timer logs. + */ @ApiOperation({ summary: 'Get Timer Logs Conflict' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Successfully retrieved conflicting timer logs', + isArray: true, + }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: - 'Invalid input, The response body may contain clues as to what went wrong' + description: 'Invalid input. The response body may contain clues as to what went wrong.', }) @Get('conflict') async getConflict( @@ -54,164 +63,196 @@ export class TimeLogController { return await this.timeLogService.checkConflictTime(entity); } + /** + * Get daily report for timer logs based on the provided options. + * @param options The options for retrieving the daily report. + * @returns The daily report for timer logs. + */ @ApiOperation({ summary: 'Find Timer Log by id' }) @ApiResponse({ status: HttpStatus.OK, - description: 'Found one record' /*, type: T*/ + description: 'Successfully retrieved the daily report', }) @ApiResponse({ status: HttpStatus.NOT_FOUND, - description: 'Record not found' + description: 'No records found for the provided options', }) @Get('report/daily') @UsePipes(new ValidationPipe({ whitelist: true, transform: true })) async getDailyReport( @Query() options: TimeLogQueryDTO - ) { + ): Promise { return await this.timeLogService.getDailyReport(options); } + /** + * Get chart data for the daily report of timer logs based on the provided options. + * @param options The options for retrieving the daily report chart data. + * @returns The chart data for the daily report of timer logs. + */ @ApiOperation({ summary: 'Find Timer Log by id' }) @ApiResponse({ status: HttpStatus.OK, - description: 'Found one record' /*, type: T*/ + description: 'Successfully retrieved the chart data for the daily report', }) @ApiResponse({ status: HttpStatus.NOT_FOUND, - description: 'Record not found' + description: 'No records found for the provided options', }) @Get('report/daily-chart') @UsePipes(new ValidationPipe({ whitelist: true })) async getDailyReportChartData( @Query() options: TimeLogQueryDTO - ): Promise { + ): Promise { return await this.timeLogService.getDailyReportChartData(options); } + /** + * Get report data for the owed amount based on the provided options. + * @param options The options for retrieving the owed amount report data. + * @returns The report data for the owed amount. + */ @ApiOperation({ summary: 'Get Owed Amount Report' }) @ApiResponse({ status: HttpStatus.OK, - description: 'Get report data' + description: 'Successfully retrieved the report data for the owed amount', }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: - 'Invalid input, The response body may contain clues as to what went wrong' + description: 'Invalid input. The response body may contain clues as to what went wrong.', }) @Get('report/owed-report') @UsePipes(new ValidationPipe({ whitelist: true, transform: true })) async getOwedAmountReport( @Query() options: TimeLogQueryDTO - ): Promise { + ): Promise { return await this.timeLogService.getOwedAmountReport(options); } + /** + * Get chart data for the owed amount report based on the provided options. + * @param options The options for retrieving the owed amount report chart data. + * @returns The chart data for the owed amount report. + */ @ApiOperation({ summary: 'Get Owed Amount Report Chart Data' }) @ApiResponse({ status: HttpStatus.OK, - description: 'Get report chart data' + description: 'Successfully retrieved the chart data for the owed amount report', }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: - 'Invalid input, The response body may contain clues as to what went wrong' + description: 'Invalid input. The response body may contain clues as to what went wrong.', }) @Get('report/owed-chart-data') @UsePipes(new ValidationPipe({ whitelist: true, transform: true })) async getOwedAmountReportChartData( @Query() options: TimeLogQueryDTO - ): Promise { + ): Promise { return await this.timeLogService.getOwedAmountReportChartData(options); } - @ApiOperation({ summary: 'Find Timer Log by id' }) + /** + * Get the weekly report for timer logs based on the provided options. + * @param options The options for retrieving the weekly report. + * @returns The weekly report for timer logs if found, otherwise null. + */ + @ApiOperation({ summary: 'Get Weekly Report' }) @ApiResponse({ status: HttpStatus.OK, - description: 'Found one record' /*, type: T*/ + description: 'Successfully retrieved the weekly report for timer logs', }) @ApiResponse({ status: HttpStatus.NOT_FOUND, - description: 'Record not found' + description: 'No records found for the specified options', }) @Get('report/weekly') @UsePipes(new ValidationPipe({ whitelist: true, transform: true })) async getWeeklyReport( @Query() options: TimeLogQueryDTO - ) { + ): Promise { return await this.timeLogService.getWeeklyReport(options); } - @ApiOperation({ summary: 'Find Timer Log by id' }) + /** + * Get the time limit report for timer logs based on the provided options. + * @param options The options for retrieving the time limit report. + * @returns The time limit report for timer logs if found, otherwise null. + */ + @ApiOperation({ summary: 'Get Time Limit Report' }) @ApiResponse({ status: HttpStatus.OK, - description: 'Found one record' /*, type: T*/ + description: 'Successfully retrieved the time limit report for timer logs', }) @ApiResponse({ status: HttpStatus.NOT_FOUND, - description: 'Record not found' - }) - @ApiOperation({ summary: 'Time Limit' }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Found records' + description: 'No records found for the specified options', }) @Get('time-limit') @UsePipes(new ValidationPipe({ whitelist: true, transform: true })) async getTimeLimitReport( @Query() options: TimeLogLimitQueryDTO - ) { + ): Promise { return await this.timeLogService.getTimeLimit(options); } - @ApiOperation({ summary: 'Budget limit' }) + /** + * Get project budget limit based on the provided options. + * @param options The options for retrieving the project budget limit. + * @returns The project budget limit if found, otherwise null. + */ + @ApiOperation({ summary: 'Get Project Budget Limit' }) @ApiResponse({ status: HttpStatus.OK, - description: 'Found one record' + description: 'Successfully retrieved the project budget limit.' }) @ApiResponse({ status: HttpStatus.NOT_FOUND, - description: 'Record not found' - }) - @ApiOperation({ summary: 'Time Limit' }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Found records' + description: 'Project budget limit not found.' }) @Get('project-budget-limit') @UsePipes(new ValidationPipe({ whitelist: true, transform: true })) - async projectBudgetLimit( + async getProjectBudgetLimit( @Query() options: TimeLogQueryDTO ) { - return await this.timeLogService.projectBudgetLimit(options); + return await this.timeLogService.getProjectBudgetLimit(options); } - @ApiOperation({ summary: 'Budget limit' }) + /** + * Retrieve the client budget limit based on the provided options. + * @param options The options for retrieving the client budget limit. + * @returns The client budget limit if found; otherwise, null. + */ + @ApiOperation({ summary: 'Get Client Budget Limit' }) @ApiResponse({ status: HttpStatus.OK, - description: 'Found one record' + description: 'Successfully retrieved the client budget limit.' }) @ApiResponse({ status: HttpStatus.NOT_FOUND, - description: 'Record not found' - }) - @ApiOperation({ summary: 'Time Limit' }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Found records' + description: 'Client budget limit not found.' }) @Get('client-budget-limit') @UsePipes(new ValidationPipe({ whitelist: true, transform: true })) async clientBudgetLimit( @Query() options: TimeLogQueryDTO ) { - return await this.timeLogService.clientBudgetLimit(options); + return await this.timeLogService.getClientBudgetLimit(options); } + /** + * Get timer logs based on provided options. + * @param options The options for querying timer logs. + * @returns An array of timer logs. + */ @ApiOperation({ summary: 'Get Timer Logs' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Successfully retrieved timer logs', + isArray: true, + }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: - 'Invalid input, The response body may contain clues as to what went wrong' + description: 'Invalid input. The response body may contain clues as to what went wrong.', }) @Get() @UsePipes(new ValidationPipe({ whitelist: true, transform: true })) @@ -221,14 +262,25 @@ export class TimeLogController { return await this.timeLogService.getTimeLogs(options); } + /** + * Find time log by ID + * @param id The ID of the time log. + * @param options Additional options for finding the time log. + * @returns The found time log. + */ @Get(':id') async findById( @Param('id', UUIDValidationPipe) id: ITimeLog['id'], @Query() options: FindOneOptions ): Promise { - return this.timeLogService.findOneByIdString(id, options); + return await this.timeLogService.findOneByIdString(id, options); } + /** + * Add manual time + * @param entity The data for creating a manual time log. + * @returns The created manual time log. + */ @ApiOperation({ summary: 'Add manual time' }) @ApiResponse({ status: HttpStatus.OK, @@ -236,8 +288,7 @@ export class TimeLogController { }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: - 'Invalid input, The response body may contain clues as to what went wrong' + description: 'Invalid input, The response body may contain clues as to what went wrong' }) @Post() @UseGuards(OrganizationPermissionGuard) @@ -248,6 +299,12 @@ export class TimeLogController { return await this.timeLogService.addManualTime(entity); } + /** + * Update time log + * @param id The ID of the time log entry to be updated. + * @param entity The updated data for the manual time log. + * @returns The updated time log entry. + */ @ApiOperation({ summary: 'Update time log' }) @ApiResponse({ status: HttpStatus.OK, @@ -268,6 +325,10 @@ export class TimeLogController { return await this.timeLogService.updateManualTime(id, entity); } + /** + * Delete time log + * @param deleteQuery The query parameters for deleting time logs. + */ @ApiOperation({ summary: 'Delete time log' }) @ApiResponse({ status: HttpStatus.OK, @@ -275,16 +336,15 @@ export class TimeLogController { }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: - 'Invalid input, The response body may contain clues as to what went wrong' + description: 'Invalid input, The response body may contain clues as to what went wrong' }) @UseGuards(OrganizationPermissionGuard) @Permissions(PermissionsEnum.ALLOW_DELETE_TIME) @Delete() @UsePipes(new ValidationPipe({ transform: true })) async deleteTimeLog( - @Query() query: DeleteTimeLogDTO + @Query() deleteQuery: DeleteTimeLogDTO ): Promise { - return await this.timeLogService.deleteTimeLogs(query); + return await this.timeLogService.deleteTimeLogs(deleteQuery); } } diff --git a/packages/core/src/time-tracking/time-log/time-log.entity.ts b/packages/core/src/time-tracking/time-log/time-log.entity.ts index b4ee549d852..2f6184386bc 100644 --- a/packages/core/src/time-tracking/time-log/time-log.entity.ts +++ b/packages/core/src/time-tracking/time-log/time-log.entity.ts @@ -27,6 +27,7 @@ import { @Entity('time_log') export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { + @ApiProperty({ type: () => 'timestamptz' }) @IsDateString() @Index() @@ -39,6 +40,15 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { @Column({ nullable: true }) stoppedAt?: Date; + /** + * Edited timestamp column + */ + @Column({ type: 'timestamp' }) + @IsDateString() + @Index() + @Column({ nullable: true }) + editedAt?: Date; + @ApiProperty({ type: () => String, enum: TimeLogType, default: TimeLogType.TRACKED }) @IsEnum(TimeLogType) @Index() @@ -86,7 +96,14 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { /** Additional fields */ duration: number; + + /** + * Indicates whether the TimeLog has been edited. + * If the value is true, it means the TimeLog has been edited. + * If the value is false or undefined, it means the TimeLog has not been edited. + */ isEdited?: boolean; + /* |-------------------------------------------------------------------------- | @ManyToOne @@ -94,18 +111,16 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { */ /** - * Employee + * Employee relationship */ - @ApiPropertyOptional({ type: () => String }) - @IsOptional() - @ManyToOne(() => Employee, (employee) => employee.timeLogs, { + @ManyToOne(() => Employee, (it) => it.timeLogs, { + /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @JoinColumn() employee: IEmployee; - @ApiPropertyOptional({ type: () => String }) - @IsOptional() + @ApiProperty({ type: () => String }) @IsUUID() @RelationId((it: TimeLog) => it.employee) @Index() @@ -113,11 +128,13 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { employeeId: IEmployee['id']; /** - * Timesheet + * Timesheet relationship */ - @ApiPropertyOptional({ type: () => Timesheet }) - @IsOptional() @ManyToOne(() => Timesheet, { + /** Indicates if the relation column value can be nullable or not. */ + nullable: true, + + /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @JoinColumn() @@ -134,9 +151,7 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { /** * Organization Project Relationship */ - @ApiPropertyOptional({ type: () => OrganizationProject }) - @IsOptional() - @ManyToOne(() => OrganizationProject, (project) => project.timeLogs, { + @ManyToOne(() => OrganizationProject, (it) => it.timeLogs, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -160,11 +175,12 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { /** * Task */ - @ApiPropertyOptional({ type: () => Task }) - @IsOptional() - @ManyToOne(() => Task, (task) => task.activities, { + @ManyToOne(() => Task, (it) => it.timeLogs, { + /** Indicates if the relation column value can be nullable or not. */ nullable: true, - onDelete: 'SET NULL' + + /** Defines the database cascade action on delete. */ + onDelete: 'SET NULL', }) @JoinColumn() task?: ITask; @@ -180,11 +196,12 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { /** * OrganizationContact */ - @ApiPropertyOptional({ type: () => OrganizationContact }) - @IsOptional() - @ManyToOne(() => OrganizationContact, (contact) => contact.timeLogs, { + @ManyToOne(() => OrganizationContact, (it) => it.timeLogs, { + /** Indicates if the relation column value can be nullable or not. */ nullable: true, - onDelete: 'SET NULL' + + /** Defines the database cascade action on delete. */ + onDelete: 'SET NULL', }) @JoinColumn() organizationContact?: IOrganizationContact; @@ -200,11 +217,12 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { /** * Organization Team */ - @ApiPropertyOptional({ type: () => OrganizationTeam }) - @IsOptional() @ManyToOne(() => OrganizationTeam, { + /** Indicates if the relation column value can be nullable or not. */ nullable: true, - onDelete: 'SET NULL' + + /** Defines the database cascade action on delete. */ + onDelete: 'SET NULL', }) @JoinColumn() organizationTeam?: IOrganizationTeam; @@ -228,7 +246,9 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { */ @ApiProperty({ type: () => TimeSlot, isArray: true }) @ManyToMany(() => TimeSlot, (timeLogs) => timeLogs.timeLogs, { + /** Database cascade action on update. */ onUpdate: 'CASCADE', + /** Database cascade action on delete. */ onDelete: 'CASCADE' }) timeSlots?: ITimeSlot[]; @@ -249,10 +269,11 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { this.duration = stoppedAt.diff(startedAt, 'seconds'); /** - * Check If, TimeLog is edited or not + * Sets the 'isEdited' property based on the presence of 'editedAt'. + * If 'editedAt' is defined, 'isEdited' is set to true; otherwise, it is set to false. */ - const createdAt = moment(this.createdAt, 'YYYY-MM-DD HH:mm:ss'); - const updatedAt = moment(this.updatedAt, 'YYYY-MM-DD HH:mm:ss'); - this.isEdited = updatedAt.diff(createdAt, 'seconds') > 0; + if ('editedAt' in this) { + this.isEdited = !!this.editedAt; + } } } diff --git a/packages/core/src/time-tracking/time-log/time-log.service.ts b/packages/core/src/time-tracking/time-log/time-log.service.ts index eb37a912960..d50d450bbf9 100644 --- a/packages/core/src/time-tracking/time-log/time-log.service.ts +++ b/packages/core/src/time-tracking/time-log/time-log.service.ts @@ -724,24 +724,16 @@ export class TimeLogService extends TenantAwareCrudService { } /** - * Project budget report + * Get Project budget report * * @param request * @returns */ - async projectBudgetLimit(request: IGetTimeLogReportInput) { - const { - organizationId, - employeeIds = [], - projectIds = [], - startDate, - endDate - } = request; - const tenantId = RequestContext.currentTenantId(); + async getProjectBudgetLimit(request: IGetTimeLogReportInput) { + const { organizationId, employeeIds = [], projectIds = [], startDate, endDate } = request; + const tenantId = RequestContext.currentTenantId() || request.tenantId; - const query = this.organizationProjectRepository.createQueryBuilder( - 'organization_project' - ); + const query = this.organizationProjectRepository.createQueryBuilder('organization_project'); query.setFindOptions({ select: { id: true, @@ -756,10 +748,9 @@ export class TimeLogService extends TenantAwareCrudService { query.innerJoinAndSelect(`timeLogs.employee`, 'employee'); query.andWhere( new Brackets((qb: WhereExpressionBuilder) => { - qb.andWhere( - `"${query.alias}"."organizationId" =:organizationId`, - { organizationId } - ); + qb.andWhere(`"${query.alias}"."organizationId" =:organizationId`, { + organizationId + }); qb.andWhere(`"${query.alias}"."tenantId" =:tenantId`, { tenantId }); @@ -770,8 +761,9 @@ export class TimeLogService extends TenantAwareCrudService { qb.andWhere(`"employee"."organizationId" =:organizationId`, { organizationId }); - qb.andWhere(`"employee"."tenantId" =:tenantId`, { tenantId }); - + qb.andWhere(`"employee"."tenantId" =:tenantId`, { + tenantId + }); if (isNotEmpty(employeeIds)) { qb.andWhere(`"employee"."id" IN (:...employeeIds)`, { employeeIds @@ -786,25 +778,20 @@ export class TimeLogService extends TenantAwareCrudService { }); qb.andWhere(`"timeLogs"."tenantId" =:tenantId`, { tenantId }); + // Date range condition const { start, end } = getDateRangeFormat( moment.utc(startDate), moment.utc(endDate) ); - qb.andWhere( - `"timeLogs"."startedAt" >= :startDate AND "timeLogs"."startedAt" < :endDate`, - { - startDate: start, - endDate: end - } - ); + qb.andWhere(`"timeLogs"."startedAt" >= :startDate AND "timeLogs"."startedAt" < :endDate`, { + startDate: start, + endDate: end + }); if (isNotEmpty(employeeIds)) { - qb.andWhere( - `"timeLogs"."employeeId" IN (:...employeeIds)`, - { - employeeIds - } - ); + qb.andWhere(`"timeLogs"."employeeId" IN (:...employeeIds)`, { + employeeIds + }); } if (isNotEmpty(projectIds)) { qb.andWhere(`"timeLogs"."projectId" IN (:...projectIds)`, { @@ -815,61 +802,52 @@ export class TimeLogService extends TenantAwareCrudService { ); const organizationProjects = await query.getMany(); - const projects = organizationProjects.map( - ( - organizationProject: IOrganizationProject - ): IProjectBudgetLimitReport => { - const { budgetType, timeLogs = [] } = organizationProject; - const budget = organizationProject.budget || 0; + const projects = organizationProjects.map((organizationProject: IOrganizationProject): IProjectBudgetLimitReport => { + const { budgetType, timeLogs = [] } = organizationProject; + const budget = organizationProject.budget || 0; - let spent = 0; - let spentPercentage = 0; - let reamingBudget = 0; - - if (budgetType == OrganizationProjectBudgetTypeEnum.HOURS) { - spent = timeLogs.reduce((iteratee: any, log: ITimeLog) => { - return iteratee + log.duration / 3600; - }, 0); - } else { - spent = timeLogs.reduce((iteratee: any, log: ITimeLog) => { - let amount = 0; - if (log.employee) { - amount = - (log.duration / 3600) * - log.employee.billRateValue; - } - return iteratee + amount; - }, 0); - } + let spent = 0; + let spentPercentage = 0; + let reamingBudget = 0; - spentPercentage = (spent * 100) / budget; - reamingBudget = Math.max(budget - spent, 0); - - delete organizationProject['timeLogs']; - return { - project: organizationProject, - budgetType, - budget, - spent: parseFloat(spent.toFixed(2)), - reamingBudget: Number.isFinite(reamingBudget) - ? parseFloat(reamingBudget.toFixed(2)) - : 0, - spentPercentage: Number.isFinite(spentPercentage) - ? parseFloat(spentPercentage.toFixed(2)) - : 0 - }; + if (budgetType == OrganizationProjectBudgetTypeEnum.HOURS) { + spent = timeLogs.reduce((iteratee: any, log: ITimeLog) => { + return iteratee + log.duration / 3600; + }, 0); + } else { + spent = timeLogs.reduce((iteratee: any, log: ITimeLog) => { + let amount = 0; + if (log.employee) { + amount = (log.duration / 3600) * log.employee.billRateValue; + } + return iteratee + amount; + }, 0); } - ); + + spentPercentage = (spent * 100) / budget; + reamingBudget = Math.max(budget - spent, 0); + + /** */ + delete organizationProject['timeLogs']; + return { + project: organizationProject, + budgetType, + budget, + spent: parseFloat(spent.toFixed(2)), + reamingBudget: Number.isFinite(reamingBudget) ? parseFloat(reamingBudget.toFixed(2)) : 0, + spentPercentage: Number.isFinite(spentPercentage) ? parseFloat(spentPercentage.toFixed(2)) : 0 + }; + }); return projects; } /** - * Client budget report + * Get Client budget report * * @param request * @returns */ - async clientBudgetLimit(request: IGetTimeLogReportInput) { + async getClientBudgetLimit(request: IGetTimeLogReportInput) { const { organizationId, employeeIds = [], @@ -1117,135 +1095,148 @@ export class TimeLogService extends TenantAwareCrudService { return query; } - async addManualTime(request: IManualTimeInput): Promise { - const tenantId = RequestContext.currentTenantId(); - const { employeeId, startedAt, stoppedAt, organizationId } = request; + /** + * Adds a manual time log entry. + * + * @param request The input data for the manual time log. + * @returns The created time log entry. + */ + async addManualTime(request: IManualTimeInput): Promise { + try { + const tenantId = RequestContext.currentTenantId(); + const { employeeId, startedAt, stoppedAt, organizationId } = request; + + // Validate input + if (!startedAt || !stoppedAt) { + throw new BadRequestException('Please select valid Date, start time and end time'); + } - if (!startedAt || !stoppedAt) { - throw new BadRequestException('Please select valid Date, start time and end time'); - } + // Retrieve employee information + const employee = await this.employeeRepository.findOne({ + where: { id: employeeId }, + relations: { organization: true } + }); - /** - * Get Employee - */ - const employee = await this.employeeRepository.findOne({ - where: { - id: employeeId - }, - relations: { - organization: true - } - }); - const isDateAllow = this.allowDate( - startedAt, - stoppedAt, - employee.organization - ); + // Check if the selected date and time range is allowed for the organization + const isDateAllow = this.allowDate(startedAt, stoppedAt, employee.organization); - if (!isDateAllow) { - throw new BadRequestException('Please select valid Date, start time and end time'); - } + if (!isDateAllow) { + throw new BadRequestException('Please select valid Date, start time and end time'); + } - const conflicts = await this.checkConflictTime({ - startDate: startedAt, - endDate: stoppedAt, - employeeId, - organizationId, - tenantId, - ...(request.id ? { ignoreId: request.id } : {}) - }); + // Check for conflicts with existing time logs + const conflicts = await this.checkConflictTime({ + startDate: startedAt, + endDate: stoppedAt, + employeeId, + organizationId, + tenantId, + ...(request.id ? { ignoreId: request.id } : {}) + }); - if (isNotEmpty(conflicts)) { - const times: IDateRange = { - start: new Date(startedAt), - end: new Date(stoppedAt) - }; - for await (const timeLog of conflicts) { - const { timeSlots = [] } = timeLog; - for await (const timeSlot of timeSlots) { - await this.commandBus.execute( - new DeleteTimeSpanCommand(times, timeLog, timeSlot) - ); + // Resolve conflicts by deleting conflicting time slots + if (conflicts && conflicts.length > 0) { + const times: IDateRange = { + start: new Date(startedAt), + end: new Date(stoppedAt) + }; + for await (const timeLog of conflicts) { + const { timeSlots = [] } = timeLog; + for await (const timeSlot of timeSlots) { + await this.commandBus.execute( + new DeleteTimeSpanCommand(times, timeLog, timeSlot) + ); + } } } + + // Create the new time log entry + return await this.commandBus.execute( + new TimeLogCreateCommand(request) + ); + } catch (error) { + // Handle exceptions appropriately + throw new BadRequestException('Failed to add manual time log'); } - return await this.commandBus.execute(new TimeLogCreateCommand(request)); } + /** + * Updates a manual time log entry. + * + * @param id The ID of the time log entry to be updated. + * @param request The updated data for the manual time log. + * @returns The updated time log entry. + */ async updateManualTime( - id: string, + id: ITimeLog['id'], request: IManualTimeInput - ): Promise { - const tenantId = RequestContext.currentTenantId(); - const { startedAt, stoppedAt, employeeId, organizationId } = request; + ): Promise { + try { + const tenantId = RequestContext.currentTenantId(); + const { startedAt, stoppedAt, employeeId, organizationId } = request; + + // Validate input + if (!startedAt || !stoppedAt) { + throw new BadRequestException('Please select valid Date start and end time'); + } - if (!startedAt || !stoppedAt) { - throw new BadRequestException( - 'Please select valid Date start and end time' - ); - } + // Retrieve employee information + const employee = await this.employeeRepository.findOne({ + where: { id: employeeId }, + relations: { organization: true } + }); - /** - * Get Employee - */ - const employee = await this.employeeRepository.findOne({ - where: { - id: employeeId - }, - relations: { - organization: true + // Check if the selected date and time range is allowed for the organization + const isDateAllow = this.allowDate(startedAt, stoppedAt, employee.organization); + + if (!isDateAllow) { + throw new BadRequestException('Please select valid Date start and end time'); } - }); - /** - * Check future date allow - */ - const isDateAllow = this.allowDate( - startedAt, - stoppedAt, - employee.organization - ); - if (!isDateAllow) { - throw new BadRequestException( - 'Please select valid Date start and end time' - ); - } - /** - * Check Conflicts TimeLogs - */ - const timeLog = await this.timeLogRepository.findOneBy({ - id: id - }); - const conflicts = await this.checkConflictTime({ - startDate: startedAt, - endDate: stoppedAt, - employeeId, - organizationId, - tenantId, - ...(id ? { ignoreId: id } : {}) - }); - if (isNotEmpty(conflicts)) { - const times: IDateRange = { - start: new Date(startedAt), - end: new Date(stoppedAt) - }; - for await (const timeLog of conflicts) { - const { timeSlots = [] } = timeLog; - for await (const timeSlot of timeSlots) { - await this.commandBus.execute( - new DeleteTimeSpanCommand(times, timeLog, timeSlot) - ); + // Check for conflicts with existing time logs + const timeLog = await this.timeLogRepository.findOneBy({ + id: id + }); + + const conflicts = await this.checkConflictTime({ + startDate: startedAt, + endDate: stoppedAt, + employeeId, + organizationId, + tenantId, + ...(id ? { ignoreId: id } : {}) + }); + + // Resolve conflicts by deleting conflicting time slots + if (isNotEmpty(conflicts)) { + const times: IDateRange = { + start: new Date(startedAt), + end: new Date(stoppedAt) + }; + for await (const timeLog of conflicts) { + const { timeSlots = [] } = timeLog; + for await (const timeSlot of timeSlots) { + await this.commandBus.execute( + new DeleteTimeSpanCommand(times, timeLog, timeSlot) + ); + } } } - } - await this.commandBus.execute( - new TimeLogUpdateCommand(request, timeLog) - ); + // Update the last edited date for the manual time log + request.editedAt = new Date(); - return await this.timeLogRepository.findOneBy({ - id: request.id - }); + // Execute the command to update the time log + await this.commandBus.execute( + new TimeLogUpdateCommand(request, timeLog) + ); + + // Retrieve the updated time log entry + return await this.timeLogRepository.findOneBy({ id: request.id }); + } catch (error) { + // Handle exceptions appropriately + throw new BadRequestException('Failed to update manual time log'); + } } async deleteTimeLogs( diff --git a/packages/core/src/time-tracking/timesheet/timesheet.entity.ts b/packages/core/src/time-tracking/timesheet/timesheet.entity.ts index bd140effc2d..5ef3d124a98 100644 --- a/packages/core/src/time-tracking/timesheet/timesheet.entity.ts +++ b/packages/core/src/time-tracking/timesheet/timesheet.entity.ts @@ -4,20 +4,20 @@ import { RelationId, ManyToOne, JoinColumn, - Index + Index, + AfterLoad } from 'typeorm'; -import { IEmployee, ITimesheet, IUser, TimesheetStatus } from '@gauzy/contracts'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsDateString, IsEnum, IsNumber, IsOptional, IsUUID } from 'class-validator'; +import { IEmployee, ITimesheet, IUser, TimesheetStatus } from '@gauzy/contracts'; import { Employee, TenantOrganizationBaseEntity, User } from './../../core/entities/internal'; -import { IsBoolean, IsDateString, IsEnum, IsNumber, IsOptional, IsUUID } from 'class-validator'; @Entity('timesheet') -export class Timesheet extends TenantOrganizationBaseEntity - implements ITimesheet { +export class Timesheet extends TenantOrganizationBaseEntity implements ITimesheet { @ApiPropertyOptional({ type: () => Number, default: 0 }) @IsOptional() @@ -76,6 +76,15 @@ export class Timesheet extends TenantOrganizationBaseEntity @Column({ nullable: true }) lockedAt?: Date; + /** + * Edited timestamp column + */ + @Column({ type: 'timestamp' }) + @IsDateString() + @Index() + @Column({ nullable: true }) + editedAt?: Date; + @ApiPropertyOptional({ type: () => Boolean, default: false }) @IsOptional() @IsBoolean() @@ -90,6 +99,14 @@ export class Timesheet extends TenantOrganizationBaseEntity @Column({ default: TimesheetStatus.PENDING }) status: string; + /** Additional fields */ + + /** + * Indicates whether the Timesheet has been edited. + * If the value is true, it means the Timesheet has been edited. + * If the value is false or undefined, it means the Timesheet has not been edited. + */ + isEdited?: boolean; /* |-------------------------------------------------------------------------- @@ -100,9 +117,8 @@ export class Timesheet extends TenantOrganizationBaseEntity /** * Employee */ - @ApiPropertyOptional({ type: () => Employee }) - @IsOptional() - @ManyToOne(() => Employee, (employee) => employee.timesheets, { + @ManyToOne(() => Employee, (it) => it.timesheets, { + /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @JoinColumn() @@ -116,11 +132,12 @@ export class Timesheet extends TenantOrganizationBaseEntity employeeId?: IEmployee['id']; /** - * Approve By Employee + * Approve By User */ - @ApiPropertyOptional({ type: () => Employee }) - @IsOptional() - @ManyToOne(() => User) + @ManyToOne(() => User, { + /** Indicates if the relation column value can be nullable or not. */ + nullable: true, + }) @JoinColumn() approvedBy?: IUser; @@ -131,4 +148,18 @@ export class Timesheet extends TenantOrganizationBaseEntity @Index() @Column({ nullable: true }) approvedById?: IUser['id']; + + /** + * Called after entity is loaded. + */ + @AfterLoad() + afterLoadEntity?() { + /** + * Sets the 'isEdited' property based on the presence of 'editedAt'. + * If 'editedAt' is defined, 'isEdited' is set to true; otherwise, it is set to false. + */ + if ('editedAt' in this) { + this.isEdited = !!this.editedAt; + } + } }