feat(share): public folder browsing with descendant check, range, and zip#347
feat(share): public folder browsing with descendant check, range, and zip#347abnvle wants to merge 2 commits intoAtalayaLabs:mainfrom
Conversation
2c11d5c to
7a80285
Compare
Five new public endpoints under /api/s/{token}/...:
GET /contents
GET /contents/{folder_id}
GET /file/{file_id}
GET /zip
GET /zip/{folder_id}
All honour the unlock cookie from /verify, so password-protected
folder shares work end-to-end.
Folder/file IDs are validated against the share subtree via a single
ltree containment query (O(log N) on the existing GiST index).
Out-of-scope IDs return 404.
download_shared_file refactored to a Range/304/206/416-aware
serve_share_file helper, shared with the new /file/{file_id}
endpoint. content_disposition extracted from FileHandler so RFC 5987
formatting is identical across auth and share download paths.
7a80285 to
b4ca223
Compare
|
Nice ! I see lot of duplicate code (in CSS & JS) The reuse the current builder to display list & grid is more complicated: this part need a refactor (currently too deeply included in ui.js) I will check to extract this part in the future |
|
I tested your branch, this is great Just seen that .zip date seems to wrong date (see below) otherwise everything is functionnal ! small issue: .zip files are at 30 nov 1979: % cd test\ share
% ls -la
total 606464
drwxrwxr-x@ 14 ed staff 448 30 nov 1979 .
drwx------@ 1533 ed staff 49056 5 mai 23:34 ..
-rw-rw-r--@ 1 ed staff 173713927 30 nov 1979 276047.mp4
-rw-rw-r--@ 1 ed staff 7591474 30 nov 1979 35862-408654167.mp4
-rw-rw-r--@ 1 ed staff 169764 30 nov 1979 API Stage.png
-rw-rw-r--@ 1 ed staff 121283919 30 nov 1979 BigBuckBunny.m4v
-rw-rw-r--@ 1 ed staff 5174784 30 nov 1979 premiummusicodyssey-sunday-lights-446231.mp3
-rw-rw-r--@ 1 ed staff 1971244 30 nov 1979 projet four pizza.pdf
-rw-rw-r--@ 1 ed staff 5 30 nov 1979 README.md
-rw-rw-r--@ 1 ed staff 407710 30 nov 1979 ReBAC example.png
drwxrwxr-x@ 2 ed staff 64 30 nov 1979 test subfolder
-rw-rw-r--@ 1 ed staff 0 30 nov 1979 test.doc
-rw-rw-r--@ 1 ed staff 0 30 nov 1979 test.ppt
-rw-rw-r--@ 1 ed staff 176068 30 nov 1979 this is a very long name, i repeat, this is a very long name.jpg |
Replaces the placeholder with a gallery rendered by publicShare.js against the new public endpoints. - Grid / list view toggle (localStorage) - Image thumbs and lazy-loaded video posters - Click-to-open lightbox with Esc / arrow nav - ZIP download for the current folder - Subfolder navigation via URL hash, History API for back Adds five overlay-related tokens to base/variables.css for the lightbox surface (on-overlay text, translucent button, drop shadow). share.html itself only adds an icons.js module import and empties #share-folder for JS to populate.
b4ca223 to
cb454d8
Compare
|
Thanks a lot :) I saw a problem with .zip - I think it's easy to fix. The zip builds without the .last_modification_date(...) |
|
feel free to update uiFileTypes mapping if needed ;) |
|
Okay, so I'll try to do that. For now, I'm doing one more thing related to the PRs I made today. You should have received a notification because I've taken the liberty of adding you to my private repo :) |


What
Replaces the "Folder browsing is not yet available" placeholder in
share.htmlwith a public folder gallery, backed by five new endpoints under/api/s/{token}/...:GET /contentsGET /contents/{folder_id}GET /file/{file_id}GET /zipGET /zip/{folder_id}All five honour the unlock cookie from #, so password-protected folder shares work end-to-end.
The existing
/api/s/{token}/downloadis also refactored to use the new range-aware streaming helper, gaining 206 Partial Content (video seeking, resumable downloads).Security
Every folder/file ID accepted by the new endpoints is validated against the share's subtree via a single ltree
<@containment query (O(log N) on the existing GiST index). Out-of-scope IDs return 404, not 403, so a folder-share token cannot be used to enumerate IDs across the instance. Trashed folders/files are excluded at SQL. Listings always run owner-scoped under the share creator.Implementation
Backend
FolderRepository::is_folder_in_subtreeandis_file_in_subtree: new trait methods with safeOk(false)defaults; pg impl uses one ltree query each.ShareBrowseService: composesShareService(token validation + unlock),FolderService(listing),FileRetrievalService,FolderDbRepository(descendant check). Wired intoAppState.download_shared_filerefactored to callserve_share_filewith full Range / 206 / 416 / 304 /Accept-Ranges/ETagsupport;/file/{file_id}reuses the same helper.FileHandler::content_dispositionextracted intopub(super) build_content_dispositionso RFC 5987 disposition formatting is identical across auth and share download paths.Frontend
share.htmlkeeps its structure; only two changes:<script type="module" src="/js/core/icons.js">so<i class="fa-…">is auto-replaced with inline SVGs by the existing icon system#share-folderis now an empty container the JS fillspublicShare.jsrewritten with:/api/s/{token}/file/{id}(cover-cropped 4:3)<video preload="metadata">(IntersectionObserver, 300 px lookahead) with play-marker overlay/api/s/{token}/zip[/{folder_id}]share-public.cssextended with gallery, list view, and lightbox styles using existing theme tokens (light and dark modes).Test plan
/file/{id}: returns 206 withContent-Rangedocker compose buildsucceeds