diff --git a/backend/Dockerfile b/backend/Dockerfile index 5b93d9da..5a7f515d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -23,19 +23,20 @@ RUN apt-get clean && rm -rf /var/lib/apt/lists/* RUN pip install --upgrade pip -RUN pip install poetry +# Install setuptools==68.2.2 to avoid the following error: +# ERROR: setuptools==58.1.0 is used in combination with setuptools-scm>=8.x +RUN pip install setuptools==68.2.2 WORKDIR /backend COPY requirements.txt requirements.txt -COPY requirements_dev.txt requirements_dev.txt -COPY poetry.lock poetry.lock -COPY pyproject.toml pyproject.toml -RUN poetry install --no-interaction +RUN pip install -r requirements.txt COPY . . COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +EXPOSE 8000 + CMD ["/usr/bin/supervisord"] diff --git a/backend/fly.toml b/backend/fly.toml new file mode 100644 index 00000000..dd995236 --- /dev/null +++ b/backend/fly.toml @@ -0,0 +1,18 @@ + +app = "integraflow-backend" +primary_region = "fra" +swap_size_mb = 512 + +[build] + dockerfile = "Dockerfile" + +[[vm]] + size = "shared-cpu-2x" + memory = "512mb" + +[http_service] + internal_port = 8000 + force_https = true + auto_stop_machines = "stop" + auto_start_machines = true + min_machines_running = 1 diff --git a/backend/integraflow/core/jwt_manager.py b/backend/integraflow/core/jwt_manager.py index aa3ddd73..c70292ad 100644 --- a/backend/integraflow/core/jwt_manager.py +++ b/backend/integraflow/core/jwt_manager.py @@ -1,3 +1,4 @@ +import base64 import json import logging from os.path import exists, join @@ -15,7 +16,7 @@ from jwt import api_jws from jwt.algorithms import RSAAlgorithm -from .utils import build_absolute_uri, get_domain +from .utils import build_absolute_uri, get_domain, is_base_64 logger = logging.getLogger(__name__) @@ -92,7 +93,10 @@ def get_private_key(cls) -> rsa.RSAPrivateKey: @classmethod def _get_private_key(cls, pem: Union[str, bytes]) -> rsa.RSAPrivateKey: if isinstance(pem, str): - pem = pem.encode("utf-8") + if is_base_64(pem): + pem = base64.b64decode(pem) + else: + pem = pem.encode("utf-8") password: Union[str, bytes, None] = settings.RSA_PRIVATE_PASSWORD if isinstance(password, str): diff --git a/backend/integraflow/core/utils/__init__.py b/backend/integraflow/core/utils/__init__.py index 7e4d033e..8b4dce36 100644 --- a/backend/integraflow/core/utils/__init__.py +++ b/backend/integraflow/core/utils/__init__.py @@ -1,8 +1,9 @@ +import base64 +import binascii import datetime import secrets import socket import string -import pytz from random import choice from typing import ( TYPE_CHECKING, @@ -12,10 +13,11 @@ Optional, Tuple, TypeVar, - Union + Union, ) from urllib.parse import urljoin, urlparse +import pytz from celery.utils.log import get_task_logger from django.conf import settings from django.db import IntegrityError, transaction @@ -382,3 +384,11 @@ def __eq__(self, other): self.name == other.name and self.expression == other.expression ) return super().__eq__(other) + + +def is_base_64(text: str): + try: + base64.b64decode(text, validate=True) + return True + except binascii.Error: + return False diff --git a/backend/integraflow/graphql/user/mutations/authentication/google_user_auth.py b/backend/integraflow/graphql/user/mutations/authentication/google_user_auth.py index e4875cf4..f0793b3c 100644 --- a/backend/integraflow/graphql/user/mutations/authentication/google_user_auth.py +++ b/backend/integraflow/graphql/user/mutations/authentication/google_user_auth.py @@ -1,3 +1,5 @@ +import json +import logging from typing import Dict, cast import graphene @@ -20,6 +22,8 @@ GOOGLE_AUTH_CLIENT_CREDENTIALS = settings.GOOGLE_AUTH_CLIENT_CREDENTIALS +logger = logging.getLogger(__name__) + class GoogleUserAuth(BaseMutation): """ @@ -67,8 +71,8 @@ class Meta: @classmethod def _get_credentials(cls, code: str) -> Dict[str, str]: try: - flow = Flow.from_client_secrets_file( - GOOGLE_AUTH_CLIENT_CREDENTIALS, + flow = Flow.from_client_config( + json.loads(GOOGLE_AUTH_CLIENT_CREDENTIALS), scopes=[ "https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email", @@ -83,7 +87,9 @@ def _get_credentials(cls, code: str) -> Dict[str, str]: clock_skew_in_seconds=10 ) return credentials # type: ignore - except Exception: + except Exception as err: + logger.exception(f"Google auth error: {err}") + raise ValidationError( "Failed to fetch user info from google auth.", ) diff --git a/backend/integraflow/survey/migrations/0015_alter_surveyresponse_options.py b/backend/integraflow/survey/migrations/0015_alter_surveyresponse_options.py new file mode 100644 index 00000000..c5b3cf8c --- /dev/null +++ b/backend/integraflow/survey/migrations/0015_alter_surveyresponse_options.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.23 on 2025-03-24 19:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('survey', '0014_auto_20240511_2019'), + ] + + operations = [ + migrations.AlterModelOptions( + name='surveyresponse', + options={'ordering': ['-created_at'], 'verbose_name': 'SurveyResponse', 'verbose_name_plural': 'SurveyResponses'}, + ), + ] diff --git a/backend/supervisord.conf b/backend/supervisord.conf index cb3f230e..57037965 100644 --- a/backend/supervisord.conf +++ b/backend/supervisord.conf @@ -10,13 +10,13 @@ stdout_logfile_maxbytes=0 redirect_stderr=true [program:django] -command=gunicorn --bind :8000 --workers 8 --worker-class integraflow.asgi.gunicorn_worker.UvicornWorker integraflow.asgi:application +command=gunicorn --bind :8000 --workers 2 --worker-class integraflow.asgi.gunicorn_worker.UvicornWorker integraflow.asgi:application stdout_logfile=/dev/fd/1 stdout_logfile_maxbytes=0 redirect_stderr=true [program:celery] -command=celery -A integraflow worker --loglevel=info +command=celery -A integraflow worker --loglevel=info --beat stdout_logfile=/dev/fd/1 stdout_logfile_maxbytes=0 redirect_stderr=true