feat(T06): deployment config - Dockerfiles, docker-compose, Coolify guide, env example
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
# Runtime Report — ERP Nutzfahrzeuge
|
||||
|
||||
**Date:** 2026-07-17
|
||||
**Phase:** 5 — Runtime Verification
|
||||
**Runtime:** `/opt/venv/bin/python` (execution environment)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Check | Status |
|
||||
|-------|--------|
|
||||
| Backend startup | ✅ PASS |
|
||||
| Health endpoint | ✅ PASS (200, `{"status":"ok"}`) |
|
||||
| Root endpoint | ✅ PASS (200, app metadata) |
|
||||
| Auth login (GET) | ✅ PASS (405 Method Not Allowed — POST-only) |
|
||||
| OpenAPI/Swagger | ✅ PASS (200, full schema) |
|
||||
| Frontend build artifacts | ✅ PASS (`.next/` with BUILD_ID, server/, static/) |
|
||||
| Runtime errors | ✅ NONE (clean logs, no tracebacks) |
|
||||
| PostgreSQL | ✅ Running (localhost:5432, accepting connections) |
|
||||
| Redis | ⚠️ NOT installed (JWT-based refresh tokens, no Redis storage needed) |
|
||||
|
||||
**Overall verdict:** Backend starts and runs cleanly. All public endpoints respond as expected. No runtime errors detected.
|
||||
|
||||
---
|
||||
|
||||
## 1. Startup Verification
|
||||
|
||||
**Start command:**
|
||||
```bash
|
||||
cd backend && /opt/venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
**Result:** Server started successfully (PID 3383733).
|
||||
|
||||
**Startup log:**
|
||||
```
|
||||
INFO: Started server process [3383733]
|
||||
INFO: Waiting for application startup.
|
||||
INFO: Application startup complete.
|
||||
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
**Import check:** `from app.main import app` — OK (no ImportError)
|
||||
|
||||
**Core dependencies verified:**
|
||||
- `uvicorn` ✅
|
||||
- `fastapi` ✅
|
||||
- `sqlalchemy` ✅
|
||||
- `asyncpg` ✅
|
||||
|
||||
---
|
||||
|
||||
## 2. Health Check
|
||||
|
||||
**Endpoint:** `GET http://localhost:8000/api/v1/health`
|
||||
|
||||
**Response:**
|
||||
- HTTP Status: `200 OK`
|
||||
- Body: `{"status":"ok"}`
|
||||
|
||||
**Verdict:** ✅ Health endpoint reachable and returning expected payload.
|
||||
|
||||
---
|
||||
|
||||
## 3. Endpoint Verification
|
||||
|
||||
### 3.1 Root Endpoint
|
||||
- **URL:** `GET http://localhost:8000/`
|
||||
- **HTTP Status:** `200 OK`
|
||||
- **Body:** `{"name":"ERP Nutzfahrzeuge","version":"1.0.0","docs":"/docs"}`
|
||||
|
||||
### 3.2 Auth Login (GET — should fail)
|
||||
- **URL:** `GET http://localhost:8000/api/v1/auth/login`
|
||||
- **HTTP Status:** `405 Method Not Allowed`
|
||||
- **Body:** `{"detail":"Method Not Allowed"}`
|
||||
- **Expected behavior:** Login is POST-only; GET correctly rejected.
|
||||
|
||||
### 3.3 OpenAPI Schema
|
||||
- **URL:** `GET http://localhost:8000/openapi.json`
|
||||
- **HTTP Status:** `200 OK`
|
||||
- **Schema version:** OpenAPI 3.1.0
|
||||
- **Title:** ERP Nutzfahrzeuge Backend API
|
||||
- **Version:** 1.0.0
|
||||
|
||||
---
|
||||
|
||||
## 4. Available API Endpoints (from OpenAPI)
|
||||
|
||||
### Auth (`/api/v1/auth`)
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|------|-------------|------|
|
||||
| POST | `/api/v1/auth/login` | Login with email+password → JWT tokens | No |
|
||||
| POST | `/api/v1/auth/refresh` | Exchange refresh token for new token pair | No |
|
||||
| GET | `/api/v1/auth/me` | Get current user profile | Bearer |
|
||||
|
||||
### Users (`/api/v1/users`)
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|------|-------------|------|
|
||||
| GET | `/api/v1/users/` | List users (paginated, admin only) | Bearer |
|
||||
| POST | `/api/v1/users/` | Create user (admin only) | Bearer |
|
||||
| PUT | `/api/v1/users/{user_id}` | Update user (admin only) | Bearer |
|
||||
| DELETE | `/api/v1/users/{user_id}` | Soft-delete user (admin only) | Bearer |
|
||||
|
||||
### Vehicles (`/api/v1/vehicles`)
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|------|-------------|------|
|
||||
| GET | `/api/v1/vehicles/` | List vehicles (filter, sort, paginate) | Bearer |
|
||||
| POST | `/api/v1/vehicles/` | Create vehicle | Bearer |
|
||||
| GET | `/api/v1/vehicles/{vehicle_id}` | Get single vehicle | Bearer |
|
||||
| PUT | `/api/v1/vehicles/{vehicle_id}` | Update vehicle | Bearer |
|
||||
| DELETE | `/api/v1/vehicles/{vehicle_id}` | Soft-delete vehicle | Bearer |
|
||||
| POST | `/api/v1/vehicles/{vehicle_id}/mobile-de/push` | Push to mobile.de (async, 202) | Bearer |
|
||||
| GET | `/api/v1/vehicles/{vehicle_id}/mobile-de/status` | Get mobile.de sync status | Bearer |
|
||||
|
||||
### Contacts (`/api/v1/contacts`)
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|------|-------------|------|
|
||||
| GET | `/api/v1/contacts/` | List contacts (filter, sort, paginate) | Bearer |
|
||||
| POST | `/api/v1/contacts/` | Create contact (admin/verkaeufer) | Bearer |
|
||||
| GET | `/api/v1/contacts/{contact_id}` | Get contact with persons | Bearer |
|
||||
| PUT | `/api/v1/contacts/{contact_id}` | Update contact | Bearer |
|
||||
| DELETE | `/api/v1/contacts/{contact_id}` | Soft-delete contact | Bearer |
|
||||
| POST | `/api/v1/contacts/{contact_id}/persons` | Add contact person | Bearer |
|
||||
| DELETE | `/api/v1/contacts/{contact_id}/persons/{person_id}` | Remove contact person | Bearer |
|
||||
|
||||
### Files (`/api/v1/vehicles/{vehicle_id}/files`)
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|------|-------------|------|
|
||||
| GET | `/api/v1/vehicles/{vehicle_id}/files` | List files for vehicle | Bearer |
|
||||
| POST | `/api/v1/vehicles/{vehicle_id}/files` | Upload file (multipart, max 20MB) | Bearer |
|
||||
| GET | `/api/v1/vehicles/{vehicle_id}/files/{file_id}` | Download file | Bearer |
|
||||
| DELETE | `/api/v1/vehicles/{vehicle_id}/files/{file_id}` | Delete file | Bearer |
|
||||
|
||||
### OCR (`/api/v1/ocr`)
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|------|-------------|------|
|
||||
| POST | `/api/v1/ocr/upload` | Upload scan for OCR (async, 202) | Bearer |
|
||||
| GET | `/api/v1/ocr/results/{result_id}` | Get OCR result | Bearer |
|
||||
| GET | `/api/v1/ocr/results` | List OCR results | Bearer |
|
||||
| POST | `/api/v1/ocr/results/{result_id}/apply` | Apply OCR data to vehicle | Bearer |
|
||||
|
||||
### Sales (`/api/v1/sales`)
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|------|-------------|------|
|
||||
| GET | `/api/v1/sales/` | List sales (filter by status/date) | Bearer |
|
||||
| POST | `/api/v1/sales/` | Create sale (sets vehicle to 'sold') | Bearer |
|
||||
| GET | `/api/v1/sales/{sale_id}` | Get sale with nested data | Bearer |
|
||||
| PUT | `/api/v1/sales/{sale_id}` | Update sale | Bearer |
|
||||
| DELETE | `/api/v1/sales/{sale_id}` | Cancel sale (soft delete) | Bearer |
|
||||
| POST | `/api/v1/sales/{sale_id}/contract` | Regenerate contract PDF | Bearer |
|
||||
| GET | `/api/v1/sales/{sale_id}/contract` | Download contract PDF | Bearer |
|
||||
| POST | `/api/v1/sales/{sale_id}/verify-ust-id` | Verify USt-IdNr. via BZSt API | Bearer |
|
||||
|
||||
### DATEV (`/api/v1/datev`)
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|------|-------------|------|
|
||||
| POST | `/api/v1/datev/export` | Create DATEV export (CSV) | Bearer |
|
||||
| GET | `/api/v1/datev/exports` | List DATEV exports | Bearer |
|
||||
| GET | `/api/v1/datev/exports/{export_id}/download` | Download DATEV CSV | Bearer |
|
||||
|
||||
### Copilot (`/api/v1/copilot`)
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|------|-------------|------|
|
||||
| POST | `/api/v1/copilot/chat` | Send message to AI Copilot | Bearer |
|
||||
| POST | `/api/v1/copilot/action` | Execute confirmed action | Bearer |
|
||||
| GET | `/api/v1/copilot/history` | Get chat history | Bearer |
|
||||
| POST | `/api/v1/copilot/voice` | Voice input (base64 audio) | Bearer |
|
||||
|
||||
### Retouch (`/api/v1/retouch`)
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|------|-------------|------|
|
||||
| POST | `/api/v1/retouch/process` | Upload image for retouching (async, 202) | Bearer |
|
||||
| GET | `/api/v1/retouch/results/{result_id}` | Get retouch result | Bearer |
|
||||
| POST | `/api/v1/retouch/price-compare` | Compare prices for vehicle | Bearer |
|
||||
|
||||
### Health
|
||||
| Method | Path | Description | Auth |
|
||||
|--------|------|-------------|------|
|
||||
| GET | `/api/v1/health` | Health check | No |
|
||||
| GET | `/` | Root info | No |
|
||||
|
||||
---
|
||||
|
||||
## 5. Frontend Build Verification
|
||||
|
||||
**Location:** `frontend/.next/`
|
||||
|
||||
**Build artifacts confirmed:**
|
||||
- `BUILD_ID` — present (20 bytes)
|
||||
- `build-manifest.json` — present
|
||||
- `app-build-manifest.json` — present
|
||||
- `routes-manifest.json` — present
|
||||
- `server/` — directory present
|
||||
- `static/` — directory present
|
||||
- `prerender-manifest.json` — present
|
||||
- `required-server-files.json` — present
|
||||
- `trace/` — build trace present
|
||||
|
||||
**Verdict:** ✅ Frontend build artifacts are complete and present.
|
||||
|
||||
---
|
||||
|
||||
## 6. Environment Verification
|
||||
|
||||
### Required Environment Variables (from `.env.example`)
|
||||
|
||||
| Variable | Required | Status | Notes |
|
||||
|----------|----------|--------|-------|
|
||||
| `DATABASE_URL` | ✅ Yes | Set (local PostgreSQL) | `postgresql+asyncpg://erp_test_user:***@localhost:5432/erp_test` |
|
||||
| `REDIS_URL` | ✅ Yes | Set in .env.example | Redis NOT installed; JWT refresh tokens are stateless (no Redis storage needed) |
|
||||
| `JWT_SECRET` | ✅ Yes | Placeholder in .env.example | Must be set to a secure random string for production |
|
||||
| `JWT_ALGORITHM` | Optional | Default HS256 | — |
|
||||
| `JWT_ACCESS_TTL_MINUTES` | Optional | Default 15 | — |
|
||||
| `JWT_REFRESH_TTL_DAYS` | Optional | Default 7 | — |
|
||||
| `CORS_ORIGINS` | ✅ Yes | Set | `http://localhost:3000,http://localhost:3001` |
|
||||
| `UPLOAD_DIR` | Optional | Default `/tmp/uploads` | Must be persistent volume in production |
|
||||
| `APP_NAME` | Optional | Default `ERP Nutzfahrzeuge` | — |
|
||||
| `APP_ENV` | Optional | Default `development` | Set to `production` for deployment |
|
||||
| `MOBILE_DE_API_KEY` | Optional | Empty | Required for mobile.de integration |
|
||||
| `MOBILE_DE_SELLER_ID` | Optional | Empty | Required for mobile.de integration |
|
||||
| `OPENROUTER_API_KEY` | Optional | Not in .env.example | Required for AI Copilot features (currently empty/non-functional) |
|
||||
|
||||
### Infrastructure Status
|
||||
|
||||
| Service | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| PostgreSQL | ✅ Running | localhost:5432, accepting connections |
|
||||
| Redis | ⚠️ Not installed | Not required for JWT; may be needed for async task queues (OCR, retouch, mobile.de push) |
|
||||
| OpenRouter API | ⚠️ Not configured | AI Copilot features will not work without API key |
|
||||
|
||||
---
|
||||
|
||||
## 7. Errors Found
|
||||
|
||||
**No errors, tracebacks, warnings, or critical messages found in runtime logs.**
|
||||
|
||||
Log output was clean:
|
||||
```
|
||||
INFO: Started server process [3383733]
|
||||
INFO: Waiting for application startup.
|
||||
INFO: Application startup complete.
|
||||
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
|
||||
INFO: 127.0.0.1:48688 - "GET /api/v1/health HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48700 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48706 - "GET /api/v1/auth/login HTTP/1.1" 405 Method Not Allowed
|
||||
INFO: 127.0.0.1:48710 - "GET /openapi.json HTTP/1.1" 200 OK
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Known Risks
|
||||
|
||||
| # | Risk | Severity | Mitigation |
|
||||
|---|------|----------|------------|
|
||||
| 1 | **Redis not installed** — async task processing (OCR, retouch, mobile.de push) may fail or fall back to synchronous processing | Medium | Install Redis or verify async tasks have graceful fallback. Check if background tasks use in-memory queues. |
|
||||
| 2 | **OPENROUTER_API_KEY not set** — AI Copilot (`/api/v1/copilot/*`) will not function | Medium | Set `OPENROUTER_API_KEY` in production environment for Copilot features. |
|
||||
| 3 | **JWT_SECRET is placeholder** — `change-me-to-a-secure-random-string` in .env.example | High | Generate a cryptographically secure secret (≥256 bits) for production. |
|
||||
| 4 | **PostgreSQL is local only** — no connection pooling or HA configured | Medium | Configure PgBouncer or use managed PostgreSQL (e.g., Coolify managed DB) for production. |
|
||||
| 5 | **UPLOAD_DIR = `/tmp/uploads`** — ephemeral storage, files lost on container restart | High | Mount a persistent volume for `UPLOAD_DIR` in production. |
|
||||
| 6 | **CORS_ORIGINS** — currently allows localhost:3000,3001 only | Low | Add production frontend origin to `CORS_ORIGINS` for deployment. |
|
||||
| 7 | **MOBILE_DE_API_KEY empty** — mobile.de push/status endpoints will fail | Low | Set API key when mobile.de integration is needed. |
|
||||
| 8 | **No HTTPS/TLS** — backend runs on plain HTTP | Medium | Use reverse proxy (nginx/Traefik) or Coolify proxy for TLS termination. |
|
||||
| 9 | **No rate limiting** — no rate limiting middleware detected | Medium | Add rate limiting (e.g., slowapi) before public exposure. |
|
||||
|
||||
---
|
||||
|
||||
## 9. Recommendations
|
||||
|
||||
### Immediate (before deployment)
|
||||
1. Generate a secure `JWT_SECRET` and configure in production environment
|
||||
2. Set `APP_ENV=production` for deployment
|
||||
3. Configure persistent volume for `UPLOAD_DIR`
|
||||
4. Add production frontend origin to `CORS_ORIGINS`
|
||||
5. Install Redis if async task processing (OCR, retouch, mobile.de) is required
|
||||
6. Set `OPENROUTER_API_KEY` if AI Copilot features are needed
|
||||
|
||||
### Deployment preparation
|
||||
7. Create Dockerfile / docker-compose.yml for containerized deployment
|
||||
8. Configure reverse proxy with TLS termination (Coolify proxy or nginx)
|
||||
9. Set up PostgreSQL as managed service or with backup strategy
|
||||
10. Add health check to Coolify resource configuration (`GET /api/v1/health`)
|
||||
11. Configure log rotation and monitoring
|
||||
|
||||
### Post-deployment
|
||||
12. Run smoke tests against production endpoints
|
||||
13. Verify JWT auth flow end-to-end
|
||||
14. Test file upload with persistent volume
|
||||
15. Monitor for async task failures (OCR, retouch)
|
||||
|
||||
---
|
||||
|
||||
## 10. Sign-off
|
||||
|
||||
| Item | Status |
|
||||
|------|--------|
|
||||
| App starts without errors | ✅ Yes |
|
||||
| Health endpoint returns 200 | ✅ Yes |
|
||||
| Bounded error output shows no critical runtime failure | ✅ Yes (zero errors) |
|
||||
| Frontend build artifacts present | ✅ Yes |
|
||||
| Deployment docs ready | ✅ Yes (see `deploy/` directory) |
|
||||
| Ready for deployment | ⚠️ Conditional — requires secure JWT_SECRET, persistent storage, and Redis decision |
|
||||
|
||||
**Runtime verification: PASSED**
|
||||
**Deployment readiness: CONDITIONAL** (env vars + infrastructure must be configured)
|
||||
Reference in New Issue
Block a user