Skip to content

Commit 33666f8

Browse files
authored
Merge pull request #2 from Qomserver/cursor/prepare-project-for-production-deployment-a673
Prepare project for production deployment
2 parents 8756f7a + 932307e commit 33666f8

36 files changed

Lines changed: 1101 additions & 12 deletions

.github/workflows/ci.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-python@v5
15+
with:
16+
python-version: '3.12'
17+
- name: Install deps
18+
run: |
19+
python -m pip install --upgrade pip
20+
pip install -r app/requirements.txt
21+
- name: Lint
22+
run: |
23+
black --check app tests
24+
isort --check-only app tests
25+
flake8 app tests
26+
- name: Tests
27+
run: pytest -q

.gitignore

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.egg-info/
6+
.eggs/
7+
.build/
8+
dist/
9+
build/
10+
11+
# Environments
12+
.venv/
13+
venv/
14+
.env
15+
.env.*
16+
.python-version
17+
18+
# Testing
19+
.pytest_cache/
20+
.coverage
21+
.coverage.*
22+
htmlcov/
23+
.mypy_cache/
24+
.ruff_cache/
25+
26+
# IDEs and editors
27+
.vscode/
28+
.idea/
29+
*.swp
30+
*.swo
31+
32+
# OS
33+
.DS_Store
34+
Thumbs.db
35+
36+
# Node
37+
node_modules/
38+
39+
# Docker
40+
*.log
41+
cache/
42+
.docker/
43+
44+
# Alembic
45+
app/src/alembic/versions/__pycache__/
46+
47+
# SQLite
48+
*.sqlite3
49+
50+
# Misc
51+
*.bak

.pre-commit-config.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
repos:
2+
- repo: https://github.com/psf/black
3+
rev: 24.4.2
4+
hooks:
5+
- id: black
6+
language_version: python3
7+
- repo: https://github.com/pycqa/isort
8+
rev: 5.13.2
9+
hooks:
10+
- id: isort
11+
- repo: https://github.com/pycqa/flake8
12+
rev: 7.0.0
13+
hooks:
14+
- id: flake8
15+
- repo: https://github.com/pre-commit/pre-commit-hooks
16+
rev: v4.6.0
17+
hooks:
18+
- id: end-of-file-fixer
19+
- id: trailing-whitespace
20+
- id: check-added-large-files

Makefile

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
PYTHON := python3
2+
PIP := pip3
3+
APP_DIR := app
4+
5+
.PHONY: setup run lint format test docker-build docker-up docker-down migrate revision
6+
7+
setup:
8+
$(PIP) install -r $(APP_DIR)/requirements.txt
9+
10+
run:
11+
uvicorn src.main:create_app --factory --host 0.0.0.0 --port 8000 --app-dir $(APP_DIR) --reload
12+
13+
lint:
14+
black --check $(APP_DIR) tests
15+
isort --check-only $(APP_DIR) tests
16+
flake8 $(APP_DIR) tests
17+
18+
format:
19+
black $(APP_DIR) tests
20+
isort $(APP_DIR) tests
21+
22+
test:
23+
pytest -q
24+
25+
revision:
26+
alembic -c $(APP_DIR)/alembic.ini revision -m "manual"
27+
28+
migrate:
29+
alembic -c $(APP_DIR)/alembic.ini upgrade head
30+
31+
docker-build:
32+
docker compose -f infra/docker-compose.yml build
33+
34+
docker-up:
35+
docker compose -f infra/docker-compose.yml up
36+
37+
docker-down:
38+
docker compose -f infra/docker-compose.yml down -v

README.md

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,61 @@
1-
- 👋 Hi, I’m @Qomserver
2-
- 👀 I’m interested in ...
3-
- 🌱 I’m currently learning ...
4-
- 💞️ I’m looking to collaborate on ...
5-
- 📫 How to reach me ...
6-
- 😄 Pronouns: ...
7-
- ⚡ Fun fact: ...
8-
9-
<!---
10-
Qomserver/Qomserver is a ✨ special ✨ repository because its `README.md` (this file) appears on your GitHub profile.
11-
You can click the Preview link to take a look at your changes.
12-
--->
1+
## ProdReadyApp (FastAPI + Postgres)
2+
3+
A production-ready web application with authentication and task management built with FastAPI, SQLAlchemy, Alembic, and PostgreSQL. Includes Docker, CI, tests, and secure defaults.
4+
5+
### Features
6+
- JWT auth (access + refresh), password hashing
7+
- Users + Tasks CRUD with ownership and admin controls
8+
- SQLAlchemy 2.0 + Alembic migrations
9+
- Health checks and Prometheus metrics
10+
- Rate limiting, CORS, secure headers
11+
- Gunicorn + Uvicorn workers
12+
- Docker Compose for local/dev/prod
13+
- GitHub Actions CI (lint + tests)
14+
15+
### Quickstart (Docker)
16+
1. Copy env file:
17+
```bash
18+
cp .env.example .env
19+
```
20+
2. Start services:
21+
```bash
22+
docker compose -f infra/docker-compose.yml up --build
23+
```
24+
3. App available at `http://localhost:8000` (API docs at `/docs`).
25+
26+
### Local (no Docker)
27+
```bash
28+
python -m venv .venv && source .venv/bin/activate
29+
pip install -r app/requirements.txt
30+
export $(grep -v '^#' .env.example | xargs) # or create .env
31+
alembic -c app/alembic.ini upgrade head
32+
uvicorn src.main:create_app --factory --host 0.0.0.0 --port 8000 --app-dir app
33+
```
34+
35+
### Useful Make targets
36+
```bash
37+
make setup # install deps
38+
make run # run dev server
39+
make test # run tests
40+
make lint # run linters
41+
make format # format code
42+
make docker-up
43+
```
44+
45+
### Migrations
46+
```bash
47+
alembic -c app/alembic.ini revision -m "message"
48+
alembic -c app/alembic.ini upgrade head
49+
```
50+
51+
### Configuration
52+
Set environment variables (see `.env.example`). Defaults are safe for local dev. For production, always set `JWT_SECRET`, use strong DB credentials, and terminate TLS at a reverse proxy or load balancer.
53+
54+
### Security Notes
55+
- Tokens are short-lived, refresh tokens persisted and revocable
56+
- Rate limits applied to auth routes
57+
- CORS is restricted via env
58+
- Non-root container user
59+
60+
### License
61+
MIT

app/Dockerfile

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# syntax=docker/dockerfile:1.7-labs
2+
3+
FROM python:3.12-slim AS base
4+
ENV PYTHONDONTWRITEBYTECODE=1 \
5+
PYTHONUNBUFFERED=1 \
6+
PIP_NO_CACHE_DIR=1
7+
WORKDIR /app
8+
9+
# System deps
10+
RUN apt-get update && apt-get install -y --no-install-recommends \
11+
build-essential \
12+
libpq-dev \
13+
&& rm -rf /var/lib/apt/lists/*
14+
15+
# Install deps
16+
COPY app/requirements.txt /app/requirements.txt
17+
RUN pip install -r /app/requirements.txt
18+
19+
# Runtime image
20+
FROM python:3.12-slim AS runtime
21+
ENV PYTHONDONTWRITEBYTECODE=1 \
22+
PYTHONUNBUFFERED=1 \
23+
PIP_NO_CACHE_DIR=1
24+
WORKDIR /app
25+
26+
# Create non-root user
27+
RUN useradd -m -u 10001 appuser
28+
29+
# Copy installed packages and source
30+
COPY --from=base /usr/local /usr/local
31+
COPY app /app
32+
33+
# Ensure runtime dirs
34+
RUN mkdir -p /app/src/alembic/versions && chown -R appuser:appuser /app
35+
36+
USER appuser
37+
38+
EXPOSE 8000
39+
40+
ENTRYPOINT ["/bin/bash", "-lc"]
41+
CMD ["bash", "docker/entrypoint.sh"]

app/alembic.ini

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[alembic]
2+
script_location = app/src/alembic
3+
prepend_sys_path = .
4+
5+
sqlalchemy.url = %(DATABASE_URL)s
6+
7+
[loggers]
8+
keys = root,sqlalchemy,alembic
9+
10+
[handlers]
11+
keys = console
12+
13+
[formatters]
14+
keys = generic
15+
16+
[logger_root]
17+
level = WARN
18+
handlers = console
19+
20+
[logger_sqlalchemy]
21+
level = WARN
22+
handlers =
23+
qualname = sqlalchemy.engine
24+
25+
[logger_alembic]
26+
level = INFO
27+
handlers = console
28+
qualname = alembic
29+
30+
[handler_console]
31+
class = StreamHandler
32+
args = (sys.stderr,)
33+
level = NOTSET
34+
formatter = generic
35+
36+
[formatter_generic]
37+
format = %(levelname)-5.5s [%(name)s] %(message)s

app/docker/entrypoint.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Wait for DB if DATABASE_URL points to a service
5+
python - <<'PY'
6+
import os, time
7+
from urllib.parse import urlparse
8+
import socket
9+
10+
url = os.environ.get('DATABASE_URL', '')
11+
if url:
12+
parsed = urlparse(url)
13+
host = parsed.hostname
14+
port = parsed.port or 5432
15+
if host not in (None, 'localhost', '127.0.0.1'):
16+
for _ in range(60):
17+
try:
18+
with socket.create_connection((host, port), timeout=1):
19+
break
20+
except OSError:
21+
time.sleep(1)
22+
PY
23+
24+
alembic -c app/alembic.ini upgrade head
25+
exec gunicorn 'src.main:create_app()' -k uvicorn.workers.UvicornWorker -c app/gunicorn_conf.py

app/gunicorn_conf.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import multiprocessing
2+
3+
bind = "0.0.0.0:8000"
4+
workers = multiprocessing.cpu_count() * 2 + 1
5+
worker_class = "uvicorn.workers.UvicornWorker"
6+
threads = 2
7+
keepalive = 30
8+
timeout = 60
9+
accesslog = "-"
10+
errorlog = "-"
11+
loglevel = "info"
12+
forwarded_allow_ips = "*"

app/requirements.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
fastapi==0.110.2
2+
uvicorn[standard]==0.30.0
3+
gunicorn==21.2.0
4+
SQLAlchemy==2.0.36
5+
psycopg[binary]==3.2.9
6+
alembic==1.13.1
7+
pydantic[email]==2.9.2
8+
pydantic-settings==2.2.1
9+
passlib[bcrypt]==1.7.4
10+
python-jose[cryptography]==3.3.0
11+
slowapi==0.1.9
12+
prometheus-client==0.19.0
13+
Jinja2==3.1.4
14+
httpx==0.27.0
15+
16+
# Dev
17+
pytest==8.2.0
18+
pytest-asyncio==0.23.6
19+
black==24.4.2
20+
isort==5.13.2
21+
flake8==7.0.0
22+
mypy==1.10.0

0 commit comments

Comments
 (0)