feat(share): public folder browsing API + range support + zip

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.
This commit is contained in:
abnvle
2026-05-05 22:41:55 +02:00
parent 8527765bf2
commit d15d7b8f8e
8 changed files with 725 additions and 85 deletions
@@ -965,6 +965,58 @@ impl FolderRepository for FolderDbRepository {
})
.collect()
}
async fn is_folder_in_subtree(
&self,
candidate_folder_id: &str,
root_folder_id: &str,
) -> Result<bool, DomainError> {
let exists: bool = sqlx::query_scalar(
"SELECT EXISTS (\
SELECT 1 \
FROM storage.folders c, storage.folders r \
WHERE c.id = $1::uuid \
AND r.id = $2::uuid \
AND c.is_trashed = false \
AND r.is_trashed = false \
AND c.lpath <@ r.lpath \
)",
)
.bind(candidate_folder_id)
.bind(root_folder_id)
.fetch_one(self.pool())
.await
.map_err(|e| {
DomainError::internal_error("FolderDb", format!("is_folder_in_subtree: {e}"))
})?;
Ok(exists)
}
async fn is_file_in_subtree(
&self,
file_id: &str,
root_folder_id: &str,
) -> Result<bool, DomainError> {
let exists: bool = sqlx::query_scalar(
"SELECT EXISTS (\
SELECT 1 \
FROM storage.files f \
JOIN storage.folders parent ON f.folder_id = parent.id \
JOIN storage.folders root ON root.id = $2::uuid \
WHERE f.id = $1::uuid \
AND f.is_trashed = false \
AND parent.is_trashed = false \
AND root.is_trashed = false \
AND parent.lpath <@ root.lpath \
)",
)
.bind(file_id)
.bind(root_folder_id)
.fetch_one(self.pool())
.await
.map_err(|e| DomainError::internal_error("FolderDb", format!("is_file_in_subtree: {e}")))?;
Ok(exists)
}
}
// ── Extra helpers for blob-storage bootstrap ──