diff --git a/.dockerignore b/.dockerignore index 782a267..889c9d9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,3 +11,7 @@ venv/ .gitignore node_modules/ uv.lock +test.py +result.xml +README.md +.gitea/ diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 4eac1e4..e3dbb65 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -2,35 +2,33 @@ on: workflow_dispatch: inputs: github_release: - description: 'Create Gitea Release' + description: "Create Gitea Release" default: true type: boolean docker_release: - description: 'Push Docker images' + description: "Push Docker images" default: true type: boolean bump: - description: 'Bump type' + description: "Bump type" required: false - default: 'patch' + default: "patch" type: choice options: - - 'major' - - 'minor' - - 'patch' + - "major" + - "minor" + - "patch" - - jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@master + uses: actions/checkout@v4.2.2 with: - fetch-depth: 0 # Fetch full history - fetch-tags: true # Fetch all tags (refs/tags) + fetch-depth: 0 # Fetch full history + fetch-tags: true # Fetch all tags (refs/tags) - name: Install uv uses: astral-sh/setup-uv@v5 @@ -44,7 +42,7 @@ jobs: uv add pip uv export --format requirements.txt -o requirements.txt # uv run python -m pip install --upgrade pip - # uv run python -m pip install -r requirements.txt + # uv run python -m pip install -r requirements.txt - name: Set Git identity run: | @@ -61,6 +59,8 @@ jobs: echo "EOF" >> $GITHUB_ENV - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -78,31 +78,46 @@ jobs: echo "tag=$prev" >> "$GITHUB_OUTPUT" - - name: Build and store Docker image for multiple architectures + - name: Compute lowercased image repo if: ${{ github.event.inputs.docker_release == 'true' }} - run: | - REPO_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') - docker buildx build \ - --platform linux/amd64,linux/arm64,linux/arm/v7 \ - --tag ${{ secrets.REGISTRY }}/${REPO_NAME}:latest \ - --tag ${{ secrets.REGISTRY }}/${REPO_NAME}:${{ env.VERSION }} \ - --push . + echo "IMAGE_REPO=${{ secrets.REGISTRY }}/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: Docker meta + if: ${{ github.event.inputs.docker_release == 'true' }} + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_REPO }} + tags: | + type=raw,value=latest + type=raw,value=${{ env.VERSION }} + + - name: Build and push Docker image + if: ${{ github.event.inputs.docker_release == 'true' }} + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + - name: Push changes uses: ad-m/github-push-action@master with: - github_token: ${{ secrets.TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} branch: ${{ github.ref }} - name: Build Changelog id: build_changelog uses: https://github.com/mikepenz/release-changelog-builder-action@v5 with: platform: "gitea" - baseURL: "http://gitea:3000" + baseURL: "http://192.168.178.110:3000" configuration: ".gitea/changelog_config.json" env: - GITHUB_TOKEN: ${{ secrets.TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }} - name: Create Gitea Release if: ${{ github.event.inputs.github_release == 'true' }} @@ -115,6 +130,4 @@ jobs: prerelease: false env: GITHUB_TOKEN: ${{ secrets.TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} - - + GITHUB_REPOSITORY: ${{ github.repository }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7207b86..19f8deb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,22 @@ # syntax=docker/dockerfile:1 -FROM python:3.13 +FROM python:3.13-slim ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ - PIP_DISABLE_PIP_VERSION_CHECK=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + # Playwright won't be installed for actual browser automation + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 WORKDIR /app -# Copy dependency list and install first (leverages Docker layer cache) +# Install only runtime dependencies needed for bibapi and requests +# This avoids installing Playwright browsers which are huge COPY requirements.txt ./ -RUN pip install --no-cache-dir -r requirements.txt +RUN pip install --no-cache-dir \ + --extra-index-url https://git.theprivateserver.de/api/packages/PHB/pypi/simple/ \ + -r requirements.txt && \ + # Clean up pip cache + rm -rf /root/.cache/pip # Copy application code COPY app ./app diff --git a/app/main.py b/app/main.py index 2ea2be1..dacc0e2 100755 --- a/app/main.py +++ b/app/main.py @@ -4,8 +4,9 @@ import smtplib from email.mime.text import MIMEText from xml.etree.ElementTree import Element, SubElement, tostring +from bibapi import catalogue from fastapi import FastAPI, Form, HTTPException, Request, status -from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates @@ -15,6 +16,9 @@ templates = Jinja2Templates(directory="app/templates") # Serve static files (CSS, images) app.mount("/static", StaticFiles(directory="app/static"), name="static") +# Initialize catalogue for signature validation +cat = catalogue.Catalogue() + # add somewhere near the top-level constants EMAIL_REGEX = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$") @@ -44,6 +48,39 @@ async def elsa_form(request: Request): return templates.TemplateResponse("elsa_mono_form.html", {"request": request}) +@app.get("/api/validate-signature") +async def validate_signature(signature: str): + """Validate a book signature and return total pages""" + try: + book_result = cat.get_book_with_data(signature) + if book_result and hasattr(book_result, "pages") and book_result.pages: + # Try to extract numeric page count + pages_str = str(book_result.pages) + # Extract first number from pages string (e.g., "245 S." -> 245) + match = re.search(r"(\d+)", pages_str) + if match: + total_pages = int(match.group(1)) + return JSONResponse( + {"valid": True, "total_pages": total_pages, "signature": signature} + ) + + return JSONResponse( + { + "valid": False, + "error": "Signatur nicht gefunden oder keine Seitenzahl verfügbar", + "signature": signature, + } + ) + except Exception as e: + return JSONResponse( + { + "valid": False, + "error": f"Fehler bei der Validierung: {str(e)}", + "signature": signature, + } + ) + + @app.post("/submit") async def handle_form( request: Request, diff --git a/app/static/styles.css b/app/static/styles.css index 33b94b8..bb489ae 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -725,3 +725,52 @@ input[type="radio"] { accent-color: var(--control-accent); } padding: 6px 8px; } } + +/* Signature validation states */ +.signature-validating { + border-color: #3b82f6 !important; + background-color: #eff6ff !important; +} + +[data-theme="dark"] .signature-validating { + background-color: #1e3a8a !important; +} + +.signature-valid { + border-color: #22c55e !important; + background-color: #f0fdf4 !important; +} + +[data-theme="dark"] .signature-valid { + background-color: #14532d !important; +} + +.signature-invalid { + border-color: #ef4444 !important; + background-color: #fef2f2 !important; +} + +[data-theme="dark"] .signature-invalid { + background-color: #7f1d1d !important; +} + +/* Threshold exceeded - entire row in red */ +.threshold-exceeded { + background-color: #fee2e2 !important; +} + +[data-theme="dark"] .threshold-exceeded { + background-color: #991b1b !important; +} + +.threshold-exceeded input, +.threshold-exceeded select { + border-color: #ef4444 !important; +} + +/* Disabled submit button styling */ +button[type="submit"]:disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: #9ca3af !important; +} diff --git a/app/templates/elsa_mono_form.html b/app/templates/elsa_mono_form.html index 1f1a081..7441c93 100644 --- a/app/templates/elsa_mono_form.html +++ b/app/templates/elsa_mono_form.html @@ -136,6 +136,12 @@ }; let sectionCounter = 0; + + // Track signature validations: { signature: { totalPages: int, requestedPages: int, inputs: [elements] } } + let signatureTracking = {}; + + // Debounce timer for signature validation + let validationTimers = {}; // Theme toggle functionality (in IIFE to avoid polluting global scope) (function() { @@ -266,6 +272,7 @@ '