Skip to content

Commit

Permalink
Merge pull request #24 from grezy-software/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Xenepix authored Nov 9, 2024
2 parents 8229437 + 0ee1e1d commit f4b146c
Show file tree
Hide file tree
Showing 15 changed files with 74 additions and 120 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/prod-build-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
run: make up-prod
109 changes: 10 additions & 99 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -132,3 +37,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 <branch>` 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.
2 changes: 0 additions & 2 deletions backend/config/settings/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
from .development import *
from .production import *
8 changes: 6 additions & 2 deletions backend/config/settings/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
# ------------------------------------------------------------------------------
Expand All @@ -42,6 +43,9 @@
# 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]

# stop the reloader from going crazy
RUNSERVERPLUS_POLLER_RELOADER_TYPE = "stat"
1 change: 0 additions & 1 deletion backend/config/settings/modules/drf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",),
Expand Down
6 changes: 2 additions & 4 deletions backend/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"),
]
5 changes: 5 additions & 0 deletions backend/nango/bridge/float_serializer_method_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from rest_framework import serializers


class FloatSerializerMethodField(serializers.SerializerMethodField):
"""Used to type method serializers."""
5 changes: 5 additions & 0 deletions backend/nango/bridge/int_serializer_method_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from rest_framework import serializers


class IntSerializerMethodField(serializers.SerializerMethodField):
"""Used to type method serializers."""
5 changes: 5 additions & 0 deletions backend/nango/bridge/string_serializer_method_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from rest_framework import serializers


class StringSerializerMethodField(serializers.SerializerMethodField):
"""Used to type method serializers."""
15 changes: 11 additions & 4 deletions backend/nango/bridge/type_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
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.int_serializer_method_field import IntSerializerMethodField
from nango.bridge.string_serializer_method_field import StringSerializerMethodField

if TYPE_CHECKING:
from pathlib import Path

Expand Down Expand Up @@ -37,7 +41,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__:
Expand All @@ -46,7 +50,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]
Expand Down Expand Up @@ -149,7 +153,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"

Expand All @@ -161,7 +165,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 | IntSerializerMethodField):
return "number"
print(f"Impossible to get a TypeScript match for {field} ({type(field)})") # noqa: T201
return ""

Expand Down
2 changes: 2 additions & 0 deletions backend/requirements/production.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
-r ./base.txt

gunicorn==21.2.0 # https://github.com/benoitc/gunicorn
2 changes: 1 addition & 1 deletion frontend/src/components/pages/forms/signIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
>
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/lib/useMounted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useEffect, useState } from "react"

export const useMounted = () => {
const [mounted, setMounted] = useState<boolean>()
// 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
}
9 changes: 6 additions & 3 deletions setup/env_file_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -79,10 +81,11 @@ 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_DEBUG=True
IS_LOCAL=True
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}"
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"""
Expand Down

0 comments on commit f4b146c

Please sign in to comment.