diff --git a/app/api/files.py b/app/api/files.py index ee5d2998..2277e2e9 100644 --- a/app/api/files.py +++ b/app/api/files.py @@ -1590,15 +1590,11 @@ def assign_pipeline_to_file( HTTPException 404: If the file or pipeline does not exist / is not accessible to the current user. """ - from app.auth import get_current_user + from app.auth import get_current_user, get_current_user_id from app.models import Pipeline user = get_current_user(request) - # Derive user identity the same way the pipelines API does (_get_user_id) - if user: - user_id: str = user.get("preferred_username") or user.get("email") or user.get("id") or "anonymous" - else: - user_id = "anonymous" + user_id: str = get_current_user_id(request) is_admin_user = bool(user and user.get("is_admin")) diff --git a/app/api/pipelines.py b/app/api/pipelines.py index 13182a80..d1ee6c23 100644 --- a/app/api/pipelines.py +++ b/app/api/pipelines.py @@ -18,7 +18,7 @@ from fastapi import APIRouter, Body, Depends, HTTPException, Request, status from pydantic import BaseModel, Field from sqlalchemy.orm import Session -from app.auth import get_current_user, require_login +from app.auth import get_current_user, get_current_user_id, require_login from app.database import get_db from app.models import Pipeline, PipelineStep @@ -90,11 +90,12 @@ MAX_NAME_LENGTH = 255 def _get_user_id(request: Request) -> str: - """Return a stable user identifier from the session.""" - user = get_current_user(request) - if user: - return user.get("preferred_username") or user.get("email") or user.get("id", "anonymous") - return "anonymous" + """Return a stable user identifier from the session. + + Delegates to :func:`app.auth.get_current_user_id` so the same fallback + logic ("anonymous") is used consistently throughout the application. + """ + return get_current_user_id(request) def _is_admin(request: Request) -> bool: @@ -591,7 +592,7 @@ def add_step(pipeline_id: int, request: Request, db: DbSession, body: PipelineSt return _serialize_step(step) -@router.put("/{pipeline_id}/steps/reorder", tags=["pipelines"]) +@router.put("/{pipeline_id}/steps/reorder") @require_login def reorder_steps( pipeline_id: int, diff --git a/app/auth.py b/app/auth.py index 3f8a43fa..c9c7099c 100644 --- a/app/auth.py +++ b/app/auth.py @@ -43,6 +43,27 @@ def get_current_user(request: Request): return request.session.get("user") +def get_current_user_id(request: Request) -> str: + """Return a stable string identifier for the authenticated user. + + Falls back to ``"anonymous"`` when no user is in the session (e.g. when + AUTH_ENABLED=False in single-user mode). The returned value is consistent + with how pipeline and file ownership is stored in the database. + + Priority order: ``preferred_username`` → ``email`` → ``id`` → ``"anonymous"``. + + Args: + request: The current FastAPI request. + + Returns: + A non-empty string identifying the current user. + """ + user = get_current_user(request) + if not user or not isinstance(user, dict): + return "anonymous" + return user.get("preferred_username") or user.get("email") or user.get("id") or "anonymous" + + def require_login(func): if not AUTH_ENABLED: return func # no-op diff --git a/docs/API.md b/docs/API.md index e736c260..2d016ddc 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1441,6 +1441,176 @@ Execute a full data migration from source to target database. ``` +## Pipelines + +The pipeline API lets you build and manage custom document processing workflows. Each pipeline is owned by a single user (or by the system when `owner_id` is `null`). + +### Step-types catalogue + +```bash +GET /api/pipelines/step-types +``` + +Returns the catalogue of built-in step types. + +**Response (200):** +```json +{ + "convert_to_pdf": { + "label": "Convert to PDF", + "description": "Convert non-PDF documents to PDF format using Gotenberg.", + "config_schema": {} + }, + "ocr": { + "label": "OCR Processing", + "description": "Extract text using Azure Document Intelligence or local Tesseract.", + "config_schema": { + "force_cloud_ocr": { "type": "boolean", "default": false } + } + } +} +``` + +### List pipelines + +```bash +GET /api/pipelines +``` + +Returns pipelines visible to the current user (own + system pipelines). Admins see all pipelines. + +### Create pipeline + +```bash +POST /api/pipelines +Content-Type: application/json + +{ + "name": "My Workflow", + "description": "Converts, OCRs, and stores documents.", + "is_default": false, + "is_active": true +} +``` + +**Response (201):** +```json +{ + "id": 1, + "owner_id": "alice", + "name": "My Workflow", + "description": "Converts, OCRs, and stores documents.", + "is_default": false, + "is_active": true, + "created_at": "2026-03-07T10:00:00+00:00", + "updated_at": "2026-03-07T10:00:00+00:00" +} +``` + +### Create system pipeline (admin only) + +```bash +POST /api/pipelines/admin/system +Content-Type: application/json + +{ + "name": "Global Default", + "is_default": true +} +``` + +### Get pipeline with steps + +```bash +GET /api/pipelines/{pipeline_id} +``` + +**Response (200):** +```json +{ + "id": 1, + "owner_id": "alice", + "name": "My Workflow", + "steps": [ + { "id": 1, "position": 0, "step_type": "convert_to_pdf", "enabled": true, "config": {} }, + { "id": 2, "position": 1, "step_type": "ocr", "enabled": true, "config": { "force_cloud_ocr": false } } + ] +} +``` + +### Update pipeline + +```bash +PUT /api/pipelines/{pipeline_id} +Content-Type: application/json + +{ "name": "Renamed Workflow", "is_default": true } +``` + +### Delete pipeline + +```bash +DELETE /api/pipelines/{pipeline_id} +``` + +Returns **204 No Content**. + +### Add step + +```bash +POST /api/pipelines/{pipeline_id}/steps +Content-Type: application/json + +{ + "step_type": "ocr", + "label": "Cloud OCR", + "config": { "force_cloud_ocr": true }, + "enabled": true +} +``` + +### Update step + +```bash +PUT /api/pipelines/{pipeline_id}/steps/{step_id} +Content-Type: application/json + +{ "enabled": false } +``` + +### Delete step + +```bash +DELETE /api/pipelines/{pipeline_id}/steps/{step_id} +``` + +Returns **204 No Content**. + +### Reorder steps + +```bash +PUT /api/pipelines/{pipeline_id}/steps/reorder +Content-Type: application/json + +[3, 1, 2] +``` + +Provide a complete ordered list of **all** step IDs. Their positions are reassigned 0, 1, 2, … in the given order. + +### Assign pipeline to a file + +```bash +POST /api/files/{file_id}/assign-pipeline?pipeline_id=2 +``` + +Pass no `pipeline_id` query parameter (or omit it) to clear the assignment. + +**Response (200):** +```json +{ "file_id": 42, "pipeline_id": 2 } +``` + + ## Further Assistance For additional help with the API, please contact our support team or refer to the [Development Guide](../CONTRIBUTING.md). diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 98ff365c..399791ea 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -334,6 +334,55 @@ PAPERLESS_CUSTOM_FIELDS_MAPPING='{"absender": "Sender", "empfaenger": "Recipient 3. After successful upload, custom fields are automatically populated 4. You can view the populated fields in your Paperless-ngx document details +## Processing Pipelines + +Processing pipelines let you define exactly what happens to your documents when they are uploaded. Each pipeline is an ordered sequence of **steps** — for example: convert to PDF → OCR → extract metadata → send to storage. + +### Key concepts + +| Term | Meaning | +|------|---------| +| **Pipeline** | A named, ordered list of processing steps | +| **Step** | A single processing action (e.g., OCR, metadata extraction) | +| **System pipeline** | Created by an admin; visible to all users as a shared default | +| **User pipeline** | Created by a regular user; private to that user | +| **Default pipeline** | Marked `is_default=true`; used automatically for new uploads | + +### Managing your pipelines + +1. Navigate to **Pipelines** in the top navigation bar. +2. Click **New Pipeline** to create a pipeline, give it a name and optional description. +3. Expand the pipeline card and click **Add Step** to build the workflow. +4. Use the ↑ / ↓ arrows to reorder steps, or click the edit icon to change step settings. +5. Mark a pipeline as **Default** so new documents are automatically processed by it. + +### Available step types + +| Step Type | Description | +|-----------|-------------| +| `convert_to_pdf` | Convert non-PDF files to PDF using Gotenberg | +| `check_duplicates` | Detect duplicate files by content hash | +| `ocr` | Extract text with Azure Document Intelligence or local Tesseract | +| `extract_metadata` | Extract structured metadata (type, sender, tags) with AI | +| `embed_metadata` | Write extracted metadata into the PDF document properties | +| `compute_embedding` | Compute semantic embeddings for similarity search | +| `send_to_destinations` | Upload the processed document to all configured storage destinations | +| `classify` | Classify the document type with AI | + +### Assigning a pipeline to a file + +You can assign (or change) the pipeline for an individual document via the file detail page or the API: + +```bash +POST /api/files/{file_id}/assign-pipeline?pipeline_id=3 +``` + +Pass no `pipeline_id` to clear the assignment and fall back to the system default. + +### Admin: system-wide pipelines + +Admins can create **system pipelines** that appear in every user's pipeline list. These can be set as the global default so all users benefit from a consistent processing baseline. Navigate to **Pipelines** and check the **System pipeline** box when creating a new one (admin only). + ## API Access For programmatic access, DocuElevate provides a comprehensive REST API: diff --git a/frontend/templates/pipelines.html b/frontend/templates/pipelines.html index fb36ec47..92d04b07 100644 --- a/frontend/templates/pipelines.html +++ b/frontend/templates/pipelines.html @@ -29,15 +29,21 @@ -