Skip to content

Commit eadaf90

Browse files
committed
#288 #489 Register Endpoint and true URLs
1 parent 3ee71c5 commit eadaf90

20 files changed

+225
-95
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

+16-12
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ use std::{
1313
sync::{Arc, Mutex},
1414
};
1515

16-
use tracing::{info, instrument};
16+
use tracing::instrument;
17+
use url::Url;
1718

1819
use crate::{
1920
atoms::IndexAtom,
@@ -75,7 +76,7 @@ pub struct Db {
7576
/// A list of all the Collections currently being used. Is used to update `query_index`.
7677
watched_queries: sled::Tree,
7778
/// The address where the db will be hosted, e.g. http://localhost/
78-
server_url: String,
79+
server_url: Url,
7980
/// Endpoints are checked whenever a resource is requested. They calculate (some properties of) the resource and return it.
8081
endpoints: Vec<Endpoint>,
8182
/// Function called whenever a Commit is applied.
@@ -192,14 +193,15 @@ impl Db {
192193
}
193194

194195
fn map_sled_item_to_resource(
196+
&self,
195197
item: Result<(sled::IVec, sled::IVec), sled::Error>,
196198
self_url: String,
197199
include_external: bool,
198200
) -> Option<Resource> {
199201
let (subject, resource_bin) = item.expect(DB_CORRUPT_MSG);
200202
let subject: String = String::from_utf8_lossy(&subject).to_string();
201203

202-
if !include_external && !subject.starts_with(&self_url) {
204+
if !include_external && self.is_external_subject(&subject).ok()? {
203205
return None;
204206
}
205207

@@ -307,14 +309,14 @@ impl Storelike for Db {
307309
Ok(())
308310
}
309311

310-
fn get_server_url(&self) -> &str {
312+
fn get_server_url(&self) -> &Url {
311313
&self.server_url
312314
}
313315

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

320322
fn get_default_agent(&self) -> AtomicResult<crate::agents::Agent> {
@@ -486,7 +488,7 @@ impl Storelike for Db {
486488
// Maybe make this optional?
487489
q_filter.watch(self)?;
488490

489-
info!(filter = ?q_filter, "Building query index");
491+
tracing::info!(filter = ?q_filter, "Building query index");
490492

491493
let atoms: IndexIterator = match (&q.property, q.value.as_ref()) {
492494
(Some(prop), val) => find_in_prop_val_sub_index(self, prop, val),
@@ -530,7 +532,7 @@ impl Storelike for Db {
530532
.expect("No self URL set, is required in DB");
531533

532534
let result = self.resources.into_iter().filter_map(move |item| {
533-
Db::map_sled_item_to_resource(item, self_url.clone(), include_external)
535+
Db::map_sled_item_to_resource(self, item, self_url.to_string(), include_external)
534536
});
535537

536538
Box::new(result)
@@ -543,9 +545,11 @@ impl Storelike for Db {
543545
// This is a potentially expensive operation, but is needed to make TPF queries work with the models created in here
544546
self.build_index(true)
545547
.map_err(|e| format!("Failed to build index. {}", e))?;
546-
crate::populate::create_drive(self)
548+
let default_agent = self
549+
.get_default_agent()
550+
.map_err(|_| "No default agent found")?;
551+
crate::populate::create_drive(self, None, &default_agent.subject, true)
547552
.map_err(|e| format!("Failed to create drive. {}", e))?;
548-
crate::populate::set_drive_rights(self, true)?;
549553
crate::populate::populate_collections(self)
550554
.map_err(|e| format!("Failed to populate collections. {}", e))?;
551555
crate::populate::populate_endpoints(self)

lib/src/db/query_index.rs

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

102-
let self_url = store
103-
.get_self_url()
104-
.ok_or("No self_url set, required for Queries")?;
105-
106102
let limit = if let Some(limit) = q.limit {
107103
limit
108104
} else {
@@ -119,7 +115,7 @@ pub fn query_indexed(store: &Db, q: &Query) -> AtomicResult<QueryResult> {
119115
let (_q_filter, _val, subject) = parse_collection_members_key(&k)?;
120116

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

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
@@ -151,32 +151,49 @@ pub fn populate_base_models(store: &impl Storelike) -> AtomicResult<()> {
151151
Ok(())
152152
}
153153

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

171-
/// Adds rights to the default agent to the Drive resource (at the base URL). Optionally give Public Read rights.
172-
pub fn set_drive_rights(store: &impl Storelike, public_read: bool) -> AtomicResult<()> {
173-
// Now let's add the agent as the Root user and provide write access
174-
let mut drive = store.get_resource(store.get_server_url())?;
175-
let write_agent = store.get_default_agent()?.subject;
176-
let read_agent = write_agent.clone();
177-
178-
drive.push_propval(urls::WRITE, write_agent.into(), true)?;
179-
drive.push_propval(urls::READ, read_agent.into(), true)?;
194+
// Set rights
195+
drive.push_propval(urls::WRITE, for_agent.into(), true)?;
196+
drive.push_propval(urls::READ, for_agent.into(), true)?;
180197
if public_read {
181198
drive.push_propval(urls::READ, urls::PUBLIC_AGENT.into(), true)?;
182199
}
@@ -189,8 +206,10 @@ Register your Agent by visiting [`/setup`]({}/setup). After that, edit this page
189206
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`.
190207
"#, store.get_server_url()), store)?;
191208
}
209+
192210
drive.save_locally(store)?;
193-
Ok(())
211+
212+
Ok(drive)
194213
}
195214

196215
/// Imports the Atomic Data Core items (the entire atomicdata.dev Ontology / Vocabulary)
@@ -251,9 +270,13 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> {
251270
let base = store
252271
.get_self_url()
253272
.ok_or("No self URL in this Store - required for populating importer")?;
254-
let mut importer = Resource::new(urls::construct_path_import(&base));
273+
let mut importer = Resource::new(urls::construct_path_import(base));
255274
importer.set_class(urls::IMPORTER);
256-
importer.set_propval(urls::PARENT.into(), Value::AtomicUrl(base), store)?;
275+
importer.set_propval(
276+
urls::PARENT.into(),
277+
Value::AtomicUrl(base.to_string()),
278+
store,
279+
)?;
257280
importer.set_propval(urls::NAME.into(), Value::String("Import".into()), store)?;
258281
importer.save_locally(store)?;
259282
Ok(())
@@ -264,7 +287,7 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> {
264287
/// Useful for helping a new user get started.
265288
pub fn populate_sidebar_items(store: &crate::Db) -> AtomicResult<()> {
266289
let base = store.get_self_url().ok_or("No self_url")?;
267-
let mut drive = store.get_resource(&base)?;
290+
let mut drive = store.get_resource(base.as_str())?;
268291
let arr = vec![
269292
format!("{}/setup", base),
270293
format!("{}/import", base),

lib/src/resources.rs

+1-7
Original file line numberDiff line numberDiff line change
@@ -320,13 +320,7 @@ impl Resource {
320320
let commit_builder = self.get_commit_builder().clone();
321321
let commit = commit_builder.sign(&agent, store, self)?;
322322
// If the current client is a server, and the subject is hosted here, don't post
323-
let should_post = if let Some(self_url) = store.get_self_url() {
324-
!self.subject.starts_with(&self_url)
325-
} else {
326-
// Current client is not a server, has no own persisted store
327-
true
328-
};
329-
if should_post {
323+
if store.is_external_subject(&commit.subject)? {
330324
crate::client::post_commit(&commit, store)?;
331325
}
332326
let opts = CommitOpts {

lib/src/store.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ pub struct Store {
1515
default_agent: Arc<Mutex<Option<crate::agents::Agent>>>,
1616
}
1717

18+
lazy_static::lazy_static! {
19+
static ref LOCAL_STORE_URL: Url = Url::parse("local:store").unwrap();
20+
}
21+
1822
impl Store {
1923
/// Creates an empty Store.
2024
/// Run `.populate()` to get useful standard models loaded into your store.
@@ -180,14 +184,14 @@ impl Storelike for Store {
180184
)
181185
}
182186

183-
fn get_server_url(&self) -> &str {
187+
fn get_server_url(&self) -> &Url {
184188
// TODO Should be implemented later when companion functionality is here
185189
// https://github.com./atomicdata-dev/atomic-data-rust/issues/6
186-
"local:store"
190+
&LOCAL_STORE_URL
187191
}
188192

189-
fn get_self_url(&self) -> Option<String> {
190-
Some(self.get_server_url().into())
193+
fn get_self_url(&self) -> Option<&Url> {
194+
Some(self.get_server_url())
191195
}
192196

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

0 commit comments

Comments
 (0)