Skip to content

Commit 85c9c6e

Browse files
committed
#288 #489 Register Endpoint and true URLs
1 parent 5d2da53 commit 85c9c6e

18 files changed

+195
-63
lines changed

lib/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ html2md = { version = "0.2.14", optional = true }
2323
kuchikiki = { version = "0.8.2", optional = true }
2424
lol_html = { version = "1", optional = true }
2525
rand = { version = "0.8" }
26+
lazy_static = "1"
2627
regex = "1"
2728
ring = "0.17.6"
2829
rio_api = { version = "0.8", optional = true }

lib/src/collections.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ pub fn create_collection_resource_for_class(
416416

417417
// Let the Collections collection be the top level item
418418
let parent = if class.subject == urls::COLLECTION {
419-
drive
419+
drive.to_string()
420420
} else {
421421
format!("{}/collections", drive)
422422
};

lib/src/commit.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ impl Commit {
181181
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.")?;
182182
resource_old.set(
183183
urls::PARENT.into(),
184-
Value::AtomicUrl(default_parent),
184+
Value::AtomicUrl(default_parent.to_string()),
185185
store,
186186
)?;
187187
}

lib/src/db.rs

+12-9
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use std::{
1414
vec,
1515
};
1616

17-
use tracing::{info, instrument};
17+
use tracing::instrument;
18+
use url::Url;
1819

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

199200
fn map_sled_item_to_resource(
201+
&self,
200202
item: Result<(sled::IVec, sled::IVec), sled::Error>,
201203
self_url: String,
202204
include_external: bool,
203205
) -> Option<Resource> {
204206
let (subject, resource_bin) = item.expect(DB_CORRUPT_MSG);
205207
let subject: String = String::from_utf8_lossy(&subject).to_string();
206208

207-
if !include_external && !subject.starts_with(&self_url) {
209+
if !include_external && self.is_external_subject(&subject).ok()? {
208210
return None;
209211
}
210212

@@ -424,14 +426,14 @@ impl Storelike for Db {
424426
Ok(())
425427
}
426428

427-
fn get_server_url(&self) -> &str {
429+
fn get_server_url(&self) -> &Url {
428430
&self.server_url
429431
}
430432

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

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

602604
let result = self.resources.into_iter().filter_map(move |item| {
603-
Db::map_sled_item_to_resource(item, self_url.clone(), include_external)
605+
Db::map_sled_item_to_resource(self, item, self_url.to_string(), include_external)
604606
});
605607

606608
Box::new(result)
@@ -654,6 +656,7 @@ impl Storelike for Db {
654656

655657
fn populate(&self) -> AtomicResult<()> {
656658
crate::populate::populate_all(self)
659+
crate::populate::create_drive(self, None, &default_agent.subject, true)
657660
}
658661

659662
#[instrument(skip(self))]

lib/src/db/query_index.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ pub fn query_sorted_indexed(
115115
let (_q_filter, _val, subject) = parse_collection_members_key(&k)?;
116116

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

lib/src/endpoints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ pub fn default_endpoints() -> Vec<Endpoint> {
8181
plugins::path::path_endpoint(),
8282
plugins::search::search_endpoint(),
8383
plugins::files::upload_endpoint(),
84+
plugins::register::register_endpoint(),
8485
#[cfg(feature = "html")]
8586
plugins::bookmark::bookmark_endpoint(),
8687
plugins::importer::import_endpoint(),

lib/src/plugins/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@ pub mod files;
4545
pub mod path;
4646
pub mod prunetests;
4747
pub mod query;
48+
pub mod register;
4849
pub mod search;
4950
pub mod versioning;

lib/src/plugins/register.rs

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

lib/src/populate.rs

+44-12
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
parse::ParseOpts,
1010
schema::{Class, Property},
1111
storelike::Query,
12-
urls, Storelike, Value,
12+
urls, Resource, Storelike, Value,
1313
};
1414

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

156-
/// Creates a Drive resource at the base URL. Does not set rights. Use set_drive_rights for that.
157-
pub fn create_drive(store: &impl Storelike) -> AtomicResult<()> {
158-
let self_url = store
159-
.get_self_url()
160-
.ok_or("No self_url set, cannot populate store with Drive")?;
161-
let mut drive = store.get_resource_new(&self_url);
156+
/// Creates a Drive resource at the base URL if no name is passed.
157+
#[tracing::instrument(skip(store), level = "info")]
158+
pub fn create_drive(
159+
store: &impl Storelike,
160+
drive_name: Option<&str>,
161+
for_agent: &str,
162+
public_read: bool,
163+
) -> AtomicResult<Resource> {
164+
let mut self_url = if let Some(url) = store.get_self_url() {
165+
url.to_owned()
166+
} else {
167+
return Err("No self URL set. Cannot create drive.".into());
168+
};
169+
let drive_subject: String = if let Some(name) = drive_name {
170+
// Let's make a subdomain
171+
let host = self_url.host().expect("No host in server_url");
172+
let subdomain_host = format!("{}.{}", name, host);
173+
self_url.set_host(Some(&subdomain_host))?;
174+
self_url.to_string()
175+
} else {
176+
self_url.to_string()
177+
};
178+
179+
let mut drive = if drive_name.is_some() {
180+
if store.get_resource(&drive_subject).is_ok() {
181+
return Err("Drive URL is already taken".into());
182+
}
183+
Resource::new(drive_subject)
184+
} else {
185+
// Only for the base URL (of no drive name is passed), we should not check if the drive exists.
186+
// This is because we use `create_drive` in the `--initialize` command.
187+
store.get_resource_new(&drive_subject)
188+
};
162189
drive.set_class(urls::DRIVE);
163-
let server_url = url::Url::parse(store.get_server_url())?;
164190
drive.set_string(
165191
urls::NAME.into(),
166-
server_url.host_str().ok_or("Can't use current base URL")?,
192+
drive_name.unwrap_or_else(|| self_url.host_str().unwrap()),
167193
store,
168194
)?;
169195
drive.save_locally(store)?;
@@ -236,8 +262,10 @@ To use the data in your web apps checkout our client libraries: [@tomic/lib](htt
236262
Use [@tomic/cli](https://docs.atomicdata.dev/js-cli) to generate typed ontologies inside your code.
237263
"#, store.get_server_url(), &format!("{}/{}", drive.get_subject(), DEFAULT_ONTOLOGY_PATH)), store)?;
238264
}
265+
239266
drive.save_locally(store)?;
240-
Ok(())
267+
268+
Ok(drive)
241269
}
242270

243271
/// Imports the Atomic Data Core items (the entire atomicdata.dev Ontology / Vocabulary)
@@ -312,7 +340,11 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> {
312340
.ok_or("No self URL in this Store - required for populating importer")?;
313341
let mut importer = crate::Resource::new(urls::construct_path_import(&base));
314342
importer.set_class(urls::IMPORTER);
315-
importer.set(urls::PARENT.into(), Value::AtomicUrl(base), store)?;
343+
importer.set(
344+
urls::PARENT.into(),
345+
Value::AtomicUrl(base.to_string()),
346+
store,
347+
)?;
316348
importer.set(urls::NAME.into(), Value::String("Import".into()), store)?;
317349
importer.save_locally(store)?;
318350
Ok(())
@@ -323,7 +355,7 @@ pub fn populate_importer(store: &crate::Db) -> AtomicResult<()> {
323355
/// Useful for helping a new user get started.
324356
pub fn populate_sidebar_items(store: &crate::Db) -> AtomicResult<()> {
325357
let base = store.get_self_url().ok_or("No self_url")?;
326-
let mut drive = store.get_resource(&base)?;
358+
let mut drive = store.get_resource(base.as_str())?;
327359
let arr = vec![
328360
format!("{}/setup", base),
329361
format!("{}/import", base),

lib/src/resources.rs

+1-7
Original file line numberDiff line numberDiff line change
@@ -342,13 +342,7 @@ impl Resource {
342342
let commit_builder = self.get_commit_builder().clone();
343343
let commit = commit_builder.sign(&agent, store, self)?;
344344
// If the current client is a server, and the subject is hosted here, don't post
345-
let should_post = if let Some(self_url) = store.get_self_url() {
346-
!self.subject.starts_with(&self_url)
347-
} else {
348-
// Current client is not a server, has no own persisted store
349-
true
350-
};
351-
if should_post {
345+
if store.is_external_subject(&commit.subject)? {
352346
crate::client::post_commit(&commit, store)?;
353347
}
354348
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::agents::Agent;
57
use crate::storelike::QueryResult;
68
use crate::Value;
@@ -17,6 +19,10 @@ pub struct Store {
1719
default_agent: Arc<Mutex<Option<crate::agents::Agent>>>,
1820
}
1921

22+
lazy_static::lazy_static! {
23+
static ref LOCAL_STORE_URL: Url = Url::parse("local:store").unwrap();
24+
}
25+
2026
impl Store {
2127
/// Creates an empty Store.
2228
/// Run `.populate()` to get useful standard models loaded into your store.
@@ -158,14 +164,14 @@ impl Storelike for Store {
158164
Box::new(self.hashmap.lock().unwrap().clone().into_values())
159165
}
160166

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

167-
fn get_self_url(&self) -> Option<String> {
168-
Some(self.get_server_url().into())
173+
fn get_self_url(&self) -> Option<&Url> {
174+
Some(self.get_server_url())
169175
}
170176

171177
fn get_default_agent(&self) -> AtomicResult<Agent> {

0 commit comments

Comments
 (0)