Skip to content

Commit bd6288c

Browse files
committed
Register endpoint, URLs #288 #48gister Endpoint and true URLs
1 parent 21da274 commit bd6288c

18 files changed

+207
-69
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
@@ -240,7 +240,7 @@ impl Commit {
240240
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.")?;
241241
resource_old.set(
242242
urls::PARENT.into(),
243-
Value::AtomicUrl(default_parent),
243+
Value::AtomicUrl(default_parent.to_string()),
244244
store,
245245
)?;
246246
}

lib/src/db.rs

+15-12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::{
1818

1919
use tracing::{info, instrument};
2020
use trees::{Method, Operation, Transaction, Tree};
21+
use url::Url;
2122

2223
use crate::{
2324
agents::ForAgent,
@@ -79,7 +80,7 @@ pub struct Db {
7980
/// [Tree::WatchedQueries]
8081
watched_queries: sled::Tree,
8182
/// The address where the db will be hosted, e.g. http://localhost/
82-
server_url: String,
83+
server_url: Url,
8384
/// Endpoints are checked whenever a resource is requested. They calculate (some properties of) the resource and return it.
8485
endpoints: Vec<Endpoint>,
8586
/// Function called whenever a Commit is applied.
@@ -109,7 +110,7 @@ impl Db {
109110
reference_index,
110111
query_index,
111112
prop_val_sub_index,
112-
server_url,
113+
server_url: Url::parse(&server_url)?,
113114
watched_queries,
114115
endpoints: default_endpoints(),
115116
on_commit: None,
@@ -268,14 +269,15 @@ impl Db {
268269
}
269270

270271
fn map_sled_item_to_resource(
272+
&self,
271273
item: Result<(sled::IVec, sled::IVec), sled::Error>,
272274
self_url: String,
273275
include_external: bool,
274276
) -> Option<Resource> {
275277
let (subject, resource_bin) = item.expect(DB_CORRUPT_MSG);
276278
let subject: String = String::from_utf8_lossy(&subject).to_string();
277279

278-
if !include_external && !subject.starts_with(&self_url) {
280+
if !include_external && self.is_external_subject(&subject).ok()? {
279281
return None;
280282
}
281283

@@ -398,7 +400,7 @@ impl Db {
398400

399401
for (i, atom_res) in atoms.enumerate() {
400402
let atom = atom_res?;
401-
if !q.include_external && !atom.subject.starts_with(&self_url) {
403+
if !q.include_external && self.is_external_subject(&atom.subject).unwrap() {
402404
continue;
403405
}
404406

@@ -660,14 +662,14 @@ impl Storelike for Db {
660662
Ok(commit_response)
661663
}
662664

663-
fn get_server_url(&self) -> &str {
665+
fn get_server_url(&self) -> &Url {
664666
&self.server_url
665667
}
666668

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

673675
fn get_default_agent(&self) -> AtomicResult<crate::agents::Agent> {
@@ -686,7 +688,7 @@ impl Storelike for Db {
686688
let resource = crate::resources::Resource::from_propvals(propvals, subject.into());
687689
Ok(resource)
688690
}
689-
Err(e) => self.handle_not_found(subject, e, None),
691+
Err(e) => self.handle_not_found(subject, e),
690692
}
691693
}
692694

@@ -836,7 +838,7 @@ impl Storelike for Db {
836838
.expect("No self URL set, is required in DB");
837839

838840
let result = self.resources.into_iter().filter_map(move |item| {
839-
Db::map_sled_item_to_resource(item, self_url.clone(), include_external)
841+
Db::map_sled_item_to_resource(self, item, self_url.to_string(), include_external)
840842
});
841843

842844
Box::new(result)
@@ -889,7 +891,8 @@ impl Storelike for Db {
889891
}
890892

891893
fn populate(&self) -> AtomicResult<()> {
892-
crate::populate::populate_all(self)
894+
crate::populate::populate_all(self)?;
895+
Ok(())
893896
}
894897

895898
#[instrument(skip(self))]

lib/src/db/query_index.rs

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

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

lib/src/endpoints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub fn default_endpoints() -> Vec<Endpoint> {
8383
plugins::files::upload_endpoint(),
8484
plugins::files::download_endpoint(),
8585
plugins::export::export_endpoint(),
86+
plugins::register::register_endpoint(),
8687
#[cfg(feature = "html")]
8788
plugins::bookmark::bookmark_endpoint(),
8889
plugins::importer::import_endpoint(),

lib/src/plugins/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ pub mod files;
4646
pub mod path;
4747
pub mod prunetests;
4848
pub mod query;
49+
pub mod register;
4950
pub mod search;
5051
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

+53-15
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,26 +153,52 @@ 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)?;
170196

171-
Ok(())
197+
Ok(drive)
172198
}
173199

174200
pub fn create_default_ontology(store: &impl Storelike) -> AtomicResult<()> {
175-
let mut drive = store.get_resource(store.get_server_url())?;
201+
let mut drive = store.get_resource(store.get_server_url().as_str())?;
176202

177203
let ontology_subject = format!("{}/{}", drive.get_subject(), DEFAULT_ONTOLOGY_PATH);
178204

@@ -209,7 +235,7 @@ pub fn create_default_ontology(store: &impl Storelike) -> AtomicResult<()> {
209235
/// Adds rights to the default agent to the Drive resource (at the base URL). Optionally give Public Read rights.
210236
pub fn set_drive_rights(store: &impl Storelike, public_read: bool) -> AtomicResult<()> {
211237
// Now let's add the agent as the Root user and provide write access
212-
let mut drive = store.get_resource(store.get_server_url())?;
238+
let mut drive = store.get_resource(store.get_server_url().as_str())?;
213239
let write_agent = store.get_default_agent()?.subject;
214240
let read_agent = write_agent.clone();
215241

@@ -236,7 +262,9 @@ 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)?;
267+
240268
Ok(())
241269
}
242270

@@ -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),
@@ -342,7 +374,13 @@ pub fn populate_all(store: &crate::Db) -> AtomicResult<()> {
342374
// populate_base_models should be run in init, instead of here, since it will result in infinite loops without
343375
populate_default_store(store)
344376
.map_err(|e| format!("Failed to populate default store. {}", e))?;
345-
create_drive(store).map_err(|e| format!("Failed to create drive. {}", e))?;
377+
create_drive(
378+
store,
379+
None,
380+
&store.get_default_agent().unwrap().subject,
381+
true,
382+
)
383+
.map_err(|e| format!("Failed to create drive. {}", e))?;
346384
create_default_ontology(store)
347385
.map_err(|e| format!("Failed to create default ontology. {}", e))?;
348386
set_drive_rights(store, true)?;

lib/src/resources.rs

+1-7
Original file line numberDiff line numberDiff line change
@@ -347,13 +347,7 @@ impl Resource {
347347
let commit_builder = self.get_commit_builder().clone();
348348
let commit = commit_builder.sign(&agent, store, self)?;
349349
// If the current client is a server, and the subject is hosted here, don't post
350-
let should_post = if let Some(self_url) = store.get_self_url() {
351-
!self.subject.starts_with(&self_url)
352-
} else {
353-
// Current client is not a server, has no own persisted store
354-
true
355-
};
356-
if should_post {
350+
if store.is_external_subject(&commit.subject)? {
357351
crate::client::post_commit(&commit, store)?;
358352
}
359353
let opts = CommitOpts {

0 commit comments

Comments
 (0)