My first app - adding resources?


#1

Okay, I’ve made my first “lunch break app” and got it running with WebID login. Next step is to build something simple that writes data to my POD. So I’ve dived into JavaScript programming with Vue and Bootstrap to make a single-page web-app that allows me to enter logbook entries - Click “New entry”, enter date and text and update HTML DOM using Vue to show the new entry. So far, so good.

But how do I then store that in my POD?

I’ve been reading through the " Linked Data Platform 1.0 Primer" at https://www.w3.org/TR/ldp-primer/ as well as Solid’s intro to RDFLIB at https://github.com/solid/solid-tutorial-rdflib.js. Reading/querying RDF data seems to be simple … but where is the code example that shows how to use RDFLIB to do the following:

  1. Check if my POD has a container for my lognotes: does my POD have a container at /public/logbook ?

  2. If there is no container at /public/logbook then create a new one.

  3. Assuming the logbook container exists, create new resources in the container for my logbook entries. What if I want to use the “slug” header to name the new resource? Where to I state the “link” header that declares the type of my new resource? Can that be done at all in RDFLIB?

  4. Delete resources.

I probably wouldn’t have much problem doing all that with HTTP from server side code in C# - but I’m absolutely lost in the world of JavaScript :slight_smile:

Can anyone point me to the right tutorial section or sample code?

And there is the issue of being able to grant my web-app access to my private containers, which I haven’t figured out yet - see How to change the Sharing settings for a resource?

Thanks.


How to define rdf for interchangeability between apps
Question about retrieving data
RDFLIB documentation
#2

I think I can answer at least one of your questions - about the slug and the link:

// add a file to a directory
//
var link = ‘http://www.w3.org/ns/ldp#Resource; rel=“type”’
var filename = ‘myfile.ttl’
var parentFolder = “https://me.solid.community/public/somepath/
var request = {
method : ‘POST’,
headers : { ‘Content-Type’:‘text/turtle’,slug:filename,link:link }
}
solid.auth.fetch( parentFolder, request ) // …


#3

and add body : content-of-the-file after headers to both create the file and add content in the same fetch.


#4

My fork of Solid Plume is a simple blog app which does all those functions IIRC.

The original Plume was old but I updated/bugfixed it a bit so it did work (with SAFE as a backend). I chose it because it is so simple, and made it easier for me to understand what it was doing, so I think it’s still good for this kind of learning.

So in case it’s of use, see


#5

Thanks Mark. I did actually scroll through Plume as one of the mentioned example applications. But I was surprised to see how much manual HTTP work you had implemented and was expecting to be able to use something like RDFLIB.js for more of that kind of heavy lifting.

Besides that, the solid Git repository at https://github.com/solid/solid-apps refers to https://github.com/deiu/solid-plume/ which is different from yours (and the one I was previously looking at).

But at least I have some samples now - thanks!


#6

I think it would be great if developers new to JavaScript could use https://github.com/solid/react-components to make new apps


#7

@JornWildt I’ve found the most help on this topic from these resources:

  1. Inrupt’s guide to Manipulating LD with rdflib
  2. The docs for rdflib.js (and sometimes the src).
  3. The rdf js draft for a better understanding of the terms (namedNode, graph, etc.)

#8

Thanks. I’ve been trying to understand Inrupt’s guide, but there are a few funky corners I fail to understand. For instance:

[about the updater]
Just as the Fetcher allows the store to read and write resources from the web, generally a resource (file) at a time, the UpdateManager object allows the store to send small changes to the data web.

So apparently the Fetcher can do updates - but how? I can insert quads as they describe:

store.add(me, VCARD(‘fn’), “John Bloggs”, profile);

which will add the triple <me, fn, “John Bloggs”> to the profile document. But does that mean the original profile document on the web gets updated instantaneously? If not, where is then the “sync” method that initiates the write operation?

And, either using Fetcher or Updater, what if the fourth quad (document) element does not exist already - how will the document/resource be created? Where can I specify the “slug” and “link” headers for the new resource?

/Jørn


#9

Just noticed how the data browser creates folders - nothing about “slug” or “link” headers here. It simply creates an empty “.dummy” file under the folder name and leave it to the server to create the folder itself:

PUT https://elfisk.solid.community/public/solidrc/.dummy HTTP/1.1
Host: elfisk.solid.community
Content-Length: 0
Origin: https://elfisk.solid.community
Accept: */*
Referer: https://elfisk.solid.community/public/
Accept-Encoding: gzip, deflate, br
Accept-Language: da-DK,da;q=0.9,en-US;q=0.8,en;q=0.7,sv;q=0.6,nb;q=0.5
DNT: 1

and then deletes it again:

DELETE https://elfisk.solid.community/public/solidrc/.dummy HTTP/1.1
Host: elfisk.solid.community
Origin: https://elfisk.solid.community
Accept: */*
Referer: https://elfisk.solid.community/public/
Accept-Encoding: gzip, deflate, br
Accept-Language: da-DK,da;q=0.9,en-US;q=0.8,en;q=0.7,sv;q=0.6,nb;q=0.5
DNT: 1

#10

Finally found an easy way to create new resources using the standard utilities. Browsing through the Fetcher source “indexed-formula.js” I found “putBack”:

let store = $rdf.graph();
let fetcher = new $rdf.Fetcher(store);

let entry = store.sym(LogbookRepository.logBookUrl + 'entry');
store.add(entry, NS_SOLIDRC('fn'), 'TEST', entry);

fetcher.putBack(entry);

The above code adds a new resource at “logBookUrl/entry” with one single RDF statement.

(beware, this seems like an older version of rdflib.js as “add” is “addEntry” in the github repo).


#11

Continuing on this, I also found this in update-manager.js:

/**
 * This is suitable for an initial creation of a document
 *
 * @param doc {Node}
 * @param data {string|Array<Statement>}
 * @param contentType {string}
 * @param callback {Function}  callback(uri, ok, message, response)
 *
 * @throws {Error} On unsupported content type (via serialize())
 *
 * @returns {Promise}
 */
put (doc, data, contentType, callback) {

I haven’t tried it, but it seems to do what I want.


#12

For what it is worth, I actually managed to pull off a little “append only” (so far) application that depends on RDFLIB only without using any direct HTTP actions. It helps a lot reading through the RDFLIB source to figure out how it is supposed to be used.

I have tried to make a readable little “logbook entry” repository and put it on Github: https://github.com/JornWildt/SolidRC/blob/master/wwwroot/js/logbookRepository.js (comments are welcome, should you have any)

I’ll post a few more notes here as it develops (adding delete and update features). If someone feels that is an inappropriate use of this forum then please let me know.

The logbook is for logging flights with model airplanes (which explains some of the values present on the entries).

The UI/UX so far is not relevant - and it is hard coded to my WebID.


#13

Hi, for 3. you won’t need the Slug

    solid.auth.fetch('https://roger.localhost:8443/public/'+file, {
       method: 'PUT', // or 'PUT'
       headers:{
    'Content-Type': 'text/plain',
        'Content-Length': data.length.toString()
   },
       body: data // data can be `string` or {object}!
       }).then((res) => {return res;})
    .then((response) => {callback(null);})
    .catch((error) => {callback('Error: '+JSON.stringify(error));});

will write a resource into a container where file is the name such as primary-disk.scad and data is text content


#14

for 2. you need the Slug and Link

        solid.auth.fetch('https://roger.localhost:8443/public/', {
           method: 'POST', // or 'PUT'
           headers:{
            'Content-Type': 'text/turtle',
	    'Link': '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"',
            'Slug':  'logbook'
	   }
           }).then((res) => {return res;})
        .then((response) => {callback(null);})
        .catch((error) => {callback('Error: '+JSON.stringify(error));});

to create an empty container inside another container


#15
  1. is a DELETE

    solid.auth.fetch('https://roger.localhost:8443/public/’+file, {
    method: ‘DELETE’
    }).then(res => {return res;})
    .then((response) => {callback(null);})
    .catch(error => callback('Error: '+JSON.stringify(error)));


#16

I have seen the data browser simply doing a PUT …/X/.dummy and then DELETE …/X/.dummy to create the empty container …/X. No need for slug or special headers that way.


#17

I’ve managed to use RDFLIB without any low level HTTP work. Add, Query, Delete works like this:

Adding resource at URL X:

  • Add statements <X, some-predicate, some-value, X> for each property of the resource. Use store.add(X,p,v,X) to save the statements locally;
  • PUT the new statements onto the web: Use fetcher.putBack(X);

Reading list of resources:

  • Load all resources into local store using “globbing”. Use fetcher.load('baseUrl/*")
  • Extract all the found data from the local store. Use store.match(…)

Remove a single resource at url X:

  • Remove the resource from the web. Use fetcher.delete(X).
  • Remove local knowledge of the resource: Use store.removeDocument(X).
  • Remove all local statements: Use store.removeMatches(X);

See my repositories at https://github.com/JornWildt/SolidRC/tree/master/wwwroot/js for an implementation.

Now I just need to get the Update right.


#18

yea but that’s creating a dummy and deleting; the Slug lets you create an empty container


#19

Hi Jorn,

Container - you mean data stores? Can a solid POD have more than 1 data store and corresponding fetcher?


#20

I mean container - see https://www.w3.org/TR/ldp/#ldpc. Containers are like folders on a harddisk.