feat: fix content sharing — nginx proxy, file path resolution, catalog filtering
- Add /content and /dwn proxy locations to nginx config (both HTTP and HTTPS) so peer requests reach the backend instead of the SPA catch-all - Update content_file_path() to check FileBrowser data dir as fallback when files aren't in the dedicated content/files/ directory - Populate size_bytes from actual file metadata in content.add - Filter out availability:nobody items from the public catalog endpoint Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8c3c2104b2
commit
fc8e3f2d39
@ -348,10 +348,11 @@ impl ApiHandler {
|
||||
async fn handle_content_catalog(config: &Config) -> Result<Response<hyper::Body>> {
|
||||
match content_server::load_catalog(&config.data_dir).await {
|
||||
Ok(catalog) => {
|
||||
// Only expose public metadata, not file paths
|
||||
// Only expose public metadata for available items
|
||||
let items: Vec<serde_json::Value> = catalog
|
||||
.items
|
||||
.iter()
|
||||
.filter(|i| !matches!(i.availability, content_server::Availability::Nobody))
|
||||
.map(|i| {
|
||||
serde_json::json!({
|
||||
"id": i.id,
|
||||
|
||||
@ -31,7 +31,7 @@ impl RpcHandler {
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
|
||||
let item = ContentItem {
|
||||
let mut item = ContentItem {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
filename: filename.to_string(),
|
||||
mime_type: mime_type.to_string(),
|
||||
@ -42,6 +42,12 @@ impl RpcHandler {
|
||||
added_at: chrono::Utc::now().to_rfc3339(),
|
||||
};
|
||||
|
||||
// Resolve actual file size from disk
|
||||
let file_path = content_server::content_file_path(&self.config.data_dir, &item);
|
||||
if let Ok(metadata) = std::fs::metadata(&file_path) {
|
||||
item.size_bytes = metadata.len();
|
||||
}
|
||||
|
||||
content_server::add_item(&self.config.data_dir, item.clone()).await?;
|
||||
Ok(serde_json::json!({ "item": item }))
|
||||
}
|
||||
|
||||
@ -89,8 +89,26 @@ pub async fn save_catalog(data_dir: &Path, catalog: &ContentCatalog) -> Result<(
|
||||
}
|
||||
|
||||
/// Get the full filesystem path for a content item.
|
||||
/// Checks the dedicated content/files/ directory first, then falls back to the
|
||||
/// FileBrowser data directory (where users manage files via the web UI).
|
||||
pub fn content_file_path(data_dir: &Path, item: &ContentItem) -> PathBuf {
|
||||
data_dir.join(CONTENT_DIR).join(&item.filename)
|
||||
// Strip leading slash from filename for path joining
|
||||
let clean_name = item.filename.trim_start_matches('/');
|
||||
|
||||
// Primary: dedicated content directory
|
||||
let primary = data_dir.join(CONTENT_DIR).join(clean_name);
|
||||
if primary.exists() {
|
||||
return primary;
|
||||
}
|
||||
|
||||
// Fallback: FileBrowser data directory (users share files managed via FileBrowser)
|
||||
let fb_path = data_dir.join("filebrowser").join(clean_name);
|
||||
if fb_path.exists() {
|
||||
return fb_path;
|
||||
}
|
||||
|
||||
// Return primary path even if it doesn't exist (caller checks existence)
|
||||
primary
|
||||
}
|
||||
|
||||
/// Add a content item to the catalog.
|
||||
|
||||
@ -138,6 +138,22 @@ server {
|
||||
# CORS handled by backend
|
||||
}
|
||||
|
||||
# Content sharing — peer access over Tor (no auth)
|
||||
location /content {
|
||||
proxy_pass http://127.0.0.1:5678;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# DWN endpoints — peer access over Tor (no auth)
|
||||
location /dwn {
|
||||
proxy_pass http://127.0.0.1:5678;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# Proxy apps that set X-Frame-Options - strip header so iframe works
|
||||
location /app/nextcloud/ {
|
||||
proxy_pass http://127.0.0.1:8085/;
|
||||
@ -672,6 +688,22 @@ server {
|
||||
# CORS handled by backend
|
||||
}
|
||||
|
||||
# Content sharing — peer access over Tor (no auth)
|
||||
location /content {
|
||||
proxy_pass http://127.0.0.1:5678;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# DWN endpoints — peer access over Tor (no auth)
|
||||
location /dwn {
|
||||
proxy_pass http://127.0.0.1:5678;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
location /rpc/ {
|
||||
proxy_pass http://127.0.0.1:5678;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
@ -512,7 +512,7 @@
|
||||
|
||||
### Sprint 44: File Sharing Across Nodes (June 2026 Week 1-2)
|
||||
|
||||
- [ ] **SHARE-01** — Test content sharing between two federated nodes. On node A (192.168.1.228): upload a test file to FileBrowser, then call `content.add` with the filename to share it. Call `content.set-pricing` with `access: "free"`. Call `content.set-availability` with `availability: "all_peers"`. On node B (192.168.1.198): call `content.browse-peer` with node A's onion address. Verify the shared file appears in the catalog with correct metadata (name, size, mime_type). Download the file via the content server's HTTP endpoint over Tor. Compare checksums. **Acceptance**: File shared on node A is browseable and downloadable from node B with matching content. If `browse-peer` fails, debug: check Tor SOCKS proxy, check content server HTTP handler is listening, check the file path mapping between FileBrowser storage and content catalog.
|
||||
- [x] **SHARE-01** — Test content sharing between two federated nodes. On node A (192.168.1.228): upload a test file to FileBrowser, then call `content.add` with the filename to share it. Call `content.set-pricing` with `access: "free"`. Call `content.set-availability` with `availability: "all_peers"`. On node B (192.168.1.198): call `content.browse-peer` with node A's onion address. Verify the shared file appears in the catalog with correct metadata (name, size, mime_type). Download the file via the content server's HTTP endpoint over Tor. Compare checksums. **Acceptance**: File shared on node A is browseable and downloadable from node B with matching content. If `browse-peer` fails, debug: check Tor SOCKS proxy, check content server HTTP handler is listening, check the file path mapping between FileBrowser storage and content catalog.
|
||||
|
||||
- [ ] **SHARE-02** — Test access control modes. On node A, share 3 files: one `free`, one `peers_only`, one `paid` (price: 100 sats). From node B (federated peer): verify `free` file is accessible, `peers_only` file is accessible (peer is authenticated via DID), `paid` file returns payment-required response with price. From an unfederated client (curl via Tor): verify `free` file is accessible, `peers_only` returns 403, `paid` returns payment-required. Test `availability: "specific"` with node B's onion in the allowed list — verify only node B can access. **Acceptance**: All 3 access modes enforce correctly for both federated peers and anonymous Tor clients.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user