mirror of
https://github.com/fastapi/full-stack-fastapi-template.git
synced 2026-11-03 20:36:27 +00:00
Compare commits
8 Commits
64221a627e
..
0.11.1
| Author | SHA1 | Date | |
|---|---|---|---|
| ddb3c34f19 | |||
| eae2d6701d | |||
| 6b9fa4113c | |||
| b55b3122ab | |||
| 6dbb2e8256 | |||
| 6d75b2d132 | |||
| 32ab6dd022 | |||
| cf1548a983 |
@@ -14,7 +14,5 @@ SMTP_TLS=False
|
|||||||
SMTP_PORT=1025
|
SMTP_PORT=1025
|
||||||
|
|
||||||
# Postgres
|
# Postgres
|
||||||
POSTGRES_SERVER=localhost
|
|
||||||
POSTGRES_DB=app
|
|
||||||
POSTGRES_USER=postgres
|
|
||||||
POSTGRES_PASSWORD=changethis
|
POSTGRES_PASSWORD=changethis
|
||||||
|
DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@localhost:5432/app
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
name: Deploy to Production
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types:
|
|
||||||
- published
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
environment: production
|
|
||||||
# Do not deploy in the main repository, only in user projects
|
|
||||||
if: github.repository_owner != 'fastapi'
|
|
||||||
runs-on:
|
|
||||||
- self-hosted
|
|
||||||
- production
|
|
||||||
env:
|
|
||||||
DOMAIN: ${{ secrets.DOMAIN }}
|
|
||||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
|
||||||
FIRST_SUPERUSER: ${{ secrets.FIRST_SUPERUSER }}
|
|
||||||
FIRST_SUPERUSER_PASSWORD: ${{ secrets.FIRST_SUPERUSER_PASSWORD }}
|
|
||||||
SMTP_HOST: ${{ secrets.SMTP_HOST }}
|
|
||||||
SMTP_USER: ${{ secrets.SMTP_USER }}
|
|
||||||
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
|
|
||||||
EMAILS_FROM_EMAIL: ${{ secrets.EMAILS_FROM_EMAIL }}
|
|
||||||
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- run: docker compose -f compose.yml -f compose.deploy.yml build
|
|
||||||
- run: docker compose -f compose.yml -f compose.deploy.yml run --rm backend bash scripts/prestart.sh
|
|
||||||
- run: docker compose -f compose.yml -f compose.deploy.yml up -d
|
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
name: Deploy to Staging
|
name: Deploy
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- release-notes.md
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
environment: staging
|
|
||||||
# Do not deploy in the main repository, only in user projects
|
# Do not deploy in the main repository, only in user projects
|
||||||
if: github.repository_owner != 'fastapi'
|
if: github.repository_owner != 'fastapi'
|
||||||
runs-on:
|
runs-on: self-hosted
|
||||||
- self-hosted
|
|
||||||
- staging
|
|
||||||
env:
|
env:
|
||||||
DOMAIN: ${{ secrets.DOMAIN }}
|
DOMAIN: ${{ secrets.DOMAIN }}
|
||||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||||
@@ -31,7 +31,7 @@ target_metadata = SQLModel.metadata
|
|||||||
|
|
||||||
|
|
||||||
def get_url():
|
def get_url():
|
||||||
return str(settings.SQLALCHEMY_DATABASE_URI)
|
return str(settings.DATABASE_URL)
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline():
|
def run_migrations_offline():
|
||||||
|
|||||||
+12
-17
@@ -6,6 +6,7 @@ from pydantic import (
|
|||||||
HttpUrl,
|
HttpUrl,
|
||||||
PostgresDsn,
|
PostgresDsn,
|
||||||
computed_field,
|
computed_field,
|
||||||
|
field_validator,
|
||||||
model_validator,
|
model_validator,
|
||||||
)
|
)
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
@@ -27,23 +28,16 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
PROJECT_NAME: str
|
PROJECT_NAME: str
|
||||||
SENTRY_DSN: HttpUrl | None = None
|
SENTRY_DSN: HttpUrl | None = None
|
||||||
POSTGRES_SERVER: str
|
DATABASE_URL: PostgresDsn
|
||||||
POSTGRES_PORT: int = 5432
|
|
||||||
POSTGRES_USER: str
|
|
||||||
POSTGRES_PASSWORD: str = ""
|
|
||||||
POSTGRES_DB: str = ""
|
|
||||||
|
|
||||||
@computed_field # type: ignore[prop-decorator]
|
@field_validator("DATABASE_URL", mode="before")
|
||||||
@property
|
@classmethod
|
||||||
def SQLALCHEMY_DATABASE_URI(self) -> PostgresDsn:
|
def _use_psycopg_driver(cls, value: str | PostgresDsn) -> str:
|
||||||
return PostgresDsn.build(
|
database_url = str(value)
|
||||||
scheme="postgresql+psycopg",
|
for scheme in ("postgres://", "postgresql://"):
|
||||||
username=self.POSTGRES_USER,
|
if database_url.startswith(scheme):
|
||||||
password=self.POSTGRES_PASSWORD,
|
return database_url.replace(scheme, "postgresql+psycopg://", 1)
|
||||||
host=self.POSTGRES_SERVER,
|
return database_url
|
||||||
port=self.POSTGRES_PORT,
|
|
||||||
path=self.POSTGRES_DB,
|
|
||||||
)
|
|
||||||
|
|
||||||
SMTP_TLS: bool = True
|
SMTP_TLS: bool = True
|
||||||
SMTP_SSL: bool = False
|
SMTP_SSL: bool = False
|
||||||
@@ -85,7 +79,8 @@ class Settings(BaseSettings):
|
|||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def _enforce_non_default_secrets(self) -> Self:
|
def _enforce_non_default_secrets(self) -> Self:
|
||||||
self._check_default_secret("SECRET_KEY", self.SECRET_KEY)
|
self._check_default_secret("SECRET_KEY", self.SECRET_KEY)
|
||||||
self._check_default_secret("POSTGRES_PASSWORD", self.POSTGRES_PASSWORD)
|
for host in self.DATABASE_URL.hosts():
|
||||||
|
self._check_default_secret("DATABASE_URL password", host["password"])
|
||||||
self._check_default_secret(
|
self._check_default_secret(
|
||||||
"FIRST_SUPERUSER_PASSWORD", self.FIRST_SUPERUSER_PASSWORD
|
"FIRST_SUPERUSER_PASSWORD", self.FIRST_SUPERUSER_PASSWORD
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from app import crud
|
|||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.models import User, UserCreate
|
from app.models import User, UserCreate
|
||||||
|
|
||||||
engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))
|
engine = create_engine(str(settings.DATABASE_URL))
|
||||||
|
|
||||||
|
|
||||||
# make sure all SQLModel models are imported (app.models) before initializing DB
|
# make sure all SQLModel models are imported (app.models) before initializing DB
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
"@tanstack/react-query-devtools": "^5.101.4",
|
"@tanstack/react-query-devtools": "^5.101.4",
|
||||||
"@tanstack/react-router": "^1.170.18",
|
"@tanstack/react-router": "^1.170.18",
|
||||||
"@tanstack/react-router-devtools": "^1.167.0",
|
"@tanstack/react-router-devtools": "^1.167.0",
|
||||||
"@tanstack/react-table": "^9.0.0",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"axios": "1.18.0",
|
"axios": "1.18.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
"@vitejs/plugin-react-swc": "^4.3.3",
|
"@vitejs/plugin-react-swc": "^4.3.3",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^7.0.2",
|
"typescript": "^6.0.3",
|
||||||
"vite": "^8.2.0",
|
"vite": "^8.2.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -526,7 +526,7 @@
|
|||||||
|
|
||||||
"@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="],
|
"@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="],
|
||||||
|
|
||||||
"@tanstack/react-table": ["@tanstack/react-table@9.0.0", "", { "dependencies": { "@tanstack/react-store": "^0.11.0", "@tanstack/table-core": "9.0.0" }, "peerDependencies": { "react": ">=18" } }, "sha512-Q/Z49MQcdMwge67U+LTjSEQJCnQE9/tNWK5IpiYex9JFDzfjNkLIi7yzGA4dfW4UpuMBrtqbSnSzn7oPr60T+w=="],
|
"@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="],
|
||||||
|
|
||||||
"@tanstack/router-core": ["@tanstack/router-core@1.171.15", "", { "dependencies": { "@tanstack/history": "1.162.0", "cookie-es": "^3.0.0", "seroval": "^1.5.4", "seroval-plugins": "^1.5.4" } }, "sha512-IILCDcLaItMZQ2jEmCABHY1Nhjjn5XUvwpQp3e4Nmu+vfg0BgYFuu/QASz2SwE2ZNbVMrvt8X/wxa+Gg5aErxA=="],
|
"@tanstack/router-core": ["@tanstack/router-core@1.171.15", "", { "dependencies": { "@tanstack/history": "1.162.0", "cookie-es": "^3.0.0", "seroval": "^1.5.4", "seroval-plugins": "^1.5.4" } }, "sha512-IILCDcLaItMZQ2jEmCABHY1Nhjjn5XUvwpQp3e4Nmu+vfg0BgYFuu/QASz2SwE2ZNbVMrvt8X/wxa+Gg5aErxA=="],
|
||||||
|
|
||||||
@@ -542,7 +542,7 @@
|
|||||||
|
|
||||||
"@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="],
|
"@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="],
|
||||||
|
|
||||||
"@tanstack/table-core": ["@tanstack/table-core@9.0.0", "", { "dependencies": { "@tanstack/store": "^0.11.0" } }, "sha512-IyKCc4D7d/+I9euQntlVDQ7lnilmFRKpIROBeI5/866adFy+65xWvCFPz76NZ5j2lXe/RFDvFVV7bfETmwHEMA=="],
|
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
|
||||||
|
|
||||||
"@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="],
|
"@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="],
|
||||||
|
|
||||||
@@ -558,46 +558,6 @@
|
|||||||
|
|
||||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||||
|
|
||||||
"@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="],
|
|
||||||
|
|
||||||
"@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="],
|
|
||||||
|
|
||||||
"@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@4.3.3", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1", "@swc/core": "^1.15.46" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7 || ^8" } }, "sha512-bti8ZAcvz4Lh6/e4Uk2k3aa1TiUXbbMsahuqOHvd3MveFTkKDZOA6wQVkpj7J/+tepX/wGfe+lsGh/t24HTXMQ=="],
|
"@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@4.3.3", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1", "@swc/core": "^1.15.46" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7 || ^8" } }, "sha512-bti8ZAcvz4Lh6/e4Uk2k3aa1TiUXbbMsahuqOHvd3MveFTkKDZOA6wQVkpj7J/+tepX/wGfe+lsGh/t24HTXMQ=="],
|
||||||
|
|
||||||
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
||||||
@@ -1020,7 +980,7 @@
|
|||||||
|
|
||||||
"type-fest": ["type-fest@5.8.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA=="],
|
"type-fest": ["type-fest@5.8.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA=="],
|
||||||
|
|
||||||
"typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="],
|
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||||
|
|
||||||
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
|
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
|
||||||
|
|
||||||
@@ -1110,8 +1070,6 @@
|
|||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"@tanstack/react-table/@tanstack/react-store": ["@tanstack/react-store@0.11.1", "", { "dependencies": { "@tanstack/store": "0.11.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ=="],
|
|
||||||
|
|
||||||
"@tanstack/router-generator/jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
|
"@tanstack/router-generator/jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
|
||||||
|
|
||||||
"@tanstack/router-plugin/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
"@tanstack/router-plugin/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||||
@@ -1124,8 +1082,6 @@
|
|||||||
|
|
||||||
"@tanstack/router-utils/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
"@tanstack/router-utils/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||||
|
|
||||||
"@tanstack/table-core/@tanstack/store": ["@tanstack/store@0.11.1", "", {}, "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA=="],
|
|
||||||
|
|
||||||
"@types/cors/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
|
"@types/cors/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
|
||||||
|
|
||||||
"@types/ws/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
|
"@types/ws/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
|
||||||
@@ -1218,8 +1174,6 @@
|
|||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
"@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||||
|
|
||||||
"@tanstack/react-table/@tanstack/react-store/@tanstack/store": ["@tanstack/store@0.11.1", "", {}, "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA=="],
|
|
||||||
|
|
||||||
"@tanstack/router-plugin/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
"@tanstack/router-plugin/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||||
|
|
||||||
"@tanstack/router-utils/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
"@tanstack/router-utils/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||||
|
|||||||
+3
-7
@@ -20,7 +20,7 @@ services:
|
|||||||
db:
|
db:
|
||||||
image: postgres:18
|
image: postgres:18
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
test: ["CMD-SHELL", "pg_isready -U postgres -d app"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
retries: 5
|
retries: 5
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
@@ -29,8 +29,7 @@ services:
|
|||||||
- app-db-data:/var/lib/postgresql
|
- app-db-data:/var/lib/postgresql
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?Variable not set}
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?Variable not set}
|
||||||
- POSTGRES_USER=${POSTGRES_USER:?Variable not set}
|
- POSTGRES_DB=app
|
||||||
- POSTGRES_DB=${POSTGRES_DB:?Variable not set}
|
|
||||||
|
|
||||||
adminer:
|
adminer:
|
||||||
image: adminer
|
image: adminer
|
||||||
@@ -62,10 +61,7 @@ services:
|
|||||||
SMTP_USER: ${SMTP_USER:-}
|
SMTP_USER: ${SMTP_USER:-}
|
||||||
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
|
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
|
||||||
EMAILS_FROM_EMAIL: ${EMAILS_FROM_EMAIL}
|
EMAILS_FROM_EMAIL: ${EMAILS_FROM_EMAIL}
|
||||||
POSTGRES_SERVER: db
|
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:?Variable not set}@db:5432/app
|
||||||
POSTGRES_DB: ${POSTGRES_DB:?Variable not set}
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER:?Variable not set}
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Variable not set}
|
|
||||||
SENTRY_DSN: ${SENTRY_DSN:-}
|
SENTRY_DSN: ${SENTRY_DSN:-}
|
||||||
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
+12
-50
@@ -4,13 +4,13 @@ You can deploy the project using Docker Compose to a remote server.
|
|||||||
|
|
||||||
The deployment Docker Compose configuration includes Traefik to handle HTTPS and route incoming traffic to the application.
|
The deployment Docker Compose configuration includes Traefik to handle HTTPS and route incoming traffic to the application.
|
||||||
|
|
||||||
You can use CI/CD (continuous integration and continuous deployment) systems to deploy automatically, there are already configurations to do it with GitHub Actions.
|
The included GitHub Actions workflow can deploy the application automatically.
|
||||||
|
|
||||||
But you have to configure a couple things first. 🤓
|
But you have to configure a couple things first. 🤓
|
||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
|
|
||||||
* Have a remote server ready and available. Use a separate server for each environment, for example one for staging and one for production.
|
* Have a remote server ready and available.
|
||||||
* Configure DNS records pointing to the server for the application domain and any supporting service subdomains you want to expose, e.g. `fastapi-project.example.com` and `adminer.fastapi-project.example.com`.
|
* Configure DNS records pointing to the server for the application domain and any supporting service subdomains you want to expose, e.g. `fastapi-project.example.com` and `adminer.fastapi-project.example.com`.
|
||||||
* Install and configure [Docker](https://docs.docker.com/engine/install/) on the remote server (Docker Engine, not Docker Desktop).
|
* Install and configure [Docker](https://docs.docker.com/engine/install/) on the remote server (Docker Engine, not Docker Desktop).
|
||||||
|
|
||||||
@@ -80,16 +80,17 @@ You can set several other environment variables:
|
|||||||
* `SMTP_USER`: The SMTP server user to send emails.
|
* `SMTP_USER`: The SMTP server user to send emails.
|
||||||
* `SMTP_PASSWORD`: The SMTP server password to send emails.
|
* `SMTP_PASSWORD`: The SMTP server password to send emails.
|
||||||
* `EMAILS_FROM_EMAIL`: The email account to send emails from.
|
* `EMAILS_FROM_EMAIL`: The email account to send emails from.
|
||||||
* `POSTGRES_USER`: The Postgres user, you can leave the default.
|
|
||||||
* `POSTGRES_DB`: The database name to use for this application. You can leave the default of `app`.
|
|
||||||
* `SENTRY_DSN`: The DSN for Sentry, if you are using it.
|
* `SENTRY_DSN`: The DSN for Sentry, if you are using it.
|
||||||
|
|
||||||
## GitHub Actions Environment Variables
|
## GitHub Repository Automation
|
||||||
|
|
||||||
There are some environment variables only used by GitHub Actions that you can configure:
|
Install the following GitHub Apps to enable the included repository automation:
|
||||||
|
|
||||||
* `LATEST_CHANGES`: Used by the GitHub Action [latest-changes](https://github.com/tiangolo/latest-changes) to automatically add release notes based on the PRs merged. It's a personal access token, read the docs for details.
|
* [Latest Changes](https://github.com/apps/latest-changes) updates `release-notes.md` when a pull request is merged.
|
||||||
* `SMOKESHOW_AUTH_KEY`: Used to handle and publish the code coverage using [Smokeshow](https://github.com/samuelcolvin/smokeshow), follow their instructions to create a (free) Smokeshow key.
|
* [PR Push](https://github.com/apps/pr-push) lets the pre-commit workflow push automated fixes to pull request branches.
|
||||||
|
* [PR Submit](https://github.com/apps/pr-submit) lets the **Bump pre-commit hooks** and **Prepare Release** workflows create pull requests.
|
||||||
|
|
||||||
|
To publish code coverage with [Smokeshow](https://github.com/samuelcolvin/smokeshow), add `SMOKESHOW_AUTH_KEY` as a repository secret.
|
||||||
|
|
||||||
### Deploy with Docker Compose
|
### Deploy with Docker Compose
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ The `compose.deploy.yml` file adds the deployment settings to the shared configu
|
|||||||
|
|
||||||
You can use GitHub Actions to deploy your project automatically. 😎
|
You can use GitHub Actions to deploy your project automatically. 😎
|
||||||
|
|
||||||
There are already two environment deployments configured, `staging` and `production`. Each environment should be deployed to a separate server. 🚀
|
The included `deploy.yml` workflow deploys the application whenever changes are pushed to `master`, including when a pull request is merged. 🚀
|
||||||
|
|
||||||
### Install GitHub Actions Runner
|
### Install GitHub Actions Runner
|
||||||
|
|
||||||
@@ -138,8 +139,6 @@ cd
|
|||||||
|
|
||||||
* [Install a GitHub Action self-hosted runner following the official guide](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners#adding-a-self-hosted-runner-to-a-repository).
|
* [Install a GitHub Action self-hosted runner following the official guide](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners#adding-a-self-hosted-runner-to-a-repository).
|
||||||
|
|
||||||
* When asked about labels, add a label for the environment, e.g. `production`. You can also add labels later.
|
|
||||||
|
|
||||||
After installing, the guide would tell you to run a command to start the runner. Nevertheless, it would stop once you terminate that process or if your local connection to your server is lost.
|
After installing, the guide would tell you to run a command to start the runner. Nevertheless, it would stop once you terminate that process or if your local connection to your server is lost.
|
||||||
|
|
||||||
To make sure it runs on startup and continues running, you can install it as a service. To do that, exit the `github` user and go back to the `root` user:
|
To make sure it runs on startup and continues running, you can install it as a service. To do that, exit the `github` user and go back to the `root` user:
|
||||||
@@ -182,17 +181,9 @@ cd /home/github/actions-runner
|
|||||||
|
|
||||||
You can read more about it in the official guide: [Configuring the self-hosted runner application as a service](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service).
|
You can read more about it in the official guide: [Configuring the self-hosted runner application as a service](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service).
|
||||||
|
|
||||||
### Configure GitHub Environments
|
### Set Repository Secrets
|
||||||
|
|
||||||
The deployment workflows use [GitHub Environments](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) for `staging` and `production`. This enables environment-specific secrets, deployment protection rules (e.g. required reviewers, wait timers), and deployment status tracking.
|
In your repository, go to **Settings** > **Secrets and variables** > **Actions** and add the following [repository secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets#creating-secrets-for-a-repository):
|
||||||
|
|
||||||
To configure them, go to your repository's **Settings** > **Environments** and create the `staging` and `production` environments.
|
|
||||||
|
|
||||||
### Set Secrets
|
|
||||||
|
|
||||||
For each GitHub Environment (`staging` and `production`), configure the required secrets as [environment secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets#creating-secrets-for-an-environment). Environment secrets are preferred over [repository secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets#creating-secrets-for-a-repository) because they are scoped to the specific environment, reducing exposure and aligning with any protection rules you configure.
|
|
||||||
|
|
||||||
The deployment workflows require these secrets:
|
|
||||||
|
|
||||||
* `DOMAIN`
|
* `DOMAIN`
|
||||||
* `FIRST_SUPERUSER`
|
* `FIRST_SUPERUSER`
|
||||||
@@ -209,41 +200,12 @@ To enable emails, configure these additional secrets with the values from your e
|
|||||||
|
|
||||||
To enable Sentry, configure the `SENTRY_DSN` secret.
|
To enable Sentry, configure the `SENTRY_DSN` secret.
|
||||||
|
|
||||||
## GitHub Action Deployment Workflows
|
|
||||||
|
|
||||||
There are GitHub Action workflows in the `.github/workflows` directory already configured for deploying to the environments (GitHub Actions runners with the labels):
|
|
||||||
|
|
||||||
* `staging`: after pushing (or merging) to the branch `master`.
|
|
||||||
* `production`: after publishing a release.
|
|
||||||
|
|
||||||
Both workflows are associated with their respective GitHub Environments, so deployments will be visible in the repository's **Environments** section and will respect any protection rules you configure.
|
|
||||||
|
|
||||||
### Prepare a Release
|
|
||||||
|
|
||||||
Install the [PR Submit GitHub App](https://github.com/apps/pr-submit) in your repository to enable the **Prepare Release** workflow.
|
|
||||||
|
|
||||||
Run the workflow manually from the **Actions** tab and select the version bump. It creates a pull request that updates the release notes. When you merge that pull request, the **Create Draft Release** workflow creates a draft GitHub release with the corresponding release notes.
|
|
||||||
|
|
||||||
Review and publish the draft release to trigger the production deployment.
|
|
||||||
|
|
||||||
If you need to add extra environments you could use those as a starting point.
|
|
||||||
|
|
||||||
## URLs
|
## URLs
|
||||||
|
|
||||||
Replace `fastapi-project.example.com` with your domain.
|
Replace `fastapi-project.example.com` with your domain.
|
||||||
|
|
||||||
### Production
|
|
||||||
|
|
||||||
Application (frontend and API): `https://fastapi-project.example.com`
|
Application (frontend and API): `https://fastapi-project.example.com`
|
||||||
|
|
||||||
Interactive API docs: `https://fastapi-project.example.com/docs`
|
Interactive API docs: `https://fastapi-project.example.com/docs`
|
||||||
|
|
||||||
Adminer: `https://adminer.fastapi-project.example.com`
|
Adminer: `https://adminer.fastapi-project.example.com`
|
||||||
|
|
||||||
### Staging
|
|
||||||
|
|
||||||
Application (frontend and API): `https://staging.fastapi-project.example.com`
|
|
||||||
|
|
||||||
Interactive API docs: `https://staging.fastapi-project.example.com/docs`
|
|
||||||
|
|
||||||
Adminer: `https://adminer.staging.fastapi-project.example.com`
|
|
||||||
|
|||||||
+1
-1
@@ -156,7 +156,7 @@ biome check..............................................................Passed
|
|||||||
|
|
||||||
## URLs
|
## URLs
|
||||||
|
|
||||||
The production or staging URLs would use these same paths, but with your own domain.
|
The deployed URLs use these same paths, but with your own domain.
|
||||||
|
|
||||||
### Development URLs
|
### Development URLs
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM mcr.microsoft.com/playwright:v1.61.1-noble
|
FROM mcr.microsoft.com/playwright:v1.62.1-noble
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
+14
-2
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
|
"$schema": "https://biomejs.dev/schemas/2.5.6/schema.json",
|
||||||
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
||||||
"files": {
|
"files": {
|
||||||
"includes": [
|
"includes": [
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"recommended": true,
|
"preset": "recommended",
|
||||||
"suspicious": {
|
"suspicious": {
|
||||||
"noExplicitAny": "off",
|
"noExplicitAny": "off",
|
||||||
"noArrayIndexKey": "off"
|
"noArrayIndexKey": "off"
|
||||||
@@ -42,5 +42,17 @@
|
|||||||
"parser": {
|
"parser": {
|
||||||
"tailwindDirectives": true
|
"tailwindDirectives": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"includes": ["**/*.svg"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"a11y": {
|
||||||
|
"noSvgWithoutTitle": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"@tanstack/react-query-devtools": "^5.101.4",
|
"@tanstack/react-query-devtools": "^5.101.4",
|
||||||
"@tanstack/react-router": "^1.170.18",
|
"@tanstack/react-router": "^1.170.18",
|
||||||
"@tanstack/react-router-devtools": "^1.167.0",
|
"@tanstack/react-router-devtools": "^1.167.0",
|
||||||
"@tanstack/react-table": "^9.0.0",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"axios": "1.18.0",
|
"axios": "1.18.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"@vitejs/plugin-react-swc": "^4.3.3",
|
"@vitejs/plugin-react-swc": "^4.3.3",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^7.0.2",
|
"typescript": "^6.0.3",
|
||||||
"vite": "^8.2.0"
|
"vite": "^8.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,29 +9,18 @@
|
|||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
import { Route as SignupRouteImport } from './routes/signup'
|
|
||||||
import { Route as ResetPasswordRouteImport } from './routes/reset-password'
|
|
||||||
import { Route as RecoverPasswordRouteImport } from './routes/recover-password'
|
|
||||||
import { Route as LoginRouteImport } from './routes/login'
|
|
||||||
import { Route as LayoutRouteImport } from './routes/_layout'
|
import { Route as LayoutRouteImport } from './routes/_layout'
|
||||||
|
import { Route as LoginRouteImport } from './routes/login'
|
||||||
|
import { Route as RecoverPasswordRouteImport } from './routes/recover-password'
|
||||||
|
import { Route as ResetPasswordRouteImport } from './routes/reset-password'
|
||||||
|
import { Route as SignupRouteImport } from './routes/signup'
|
||||||
import { Route as LayoutIndexRouteImport } from './routes/_layout/index'
|
import { Route as LayoutIndexRouteImport } from './routes/_layout/index'
|
||||||
import { Route as LayoutSettingsRouteImport } from './routes/_layout/settings'
|
|
||||||
import { Route as LayoutItemsRouteImport } from './routes/_layout/items'
|
|
||||||
import { Route as LayoutAdminRouteImport } from './routes/_layout/admin'
|
import { Route as LayoutAdminRouteImport } from './routes/_layout/admin'
|
||||||
|
import { Route as LayoutItemsRouteImport } from './routes/_layout/items'
|
||||||
|
import { Route as LayoutSettingsRouteImport } from './routes/_layout/settings'
|
||||||
|
|
||||||
const SignupRoute = SignupRouteImport.update({
|
const LayoutRoute = LayoutRouteImport.update({
|
||||||
id: '/signup',
|
id: '/_layout',
|
||||||
path: '/signup',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
const ResetPasswordRoute = ResetPasswordRouteImport.update({
|
|
||||||
id: '/reset-password',
|
|
||||||
path: '/reset-password',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
const RecoverPasswordRoute = RecoverPasswordRouteImport.update({
|
|
||||||
id: '/recover-password',
|
|
||||||
path: '/recover-password',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
const LoginRoute = LoginRouteImport.update({
|
const LoginRoute = LoginRouteImport.update({
|
||||||
@@ -39,8 +28,19 @@ const LoginRoute = LoginRouteImport.update({
|
|||||||
path: '/login',
|
path: '/login',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
const LayoutRoute = LayoutRouteImport.update({
|
const RecoverPasswordRoute = RecoverPasswordRouteImport.update({
|
||||||
id: '/_layout',
|
id: '/recover-password',
|
||||||
|
path: '/recover-password',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const ResetPasswordRoute = ResetPasswordRouteImport.update({
|
||||||
|
id: '/reset-password',
|
||||||
|
path: '/reset-password',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const SignupRoute = SignupRouteImport.update({
|
||||||
|
id: '/signup',
|
||||||
|
path: '/signup',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
const LayoutIndexRoute = LayoutIndexRouteImport.update({
|
const LayoutIndexRoute = LayoutIndexRouteImport.update({
|
||||||
@@ -48,9 +48,9 @@ const LayoutIndexRoute = LayoutIndexRouteImport.update({
|
|||||||
path: '/',
|
path: '/',
|
||||||
getParentRoute: () => LayoutRoute,
|
getParentRoute: () => LayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const LayoutSettingsRoute = LayoutSettingsRouteImport.update({
|
const LayoutAdminRoute = LayoutAdminRouteImport.update({
|
||||||
id: '/settings',
|
id: '/admin',
|
||||||
path: '/settings',
|
path: '/admin',
|
||||||
getParentRoute: () => LayoutRoute,
|
getParentRoute: () => LayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const LayoutItemsRoute = LayoutItemsRouteImport.update({
|
const LayoutItemsRoute = LayoutItemsRouteImport.update({
|
||||||
@@ -58,9 +58,9 @@ const LayoutItemsRoute = LayoutItemsRouteImport.update({
|
|||||||
path: '/items',
|
path: '/items',
|
||||||
getParentRoute: () => LayoutRoute,
|
getParentRoute: () => LayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const LayoutAdminRoute = LayoutAdminRouteImport.update({
|
const LayoutSettingsRoute = LayoutSettingsRouteImport.update({
|
||||||
id: '/admin',
|
id: '/settings',
|
||||||
path: '/admin',
|
path: '/settings',
|
||||||
getParentRoute: () => LayoutRoute,
|
getParentRoute: () => LayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
@@ -140,25 +140,11 @@ export interface RootRouteChildren {
|
|||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
'/signup': {
|
'/_layout': {
|
||||||
id: '/signup'
|
id: '/_layout'
|
||||||
path: '/signup'
|
path: ''
|
||||||
fullPath: '/signup'
|
fullPath: '/'
|
||||||
preLoaderRoute: typeof SignupRouteImport
|
preLoaderRoute: typeof LayoutRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
'/reset-password': {
|
|
||||||
id: '/reset-password'
|
|
||||||
path: '/reset-password'
|
|
||||||
fullPath: '/reset-password'
|
|
||||||
preLoaderRoute: typeof ResetPasswordRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
'/recover-password': {
|
|
||||||
id: '/recover-password'
|
|
||||||
path: '/recover-password'
|
|
||||||
fullPath: '/recover-password'
|
|
||||||
preLoaderRoute: typeof RecoverPasswordRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
'/login': {
|
'/login': {
|
||||||
@@ -168,11 +154,25 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof LoginRouteImport
|
preLoaderRoute: typeof LoginRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
'/_layout': {
|
'/recover-password': {
|
||||||
id: '/_layout'
|
id: '/recover-password'
|
||||||
path: ''
|
path: '/recover-password'
|
||||||
fullPath: '/'
|
fullPath: '/recover-password'
|
||||||
preLoaderRoute: typeof LayoutRouteImport
|
preLoaderRoute: typeof RecoverPasswordRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/reset-password': {
|
||||||
|
id: '/reset-password'
|
||||||
|
path: '/reset-password'
|
||||||
|
fullPath: '/reset-password'
|
||||||
|
preLoaderRoute: typeof ResetPasswordRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/signup': {
|
||||||
|
id: '/signup'
|
||||||
|
path: '/signup'
|
||||||
|
fullPath: '/signup'
|
||||||
|
preLoaderRoute: typeof SignupRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
'/_layout/': {
|
'/_layout/': {
|
||||||
@@ -182,11 +182,11 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof LayoutIndexRouteImport
|
preLoaderRoute: typeof LayoutIndexRouteImport
|
||||||
parentRoute: typeof LayoutRoute
|
parentRoute: typeof LayoutRoute
|
||||||
}
|
}
|
||||||
'/_layout/settings': {
|
'/_layout/admin': {
|
||||||
id: '/_layout/settings'
|
id: '/_layout/admin'
|
||||||
path: '/settings'
|
path: '/admin'
|
||||||
fullPath: '/settings'
|
fullPath: '/admin'
|
||||||
preLoaderRoute: typeof LayoutSettingsRouteImport
|
preLoaderRoute: typeof LayoutAdminRouteImport
|
||||||
parentRoute: typeof LayoutRoute
|
parentRoute: typeof LayoutRoute
|
||||||
}
|
}
|
||||||
'/_layout/items': {
|
'/_layout/items': {
|
||||||
@@ -196,11 +196,11 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof LayoutItemsRouteImport
|
preLoaderRoute: typeof LayoutItemsRouteImport
|
||||||
parentRoute: typeof LayoutRoute
|
parentRoute: typeof LayoutRoute
|
||||||
}
|
}
|
||||||
'/_layout/admin': {
|
'/_layout/settings': {
|
||||||
id: '/_layout/admin'
|
id: '/_layout/settings'
|
||||||
path: '/admin'
|
path: '/settings'
|
||||||
fullPath: '/admin'
|
fullPath: '/settings'
|
||||||
preLoaderRoute: typeof LayoutAdminRouteImport
|
preLoaderRoute: typeof LayoutSettingsRouteImport
|
||||||
parentRoute: typeof LayoutRoute
|
parentRoute: typeof LayoutRoute
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"baseUrl": ".",
|
"types": ["node"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(import.meta.dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
@@ -2,6 +2,19 @@
|
|||||||
|
|
||||||
## Latest Changes
|
## Latest Changes
|
||||||
|
|
||||||
|
## 0.11.1 (2026-08-11)
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
|
||||||
|
* ♻️ Simplify Docker Compose deployment. PR [#2435](https://github.com/fastapi/full-stack-fastapi-template/pull/2435) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ♻️ Migrate to `DATABASE_URL` instead of separate variables. PR [#2184](https://github.com/fastapi/full-stack-fastapi-template/pull/2184) by [@patrick91](https://github.com/patrick91).
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
* ⬆ Bump the npm-packages group across 1 directory with 33 updates. PR [#2433](https://github.com/fastapi/full-stack-fastapi-template/pull/2433) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||||
|
|
||||||
|
## 0.11.0 (2026-08-11)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* ✨ Add react-email for email templates. PR [#2421](https://github.com/fastapi/full-stack-fastapi-template/pull/2421) by [@alejsdev](https://github.com/alejsdev).
|
* ✨ Add react-email for email templates. PR [#2421](https://github.com/fastapi/full-stack-fastapi-template/pull/2421) by [@alejsdev](https://github.com/alejsdev).
|
||||||
|
|||||||
Reference in New Issue
Block a user