diff --git a/drafts/use-cases/0.intro.md b/drafts/use-cases/0.intro.md index 36e9cb0..ea77149 100644 --- a/drafts/use-cases/0.intro.md +++ b/drafts/use-cases/0.intro.md @@ -49,10 +49,18 @@ These would be: "@id": "hydra:title", "@type": "xsd:string" }, + "action": { + "@id": "hydra:action", + "@type": "@id" + }, "operation": { "@id": "hydra:operation", "@type": "@id" }, + "target": { + "@id": "hydra:target", + "@type": "@id" + }, "method": { "@id": "hydra:method", "@type": "xsd:string" @@ -101,13 +109,6 @@ These would be: "@id": "hydra:totalItems", "@type": "xsd:int" }, - "search": { - "@id": "hydra:search", - }, - "Event": { - "@id": "schema:Event", - "@type": "@id" - }, "eventName": { "@id": "schema:name", "@type": "xsd:string" diff --git a/drafts/use-cases/1.1.security-considerations.md b/drafts/use-cases/1.1.security-considerations.md index dcedce2..6e7f7cb 100644 --- a/drafts/use-cases/1.1.security-considerations.md +++ b/drafts/use-cases/1.1.security-considerations.md @@ -25,11 +25,15 @@ There are several possibilities here. One of them would be to use a *@graph* not "property": "rdf:type", "object": "schema:Event" }, - "operation": { - "@type": ["Operation", "schema:CreateAction"], + "action": { + "@type": ["schema:AddAction", "schema:CreateAction"], "title": "Create new event", - "method": "POST", - "expects": "schema:Event" + "operation": { + "@type": "Operation", + "method": "POST", + "expects": "schema:Event", + "target": "/api/events" + } } }, { @@ -40,11 +44,15 @@ There are several possibilities here. One of them would be to use a *@graph* not "property": "rdf:type", "object": "schema:Person" }, - "operation": { - "@type": ["Operation", "schema:CreateAction"], + "action": { + "@type": ["schema:AddAction", "schema:CreateAction"], "title": "Create new person", - "method": "POST", - "expects": "schema:Person" + "operation": { + "@type": "Operation", + "method": "POST", + "expects": "schema:Person", + "target": "/api/people" + } } }, { @@ -55,11 +63,15 @@ There are several possibilities here. One of them would be to use a *@graph* not "property": "rdf:type", "object": "schema:Place" }, - "operation": { - "@type": ["Operation", "schema:CreateAction"], + "action": { + "@type": ["schema:AddAction", "schema:CreateAction"], "title": "Create new venue", - "method": "POST", - "expects": "schema:Place" + "operation": { + "@type": "Operation", + "method": "POST", + "expects": "schema:Place", + "target": "/api/venues" + } } } ] diff --git a/drafts/use-cases/1.entry-point.md b/drafts/use-cases/1.entry-point.md index 691c99a..5e503cf 100644 --- a/drafts/use-cases/1.entry-point.md +++ b/drafts/use-cases/1.entry-point.md @@ -46,11 +46,15 @@ HTTP 200 OK "property": "rdf:type", "object": "schema:Event" }, - "operation": { - "@type": ["Operation", "schema:CreateAction"], + "action": { + "@type": ["schema:AddAction", "schema:CreateAction"], "title": "Create new event", - "method": "POST", - "expects": "schema:Event" + "operation": { + "@type": "Operation", + "method": "POST", + "expects": "schema:Event", + "target": "/api/events", + } } }, { @@ -61,11 +65,15 @@ HTTP 200 OK "property": "rdf:type", "object": "schema:Person" }, - "operation": { - "@type": ["Operation", "schema:CreateAction"], + "action": { + "@type": ["schema:AddAction", "schema:CreateAction"], "title": "Create new person", - "method": "POST", - "expects": "schema:Person" + "operation": { + "@type": "Operation", + "method": "POST", + "expects": "schema:Person", + "target": "/api/people" + } } }, { @@ -76,11 +84,15 @@ HTTP 200 OK "property": "rdf:type", "object": "schema:Place" }, - "operation": { - "@type": ["Operation", "schema:CreateAction"], + "action": { + "@type": ["schema:AddAction", "schema:CreateAction"], "title": "Create new venue", - "method": "POST", - "expects": "schema:Place" + "operation": { + "@type": "Operation", + "method": "POST", + "expects": "schema:Place", + "target": "/api/venues" + } } } ] diff --git a/drafts/use-cases/3.1.extensions-considerations.md b/drafts/use-cases/3.1.extensions-considerations.md index 3debda4..4bce91c 100644 --- a/drafts/use-cases/3.1.extensions-considerations.md +++ b/drafts/use-cases/3.1.extensions-considerations.md @@ -32,12 +32,16 @@ Again, named graphs would come in handy in this situation, i.e.: "@graph": [ { "@id": "/api/events", - "operation": [ + "action": [ { - "@type": ["Operation", "schema:CreateAction"], + "@type": ["schema:AddAction", "schema:CreateAction"], "title": "Create new event", - "method": "POST", - "expects": "schema:Event" + "operation": { + "@type": "Operation", + "method": "POST", + "expects": "schema:Event", + "target": "/api/events" + } } ], "member": [ diff --git a/drafts/use-cases/5.1.creating-event-with-put.md b/drafts/use-cases/5.1.creating-event-with-put.md index 6cbd4ac..e92117e 100644 --- a/drafts/use-cases/5.1.creating-event-with-put.md +++ b/drafts/use-cases/5.1.creating-event-with-put.md @@ -22,28 +22,22 @@ var event = { 'startDate': '2017-04-19', 'endDate': '2017-04-19' }; -var templateVariables = { - slug: [ - 'meeting', - 'with-will' - ] -}; - -var client = new HydraClient(); -var memberTemplate = client.get('http://example.com/events').memberTemplate; -var operation = memberTemplate - .getOperationsOfType('http://schema.org/CreateAction') - .thatExpect('http://schema.org/Event') - .first(); -const operationWithTargetExpanded = operation.expandTarget(templateVariables); -client.invoke(operationWithTargetExpanded, event); +client.usingApi("http://example.com/") + .findCollectionWith({ + property: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + object: "http://schema.org/Event" + }) + .invoke('http://schema.org/CreateAction') + .whichExpects('http://schema.org/Event') + .withData(event) + .getResult(); ``` ## Details -To create an `Event` using `PUT`, the collection resource has to be linked to the actual target of the operation. +To create an `Event` using `PUT`, the collection resource has to advertise an action with a target for the operation. The target of the operation can either be a concrete URL or an `IriTemplate` as shown in the example above. Concrete URLs are out of scope for this use case and might be discussed at a later point in a separate use case document. @@ -66,39 +60,42 @@ HTTP 200 OK }, "totalItems": 0, "members": [ ], - "memberTemplate": { - "@type": "IriTemplate", - "template": "http://example.com/api/event{/slug*}", - "variableRepresentation": "BasicRepresentation", - "mapping": [ - { - "@type": "IriTemplateMapping", - "variable": "slug", - "property": "schema:name", - "required": true - } - ], - "operation": [ - { - "@type": [ "Operation", "schema:CreateAction" ], - "title": "Create new event", + "action": { + "@type": ["schema:AddAction", "schema:CreateAction"], + "title": "create an event and add to collection", + "operation": { + "@type": "Operation", "method": "PUT", - "expects": "schema:Event" - } - ] + "expects": "schema:Event", + "target": { + "@type": "IriTemplate", + "template": "http://example.com/api/events/{uuid}", + "variableRepresentation": "BasicRepresentation", + "mapping": [ + { + "@type": "IriTemplateMapping", + "variable": "uuid", + "property": "schema:uuid", + "required": true + } + ] + } + } } } ``` +We will assume that hydra client understands `schema:uuid` and will automaticaly generate [UUID v4](https://tools.ietf.org/html/rfc4122#section-4.4) and substituted variable in IRI template which maps to it. + Upon executing the snippet above the following request will be sent to the server: ```http -PUT /api/event/meeting/with-will HTTP/1.1 +PUT /api/events/f94ebb70-bf47-4713-8486-386262ca2a12 HTTP/1.1 ``` ```json { - "@context": "/api/context.jsonld", + "@context": "http://example.com/api/context.jsonld", "@type": "schema:Event", "eventName": "My brand new event", "eventDescription": "Hope it will work", @@ -136,7 +133,7 @@ In such case it would respond with `HTTP/1.1 412 Precondition Failed` status. ### Advertising connection between collection and created event -The `schema:CreateAction` operation is attached to collection's `memberTemplate` which informs the client that the newly +The `schema:AddAction` operation is advertised on the collection itself, which informs the client that the newly created event will become part of the collection, i.e. it's `"member"` property. However, such operation can have an effect on more that one related resource. Building upon this example, in a more elaborate setting creating a personal event could also add that event to the events collection of all participants. diff --git a/drafts/use-cases/5.creating-new-event.md b/drafts/use-cases/5.creating-new-event.md index cd9d756..fb74787 100644 --- a/drafts/use-cases/5.creating-new-event.md +++ b/drafts/use-cases/5.creating-new-event.md @@ -22,16 +22,15 @@ var event = { "startDate": "2017-04-19", "endDate": "2017-04-19" }; -var client = new HydraClient(); -var operation = client.get("http://example.com") - .getApiDocumentation() - .getEntryPoint() - .getCollection({ - property: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", - object: "http://schema.org/Event" - }) - .getOperationOfType('http://schema.org/CreateAction'); -client.invoke(operation, event); +client.usingApi("http://example.com/") + .findCollectionWith({ + property: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + object: "http://schema.org/Event" + }) + .invoke('http://schema.org/CreateAction') + .whichExpects('http://schema.org/Event') + .withData(event) + .getResult(); ``` @@ -51,7 +50,7 @@ POST /api/events ```json { - "@context": "/api/context.jsonld", + "@context": "http://example.com/api/context.jsonld", "@type": "schema:Event", "eventName": "My brand new event", "eventDescription": "Hope it will work", @@ -62,7 +61,7 @@ POST /api/events ```http HTTP 201 Created -Location: /api/events/2 +Location: /api/events/173a6d5b-e9fc-43d7-a19a-215a06d37f31 ``` Brand new event should be now added to the calendar. diff --git a/drafts/use-cases/6.updating-event.md b/drafts/use-cases/6.updating-event.md index 29693ee..2fcd571 100644 --- a/drafts/use-cases/6.updating-event.md +++ b/drafts/use-cases/6.updating-event.md @@ -46,13 +46,17 @@ HTTP 200 OK "eventDescription": "Hope it will work", "startDate": "2017-04-19", "endDate": "2017-04-19", - "operation": [ - { - "@type": ["Operation", "schema:ReplaceAction"], - "title": "Update an existing event", - "method": "PUT", - "expects": "schema:Event" + "action": [ + { + "@type": "schema:ReplaceAction", + "title": "Update an existing event", + "operation": { + "@type": "Operation", + "method": "PUT", + "expects": "schema:Event", + "target": "/api/events/1" } + } ] } ``` diff --git a/drafts/use-cases/7.searching-events.md b/drafts/use-cases/7.searching-events.md index e138f1f..41eec04 100644 --- a/drafts/use-cases/7.searching-events.md +++ b/drafts/use-cases/7.searching-events.md @@ -15,20 +15,14 @@ So I can find those interesting to me. ```javascript var client = new HydraClient(); -var collection = client.get("/api/events"); -if (colletion.search) { - var filter = {}; - for (let mapping of collection.search.mappings) { - filter[mapping.variable] = "some text"; - } - - var query = urlTemplate - .parse(collection.search.template) - .expand(filter); - var data = client.get(query); - for (var member of data.members) { - // do something with the _member_, i.e. display it - } +var operation = client.get("http://example.com/api/events") + .getOperationOfType('http://schema.org/SearchAction'); +if (operation) { + var templateVariables = { + hydra.freetextQuery: "Hydra" + } +const operationWithTargetExpanded = operation.expandTarget(templateVariables); +client.invoke(operationWithTargetExpanded); } ``` @@ -58,28 +52,43 @@ HTTP 200 OK "property": "rdf:type", "object": "schema:Event" }, - "totalItems": 1, + "totalItems": 2, "members": [ { "@id": "/api/events/1", - "eventName": "Event 1", - "eventDescription": "Some event 1", - "startDate": "2017-04-19", - "endDate": "2017-04-19" + "eventName": "Hydra Telecon", + "eventDescription": "first telecon after the holidays break", + "startDate": "2018-01-08", + "endDate": "2018-01-08" + }, + { + "@id": "/api/events/2", + "eventName": "Web Credentials Telecon", + "eventDescription": "DID spec closure", + "startDate": "2018-01-04", + "endDate": "2018-01-04" } ], - "search": { - "@type": "IriTemplate", - "template": "http://example.com/api/events{?search}", - "variableRepresentation": "BasicRepresentation", - "mapping": [ - { - "@type": "IriTemplateMapping", - "variable": "search", - "property": "freetextQuery", - "required": true + "action": { + "@type": "schema:SearchAction", + "title": "search for events using a search term", + "operation": { + "@type": "Operation", + "method": "GET", + "target": { + "@type": "IriTemplate", + "template": "http://example.com/api/events{?search}", + "variableRepresentation": "BasicRepresentation", + "mapping": [ + { + "@type": "IriTemplateMapping", + "variable": "search", + "property": "freetextQuery", + "required": true + } + ] } - ] + } } } ``` @@ -95,98 +104,17 @@ HTTP 200 OK ```json { "@context": "/api/context.jsonld", - "@id": "/api/events?search=some text", + "@id": "/api/events?search=Hydra", "@type": "Collection", "totalItems": 1, "members": [ { "@id": "/api/events/1", - "eventName": "Event 1", - "eventDescription": "Some event having a search phrase", - "startDate": "2017-04-19", - "endDate": "2017-04-19" + "eventName": "Hydra Telecon 2018-01-09", + "eventDescription": "first telecon after the holidays break", + "startDate": "2018-01-08", + "endDate": "2018-01-08" } ] } ``` - - -## Considerations - -### How the client would be informed of the search possibility -There are two main sources of that information which can enable client -with a search feature: - -- API documentation -- hypermedia controls - -Unfortunately, current specification does not clearly state, -how the templated link should be embedded in the payload of both. -There is an example of a templated link using a special -purpose *freetextQuery* property that can be used for search. -There is also a special purpose templated link *search*, -but again it is unclear on how it should be used. -Possible payload for hypermedia control would look like this: - -```http -GET /api/events -``` - -```http -HTTP 200 OK -``` - -```json -{ - "@context": "/api/context.jsonld", - "@id": "/api/events", - "totalItems": 1, - "members": [ - { - "@id": "/api/events/1", - "eventName": "Event 1", - "eventDescription": "Some event 1", - "startDate": "2017-04-19", - "endDate": "2017-04-19" - } - ], - "search": { - "@type": "IriTemplate", - "template": "http://example.com/api/events{?search}", - "variableRepresentation": "BasicRepresentation", - "mapping": [ - { - "@type": "IriTemplateMapping", - "variable": "search", - "property": "freetextQuery", - "required": true - } - ] - } -} -``` - - -### Templated link usage - -Following analogy of other predefined links, like *first* for collections, -templated links should be used as predicates: - -```turtle - search . -``` - -The example above of a triple with a templated link -seems inconvenient as in order to define a custom -templated link, one should create a separate resource. -IRI of that resource shall be nicely foldable to QIri form -in order to improve readability, resulting in a number -of fake namespaces. - -Also a location of the template itself within the graph is not clear. -The example above puts it as a related resource of the -*search* predicate, but only to fill the gap as -there is no other resource that could be used to -build up a valid statement. -Also this seems compliant (again by analogy) to simple scenario -with *first* predicate, where the related resource is another collection page. diff --git a/drafts/use-cases/8.filtering-events.md b/drafts/use-cases/8.filtering-events.md index 0c2afc9..9b35b6a 100644 --- a/drafts/use-cases/8.filtering-events.md +++ b/drafts/use-cases/8.filtering-events.md @@ -71,18 +71,26 @@ HTTP 200 OK "endDate": "2017-04-19" } ], - "search": { - "@type": "IriTemplate", - "template": "http://example.com/api/events{?eventName}", - "variableRepresentation": "BasicRepresentation", - "mapping": [ - { - "@type": "IriTemplateMapping", - "variable": "eventName", - "property": "http://schema.org/name", - "required": false + "action": { + "@type": "schema:SearchAction", + "title": "filter events by name", + "operation": { + "@type": "Operation", + "method": "GET", + "target": { + "@type": "IriTemplate", + "template": "http://example.com/api/events{?eventName}", + "variableRepresentation": "BasicRepresentation", + "mapping": [ + { + "@type": "IriTemplateMapping", + "variable": "eventName", + "property": "http://schema.org/name", + "required": false + } + ] } - ] + } } } ``` diff --git a/drafts/use-cases/9.adding-existing-resources-to-collections.md b/drafts/use-cases/9.adding-existing-resources-to-collections.md deleted file mode 100644 index e391d78..0000000 --- a/drafts/use-cases/9.adding-existing-resources-to-collections.md +++ /dev/null @@ -1,110 +0,0 @@ -# Adding existing resources to collections - -## Story - -As an application user -I want to be able to add people to the list of event attendees -So I know who attended the event - -As an application user -I want to be able to add event to the list of events I attended -So I can keep track on events I attended - -As an API consumer -I want to be able to add existing resources to collections -So I can create various meaningful associations - - -## TODO - -* define operation for adding existing resources to a collection [Issue#134](https://github.com/HydraCG/Specifications/issues/134) - - -## Details - -Each event can reference a collection which acts as list of the attendees - -```http -GET /api/events/1 -``` - -```http -HTTP 200 OK -``` - -```json -{ - "@context": "/api/context.jsonld", - "@id": "/api/events/1", - "@type": "schema:Event", - "eventName": "Event 1", - "collection": { - "@id": "/api/events/1/attendees", - "title": "List of attendees", - "@type": "Collection", - "manages": [ - { - "property": "rdf:type", - "object": "schema:Person" - }, - { - "subject": "/api/events/1", - "property": "schema:attendee" - } - ], - "operation": { - "@type": ["Operation", "schema:AddAction"], - "title": "Add event attendee", - "method": "LINK", - "expects": "schema:Person" - } - - } -} -``` - -Similarly, each person can reference a collection which lists the events this person attended: - -```http -GET /api/people/1 -``` - -```http -HTTP 200 OK -``` - -```json -{ - "@context": "/api/context.jsonld", - "@id": "/api/people/1", - "@type": "schema:Person", - "schema:name": "Alice Foobarion", - "collection": { - "@id": "/api/people/1/attended", - "title": "List of attended events", - "@type": "Collection", - "manages": [ - { - "property": "rdf:type", - "object": "schema:Event" - }, - { - "property": "schema:attendee", - "object": "/api/person/1", - } - ], - "operation": { - "@type": ["Operation", "schema:AddAction"], - "title": "Add attended event", - "method": "LINK", - "expects": "schema:Event" - } - - } -} -``` - -In both cases the `schema:AddAction` operation expects an existing resource -(with an `@id`) and just adds it to collection's `member` array. Unlike -`schema:CreateAction` explained in -[5.creating-new-event.md](https://github.com/HydraCG/Specifications/blob/master/drafts/use-cases/5.creating-new-event.md). diff --git a/drafts/use-cases/9.creating-relations-between-existing-resources.md b/drafts/use-cases/9.creating-relations-between-existing-resources.md new file mode 100644 index 0000000..21bc0b0 --- /dev/null +++ b/drafts/use-cases/9.creating-relations-between-existing-resources.md @@ -0,0 +1,199 @@ +# Creating relations between existing resources + +## Story + +As an application user +I want to be able to add people to the list of event attendees +So I know who attended the event + +As an application user +I want to be able to add event to the list of events I attended +So I can keep track on events I attended + +As an API consumer +I want to be able to link existing resources using specific property +So I can create various meaningful relations + + +## Details + +Each event can advertise an action to link it to another resource using +specific property (sometimes called relation or predicate). In this case it only +gets included in responses to authenticated requests, since target of operation +that handles such action depends on identity of the person using application. +Here `tbd:triplePattern` does include value for `subject` since person can only +link oneself as attending an event. + + + +```http +GET /api/events/173a6d5b-e9fc-43d7-a19a-215a06d37f31 +``` + +```http +HTTP 200 OK +``` + +```json +{ + "@context": "/context.jsonld", + "@id": "/api/events/173a6d5b-e9fc-43d7-a19a-215a06d37f31", + "@type": "schema:Event", + "eventName": "Event 1", + "action": { + "@type:": "tbd:LinkAction", + "title": "Add event attendee", + "expects": "rdf:Statement", + "tbd:triplePattern": { + "subject": "/api/events/173a6d5b-e9fc-43d7-a19a-215a06d37f31", + "property": "schema:attendee", + "object": "/api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c" + }, + "operation": { + "@type": "Operation", + "method": "PUT", + "target": { + "@type": "IriTemplate", + "template": "/api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c/attended/{uuid}", + "variableRepresentation": "BasicRepresentation", + "mapping": [ + { + "@type": "IriTemplateMapping", + "variable": "uuid", + "property": "schema:uuid", + "required": true + } + ] + } + } + } +} +``` + +Similarly, each person can advertise action to link it to another resource, +in this case using the same specific property (sometimes called relation or predicate). +Here `tbd:triplePattern` does not include value for `subject` since person can link onself +as attending any event. + + +```http +GET /api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c +``` + +```http +HTTP 200 OK +``` + +```json +{ + "@context": "/context.jsonld", + "@id": "/api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c", + "@type": "schema:Person", + "schema:name": "Alice Foobarion", + "action": { + "@type:": "tbd:LinkAction", + "title": "Add event attendee", + "expects": "rdf:Statement", + "tbd:triplePattern": { + "property": "schema:attendee", + "object": "/api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c" + }, + "operation": { + "@type": "Operation", + "method": "PUT", + "target": { + "@type": "IriTemplate", + "template": "/api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c/attended/{uuid}", + "variableRepresentation": "BasicRepresentation", + "mapping": [ + { + "@type": "IriTemplateMapping", + "variable": "uuid", + "property": "schema:uuid", + "required": true + } + ] + } + } + } +} +``` + +Since both resources originate from two different datasets. Client application could +add relation based on `schema:attendee` to both datasets. + +```http +PUT /api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c/attended/61d4db0b-91da-46ce-8cca-afc8dc3eaa34 +``` + +```jsonld +{ + "@id": "https://example.org/api/events/173a6d5b-e9fc-43d7-a19a-215a06d37f31", + "https://schema.org/attendee": "https://example.org/api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c" +} +``` + +``` http +HTTP/1.1 201 Created +``` + +Now dataset includes relation between given event and given person. One will see it in +results of [Tripple Pattern Fragment](https://www.hydra-cg.com/spec/latest/triple-pattern-fragments) queries based on patterns: + +```json +{ + "rdf:subject": "https://example.org/api/events/173a6d5b-e9fc-43d7-a19a-215a06d37f31", + "rdf:predicate": "https://schema.org/attendee" +} +``` + +```json +{ + "rdf:predicate": "https://schema.org/attendee", + "rdf:object": "https://example.org/api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c" +} +``` + +This would also result in event providing `tbd:UnlinkAction` for the person who +attends particular event. + +```http +GET /api/events/173a6d5b-e9fc-43d7-a19a-215a06d37f31 +``` + +```http +HTTP 200 OK +``` + +```json +{ + "@context": "/context.jsonld", + "@id": "/api/events/173a6d5b-e9fc-43d7-a19a-215a06d37f31", + "@type": "schema:Event", + "eventName": "Event 1", + "action": { + "@type:": "tbd:UnlinkAction", + "title": "Add event attendee", + "tbd:triplePattern": { + "subject": "/api/events/173a6d5b-e9fc-43d7-a19a-215a06d37f31", + "property": "schema:attendee", + "object": "/api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c" + }, + "operation": { + "@type": "Operation", + "method": "DELETE", + "target": "/api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c/attended/61d4db0b-91da-46ce-8cca-afc8dc3eaa34" + } + } +} +``` + +Client can perform this action by + +```http +DELETE /api/people/28c8610e-e291-4e5a-abd4-b3f8fe99b11c/attended/61d4db0b-91da-46ce-8cca-afc8dc3eaa34 +``` + +``` http +HTTP/1.1 204 No Content +```