diff --git a/nextgraph/src/local_broker.rs b/nextgraph/src/local_broker.rs index 76ea850..80b4f21 100644 --- a/nextgraph/src/local_broker.rs +++ b/nextgraph/src/local_broker.rs @@ -1085,15 +1085,15 @@ impl LocalBroker { } fn add_session(&mut self, session: Session) -> Result { - let private_store_id = self - .get_site_store_of_session(&session, SiteStoreType::Private)? - .to_string(); - let protected_store_id = self - .get_site_store_of_session(&session, SiteStoreType::Protected)? - .to_string(); - let public_store_id = self - .get_site_store_of_session(&session, SiteStoreType::Public)? - .to_string(); + let private_store_id = NuriV0::to_store_nuri_string( + &self.get_site_store_of_session(&session, SiteStoreType::Private)?, + ); + let protected_store_id = NuriV0::to_store_nuri_string( + &self.get_site_store_of_session(&session, SiteStoreType::Protected)?, + ); + let public_store_id = NuriV0::to_store_nuri_string( + &self.get_site_store_of_session(&session, SiteStoreType::Public)?, + ); let user_id = session.config.user_id(); @@ -2216,9 +2216,9 @@ pub async fn session_start(config: SessionConfig) -> Result Result Result Result<(), String> { - let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; + let nuriv0 = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_write_query(), - nuri, - payload: Some(AppRequestPayload::new_sparql_query(sparql)), + nuri: nuriv0, + payload: Some(AppRequestPayload::new_sparql_query(sparql, Some(nuri))), session_id, }); @@ -560,6 +560,7 @@ async fn sparql_update(session_id: u64, sparql: String, nuri: String) -> Result< async fn sparql_query( session_id: u64, sparql: String, + base: Option, nuri: Option, ) -> Result { let nuri = if nuri.is_some() { @@ -571,7 +572,7 @@ async fn sparql_query( let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_read_query(), nuri, - payload: Some(AppRequestPayload::new_sparql_query(sparql)), + payload: Some(AppRequestPayload::new_sparql_query(sparql, base)), session_id, }); diff --git a/ng-app/src/apps/SparqlUpdateEditor.svelte b/ng-app/src/apps/SparqlUpdateEditor.svelte index 1cf2ae4..d644e2e 100644 --- a/ng-app/src/apps/SparqlUpdateEditor.svelte +++ b/ng-app/src/apps/SparqlUpdateEditor.svelte @@ -33,7 +33,7 @@ onMount(()=>{ reset_in_memory(); if (!$in_memory_discrete){ - $in_memory_discrete = "INSERT DATA { \n \"An example value\".\r}"; + $in_memory_discrete = "INSERT DATA { \n <> \"An example value\".\r}"; } }); const run = async () => { diff --git a/ng-app/src/lib/FullLayout.svelte b/ng-app/src/lib/FullLayout.svelte index dd0ac75..b649f99 100644 --- a/ng-app/src/lib/FullLayout.svelte +++ b/ng-app/src/lib/FullLayout.svelte @@ -558,7 +558,7 @@ await reset_toasts(); let store_repo = $cur_tab.store.repo; if (!store_repo) { - store_repo = $all_tabs["o:"+$active_session.private_store_id].store.repo + store_repo = $all_tabs[$active_session.private_store_id].store.repo } let nuri = await ng.doc_create($active_session.session_id, get_class(class_name)["ng:crdt"], class_name, store_repo, destination); push("#/"+nuri); diff --git a/ng-app/src/lib/Home.svelte b/ng-app/src/lib/Home.svelte index 9d79970..64e68a6 100644 --- a/ng-app/src/lib/Home.svelte +++ b/ng-app/src/lib/Home.svelte @@ -51,7 +51,7 @@ reset_in_memory(); }); - let nuri = $active_session && ("o:"+$active_session.private_store_id); + let nuri = $active_session && $active_session.private_store_id; @@ -119,6 +119,7 @@
{$t("doc.header.buttons.all_docs")}
+
diff --git a/ng-app/src/routes/NURI.svelte b/ng-app/src/routes/NURI.svelte index 8bdfaf2..ee01406 100644 --- a/ng-app/src/routes/NURI.svelte +++ b/ng-app/src/routes/NURI.svelte @@ -32,9 +32,9 @@ } from "svelte-heros-v2"; //console.log(params); let nuri = ""; - $: if ($active_session && params[1]) { if (params[1].startsWith("o:"+$active_session.private_store_id)) push("#/"); - else if (params[1].startsWith("o:"+$active_session.protected_store_id)) push("#/shared"); - else if (params[1].startsWith("o:"+$active_session.public_store_id)) push("#/site"); else nuri = params[1]; } + $: if ($active_session && params[1]) { if (params[1].startsWith($active_session.private_store_id)) push("#/"); + else if (params[1].startsWith($active_session.protected_store_id)) push("#/shared"); + else if (params[1].startsWith($active_session.public_store_id)) push("#/site"); else nuri = params[1]; } onMount(() => { if ($cur_tab.store.store_type) change_nav_bar(`nav:${$cur_tab.store.store_type}`,$t(`doc.${$cur_tab.store.store_type}_store`), true); diff --git a/ng-app/src/routes/Shared.svelte b/ng-app/src/routes/Shared.svelte index de65767..35f3815 100644 --- a/ng-app/src/routes/Shared.svelte +++ b/ng-app/src/routes/Shared.svelte @@ -30,7 +30,7 @@ change_nav_bar("nav:protected",$t("doc.protected_store"), false); reset_in_memory(); }); - let nuri = $active_session && ("o:"+$active_session.protected_store_id); + let nuri = $active_session && $active_session.protected_store_id; diff --git a/ng-app/src/routes/Site.svelte b/ng-app/src/routes/Site.svelte index 94d1437..cf1dc96 100644 --- a/ng-app/src/routes/Site.svelte +++ b/ng-app/src/routes/Site.svelte @@ -28,7 +28,7 @@ change_nav_bar("nav:public",$t("doc.public_store"), false); reset_in_memory(); }); - let nuri = $active_session && ("o:"+$active_session.public_store_id); + let nuri = $active_session && $active_session.public_store_id; diff --git a/ng-app/src/store.ts b/ng-app/src/store.ts index c6d713a..79f3c27 100644 --- a/ng-app/src/store.ts +++ b/ng-app/src/store.ts @@ -433,8 +433,10 @@ export const sparql_query = async function(sparql:string, union:boolean) { }); throw new Error("no session"); } - let nuri = union ? undefined : "did:ng:"+get(cur_tab).branch.nuri; - return await ng.sparql_query(session.session_id, sparql, nuri); + let base = "did:ng:"+get(cur_tab).branch.nuri; + console.log(base) + let nuri = union ? undefined : base; + return await ng.sparql_query(session.session_id, sparql, base, nuri); } export const sparql_update = async function(sparql:string) { diff --git a/ng-app/src/tab.ts b/ng-app/src/tab.ts index 0e5d403..3a25a02 100644 --- a/ng-app/src/tab.ts +++ b/ng-app/src/tab.ts @@ -245,7 +245,7 @@ export const all_tabs = writable({ can_edit: false, }, branch: { - nuri: "", // :o or :o:b + nuri: "", // :o:v or :o:v:b readcap: "", // "r:" comment_branch: "", // nuri class: "", diff --git a/ng-app/src/wallet_emojis.ts b/ng-app/src/wallet_emojis.ts index aa279b7..65818d2 100644 --- a/ng-app/src/wallet_emojis.ts +++ b/ng-app/src/wallet_emojis.ts @@ -1434,25 +1434,25 @@ export async function load_svg() { /************** SPORT *********************/ - // sport[0].svg = await import("./assets/pazzle/emoji_u1f93a.svg?component"); - // sport[1].svg = await import("./assets/pazzle/emoji_u1f3c7.svg?component"); - // sport[2].svg = await import("./assets/pazzle/emoji_u26f7.svg?component"); + sport[0].svg = await import("./assets/pazzle/emoji_u1f93a.svg?component"); + sport[1].svg = await import("./assets/pazzle/emoji_u1f3c7.svg?component"); + sport[2].svg = await import("./assets/pazzle/emoji_u26f7.svg?component"); - // sport[3].svg = await import("./assets/pazzle/emoji_u1f6a3.svg?component"); - // sport[4].svg = await import("./assets/pazzle/emoji_u1f3ca.svg?component"); - // sport[5].svg = await import("./assets/pazzle/emoji_u1f3c4.svg?component"); + sport[3].svg = await import("./assets/pazzle/emoji_u1f6a3.svg?component"); + sport[4].svg = await import("./assets/pazzle/emoji_u1f3ca.svg?component"); + sport[5].svg = await import("./assets/pazzle/emoji_u1f3c4.svg?component"); - // sport[6].svg = await import("./assets/pazzle/emoji_u1f3cb.svg?component"); - // sport[7].svg = await import("./assets/pazzle/emoji_u1f93c.svg?component"); - // sport[8].svg = await import("./assets/pazzle/emoji_u1f6b4.svg?component"); + sport[6].svg = await import("./assets/pazzle/emoji_u1f3cb.svg?component"); + sport[7].svg = await import("./assets/pazzle/emoji_u1f93c.svg?component"); + sport[8].svg = await import("./assets/pazzle/emoji_u1f6b4.svg?component"); - // sport[9].svg = await import("./assets/pazzle/emoji_u1fa82.svg?component"); - // sport[10].svg = await import("./assets/pazzle/emoji_u26bd.svg?component"); - // sport[11].svg = await import("./assets/pazzle/emoji_u1f3c0.svg?component"); + sport[9].svg = await import("./assets/pazzle/emoji_u1fa82.svg?component"); + sport[10].svg = await import("./assets/pazzle/emoji_u26bd.svg?component"); + sport[11].svg = await import("./assets/pazzle/emoji_u1f3c0.svg?component"); - // sport[12].svg = await import("./assets/pazzle/emoji_u1f3be.svg?component"); - // sport[13].svg = await import("./assets/pazzle/emoji_u1f3d3.svg?component"); - // sport[14].svg = await import("./assets/pazzle/emoji_u1f94b.svg?component"); + sport[12].svg = await import("./assets/pazzle/emoji_u1f3be.svg?component"); + sport[13].svg = await import("./assets/pazzle/emoji_u1f3d3.svg?component"); + sport[14].svg = await import("./assets/pazzle/emoji_u1f94b.svg?component"); /************** BIGGER ANIMAL *********************/ diff --git a/ng-net/src/app_protocol.rs b/ng-net/src/app_protocol.rs index a2a6aaf..0537d77 100644 --- a/ng-net/src/app_protocol.rs +++ b/ng-net/src/app_protocol.rs @@ -238,10 +238,27 @@ impl NuriV0 { } } + pub fn to_store_nuri_string(store_id: &RepoId) -> String { + let overlay_id = OverlayId::outer(store_id); + format!("o:{store_id}:v:{overlay_id}") + } + pub fn repo_graph_name(repo_id: &RepoId, overlay_id: &OverlayId) -> String { format!("{DID_PREFIX}:o:{repo_id}:v:{overlay_id}") } + pub fn repo_skolem( + prefix: &String, + peer_id: &Vec, + random: u128, + ) -> Result { + let mut arr = Vec::with_capacity(32); + arr.extend_from_slice(peer_id); + arr.extend_from_slice(&random.to_be_bytes()); + let sko: SymKey = arr.as_slice().try_into()?; + Ok(format!("{prefix}:u:{sko}")) + } + pub fn overlay_id(overlay_id: &OverlayId) -> String { format!("{DID_PREFIX}:v:{overlay_id}") } @@ -612,7 +629,10 @@ impl AppSessionStart { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum DocQuery { - V0(String), // Sparql + V0 { + sparql: String, + base: Option, + }, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -718,8 +738,8 @@ pub enum AppRequestPayload { } impl AppRequestPayload { - pub fn new_sparql_query(query: String) -> Self { - AppRequestPayload::V0(AppRequestPayloadV0::Query(DocQuery::V0(query))) + pub fn new_sparql_query(sparql: String, base: Option) -> Self { + AppRequestPayload::V0(AppRequestPayloadV0::Query(DocQuery::V0 { sparql, base })) } pub fn new_discrete_update( head_strings: Vec, diff --git a/ng-oxigraph/src/oxrdf/parser.rs b/ng-oxigraph/src/oxrdf/parser.rs index d0386bb..987972b 100644 --- a/ng-oxigraph/src/oxrdf/parser.rs +++ b/ng-oxigraph/src/oxrdf/parser.rs @@ -192,8 +192,7 @@ fn read_blank_node(s: &str) -> Result<(BlankNode, &str), TermParseError> { if let Some(remain) = s.strip_prefix("_:") { let end = remain .find(|v: char| { - v.is_whitespace() - || matches!(v, '<' | '_' | '?' | '$' | '"' | '\'' | '>' | '@' | '^') + v.is_whitespace() || matches!(v, '<' | '?' | '$' | '"' | '\'' | '>' | '@' | '^') }) .unwrap_or(remain.len()); let (value, remain) = remain.split_at(end); diff --git a/ng-sdk-js/README.md b/ng-sdk-js/README.md index 3905332..1d98d55 100644 --- a/ng-sdk-js/README.md +++ b/ng-sdk-js/README.md @@ -30,10 +30,10 @@ npm i ng-sdk-js The API is divided in 4 parts: -- the wallet API that lets user open and change their wallet and use its credentials -- the LocalVerifier API to open the documents locally -- the RemoteVerifier API that is connecting to the ngd server and runs the verifier on the server. -- a special mode of operation for ngd called `Headless` where all the users of that server have given full control of their data, to the server. +- the wallet API that lets user open and change their wallet and use its credentials +- the LocalVerifier API to open the documents locally +- the RemoteVerifier API that is connecting to the ngd server and runs the verifier on the server. +- a special mode of operation for ngd called `Headless` where all the users of that server have given full control of their data, to the server. All of those API share a common `Session API` (all the functions that have a session_id as first argument) @@ -73,29 +73,29 @@ entrust the credentials of user to an ngd server. coming soon ## Headless API -- `ng.init_headless(config)` must be called before any other call. -- `ng.admin_create_user(config)` creates a new user on the server, and populates their 3P stores. returns the user_id -- `ng.session_headless_start(user_id)` starts a new session for the user. returns the session info, including the session_id -- `ng.sparql_query(session_id, "[SPARQL query]")` returns or: - - for SELECT queries: a JSON Sparql Query Result as a Javascript object. [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/) - - for CONSTRUCT queries: a list of quads in the format [RDF-JS data model](http://rdf.js.org/data-model-spec/) that can be used as ingress to RDFjs lib. - - for ASK queries: a boolean -- `ng.sparql_update(session_id, "[SPARQL update]")` returns nothing, but can throw an error. -- `ng.file_put_to_private_store(session_id,"[filename]","[mimetype]")` returns the Nuri (NextGraph URI) of the file, as a string. -- `ng.file_get_from_private_store(session_id, "[nuri]", callback)` returns a cancel function. The `callback(file)` function will be called as follow - - once at first with some metadata information in `file.V0.FileMeta` - - one or more times with all the blobs of data, in `file.V0.FileBinary` - - finally, one more time with `file.V0 == 'EndOfStream'`. See the example on how to reconstruct a buffer out of this. -- `ng.session_headless_stop(session_id, force_close)` stops the session, but doesn't close the remote verifier, except if force_close is true. if false, the verifier is detached from the session and continues to run on the server. A new session can then be reattached to it, by calling session_headless_start with the same user_id. +- `ng.init_headless(config)` must be called before any other call. +- `ng.admin_create_user(config)` creates a new user on the server, and populates their 3P stores. returns the user_id +- `ng.session_headless_start(user_id)` starts a new session for the user. returns the session info, including the session_id +- `ng.sparql_query(session_id, "[SPARQL query]", base, nuri)` returns or: + - for SELECT queries: a JSON Sparql Query Result as a Javascript object. [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/) + - for CONSTRUCT queries: a list of quads in the format [RDF-JS data model](http://rdf.js.org/data-model-spec/) that can be used as ingress to RDFjs lib. + - for ASK queries: a boolean +- `ng.sparql_update(session_id, "[SPARQL update]")` returns nothing, but can throw an error. +- `ng.file_put_to_private_store(session_id,"[filename]","[mimetype]")` returns the Nuri (NextGraph URI) of the file, as a string. +- `ng.file_get_from_private_store(session_id, "[nuri]", callback)` returns a cancel function. The `callback(file)` function will be called as follow + - once at first with some metadata information in `file.V0.FileMeta` + - one or more times with all the blobs of data, in `file.V0.FileBinary` + - finally, one more time with `file.V0 == 'EndOfStream'`. See the example on how to reconstruct a buffer out of this. +- `ng.session_headless_stop(session_id, force_close)` stops the session, but doesn't close the remote verifier, except if force_close is true. if false, the verifier is detached from the session and continues to run on the server. A new session can then be reattached to it, by calling session_headless_start with the same user_id. Here is the format of the config object to be supplied in the calls to `init_headless` and `admin_create_user`: ```js config = { - server_peer_id: "[your server ID]", - admin_user_key: "[your admin key]", - client_peer_key: "[the client key]", - server_addr: "[IP and PORT of the server]", // this one is optional. it will default to localhost:1440. Format is: A.A.A.A:P for IPv4 or [AAAA:::]:P for IpV6 + server_peer_id: "[your server ID]", + admin_user_key: "[your admin key]", + client_peer_key: "[the client key]", + server_addr: "[IP and PORT of the server]", // this one is optional. it will default to localhost:1440. Format is: A.A.A.A:P for IPv4 or [AAAA:::]:P for IpV6 }; ``` @@ -182,9 +182,9 @@ If you have configured a domain, then the web app can be accessed at https://app Licensed under either of -- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - at your option. +- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + at your option. `SPDX-License-Identifier: Apache-2.0 OR MIT` diff --git a/ng-sdk-js/app-node/index.js b/ng-sdk-js/app-node/index.js index bc8026d..234fdc8 100644 --- a/ng-sdk-js/app-node/index.js +++ b/ng-sdk-js/app-node/index.js @@ -15,7 +15,8 @@ global.WebSocket = WebSocket; let config = { server_peer_id: "FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA", admin_user_key: "pye0YFzk1ix1amKEwd6AeqaUAN_PNpH5zGLomh0M1PAA", - client_peer_key: "GRP0QnlzaB8o2vdiBaNoOYDNOFX-uehLZMxeCaG3JA0A" + client_peer_key: "GRP0QnlzaB8o2vdiBaNoOYDNOFX-uehLZMxeCaG3JA0A", + server_addr: "127.0.0.1:14400" }; ng.init_headless(config).then( async() => { @@ -24,7 +25,9 @@ ng.init_headless(config).then( async() => { //let user_id = await ng.admin_create_user(config); //console.log("user created: ",user_id); - let user_id = "tJVG293o6xirl3Ys5rzxMgdnPE_1d3IPAdrlR5qGRAIA"; + let user_id = "NnAJWxO-KapuWyCm7RGwO5VszZwaJARGit-i3i1mXbkA"; + + let base = "did:ng:o:8mqfhoSprneBjkAASinRk0OYvFpbiyhjMBVHKQIarDEA:v:dmn9xLARD-LrCz1tdmRiTKelikOCadGEvsLklUrwee4A"; let session = await ng.session_headless_start(user_id); session_id = session.session_id; @@ -33,38 +36,55 @@ ng.init_headless(config).then( async() => { let dump = await ng.rdf_dump(session.session_id); console.log(dump); - let sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?p ?o WHERE { ?s ?p ?o }"); + console.log("******** SELECT") + + let sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?p ?o WHERE { ?s ?p ?o }", base); console.log(sparql_result); for (const q of sparql_result.results.bindings) { console.log(q); } - let history = await ng.branch_history(session.session_id); - for (const h of history.history) { - console.log(h[0], h[1]); - } - console.log(history.swimlane_state); - + // let history = await ng.branch_history(session.session_id); + // for (const h of history.history) { + // console.log(h[0], h[1]); + // } + // console.log(history.swimlane_state); + + //await ng.sparql_update(session.session_id, "INSERT DATA { }"); // await ng.sparql_update(session.session_id, "DELETE DATA { }"); // await ng.sparql_update(session.session_id, "INSERT DATA { }"); // await ng.sparql_update(session.session_id, "INSERT { ?s } WHERE { ?s } "); // await ng.sparql_update(session.session_id, "INSERT DATA { . }"); + //await ng.sparql_update(session.session_id, "INSERT DATA { [ ]. }"); + //await ng.sparql_update(session.session_id, "INSERT DATA { [ ] . }"); + //await ng.sparql_update(session.session_id, "INSERT { ?a . } WHERE { ?a } "); + //await ng.sparql_update(session.session_id, "INSERT DATA { _:1 . _:1 . }"); + //await ng.sparql_update(session.session_id, "INSERT DATA { _:f766ca988268ddc60315ddd5bd621387 . }"); + //await ng.sparql_update(session.session_id, "INSERT { _:_ . } WHERE { _:_ } "); + //await ng.sparql_update(session.session_id, "INSERT DATA { _:_ . _:_a . }"); - sparql_result = await ng.sparql_query(session.session_id, "SELECT ?a WHERE { ?a _:abc. _:abc }"); + //await ng.sparql_update(session.session_id, "INSERT DATA { <> . }",base); + + await ng.sparql_update(session.session_id, "INSERT DATA { _:_ . _:_ . }", base); + await ng.sparql_update(session.session_id, "INSERT DATA { [ ]. }", base); + + sparql_result = await ng.sparql_query(session.session_id, "SELECT ?a WHERE { ?a _:abc. _:abc }", base); console.log(sparql_result); for (const q of sparql_result.results.bindings) { console.log(q); } - sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?a WHERE { ?s ?a }"); + sparql_result = await ng.sparql_query(session.session_id, "SELECT ?s ?a WHERE { ?s ?a }", base); console.log(sparql_result); for (const q of sparql_result.results.bindings) { console.log(q); } - let quads = await ng.sparql_query(session.session_id, "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }"); + console.log("******** CONSTRUCT") + + let quads = await ng.sparql_query(session.session_id, "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }",base); for (const q of quads) { console.log(q.subject.toString(), q.predicate.toString(), q.object.toString(), q.graph.toString()) } diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs index a999f2a..e06a28f 100644 --- a/ng-sdk-js/src/lib.rs +++ b/ng-sdk-js/src/lib.rs @@ -268,6 +268,7 @@ pub async fn session_headless_stop(session_id: JsValue, force_close: bool) -> Re pub async fn sparql_query( session_id: JsValue, sparql: String, + base: JsValue, nuri: JsValue, ) -> Result { let session_id: u64 = serde_wasm_bindgen::from_value::(session_id) @@ -278,10 +279,16 @@ pub async fn sparql_query( NuriV0::new_entire_user_site() }; + let base_opt = if base.is_string() { + Some(base.as_string().unwrap()) + } else { + None + }; + let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_read_query(), nuri, - payload: Some(AppRequestPayload::new_sparql_query(sparql)), + payload: Some(AppRequestPayload::new_sparql_query(sparql, base_opt)), session_id, }); @@ -361,16 +368,22 @@ pub async fn discrete_update( pub async fn sparql_update( session_id: JsValue, sparql: String, - nuri: String, + nuri: JsValue, ) -> Result<(), String> { let session_id: u64 = serde_wasm_bindgen::from_value::(session_id) .map_err(|_| "Invalid session_id".to_string())?; - let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; + + let (nuri, base) = if nuri.is_string() { + let n = nuri.as_string().unwrap(); + (NuriV0::new_from(&n).map_err(|e| e.to_string())?, Some(n)) + } else { + (NuriV0::new_private_store_target(), None) + }; let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_write_query(), nuri, - payload: Some(AppRequestPayload::new_sparql_query(sparql)), + payload: Some(AppRequestPayload::new_sparql_query(sparql, base)), session_id, }); @@ -389,6 +402,7 @@ pub async fn sparql_update( pub async fn sparql_query( session_id: JsValue, sparql: String, + base: JsValue, nuri: JsValue, ) -> Result { let session_id: u64 = serde_wasm_bindgen::from_value::(session_id) @@ -399,10 +413,16 @@ pub async fn sparql_query( } else { NuriV0::new_entire_user_site() }; + let base_opt = if base.is_string() { + Some(base.as_string().unwrap()) + } else { + None + }; + let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_read_query(), nuri, - payload: Some(AppRequestPayload::new_sparql_query(sparql)), + payload: Some(AppRequestPayload::new_sparql_query(sparql, base_opt)), session_id, }); @@ -460,13 +480,19 @@ pub async fn rdf_dump(session_id: JsValue) -> Result { } #[wasm_bindgen] -pub async fn branch_history(session_id: JsValue, nuri: String) -> Result { +pub async fn branch_history(session_id: JsValue, nuri: JsValue) -> Result { let session_id: u64 = serde_wasm_bindgen::from_value::(session_id) .map_err(|_| "Invalid session_id".to_string())?; + let nuri = if nuri.is_string() { + NuriV0::new_from(&nuri.as_string().unwrap()).map_err(|e| e.to_string())? + } else { + NuriV0::new_private_store_target() + }; + let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_history(), - nuri: NuriV0::new_from(&nuri).map_err(|e| e.to_string())?, + nuri, payload: None, session_id, }); diff --git a/ng-verifier/src/commits/transaction.rs b/ng-verifier/src/commits/transaction.rs index 17b2a39..85dd63d 100644 --- a/ng-verifier/src/commits/transaction.rs +++ b/ng-verifier/src/commits/transaction.rs @@ -20,7 +20,9 @@ use yrs::updates::decoder::Decode; use yrs::{ReadTxn, StateVector, Transact, Update}; use ng_net::app_protocol::*; -use ng_oxigraph::oxrdf::{GraphName, GraphNameRef, NamedNode, Quad, Triple, TripleRef}; +use ng_oxigraph::oxrdf::{ + BlankNode, GraphName, GraphNameRef, NamedNode, Quad, Subject, Term, Triple, TripleRef, +}; use ng_repo::errors::VerifierError; use ng_repo::log::*; use ng_repo::store::Store; @@ -668,21 +670,63 @@ impl Verifier { &mut self, nuri: &NuriV0, query: &String, + base: &Option, + peer_id: Vec, ) -> Result<(), String> { let store = self.graph_dataset.as_ref().unwrap(); + let update = ng_oxigraph::oxigraph::sparql::Update::parse(query, base.as_deref()) + .map_err(|e| e.to_string())?; + let res = store.ng_update( - query, + update, self.resolve_target_for_sparql(&nuri.target, true) .map_err(|e| e.to_string())?, ); match res { Err(e) => Err(e.to_string()), - Ok((inserts, removes)) => { + Ok((mut inserts, removes)) => { if inserts.is_empty() && removes.is_empty() { Ok(()) } else { - self.prepare_sparql_update(Vec::from_iter(inserts), Vec::from_iter(removes)) + let mut new_inserts = Vec::with_capacity(inserts.len()); + for mut quad in inserts.drain() { + //log_debug!("INSERTING BN {}", quad); + if quad.subject.is_blank_node() { + if base.is_none() { + return Err("Cannot insert blank nodes without a base".to_string()); + } + //log_debug!("INSERTING SUBJECT BN {}", quad.subject); + if let Subject::BlankNode(b) = &quad.subject { + let iri = NuriV0::repo_skolem( + base.as_ref().unwrap(), + &peer_id, + b.as_ref().unique_id().unwrap(), + ) + .map_err(|e| e.to_string())?; + quad.subject = Subject::NamedNode(NamedNode::new_unchecked(iri)); + } + } + if quad.object.is_blank_node() { + if base.is_none() { + return Err("Cannot insert blank nodes without a base".to_string()); + } + //log_debug!("INSERTING OBJECT BN {}", quad.object); + if let Term::BlankNode(b) = &quad.object { + let iri = NuriV0::repo_skolem( + base.as_ref().unwrap(), + &peer_id, + b.as_ref().unique_id().unwrap(), + ) + .map_err(|e| e.to_string())?; + quad.object = Term::NamedNode(NamedNode::new_unchecked(iri)); + } + } + // TODO deal with triples in subject and object (RDF-STAR) + new_inserts.push(quad); + } + + self.prepare_sparql_update(new_inserts, Vec::from_iter(removes)) .await .map_err(|e| e.to_string()) } diff --git a/ng-verifier/src/request_processor.rs b/ng-verifier/src/request_processor.rs index 43a0197..7ba42cf 100644 --- a/ng-verifier/src/request_processor.rs +++ b/ng-verifier/src/request_processor.rs @@ -273,7 +273,9 @@ impl Verifier { NuriV0::repo_graph_name(doc_create.store.repo_id(), &overlay_id); let query = format!("INSERT DATA {{ <{store_nuri_string}> <{nuri}>. }}"); - let ret = self.process_sparql_update(&store_nuri, &query).await; + let ret = self + .process_sparql_update(&store_nuri, &query, &None, vec![]) + .await; if let Err(e) = ret { return Ok(AppResponse::error(e)); } @@ -287,19 +289,22 @@ impl Verifier { } AppRequestCommandV0::Fetch(fetch) => match fetch { AppFetchContentV0::ReadQuery => { - if let Some(AppRequestPayload::V0(AppRequestPayloadV0::Query(DocQuery::V0( - query, - )))) = payload + if let Some(AppRequestPayload::V0(AppRequestPayloadV0::Query(DocQuery::V0 { + sparql, + base, + }))) = payload { //log_debug!("query={}", query); let store = self.graph_dataset.as_ref().unwrap(); - let parsed = Query::parse(&query, None); + let parsed = Query::parse(&sparql, base.as_deref()); if parsed.is_err() { return Ok(AppResponse::error(parsed.unwrap_err().to_string())); } let mut parsed = parsed.unwrap(); let dataset = parsed.dataset_mut(); + //log_debug!("DEFAULTS {:?}", dataset.default_graph_graphs()); if dataset.has_no_default_dataset() { + //log_info!("DEFAULT GRAPH AS UNION"); dataset.set_default_graph_as_union(); } let results = store @@ -323,13 +328,23 @@ impl Verifier { return Err(NgError::InvalidNuri); } return if let Some(AppRequestPayload::V0(AppRequestPayloadV0::Query( - DocQuery::V0(query), + DocQuery::V0 { sparql, base }, ))) = payload { - Ok(match self.process_sparql_update(&nuri, &query).await { - Err(e) => AppResponse::error(e), - Ok(_) => AppResponse::ok(), - }) + Ok( + match self + .process_sparql_update( + &nuri, + &sparql, + &base, + self.get_peer_id_for_skolem(), + ) + .await + { + Err(e) => AppResponse::error(e), + Ok(_) => AppResponse::ok(), + }, + ) } else { Err(NgError::InvalidPayload) }; diff --git a/ng-verifier/src/verifier.rs b/ng-verifier/src/verifier.rs index 9f1661a..d4f4215 100644 --- a/ng-verifier/src/verifier.rs +++ b/ng-verifier/src/verifier.rs @@ -122,6 +122,10 @@ struct EventOutboxStorage { } impl Verifier { + pub(crate) fn get_peer_id_for_skolem(&self) -> Vec { + self.peer_id.to_dh_slice()[0..16].to_vec() + } + pub fn complement_credentials(&self, creds: &mut Credentials) { creds.private_store = self.private_store_id().clone(); creds.protected_store = self.protected_store_id().clone(); @@ -2416,6 +2420,7 @@ impl Verifier { fn add_repo_(&mut self, repo: Repo) -> &Repo { //self.populate_topics(&repo); + let _ = self.add_doc(&repo.id, &repo.store.overlay_id); let repo_ref = self.repos.entry(repo.id).or_insert(repo); repo_ref } @@ -2547,7 +2552,15 @@ impl Verifier { mod test { use crate::verifier::*; + use ng_oxigraph::oxrdf::BlankNode; use ng_repo::store::Store; + use std::str::FromStr; + + #[test] + pub fn test_blank_node() { + let bn = BlankNode::from_str("_:____").expect("parse"); + log_debug!("{:?}", bn); + } #[async_std::test] pub async fn test_new_repo_default() {