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

93 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`
```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