fix(client): route /apps/* through /index.php for non-pretty-URL installs

Bare /apps/<app>/... URLs return 404 on Nextcloud installs without Pretty
URLs (URL rewriting), which is opt-in and not the default — see #732. The
/index.php/apps/... form is the universal entry point and works regardless
of web-server config, matching how /remote.php/dav and /ocs/v2.php already
have dedicated entry points.

Add a small _resolve_url helper on BaseNextcloudClient that rewrites
/apps/... → /index.php/apps/... at the top of _make_request, so every
current call site (notes, deck, cookbook, news) and any future ones are
covered transparently with no per-client churn.

Other path prefixes (/remote.php, /ocs, absolute URLs, already-prefixed
/index.php/apps) pass through unchanged. New unit tests in
tests/unit/client/test_base.py pin all six cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-04-26 16:33:37 +02:00
co-authored by Claude Opus 4.7
parent 4e669781ee
commit 3fb3680a83
2 changed files with 86 additions and 0 deletions
+16
View File
@@ -99,6 +99,21 @@ class BaseNextcloudClient(ABC):
"""Helper to get the base WebDAV path for the authenticated user."""
return f"/remote.php/dav/files/{self.username}"
@staticmethod
def _resolve_url(url: str) -> str:
"""Prefix bare ``/apps/...`` paths with ``/index.php``.
Pretty URLs (URL rewriting that strips ``index.php``) are an opt-in
Nextcloud feature; without them, ``/apps/<app>/...`` returns 404 — see
issue #732. ``/index.php/apps/<app>/...`` is the universal entry point
and works on every Nextcloud install regardless of web-server config,
so we route all app-API calls through it. ``/remote.php/dav/...`` and
``/ocs/...`` have their own dedicated entry points and are unaffected.
"""
if url.startswith("/apps/"):
return "/index.php" + url
return url
@retry_on_429
async def _make_request(self, method: str, url: str, **kwargs):
"""Common request wrapper with logging, tracing, and error handling.
@@ -111,6 +126,7 @@ class BaseNextcloudClient(ABC):
Returns:
Response object
"""
url = self._resolve_url(url)
logger.debug(f"Making {method} request to {url}")
# Start timer for metrics