feat: allow --list to accept item IDs for filtering
- Local and client/server modes now support ID-based filtering - keep -l 1 2 3 lists specific items by ID - keep -l --ids-only 1 2 3 outputs just those IDs - Server API adds optional 'ids' query parameter to GET /api/item/ - KeepClient.list_items gains ids parameter
This commit is contained in:
@@ -253,6 +253,7 @@ impl KeepClient {
|
|||||||
|
|
||||||
pub fn list_items(
|
pub fn list_items(
|
||||||
&self,
|
&self,
|
||||||
|
ids: &[i64],
|
||||||
tags: &[String],
|
tags: &[String],
|
||||||
order: &str,
|
order: &str,
|
||||||
start: u64,
|
start: u64,
|
||||||
@@ -268,6 +269,15 @@ impl KeepClient {
|
|||||||
params.push(("order".to_string(), order.to_string()));
|
params.push(("order".to_string(), order.to_string()));
|
||||||
params.push(("start".to_string(), start.to_string()));
|
params.push(("start".to_string(), start.to_string()));
|
||||||
params.push(("count".to_string(), count.to_string()));
|
params.push(("count".to_string(), count.to_string()));
|
||||||
|
if !ids.is_empty() {
|
||||||
|
params.push((
|
||||||
|
"ids".to_string(),
|
||||||
|
ids.iter()
|
||||||
|
.map(|i| i.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(","),
|
||||||
|
));
|
||||||
|
}
|
||||||
if !tags.is_empty() {
|
if !tags.is_empty() {
|
||||||
params.push(("tags".to_string(), tags.join(",")));
|
params.push(("tags".to_string(), tags.join(",")));
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/main.rs
10
src/main.rs
@@ -81,7 +81,7 @@ fn main() -> Result<(), Error> {
|
|||||||
let ids = &mut Vec::new();
|
let ids = &mut Vec::new();
|
||||||
let tags = &mut Vec::new();
|
let tags = &mut Vec::new();
|
||||||
|
|
||||||
// For --info, --get, and --export modes, treat numeric strings as IDs
|
// For --info, --get, --export, and --list modes, treat numeric strings as IDs
|
||||||
for v in args.ids_or_tags.iter() {
|
for v in args.ids_or_tags.iter() {
|
||||||
debug!("MAIN: Parsed value: {v:?}");
|
debug!("MAIN: Parsed value: {v:?}");
|
||||||
match v.clone() {
|
match v.clone() {
|
||||||
@@ -90,15 +90,15 @@ fn main() -> Result<(), Error> {
|
|||||||
ids.push(num)
|
ids.push(num)
|
||||||
}
|
}
|
||||||
NumberOrString::Str(str) => {
|
NumberOrString::Str(str) => {
|
||||||
// For --info, --get, and --export, try to parse strings as numbers to treat them as IDs
|
// For --info, --get, --export, and --list, try to parse strings as numbers to treat them as IDs
|
||||||
if (args.mode.info || args.mode.get || args.mode.export)
|
if (args.mode.info || args.mode.get || args.mode.export || args.mode.list)
|
||||||
&& let Ok(num) = str.parse::<i64>()
|
&& let Ok(num) = str.parse::<i64>()
|
||||||
{
|
{
|
||||||
debug!("MAIN: Adding parsed string to ids: {num}");
|
debug!("MAIN: Adding parsed string to ids: {num}");
|
||||||
ids.push(num);
|
ids.push(num);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If not a number, or not using --info/--get/--export, treat as tag
|
// If not a number, or not using --info/--get/--export/--list, treat as tag
|
||||||
debug!("MAIN: Adding to tags: {str}");
|
debug!("MAIN: Adding to tags: {str}");
|
||||||
tags.push(str)
|
tags.push(str)
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ fn main() -> Result<(), Error> {
|
|||||||
filter_chain,
|
filter_chain,
|
||||||
),
|
),
|
||||||
KeepModes::List => {
|
KeepModes::List => {
|
||||||
keep::modes::client::list::mode(&client, &mut cmd, &settings, tags)
|
keep::modes::client::list::mode(&client, &mut cmd, &settings, ids, tags)
|
||||||
}
|
}
|
||||||
KeepModes::Delete => {
|
KeepModes::Delete => {
|
||||||
keep::modes::client::delete::mode(&client, &mut cmd, &settings, ids)
|
keep::modes::client::delete::mode(&client, &mut cmd, &settings, ids)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub fn mode(
|
|||||||
client: &KeepClient,
|
client: &KeepClient,
|
||||||
_cmd: &mut Command,
|
_cmd: &mut Command,
|
||||||
settings: &crate::config::Settings,
|
settings: &crate::config::Settings,
|
||||||
|
ids: &[i64],
|
||||||
tags: &[String],
|
tags: &[String],
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
debug!("CLIENT_LIST: Listing items via remote server");
|
debug!("CLIENT_LIST: Listing items via remote server");
|
||||||
@@ -19,7 +20,7 @@ pub fn mode(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
let items = client.list_items(tags, "newest", 0, 100, &meta_filter)?;
|
let items = client.list_items(ids, tags, "newest", 0, 100, &meta_filter)?;
|
||||||
|
|
||||||
if settings.ids_only {
|
if settings.ids_only {
|
||||||
for item in &items {
|
for item in &items {
|
||||||
|
|||||||
@@ -673,13 +673,13 @@ pub fn resolve_item_id(
|
|||||||
if !ids.is_empty() {
|
if !ids.is_empty() {
|
||||||
Ok(ids[0])
|
Ok(ids[0])
|
||||||
} else if !tags.is_empty() {
|
} else if !tags.is_empty() {
|
||||||
let items = client.list_items(tags, "newest", 0, 1, &HashMap::new())?;
|
let items = client.list_items(&[], tags, "newest", 0, 1, &HashMap::new())?;
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
return Err(anyhow!("No items found matching tags: {:?}", tags));
|
return Err(anyhow!("No items found matching tags: {:?}", tags));
|
||||||
}
|
}
|
||||||
Ok(items[0].id)
|
Ok(items[0].id)
|
||||||
} else {
|
} else {
|
||||||
let items = client.list_items(&[], "newest", 0, 1, &HashMap::new())?;
|
let items = client.list_items(&[], &[], "newest", 0, 1, &HashMap::new())?;
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
return Err(anyhow!("No items found"));
|
return Err(anyhow!("No items found"));
|
||||||
}
|
}
|
||||||
@@ -696,13 +696,13 @@ pub fn resolve_item_ids(
|
|||||||
if !ids.is_empty() {
|
if !ids.is_empty() {
|
||||||
Ok(ids.to_vec())
|
Ok(ids.to_vec())
|
||||||
} else if !tags.is_empty() {
|
} else if !tags.is_empty() {
|
||||||
let items = client.list_items(tags, "newest", 0, 0, &HashMap::new())?;
|
let items = client.list_items(&[], tags, "newest", 0, 0, &HashMap::new())?;
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
return Err(anyhow!("No items found matching tags: {:?}", tags));
|
return Err(anyhow!("No items found matching tags: {:?}", tags));
|
||||||
}
|
}
|
||||||
Ok(items.into_iter().map(|i| i.id).collect())
|
Ok(items.into_iter().map(|i| i.id).collect())
|
||||||
} else {
|
} else {
|
||||||
let items = client.list_items(&[], "newest", 0, 1, &HashMap::new())?;
|
let items = client.list_items(&[], &[], "newest", 0, 1, &HashMap::new())?;
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
return Err(anyhow!("No items found"));
|
return Err(anyhow!("No items found"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,28 +81,20 @@ struct ListItem {
|
|||||||
///
|
///
|
||||||
/// * `Result<()>` - Success or error if listing fails.
|
/// * `Result<()>` - Success or error if listing fails.
|
||||||
pub fn mode_list(
|
pub fn mode_list(
|
||||||
cmd: &mut clap::Command,
|
_cmd: &mut clap::Command,
|
||||||
settings: &config::Settings,
|
settings: &config::Settings,
|
||||||
ids: &mut [i64],
|
ids: &mut [i64],
|
||||||
tags: &[String],
|
tags: &[String],
|
||||||
conn: &mut rusqlite::Connection,
|
conn: &mut rusqlite::Connection,
|
||||||
data_path: std::path::PathBuf,
|
data_path: std::path::PathBuf,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if !ids.is_empty() {
|
|
||||||
cmd.error(
|
|
||||||
clap::error::ErrorKind::InvalidValue,
|
|
||||||
"ID given, you can only supply tags when using --list",
|
|
||||||
)
|
|
||||||
.exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
let item_service = ItemService::new(data_path.clone());
|
let item_service = ItemService::new(data_path.clone());
|
||||||
let meta_filter: std::collections::HashMap<String, Option<String>> = settings
|
let meta_filter: std::collections::HashMap<String, Option<String>> = settings
|
||||||
.meta
|
.meta
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
let items_with_meta = item_service.list_items(conn, tags, &meta_filter)?;
|
let items_with_meta = item_service.get_items(conn, ids, tags, &meta_filter)?;
|
||||||
|
|
||||||
if settings.ids_only {
|
if settings.ids_only {
|
||||||
for item_with_meta in &items_with_meta {
|
for item_with_meta in &items_with_meta {
|
||||||
|
|||||||
@@ -114,6 +114,17 @@ pub async fn handle_list_items(
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Query(params): Query<ListItemsQuery>,
|
Query(params): Query<ListItemsQuery>,
|
||||||
) -> Result<Response, StatusCode> {
|
) -> Result<Response, StatusCode> {
|
||||||
|
// Parse IDs from query parameter
|
||||||
|
let ids: Vec<i64> = params
|
||||||
|
.ids
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| {
|
||||||
|
s.split(',')
|
||||||
|
.filter_map(|id| id.trim().parse::<i64>().ok())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let tags: Vec<String> = params
|
let tags: Vec<String> = params
|
||||||
.tags
|
.tags
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -136,7 +147,7 @@ pub async fn handle_list_items(
|
|||||||
let mut items_with_meta = task::spawn_blocking(move || {
|
let mut items_with_meta = task::spawn_blocking(move || {
|
||||||
let conn = db.blocking_lock();
|
let conn = db.blocking_lock();
|
||||||
let item_service = ItemService::new(data_dir);
|
let item_service = ItemService::new(data_dir);
|
||||||
item_service.list_items(&conn, &tags, &meta_filter)
|
item_service.get_items(&conn, &ids, &tags, &meta_filter)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
|||||||
@@ -459,6 +459,10 @@ pub struct TagsQuery {
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct ListItemsQuery {
|
pub struct ListItemsQuery {
|
||||||
|
/// Optional comma-separated item IDs for filtering.
|
||||||
|
///
|
||||||
|
/// String containing numeric IDs to filter the item list.
|
||||||
|
pub ids: Option<String>,
|
||||||
/// Optional comma-separated tags for filtering.
|
/// Optional comma-separated tags for filtering.
|
||||||
///
|
///
|
||||||
/// String containing tags to filter the item list.
|
/// String containing tags to filter the item list.
|
||||||
|
|||||||
Reference in New Issue
Block a user