# CRM Step 09 — Integration: Nextcloud WebDAV ## Context Read `.claude/crm-build-plan.md` for full context and IMPORTANT NOTES. Steps 01–08 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` ```python 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: ```python 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 `` 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