From ba15bef1e89f4cf7509b01d2c97f33586bd87844 Mon Sep 17 00:00:00 2001 From: Tom JEANNESSON Date: Thu, 31 Oct 2024 22:34:13 +0100 Subject: [PATCH 01/12] fix(type_factory): corrected Date type to string --- backend/nango/bridge/type_factory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/nango/bridge/type_factory.py b/backend/nango/bridge/type_factory.py index 89c61f0..4ef4ed1 100644 --- a/backend/nango/bridge/type_factory.py +++ b/backend/nango/bridge/type_factory.py @@ -46,7 +46,7 @@ def get_type_name(self, serializer: Serializer | ListSerializer) -> str: case "CharField" | "EmailField" | "ChoiceField": return "string" case "DateField" | "DateTimeField": - return "Date" + return "string" case "IntegerField" | "FloatField": return "number" return serializer.__class__.__name__.split("Serializer")[0] @@ -149,7 +149,7 @@ def map_serializer_field_to_type(self, field: Field) -> str: # noqa: PLR0911 case "CharField" | "EmailField" | "ChoiceField": return "string" case "DateField" | "DateTimeField": - return "Date" + return "string" case "IntegerField" | "FloatField": return "number" From 6f86ca0ff19243ff74c303e74c13a1ed8d7eab26 Mon Sep 17 00:00:00 2001 From: Tom JEANNESSON Date: Tue, 5 Nov 2024 18:50:17 +0100 Subject: [PATCH 02/12] fix(werkzeug): stop the reload from going crazy --- backend/config/settings/development.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/config/settings/development.py b/backend/config/settings/development.py index aec4fed..62e7001 100644 --- a/backend/config/settings/development.py +++ b/backend/config/settings/development.py @@ -45,3 +45,6 @@ ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] # noqa: S104 CORS_ALLOWED_ORIGINS = [f"http://{host}:3000" for host in ALLOWED_HOSTS] CSRF_TRUSTED_ORIGINS = [f"http://{host}:3000" for host in ALLOWED_HOSTS] + +# stop the reloader from going crazy +RUNSERVERPLUS_POLLER_RELOADER_TYPE = "stat" From 12d9f290c48fa421a22a68827ed2175521bb6e2d Mon Sep 17 00:00:00 2001 From: Tom JEANNESSON Date: Thu, 7 Nov 2024 12:21:48 +0100 Subject: [PATCH 03/12] fix(type_factory): ListField & MethodField --- README.md | 6 ++++++ backend/nango/bridge/float_serializer_method_field.py | 5 +++++ backend/nango/bridge/string_serializer_method_field.py | 5 +++++ backend/nango/bridge/type_factory.py | 10 ++++++++-- 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 backend/nango/bridge/float_serializer_method_field.py create mode 100644 backend/nango/bridge/string_serializer_method_field.py diff --git a/README.md b/README.md index 4d489d2..19c8d9c 100644 --- a/README.md +++ b/README.md @@ -132,3 +132,9 @@ Now, you can pull directly from nango to get all our latest updates by running ` If you have problems with pushing to the wrong remote, run: `git push -u origin ` to set the upstream branch for the current checked out branch. Otherwise, you can manually edit the git config with `git config --edit` + +### Generate a token for AWS in order to prove private images + +`echo -n "$USERNAME:$GH_TOKEN" | base64` + +The GH_Token need the read:packages permission. diff --git a/backend/nango/bridge/float_serializer_method_field.py b/backend/nango/bridge/float_serializer_method_field.py new file mode 100644 index 0000000..3477fff --- /dev/null +++ b/backend/nango/bridge/float_serializer_method_field.py @@ -0,0 +1,5 @@ +from rest_framework import serializers + + +class FloatSerializerMethodField(serializers.SerializerMethodField): + """Used to type method serializers.""" diff --git a/backend/nango/bridge/string_serializer_method_field.py b/backend/nango/bridge/string_serializer_method_field.py new file mode 100644 index 0000000..a96c053 --- /dev/null +++ b/backend/nango/bridge/string_serializer_method_field.py @@ -0,0 +1,5 @@ +from rest_framework import serializers + + +class StringSerializerMethodField(serializers.SerializerMethodField): + """Used to type method serializers.""" diff --git a/backend/nango/bridge/type_factory.py b/backend/nango/bridge/type_factory.py index 4ef4ed1..3becd81 100644 --- a/backend/nango/bridge/type_factory.py +++ b/backend/nango/bridge/type_factory.py @@ -9,6 +9,9 @@ from rest_framework.relations import ManyRelatedField, PrimaryKeyRelatedField from rest_framework.serializers import ListSerializer, Serializer +from nango.bridge.float_serializer_method_field import FloatSerializerMethodField +from nango.bridge.string_serializer_method_field import StringSerializerMethodField + if TYPE_CHECKING: from pathlib import Path @@ -37,7 +40,7 @@ def __init__(self, **kwargs: dict[str, any]) -> None: def get_type_name(self, serializer: Serializer | ListSerializer) -> str: """Return the type name, deduced from the serializer's name.""" - if isinstance(serializer, ListSerializer): + if isinstance(serializer, ListSerializer | ListField): return self.get_type_name(serializer.child) match serializer.__class__.__name__: @@ -161,7 +164,10 @@ def map_serializer_field_to_type(self, field: Field) -> str: # noqa: PLR0911 return "number[]" if isinstance(field, ListSerializer | ListField): return f"{self.get_type_name(field)}[]" - + if isinstance(field, StringSerializerMethodField): + return "string" + if isinstance(field, FloatSerializerMethodField): + return "number" print(f"Impossible to get a TypeScript match for {field} ({type(field)})") # noqa: T201 return "" From f30d3b3d3bfc343883c5c3acf6ca7d5544904b04 Mon Sep 17 00:00:00 2001 From: Tom JEANNESSON Date: Thu, 7 Nov 2024 12:28:51 +0100 Subject: [PATCH 04/12] fix(ci): up-prod --- .github/workflows/prod-build-check.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/prod-build-check.yml b/.github/workflows/prod-build-check.yml index 146b3ea..d9b1f8b 100644 --- a/.github/workflows/prod-build-check.yml +++ b/.github/workflows/prod-build-check.yml @@ -28,10 +28,12 @@ jobs: python-version: "3.12" cache: "pip" # caching pip dependencies - - name: Create ./backend/.envs/.development files + - name: Create ./backend/.envs/.production files run: | python -m pip install cryptography python ./setup/env_file_generator.py production + echo NEXT_PUBLIC_FRONTEND_URL=\"http://localhost:3000/\" >> frontend/.env + echo NEXT_PUBLIC_BACKEND_URL=\"http://localhost:8000/\" >> frontend/.env - name: Start containers - run: make build-prod \ No newline at end of file + run: make up-prod \ No newline at end of file From 18833cd16cbaa3742629e4494bab17084b0a23b4 Mon Sep 17 00:00:00 2001 From: Tom JEANNESSON Date: Fri, 8 Nov 2024 21:39:58 +0100 Subject: [PATCH 05/12] feat(frontend): useMounted --- .github/workflows/deploy.yml | 7 +++++-- backend/config/urls.py | 6 ++---- backend/nango/bridge/int_serializer_method_field.py | 5 +++++ backend/nango/bridge/type_factory.py | 3 ++- frontend/src/components/pages/forms/signIn.tsx | 2 +- frontend/src/lib/useMounted.ts | 12 ++++++++++++ 6 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 backend/nango/bridge/int_serializer_method_field.py create mode 100644 frontend/src/lib/useMounted.ts diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2321c4e..7064c17 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -30,8 +30,11 @@ jobs: echo "Tagging with $TAG_NAME" VERSION=v$TAG_NAME echo "VERSION=$VERSION" >> $GITHUB_ENV - - make build-prod + echo NEXT_PUBLIC_FRONTEND_URL=\"https://grezy.org/\" >> frontend/.env # TODO: Change + echo NEXT_PUBLIC_BACKEND_URL=\"https://grezy.org/\" >> frontend/.env # TODO: Change + python -m pip install cryptography + python ./setup/env_file_generator.py production + make up-prod docker tag nextjs ghcr.io/${{ github.repository }}/nextjs:$VERSION docker tag nextjs ghcr.io/${{ github.repository }}/nextjs:latest docker tag nango ghcr.io/${{ github.repository }}/nango:$VERSION diff --git a/backend/config/urls.py b/backend/config/urls.py index 2d8533f..db7111c 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -18,10 +18,7 @@ from api.urls import main_api_router from django.http import HttpResponse from django.urls import include, path -from rest_framework_simplejwt.views import ( - TokenObtainPairView, - TokenRefreshView, -) +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView def health_check(request) -> HttpResponse: # noqa: ANN001, ARG001 @@ -34,4 +31,5 @@ def health_check(request) -> HttpResponse: # noqa: ANN001, ARG001 path("api/", include(main_api_router.urls)), path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), + path("api/token/verify/", TokenVerifyView.as_view(), name="token_verify"), ] diff --git a/backend/nango/bridge/int_serializer_method_field.py b/backend/nango/bridge/int_serializer_method_field.py new file mode 100644 index 0000000..544c240 --- /dev/null +++ b/backend/nango/bridge/int_serializer_method_field.py @@ -0,0 +1,5 @@ +from rest_framework import serializers + + +class IntSerializerMethodField(serializers.SerializerMethodField): + """Used to type method serializers.""" diff --git a/backend/nango/bridge/type_factory.py b/backend/nango/bridge/type_factory.py index 3becd81..c4d07a1 100644 --- a/backend/nango/bridge/type_factory.py +++ b/backend/nango/bridge/type_factory.py @@ -10,6 +10,7 @@ from rest_framework.serializers import ListSerializer, Serializer from nango.bridge.float_serializer_method_field import FloatSerializerMethodField +from nango.bridge.int_serializer_method_field import IntSerializerMethodField from nango.bridge.string_serializer_method_field import StringSerializerMethodField if TYPE_CHECKING: @@ -166,7 +167,7 @@ def map_serializer_field_to_type(self, field: Field) -> str: # noqa: PLR0911 return f"{self.get_type_name(field)}[]" if isinstance(field, StringSerializerMethodField): return "string" - if isinstance(field, FloatSerializerMethodField): + if isinstance(field, FloatSerializerMethodField | IntSerializerMethodField): return "number" print(f"Impossible to get a TypeScript match for {field} ({type(field)})") # noqa: T201 return "" diff --git a/frontend/src/components/pages/forms/signIn.tsx b/frontend/src/components/pages/forms/signIn.tsx index d172a8e..d6e331e 100644 --- a/frontend/src/components/pages/forms/signIn.tsx +++ b/frontend/src/components/pages/forms/signIn.tsx @@ -52,10 +52,10 @@ export default function SignInForm() { loggedIn.setState(true) user.setState({ name: userInfo.username, email: userInfo.email }) toast.success("Login successful!") + router.push(BASE_LOGGED_IN_URL + "/") } else { toast.error("An error has occurred, please try again...") } - router.push(BASE_LOGGED_IN_URL + "/") })} className="w-full space-y-4 py-8" > diff --git a/frontend/src/lib/useMounted.ts b/frontend/src/lib/useMounted.ts new file mode 100644 index 0000000..cc1ede8 --- /dev/null +++ b/frontend/src/lib/useMounted.ts @@ -0,0 +1,12 @@ +import { useEffect, useState } from "react" + +export const useMounted = () => { + const [mounted, setMounted] = useState() + // effects run only client-side + // so we can detect when the component is hydrated/mounted + // @see https://react.dev/reference/react/useEffect + useEffect(() => { + setMounted(true) + }, []) + return mounted +} From 3f084748e686469a1a31efbb70998ea8c2050bca Mon Sep 17 00:00:00 2001 From: Tom JEANNESSON Date: Sat, 9 Nov 2024 19:56:10 +0100 Subject: [PATCH 06/12] fix(ci): removed imports in __init__.py --- backend/config/settings/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/config/settings/__init__.py b/backend/config/settings/__init__.py index 1f5d6ef..e69de29 100644 --- a/backend/config/settings/__init__.py +++ b/backend/config/settings/__init__.py @@ -1,2 +0,0 @@ -from .development import * -from .production import * From 5ed0174fc538c7756364ac421dca1863ead5939d Mon Sep 17 00:00:00 2001 From: Xenepix Date: Sat, 9 Nov 2024 20:06:04 +0100 Subject: [PATCH 07/12] fix env file generator --- setup/env_file_generator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup/env_file_generator.py b/setup/env_file_generator.py index a3d57cb..5e716f7 100644 --- a/setup/env_file_generator.py +++ b/setup/env_file_generator.py @@ -80,9 +80,10 @@ def build_django_secrets(env_name: str) -> None: # Django # ------------------------------------------------------------------------------ DJANGO_SECRET_KEY="{get_or_generate_key(name="DJANGO_SECRET_KEY", multiplier=2)}" # noqa: S105 -DJANGO_DEBUG=True -IS_LOCAL=True +DJANGO_DEBUG={env_name!='Production'} +IS_LOCAL={env_name!='Production'} DJANGO_SETTINGS_MODULE="config.settings.{env_name}" +DB_SETUP='postgres' # (postgres or litestream) # Celery CELERY_FLOWER_USER="{get_or_generate_key(name="CELERY_FLOWER_USER")}" CELERY_FLOWER_PASSWORD="{get_or_generate_key(name="CELERY_FLOWER_PASSWORD", multiplier=2)}" # noqa: S105""" From fab8ecffe78121b30119459991edda3148223cc9 Mon Sep 17 00:00:00 2001 From: Tom JEANNESSON Date: Sat, 9 Nov 2024 20:08:00 +0100 Subject: [PATCH 08/12] fix(requirement): add gunicorn --- backend/requirements/production.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/requirements/production.txt b/backend/requirements/production.txt index 39bda30..bc54497 100644 --- a/backend/requirements/production.txt +++ b/backend/requirements/production.txt @@ -1 +1,3 @@ -r ./base.txt + +gunicorn==21.2.0 # https://github.com/benoitc/gunicorn \ No newline at end of file From 014ccd638f5dfbc8a1cf6364f4e1b7c68add1cfb Mon Sep 17 00:00:00 2001 From: Xenepix Date: Sat, 9 Nov 2024 20:10:07 +0100 Subject: [PATCH 09/12] fix(.envs): fix variables & logs for envs files generator --- setup/env_file_generator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup/env_file_generator.py b/setup/env_file_generator.py index 5e716f7..0e612a7 100644 --- a/setup/env_file_generator.py +++ b/setup/env_file_generator.py @@ -43,6 +43,7 @@ def build_postgres_env(env_name: str) -> None: folder: Path = make_folder(env_name) path: Path = folder.joinpath(".postgres") if path.exists(): + print(f".{env_name}/.postgres already exists. Stop.") # noqa: T201 return content: str = f"""# PostgreSQL\n @@ -64,6 +65,7 @@ def build_django_secrets(env_name: str) -> None: path: Path = folder.joinpath(".django") if path.exists(): + print(f".{env_name}/.django already exists. Stop.") # noqa: T201 return content: str = f"""# General\n @@ -80,8 +82,8 @@ def build_django_secrets(env_name: str) -> None: # Django # ------------------------------------------------------------------------------ DJANGO_SECRET_KEY="{get_or_generate_key(name="DJANGO_SECRET_KEY", multiplier=2)}" # noqa: S105 -DJANGO_DEBUG={env_name!='Production'} -IS_LOCAL={env_name!='Production'} +DJANGO_DEBUG={env_name!='production'} +IS_LOCAL={env_name!='production'} DJANGO_SETTINGS_MODULE="config.settings.{env_name}" DB_SETUP='postgres' # (postgres or litestream) # Celery From ee413ddc7de044a5925df7e7bfb383edc57dd86b Mon Sep 17 00:00:00 2001 From: Xenepix Date: Sat, 9 Nov 2024 20:12:16 +0100 Subject: [PATCH 10/12] fix(readme): remove todo list from readme --- README.md | 103 +++--------------------------------------------------- 1 file changed, 4 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 19c8d9c..b39eb2b 100644 --- a/README.md +++ b/README.md @@ -10,110 +10,15 @@ The bridge contains: - drf endpoints - backend routes - front types -- api call methods - -The workflow is simple: - -1. Create a model in your backend -2. Run the `make bridge` command -3. Import type and api call methods in your frontend +- api call methods (WIP) +- Tests (WIP) +- Scripts (for easy manual testing) (WIP) ## Activity ![Alt](https://repobeats.axiom.co/api/embed/3697d98ced5eddd922d97cdc1b47ecbc46b5f23c.svg "Repobeats analytics image") -##  Prerequisites - -###  Env variables - -Database: - -- POSTGRES_USER -- POSTGRES_PASSWORD - -Django: - -- DJANGO_SECRET_KEY - -Celery: - -- CELERY_FLOWER_USER (optional) -- CELERY_FLOWER_PASSWORD (optional) - -Stripe: - -- STRIPE_PUBLISHABLE_KEY -- STRIPE_SECRET_KEY -- STRIPE_ENDPOINT_SECRET (optional) - -Other: - -- FRONTEND_URL (optional) - -### Add node modules - -To add a node module with `npx`, please ensure to add your module to the `./frontend`'s `package.json` file and not to the `/`'s `package.json`. - -## Todo - -### Blue print - -- Django - - [x] Structure - - [x] Settings - - [x] envs files generator - - [x] Ruff - - [x] Requirements - - [x] Celery - - [ ] API - - [x] Structure - - [x] Libraries - - [ ] JWT -- NextJs - - [x] Structure - - [ ] Tailwind - - [ ] Typescript - - [ ] Eslint - - [ ] Shadcn - -- Github - - [x] Dependabot (front & back) - - [ ] CI (Tests, Lint, Build, Coverage) - - [x] Pre-commit hook (ruff) - -- Docker - - Development - - [ ] postgres - - [ ] litestream - - Production - - [ ] postgres - - [ ] litestream - (content: postgres / litestream, redis, traefik, celery (worker, beat), flower, django, mkdocs, nextjs) - -- [x] Semantic release -- [x] Mkdocs - -### Bridge - -- Backend - - [ ] View Generator - - [x] Serializer Generator - - [x] Dynamic fields from models - - [ ] Detail serializer - - Handle nested models for detail serializer - - [ ] ForeignKey - - [ ] ManyToMany - - [ ] OneToOne - - [ ] Chirurgical edit on fields - - [ ] Dynamic routes - - [ ] Retrieve types from models - - [ ] Progress bar - -- Frontend - - [ ] API call methods generator - - [ ] Types generator - -### Setup a new git repo +## Setup a new git repo Once you've created a new git repo from this template, we advise you enter the following command: From 43bbf2c6903c4c5464af45e4cd2ef0b748e401cb Mon Sep 17 00:00:00 2001 From: Xenepix Date: Sat, 9 Nov 2024 20:16:02 +0100 Subject: [PATCH 11/12] fix django key for local dev --- setup/env_file_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/env_file_generator.py b/setup/env_file_generator.py index 0e612a7..0cdc8d4 100644 --- a/setup/env_file_generator.py +++ b/setup/env_file_generator.py @@ -81,7 +81,7 @@ def build_django_secrets(env_name: str) -> None: STRIPE_ENDPOINT_SECRET = "" # Django # ------------------------------------------------------------------------------ -DJANGO_SECRET_KEY="{get_or_generate_key(name="DJANGO_SECRET_KEY", multiplier=2)}" # noqa: S105 +DJANGO_SECRET_KEY="{'brBDrH4Gb-65!' if env_name == 'development' else get_or_generate_key(name="DJANGO_SECRET_KEY", multiplier=2)}" # noqa: S105 DJANGO_DEBUG={env_name!='production'} IS_LOCAL={env_name!='production'} DJANGO_SETTINGS_MODULE="config.settings.{env_name}" From 862c1d3b5dcbdc6cb41dc0ffba0009ef913865c6 Mon Sep 17 00:00:00 2001 From: Tom JEANNESSON Date: Sat, 9 Nov 2024 20:19:07 +0100 Subject: [PATCH 12/12] fix(drf): DEFAULT_SCHEMA_CLASS only in dev mode --- backend/config/settings/development.py | 5 +++-- backend/config/settings/modules/drf.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/config/settings/development.py b/backend/config/settings/development.py index 62e7001..77b1ca6 100644 --- a/backend/config/settings/development.py +++ b/backend/config/settings/development.py @@ -3,7 +3,7 @@ from config.settings.base import * # noqa: F403 from config.settings.base import SECRET_KEY, env from config.settings.modules import * # noqa: F403 -from config.settings.modules import SIMPLE_JWT +from config.settings.modules import REST_FRAMEWORK, SIMPLE_JWT # Add signing key to JWT settings SIMPLE_JWT["SIGNING_KEY"] = SECRET_KEY @@ -22,6 +22,7 @@ # https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration INSTALLED_APPS += ["django_extensions"] # noqa: F405 +REST_FRAMEWORK["DEFAULT_SCHEMA_CLASS"] = "drf_spectacular.openapi.AutoSchema" # DATABASES # ------------------------------------------------------------------------------ @@ -42,7 +43,7 @@ # https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-DEFAULT_AUTO_FIELD DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" -ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] # noqa: S104 +ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1", "django"] # noqa: S104 CORS_ALLOWED_ORIGINS = [f"http://{host}:3000" for host in ALLOWED_HOSTS] CSRF_TRUSTED_ORIGINS = [f"http://{host}:3000" for host in ALLOWED_HOSTS] diff --git a/backend/config/settings/modules/drf.py b/backend/config/settings/modules/drf.py index ceeb277..fca32d2 100644 --- a/backend/config/settings/modules/drf.py +++ b/backend/config/settings/modules/drf.py @@ -2,7 +2,6 @@ REST_FRAMEWORK = { # To configure APISettings of DRF (rest_framework.settings.APISettings) - "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "PAGE_SIZE": 50, "DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework_simplejwt.authentication.JWTAuthentication",),