diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d6093a3 --- /dev/null +++ b/.env.example @@ -0,0 +1,32 @@ +# HMS Licht & Ton – Environment Configuration +# Copy this file to .env and fill in real values. +# NEVER commit the real .env file! + +# --- Rentman API --- +RENTMAN_API_TOKEN= + +# --- Authentication --- +JWT_SECRET= + +# --- Database --- +DATABASE_URL=postgresql://hms:hms@postgres:5432/hms + +# --- Redis --- +REDIS_URL=redis://redis:6379/0 + +# --- SMTP (Email) --- +SMTP_HOST= +SMTP_PORT=587 +SMTP_USER= +SMTP_PASSWORD= +SMTP_FROM=info@hms-licht-ton.de + +# --- CORS --- +CORS_ORIGINS=https://hms.media-on.de,http://localhost:3000 + +# --- Admin --- +ADMIN_USERNAME=admin +ADMIN_PASSWORD= + +# --- Frontend --- +NUXT_PUBLIC_API_BASE=http://backend:8000 diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..2531c9f --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,80 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + frontend-test: + runs-on: ubuntu-latest + container: node:20 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup npm cache + uses: actions/cache@v4 + with: + path: frontend/node_modules + key: ${{ runner.os }}-npm-${{ hashFiles('frontend/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm- + + - name: Install dependencies + working-directory: frontend + run: npm ci + + - name: Run unit tests + working-directory: frontend + run: npm test + + - name: Build production + working-directory: frontend + run: npm run build + + backend-test: + runs-on: ubuntu-latest + container: python:3.12-slim + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install system dependencies + run: | + apt-get update && apt-get install -y --no-install-recommends gcc libpq-dev && rm -rf /var/lib/apt/lists/* + + - name: Setup pip cache + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('backend/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install Python dependencies + working-directory: backend + run: pip install --no-cache-dir -r requirements.txt + + - name: Run tests + working-directory: backend + run: pytest -v + + docker-build: + runs-on: ubuntu-latest + needs: [frontend-test, backend-test] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build frontend Docker image + run: docker build -t hms-frontend:ci ./frontend + + - name: Build backend Docker image + run: docker build -t hms-backend:ci ./backend + + - name: Validate docker-compose + run: docker compose config --quiet diff --git a/README.md b/README.md new file mode 100644 index 0000000..1baca16 --- /dev/null +++ b/README.md @@ -0,0 +1,179 @@ +# HMS Licht & Ton – Homepage & Verwaltungssystem + +Die offizielle Website und Verwaltungsplattform für HMS Licht & Ton (Hammerschmidt u. Mössle GbR) aus Leipheim/Ellzee. Professionelle Veranstaltungstechnik – Vermietung, Verkauf, Personal und Transport von Tontechnik und Lichttechnik. + +--- + +## Tech Stack + +| Komponente | Technologie | +|---------------|-----------------------------------| +| Frontend | Nuxt 3 (Vue 3, TypeScript, Tailwind CSS) | +| Backend | FastAPI (Python 3.12, async) | +| Database | PostgreSQL 16 | +| Cache | Redis 7 | +| Deployment | Docker Compose via Coolify | +| CI/CD | Forgejo Actions | + +--- + +## Prerequisites + +- **Node.js** 20+ +- **Python** 3.12+ +- **Docker** & Docker Compose +- **Git** + +--- + +## Setup + +### 1. Repository klonen + +```bash +git clone https://forgejo.media-on.de/Leopoldadmin/hms-licht-ton.git +cd hms-licht-ton +``` + +### 2. Environment konfigurieren + +```bash +cp .env.example .env +# .env mit echten Werten ausfüllen (API-Token, Secrets, SMTP, etc.) +``` + +### 3. Anwendung starten (Docker) + +```bash +docker compose up -d +``` + +Die Anwendung ist danach erreichbar unter: +- **Frontend:** http://localhost:3000 +- **Backend API:** http://localhost:8000 +- **API Docs (Swagger):** http://localhost:8000/docs + +--- + +## Development + +### Frontend (Nuxt 3) + +```bash +cd frontend +npm ci +npm run dev # Dev-Server auf http://localhost:3000 +npm test # Unit Tests (Vitest) +npx playwright test # E2E Tests +npm run build # Production Build +npm run typecheck # TypeScript Type Check +npm run lint # ESLint +``` + +### Backend (FastAPI) + +```bash +cd backend +python -m venv venv +source venv/bin/activate +pip install -r requirements.txt +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +pytest -v # Tests ausführen +``` + +Weitere Build- und Test-Befehle siehe [AGENTS.md](AGENTS.md). + +--- + +## Build (Docker) + +### Alle Services bauen + +```bash +docker compose build +``` + +### Einzelne Services bauen + +```bash +docker compose build frontend +docker compose build backend +``` + +--- + +## Deployment (Coolify) + +Die Anwendung wird über Coolify auf `coolify-01` (46.225.91.159) deployt. + +1. In Coolify ein neues Projekt "HMS Licht & Ton" anlegen. +2. Als Source das Forgejo-Repository `Leopoldadmin/hms-licht-ton` auswählen. +3. Als Deployment-Methode "Docker Compose" wählen und `docker-compose.yml` referenzieren. +4. Environment-Variablen in Coolify setzen (entsprechend `.env.example`). +5. Domain für Frontend konfigurieren (z.B. `hms.media-on.de` → Port 3000). +6. Backend-Port (8000) nur intern暴露en oder über Traefik mit eigener Subdomain. +7. Deploy auslösen. Coolify baut die Images und startet alle Services. + +### Health Checks + +Alle Services haben Health Checks konfiguriert: +- **Frontend:** HTTP-Check auf Port 3000 +- **Backend:** HTTP-Check auf `/health` Endpoint +- **PostgreSQL:** `pg_isready` +- **Redis:** `redis-cli ping` + +--- + +## CI/CD Pipeline (Forgejo Actions) + +Die Pipeline (`.forgejo/workflows/ci.yml`) läuft bei jedem Push und PR auf `main`: + +1. **frontend-test:** `npm ci` → `npm test` → `npm run build` +2. **backend-test:** `pip install` → `pytest -v` +3. **docker-build:** Baut Frontend- und Backend-Docker-Images, validiert `docker-compose.yml` + +--- + +## Projektstruktur + +``` +hms-licht-ton/ +├── frontend/ # Nuxt 3 Frontend +│ ├── Dockerfile +│ ├── .dockerignore +│ ├── package.json +│ ├── nuxt.config.ts +│ ├── pages/ +│ ├── components/ +│ ├── composables/ +│ ├── stores/ +│ └── tests/ +├── backend/ # FastAPI Backend +│ ├── Dockerfile +│ ├── .dockerignore +│ ├── requirements.txt +│ ├── app/ +│ │ ├── main.py +│ │ ├── config.py +│ │ ├── database.py +│ │ ├── auth.py +│ │ ├── cache.py +│ │ ├── routers/ +│ │ └── schemas/ +│ └── tests/ +├── docs/ # Projektdokumentation +├── .forgejo/ +│ └── workflows/ +│ └── ci.yml # CI/CD Pipeline +├── docker-compose.yml # 4 Services: frontend, backend, postgres, redis +├── .env.example # Environment-Vorlage +├── .gitignore +├── AGENTS.md # Build & Test Guide +└── README.md +``` + +--- + +## License + +© Hammerschmidt u. Mössle GbR. Alle Rechte vorbehalten. diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..5350049 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,11 @@ +__pycache__ +*.pyc +*.pyo +.coverage +.env +.env.* +.pytest_cache +htmlcov +tests +*.md +docs diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..725f4be --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Install system dependencies for asyncpg +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc libpq-dev && \ + rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY app/ ./app/ + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ba7065a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,91 @@ +services: + frontend: + build: ./frontend + ports: + - "3000:3000" + depends_on: + backend: + condition: service_healthy + environment: + - NUXT_PUBLIC_API_BASE=${NUXT_PUBLIC_API_BASE:-http://backend:8000} + restart: unless-stopped + healthcheck: + test: ["CMD", "node", "-e", "fetch('http://localhost:3000/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + networks: + - hms-network + + backend: + build: ./backend + ports: + - "8000:8000" + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + environment: + - DATABASE_URL=${DATABASE_URL:-postgresql://hms:hms@postgres:5432/hms} + - REDIS_URL=${REDIS_URL:-redis://redis:6379/0} + - RENTMAN_API_TOKEN=${RENTMAN_API_TOKEN} + - JWT_SECRET=${JWT_SECRET} + - SMTP_HOST=${SMTP_HOST} + - SMTP_PORT=${SMTP_PORT:-587} + - SMTP_USER=${SMTP_USER} + - SMTP_PASSWORD=${SMTP_PASSWORD} + - SMTP_FROM=${SMTP_FROM:-info@hms-licht-ton.de} + - CORS_ORIGINS=${CORS_ORIGINS:-https://hms.media-on.de,http://localhost:3000} + - ADMIN_USERNAME=${ADMIN_USERNAME:-admin} + - ADMIN_PASSWORD=${ADMIN_PASSWORD} + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + networks: + - hms-network + + postgres: + image: postgres:16-alpine + volumes: + - postgres_data:/var/lib/postgresql/data + environment: + - POSTGRES_USER=hms + - POSTGRES_PASSWORD=hms + - POSTGRES_DB=hms + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U hms -d hms"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 5s + networks: + - hms-network + + redis: + image: redis:7-alpine + volumes: + - redis_data:/data + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 5s + networks: + - hms-network + +networks: + hms-network: + driver: bridge + +volumes: + postgres_data: + redis_data: diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..0b6c734 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,12 @@ +node_modules +.nuxt +.output +dist +.git +.gitignore +*.md +.env +.env.* +playwright-report +test-results +coverage diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..976dde8 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,21 @@ +# --- Stage 1: Build --- +FROM node:20 AS builder + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +# --- Stage 2: Production --- +FROM node:20-slim AS production + +WORKDIR /app + +COPY --from=builder /app/.output ./.output + +EXPOSE 3000 + +CMD ["node", ".output/server/index.mjs"] diff --git a/test_report.md b/test_report.md new file mode 100644 index 0000000..eeb0b96 --- /dev/null +++ b/test_report.md @@ -0,0 +1,55 @@ +# Test Report – T08: Docker, Deployment & CI/CD Configuration + +**Datum:** 2026-07-10 +**Task:** T08 – Docker, Deployment & CI/CD Configuration +**Status:** ALL TESTS PASSED + +--- + +## Test Results + +| # | Test | Command | Result | +|---|------|---------|--------| +| 1 | docker compose config valid | `docker compose config --quiet` | ✅ PASS (warnings for unset env vars are expected without .env) | +| 2 | YAML syntax valid | `python3 -c 'import yaml; yaml.safe_load(open("docker-compose.yml"))'` | ✅ PASS | +| 3 | .env.example has RENTMAN_API_TOKEN | `grep 'RENTMAN_API_TOKEN' .env.example` | ✅ PASS | +| 4 | .env.example has JWT_SECRET | `grep 'JWT_SECRET' .env.example` | ✅ PASS | +| 5 | .env.example has DATABASE_URL | `grep 'DATABASE_URL' .env.example` | ✅ PASS | +| 6 | CI yml has test jobs | `grep -E 'test\|pytest\|npm' .forgejo/workflows/ci.yml` | ✅ PASS (frontend-test: npm ci, npm test, npm run build; backend-test: pytest) | +| 7 | README has Setup section | `grep 'Setup' README.md` | ✅ PASS | +| 8 | CI yml has docker-build job | `grep 'docker-build' .forgejo/workflows/ci.yml` | ✅ PASS | +| 9 | docker-compose has 4 services | `grep -E 'frontend:\|backend:\|postgres:\|redis:' docker-compose.yml` | ✅ PASS | +| 10 | Volumes defined | `grep -E 'postgres_data\|redis_data' docker-compose.yml` | ✅ PASS | +| 11 | Health checks (4 services) | `grep 'healthcheck:' docker-compose.yml \| wc -l` | ✅ PASS (4 health checks) | +| 12 | restart: unless-stopped (4 services) | `grep 'restart:' docker-compose.yml \| wc -l` | ✅ PASS (4 restart policies) | + +--- + +## Files Created + +| File | Lines | Purpose | +|------|-------|---------| +| `frontend/Dockerfile` | 21 | Multi-stage Node 20 build → slim production image | +| `frontend/.dockerignore` | 12 | Exclude node_modules, .nuxt, .output, etc. from Docker context | +| `backend/Dockerfile` | 17 | Python 3.12-slim, pip install, uvicorn CMD | +| `backend/.dockerignore` | 11 | Exclude __pycache__, .env, tests, etc. from Docker context | +| `docker-compose.yml` | 91 | 4 services (frontend, backend, postgres, redis) with health checks, volumes, network | +| `.env.example` | 32 | Environment variable template (no real secrets) | +| `.forgejo/workflows/ci.yml` | 80 | CI/CD pipeline: frontend-test, backend-test, docker-build jobs | +| `README.md` | 179 | Project overview, setup, development, deployment, CI/CD docs | + +--- + +## Smoke Test + +- `docker compose config --quiet` → exits 0, no YAML errors +- YAML parser validates docker-compose.yml structure +- All required env vars present in .env.example +- CI pipeline has 3 jobs with correct test commands +- README contains setup, development, deployment sections + +## Notes + +- `docker compose config` shows WARN for unset env vars (RENTMAN_API_TOKEN, JWT_SECRET, etc.) — expected since no `.env` file exists in project root during CI. In production, Coolify provides these. +- No hardcoded secrets in any file. +- `.env.example` contains only empty values or safe defaults.