"""Thin wrapper around the bits of Nextcloud's WebDAV and OCS Share API we need. Auth is HTTP Basic with an app password obtained per-user via Login Flow v2 (see auth.py) - callers pass (username, app_password) explicitly rather than this module reading a single global credential, since every user acts as themselves now. """ from __future__ import annotations import requests from .config import Config def _webdav_root(username: str) -> str: return f"{Config.NC_BASE_URL}/remote.php/dav/files/{username}" def ensure_upload_folder(username: str, app_password: str) -> None: """MKCOL each segment of the configured upload folder path if missing. NC_UPLOAD_FOLDER may be nested (e.g. "Documents/Cospend/Assets"); MKCOL only creates one level at a time, so walk the path segment by segment. Idempotent. """ if not Config.NC_UPLOAD_FOLDER: return segments = Config.NC_UPLOAD_FOLDER.split("/") partial = "" for segment in segments: partial = f"{partial}/{segment}" if partial else segment url = f"{_webdav_root(username)}/{partial}" resp = requests.request("MKCOL", url, auth=(username, app_password), timeout=15) # 201 = created, 405 = already exists. Anything else is a real problem. if resp.status_code not in (201, 405): resp.raise_for_status() def upload_file( username: str, app_password: str, filename: str, content: bytes, content_type: str = "image/jpeg" ) -> str: """Uploads `content` to / via WebDAV PUT, into the given user's own NC files. Returns the server-relative path (e.g. "wgBill/receipt-123.jpg"), which is what the Share API expects as `path`. """ ensure_upload_folder(username, app_password) rel_path = f"{Config.NC_UPLOAD_FOLDER}/{filename}" if Config.NC_UPLOAD_FOLDER else filename url = f"{_webdav_root(username)}/{rel_path}" resp = requests.put( url, data=content, auth=(username, app_password), headers={"Content-Type": content_type}, timeout=30, ) resp.raise_for_status() return rel_path def create_public_share(username: str, app_password: str, rel_path: str) -> str: """Creates a public read-only link share for `rel_path`. Returns the share URL.""" url = f"{Config.NC_BASE_URL}/ocs/v2.php/apps/files_sharing/api/v1/shares" resp = requests.post( url, auth=(username, app_password), headers={"OCS-APIRequest": "true"}, data={ "path": f"/{rel_path}", "shareType": 3, # public link "permissions": 1, # read-only }, params={"format": "json"}, timeout=15, ) resp.raise_for_status() payload = resp.json() share_url = payload["ocs"]["data"]["url"] return share_url