fix(viewer): show restricted viewers their own media and storage stats - #473
Conversation
A viewer restricted to some accounts or chats had media_files and total_size_mb removed from /api/stats, because the cached statistics only held archive-wide media totals. The stats popup then rendered the missing fields as 0 files / 0 MiB. The statistics job now also stores downloaded media count and bytes per (account, chat), under the same keys as the per-chat message counts, and /api/stats sums them over the principal's visible chats. Bytes are the logical DB file sizes, since on-disk usage cannot be split per chat once media is deduplicated. A blob calculated before this change has no media maps, so the figures stay omitted for restricted principals until the next calculation, and the UI now hides those rows instead of showing a zero. Both maps are stripped from /api/stats and /api/stats/refresh responses, like the message map, since their keys are chat ids. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBKaAtgJ6LTpJMrLCk98WM
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: GeiserX/Telegram-Archive/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review. 📝 WalkthroughWalkthroughThe statistics cache now stores downloaded-media counts and byte totals by account and chat. Restricted responses scope these values to entitled chats. The stats popup hides media and storage rows when their values are unavailable. ChangesAccount-scoped media statistics
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to The scoped media statistics and unavailable-value display are ready for normal merge checks; no actionable merge-blocking issue was established. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #473 +/- ##
==========================================
- Coverage 95.06% 94.95% -0.11%
==========================================
Files 29 29
Lines 12252 12318 +66
==========================================
+ Hits 11647 11697 +50
- Misses 605 621 +16
🚀 New features to boost your workflow:
|
…e account half of the key Three gaps the review's mutants walked through: a template that renders 0 for an absent media figure (the bug this fixes) stayed green, a grouped SUM without the coalesce aborted the statistics job on a NULL file_size with no test noticing, and the test named for the colliding chat id never held downloaded media on the second account's copy. Each new test goes red on its mutant and green on the fix.
|
Thanks for this. The per-(account, chat) maps reuse the existing key format and the fail-closed reader, the new maps never reach the browser, and the pre-change blob case hides the rows instead of guessing. The suite ran on SQLite and PostgreSQL, and mutations of the scoping, the key builder and the response filtering were all caught by your tests. Merged with three small test additions stacked on top: a node test on loadStats for the omitted-key and real-zero cases plus a static check of the two v-if guards, a NULL file_size row to pin the coalesce, and a downloaded row on account 2's copy of the colliding chat id so that test exercises the account half of the key. Two nits are tracked as follow-ups: totals under half a MiB render as "0 MiB" (pre-existing formatter), and the job now scans media three times where one grouped scan would do. It ships with the next release. |
…per-chat maps Semantic port of upstream GeiserX#473 and GeiserX#476 for our one-account-per-archive layout (maps keyed by chat id, not account:chat). /api/stats returned the cached blob as is, so every login - including share tokens and viewer accounts restricted to a few chats - received the archive-wide totals and per_chat_message_counts, the id and message count of every archived chat. - The per-chat maps (messages, and now downloaded media count and bytes) are scoping input only: stripped from /api/stats and /api/stats/refresh. - A restricted viewer's chats, messages, media and storage are summed over its visible chats (fail closed). A blob written before the media maps existed omits the two media figures, and the popup hides those rows rather than showing 0 or archive-wide numbers. - The stats job reads downloaded media in one grouped scan instead of three; totals still count rows without a chat id. - formatSize shows "<1 MiB" instead of "0 MiB" for tiny totals. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Summary
media_filesandtotal_size_mbremoved from/api/stats, because the cached statistics only had archive-wide media totals. The stats popup then showed those rows as 0 files / 0 MiB.(account, chat), under the same"<account>:<chat>"keys as the per-chat message counts./api/statssums them over the principal's visible chats, through the same fail-closed reader (_scoped_message_counts, renamed_scoped_chat_countssince it now serves three maps). The UI hides the two rows when the server omits them, instead of rendering a false zero.Where
src/db/adapter.py:calculate_and_store_statisticsruns one more grouped query (count,coalesce(sum(file_size), 0)overdownloaded = 1, grouped byaccount_id, chat_id) and addsper_account_chat_media_counts/per_account_chat_media_bytesto the blob. Archive-wide totals are unchanged.src/web/main.py:/api/statspops both maps, like the message map, and for a restricted principal fillsmedia_files/total_size_mbfrom them./api/stats/refreshpops them too (their keys are chat ids).src/web/templates/index.html:media_files/total_size_mbbecome?? nullrather than|| 0, and each row hasv-if="... != null". A real zero still shows.Semantics worth reviewing
duon the backup path. Per-chat on-disk usage isn't defined onceDEDUPLICATE_MEDIAsymlinks rows into_shared/, so the restricted figure isSUM(media.file_size)of the principal's downloaded rows. A blob shared by two visible chats counts twice. This is the same value the archive-wide stat already falls back to when the path is unmounted.POST /api/stats/refresh. The archive-wide figures never stand in for a restricted principal's own.Type of Change
Database Changes
metadata.cached_statsJSON blob gains two keys)Data Consistency Checklist
chat_idvalues use marked format (read back frommedia.chat_id, already marked)_strip_tz()before DB operations (no new datetimes)Testing
tests/test_stats_account_scoped.py: 63 passed on SQLite and PostgreSQL 18 (real_adapter). New coverage: media maps grouped by(account, chat)with a never-downloaded row excluded; per-account and per-ref-grant media totals; empty grant reads zero; a pre-change blob omits the media keys; neither map reaches the/api/statsor/api/stats/refreshresponse.tests/test_db_adapter.py::TestCalculateAndStoreStatisticsupdated for the extraexecutecall.tests/test_frontend_bootstrap.pytests that need anodebinary. They fail the same way on unmodifiedmainin the same container.ruff check ./ruff format --check .clean.Security Checklist
_visible_chat_pair_set; the new maps never leave the server)Deployment Notes
🤖 Generated with Claude Code
https://claude.ai/code/session_01HBKaAtgJ6LTpJMrLCk98WM
Summary by CodeRabbit