93 lines
3.9 KiB
Markdown
93 lines
3.9 KiB
Markdown
# 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 `<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
|