diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index bec2f25..ae8ed9f 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -8,6 +8,9 @@ on: branches: [main, develop] pull_request: branches: [main] + workflow_dispatch: + schedule: + - cron: '17 4 * * *' env: NODE_VERSION: '20' @@ -71,3 +74,87 @@ jobs: docker cp mozdit-app-ci:/app/.next/BUILD_ID ./build-id 2>/dev/null || \ echo "BUILD_ID nem elérhető — build verification skip" docker rm mozdit-app-ci + + docker-integration: + name: 🐳 Docker integration & API E2E + runs-on: ubuntu-latest + needs: test + steps: + - name: 📥 Checkout code + uses: actions/checkout@v4 + + - name: 📦 Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + cache-dependency-path: proto/package-lock.json + + - name: 📥 Install dependencies + working-directory: ./proto + run: npm ci + + - name: 🐳 Start Docker development stack + run: docker compose -f docker-compose.dev.yml up --build -d + + - name: ⏳ Wait for application health + run: | + for attempt in $(seq 1 30); do + if curl --fail --silent http://localhost:8080/api/health > /dev/null; then + exit 0 + fi + sleep 2 + done + docker compose -f docker-compose.dev.yml logs --tail=100 + exit 1 + + - name: 🔗 Run integration tests + working-directory: ./proto + run: npm run test:integration + + - name: 🔄 Run API E2E tests + working-directory: ./proto + run: npm run test:e2e + + - name: 🧹 Stop Docker development stack + if: always() + run: docker compose -f docker-compose.dev.yml down -v + + staging-smoke: + name: 🌐 Staging Playwright smoke + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' + steps: + - name: 📥 Checkout code + uses: actions/checkout@v4 + + - name: 📦 Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + cache-dependency-path: proto/package-lock.json + + - name: 📥 Install dependencies + working-directory: ./proto + run: npm ci + + - name: 🌐 Install Chromium for Playwright + working-directory: ./proto + run: npx playwright install --with-deps chromium + + - name: 🧪 Run staging smoke suite + working-directory: ./proto + env: + BASE_URL: https://stage.mozdit.hu + run: npm run test:smoke:staging + + - name: 📦 Upload Playwright artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: staging-smoke-artifacts + path: | + proto/playwright-report/ + proto/test-results/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index a708661..1430edf 100755 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,8 @@ mcp.json # Plane sync backup fájlok TODO.md.backup.* +.content-backups/ +.content-editor-audit.jsonl +.playwright-mcp/ +proto/playwright-report/ +proto/test-results/ diff --git a/content-editor.js b/content-editor.js index 6afc477..0f06c34 100644 --- a/content-editor.js +++ b/content-editor.js @@ -12,9 +12,17 @@ const http = require('http'); const fs = require('fs'); const path = require('path'); const { exec } = require('child_process'); +const crypto = require('crypto'); +const { validateContent } = require('./proto/src/content/schema'); const PORT = 4001; const CONTENT_DIR = path.join(__dirname, 'proto', 'src', 'content'); +const BACKUP_DIR = path.join(__dirname, '.content-backups'); +const MAX_REQUEST_BODY_BYTES = 256 * 1024; +const AUDIT_LOG_FILE = path.join(__dirname, '.content-editor-audit.jsonl'); +const RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000; +const AUTH_MAX_ATTEMPTS = 5; +const PUBLISH_MAX_ATTEMPTS = 3; const FILES = { common: path.join(CONTENT_DIR, 'common.json'), @@ -36,7 +44,7 @@ const FILE_LABELS = { hasznalatiFeltetelek: '⚖️ ÁSZF', }; -const HTML = (activeFile, jsonData, message) => ` +const HTML = (activeFile, jsonData, message, csrfToken) => ` @@ -145,6 +153,7 @@ ${message ? `
${messag