Add name parameter

This commit is contained in:
2026-03-26 09:57:02 -07:00
parent 5b0953d250
commit f9a39d5ff9
3 changed files with 43 additions and 9 deletions

View File

@@ -20,6 +20,7 @@ pub struct ExtractQuery {
#[serde(default)] #[serde(default)]
download: bool, download: bool,
name: Option<String>,
} }
/// Extract a specific field from an item's metadata. /// Extract a specific field from an item's metadata.
@@ -31,6 +32,7 @@ pub struct ExtractQuery {
("source" = String, Query, description = "Source label"), ("source" = String, Query, description = "Source label"),
("key" = String, Query, description = "Item key"), ("key" = String, Query, description = "Item key"),
("path" = String, Query, description = "Object path (e.g. $.flac.title); repeat for fallbacks"), ("path" = String, Query, description = "Object path (e.g. $.flac.title); repeat for fallbacks"),
("name" = Option<String>, Query, description = "Downloaded filename; defaults to the last segment of the key"),
), ),
responses( responses(
(status = 200, description = "Field value as JSON"), (status = 200, description = "Field value as JSON"),
@@ -112,18 +114,27 @@ pub async fn get_extract(
time_ms = start.elapsed().as_millis() time_ms = start.elapsed().as_millis()
); );
let disposition = if params.download { let disposition_type = if params.download {
"attachment" "attachment"
} else { } else {
"inline" "inline"
}; };
let file_name = params.name.unwrap_or_else(|| {
params
.key
.rsplit('/')
.next()
.unwrap_or(&params.key)
.to_owned()
});
let disposition = format!("{disposition_type}; filename=\"{file_name}\"");
match value { match value {
PileValue::String(s) => ( PileValue::String(s) => (
StatusCode::OK, StatusCode::OK,
[ [
(header::CONTENT_TYPE, "text/plain".to_owned()), (header::CONTENT_TYPE, "text/plain".to_owned()),
(header::CONTENT_DISPOSITION, disposition.to_owned()), (header::CONTENT_DISPOSITION, disposition),
], ],
s.to_string(), s.to_string(),
) )
@@ -132,7 +143,7 @@ pub async fn get_extract(
StatusCode::OK, StatusCode::OK,
[ [
(header::CONTENT_TYPE, mime.to_string()), (header::CONTENT_TYPE, mime.to_string()),
(header::CONTENT_DISPOSITION, disposition.to_owned()), (header::CONTENT_DISPOSITION, disposition),
], ],
bytes.as_ref().clone(), bytes.as_ref().clone(),
) )
@@ -140,7 +151,7 @@ pub async fn get_extract(
_ => match value.to_json(&extract_state).await { _ => match value.to_json(&extract_state).await {
Ok(json) => ( Ok(json) => (
StatusCode::OK, StatusCode::OK,
[(header::CONTENT_DISPOSITION, disposition.to_owned())], [(header::CONTENT_DISPOSITION, disposition)],
Json(json), Json(json),
) )
.into_response(), .into_response(),

View File

@@ -21,6 +21,7 @@ pub struct FieldQuery {
#[serde(default)] #[serde(default)]
download: bool, download: bool,
name: Option<String>,
} }
/// Extract a specific field from an item's metadata. /// Extract a specific field from an item's metadata.
@@ -31,6 +32,7 @@ pub struct FieldQuery {
("source" = String, Query, description = "Source label"), ("source" = String, Query, description = "Source label"),
("key" = String, Query, description = "Item key"), ("key" = String, Query, description = "Item key"),
("field" = String, Query, description = "Schema field"), ("field" = String, Query, description = "Schema field"),
("name" = Option<String>, Query, description = "Downloaded filename; defaults to the last segment of the key"),
), ),
responses( responses(
(status = 200, description = "Field value as JSON"), (status = 200, description = "Field value as JSON"),
@@ -98,18 +100,27 @@ pub async fn get_field(
time_ms = start.elapsed().as_millis() time_ms = start.elapsed().as_millis()
); );
let disposition = if params.download { let disposition_type = if params.download {
"attachment" "attachment"
} else { } else {
"inline" "inline"
}; };
let file_name = params.name.unwrap_or_else(|| {
params
.key
.rsplit('/')
.next()
.unwrap_or(&params.key)
.to_owned()
});
let disposition = format!("{disposition_type}; filename=\"{file_name}\"");
match value { match value {
PileValue::String(s) => ( PileValue::String(s) => (
StatusCode::OK, StatusCode::OK,
[ [
(header::CONTENT_TYPE, "text/plain".to_owned()), (header::CONTENT_TYPE, "text/plain".to_owned()),
(header::CONTENT_DISPOSITION, disposition.to_owned()), (header::CONTENT_DISPOSITION, disposition),
], ],
s.to_string(), s.to_string(),
) )
@@ -118,7 +129,7 @@ pub async fn get_field(
StatusCode::OK, StatusCode::OK,
[ [
(header::CONTENT_TYPE, mime.to_string()), (header::CONTENT_TYPE, mime.to_string()),
(header::CONTENT_DISPOSITION, disposition.to_owned()), (header::CONTENT_DISPOSITION, disposition),
], ],
bytes.as_ref().clone(), bytes.as_ref().clone(),
) )
@@ -126,7 +137,7 @@ pub async fn get_field(
_ => match value.to_json(&extract_state).await { _ => match value.to_json(&extract_state).await {
Ok(json) => ( Ok(json) => (
StatusCode::OK, StatusCode::OK,
[(header::CONTENT_DISPOSITION, disposition.to_owned())], [(header::CONTENT_DISPOSITION, disposition)],
Json(json), Json(json),
) )
.into_response(), .into_response(),

View File

@@ -19,8 +19,10 @@ use crate::Datasets;
pub struct ItemQuery { pub struct ItemQuery {
source: String, source: String,
key: String, key: String,
#[serde(default)] #[serde(default)]
download: bool, download: bool,
name: Option<String>,
} }
/// Parse a `Range: bytes=...` header value. /// Parse a `Range: bytes=...` header value.
@@ -48,6 +50,7 @@ fn parse_byte_range(s: &str) -> Option<(Option<u64>, Option<u64>)> {
params( params(
("source" = String, Query, description = "Source label"), ("source" = String, Query, description = "Source label"),
("key" = String, Query, description = "Item key"), ("key" = String, Query, description = "Item key"),
("name" = Option<String>, Query, description = "Downloaded filename; defaults to the last segment of the key"),
), ),
responses( responses(
(status = 200, description = "Raw item bytes"), (status = 200, description = "Raw item bytes"),
@@ -163,11 +166,20 @@ pub async fn item_get(
StatusCode::OK StatusCode::OK
}; };
let disposition = if params.download { let disposition_type = if params.download {
"attachment" "attachment"
} else { } else {
"inline" "inline"
}; };
let file_name = params.name.unwrap_or_else(|| {
params
.key
.rsplit('/')
.next()
.unwrap_or(&params.key)
.to_owned()
});
let disposition = format!("{disposition_type}; filename=\"{file_name}\"");
let mut builder = axum::http::Response::builder() let mut builder = axum::http::Response::builder()
.status(status) .status(status)