Add paperless-ngx integration for document storage and share links

- dlib/integrations/paperless.py: sync HTTP client wrapping the paperless-ngx
  REST API (upload doc, poll task, create/delete share links, delete document)
- config: PAPERLESS_ENABLED, PAPERLESS_BASE_URL, PAPERLESS_TOKEN, PAPERLESS_TAG_IDS
- PublicAsset model: paperless_document_id + paperless_share_slug columns
- publication service: after creating the asset, if paperless is enabled upload
  the patched PDF and create a share link; stores doc id + share slug on the asset
- public routes: pass expires_at through to publish_version; new
  POST /{slug}/share-links endpoint to (re)create expiring share links on demand
- schemas: PublishRequest.expires_at, PublicAssetResponse.paperless_share_url,
  new ShareLinkRequest model
- frontend: paperless_share_url field on PublicAsset type, createShareLink()
  and expiresAt param on publishVersion() in api.ts
- .env.example: documented paperless env vars

https://claude.ai/code/session_01YPVs6uBwCvcwVMvrfLBBdu
This commit is contained in:
Claude
2026-04-09 09:27:26 +00:00
parent 61430317f4
commit f5621f120f
11 changed files with 214 additions and 18 deletions

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import asyncio
import re
from datetime import datetime
from uuid import uuid4
@@ -7,7 +8,11 @@ from uuid import uuid4
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import get_settings
from app.models import CvDocument, CvVersion, PublicAsset, Submission
from app.services.storage import storage_client
from dlib.cv import docx_bytes_to_pdf, generate_patched_docx
from dlib.integrations.paperless import get_paperless_client
async def publish_version(
@@ -17,6 +22,7 @@ async def publish_version(
version_id: str | None,
submission_id: str | None,
slug: str | None,
expires_at: datetime | None = None,
) -> PublicAsset | None:
target_version: CvVersion | None = None
target_submission: Submission | None = None
@@ -55,11 +61,27 @@ async def publish_version(
slug=resolved_slug,
artifact_key=target_version.artifact_docx_key,
is_public=True,
expires_at=None,
expires_at=expires_at,
)
session.add(asset)
await session.commit()
await session.refresh(asset)
settings = get_settings()
client = get_paperless_client(settings)
if client:
docx = storage_client.download_bytes(target_version.artifact_docx_key)
blocks = target_version.structured_blocks or []
pdf = docx_bytes_to_pdf(generate_patched_docx(docx, blocks))
doc_id = await asyncio.to_thread(
client.upload_document, pdf, resolved_slug, settings.paperless_tag_ids or []
)
_, share_url = await asyncio.to_thread(client.create_share_link, doc_id, expires_at)
asset.paperless_document_id = doc_id
asset.paperless_share_slug = share_url.split("/share/")[-1]
await session.commit()
await session.refresh(asset)
return asset