From 09c5f780611e94fabf4d49aaf0e672f4eaa92ece Mon Sep 17 00:00:00 2001 From: h Date: Wed, 14 Oct 2026 03:53:56 +0200 Subject: [PATCH] fix(frontend,backend): all stories panel groups by peer again, missing trashed file answers 404, access log hides the token --- backend/src/api/access_log.py | 14 ++++++++----- backend/src/api/app.py | 4 ++-- backend/src/api/routers/storage.py | 2 +- backend/src/utils/storage.py | 3 +++ .../stories/AllStoriesArchive.svelte | 21 ++++++++++++------- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/backend/src/api/access_log.py b/backend/src/api/access_log.py index 1fecfcb..f5f72df 100644 --- a/backend/src/api/access_log.py +++ b/backend/src/api/access_log.py @@ -1,19 +1,23 @@ import logging +import re QUERYLESS_PREFIXES = ("/mcp",) _PATH_ARG = 2 +_TOKEN = re.compile(r"(?<=[?&]token=)[^&]+") -class DropQueryFilter(logging.Filter): +class ScrubQueryFilter(logging.Filter): def filter(self, record: logging.LogRecord) -> bool: args = record.args if isinstance(args, tuple) and len(args) > _PATH_ARG: path = str(args[_PATH_ARG]) if path.startswith(QUERYLESS_PREFIXES): - bare = path.partition("?")[0] - record.args = (*args[:_PATH_ARG], bare, *args[_PATH_ARG + 1 :]) + path = path.partition("?")[0] + else: + path = _TOKEN.sub("***", path) + record.args = (*args[:_PATH_ARG], path, *args[_PATH_ARG + 1 :]) return True -def hide_mcp_query() -> None: - logging.getLogger("uvicorn.access").addFilter(DropQueryFilter()) +def scrub_access_log() -> None: + logging.getLogger("uvicorn.access").addFilter(ScrubQueryFilter()) diff --git a/backend/src/api/app.py b/backend/src/api/app.py index 2ce8f32..4757664 100644 --- a/backend/src/api/app.py +++ b/backend/src/api/app.py @@ -10,7 +10,7 @@ from fastapi.responses import FileResponse, RedirectResponse from fastmcp.utilities.lifespan import combine_lifespans from starlette.applications import Starlette -from api.access_log import hide_mcp_query +from api.access_log import scrub_access_log from api.auth import BearerAuthMiddleware from api.mcp.server import mcp from api.realtime import hub @@ -50,7 +50,7 @@ if env.auth.token is None: raise RuntimeError(msg) _token = env.auth.token.get_secret_value() -hide_mcp_query() +scrub_access_log() mcp_app = mcp.http_app(path="/") diff --git a/backend/src/api/routers/storage.py b/backend/src/api/routers/storage.py index 92f9e27..45911b2 100644 --- a/backend/src/api/routers/storage.py +++ b/backend/src/api/routers/storage.py @@ -98,7 +98,7 @@ async def serve_trashed( storage_key: str, ) -> FileResponse: item = await storage_read.get_trash_item(pool, storage_key) - if item is None: + if item is None or not storage.trashed(storage_key): raise HTTPException(status_code=404, detail="file is not in trash") return FileResponse( storage.trash_url(storage_key), diff --git a/backend/src/utils/storage.py b/backend/src/utils/storage.py index b34b4d5..9456e8c 100644 --- a/backend/src/utils/storage.py +++ b/backend/src/utils/storage.py @@ -53,5 +53,8 @@ class ContentAddressedStorage: def purge(self, key: str) -> None: self._trash_path(key).unlink(missing_ok=True) + def trashed(self, key: str) -> bool: + return self._trash_path(key).is_file() + def trash_url(self, key: str) -> str: return str(self._trash_path(key)) diff --git a/frontend/src/lib/components/stories/AllStoriesArchive.svelte b/frontend/src/lib/components/stories/AllStoriesArchive.svelte index c1cbed3..34dfd3f 100644 --- a/frontend/src/lib/components/stories/AllStoriesArchive.svelte +++ b/frontend/src/lib/components/stories/AllStoriesArchive.svelte @@ -32,10 +32,14 @@ let groups = $state([]); let loading = $state(false); - const previews = $state>({}); + const previews = $state>({}); const expanded = $state>({}); let token = 0; + function previewKey(story: StoryView): string { + return `${story.peer_id}:${story.story_id}`; + } + let viewerOpen = $state(false); let viewerIndex = $state(0); let viewerItems = $state([]); @@ -78,7 +82,7 @@ kind: "peer" as const, name: peerName(peer) || `ID ${peerId}`, peerId, - stories, + stories: peerStories, }; } const chat = chatById.get(peerId); @@ -87,7 +91,7 @@ kind: "chat" as const, name: chat?.title ?? `Chat ${peerId}`, peerId, - stories, + stories: peerStories, }; }); } finally { @@ -106,7 +110,7 @@ groups = []; loading = false; for (const key of Object.keys(previews)) { - delete previews[Number(key)]; + delete previews[key]; } for (const key of Object.keys(expanded)) { delete expanded[Number(key)]; @@ -122,13 +126,14 @@ let active = true; untrack(() => { for (const item of list) { - if (!item.downloaded || previews[item.story_id] !== undefined) { + const key = previewKey(item); + if (!item.downloaded || previews[key] !== undefined) { continue; } - previews[item.story_id] = null; + previews[key] = null; loadStoryMedia(item.peer_id, item.story_id).then((url) => { if (active) { - previews[item.story_id] = url; + previews[key] = url; } }); } @@ -210,7 +215,7 @@ {#each group.stories as item, index (item.story_id)} openViewer(group, index)} - preview={previews[item.story_id]} + preview={previews[previewKey(item)]} story={item} /> {/each}