diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..669d81d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.venv/ +__pycache__/ +*.pyc +.env +.git +.gitignore +*.log +.dbpass +migrations/_migrations* diff --git a/.gitignore b/.gitignore index 06c9967..a7c4f7e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ __pycache__/ .env *.log .DS_Store +.dbpass migrations/_migrations* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..017e85d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +# Jetour BI Dashboard — Dockerfile +# Multi-stage: builder for slim runtime + +FROM python:3.12-slim AS builder + +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir --user -r requirements.txt + +FROM python:3.12-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libpq5 curl \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy installed packages from builder +COPY --from=builder /root/.local /root/.local + +# Copy app code +COPY app.py . +COPY static/ static/ +COPY migrations/ migrations/ +COPY migrate.py . + +# Make sure scripts in .local are in PATH +ENV PATH=/root/.local/bin:$PATH + +EXPOSE 8765 + +# Run migrations then start server +CMD python migrate.py && uvicorn app:app --host 0.0.0.0 --port 8765 diff --git a/README.md b/README.md index 21034f5..8adf65c 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,45 @@ Migrations are tracked in the `_migrations` table. Safe to run repeatedly. | Monthly Report | 1st 07:00 | `POST /api/send-report/monthly` | Dashboard handles all SMTP send + recipient resolution. + +## Docker Deployment + +```bash +# Build and start (app + PostgreSQL) +docker compose up -d + +# Set a custom DB password +DB_PASSWORD=mysecretpassword docker compose up -d + +# View logs +docker compose logs -f app + +# Stop +docker compose down + +# Reset (destroy DB volume) +docker compose down -v +``` + +### Environment variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `DB_HOST` | `db` | PostgreSQL host | +| `DB_PORT` | `5432` | PostgreSQL port | +| `DB_NAME` | `jetour-bi` | Database name | +| `DB_USER` | `jetour` | Database user | +| `DB_PASSWORD` | `jetour` | Database password | +| `JWT_SECRET` | `change-me-in-production` | JWT signing secret | +| `SMTP_HOST` | (empty) | SMTP server for email reports | +| `SMTP_PORT` | `587` | SMTP port | +| `SMTP_USER` | (empty) | SMTP username | +| `SMTP_PASS` | (empty) | SMTP password | +| `SMTP_FROM` | `reports@jetour.mu` | From address | + +### Ports + +| Service | Port | Description | +|---------|------|-------------| +| Dashboard | `8765` | Web UI | +| PostgreSQL | `5433` | Mapped from container `5432` | diff --git a/app.py b/app.py index f60df56..8214721 100644 --- a/app.py +++ b/app.py @@ -345,10 +345,24 @@ def get_settings(user=Depends(require_admin)): # ── Existing API endpoints ──────────────────────────────────────────────── # Database connection -HOST, PORT, DB, USER = "100.91.65.42", 5432, "jetour-bi", "postgres" -PASS = "#AlteraM@01" +import os from urllib.parse import quote_plus -DB_URL = f"postgresql://{USER}:{quote_plus(PASS)}@{HOST}:{PORT}/{DB}?sslmode=disable" + +def get_db_url(): + """Build database URL from env vars or fall back to defaults.""" + url = os.environ.get("DATABASE_URL") + if url: + return url + host = os.environ.get("DB_HOST", "100.91.65.42") + port = os.environ.get("DB_PORT", "5432") + db = os.environ.get("DB_NAME", "jetour-bi") + user = os.environ.get("DB_USER", "postgres") + password = os.environ.get("DB_PASSWORD", "") + if not password: + password = "#AlteraM@01" + return f"postgresql://{user}:{quote_plus(password)}@{host}:{port}/{db}?sslmode=disable" + +DB_URL = get_db_url() def conn(): return psycopg.connect(DB_URL, row_factory=dict_row) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cca5be3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,45 @@ +version: "3.8" + +services: + app: + build: . + container_name: jetour-bi + ports: + - "8765:8765" + environment: + DB_HOST: db + DB_PORT: "5432" + DB_NAME: jetour-bi + DB_USER: jetour + DB_PASSWORD: ${DB_PASSWORD:-jetour} + JWT_SECRET: "${JWT_SECRET:-change-me-in-production}" + SMTP_HOST: "${SMTP_HOST:-}" + SMTP_PORT: "${SMTP_PORT:-587}" + SMTP_USER: "${SMTP_USER:-}" + SMTP_PASS: "${SMTP_PASS:-}" + SMTP_FROM: "${SMTP_FROM:-reports@jetour.mu}" + depends_on: + db: + condition: service_healthy + restart: unless-stopped + + db: + image: postgres:16-alpine + container_name: jetour-db + environment: + POSTGRES_USER: jetour + POSTGRES_PASSWORD: ${DB_PASSWORD:-jetour} + POSTGRES_DB: jetour-bi + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "5433:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U jetour -d jetour-bi"] + interval: 5s + timeout: 3s + retries: 5 + restart: unless-stopped + +volumes: + pgdata: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..df7fa36 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +fastapi>=0.100.0 +uvicorn[standard]>=0.23.0 +psycopg[binary]>=3.1.0 +passlib[bcrypt]>=1.7.4 +python-jose[cryptography]>=3.3.0 +python-multipart>=0.0.6