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(
|
||||
&self,
|
||||
ids: &[i64],
|
||||
tags: &[String],
|
||||
order: &str,
|
||||
start: u64,
|
||||
@@ -268,6 +269,15 @@ impl KeepClient {
|
||||
params.push(("order".to_string(), order.to_string()));
|
||||
params.push(("start".to_string(), start.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() {
|
||||
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 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() {
|
||||
debug!("MAIN: Parsed value: {v:?}");
|
||||
match v.clone() {
|
||||
@@ -90,15 +90,15 @@ fn main() -> Result<(), Error> {
|
||||
ids.push(num)
|
||||
}
|
||||
NumberOrString::Str(str) => {
|
||||
// For --info, --get, and --export, try to parse strings as numbers to treat them as IDs
|
||||
if (args.mode.info || args.mode.get || args.mode.export)
|
||||
// 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 || args.mode.list)
|
||||
&& let Ok(num) = str.parse::<i64>()
|
||||
{
|
||||
debug!("MAIN: Adding parsed string to ids: {num}");
|
||||
ids.push(num);
|
||||
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}");
|
||||
tags.push(str)
|
||||
}
|
||||
@@ -256,7 +256,7 @@ fn main() -> Result<(), Error> {
|
||||
filter_chain,
|
||||
),
|
||||
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 => {
|
||||
keep::modes::client::delete::mode(&client, &mut cmd, &settings, ids)
|
||||
|
||||
@@ -10,6 +10,7 @@ pub fn mode(
|
||||
client: &KeepClient,
|
||||
_cmd: &mut Command,
|
||||
settings: &crate::config::Settings,
|
||||
ids: &[i64],
|
||||
tags: &[String],
|
||||
) -> Result<(), anyhow::Error> {
|
||||
debug!("CLIENT_LIST: Listing items via remote server");
|
||||
@@ -19,7 +20,7 @@ pub fn mode(
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.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 {
|
||||
for item in &items {
|
||||
|
||||
@@ -673,13 +673,13 @@ pub fn resolve_item_id(
|
||||
if !ids.is_empty() {
|
||||
Ok(ids[0])
|
||||
} 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() {
|
||||
return Err(anyhow!("No items found matching tags: {:?}", tags));
|
||||
}
|
||||
Ok(items[0].id)
|
||||
} 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() {
|
||||
return Err(anyhow!("No items found"));
|
||||
}
|
||||
@@ -696,13 +696,13 @@ pub fn resolve_item_ids(
|
||||
if !ids.is_empty() {
|
||||
Ok(ids.to_vec())
|
||||
} 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() {
|
||||
return Err(anyhow!("No items found matching tags: {:?}", tags));
|
||||
}
|
||||
Ok(items.into_iter().map(|i| i.id).collect())
|
||||
} 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() {
|
||||
return Err(anyhow!("No items found"));
|
||||
}
|
||||
|
||||
@@ -81,28 +81,20 @@ struct ListItem {
|
||||
///
|
||||
/// * `Result<()>` - Success or error if listing fails.
|
||||
pub fn mode_list(
|
||||
cmd: &mut clap::Command,
|
||||
_cmd: &mut clap::Command,
|
||||
settings: &config::Settings,
|
||||
ids: &mut [i64],
|
||||
tags: &[String],
|
||||
conn: &mut rusqlite::Connection,
|
||||
data_path: std::path::PathBuf,
|
||||
) -> 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 meta_filter: std::collections::HashMap<String, Option<String>> = settings
|
||||
.meta
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.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 {
|
||||
for item_with_meta in &items_with_meta {
|
||||
|
||||
@@ -114,6 +114,17 @@ pub async fn handle_list_items(
|
||||
State(state): State<AppState>,
|
||||
Query(params): Query<ListItemsQuery>,
|
||||
) -> 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
|
||||
.tags
|
||||
.as_ref()
|
||||
@@ -136,7 +147,7 @@ pub async fn handle_list_items(
|
||||
let mut items_with_meta = task::spawn_blocking(move || {
|
||||
let conn = db.blocking_lock();
|
||||
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
|
||||
.map_err(|e| {
|
||||
|
||||
@@ -459,6 +459,10 @@ pub struct TagsQuery {
|
||||
/// ```
|
||||
#[derive(Debug, Deserialize)]
|
||||
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.
|
||||
///
|
||||
/// String containing tags to filter the item list.
|
||||
|
||||
Reference in New Issue
Block a user