fix: add server streaming support, fix pre-existing compilation errors

Server changes for client mode streaming:
- POST /api/item/ now streams body via async channel → ChannelReader
  → save_item_raw_streaming when compress=false or meta=false
- Add POST /api/item/{id}/meta endpoint for client-side metadata
- Add save_item_raw_streaming<R: Read> to SyncDataService
- Add add_item_meta to AsyncDataService

Fix pre-existing issues that were hidden behind swagger cfg gate:
- Remove #[cfg(feature = "swagger")] from item module so it compiles
  with just the server feature
- Fix parse_comma_tags usage (returns Vec, not Result)
- Fix TextDiff temporary value lifetime issue
- Fix io::Error::new → io::Error::other
- Fix ok_or_else → ok_or for Copy types
- Inline format args throughout server code
- Fix empty line after doc comment in pages.rs
- Add cfg_attr for unused_mut where mcp feature gates mutation
- Add type_complexity allow on create_auth_middleware
- Distinguish task error vs save error in spawn_blocking handlers

Co-Authored-By: andrew/openrouter/hunter-alpha <noreply@opencode.ai>
This commit is contained in:
2026-03-12 18:02:56 -03:00
parent c5529bedbf
commit 237a581429
9 changed files with 531 additions and 112 deletions

View File

@@ -47,12 +47,6 @@ fn default_count() -> usize {
1000
}
/// Provides the default number of items to display per page.
///
/// # Returns
///
/// The default count: 1000.
/// Adds the web page routes to the Axum router.
///
/// This function configures the routes for the web interface, including the
@@ -96,7 +90,7 @@ async fn list_items(
.map_err(|_| Html("<html><body>Internal Server Error</body></html>".to_string()))?;
Ok(response)
}
Err(e) => Err(Html(format!("<html><body>Error: {}</body></html>", e))),
Err(e) => Err(Html(format!("<html><body>Error: {e}</body></html>"))),
}
}
@@ -190,8 +184,7 @@ fn build_item_list(
html.push_str("<p>");
for tag in recent_tags {
html.push_str(&format!(
"<a href=\"/?tags={}\" style=\"margin-right: 8px;\">{}</a>",
tag, tag
"<a href=\"/?tags={tag}\" style=\"margin-right: 8px;\">{tag}</a>"
));
}
html.push_str("</p>");
@@ -228,7 +221,7 @@ fn build_item_list(
"id" => {
let id_value = item.id.map(|id| id.to_string()).unwrap_or_default();
// Make the ID a link to the item details page
format!("<a href=\"/item/{}\">{}</a>", item_id, id_value)
format!("<a href=\"/item/{item_id}\">{id_value}</a>")
}
"time" => item.ts.format("%Y-%m-%d %H:%M:%S").to_string(),
"size" => item.size.map(|s| s.to_string()).unwrap_or_default(),
@@ -257,7 +250,7 @@ fn build_item_list(
if let Ok(max_len) = max_len_str.parse::<usize>() {
if value.chars().count() > max_len {
let truncated: String = value.chars().take(max_len).collect();
format!("{}...", truncated)
format!("{truncated}...")
} else {
value
}
@@ -275,16 +268,12 @@ fn build_item_list(
crate::config::ColumnAlignment::Center => "text-align: center;",
};
html.push_str(&format!(
"<td style=\"{}\">{}</td>",
align_style, display_value
));
html.push_str(&format!("<td style=\"{align_style}\">{display_value}</td>"));
}
// Actions column
html.push_str(&format!(
"<td><a href=\"/item/{}\">View</a> | <a href=\"/api/item/{}/content\">Download</a></td>",
item_id, item_id
"<td><a href=\"/item/{item_id}\">View</a> | <a href=\"/api/item/{item_id}/content\">Download</a></td>"
));
html.push_str("</tr>");
@@ -372,7 +361,7 @@ async fn show_item(
.map_err(|_| Html("<html><body>Internal Server Error</body></html>".to_string()))?;
Ok(response)
}
Err(e) => Err(Html(format!("<html><body>Error: {}</body></html>", e))),
Err(e) => Err(Html(format!("<html><body>Error: {e}</body></html>"))),
}
}
@@ -386,10 +375,10 @@ fn build_item_details(conn: &Connection, id: i64) -> Result<String> {
let metas = db::get_item_meta(conn, &item)?;
let mut html = String::new();
html.push_str(&format!("<html><head><title>Keep - Item #{}</title>", id));
html.push_str(&format!("<html><head><title>Keep - Item #{id}</title>"));
html.push_str("<link rel=\"stylesheet\" href=\"/style.css\">");
html.push_str("</head><body>");
html.push_str(&format!("<h1>Item #{}</h1>", id));
html.push_str(&format!("<h1>Item #{id}</h1>"));
// Single table for all details
html.push_str("<table>");
@@ -439,8 +428,7 @@ fn build_item_details(conn: &Connection, id: i64) -> Result<String> {
// Links
html.push_str("<h2>Actions</h2>");
html.push_str(&format!(
"<p><a href=\"/api/item/{}/content\">Download Content</a></p>",
id
"<p><a href=\"/api/item/{id}/content\">Download Content</a></p>"
));
html.push_str("<p><a href=\"/\">Back to list</a></p>");