Skip to content

Commit b3abd91

Browse files
committed
#288 Register Endpoint and true URLs
1 parent 086b5a2 commit b3abd91

20 files changed

+226
-116
lines changed

lib/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ bincode = {version = "1", optional = true}
2121
directories = {version = ">= 2, < 5", optional = true}
2222
html2md = {version = "0.2.13", optional = true}
2323
kuchiki = {version = "0.8.1", optional = true}
24+
lazy_static = "1"
2425
lol_html = {version = "0.3.1", optional = true}
2526
rand = {version = "0.8"}
2627
regex = "1"
@@ -39,7 +40,6 @@ urlencoding = "2"
3940
[dev-dependencies]
4041
criterion = "0.3"
4142
iai = "0.1"
42-
lazy_static = "1"
4343
ntest = "0.7"
4444

4545
[features]

lib/src/collections.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ pub fn create_collection_resource_for_class(
427427

428428
// Let the Collections collection be the top level item
429429
let parent = if class.subject == urls::COLLECTION {
430-
drive
430+
drive.to_string()
431431
} else {
432432
format!("{}/collections", drive)
433433
};

lib/src/commit.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ impl Commit {
172172
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.")?;
173173
resource_old.set_propval(
174174
urls::PARENT.into(),
175-
Value::AtomicUrl(default_parent),
175+
Value::AtomicUrl(default_parent.to_string()),
176176
store,
177177
)?;
178178
}

lib/src/db.rs

+14-14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::{
77
};
88

99
use tracing::{instrument, trace};
10+
use url::Url;
1011

1112
use crate::{
1213
commit::CommitResponse,
@@ -63,7 +64,7 @@ pub struct Db {
6364
/// See [collections_index]
6465
watched_queries: sled::Tree,
6566
/// The address where the db will be hosted, e.g. http://localhost/
66-
server_url: String,
67+
server_url: Url,
6768
/// Endpoints are checked whenever a resource is requested. They calculate (some properties of) the resource and return it.
6869
endpoints: Vec<Endpoint>,
6970
/// Function called whenever a Commit is applied.
@@ -86,7 +87,7 @@ impl Db {
8687
resources,
8788
reference_index,
8889
members_index,
89-
server_url,
90+
server_url: Url::parse(&server_url)?,
9091
watched_queries,
9192
endpoints: default_endpoints(),
9293
on_commit: None,
@@ -262,14 +263,14 @@ impl Storelike for Db {
262263
Ok(())
263264
}
264265

265-
fn get_server_url(&self) -> &str {
266+
fn get_server_url(&self) -> &Url {
266267
&self.server_url
267268
}
268269

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

275276
fn get_default_agent(&self) -> AtomicResult<crate::agents::Agent> {
@@ -516,13 +517,10 @@ impl Storelike for Db {
516517
#[instrument(skip(self))]
517518
fn all_resources(&self, include_external: bool) -> ResourceCollection {
518519
let mut resources: ResourceCollection = Vec::new();
519-
let self_url = self
520-
.get_self_url()
521-
.expect("No self URL set, is required in DB");
522520
for item in self.resources.into_iter() {
523521
let (subject, resource_bin) = item.expect(DB_CORRUPT_MSG);
524522
let subject: String = String::from_utf8_lossy(&subject).to_string();
525-
if !include_external && !subject.starts_with(&self_url) {
523+
if !include_external && self.is_external_subject(&subject).unwrap_or(true) {
526524
continue;
527525
}
528526
let propvals: PropVals = bincode::deserialize(&resource_bin)
@@ -540,9 +538,11 @@ impl Storelike for Db {
540538
// This is a potentially expensive operation, but is needed to make TPF queries work with the models created in here
541539
self.build_index(true)
542540
.map_err(|e| format!("Failed to build index. {}", e))?;
543-
crate::populate::create_drive(self)
541+
let default_agent = self
542+
.get_default_agent()
543+
.map_err(|_| "No default agent found")?;
544+
crate::populate::create_drive(self, None, &default_agent.subject, true)
544545
.map_err(|e| format!("Failed to create drive. {}", e))?;
545-
crate::populate::set_drive_rights(self, true)?;
546546
crate::populate::populate_collections(self)
547547
.map_err(|e| format!("Failed to populate collections. {}", e))?;
548548
crate::populate::populate_endpoints(self)
@@ -667,7 +667,7 @@ impl Storelike for Db {
667667
// WARNING: Converts all Atoms to Strings, the datatype is lost here
668668
let atom = key_to_atom(&key_string)?;
669669
// NOTE: This means we'll include random values that start with the current server URL, including paragraphs for example.
670-
if include_external || atom.subject.starts_with(self.get_server_url()) {
670+
if include_external || !self.is_external_subject(&atom.subject)? {
671671
vec.push(atom)
672672
}
673673
}

lib/src/db/query_index.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@ pub fn query_indexed(store: &Db, q: &Query) -> AtomicResult<QueryResult> {
7777
let mut resources = Vec::new();
7878
let mut count = 0;
7979

80-
let self_url = store
81-
.get_self_url()
82-
.ok_or("No self_url set, required for Queries")?;
83-
8480
let limit = if let Some(limit) = q.limit {
8581
limit
8682
} else {
@@ -97,7 +93,7 @@ pub fn query_indexed(store: &Db, q: &Query) -> AtomicResult<QueryResult> {
9793
let (_q_filter, _val, subject) = parse_collection_members_key(&k)?;
9894

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

lib/src/endpoints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub fn default_endpoints() -> Vec<Endpoint> {
4949
plugins::path::path_endpoint(),
5050
plugins::search::search_endpoint(),
5151
plugins::files::upload_endpoint(),
52+
plugins::register::register_endpoint(),
5253
#[cfg(feature = "html")]
5354
plugins::bookmark::bookmark_endpoint(),
5455
]

lib/src/plugins/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,6 @@ pub mod invite;
4343
pub mod bookmark;
4444
pub mod files;
4545
pub mod path;
46+
pub mod register;
4647
pub mod search;
4748
pub mod versioning;

lib/src/plugins/register.rs

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//! Creates a new Drive and optionally also an Agent.
2+
3+
use crate::{agents::Agent, endpoints::Endpoint, errors::AtomicResult, urls, Resource, Storelike};
4+
5+
pub fn register_endpoint() -> Endpoint {
6+
Endpoint {
7+
path: "/register".to_string(),
8+
params: [
9+
urls::INVITE_PUBKEY.to_string(),
10+
urls::NAME.to_string(),
11+
].into(),
12+
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(),
13+
shortname: "register".to_string(),
14+
handle: Some(construct_register_redirect),
15+
}
16+
}
17+
18+
#[tracing::instrument(skip(store))]
19+
pub fn construct_register_redirect(
20+
url: url::Url,
21+
store: &impl Storelike,
22+
for_agent: Option<&str>,
23+
) -> AtomicResult<Resource> {
24+
let requested_subject = url.to_string();
25+
let mut pub_key = None;
26+
let mut name_option = None;
27+
for (k, v) in url.query_pairs() {
28+
match k.as_ref() {
29+
"public-key" | urls::INVITE_PUBKEY => pub_key = Some(v.to_string()),
30+
"name" | urls::NAME => name_option = Some(v.to_string()),
31+
_ => {}
32+
}
33+
}
34+
if pub_key.is_none() && name_option.is_none() {
35+
return register_endpoint().to_resource(store);
36+
}
37+
38+
let name = if let Some(n) = name_option {
39+
n
40+
} else {
41+
return Err("No name provided".into());
42+
};
43+
44+
let mut new_agent = None;
45+
46+
let drive_creator_agent: String = if let Some(key) = pub_key {
47+
let new = Agent::new_from_public_key(store, &key)?.subject;
48+
new_agent = Some(new.clone());
49+
new
50+
} else if let Some(agent) = for_agent {
51+
agent.to_string()
52+
} else {
53+
return Err("No `public-key` provided".into());
54+
};
55+
56+
// Create the new Drive
57+
let drive = crate::populate::create_drive(store, Some(&name), &drive_creator_agent, false)?;
58+
59+
// Construct the Redirect Resource, which might provide the Client with a Subject for his Agent.
60+
let mut redirect = Resource::new_instance(urls::REDIRECT, store)?;
61+
redirect.set_propval_string(urls::DESTINATION.into(), drive.get_subject(), store)?;
62+
if let Some(agent) = new_agent {
63+
redirect.set_propval(
64+
urls::REDIRECT_AGENT.into(),
65+
crate::Value::AtomicUrl(agent),
66+
store,
67+
)?;
68+
}
69+
// The front-end requires the @id to be the same as requested
70+
redirect.set_subject(requested_subject);
71+
Ok(redirect)
72+
}

lib/src/populate.rs

+47-24
Original file line numberDiff line numberDiff line change
@@ -150,32 +150,49 @@ pub fn populate_base_models(store: &impl Storelike) -> AtomicResult<()> {
150150
Ok(())
151151
}
152152

153-
/// Creates a Drive resource at the base URL. Does not set rights. Use set_drive_rights for that.
154-
pub fn create_drive(store: &impl Storelike) -> AtomicResult<()> {
155-
let self_url = store
156-
.get_self_url()
157-
.ok_or("No self_url set, cannot populate store with Drive")?;
158-
let mut drive = store.get_resource_new(&self_url);
153+
/// Creates a Drive resource at the base URL if no name is passed.
154+
#[tracing::instrument(skip(store), level = "info")]
155+
pub fn create_drive(
156+
store: &impl Storelike,
157+
drive_name: Option<&str>,
158+
for_agent: &str,
159+
public_read: bool,
160+
) -> AtomicResult<Resource> {
161+
let mut self_url = if let Some(url) = store.get_self_url() {
162+
url.to_owned()
163+
} else {
164+
return Err("No self URL set. Cannot create drive.".into());
165+
};
166+
let drive_subject: String = if let Some(name) = drive_name {
167+
// Let's make a subdomain
168+
let host = self_url.host().expect("No host in server_url");
169+
let subdomain_host = format!("{}.{}", name, host);
170+
self_url.set_host(Some(&subdomain_host))?;
171+
self_url.to_string()
172+
} else {
173+
self_url.to_string()
174+
};
175+
176+
let mut drive = if drive_name.is_some() {
177+
if store.get_resource(&drive_subject).is_ok() {
178+
return Err("Drive URL is already taken".into());
179+
}
180+
Resource::new(drive_subject)
181+
} else {
182+
// Only for the base URL (of no drive name is passed), we should not check if the drive exists.
183+
// This is because we use `create_drive` in the `--initialize` command.
184+
store.get_resource_new(&drive_subject)
185+
};
159186
drive.set_class(urls::DRIVE);
160-
let server_url = url::Url::parse(store.get_server_url())?;
161187
drive.set_propval_string(
162188
urls::NAME.into(),
163-
server_url.host_str().ok_or("Can't use current base URL")?,
189+
drive_name.unwrap_or_else(|| self_url.host_str().unwrap()),
164190
store,
165191
)?;
166-
drive.save_locally(store)?;
167-
Ok(())
168-
}
169192

170-
/// Adds rights to the default agent to the Drive resource (at the base URL). Optionally give Public Read rights.
171-
pub fn set_drive_rights(store: &impl Storelike, public_read: bool) -> AtomicResult<()> {
172-
// Now let's add the agent as the Root user and provide write access
173-
let mut drive = store.get_resource(store.get_server_url())?;
174-
let write_agent = store.get_default_agent()?.subject;
175-
let read_agent = write_agent.clone();
176-
177-
drive.push_propval(urls::WRITE, write_agent.into(), true)?;
178-
drive.push_propval(urls::READ, read_agent.into(), true)?;
193+
// Set rights
194+
drive.push_propval(urls::WRITE, for_agent.into(), true)?;
195+
drive.push_propval(urls::READ, for_agent.into(), true)?;
179196
if public_read {
180197
drive.push_propval(urls::READ, urls::PUBLIC_AGENT.into(), true)?;
181198
}
@@ -188,8 +205,10 @@ Register your Agent by visiting [`/setup`]({}/setup). After that, edit this page
188205
Note that, by default, all resources are `public`. You can edit this by opening the context menu (the three dots in the navigation bar), and going to `share`.
189206
"#, store.get_server_url()), store)?;
190207
}
208+
191209
drive.save_locally(store)?;
192-
Ok(())
210+
211+
Ok(drive)
193212
}
194213

195214
/// Imports the Atomic Data Core items (the entire atomicdata.dev Ontology / Vocabulary)
@@ -255,9 +274,13 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> {
255274
let base = store
256275
.get_self_url()
257276
.ok_or("No self URL in this Store - required for populating importer")?;
258-
let mut importer = Resource::new(urls::construct_path_import(&base));
277+
let mut importer = Resource::new(urls::construct_path_import(base));
259278
importer.set_class(urls::IMPORTER);
260-
importer.set_propval(urls::PARENT.into(), Value::AtomicUrl(base), store)?;
279+
importer.set_propval(
280+
urls::PARENT.into(),
281+
Value::AtomicUrl(base.to_string()),
282+
store,
283+
)?;
261284
importer.set_propval(urls::NAME.into(), Value::String("Import".into()), store)?;
262285
importer.save_locally(store)?;
263286
Ok(())
@@ -268,7 +291,7 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> {
268291
/// Useful for helping a new user get started.
269292
pub fn populate_sidebar_items(store: &crate::Db) -> AtomicResult<()> {
270293
let base = store.get_self_url().ok_or("No self_url")?;
271-
let mut drive = store.get_resource(&base)?;
294+
let mut drive = store.get_resource(base.as_str())?;
272295
let arr = vec![
273296
format!("{}/setup", base),
274297
format!("{}/import", base),

lib/src/resources.rs

+1-7
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,7 @@ impl Resource {
307307
let commit_builder = self.get_commit_builder().clone();
308308
let commit = commit_builder.sign(&agent, store, self)?;
309309
// If the current client is a server, and the subject is hosted here, don't post
310-
let should_post = if let Some(self_url) = store.get_self_url() {
311-
!self.subject.starts_with(&self_url)
312-
} else {
313-
// Current client is not a server, has no own persisted store
314-
true
315-
};
316-
if should_post {
310+
if store.is_external_subject(&commit.subject)? {
317311
crate::client::post_commit(&commit, store)?;
318312
}
319313
let opts = CommitOpts {

lib/src/store.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! In-memory store of Atomic data.
22
//! This provides many methods for finding, changing, serializing and parsing Atomic Data.
33
4+
use url::Url;
5+
46
use crate::{
57
atoms::Atom,
68
storelike::{ResourceCollection, Storelike},
@@ -16,6 +18,10 @@ pub struct Store {
1618
default_agent: Arc<Mutex<Option<crate::agents::Agent>>>,
1719
}
1820

21+
lazy_static::lazy_static! {
22+
static ref LOCAL_STORE_URL: Url = Url::parse("local:store").unwrap();
23+
}
24+
1925
impl Store {
2026
/// Creates an empty Store.
2127
/// Run `.populate()` to get useful standard models loaded into your store.
@@ -87,14 +93,14 @@ impl Storelike for Store {
8793
all
8894
}
8995

90-
fn get_server_url(&self) -> &str {
96+
fn get_server_url(&self) -> &Url {
9197
// TODO Should be implemented later when companion functionality is here
9298
// https://github.com/atomicdata-dev/atomic-data-rust/issues/6
93-
"local:store"
99+
&LOCAL_STORE_URL
94100
}
95101

96-
fn get_self_url(&self) -> Option<String> {
97-
Some(self.get_server_url().into())
102+
fn get_self_url(&self) -> Option<&Url> {
103+
Some(self.get_server_url())
98104
}
99105

100106
fn get_default_agent(&self) -> AtomicResult<crate::agents::Agent> {

0 commit comments

Comments
 (0)