Skip to content

Commit

Permalink
- Fix search in CLI / atomic_lib #958
Browse files Browse the repository at this point in the history
  • Loading branch information
joepio committed Feb 18, 2025
1 parent 0950cf7 commit bf4f890
Show file tree
Hide file tree
Showing 22 changed files with 164 additions and 121 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain
## UNRELEASED

- CLI should use Agent in requests - get #986
- Search endpoint throws error for websocket requests #1047
- Fix search in CLI / atomic_lib #958

## [v0.40.2]

Expand Down
16 changes: 16 additions & 0 deletions cli/src/get.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::{print::print_resource, Context, SerializeOptions};
use atomic_lib::{errors::AtomicResult, Storelike};

pub fn get_resource(
context: &mut Context,
subject: &str,
serialize: &SerializeOptions,
) -> AtomicResult<()> {
context.read_config();

let store = &mut context.store;
let fetched = store.fetch_resource(subject, store.get_default_agent().ok().as_ref())?;
print_resource(context, &fetched, serialize)?;

Ok(())
}
25 changes: 17 additions & 8 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use dirs::home_dir;
use std::{cell::RefCell, path::PathBuf, sync::Mutex};

mod commit;
mod get;
mod new;
mod path;
mod print;
mod search;

Expand Down Expand Up @@ -45,9 +45,9 @@ enum Commands {
Visit https://docs.atomicdata.dev/core/paths.html for more info about paths. \
")]
Get {
/// The subject URL, shortname or path to be fetched
#[arg(required = true, num_args = 1..)]
path: Vec<String>,
/// The subject URL
#[arg(required = true)]
subject: String,

/// Serialization format
#[arg(long, value_enum, default_value = "pretty")]
Expand Down Expand Up @@ -98,6 +98,12 @@ enum Commands {
/// The search query
#[arg(required = true)]
query: String,
/// Subject URL of the parent Resource to filter by
#[arg(long)]
parent: Option<String>,
/// Serialization format
#[arg(long, value_enum, default_value = "pretty")]
as_: SerializeOptions,
},
/// List all bookmarks
List,
Expand All @@ -112,6 +118,7 @@ enum Commands {
pub enum SerializeOptions {
Pretty,
Json,
JsonAd,
NTriples,
}

Expand All @@ -120,6 +127,7 @@ impl Into<Format> for SerializeOptions {
match self {
SerializeOptions::Pretty => Format::Pretty,
SerializeOptions::Json => Format::Json,
SerializeOptions::JsonAd => Format::JsonAd,
SerializeOptions::NTriples => Format::NTriples,
}
}
Expand Down Expand Up @@ -153,6 +161,7 @@ impl Context {
name: None,
public_key: generate_public_key(&write_ctx.private_key).public,
});
self.store.set_server_url(&write_ctx.server);
write_ctx
}
}
Expand Down Expand Up @@ -240,8 +249,8 @@ fn exec_command(context: &mut Context) -> AtomicResult<()> {
return Err("Feature not available. Compile with `native` feature.".into());
}
}
Commands::Get { path, as_ } => {
path::get_path(context, &path, &as_)?;
Commands::Get { subject, as_ } => {
get::get_resource(context, &subject, &as_)?;
}
Commands::List => {
list(context);
Expand All @@ -259,8 +268,8 @@ fn exec_command(context: &mut Context) -> AtomicResult<()> {
} => {
commit::set(context, &subject, &property, &value)?;
}
Commands::Search { query } => {
search::search(context, query)?;
Commands::Search { query, parent, as_ } => {
search::search(context, query, parent, &as_)?;
}
Commands::Validate => {
validate(context);
Expand Down
38 changes: 0 additions & 38 deletions cli/src/path.rs

This file was deleted.

34 changes: 16 additions & 18 deletions cli/src/search.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
use atomic_lib::{errors::AtomicResult, urls, Storelike};
use atomic_lib::{errors::AtomicResult, Storelike};

pub fn search(context: &crate::Context, query: String) -> AtomicResult<()> {
use crate::print::print_resource;

pub fn search(
context: &crate::Context,
query: String,
parent: Option<String>,
serialize: &crate::SerializeOptions,
) -> AtomicResult<()> {
context.read_config();
let opts = atomic_lib::client::search::SearchOpts {
limit: Some(10),
include: Some(true),
parents: Some(vec![parent.unwrap_or_default()]),
..Default::default()
};
let subject = atomic_lib::client::search::build_search_subject(
&context.read_config().server,
&query,
opts,
);
let resource = context.store.get_resource(&subject)?;
let members = resource
.get(urls::ENDPOINT_RESULTS)
.expect("No members?")
.to_subjects(None)
.unwrap();
if members.is_empty() {
println!("No results found.");
println!("URL: {}", subject);
let resources = context.store.search(&query, opts)?;
if resources.is_empty() {
println!("No results found for query: {}", query);
return Ok(());
} else {
for member in members {
println!("{}", member);
for member in resources {
print_resource(context, &member, serialize)?;
}
}
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion lib/defaults/default_store.json
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@
},
{
"@id": "https://atomicdata.dev/properties/published-at",
"https://atomicdata.dev/properties/datatype": "https://atomicdata.dev/datatypes/timestamp",
"https://atomicdata.dev/properties/datatype": "https://atomicdata.dev/datatypes/date",
"https://atomicdata.dev/properties/description": "DateTime at which an item is made public.",
"https://atomicdata.dev/properties/isA": [
"https://atomicdata.dev/classes/Property"
Expand Down
12 changes: 6 additions & 6 deletions lib/src/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl Agent {
pub fn new(name: Option<&str>, store: &impl Storelike) -> AtomicResult<Agent> {
let keypair = generate_keypair()?;

Ok(Agent::new_from_private_key(name, store, &keypair.private))
Agent::new_from_private_key(name, store, &keypair.private)
}

/// Creates a new Agent on this server, using the server's Server URL.
Expand All @@ -94,16 +94,16 @@ impl Agent {
name: Option<&str>,
store: &impl Storelike,
private_key: &str,
) -> Agent {
) -> AtomicResult<Agent> {
let keypair = generate_public_key(private_key);

Agent {
Ok(Agent {
private_key: Some(keypair.private),
public_key: keypair.public.clone(),
subject: format!("{}/agents/{}", store.get_server_url(), keypair.public),
subject: format!("{}/agents/{}", store.get_server_url()?, keypair.public),
name: name.map(|x| x.to_owned()),
created_at: crate::utils::now(),
}
})
}

/// Creates a new Agent on this server, using the server's Server URL.
Expand All @@ -114,7 +114,7 @@ impl Agent {
Ok(Agent {
private_key: None,
public_key: public_key.into(),
subject: format!("{}/agents/{}", store.get_server_url(), public_key),
subject: format!("{}/agents/{}", store.get_server_url()?, public_key),
name: None,
created_at: crate::utils::now(),
})
Expand Down
4 changes: 2 additions & 2 deletions lib/src/client/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ pub fn fetch_body(
.into_string()
.unwrap_or_else(|_| "<failed to read response body>".to_string());
return Err(format!(
"Error when server tried fetching {}: Status: {}. Body: {}",
"Error when fetching {}: Status: {}. Body: {}",
url, status, body
)
.into());
}
Err(e) => return Err(format!("Error when server tried fetching {}: {}", url, e).into()),
Err(e) => return Err(format!("Error when fetching {}: {}", url, e).into()),
};
let status = resp.status();
let body = resp
Expand Down
5 changes: 5 additions & 0 deletions lib/src/client/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ Use the `/search` endpoint from AtomicServer to perform full-text search.
use std::collections::HashMap;
use url::Url;

use crate::agents::Agent;

// Define the SearchOpts struct with optional fields
#[derive(Debug, Default)]
pub struct SearchOpts {
pub include: Option<bool>,
pub limit: Option<u32>,
pub parents: Option<Vec<String>>,
pub filters: Option<HashMap<String, String>>,
/// The agent to use for authentication
pub agent: Option<Agent>,
}

// Function to build the base URL for search
Expand Down Expand Up @@ -126,6 +130,7 @@ mod tests {
filters
}),
parents: Some(vec!["https://test.com/parent".to_string()]),
agent: None,
};
let expected_search_url = "https://test.com/search?q=test&include=true&limit=30&filters=age%3A%2210%22&parents=https%3A%2F%2Ftest.com%2Fparent";
assert_eq!(
Expand Down
12 changes: 6 additions & 6 deletions lib/src/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ impl CollectionBuilder {
class_url: &str,
path: &str,
store: &impl Storelike,
) -> CollectionBuilder {
CollectionBuilder {
subject: format!("{}/{}", store.get_server_url(), path),
) -> AtomicResult<CollectionBuilder> {
Ok(CollectionBuilder {
subject: format!("{}/{}", store.get_server_url()?, path),
property: Some(urls::IS_A.into()),
value: Some(class_url.into()),
sort_by: None,
Expand All @@ -98,7 +98,7 @@ impl CollectionBuilder {
name: Some(format!("{} collection", path)),
include_nested: true,
include_external: false,
}
})
}

/// Converts the CollectionBuilder into a collection, with Members
Expand Down Expand Up @@ -394,7 +394,7 @@ pub fn create_collection_resource_for_class(
other => format!("{}s", other),
};

let mut collection = CollectionBuilder::class_collection(&class.subject, &pluralized, store);
let mut collection = CollectionBuilder::class_collection(&class.subject, &pluralized, store)?;

collection.sort_by = match class_subject {
urls::COMMIT => Some(urls::CREATED_AT.to_string()),
Expand Down Expand Up @@ -524,7 +524,7 @@ mod test {
println!("{:?}", subjects);
let collections_collection = store
.get_resource_extended(
&format!("{}/collections", store.get_server_url()),
&format!("{}/collections", store.get_server_url().unwrap()),
false,
&ForAgent::Public,
)
Expand Down
4 changes: 2 additions & 2 deletions lib/src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,10 @@ impl Commit {
#[tracing::instrument(skip(store))]
pub fn into_resource(&self, store: &impl Storelike) -> AtomicResult<Resource> {
let commit_subject = match self.signature.as_ref() {
Some(sig) => format!("{}/commits/{}", store.get_server_url(), sig),
Some(sig) => format!("{}/commits/{}", store.get_server_url()?, sig),
None => {
let now = crate::utils::now();
format!("{}/commitsUnsigned/{}", store.get_server_url(), now)
format!("{}/commitsUnsigned/{}", store.get_server_url()?, now)
}
};
let mut resource = Resource::new_instance(urls::COMMIT, store)?;
Expand Down
6 changes: 3 additions & 3 deletions lib/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ pub fn parse_json_ad_commit_resource(
.get(urls::SUBJECT)
.ok_or("No subject field in Commit.")?
.to_string();
let subject = format!("{}/commits/{}", store.get_server_url(), signature);
let subject = format!("{}/commits/{}", store.get_server_url()?, signature);
let mut resource = Resource::new(subject);
let propvals = match parse_json_ad_map_to_resource(json, store, &ParseOpts::default())? {
SubResource::Resource(r) => r.into_propvals(),
Expand Down Expand Up @@ -303,7 +303,7 @@ fn parse_json_ad_map_to_resource(
}
serde_json::Value::String(str) => {
// LocalIDs are mapped to @ids by appending the `localId` to the `importer`'s `parent`.
if prop == urls::LOCAL_ID {
if prop == urls::LOCAL_ID && parse_opts.importer.is_some() {
let parent = parse_opts.importer.as_ref()
.ok_or_else(|| AtomicError::parse_error(
"Encountered `localId`, which means we need a `parent` in the parsing options.",
Expand Down Expand Up @@ -672,7 +672,7 @@ mod test {

// Try to overwrite the main drive with some malicious data
let agent = store.get_default_agent().unwrap();
let mut resource = Resource::new_generate_subject(&store);
let mut resource = Resource::new_generate_subject(&store).unwrap();
resource
.set(
urls::WRITE.into(),
Expand Down
8 changes: 2 additions & 6 deletions lib/src/plugins/importer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Importers allow users to (periodically) import JSON-AD files from a remote sourc

use crate::{
agents::ForAgent,
endpoints::{Endpoint, HandleGetContext, HandlePostContext},
endpoints::{Endpoint, HandlePostContext},
errors::AtomicResult,
urls, Resource, Storelike,
};
Expand All @@ -20,15 +20,11 @@ pub fn import_endpoint() -> Endpoint {
description: "Imports one or more Resources to some parent. POST your JSON-AD and add a `parent` query param to the URL. See https://docs.atomicdata.dev/create-json-ad.html".to_string(),
shortname: "path".to_string(),
// Not sure if we need this, or if we should derive it from `None` here.
handle: Some(handle_get),
handle: None,
handle_post: Some(handle_post),
}
}

pub fn handle_get(context: HandleGetContext) -> AtomicResult<Resource> {
import_endpoint().to_resource(context.store)
}

/// When an importer is shown, we list a bunch of Parameters and a list of previously imported items.
#[tracing::instrument]
pub fn handle_post(context: HandlePostContext) -> AtomicResult<Resource> {
Expand Down
Loading

0 comments on commit bf4f890

Please sign in to comment.