From 46b2f17acc4553b9b22856e254e6d50cdfc047f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 20:33:10 +0000 Subject: [PATCH] feat(docs): add built-in help section with How-To guides embedded in app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add MkDocs Material docs build stage to Dockerfile and Dockerfile.local - Mount pre-built docs as static files at /help/ in FastAPI (app/main.py) - Add app/views/help.py with /help → /help/ permanent redirect route - Register help router in app/views/__init__.py - Add Help nav link to base.html (public + app nav, desktop + mobile) - Create how-to guides: HP printer, ScanSnap, watched folder, email ingestion, mobile scanning - Update mkdocs.yml with How-To Guides section and Material theme palette - Add optional docs service (squidfunk/mkdocs-material) to docker-compose.yaml with docs profile - Add mkdocs-material to requirements-dev.txt - Add /docs_build to .gitignore - Add tests for help view (8 tests, 100% coverage on help.py) Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- .gitignore | 1 + Dockerfile | 19 +++ Dockerfile.local | 16 +++ app/main.py | 9 ++ app/views/__init__.py | 2 + app/views/help.py | 27 ++++ docker-compose.yaml | 19 +++ docs/HowToGuides.md | 80 ++++++++++++ docs/howto/EmailIngestion.md | 154 ++++++++++++++++++++++ docs/howto/HPPrinterSetup.md | 198 +++++++++++++++++++++++++++++ docs/howto/MobileScanning.md | 152 ++++++++++++++++++++++ docs/howto/SnapScanSetup.md | 192 ++++++++++++++++++++++++++++ docs/howto/WatchedFolderSetup.md | 212 +++++++++++++++++++++++++++++++ frontend/templates/base.html | 26 ++++ mkdocs.yml | 23 ++++ requirements-dev.txt | 3 + tests/test_views_help.py | 92 ++++++++++++++ 17 files changed, 1225 insertions(+) create mode 100644 app/views/help.py create mode 100644 docs/HowToGuides.md create mode 100644 docs/howto/EmailIngestion.md create mode 100644 docs/howto/HPPrinterSetup.md create mode 100644 docs/howto/MobileScanning.md create mode 100644 docs/howto/SnapScanSetup.md create mode 100644 docs/howto/WatchedFolderSetup.md create mode 100644 tests/test_views_help.py diff --git a/.gitignore b/.gitignore index 7ac7f292..7902578c 100644 --- a/.gitignore +++ b/.gitignore @@ -171,6 +171,7 @@ venv.bak/ # mkdocs documentation /site +/docs_build # mypy .mypy_cache/ diff --git a/Dockerfile b/Dockerfile index e7d33227..2485fa56 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,22 @@ WORKDIR /app COPY requirements.txt /app/ RUN pip install --no-cache-dir -r requirements.txt +# ── Documentation build stage ─────────────────────────────────────────────── +FROM python:3.14.1-slim AS docs-builder + +WORKDIR /docs + +# Install MkDocs Material and its dependencies +COPY docs/requirements.txt /docs/requirements.txt +RUN pip install --no-cache-dir -r requirements.txt + +# Copy documentation sources +COPY docs /docs/docs +COPY mkdocs.yml /docs/mkdocs.yml + +# Build the static documentation site +RUN mkdocs build --config-file /docs/mkdocs.yml --site-dir /docs/docs_build + # Second stage for the actual runtime FROM python:3.14.3-slim @@ -43,6 +59,9 @@ COPY ./BUILD_DATE /app/BUILD_DATE COPY ./GIT_SHA /app/GIT_SHA COPY ./RUNTIME_INFO /app/RUNTIME_INFO +# Copy the pre-built MkDocs documentation site (served at /help) +COPY --from=docs-builder /docs/docs_build /app/docs_build + # Create runtime_info directory RUN mkdir -p /app/runtime_info diff --git a/Dockerfile.local b/Dockerfile.local index 4ae482d0..138d726c 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -6,6 +6,19 @@ WORKDIR /app COPY requirements.txt /app/ RUN pip install --no-cache-dir -r requirements.txt +# ── Documentation build stage ─────────────────────────────────────────────── +FROM python:3.14.1-slim AS docs-builder + +WORKDIR /docs + +COPY docs/requirements.txt /docs/requirements.txt +RUN pip install --no-cache-dir -r requirements.txt + +COPY docs /docs/docs +COPY mkdocs.yml /docs/mkdocs.yml + +RUN mkdocs build --config-file /docs/mkdocs.yml --site-dir /docs/docs_build + FROM python:3.14.1-slim WORKDIR /app @@ -33,6 +46,9 @@ COPY ./LICENSE /app/LICENSE COPY ./VERSION /app/VERSION COPY ./BUILD_DATE /app/BUILD_DATE +# Copy the pre-built MkDocs documentation site (served at /help) +COPY --from=docs-builder /docs/docs_build /app/docs_build + # Local fallbacks for build metadata RUN echo "local" > /app/GIT_SHA \ && echo "local" > /app/RUNTIME_INFO diff --git a/app/main.py b/app/main.py index 7aba642b..c03cdd1f 100644 --- a/app/main.py +++ b/app/main.py @@ -202,6 +202,15 @@ if os.path.exists(static_dir): else: print(f"WARNING: Static directory not found at {static_dir}. Static files will not be served.") +# Mount the built MkDocs documentation site at /help/ +# The docs are pre-built into docs_build/ during the Docker image build. +# When running locally, run `mkdocs build` from the repo root first. +docs_build_dir = pathlib.Path(__file__).parents[1] / "docs_build" +if os.path.exists(docs_build_dir): + app.mount("/help", StaticFiles(directory=str(docs_build_dir), html=True), name="help_docs") +else: + print(f"INFO: Help docs not found at {docs_build_dir}. Run 'mkdocs build' to generate them.") + # Custom exception handlers that return JSON for API routes and HTML for frontend routes @app.exception_handler(HTTPException) diff --git a/app/views/__init__.py b/app/views/__init__.py index 7b27bab1..6b1670d7 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -12,6 +12,7 @@ from app.views.filemanager import router as filemanager_router # Import all the view routers from app.views.general import router as general_router from app.views.google_drive import router as google_drive_router +from app.views.help import router as help_router # Built-in help / How-To docs from app.views.license_routes import router as license_router # Add the license router from app.views.onboarding import router as onboarding_router from app.views.onedrive import router as onedrive_router @@ -43,3 +44,4 @@ router.include_router(subscriptions_router) # Pricing + subscription pages router.include_router(plans_router) # Admin Plan Designer router.include_router(onboarding_router) # User onboarding wizard router.include_router(pipelines_router) # Processing pipelines +router.include_router(help_router) # Built-in help / How-To docs diff --git a/app/views/help.py b/app/views/help.py new file mode 100644 index 00000000..1df1c205 --- /dev/null +++ b/app/views/help.py @@ -0,0 +1,27 @@ +""" +Help documentation view routes. + +Serves the built MkDocs documentation site at /help. +The static site is built during the Docker image build and placed at docs_build/. +""" + +import logging +import pathlib + +from fastapi import Request +from fastapi.responses import RedirectResponse + +from app.views.base import APIRouter + +logger = logging.getLogger(__name__) + +router = APIRouter() + +# Path to the built MkDocs documentation +_DOCS_BUILD_DIR = pathlib.Path(__file__).parents[2] / "docs_build" + + +@router.get("/help", include_in_schema=False) +async def help_redirect(request: Request) -> RedirectResponse: + """Redirect /help to /help/ so the MkDocs index is served correctly.""" + return RedirectResponse(url="/help/", status_code=301) diff --git a/docker-compose.yaml b/docker-compose.yaml index 06e0f1c8..05e1584c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -72,3 +72,22 @@ services: image: redis:alpine container_name: document_redis restart: always + + # Optional: standalone docs server using MkDocs Material (live-reload during development). + # Start with: docker compose --profile docs up docs + # Access at: http://localhost:8001 + # Note: The /help route in the main app serves pre-built docs from the Docker image. + # This service is for content authors who want live preview while editing documentation. + docs: + image: squidfunk/mkdocs-material:latest + container_name: docuelevate_docs + restart: "no" + profiles: + - docs + volumes: + - ./docs:/docs/docs + - ./mkdocs.yml:/docs/mkdocs.yml + working_dir: /docs + command: ["serve", "--dev-addr=0.0.0.0:8000", "--config-file=/docs/mkdocs.yml"] + ports: + - "8001:8000" diff --git a/docs/HowToGuides.md b/docs/HowToGuides.md new file mode 100644 index 00000000..1ece48ea --- /dev/null +++ b/docs/HowToGuides.md @@ -0,0 +1,80 @@ +# How-To Guides + +Welcome to the DocuElevate How-To Guides. These step-by-step articles help you connect common scanners, printers, and mobile devices to DocuElevate for automatic document ingestion and processing. + +--- + +## Document Ingestion Guides + +Choose the method that matches your hardware or workflow: + +### 🖨️ Scanners & Printers + +| Guide | Description | +|-------|-------------| +| [HP Enterprise Printer Setup](howto/HPPrinterSetup.md) | Configure HP MFPs for automatic scan-to-DocuElevate via email, network folder, or WebDAV | +| [Fujitsu ScanSnap Setup](howto/SnapScanSetup.md) | Set up ScanSnap iX1600/iX1500/iX500 to send scans directly to DocuElevate | +| [Watched Folder Setup](howto/WatchedFolderSetup.md) | Monitor a local or network folder and automatically ingest any files dropped into it | + +### 📧 Email & Cloud + +| Guide | Description | +|-------|-------------| +| [Email Ingestion](howto/EmailIngestion.md) | Route documents sent as email attachments into DocuElevate automatically | +| [Mobile Scanning](howto/MobileScanning.md) | Use iOS/Android apps to capture and upload documents from your phone | + +--- + +## Quick Comparison: Which Method Is Right for You? + +| Method | Best For | Setup Complexity | +|--------|----------|-----------------| +| **Web Upload** | Occasional one-off uploads | ⭐ Very Easy | +| **Watched Folder** | Any scanner that saves to a folder | ⭐⭐ Easy | +| **Email Ingestion** | Scanners with Scan-to-Email, fax services | ⭐⭐ Easy | +| **HP Printer (SMB)** | HP Enterprise MFPs on a corporate network | ⭐⭐⭐ Medium | +| **ScanSnap + Cloud** | Home/office ScanSnap via Dropbox/Drive | ⭐⭐ Easy | +| **Mobile App** | On-the-go document capture | ⭐ Very Easy | +| **API Integration** | Custom workflows, developer integrations | ⭐⭐⭐⭐ Advanced | + +--- + +## General Configuration Tips + +### Enable OCR for Searchable PDFs + +DocuElevate applies OCR (Optical Character Recognition) to scanned documents, making them fully searchable. Configure the OCR engine in your `.env`: + +```env +OCR_ENABLED=true +OCR_ENGINE=tesseract # or: azure, google +OCR_LANGUAGE=eng # ISO 639-2 language code +``` + +### Set Up Automatic Cloud Backup + +After processing, DocuElevate can store documents in your preferred cloud storage. Configure in Settings or `.env`: + +```env +DEFAULT_STORAGE_TARGET=dropbox # or: gdrive, onedrive, s3, nextcloud +``` + +### Configure Notifications + +Get notified when documents are processed: + +```env +NOTIFICATION_ENABLED=true +NOTIFICATION_EMAIL=you@example.com +``` + +See [Notifications Setup](NotificationsSetup.md) for webhook, Slack, and other integrations. + +--- + +## Need Help? + +- Browse the full [Documentation](UserGuide.md) +- Check [Troubleshooting](Troubleshooting.md) for common issues +- View [Configuration Reference](ConfigurationGuide.md) for all settings +- Explore the [API Reference](API.md) for programmatic access diff --git a/docs/howto/EmailIngestion.md b/docs/howto/EmailIngestion.md new file mode 100644 index 00000000..97ed52c2 --- /dev/null +++ b/docs/howto/EmailIngestion.md @@ -0,0 +1,154 @@ +# How to Set Up Automatic Document Ingestion via Email + +Many devices (scanners, printers, fax services, and apps) can send documents as email attachments. This guide explains how to automatically route those attachments into DocuElevate. + +--- + +## Overview + +The Email Ingestion workflow works like this: + +``` +Scanner/Device → Email (SMTP) → Monitored Mailbox → DocuElevate API → Processing & Storage +``` + +DocuElevate periodically checks a designated email inbox, downloads PDF/image attachments, and processes them through the standard document pipeline. + +--- + +## Prerequisites + +- An email account dedicated to document ingestion (e.g., `scan@yourdomain.com`) +- IMAP access enabled for that account +- DocuElevate running with the Celery worker active + +--- + +## Configuration + +Add the following to your DocuElevate `.env` file: + +```env +# Email ingestion settings +EMAIL_INGESTION_ENABLED=true +EMAIL_INGESTION_IMAP_HOST=mail.yourdomain.com +EMAIL_INGESTION_IMAP_PORT=993 +EMAIL_INGESTION_IMAP_SSL=true +EMAIL_INGESTION_USERNAME=scan@yourdomain.com +EMAIL_INGESTION_PASSWORD=your-email-password +EMAIL_INGESTION_FOLDER=INBOX +EMAIL_INGESTION_INTERVAL=60 # Check every 60 seconds +EMAIL_INGESTION_MARK_SEEN=true # Mark emails as read after processing +EMAIL_INGESTION_ALLOWED_SENDERS= # Comma-separated allowlist (empty = allow all) +``` + +Restart DocuElevate after saving the configuration: + +```bash +docker compose restart api worker +``` + +--- + +## Supported File Types + +DocuElevate will process the following attachment types from emails: + +| Type | Extension | Notes | +|------|-----------|-------| +| PDF | `.pdf` | Native support; OCR applied if not searchable | +| JPEG/PNG | `.jpg`, `.jpeg`, `.png` | Converted to PDF before processing | +| TIFF | `.tif`, `.tiff` | Common format from older scanners/fax | +| Multi-page TIFF | `.tif` | Full multi-page support | + +--- + +## Setting Up Your Scanner/Device + +### HP Printers – Scan to Email + +See the detailed guide: [HP Enterprise Printer Setup](./HPPrinterSetup.md#option-a-scan-to-email--docuelevate-api-upload) + +### Fujitsu ScanSnap – Send by Email + +See the detailed guide: [ScanSnap Setup](./SnapScanSetup.md#method-3-scan-to-email--docuelevate) + +### iOS/Android Scanning Apps + +Most mobile scanning apps (Adobe Scan, Microsoft Lens, SwiftScan) can email scans: + +1. Scan your document. +2. Use the app's **Share** or **Send** function. +3. Select **Email** and enter `scan@yourdomain.com`. +4. DocuElevate will pick up the attachment within the configured interval. + +### Fax-to-Email Services + +Services like eFax, RingCentral Fax, or Twilio Fax can forward incoming faxes as email attachments. Configure them to send to `scan@yourdomain.com`. + +--- + +## Security Considerations + +> **Important:** Only process emails from trusted sources to avoid ingesting malicious documents. + +Use the `EMAIL_INGESTION_ALLOWED_SENDERS` setting to restrict which email addresses can submit documents: + +```env +EMAIL_INGESTION_ALLOWED_SENDERS=scanner@office.com,printer@office.com,fax@office.com +``` + +DocuElevate will silently skip emails from addresses not in the allowlist. + +Additionally: + +- Use a **dedicated email account** solely for document ingestion +- Enable **app-specific passwords** (Gmail, Outlook) instead of your main account password +- Store credentials in environment variables, never in config files committed to version control + +--- + +## Monitoring + +Check the DocuElevate worker logs to verify email ingestion is running: + +```bash +docker logs document_worker --follow +``` + +You should see log entries like: + +``` +INFO Email ingestion: checking inbox scan@yourdomain.com +INFO Email ingestion: found 3 new messages +INFO Email ingestion: processing attachment invoice-2024.pdf from printer@office.com +INFO Email ingestion: queued document ID 142 for processing +``` + +--- + +## Troubleshooting + +**No emails are being processed?** +→ Verify IMAP credentials and that IMAP is enabled on your mail server. +→ Check firewall rules: port 993 (SSL) or 143 (plain) must be open from DocuElevate to the mail server. + +**Gmail not working?** +→ Enable "App Passwords" in your Google Account security settings. +→ Use the App Password (not your main Google password) for `EMAIL_INGESTION_PASSWORD`. + +**Attachments processed but files are empty?** +→ Some email clients send inline images instead of attachments. Check the raw email source. + +**Emails keep getting re-processed?** +→ Set `EMAIL_INGESTION_MARK_SEEN=true` to mark emails as read after processing. +→ Alternatively, configure a separate ingestion folder and move/delete emails after processing. + +--- + +## Related Documentation + +- [HP Enterprise Printer Setup](./HPPrinterSetup.md) +- [ScanSnap Setup](./SnapScanSetup.md) +- [Configuration Guide](../ConfigurationGuide.md) +- [Notifications Setup](../NotificationsSetup.md) diff --git a/docs/howto/HPPrinterSetup.md b/docs/howto/HPPrinterSetup.md new file mode 100644 index 00000000..30fcadba --- /dev/null +++ b/docs/howto/HPPrinterSetup.md @@ -0,0 +1,198 @@ +# How to Set Up Automatic Document Ingestion with an HP Enterprise Printer + +This guide explains how to configure an HP Enterprise printer/MFP (Multi-Function Printer) to automatically send scanned documents to DocuElevate for processing and storage. + +--- + +## Prerequisites + +- An HP Enterprise printer or MFP with **Scan to Email** or **Scan to Network Folder** capability +- DocuElevate running and accessible on your network +- Admin access to the HP printer's Embedded Web Server (EWS) +- (Optional) An SMTP server or a configured email address for Scan to Email + +--- + +## Option A: Scan to Email → DocuElevate API Upload + +HP Enterprise printers can send scanned documents as email attachments. You can set up a dedicated inbox that forwards documents to DocuElevate via the REST API. + +### Step 1: Configure Scan to Email on the Printer + +1. Open a browser and navigate to the printer's IP address (e.g., `http://192.168.1.100`) to access the **Embedded Web Server (EWS)**. +2. Go to **Scan** → **Scan to E-mail**. +3. Enable **Scan to E-mail** and configure your SMTP server settings. +4. Create a **Quick Set** (shortcut) for the destination: + - **From:** `scanner@yourdomain.com` + - **To:** `docuelevate-inbox@yourdomain.com` (the receiving address you'll configure) + - **File Type:** PDF + - **Resolution:** 200–300 DPI (recommended) + - **Color Mode:** Grayscale or Black & White for text documents + +### Step 2: Set Up an Email-to-DocuElevate Bridge + +Use a lightweight tool like [imapfilter](https://github.com/lefcha/imapfilter) or a simple Python script (see below) to poll the inbox and upload attachments to DocuElevate via its REST API. + +**Example Python script (`email_to_docuelevate.py`):** + +```python +import imaplib +import email +import requests +import os + +IMAP_HOST = "mail.yourdomain.com" +IMAP_USER = "docuelevate-inbox@yourdomain.com" +IMAP_PASS = os.environ["IMAP_PASS"] +DOCUELEVATE_URL = "http://your-docuelevate-host:8000" +API_KEY = os.environ["DOCUELEVATE_API_KEY"] + +def fetch_and_upload(): + mail = imaplib.IMAP4_SSL(IMAP_HOST) + mail.login(IMAP_USER, IMAP_PASS) + mail.select("INBOX") + + _, msg_ids = mail.search(None, "UNSEEN") + for msg_id in msg_ids[0].split(): + _, msg_data = mail.fetch(msg_id, "(RFC822)") + msg = email.message_from_bytes(msg_data[0][1]) + + for part in msg.walk(): + if part.get_content_maintype() == "multipart": + continue + if part.get("Content-Disposition") is None: + continue + filename = part.get_filename() + if filename and filename.lower().endswith(".pdf"): + payload = part.get_payload(decode=True) + files = {"file": (filename, payload, "application/pdf")} + headers = {"Authorization": f"Bearer {API_KEY}"} + resp = requests.post( + f"{DOCUELEVATE_URL}/api/upload", + files=files, + headers=headers + ) + print(f"Uploaded {filename}: {resp.status_code}") + mail.store(msg_id, "+FLAGS", "\\Seen") + + mail.logout() + +if __name__ == "__main__": + fetch_and_upload() +``` + +Run this script via a cron job every few minutes: + +```bash +*/5 * * * * /usr/bin/python3 /opt/email_to_docuelevate.py >> /var/log/docuelevate_import.log 2>&1 +``` + +--- + +## Option B: Scan to Network Folder (SMB/CIFS) + +HP Enterprise printers can scan directly to a network folder. You can configure a watched folder that DocuElevate monitors for new files. + +### Step 1: Set Up a Shared Network Folder + +On your DocuElevate server (or any reachable server), create a shared folder: + +```bash +# Create the shared folder +mkdir -p /srv/scanner-inbox + +# Install Samba +sudo apt-get install samba + +# Add to /etc/samba/smb.conf: +[scanner-inbox] + path = /srv/scanner-inbox + writable = yes + guest ok = yes + force user = nobody +``` + +Restart Samba: `sudo systemctl restart smbd` + +### Step 2: Configure the HP Printer for Scan to Network Folder + +1. Open the printer's **Embedded Web Server (EWS)**. +2. Go to **Scan** → **Scan to Network Folder**. +3. Click **Add** to create a new Quick Set: + - **UNC Path:** `\\192.168.1.200\scanner-inbox` (replace with your server's IP) + - **Username/Password:** Leave blank for guest, or provide credentials + - **File Type:** PDF (Searchable PDF if available) + - **Resolution:** 200–300 DPI +4. Test the connection from the EWS interface. + +### Step 3: Configure DocuElevate to Watch the Folder + +In your DocuElevate `.env` configuration: + +```env +# Enable folder watching +WATCH_FOLDER_ENABLED=true +WATCH_FOLDER_PATH=/srv/scanner-inbox +WATCH_FOLDER_INTERVAL=30 # seconds between checks +``` + +DocuElevate's Celery worker will automatically detect and process new files placed in the watched folder. + +--- + +## Option C: Scan to FTP/WebDAV + +DocuElevate supports FTP and WebDAV as upload targets. HP printers can send scanned documents directly. + +### WebDAV Configuration + +1. In the EWS, go to **Scan** → **Save to SharePoint** or **Save to Network Folder**. +2. Some HP models support WebDAV directly — configure the WebDAV URL to point to DocuElevate's WebDAV endpoint (if enabled): + - **URL:** `http://your-docuelevate-host:8000/webdav/inbox/` + - **Username/Password:** Your DocuElevate credentials + +### FTP Configuration + +1. Ensure an FTP server is running alongside DocuElevate (or configure one in docker-compose). +2. In the EWS, configure **Scan to FTP**: + - **FTP Server:** `192.168.1.200` + - **Port:** `21` + - **Remote Path:** `/scanner-inbox/` +3. DocuElevate's watch folder will pick up the FTP-delivered files. + +--- + +## Recommended Scanner Quick Set Settings + +| Setting | Recommended Value | +|---------|------------------| +| File Type | PDF (Searchable PDF / PDF/A if available) | +| Resolution | 200–300 DPI | +| Color Mode | Auto Detect or Grayscale | +| Sides | Auto Detect (2-sided) | +| Original Size | Auto Detect | +| Orientation | Auto Detect | + +--- + +## Troubleshooting + +**Printer can't connect to the network folder?** +→ Verify the IP address and that the Samba/SMB service is running. Check firewall rules (port 445/TCP). + +**Scanned PDFs aren't being processed?** +→ Check DocuElevate's Celery worker logs: `docker logs document_worker --follow` + +**Email attachments not arriving?** +→ Verify SMTP settings on the printer. Check spam filters on the receiving mailbox. + +**Poor OCR quality?** +→ Increase scan resolution to 300 DPI and use Grayscale mode for text documents. + +--- + +## Related Documentation + +- [DocuElevate Configuration Guide](../ConfigurationGuide.md) +- [Storage Architecture](../StorageArchitecture.md) +- [Troubleshooting](../Troubleshooting.md) diff --git a/docs/howto/MobileScanning.md b/docs/howto/MobileScanning.md new file mode 100644 index 00000000..49a10da8 --- /dev/null +++ b/docs/howto/MobileScanning.md @@ -0,0 +1,152 @@ +# How to Scan Documents from Your Phone or Tablet + +DocuElevate works seamlessly with mobile scanning apps. This guide covers the best ways to capture documents with your phone or tablet and get them into DocuElevate automatically. + +--- + +## Option 1: DocuElevate Web Upload (Simplest) + +The easiest way is to use DocuElevate's built-in web interface directly from your mobile browser. + +1. Open your phone's browser and navigate to your DocuElevate instance (e.g., `http://your-docuelevate-host:8000`). +2. Tap **Upload** in the top navigation. +3. Tap **Choose File** — your phone will open the camera or file picker. +4. Select **Camera** to capture a photo of the document, or pick an existing file. +5. Tap **Upload** — DocuElevate will process the image and convert it to a searchable PDF. + +> **Tip:** Use good lighting and hold the phone steady for better OCR quality. + +--- + +## Option 2: DocuElevate Browser Extension + +The **DocuElevate Browser Extension** for Chrome/Firefox allows one-tap uploading from your mobile browser. + +See the [Browser Extension guide](../BrowserExtension.md) for installation and setup instructions. + +--- + +## Option 3: Mobile Scanning Apps + +These apps provide superior document capture (auto perspective correction, multi-page, etc.) and can send directly to DocuElevate. + +### Microsoft Lens (iOS / Android) + +Microsoft Lens is free and integrates with OneDrive, which DocuElevate supports natively. + +1. Install **Microsoft Lens** from the App Store or Google Play. +2. Scan your document. +3. Save to **OneDrive** in a folder you've connected to DocuElevate. +4. DocuElevate will automatically process new files in your OneDrive folder. + +### Adobe Scan (iOS / Android) + +Adobe Scan produces high-quality searchable PDFs. + +1. Install **Adobe Scan** from the App Store or Google Play. +2. Scan your document. +3. Tap **Share** → **Save to Files** (iOS) or **Share** (Android). +4. Choose to save to your connected cloud storage (Dropbox, Google Drive) monitored by DocuElevate. + +**Or use email ingestion:** +1. Tap **Share** → **Email**. +2. Send to your DocuElevate ingestion email address. +3. See [Email Ingestion Setup](./EmailIngestion.md) for configuration. + +### SwiftScan (iOS) + +SwiftScan (formerly Scanbot) offers excellent auto-capture and document enhancement. + +1. Install **SwiftScan** from the App Store. +2. Scan your document. +3. Configure the **Auto-Upload** feature: + - Go to **Settings** → **Cloud Services**. + - Connect Dropbox, Google Drive, or OneDrive. + - Set the upload folder to your DocuElevate-monitored folder. +4. Every scan is automatically uploaded and processed by DocuElevate. + +### CamScanner (iOS / Android) + +1. Install **CamScanner**. +2. Configure **Auto Backup** to Dropbox or Google Drive. +3. Point DocuElevate at the same cloud folder. + +--- + +## Option 4: iOS Shortcuts Automation + +On iPhone/iPad, you can create an **iOS Shortcut** that scans a document and uploads it directly to DocuElevate's API. + +### Creating the Shortcut + +1. Open the **Shortcuts** app on your iPhone. +2. Tap **+** to create a new shortcut. +3. Add the following actions: + - **Scan Document** — opens the camera for scanning + - **Get Contents of URL** — configures the API call: + - **URL:** `http://your-docuelevate-host:8000/api/upload` + - **Method:** POST + - **Headers:** `Authorization: Bearer YOUR_API_KEY` + - **Request Body:** Form data with `file` = Scanned Document +4. Name the shortcut "Send to DocuElevate". +5. Add it to your home screen for one-tap scanning. + +``` +Shortcut flow: +┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐ +│ Scan Document│ → │ Select pages │ → │ POST to DocuElevate │ +│ (Camera) │ │ & crop │ │ /api/upload │ +└─────────────┘ └──────────────┘ └──────────────────────┘ +``` + +--- + +## Option 5: Android Automation with Tasker + +On Android, **Tasker** can automate document upload when files appear in a specific folder. + +1. Install **Tasker** from Google Play. +2. Create a **Profile** triggered by **File Created** in your scanner app's output folder. +3. Add a **Task** that calls DocuElevate's upload API using the **HTTP Request** action. + +--- + +## Recommended Mobile Scanning Settings + +For best results with DocuElevate's OCR engine: + +| Setting | Recommended Value | +|---------|------------------| +| Output Format | PDF | +| Resolution/Quality | High (equivalent to 300 DPI) | +| Color Mode | Auto (greyscale for text, color for mixed) | +| Perspective Correction | On (auto-straighten) | +| Filter | Document / Black & White for text | +| Multi-page | Combine into single PDF | + +--- + +## Troubleshooting + +**Upload fails from mobile browser?** +→ Check that DocuElevate is accessible from your phone's network (same WiFi, or publicly reachable). +→ Verify your session is still logged in. + +**OCR quality is poor?** +→ Ensure good lighting, no shadows on the document. +→ Use your scanning app's auto-enhance or document filter. +→ Increase resolution to "High" or "Best" in the app settings. + +**Files synced to cloud but not processed?** +→ Check DocuElevate's cloud storage connection in Settings. +→ View worker logs: `docker logs document_worker --tail 50` + +--- + +## Related Documentation + +- [Email Ingestion Setup](./EmailIngestion.md) +- [HP Enterprise Printer Setup](./HPPrinterSetup.md) +- [ScanSnap Setup](./SnapScanSetup.md) +- [Browser Extension](../BrowserExtension.md) +- [API Reference](../API.md) diff --git a/docs/howto/SnapScanSetup.md b/docs/howto/SnapScanSetup.md new file mode 100644 index 00000000..369dfdf2 --- /dev/null +++ b/docs/howto/SnapScanSetup.md @@ -0,0 +1,192 @@ +# How to Set Up Fujitsu ScanSnap to Automatically Send Documents to DocuElevate + +This guide walks you through configuring a Fujitsu ScanSnap scanner to automatically upload scanned documents to DocuElevate for AI-powered processing and storage. + +--- + +## Supported ScanSnap Models + +This guide applies to the following ScanSnap models: + +- **iX1600 / iX1500 / iX1400** – Wi-Fi and USB, ScanSnap Home software +- **iX500 / iX100** – USB/Wi-Fi, ScanSnap Manager software +- **S1300i / S1100i** – USB, ScanSnap Manager software + +> **Note:** ScanSnap Home (for newer models) and ScanSnap Manager (for older models) differ in their profile/job configuration interface. Both methods are covered below. + +--- + +## Method 1: Scan to Folder → DocuElevate Watched Folder + +This is the simplest method and works with all ScanSnap models. + +### Step 1: Configure DocuElevate's Watched Folder + +Add the following to your DocuElevate `.env` file and restart the service: + +```env +WATCH_FOLDER_ENABLED=true +WATCH_FOLDER_PATH=/srv/scan-inbox +WATCH_FOLDER_INTERVAL=10 +``` + +Create the directory and set permissions: + +```bash +mkdir -p /srv/scan-inbox +chmod 777 /srv/scan-inbox +``` + +If running DocuElevate in Docker, mount this folder into the container in your `docker-compose.yaml`: + +```yaml +services: + api: + volumes: + - /srv/scan-inbox:/srv/scan-inbox + worker: + volumes: + - /srv/scan-inbox:/srv/scan-inbox +``` + +### Step 2: Configure ScanSnap Home (iX1600/iX1500/iX1400) + +1. Open **ScanSnap Home** on your computer. +2. Click **+** to create a new profile. +3. Select **Save to folder** as the action. +4. Configure the profile: + - **Folder:** `/srv/scan-inbox` (or a local folder that syncs to it) + - **File format:** PDF + - **Image quality:** Normal or Better + - **Color mode:** Auto + - **Scanning side:** Both sides (auto) +5. Enable **Convert to searchable PDF** if your ScanSnap and ScanSnap Home version support it. +6. Name the profile "DocuElevate" and save. + +Now, pressing the scan button while this profile is active will send the scanned PDF directly to the watched folder. + +### Step 3: Configure ScanSnap Manager (older models) + +1. Right-click the ScanSnap Manager icon in the system tray. +2. Select **Scan Button Settings**. +3. Choose the **Save** tab. +4. Set **Image saving folder** to `/srv/scan-inbox` (or use a shared network path). +5. Under **File format**, select **PDF**. +6. Click **OK** to save. + +--- + +## Method 2: Scan to Cloud Storage → DocuElevate Sync + +If you already use Dropbox, Google Drive, or OneDrive with ScanSnap, you can leverage those integrations to feed documents into DocuElevate. + +### Using Dropbox + +1. In ScanSnap Home, create a profile with action **Save to Dropbox**. +2. Set the destination folder to `ScanSnap/` (or any folder). +3. In DocuElevate settings, configure Dropbox as your storage provider and point DocuElevate to monitor/pull from that folder. +4. DocuElevate will process new documents as they appear in Dropbox. + +### Using Google Drive + +1. Create a ScanSnap profile that saves to **Google Drive** under a dedicated folder (e.g., `ScanSnap/Inbox`). +2. Configure DocuElevate's Google Drive integration to use that folder as a source/destination. + +--- + +## Method 3: Scan to Email → DocuElevate + +ScanSnap can send scanned documents as email attachments. Pair this with the email-to-DocuElevate bridge described in the [HP Printer Guide](./HPPrinterSetup.md#option-a-scan-to-email--docuelevate-api-upload) for automatic ingestion. + +1. In ScanSnap Home, create a profile with action **Send by E-mail**. +2. Configure the recipient address as your DocuElevate-monitored mailbox. +3. Set file format to **PDF**. +4. The email bridge script will pick up the attachment and upload it to DocuElevate. + +--- + +## Method 4: ScanSnap Cloud (iX1600 / ScanSnap Home 3.x+) + +The ScanSnap iX1600 supports **ScanSnap Cloud**, which can send directly to cloud services. + +1. Open the **ScanSnap Home** app or use the printer's touchscreen. +2. Configure a **ScanSnap Cloud** profile that saves to **Google Drive** or **Dropbox**. +3. Point DocuElevate to monitor that cloud folder. + +--- + +## Recommended Scan Settings + +| Setting | Recommended Value | +|---------|------------------| +| File Format | PDF (Searchable PDF if available) | +| Image Quality | Normal (200 DPI) for text, Better (300 DPI) for photos | +| Color Mode | Auto (detects black/white vs color) | +| Scanning Side | Both Sides (Auto) | +| Compression | Medium | +| Rotate | Auto | +| Remove blank pages | Yes | +| Correct skewed scans | Yes | + +--- + +## Automating with ScanSnap Home Profiles + +You can create multiple profiles in ScanSnap Home for different document types: + +| Profile | Settings | Notes | +|---------|----------|-------| +| **Documents** | B&W, 200 DPI, PDF | For invoices, letters, contracts | +| **Photos** | Color, 300 DPI, PDF | For photo documents | +| **Receipts** | B&W, 200 DPI, PDF, Remove blank pages | For expense reports | +| **Business Cards** | Color, 300 DPI, JPEG | May require separate OCR | + +Each profile can be assigned to the scanner's shortcut button (on models with a touchscreen). + +--- + +## Setting Up One-Button Scanning + +On the **ScanSnap iX1600** with its touchscreen: + +1. Tap the profile name (e.g., "DocuElevate") on the scanner's display. +2. The scanner will use that profile for the next scan. +3. Press the physical **Scan** button. +4. The document is scanned and automatically delivered to DocuElevate's watched folder. + +On models without a touchscreen: + +1. Set the "DocuElevate" profile as the **default profile** in ScanSnap Manager/Home. +2. Press the **Scan** button — the document goes directly to DocuElevate. + +--- + +## Troubleshooting + +**Files appear in the folder but DocuElevate doesn't process them?** +→ Check that `WATCH_FOLDER_ENABLED=true` in your `.env` and that the worker can read the folder: +```bash +docker logs document_worker --tail 50 +``` + +**ScanSnap can't find the network folder?** +→ Ensure the folder is shared over SMB/CIFS (Windows share). See the [HP Printer Setup guide](./HPPrinterSetup.md#step-1-set-up-a-shared-network-folder) for Samba configuration. + +**Scanned PDFs have poor text recognition?** +→ Increase scan resolution to 300 DPI. Enable "Convert to searchable PDF" in ScanSnap Home if available. + +**Files are processed twice (duplicates)?** +→ Enable duplicate detection in DocuElevate settings. Check [Troubleshooting](../Troubleshooting.md) for deduplication options. + +**ScanSnap doesn't appear in ScanSnap Home after network change?** +→ Re-run the ScanSnap network setup wizard. Ensure the scanner and your computer are on the same Wi-Fi network/subnet. + +--- + +## Related Documentation + +- [HP Enterprise Printer Setup](./HPPrinterSetup.md) +- [DocuElevate Configuration Guide](../ConfigurationGuide.md) +- [Dropbox Setup](../DropboxSetup.md) +- [Google Drive Setup](../GoogleDriveSetup.md) +- [Troubleshooting](../Troubleshooting.md) diff --git a/docs/howto/WatchedFolderSetup.md b/docs/howto/WatchedFolderSetup.md new file mode 100644 index 00000000..325a9ed4 --- /dev/null +++ b/docs/howto/WatchedFolderSetup.md @@ -0,0 +1,212 @@ +# How to Set Up a Watched Folder for Automatic Document Ingestion + +The **Watched Folder** feature allows DocuElevate to automatically detect and process documents placed in a specific local directory. Any scanner, application, or script that saves files to that folder will have its output automatically ingested. + +--- + +## Overview + +``` +Scanner / Any App → Drop files in folder → DocuElevate watches for new files → Process & Store +``` + +This is one of the simplest ingestion methods and works with virtually any scanner, OCR app, or document workflow tool. + +--- + +## Configuration + +Add these settings to your `.env` file: + +```env +WATCH_FOLDER_ENABLED=true +WATCH_FOLDER_PATH=/srv/docuelevate/watch +WATCH_FOLDER_INTERVAL=30 # Polling interval in seconds +WATCH_FOLDER_RECURSIVE=false # Watch subdirectories too +WATCH_FOLDER_DELETE_AFTER=true # Delete originals after successful processing +WATCH_FOLDER_EXTENSIONS=pdf,jpg,jpeg,png,tiff,tif +``` + +**Restart** the services after editing: + +```bash +docker compose restart api worker +``` + +--- + +## Docker Setup + +When running DocuElevate with Docker Compose, mount the watch folder into both the `api` and `worker` containers: + +```yaml +# docker-compose.yaml +services: + api: + volumes: + - /srv/docuelevate/watch:/srv/docuelevate/watch + - /var/docparse/workdir:/workdir + + worker: + volumes: + - /srv/docuelevate/watch:/srv/docuelevate/watch + - /var/docparse/workdir:/workdir +``` + +Create the folder and set permissions: + +```bash +sudo mkdir -p /srv/docuelevate/watch +sudo chmod 777 /srv/docuelevate/watch +``` + +--- + +## Multiple Watch Folders + +If you have multiple scanners or document sources, you can organize them by subfolder: + +``` +/srv/docuelevate/watch/ +├── reception/ ← Front desk scanner +├── accounting/ ← Finance team scanner +├── hr/ ← HR department +└── general/ ← General purpose +``` + +Enable recursive watching: + +```env +WATCH_FOLDER_RECURSIVE=true +``` + +DocuElevate will monitor all subdirectories and tag documents with the subfolder name for easy filtering. + +--- + +## Using with Network Scanners + +### Windows / Samba Share + +Share the watch folder over the network so scanners and Windows PCs can drop files directly: + +```bash +# Install Samba +sudo apt-get install samba -y + +# Add to /etc/samba/smb.conf +[DocuElevate-Inbox] + comment = DocuElevate Document Inbox + path = /srv/docuelevate/watch + browsable = yes + guest ok = yes + read only = no + create mask = 0777 + directory mask = 0777 +``` + +Restart Samba: +```bash +sudo systemctl restart smbd nmbd +``` + +Windows access: `\\your-server-ip\DocuElevate-Inbox` + +### NFS Share (Linux) + +```bash +# Add to /etc/exports +/srv/docuelevate/watch 192.168.1.0/24(rw,sync,no_subtree_check) + +# Apply changes +sudo exportfs -ra +``` + +### FTP Server (for older devices) + +Many older scanners only support FTP. Run a simple FTP server alongside DocuElevate: + +```yaml +# Add to docker-compose.yaml +services: + ftp: + image: garethflowers/ftp-server + container_name: docuelevate_ftp + environment: + - FTP_USER=scanner + - FTP_PASS=changeme + ports: + - "21:21" + - "20:20" + - "21100-21110:21100-21110" + volumes: + - /srv/docuelevate/watch:/home/scanner +``` + +Configure your scanner's FTP settings to use this server. Files will land in the watched folder. + +--- + +## Supported File Types + +| Format | Extension | Notes | +|--------|-----------|-------| +| PDF | `.pdf` | OCR applied if not already searchable | +| JPEG | `.jpg`, `.jpeg` | Converted to PDF | +| PNG | `.png` | Converted to PDF | +| TIFF | `.tif`, `.tiff` | Supports multi-page TIFF | +| HEIC | `.heic` | iPhone photos (converted) | + +--- + +## Monitoring + +Check that the watched folder is active: + +```bash +# View worker logs +docker logs document_worker --follow + +# Expected output: +# INFO Watch folder monitor: watching /srv/docuelevate/watch (interval: 30s) +# INFO Watch folder: found new file invoice.pdf +# INFO Watch folder: queued document ID 156 for processing +``` + +You can also view processing status in the DocuElevate web interface under **Queue Monitor**. + +--- + +## Troubleshooting + +**Files are dropped but not picked up?** +- Verify `WATCH_FOLDER_ENABLED=true` in `.env` +- Check that `WATCH_FOLDER_PATH` matches the mounted path in docker-compose +- Look at worker logs: `docker logs document_worker --tail 100` + +**Permission denied errors?** +```bash +# Fix permissions +sudo chmod -R 777 /srv/docuelevate/watch +# Or use ACLs for more granular control +sudo setfacl -m u:nobody:rwx /srv/docuelevate/watch +``` + +**Files processed but not deleted?** +- Set `WATCH_FOLDER_DELETE_AFTER=true` +- If you want to keep originals, set to `false` and manage cleanup separately + +**File appears partially uploaded?** +- Large files may arrive before the scanner finishes writing them +- Increase `WATCH_FOLDER_INTERVAL` to give time for files to be fully written +- DocuElevate uses file-lock detection to avoid processing incomplete files + +--- + +## Related Documentation + +- [HP Enterprise Printer Setup](./HPPrinterSetup.md) +- [ScanSnap Setup](./SnapScanSetup.md) +- [Email Ingestion](./EmailIngestion.md) +- [Configuration Guide](../ConfigurationGuide.md) +- [Deployment Guide](../DeploymentGuide.md) diff --git a/frontend/templates/base.html b/frontend/templates/base.html index af1c1414..5005b620 100644 --- a/frontend/templates/base.html +++ b/frontend/templates/base.html @@ -76,6 +76,12 @@ {% if request and request.url.path == '/about' %}aria-current="page"{% endif %}> About + + Help + {% else %} {# ── App nav (logged-in or single-user / auth-disabled mode) ────── #} @@ -171,6 +177,14 @@ Status + + + Help + + {% endif %}{# end multi_user_enabled / is_logged_in check #} @@ -229,6 +243,11 @@ {% if request and request.url.path == '/about' %}aria-current="page"{% endif %}> About + + Help + {% else %} {# ── App links (logged-in or single-user) ────────────────────── #} @@ -298,6 +317,13 @@ Status + + + Help + + {% endif %}{# end multi_user_enabled / is_logged_in check #} diff --git a/mkdocs.yml b/mkdocs.yml index f72c8919..0ee0b569 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,11 +12,34 @@ theme: - navigation.instant - search.suggest - content.code.copy + - navigation.sections + - navigation.top + - toc.integrate + palette: + - scheme: default + primary: blue + accent: blue + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: blue + accent: blue + toggle: + icon: material/brightness-4 + name: Switch to light mode nav: - Getting Started: - Setup Wizard: SetupWizard - User Guide: UserGuide - Browser Extension: BrowserExtension + - How-To Guides: + - Overview: HowToGuides.md + - HP Enterprise Printer: howto/HPPrinterSetup.md + - Fujitsu ScanSnap: howto/SnapScanSetup.md + - Watched Folder: howto/WatchedFolderSetup.md + - Email Ingestion: howto/EmailIngestion.md + - Mobile Scanning: howto/MobileScanning.md - API: API - Deployment: - Overview: DeploymentGuide diff --git a/requirements-dev.txt b/requirements-dev.txt index 7caf3e74..72d8d3af 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -19,6 +19,9 @@ ruff>=0.3.0 mypy>=1.8.0 djlint>=1.36.0 # HTML template linter for accessibility and best practices +# Documentation (builds the embedded /help site via MkDocs Material) +mkdocs-material>=9.5.0 # MkDocs Material theme – same package used in docs/requirements.txt + # Type stubs for mypy types-requests>=2.31.0 types-paramiko>=3.0.0 diff --git a/tests/test_views_help.py b/tests/test_views_help.py new file mode 100644 index 00000000..df9082fb --- /dev/null +++ b/tests/test_views_help.py @@ -0,0 +1,92 @@ +"""Tests for app/views/help.py module.""" + +import pytest + + +@pytest.mark.integration +class TestHelpViews: + """Tests for the help documentation view routes.""" + + def test_help_redirect(self, client): + """GET /help should redirect permanently to /help/.""" + response = client.get("/help", follow_redirects=False) + assert response.status_code == 301 + assert response.headers["location"] in ("/help/", "http://testserver/help/") + + def test_help_redirect_follows(self, client): + """Following /help redirect leads to /help/ (docs served or 404 if not built).""" + # In test environments docs_build/ is not present, so /help/ may 404. + # We only verify the initial redirect works; the final landing page depends + # on whether the docs have been built (they are built only in Docker images). + response = client.get("/help", follow_redirects=False) + assert response.status_code == 301 + # Redirect target must be /help/ + location = response.headers.get("location", "") + assert location.endswith("/help/") + + def test_help_route_is_registered(self, client): + """Verify the /help route exists in the app router.""" + # A GET to /help must not return 405 Method Not Allowed + response = client.get("/help", follow_redirects=False) + assert response.status_code != 405 + + +@pytest.mark.unit +class TestHelpViewUnit: + """Unit tests for the help view module.""" + + def test_help_redirect_returns_301(self): + """Verify the redirect is HTTP 301 (permanent).""" + from fastapi import FastAPI + from fastapi.testclient import TestClient + + from app.views.help import router + + app = FastAPI() + app.include_router(router) + with TestClient(app) as tc: + resp = tc.get("/help", follow_redirects=False) + assert resp.status_code == 301 + + def test_help_redirect_target(self): + """Verify the redirect points to /help/.""" + from fastapi import FastAPI + from fastapi.testclient import TestClient + + from app.views.help import router + + app = FastAPI() + app.include_router(router) + with TestClient(app) as tc: + resp = tc.get("/help", follow_redirects=False) + assert resp.headers["location"].rstrip("/").endswith("help") or resp.headers["location"].endswith("/help/") + + def test_docs_build_path_constant(self): + """Verify _DOCS_BUILD_DIR is resolved relative to the app package.""" + import pathlib + + from app.views.help import _DOCS_BUILD_DIR + + assert isinstance(_DOCS_BUILD_DIR, pathlib.Path) + # Should point to /docs_build + assert _DOCS_BUILD_DIR.name == "docs_build" + + +@pytest.mark.integration +class TestHelpNavigationLink: + """Tests that the Help link appears in the navigation.""" + + def test_help_link_in_nav(self, client): + """The Help navigation link should appear in the base template.""" + response = client.get("/about") + assert response.status_code == 200 + # The Help link should be present somewhere in the rendered page + assert b"/help/" in response.content + + def test_help_link_has_accessible_text(self, client): + """The Help link should have visible text for accessibility.""" + response = client.get("/about") + assert response.status_code == 200 + content = response.text + # Should include the word "Help" associated with /help/ + assert "Help" in content