fix(vector): gate scanner app polls on per-user enabled apps
The vector-sync scanner polled every indexed app (Notes, Files, News, Deck) for every provisioned user on each scan cycle. When a user lacks an app, its REST API returns 404; these were caught (indexing continued) but flooded tenant logs with repeated 404s, scaling with users x disabled-apps x scan-frequency and masking real failures. Add NextcloudClient.get_enabled_apps(), which reads the per-user /ocs/v2.php/core/navigation/apps endpoint (respects group restrictions). Chosen over /cloud/capabilities because the News app advertises no capability and never appears there. scan_user_documents now resolves the enabled-app set once per cycle and skips the Notes/News/Deck scans for apps the user lacks. Files stays unconditional (core Tags API, not a 404 source). Detection failures fall back to scanning every app (prior behaviour), so a transient nav-endpoint blip never silently halts indexing; the per-app 404 guards remain as the safety net. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
90d2192347
commit
2e609cbea7
@@ -199,6 +199,33 @@ class NextcloudClient:
|
||||
|
||||
return response.json()
|
||||
|
||||
async def get_enabled_apps(self) -> set[str]:
|
||||
"""Return the set of app ids enabled for the authenticated user.
|
||||
|
||||
Uses the per-user core navigation endpoint, which lists only apps the
|
||||
current user can access (respecting group restrictions). The vector
|
||||
scanner uses this to skip polling apps the user lacks, which would 404
|
||||
and flood tenant logs. Preferred over ``/cloud/capabilities`` because
|
||||
the News app advertises no capability and so never appears there.
|
||||
"""
|
||||
response = await self._client.get(
|
||||
"/ocs/v2.php/core/navigation/apps",
|
||||
headers={"OCS-APIRequest": "true", "Accept": "application/json"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
entries = data.get("ocs", {}).get("data", []) or []
|
||||
enabled: set[str] = set()
|
||||
for entry in entries:
|
||||
# ``app`` is the canonical app id; ``id`` matches it for the apps we
|
||||
# gate. Union both so an unexpected nav-entry shape never hides an
|
||||
# enabled app — a false "disabled" would skip real indexing.
|
||||
for key in ("app", "id"):
|
||||
value = entry.get(key)
|
||||
if value:
|
||||
enabled.add(value)
|
||||
return enabled
|
||||
|
||||
async def notes_search_notes(self, *, query: str):
|
||||
"""Search notes using token-based matching with relevance ranking."""
|
||||
all_notes = self.notes.get_all_notes()
|
||||
|
||||
Reference in New Issue
Block a user