Skip to content

Commit

Permalink
#288 #489 Register Endpoint and true URLs
Browse files Browse the repository at this point in the history
  • Loading branch information
joepio committed Jul 19, 2024
1 parent 087d7d4 commit 21e2c93
Show file tree
Hide file tree
Showing 18 changed files with 195 additions and 63 deletions.
1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ html2md = { version = "0.2.14", optional = true }
kuchikiki = { version = "0.8.2", optional = true }
lol_html = { version = "1", optional = true }
rand = { version = "0.8" }
lazy_static = "1"
regex = "1"
ring = "0.17.6"
rio_api = { version = "0.8", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion lib/src/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ pub fn create_collection_resource_for_class(

// Let the Collections collection be the top level item
let parent = if class.subject == urls::COLLECTION {
drive
drive.to_string()
} else {
format!("{}/collections", drive)
};
Expand Down
2 changes: 1 addition & 1 deletion lib/src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ impl Commit {
let default_parent = store.get_self_url().ok_or("There is no self_url set, and no parent in the Commit. The commit can not be applied.")?;
resource_old.set(
urls::PARENT.into(),
Value::AtomicUrl(default_parent),
Value::AtomicUrl(default_parent.to_string()),
store,
)?;
}
Expand Down
21 changes: 12 additions & 9 deletions lib/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use std::{
vec,
};

use tracing::{info, instrument};
use tracing::instrument;
use url::Url;

use crate::{
agents::ForAgent,
Expand Down Expand Up @@ -80,7 +81,7 @@ pub struct Db {
/// A list of all the Collections currently being used. Is used to update `query_index`.
watched_queries: sled::Tree,
/// The address where the db will be hosted, e.g. http://localhost/
server_url: String,
server_url: Url,
/// Endpoints are checked whenever a resource is requested. They calculate (some properties of) the resource and return it.
endpoints: Vec<Endpoint>,
/// Function called whenever a Commit is applied.
Expand Down Expand Up @@ -197,14 +198,15 @@ impl Db {
}

fn map_sled_item_to_resource(
&self,
item: Result<(sled::IVec, sled::IVec), sled::Error>,
self_url: String,
include_external: bool,
) -> Option<Resource> {
let (subject, resource_bin) = item.expect(DB_CORRUPT_MSG);
let subject: String = String::from_utf8_lossy(&subject).to_string();

if !include_external && !subject.starts_with(&self_url) {
if !include_external && self.is_external_subject(&subject).ok()? {
return None;
}

Expand Down Expand Up @@ -424,14 +426,14 @@ impl Storelike for Db {
Ok(())
}

fn get_server_url(&self) -> &str {
fn get_server_url(&self) -> &Url {
&self.server_url
}

// Since the DB is often also the server, this should make sense.
// Some edge cases might appear later on (e.g. a slave DB that only stores copies?)
fn get_self_url(&self) -> Option<String> {
Some(self.get_server_url().into())
fn get_self_url(&self) -> Option<&Url> {
// Since the DB is often also the server, this should make sense.
// Some edge cases might appear later on (e.g. a slave DB that only stores copies?)
Some(self.get_server_url())
}

fn get_default_agent(&self) -> AtomicResult<crate::agents::Agent> {
Expand Down Expand Up @@ -600,7 +602,7 @@ impl Storelike for Db {
.expect("No self URL set, is required in DB");

let result = self.resources.into_iter().filter_map(move |item| {
Db::map_sled_item_to_resource(item, self_url.clone(), include_external)
Db::map_sled_item_to_resource(self, item, self_url.to_string(), include_external)
});

Box::new(result)
Expand Down Expand Up @@ -654,6 +656,7 @@ impl Storelike for Db {

fn populate(&self) -> AtomicResult<()> {
crate::populate::populate_all(self)
crate::populate::create_drive(self, None, &default_agent.subject, true)
}

#[instrument(skip(self))]
Expand Down
2 changes: 1 addition & 1 deletion lib/src/db/query_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pub fn query_sorted_indexed(
let (_q_filter, _val, subject) = parse_collection_members_key(&k)?;

// If no external resources should be included, skip this one if it's an external resource
if !q.include_external && !subject.starts_with(&self_url) {
if !q.include_external && store.is_external_subject(subject)? {
continue;
}

Expand Down
1 change: 1 addition & 0 deletions lib/src/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub fn default_endpoints() -> Vec<Endpoint> {
plugins::path::path_endpoint(),
plugins::search::search_endpoint(),
plugins::files::upload_endpoint(),
plugins::register::register_endpoint(),
#[cfg(feature = "html")]
plugins::bookmark::bookmark_endpoint(),
plugins::importer::import_endpoint(),
Expand Down
1 change: 1 addition & 0 deletions lib/src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ pub mod files;
pub mod path;
pub mod prunetests;
pub mod query;
pub mod register;
pub mod search;
pub mod versioning;
73 changes: 73 additions & 0 deletions lib/src/plugins/register.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//! Creates a new Drive and optionally also an Agent.
use crate::{agents::Agent, endpoints::Endpoint, errors::AtomicResult, urls, Resource, Storelike};

pub fn register_endpoint() -> Endpoint {
Endpoint {
path: "/register".to_string(),
params: [
urls::INVITE_PUBKEY.to_string(),
urls::NAME.to_string(),
].into(),
description: "Allows new users to easily, in one request, make both an Agent and a Drive. This drive will be created at the subdomain of `name`.".to_string(),
shortname: "register".to_string(),
handle: Some(construct_register_redirect),
handle_post: None,
}
}

#[tracing::instrument(skip(store))]
pub fn construct_register_redirect(
url: url::Url,
store: &impl Storelike,
for_agent: Option<&str>,
) -> AtomicResult<Resource> {
let requested_subject = url.to_string();
let mut pub_key = None;
let mut name_option = None;
for (k, v) in url.query_pairs() {
match k.as_ref() {
"public-key" | urls::INVITE_PUBKEY => pub_key = Some(v.to_string()),
"name" | urls::NAME => name_option = Some(v.to_string()),
_ => {}
}
}
if pub_key.is_none() && name_option.is_none() {
return register_endpoint().to_resource(store);
}

let name = if let Some(n) = name_option {
n
} else {
return Err("No name provided".into());
};

let mut new_agent = None;

let drive_creator_agent: String = if let Some(key) = pub_key {
let new = Agent::new_from_public_key(store, &key)?.subject;
new_agent = Some(new.clone());
new
} else if let Some(agent) = for_agent {
agent.to_string()
} else {
return Err("No `public-key` provided".into());
};

// Create the new Drive
let drive = crate::populate::create_drive(store, Some(&name), &drive_creator_agent, false)?;

// Construct the Redirect Resource, which might provide the Client with a Subject for his Agent.
let mut redirect = Resource::new_instance(urls::REDIRECT, store)?;
redirect.set_string(urls::DESTINATION.into(), drive.get_subject(), store)?;
if let Some(agent) = new_agent {
redirect.set(
urls::REDIRECT_AGENT.into(),
crate::Value::AtomicUrl(agent),
store,
)?;
}
// The front-end requires the @id to be the same as requested
redirect.set_subject(requested_subject);
Ok(redirect)
}
56 changes: 44 additions & 12 deletions lib/src/populate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
parse::ParseOpts,
schema::{Class, Property},
storelike::Query,
urls, Storelike, Value,
urls, Resource, Storelike, Value,
};

const DEFAULT_ONTOLOGY_PATH: &str = "defaultOntology";
Expand Down Expand Up @@ -153,17 +153,43 @@ pub fn populate_base_models(store: &impl Storelike) -> AtomicResult<()> {
Ok(())
}

/// Creates a Drive resource at the base URL. Does not set rights. Use set_drive_rights for that.
pub fn create_drive(store: &impl Storelike) -> AtomicResult<()> {
let self_url = store
.get_self_url()
.ok_or("No self_url set, cannot populate store with Drive")?;
let mut drive = store.get_resource_new(&self_url);
/// Creates a Drive resource at the base URL if no name is passed.
#[tracing::instrument(skip(store), level = "info")]
pub fn create_drive(
store: &impl Storelike,
drive_name: Option<&str>,
for_agent: &str,
public_read: bool,
) -> AtomicResult<Resource> {
let mut self_url = if let Some(url) = store.get_self_url() {
url.to_owned()
} else {
return Err("No self URL set. Cannot create drive.".into());
};
let drive_subject: String = if let Some(name) = drive_name {
// Let's make a subdomain
let host = self_url.host().expect("No host in server_url");
let subdomain_host = format!("{}.{}", name, host);
self_url.set_host(Some(&subdomain_host))?;
self_url.to_string()
} else {
self_url.to_string()
};

let mut drive = if drive_name.is_some() {
if store.get_resource(&drive_subject).is_ok() {
return Err("Drive URL is already taken".into());
}
Resource::new(drive_subject)
} else {
// Only for the base URL (of no drive name is passed), we should not check if the drive exists.
// This is because we use `create_drive` in the `--initialize` command.
store.get_resource_new(&drive_subject)
};
drive.set_class(urls::DRIVE);
let server_url = url::Url::parse(store.get_server_url())?;
drive.set_string(
urls::NAME.into(),
server_url.host_str().ok_or("Can't use current base URL")?,
drive_name.unwrap_or_else(|| self_url.host_str().unwrap()),
store,
)?;
drive.save_locally(store)?;
Expand Down Expand Up @@ -236,8 +262,10 @@ To use the data in your web apps checkout our client libraries: [@tomic/lib](htt
Use [@tomic/cli](https://docs.atomicdata.dev/js-cli) to generate typed ontologies inside your code.
"#, store.get_server_url(), &format!("{}/{}", drive.get_subject(), DEFAULT_ONTOLOGY_PATH)), store)?;
}

drive.save_locally(store)?;
Ok(())

Ok(drive)
}

/// Imports the Atomic Data Core items (the entire atomicdata.dev Ontology / Vocabulary)
Expand Down Expand Up @@ -312,7 +340,11 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> {
.ok_or("No self URL in this Store - required for populating importer")?;
let mut importer = crate::Resource::new(urls::construct_path_import(&base));
importer.set_class(urls::IMPORTER);
importer.set(urls::PARENT.into(), Value::AtomicUrl(base), store)?;
importer.set(
urls::PARENT.into(),
Value::AtomicUrl(base.to_string()),
store,
)?;
importer.set(urls::NAME.into(), Value::String("Import".into()), store)?;
importer.save_locally(store)?;
Ok(())
Expand All @@ -323,7 +355,7 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> {
/// Useful for helping a new user get started.
pub fn populate_sidebar_items(store: &crate::Db) -> AtomicResult<()> {
let base = store.get_self_url().ok_or("No self_url")?;
let mut drive = store.get_resource(&base)?;
let mut drive = store.get_resource(base.as_str())?;
let arr = vec![
format!("{}/setup", base),
format!("{}/import", base),
Expand Down
8 changes: 1 addition & 7 deletions lib/src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,7 @@ impl Resource {
let commit_builder = self.get_commit_builder().clone();
let commit = commit_builder.sign(&agent, store, self)?;
// If the current client is a server, and the subject is hosted here, don't post
let should_post = if let Some(self_url) = store.get_self_url() {
!self.subject.starts_with(&self_url)
} else {
// Current client is not a server, has no own persisted store
true
};
if should_post {
if store.is_external_subject(&commit.subject)? {
crate::client::post_commit(&commit, store)?;
}
let opts = CommitOpts {
Expand Down
14 changes: 10 additions & 4 deletions lib/src/store.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! In-memory store of Atomic data.
//! This provides many methods for finding, changing, serializing and parsing Atomic Data.
use url::Url;

use crate::agents::Agent;
use crate::storelike::QueryResult;
use crate::Value;
Expand All @@ -17,6 +19,10 @@ pub struct Store {
default_agent: Arc<Mutex<Option<crate::agents::Agent>>>,
}

lazy_static::lazy_static! {
static ref LOCAL_STORE_URL: Url = Url::parse("local:store").unwrap();
}

impl Store {
/// Creates an empty Store.
/// Run `.populate()` to get useful standard models loaded into your store.
Expand Down Expand Up @@ -158,14 +164,14 @@ impl Storelike for Store {
Box::new(self.hashmap.lock().unwrap().clone().into_values())
}

fn get_server_url(&self) -> &str {
fn get_server_url(&self) -> &Url {
// TODO Should be implemented later when companion functionality is here
// https://github.com/atomicdata-dev/atomic-server/issues/6
"local:store"
&LOCAL_STORE_URL
}

fn get_self_url(&self) -> Option<String> {
Some(self.get_server_url().into())
fn get_self_url(&self) -> Option<&Url> {
Some(self.get_server_url())
}

fn get_default_agent(&self) -> AtomicResult<Agent> {
Expand Down
Loading

0 comments on commit 21e2c93

Please sign in to comment.