Skip to content

Commit ba35324

Browse files
committed
sweeping changes to make the github actions work
1 parent ddfbdcd commit ba35324

File tree

25 files changed

+272
-284
lines changed

25 files changed

+272
-284
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ See notes:
101101
- Replaced Backend native connection of PostgreSQL/SQLAlchemy with MongoDB Motor/Beanie ODM
102102
- Removed Neo4j plugin
103103
- Removed Alembic Usage
104-
- Introduced new cookiecutter environment variables `mongo_host`, `mongo_user`, `mongo_password`, and `mongo_database`
104+
- Introduced new cookiecutter environment variables `mongodb_uri`, and `mongo_database`
105105
- Introduced support for Pydantic 2
106106

107107
[Historic changes from whythawk](https://github.com/whythawk/full-stack-fastapi-postgresql/releases)

cookiecutter.json

+1-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323
"smtp_emails_from_name": "Symona Adaro",
2424
"smtp_emails_to_email": "info@{{cookiecutter.domain_main}}",
2525

26-
"mongo_host": "changethis",
27-
"mongo_user": "changethis",
28-
"mongodb_password": "changethis",
26+
"mongodb_uri": "changethis",
2927
"mongodb_database": "changethis",
3028

3129
"traefik_constraint_tag": "{{cookiecutter.domain_main}}",

docs/development-guide.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,7 @@ The input variables, with their default values (some auto generated) are:
6161
- `smtp_emails_from_email`: The email account to use as the sender in the notification emails, it could be something like `info@your-custom-domain.com`.
6262
- `smtp_emails_from_name`: The email account name to use as the sender in the notification emails, it could be something like `Symona Adaro`.
6363
- `smtp_emails_to_email`: The email account to use as the recipient for `contact us` emails, it could be something like `requests@your-custom-domain.com`.
64-
- `mongo_host`: MongoDB host cluster URI password
65-
- `mongo_user`: MongoDB User account to access the cluster
66-
- `mongodb_password`: MongoDB password for access to the cluster
64+
- `mongodb_uri`: MongoDB URI for access to the cluster
6765
- `mongodb_database`: MongoDB database to have the application operate within
6866
- `traefik_constraint_tag`: The tag to be used by the internal Traefik load balancer (for example, to divide requests between backend and frontend) for production. Used to separate this stack from any other stack you might have. This should identify each stack in each environment (production, staging, etc).
6967
- `traefik_constraint_tag_staging`: The Traefik tag to be used while on staging.

docs/getting-started.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Running Cookiecutter to customise the deployment with your settings, and then bu
8383

8484
### Setting up a Mongo Connection
8585

86-
A Mongo connection can be set up one of two ways: At the cookiecutter generation step, provide the `mongo_host`, `mongo_user`, `mongo_password`, and `mongo_database` to inform the generator on how to connect to an Atlas cloud instance. Additionally, in the generated file, you can manually set the .env.MONGO_DATABASE_URI and it will connect to the Atlas cluster made locally.
86+
A Mongo connection can be set up one of two ways: At the cookiecutter generation step, provide the `mongodb_uri`, and `mongo_database` to inform the generator on how to connect to an Atlas cloud instance. Additionally, in the generated file, you can manually set the .env.MONGO_DATABASE_URI and it will connect to the Atlas cluster made locally.
8787

8888
Additionally, developers could configure a local mongo instance, but it would either have to be spun up in docker, or generated separately and also allow for non-localhost connections to be made.
8989

{{cookiecutter.project_slug}}/.env

+2-6
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,5 @@ SENTRY_DSN={{cookiecutter.sentry_dsn}}
3939
FLOWER_BASIC_AUTH={{cookiecutter.flower_auth}}
4040

4141
# Mongo
42-
MONGO_HOST={{cookiecutter.mongo_host}}
43-
MONGO_USER={{cookiecutter.mongo_user}}
44-
MONGO_PASSWORD={{cookiecutter.mongodb_password}}
45-
MONGO_DATABASE={{cookiecutter.mongodb_database}}
46-
# To override the mongo URI, override with this setting
47-
# MONGO_DATABASE_URI=
42+
MONGO_DATABASE_URI={{cookiecutter.mongodb_uri}}
43+
MONGO_DATABASE={{cookiecutter.mongodb_database}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
name: build and deploy template
2+
on:
3+
push:
4+
branches:
5+
- main
6+
- production
7+
8+
env:
9+
TRAEFIK_PUBLIC_NETWORK: traefik-public
10+
STACK_NAME: {{cookiecutter.docker_swarm_stack_name_main}}
11+
DOCKER_IMAGE_CELERYWORKER: {{cookiecutter.docker_image_celeryworker}}
12+
TRAEFIK_TAG: {{cookiecutter.traefik_constraint_tag}}
13+
TRAEFIK_PUBLIC_TAG: {{cookiecutter.traefik_public_constraint_tag}}
14+
DOCKER_IMAGE_BACKEND: {{cookiecutter.docker_image_backend}}
15+
DOCKER_IMAGE_FRONTEND: {{cookiecutter.docker_image_frontend}}
16+
PROJECT_NAME: {{cookiecutter.project_name}}
17+
DOMAIN: localhost
18+
SMTP_HOST:
19+
20+
jobs:
21+
tests:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Check out code
25+
uses: actions/checkout@v4
26+
27+
- name: Run Tests
28+
run: sh ./scripts/test.sh
29+
30+
deploy-staging:
31+
if: github.ref == 'refs/heads/main'
32+
runs-on: ubuntu-latest
33+
steps:
34+
- name: Check out code
35+
uses: actions/checkout@v4
36+
37+
{% raw %}
38+
- name: Log in to Docker Registry
39+
uses: docker/login-action@v3
40+
with:
41+
username: ${{ secrets.DOCKERHUB_USERNAME }}
42+
password: ${{ secrets.DOCKERHUB_TOKEN }}
43+
{% endraw %}
44+
45+
- name: Install docker-auto-labels
46+
run: pip install docker-auto-labels
47+
48+
- name: Build Staging
49+
run: |
50+
DOMAIN={{cookiecutter.domain_staging}}
51+
TRAEFIK_TAG={{cookiecutter.traefik_constraint_tag_staging}}
52+
STACK_NAME={{cookiecutter.docker_swarm_stack_name_staging}}
53+
TAG=staging
54+
FRONTEND_ENV=staging
55+
sh ./scripts/build-push.sh
56+
57+
- name: Deploy Staging
58+
run: |
59+
DOMAIN={{cookiecutter.domain_staging}}
60+
TRAEFIK_TAG={{cookiecutter.traefik_constraint_tag_staging}}
61+
STACK_NAME={{cookiecutter.docker_swarm_stack_name_staging}}
62+
TAG=staging
63+
sh ./scripts/deploy.sh
64+
needs: tests
65+
66+
deploy-prod:
67+
if: github.ref == 'refs/heads/production'
68+
runs-on: ubuntu-latest
69+
steps:
70+
- name: Check out code
71+
uses: actions/checkout@v4
72+
73+
{% raw %}
74+
- name: Log in to Docker Registry
75+
uses: docker/login-action@v3
76+
with:
77+
username: ${{ secrets.DOCKERHUB_USERNAME }}
78+
password: ${{ secrets.DOCKERHUB_TOKEN }}
79+
{% endraw %}
80+
81+
- name: Install docker-auto-labels
82+
run: pip install docker-auto-labels
83+
84+
- name: Build Production
85+
run: |
86+
DOMAIN={{cookiecutter.domain_main}}
87+
TRAEFIK_TAG={{cookiecutter.traefik_constraint_tag}}
88+
STACK_NAME={{cookiecutter.docker_swarm_stack_name_main}}
89+
TAG=prod
90+
FRONTEND_ENV=production
91+
sh ./scripts/build-push.sh
92+
93+
- name: Deploy Production
94+
run: |
95+
DOMAIN={{cookiecutter.domain_main}}
96+
TRAEFIK_TAG={{cookiecutter.traefik_constraint_tag}}
97+
STACK_NAME={{cookiecutter.docker_swarm_stack_name_main}}
98+
TAG=prod
99+
sh ./scripts/deploy.sh
100+
needs: tests
101+
102+
103+
104+
105+

{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/users.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async def create_user_profile(
2222
db: AgnosticDatabase = Depends(deps.get_db),
2323
password: str = Body(...),
2424
email: EmailStr = Body(...),
25-
full_name: str = Body(None),
25+
full_name: str = Body(""),
2626
) -> Any:
2727
"""
2828
Create new user without the need to be logged in.
@@ -50,9 +50,13 @@ async def update_user(
5050
Update user.
5151
"""
5252
if current_user.hashed_password:
53-
user = await crud.user.authenticate(db, email=current_user.email, password=obj_in.original)
53+
user = await crud.user.authenticate(
54+
db, email=current_user.email, password=obj_in.original
55+
)
5456
if not obj_in.original or not user:
55-
raise HTTPException(status_code=400, detail="Unable to authenticate this update.")
57+
raise HTTPException(
58+
status_code=400, detail="Unable to authenticate this update."
59+
)
5660
current_user_data = jsonable_encoder(current_user)
5761
user_in = schemas.UserUpdate(**current_user_data)
5862
if obj_in.password is not None:
@@ -146,7 +150,9 @@ async def create_user(
146150
)
147151
user = await crud.user.create(db, obj_in=user_in)
148152
if settings.EMAILS_ENABLED and user_in.email:
149-
send_new_account_email(email_to=user_in.email, username=user_in.email, password=user_in.password)
153+
send_new_account_email(
154+
email_to=user_in.email, username=user_in.email, password=user_in.password
155+
)
150156
return user
151157

152158

{{cookiecutter.project_slug}}/backend/app/app/core/config.py

+14-32
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import secrets
22
from typing import Any, Dict, List, Optional, Union
33

4-
from pydantic import AnyHttpUrl, EmailStr, HttpUrl, validator
4+
from pydantic import AnyHttpUrl, EmailStr, HttpUrl, field_validator
5+
from pydantic_core.core_schema import ValidationInfo
56
from pydantic_settings import BaseSettings
67

78

@@ -22,7 +23,7 @@ class Settings(BaseSettings):
2223
# "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]'
2324
BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []
2425

25-
@validator("BACKEND_CORS_ORIGINS", pre=True)
26+
@field_validator("BACKEND_CORS_ORIGINS", mode="before")
2627
def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]:
2728
if isinstance(v, str) and not v.startswith("["):
2829
return [i.strip() for i in v.split(",")]
@@ -33,9 +34,9 @@ def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str
3334
PROJECT_NAME: str
3435
SENTRY_DSN: Optional[HttpUrl] = None
3536

36-
@validator("SENTRY_DSN", pre=True)
37+
@field_validator("SENTRY_DSN", mode="before")
3738
def sentry_dsn_can_be_blank(cls, v: str) -> Optional[str]:
38-
if len(v) == 0:
39+
if isinstance(v, str) and len(v) == 0:
3940
return None
4041
return v
4142

@@ -44,27 +45,8 @@ def sentry_dsn_can_be_blank(cls, v: str) -> Optional[str]:
4445
MULTI_MAX: int = 20
4546

4647
# COMPONENT SETTINGS
47-
48-
MONGO_HOST: str
49-
MONGO_USER: str
50-
MONGO_PASSWORD: str
5148
MONGO_DATABASE: str
52-
MONGO_URI_OPTIONS: str = "retryWrites=true&w=majority"
53-
MONGO_DATABASE_URI: Optional[str] = None
54-
55-
@validator("MONGO_DATABASE_URI", pre=True)
56-
def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
57-
if isinstance(v, str):
58-
return v
59-
mongo_user, mongo_password, mongo_host, mongo_options = (
60-
values.get("MONGO_USER"),
61-
values.get("MONGO_PASSWORD"),
62-
values.get("MONGO_HOST"),
63-
values.get("MONGO_URI_OPTIONS"),
64-
)
65-
return (
66-
f"mongodb+srv://{mongo_user}:{mongo_password}@{mongo_host}/?{mongo_options}"
67-
)
49+
MONGO_DATABASE_URI: str = ""
6850

6951
SMTP_TLS: bool = True
7052
SMTP_PORT: Optional[int] = None
@@ -75,22 +57,22 @@ def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any
7557
EMAILS_FROM_NAME: Optional[str] = None
7658
EMAILS_TO_EMAIL: Optional[EmailStr] = None
7759

78-
@validator("EMAILS_FROM_NAME")
79-
def get_project_name(cls, v: Optional[str], values: Dict[str, Any]) -> str:
60+
@field_validator("EMAILS_FROM_NAME")
61+
def get_project_name(cls, v: Optional[str], info: ValidationInfo) -> str:
8062
if not v:
81-
return values["PROJECT_NAME"]
63+
return info.data["PROJECT_NAME"]
8264
return v
8365

8466
EMAIL_RESET_TOKEN_EXPIRE_HOURS: int = 48
8567
EMAIL_TEMPLATES_DIR: str = "/app/app/email-templates/build"
8668
EMAILS_ENABLED: bool = False
8769

88-
@validator("EMAILS_ENABLED", pre=True)
89-
def get_emails_enabled(cls, v: bool, values: Dict[str, Any]) -> bool:
70+
@field_validator("EMAILS_ENABLED", mode="before")
71+
def get_emails_enabled(cls, v: bool, info: ValidationInfo) -> bool:
9072
return bool(
91-
values.get("SMTP_HOST")
92-
and values.get("SMTP_PORT")
93-
and values.get("EMAILS_FROM_EMAIL")
73+
info.data.get("SMTP_HOST")
74+
and info.data.get("SMTP_PORT")
75+
and info.data.get("EMAILS_FROM_EMAIL")
9476
)
9577

9678
EMAIL_TEST_USER: EmailStr = "test@example.com" # type: ignore

{{cookiecutter.project_slug}}/backend/app/app/db/session.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ class _MongoClientSingleton:
88
def __new__(cls):
99
if not hasattr(cls, "instance"):
1010
cls.instance = super(_MongoClientSingleton, cls).__new__(cls)
11-
cls.instance.mongo_client = motor_asyncio.AsyncIOMotorClient(settings.MONGO_DATABASE_URI)
11+
cls.instance.mongo_client = motor_asyncio.AsyncIOMotorClient(
12+
settings.MONGO_DATABASE_URI
13+
)
1214
return cls.instance
1315

1416

15-
def MongoDatabase() -> core.AgnosticDatabase:
17+
def MongoDatabase(test=False) -> core.AgnosticDatabase:
1618
return _MongoClientSingleton().mongo_client[settings.MONGO_DATABASE]
1719

1820

{{cookiecutter.project_slug}}/backend/app/app/models/user.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
from . import Token # noqa: F401
1111

1212

13+
def datetime_now_sec():
14+
return datetime.now().replace(microsecond=0)
15+
16+
1317
class User(Base):
14-
created: datetime = Field(default_factory=datetime.now)
15-
modified: datetime = Field(default_factory=datetime.now)
18+
created: datetime = Field(default_factory=datetime_now_sec)
19+
modified: datetime = Field(default_factory=datetime_now_sec)
1620
full_name: str = Field(default="")
1721
email: EmailStr
1822
hashed_password: Optional[str]
@@ -21,4 +25,4 @@ class User(Base):
2125
email_validated: bool = Field(default=False)
2226
is_active: bool = Field(default=False)
2327
is_superuser: bool = Field(default=False)
24-
refresh_tokens: list[Link["Token"]] = Field(default_factory=list)
28+
refresh_tokens: list[Link["Token"]] = Field(default_factory=list)

{{cookiecutter.project_slug}}/backend/app/app/schemas/user.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from typing import Optional
2-
from pydantic import BaseModel, Field, EmailStr, constr, validator
2+
from pydantic import BaseModel, Field, EmailStr, constr, field_validator
33
from beanie import PydanticObjectId
44

55

@@ -44,13 +44,13 @@ class User(UserInDBBase):
4444
class Config:
4545
populate_by_name = True
4646

47-
@validator("hashed_password", pre=True)
47+
@field_validator("hashed_password", mode="before")
4848
def evaluate_hashed_password(cls, hashed_password):
4949
if hashed_password:
5050
return True
5151
return False
5252

53-
@validator("totp_secret", pre=True)
53+
@field_validator("totp_secret", mode="before")
5454
def evaluate_totp_secret(cls, totp_secret):
5555
if totp_secret:
5656
return True

{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_celery.py

-16
This file was deleted.

0 commit comments

Comments
 (0)