Files
bellsystems-cp/.claude/crm-step-09.md

3.9 KiB
Raw Blame History

CRM Step 09 — Integration: Nextcloud WebDAV

Context

Read .claude/crm-build-plan.md for full context and IMPORTANT NOTES. Steps 0108 must be complete.

Task

Connect the console to Nextcloud via WebDAV so that:

  1. Files in a customer's Nextcloud folder are listed in the Media tab automatically
  2. Uploading a file from the console sends it to Nextcloud
  3. Files can be downloaded/previewed via a backend proxy

Backend changes

1. Add Nextcloud settings to backend/config.py

nextcloud_url: str = "https://nextcloud.bonamin.gr"            # e.g. https://cloud.example.com
nextcloud_email: str = "bellsystems.gr@gmail.com"
nextcloud_username: str = "bellsystems-console"
nextcloud_password: str = "ydE916VdaQdbP2CQGhD!"
nextcloud_app_password: str = "rtLCp-NCy3y-gNZdg-38MtN-r8D2N"
nextcloud_base_path: str = "BellSystems"   # root folder inside Nextcloud

2. Create backend/crm/nextcloud.py

WebDAV client using httpx (already available). Functions:

async def list_folder(nextcloud_path: str) -> List[dict]:
    """
    PROPFIND request to Nextcloud WebDAV.
    Returns list of {filename, path, mime_type, size, last_modified, is_dir}
    Parse the XML response (use xml.etree.ElementTree).
    URL: {nextcloud_url}/remote.php/dav/files/{username}/{nextcloud_base_path}/{nextcloud_path}
    """

async def upload_file(nextcloud_path: str, filename: str, content: bytes, mime_type: str) -> str:
    """
    PUT request to upload file.
    Returns the full nextcloud_path of the uploaded file.
    """

async def download_file(nextcloud_path: str) -> tuple[bytes, str]:
    """
    GET request. Returns (content_bytes, mime_type).
    """

async def delete_file(nextcloud_path: str) -> None:
    """
    DELETE request.
    """

Use HTTP Basic Auth with nextcloud_username/nextcloud_password. If nextcloud_url is empty string, raise HTTPException 503 "Nextcloud not configured".

3. Add to backend/crm/router.py

Media/Nextcloud endpoints:

GET /api/crm/nextcloud/browse?path=05_Customers/FOLDER → calls list_folder(path), returns file list

GET /api/crm/nextcloud/file?path=05_Customers/FOLDER/photo.jpg → calls download_file(path), returns Response(content=bytes, media_type=mime_type) → This is the proxy endpoint — frontend uses this to display images

POST /api/crm/nextcloud/upload → accepts UploadFile + form field nextcloud_path (destination folder) → calls upload_file(...), then calls create_media(...) to save the metadata record → returns the created MediaInDB

DELETE /api/crm/nextcloud/file?path=... → calls delete_file(path), also deletes the matching crm_media record if found

Frontend changes

Update Media tab in CustomerDetail.jsx

  • On load: if customer.nextcloud_folder is set, fetch GET /api/crm/nextcloud/browse?path={customer.nextcloud_folder} and merge results with existing crm_media records. Show files from both sources — deduplicate by nextcloud_path.
  • Image files: render as <img src="/api/crm/nextcloud/file?path=..." /> via the proxy endpoint
  • Other files: show as a download link hitting the same proxy endpoint
  • Upload button: file picker → POST to /api/crm/nextcloud/upload with file + destination path (default to customer's Sent Media subfolder)
  • Show upload progress indicator

Update Media tab in CustomerDetail.jsx — subfolder selector

When uploading, let user choose subfolder: "Sent Media" | "Received Media" | "Internal" (maps to direction field too)

Notes

  • httpx is likely already in requirements. If not, add it: httpx>=0.27.0
  • PROPFIND response is XML (DAV namespace). Parse D:response elements, extract D:href and D:prop children.
  • The proxy approach means the VPS never stores files — it just streams them through from Nextcloud
  • nextcloud_base_path in config allows the root to be BellSystems so paths in DB are relative to that root