Skip to content

Commit afd4519

Browse files
committed
Merge branch 'development' into master
2 parents 567858c + d8bce72 commit afd4519

143 files changed

Lines changed: 5669 additions & 702 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.devcontainer/Dockerfile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
FROM python:3.14-slim
2+
3+
ENV POETRY_VERSION=2.4.1 \
4+
POETRY_NO_INTERACTION=1 \
5+
POETRY_VIRTUALENVS_CREATE=1 \
6+
POETRY_VIRTUALENVS_IN_PROJECT=1 \
7+
PYTHONDONTWRITEBYTECODE=1 \
8+
PYTHONUNBUFFERED=1
9+
10+
WORKDIR /app
11+
12+
RUN apt-get update \
13+
&& apt-get install --no-install-recommends -y \
14+
build-essential \
15+
curl \
16+
git \
17+
&& pip install --no-cache-dir "poetry==${POETRY_VERSION}" \
18+
&& rm -rf /var/lib/apt/lists/*
19+
20+
COPY pyproject.toml poetry.lock ./
21+
22+
RUN poetry install --with dev --no-root --no-ansi
23+
24+
RUN groupadd --system app \
25+
&& useradd --system --gid app --home-dir /app --shell /usr/sbin/nologin app \
26+
&& chown -R app:app /app
27+
28+
USER app

.devcontainer/devcontainer.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "fastapi-modulith",
3+
"dockerComposeFile": "docker-compose.yml",
4+
"service": "dev",
5+
"workspaceFolder": "/app",
6+
"forwardPorts": [8000, 9090, 4318],
7+
"portsAttributes": {
8+
"8000": {
9+
"label": "API",
10+
"onAutoForward": "notify"
11+
},
12+
"9090": {
13+
"label": "Prometheus",
14+
"onAutoForward": "notify"
15+
},
16+
"4318": {
17+
"label": "OTLP HTTP",
18+
"onAutoForward": "silent"
19+
}
20+
},
21+
"customizations": {
22+
"vscode": {
23+
"extensions": [
24+
"ms-python.python",
25+
"ms-python.vscode-pylance",
26+
"charliermarsh.ruff",
27+
"ms-python.mypy-type-checker",
28+
"tamasfe.even-better-toml"
29+
],
30+
"settings": {
31+
"python.defaultInterpreterPath": "/app/.venv/bin/python",
32+
"python.terminal.activateEnvironment": true,
33+
"python.testing.pytestEnabled": true,
34+
"python.testing.cwd": "/app",
35+
"python.testing.pytestPath": "/app/.venv/bin/pytest",
36+
"mypy-type-checker.path": ["/app/.venv/bin/mypy"],
37+
"mypy-type-checker.cwd": "/app",
38+
"[python]": {
39+
"editor.formatOnSave": true,
40+
"editor.codeActionsOnSave": {
41+
"source.fixAll": "explicit",
42+
"source.organizeImports": "explicit"
43+
}
44+
}
45+
}
46+
}
47+
},
48+
"postCreateCommand": "cp -n .env.example .env 2>/dev/null; poetry install --with dev",
49+
"remoteUser": "app"
50+
}

.devcontainer/docker-compose.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
services:
2+
dev:
3+
build:
4+
context: ..
5+
dockerfile: .devcontainer/Dockerfile
6+
image: fastapi-modulith:dev
7+
restart: unless-stopped
8+
environment:
9+
APP_ENV: development
10+
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-devpass}@db:5432/${POSTGRES_DB:-todo_db}
11+
REDIS_URL: redis://:${REDIS_PASSWORD:-devpass}@redis:6379/0
12+
OTEL_ENABLED: "true"
13+
OTEL_SERVICE_NAME: "fastapi-modulith"
14+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4318/v1/traces"
15+
env_file:
16+
- ../.env
17+
volumes:
18+
- ..:/app:cached
19+
ports:
20+
- "${APP_PORT:-8000}:8000"
21+
depends_on:
22+
db:
23+
condition: service_healthy
24+
redis:
25+
condition: service_healthy
26+
command: sleep infinity
27+
28+
prometheus:
29+
image: prom/prometheus:latest
30+
restart: unless-stopped
31+
ports:
32+
- "${PROMETHEUS_PORT:-9090}:9090"
33+
volumes:
34+
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
35+
- prometheus_data:/prometheus
36+
command:
37+
- "--config.file=/etc/prometheus/prometheus.yml"
38+
- "--storage.tsdb.path=/prometheus"
39+
- "--storage.tsdb.retention.time=30d"
40+
41+
otel-collector:
42+
image: otel/opentelemetry-collector-contrib:latest
43+
restart: unless-stopped
44+
ports:
45+
- "${OTEL_COLLECTOR_PORT:-4318}:4318"
46+
volumes:
47+
- ../docker/otel-collector/config.yaml:/etc/otelcol-contrib/config.yaml:ro
48+
49+
db:
50+
image: postgres:17-alpine
51+
restart: unless-stopped
52+
environment:
53+
POSTGRES_USER: ${POSTGRES_USER:-postgres}
54+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-devpass}
55+
POSTGRES_DB: ${POSTGRES_DB:-todo_db}
56+
volumes:
57+
- postgres_data:/var/lib/postgresql/data
58+
healthcheck:
59+
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
60+
interval: 10s
61+
timeout: 5s
62+
retries: 5
63+
64+
redis:
65+
image: redis:8-alpine
66+
restart: unless-stopped
67+
command:
68+
[
69+
"redis-server",
70+
"--appendonly",
71+
"yes",
72+
"--requirepass",
73+
"${REDIS_PASSWORD:-devpass}",
74+
]
75+
volumes:
76+
- redis_data:/data
77+
healthcheck:
78+
test:
79+
[
80+
"CMD",
81+
"redis-cli",
82+
"-a",
83+
"${REDIS_PASSWORD:-devpass}",
84+
"ping",
85+
]
86+
interval: 10s
87+
timeout: 5s
88+
retries: 5
89+
90+
volumes:
91+
postgres_data:
92+
redis_data:
93+
prometheus_data:

.devcontainer/prometheus.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
global:
2+
scrape_interval: 15s
3+
evaluation_interval: 15s
4+
5+
scrape_configs:
6+
- job_name: "fastapi-modulith"
7+
static_configs:
8+
- targets: ["dev:8000"]
9+
metrics_path: /metrics

.env.example

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ REDIS_URL=
2222
# JWT signing secret. Change this in every deployed environment.
2323
SECRET_KEY=
2424

25-
# Maximum request body size in bytes.
26-
MAX_REQUEST_SIZE_MB=5242880 #5mb
25+
# Maximum request body size in bytes (default 5 MiB).
26+
MAX_REQUEST_SIZE_BYTES=5242880
2727

2828
# JWT signing, validation, and token lifetime settings.
2929
ALGORITHM=HS256
@@ -51,9 +51,19 @@ ACCOUNT_LOCKOUT_MAX_ATTEMPTS=5
5151
ACCOUNT_LOCKOUT_WINDOW_MINUTES=15
5252
ACCOUNT_LOCKOUT_DURATION_MINUTES=15
5353

54+
# CSRF protection toggle.
55+
CSRF_PROTECTION_ENABLED=true
56+
5457
# Logging output format for application logs.
5558
LOG_FORMAT=json
5659

60+
# OpenTelemetry distributed tracing configuration.
61+
# Set OTEL_ENABLED=true and point OTEL_EXPORTER_OTLP_ENDPOINT at a collector to enable tracing.
62+
OTEL_ENABLED=false
63+
OTEL_SERVICE_NAME=fastapi-modulith
64+
OTEL_EXPORTER_OTLP_ENDPOINT=
65+
OTEL_EXPORTER_OTLP_HEADERS=
66+
5767
# Email provider selection. Options: ses, sendgrid, smtp.
5868
EMAIL_PROVIDER=ses
5969

@@ -75,6 +85,9 @@ SMTP_PASSWORD=
7585
SMTP_FROM_EMAIL=noreply@example.com
7686
SMTP_USE_TLS=true
7787

88+
# Enable multitenant isolation. When false, all data uses a single "Default" tenant.
89+
MULTITENANT_ENABLED=false
90+
7891
# Optional admin and development users created by database seeders.
7992
SEED_ADMIN_EMAIL=
8093
SEED_ADMIN_PASSWORD=

.github/workflows/ci.yml

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ on:
1111
permissions:
1212
contents: read
1313
packages: write
14+
id-token: write
1415

1516
env:
1617
IMAGE_NAME: ghcr.io/${{ github.repository }}
@@ -25,6 +26,8 @@ jobs:
2526
steps:
2627
- name: Check out repository
2728
uses: actions/checkout@v4
29+
with:
30+
fetch-depth: 0
2831

2932
- name: Set up Python
3033
uses: actions/setup-python@v5
@@ -45,14 +48,19 @@ jobs:
4548
- name: Install dependencies
4649
run: poetry install --with dev --no-interaction --no-ansi --no-root
4750

51+
- name: Run secret scanning
52+
uses: gitleaks/gitleaks-action@v2
53+
env:
54+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55+
4856
- name: Run lint
4957
run: poetry run ruff check src tests scripts
5058

5159
- name: Run tests
5260
run: poetry run pytest -q
5361

5462
docker:
55-
name: Build and publish image
63+
name: Build, sign, and publish image
5664
runs-on: ubuntu-latest
5765
needs: verify
5866

@@ -81,7 +89,8 @@ jobs:
8189
type=ref,event=tag
8290
type=sha,prefix=sha-
8391
84-
- name: Build image
92+
- name: Build and push image
93+
id: build
8594
uses: docker/build-push-action@v6
8695
with:
8796
context: .
@@ -91,3 +100,30 @@ jobs:
91100
labels: ${{ steps.meta.outputs.labels }}
92101
cache-from: type=gha
93102
cache-to: type=gha,mode=max
103+
104+
- name: Generate SBOM
105+
if: github.event_name == 'push'
106+
uses: aquasecurity/trivy-action@master
107+
with:
108+
image-ref: ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
109+
format: cyclonedx
110+
output: sbom.cyclonedx.json
111+
112+
- name: Upload SBOM
113+
if: github.event_name == 'push'
114+
uses: actions/upload-artifact@v4
115+
with:
116+
name: sbom
117+
path: sbom.cyclonedx.json
118+
119+
- name: Install cosign
120+
if: github.event_name == 'push'
121+
uses: sigstore/cosign-installer@v3
122+
123+
- name: Sign container image
124+
if: github.event_name == 'push'
125+
env:
126+
DIGEST: ${{ steps.build.outputs.digest }}
127+
run: |
128+
cosign sign --yes \
129+
"${{ env.IMAGE_NAME }}@${DIGEST}"

.importlinter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ allow_indirect_imports = True
88
source_modules =
99
src.modules.todo
1010
forbidden_modules =
11+
src.modules.user.providers
1112
src.modules.user.application
1213
src.modules.user.domain
1314
src.modules.user.infrastructure

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ COMPOSE_FILE := docker-compose.yml
1111

1212
.DEFAULT_GOAL := help
1313

14-
.PHONY: help install run test lint lint-imports import-check security-scan check migrate seed downgrade revision db-up db-down db-logs clean
14+
.PHONY: help install run test lint lint-imports import-check security-scan sbom check migrate seed downgrade revision db-up db-down db-logs clean
1515

1616
help:
1717
@echo "[make:help] Available commands:"
@@ -22,6 +22,7 @@ help:
2222
@echo " [make:lint-imports] Enforce import boundary contracts"
2323
@echo " [make:import-check] Verify src.main imports"
2424
@echo " [make:security-scan] Run dependency vulnerability scan with pip-audit"
25+
@echo " [make:sbom] Generate CycloneDX SBOM for the project"
2526
@echo " [make:check] Run tests, lint, and import check"
2627
@echo " [make:migrate] Apply Alembic migrations"
2728
@echo " [make:seed] Seed baseline database records"
@@ -60,6 +61,10 @@ security-scan:
6061
@echo "[make:security-scan] Running dependency vulnerability scan"
6162
@PIP_CACHE_DIR=.cache/pip $(POETRY) run pip-audit --cache-dir .cache/pip-audit
6263

64+
sbom:
65+
@echo "[make:sbom] Generating CycloneDX SBOM"
66+
@$(POETRY) run cyclonedx-py
67+
6368
check: test lint lint-imports import-check
6469
@echo "[make:check] All checks completed"
6570

0 commit comments

Comments
 (0)