Working with calendars in Community Solid Server

Calendars are probably one of things that we cannot do without, whether it is for scheduling our work meetings, planning our holidays, or keeping track of all our weekend activities. But scheduling a meeting with someone else can be quite the hassle. You can send an email or message, but that does not work with multiple people. You can create a Doodle, that half of the people fill in too late or not at all. Or you can check their shared work calendar and send an invitation for a meeting on a free moment. Only to get a decline in return, because they have a personal appointment that is not in the shared calendar.

In this blog post we describe how you can avoid wasting your time like that, by showing how you can define different types of calendars based on your existing calendars and share them with others. For this, we use the Community Solid Server and our Solid Calendar Store. Note that this is not a tutorial: we do not discuss every detail of the server and the store. There is an accompanying presentation in PPTX and PDF.

Running example

Consider the following example. You maintain these two calendars:

Imagine that one of your colleagues wants to schedule a meeting with you. They have direct access to all details of your work calendar, so they can see when you are free. Based on this calendar, your colleague sends you an invitation for a meeting coming Friday at 15:00. Although, you do not have any meetings planned at that moment, you are not available for a meeting with them because you are traveling with your family to Brussels for the weekend. This information is not available in your work calendar, but it is available in your personal calendar. An easy solution would be to just share this calendar as well with your colleagues. But, you might want to keep certain information in this calendar private, such as medical appointments.

Another solution is the creation of a new calendar based on personal that only shares the start and end dates of your events. Each event has as title “Busy”. We call this calendar personal (external), because this calendar can be shared with external people. You share this calendar with your colleagues. They have to check now two calendars, work and personal (external), to find a time slot for a meeting. This does not scale if you share more calendars with them. And it gets worse when a meeting has to be scheduled with multiple people with each person having multiple calendars.

A better solution is the creation of an aggregated calendar called busy based on work and personal (external). This calendar contains all moments that you are busy. Work meetings have all details, but personal events have as title “Busy”. You share this single calendar with your colleagues. Now, they have a single calendar that they can use for scheduling a meeting with you. The number of total calendars to check scales with the number of people that are participating in the meeting.

Overview of how the different calendars are related to each other.

In the above image you find an overview of how the different calendars are related to each other. We remove the details of the personal calendar through a transformation. This new calendar is aggregated with the work calendar, resulting in a new calendar with all busy events.

Using custom stores to add functionality

Overview of how the different stores are related to each other.

One of the Community Solid Server’s main features is its extensibility: it allows us to support new types of data and functionality, through the use of custom stores. In our case, we want to support

Stores represent collections of resources and allow manipulating these resources. In our case, these resources are calendars and in the Solid Calendar Store we defined different stores that allow us to manipulate calendars. In the above image you find an overview of these stores and how they are related to each other. We discuss the details in the following sections.

Transforming calendars

In our example we need to transform the personal calendar: only keep the start and end date of the events and replace their titles with “Busy”. In the code snippets below we explain the different parts of the configuration file that we provide to the server. For more details we refer to the documentation of our store and for a working configuration of the server to the full example.

{
  "@id": "my-calendar:PersonalGet",
  "@type": "HttpGetStore",
  "HttpGetStore:_options_url": "https://calendar.google.com/calendar/ical/ihqfct3hddiiskgvujr3df2pe0%40group.calendar.google.com/public/basic.ics"
}

In the above snippet, we use an instance of an HTTP GET Store, which is defined in Solid Calendar Store. This store reads the ICS file of our calendar from a URL via an HTTP GET request. We identify this specific instance by my-calendar:PersonalGet.

{
  "@id": "my-calendar:PersonalCalendar",
  "@type": "RepresentationConvertingStore",
  "RepresentationConvertingStore:_source": {
    "@id": "my-calendar:PersonalGet"
  },
  "RepresentationConvertingStore:_options_outConverter": {
    "@id": "solid-calendar-store:RepresentationConverter"
  }
}

We use this HTTP GET Store in the snippet above as a source for an instance of a Representation Converting Store, which is defined in the Community Solid Server. This store enables converting our calendar data to different data representations, through the use of a Representation Converter, which is defined in Solid Calendar Store. We provide it to the Representation Converting Store via the parameter _options_outConverter. This allows us to have a JSON representation of the calendar data, which is originally in ICS. This JSON representation is used by other stores. This specific instance of the Representation Converting Store is identified by my-calendar:PersonalCalendar.

{
  "@id": "my-calendar:PersonalExternalCalendar",
  "@type": "TransformationStore",
  "TransformationStore:_options_rules": ["busy"],
  "TransformationStore:_source": {
    "@id": "my-calendar:PersonalCalendar"
  },
  "TransformationStore:_options_settingsPaths": [
    "personal-external.yaml"
  ]
}

We use the Representation Converting Store in turn as a source for our personal (external) calendar, which is an instance of the Transformation Store. This store is defined by Solid Calendar Store and only works with the JSON representation of a calendar. It allows us to define which transformations to execute via the parameters settingsPaths and rules. The former points to files that contain a list of one or more transformations. The latter selects which transformation should be executed in this store. In this case the busy transformation looks like this:

transformation:
  busy:
    - match: '^.*$'
      replace: "Busy"
      removeFields: [description]

In practice this means that every title of an event that matches ^.*$, which is all titles, is marked as busy by replacing the name of the event with “Busy”. Additionally, the store removes the description field from the event, because we do no want to share this with others.

Aggregating calendars

In our example we need to aggregate the personal (external) and work calendar. In the code snippets below we again explain the different, relevant parts of the configuration file.

{
  "@id": "my-calendar:WorkCalendar",
  "@type": "RepresentationConvertingStore",
  "RepresentationConvertingStore:_source": {
    "@id": "my-calendar:WorkGet"
  },
  "RepresentationConvertingStore:_options_outConverter": {
    "@id": "solid-calendar-store:RepresentationConverter"
  }
},
{
  "@id": "my-calendar:WorkGet",
  "@type": "HttpGetStore",
  "HttpGetStore:_options_url": "https://calendar.google.com/calendar/ical/ihqfct3hddiiskgvujr3df2pe0%40group.calendar.google.com/public/basic.ics"
}

In the snippet above, we define an HTTP GET Store and a Representation Converting Store for the work calendar. This is identical to the personal calendar with exception to the URL serving the ICS file.

{
  "@id": "my-calendar:BusyCalendar",
  "@type": "AggregateStore",
  "AggregateStore:_source1": {
    "@id": "my-calendar:PersonalExternalCalendar"
  },
  "AggregateStore:_source2": {
    "@id": "my-calendar:WorkCalendar"
  }
}

We use an instance of an Aggregate Store, which is defined in Solid Calendar Store, that aggregates two calendars. This specific instance is identified by my-calendar:BusyCalendar and we use the two previously defined calendars: my-calendar:PersonalExternalCalendar and my-calendar:WorkCalendar.

{
  "RegexRouterRule:_storeMap_key": "^/busy$",
  "RegexRouterRule:_storeMap_value": {
    "@id": "my-calendar:BusyCalendar"
  }
}

Finally, we define which route is connected to our Aggregate Store. In our case we want to return our busy events when requesting the path /busy from our server.

Serving RDF representation of calendars

When we do a GET request to our newly created calendar with the current configuration, we get the result as JSON. For example, curl http://localhost:3000/busy returns

{
  "name":"Aggregated calendar of Personal example and Work example",
  "events":[
    {
      "title":"[Personal example] Busy",
      "startDate":"2021-04-08T15:00:00.000Z",
      "endDate":"2021-04-08T17:00:00.000Z",
      "hash":"c585549052f444427a1528a430c001b5",
      "location":""
    },
    
    // The remainder of the events are excluded for brevity.
  ]
}

When Semantic Web applications want to use this calendar, they prefer the RDF representation of this calendar instead of the JSON. We achieve this by adding the following snippet.

{
  "@id": "urn:solid-server:default:ChainedConverter",
  "ChainedConverter:_converters": [{ "@id": "solid-calendar-store:json-to-rdf-converter" }]
}

Solid Calendar Store defines a converter, identified by solid-calendar-store:json-to-rdf-converter, that generates RDF from a JSON representation of a calendar. In the background this converter uses RML.io technologies for that, through the Solid RML Store. We add this converter to the Chained Converter of the server, which determines which converter needs to be used based on the request of a client. In our example we can do now curl -H 'accept: text/turtle' http://localhost:3000/busy, which returns

@prefix schema: <http://schema.org/> .
@prefix calendar: <http://example.com/calendar/> .
@prefix event: <http://example.com/event/> .

calendar:Aggregated%20calendar%20of%20Personal%20Time%20example%20and%20Work%20example
    schema:event event:09ec6f847b05ebc845da09f9777bca98,
        event:500fa723b22615892e91b64405b5a32c, event:98dd5445ee418eb6a0aeeb1da537a9a4,
        event:b22e5eed1e7599d6ea64d9e1b1150bfb, event:c585549052f444427a1528a430c001b5;
  schema:name "Aggregated calendar of Personal example and Work example" .

event:09ec6f847b05ebc845da09f9777bca98 a schema:Event;
  schema:description "";
  schema:endDate "2021-08-03T18:00:00.000Z";
  schema:location "";
  schema:name "[Work example] Meeting";
  schema:startDate "2021-08-03T16:15:00.000Z" .

# The remainder of the events are excluded for brevity.

Serving ICS representation of calendars

Beside Semantic Web applications, you might want to include your new calendars directly in your existing calendar applications, such as Google Calendar. We achieve this by supporting ICS output for our calendars. Similar to the supporting an RDF representation, we add a JSON to ICS converter:

{
  "@id": "urn:solid-server:default:ChainedConverter",
  "ChainedConverter:_converters": [{ "@id": "JsonToIcsConverter" }]
}

In our example we can do now curl -H 'accept: text/calendar' http://localhost:3000/busy, which returns

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//sebbo.net//ical-generator//EN
NAME:Aggregated calendar of Free Time example and Work example
X-WR-CALNAME:Aggregated calendar of Free Time example and Work example
BEGIN:VEVENT
UID:7abb9807-4d85-47db-81da-10ccca0a7672
SEQUENCE:0
DTSTAMP:20210817T122516Z
DTSTART:20210408T150000Z
DTEND:20210408T170000Z
SUMMARY:[Free Time example] Busy
END:VEVENT
BEGIN:VEVENT
UID:dd379c3c-b653-4a49-91e0-78b00cdabe23
SEQUENCE:0
DTSTAMP:20210817T122516Z
DTSTART:20210330T060000Z
DTEND:20210330T160000Z

# The remainder of the events are excluded for brevity.

Wrapping up

We hope you now understand how you can create your own calendars by using the Community Solid Server and Solid Calendar Store. You can find more information about both of them on their respective Github repositories and by checking out the full example.

If you have questions or remarks, don’t hesitate to contact us!

Published on 2021-08-19