Commit e7454412 authored by Tom Gordon's avatar Tom Gordon

added a RESTful API for creating, reading, updating and deleting domain models

parent 4c5a3451
# RESTful API for managing domain models
## Creating Domain Models
$ curl -X PUT "http://<domain-name>:<port>/create-domain?apikey=<uuid>&user=<userid>" -H "Content-Type: application/json" -d @<file>.json
where the domain name (or IP number) and port number are for the EAGLE argumentation tool (not the Couchdb server).
### Example
$ curl -X PUT "http://127.0.0.1:8080/create-domain?apikey=123&user=arun" -H "Content-Type: application/json" -d @test1.json
## Reading Domain Models
$ curl http://<domain-name>:<port>/read-domain/<domainId>
### Example
$ curl http://127.0.0.1:8080/read-domain/6ddbc7bf2e9a54415d6c84a2ba004a65
## Updating Domain Models
$ curl -X PUT "http://<domain-name>:<port>/update-domain?apikey=<uuid>&user=<userid>" -H "Content-Type: application/json" -d @<file>.json
### Example
$ curl -X PUT "http://127.0.0.1:8080/update-domain?apikey=123&user=arun" -H "Content-Type: application/json" -d @animals.json
## Deleting Domain Models
$ curl "http://<domain-name>:<port>/delete-domain/<domainId>?apikey=<uuid>&user=<userid>"
### Example
$ curl "http://127.0.0.1:8080/delete-domain/6ddbc7bf2e9a54415d6c84a2ba0041b7?apikey=123&user=arun"
{
"Description": "Test1c",
"Issue": "Possession of Wild Animals",
"Options": [
"possession",
"no-possession"
],
"Statements": {},
"Dimensions": {
"AC": {
"Default": "ac-none",
"Description": "Applicable Convention",
"Factors": [
"full-possession",
"informal-excluse-right",
"social-preference",
"ac-none"
]
},
"COP": {
"Default": "cop-none",
"Description": "Closeness of Pursuit",
"Factors": [
"physical-possession",
"mortal-wounding",
"certain-capture",
"hot-pursuit",
"chasing",
"started-pursuit",
"cop-none"
]
},
"DM": {
"Default": "dm-pleasure",
"Description": "Motive of Party Denying the Possession Claim",
"Factors": [
"dm-livelihood",
"dm-opportunistic",
"dm-altruism",
"dm-pleasure",
"dm-impulse",
"dm-malice"
]
},
"DR": {
"Default": "dr-innocent",
"Description": "Role of Party Denying the Possession Claim",
"Factors": [
"dr-innocent",
"dr-jointly-responsible",
"dr-ignorant-of-the-law",
"dr-accident",
"dr-solely-responsible"
]
},
"LO": {
"Default": "common",
"Description": "Land Ownership",
"Factors": [
"p-freehold",
"p-leasehold",
"p-rent",
"common",
"other-owner",
"d-rent",
"d-leasehold",
"d-freehold"
]
},
"NOA": {
"Default": "act-ok",
"Description": "Nature of Act",
"Factors": [
"violently-illegal",
"illegal",
"nuisance",
"impolite",
"act-ok"
]
},
"PM": {
"Default": "pm-pleasure",
"Description": "Motive of Party Claiming Possession",
"Factors": [
"pm-livelihood",
"pm-opportunistic",
"pm-altruism",
"pm-pleasure",
"pm-impulse",
"pm-malice"
]
},
"QLC": {
"Default": "occasional-visitor",
"Description": "Quarry Land Connection",
"Factors": [
"resident",
"frequent-visitor",
"regular-visitor",
"occasional-visitor",
"transient",
"once-only"
]
},
"QV": {
"Default": "qv-none",
"Description": "Quarry Value",
"Factors": [
"market-value",
"social-value",
"domestic-pet",
"personal-value",
"qv-none"
]
}
}
}
...@@ -37,6 +37,7 @@ func main() { ...@@ -37,6 +37,7 @@ func main() {
flags := flag.NewFlagSet("flags", flag.ContinueOnError) flags := flag.NewFlagSet("flags", flag.ContinueOnError)
httpPort := flags.String("p", defaultHttpPort, "the port number of the web service") httpPort := flags.String("p", defaultHttpPort, "the port number of the web service")
couchdbURL := flags.String("c", defaultCouchdbURL, "the URL of the Couchdb server") couchdbURL := flags.String("c", defaultCouchdbURL, "the URL of the Couchdb server")
apiKey := flags.String("k", "", "The API key of the EAGLE platform with access to this service")
if err := flags.Parse(os.Args[1:]); err != nil { if err := flags.Parse(os.Args[1:]); err != nil {
log.Fatal(err) log.Fatal(err)
} }
...@@ -45,6 +46,7 @@ func main() { ...@@ -45,6 +46,7 @@ func main() {
URL: *couchdbURL, URL: *couchdbURL,
Domains: domainsDBName, Domains: domainsDBName,
Cases: casesDBName, Cases: casesDBName,
APIKey: *apiKey,
} }
web.ArgumentationToolServer(*httpPort, cdb, templatesDir) web.ArgumentationToolServer(*httpPort, cdb, templatesDir)
......
...@@ -24,6 +24,7 @@ type Dimension struct { ...@@ -24,6 +24,7 @@ type Dimension struct {
type Domain struct { type Domain struct {
Id string `json:"_id"` Id string `json:"_id"`
Revision string `json:"_rev"` Revision string `json:"_rev"`
Owner string // userid of the owner of the domain model
Issue string // name of the issue modelled in this domain model Issue string // name of the issue modelled in this domain model
Description string Description string
Options []string // statement ids of the options of the issue Options []string // statement ids of the options of the issue
......
// Copyright © 2016 Thomas F. Gordon
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
package web
import (
"bytes"
"encoding/json"
// "fmt"
"github.com/pborman/uuid"
"io/ioutil"
"net/http"
"strings"
)
type createDomainResponse struct {
Id string `json:"id"` // UUID of the newly created domain model
Message string `json:"message"`
}
func makeCreateDomainHandler(cdb CouchDBConfig) func(http.ResponseWriter, *http.Request) {
couchdbURL := cdb.URL
domainsDBName := cdb.Domains
return func(w http.ResponseWriter, req *http.Request) {
apikey := req.URL.Query().Get("apikey")
user := req.URL.Query().Get("user")
// return a status and JSON encoded message to the client,
// including the id of the newly created domain, if successful
respond := func(status int, id string, msg string) {
response := createDomainResponse{
Id: id,
Message: msg,
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(status)
json.NewEncoder(w).Encode(response)
}
if apikey != cdb.APIKey {
respond(http.StatusUnauthorized, "", "Incorrect API Key")
return
}
body, err := ioutil.ReadAll(req.Body)
if err != nil {
respond(http.StatusBadRequest, "", "Body could not be read")
return
}
defer req.Body.Close()
// To do: Validation of the body, to check that it is well-formed JSON
// and correctly represents an domain model
// Add a property recording the user creating the domain model
// to the JSON file
domainModel := strings.Replace(string(body), "{", "{\"Owner\":\""+user+"\",", 1)
// upload the JSON document to Couchdb
id := uuid.NewUUID().String()
req2, err := http.NewRequest("PUT", couchdbURL+domainsDBName+"/"+id, bytes.NewBufferString(domainModel))
if err != nil {
respond(http.StatusInternalServerError, "", "Failed to create the PUT request to the Couchdb server.")
return
}
client := http.Client{}
resp2, err := client.Do(req2)
if err != nil {
respond(http.StatusInternalServerError, "", "The Couchdb database failed to handle the PUT request.")
return
}
defer resp2.Body.Close()
// check that uploading was successful
m := make(map[string]interface{})
v, err := ioutil.ReadAll(resp2.Body)
if err != nil {
respond(http.StatusInternalServerError, "", "Unable to read the response from the Couchdb database.")
return
}
err = json.Unmarshal(v, &m)
if err != nil {
respond(http.StatusInternalServerError, "", "Failed to unmarshall the response from the Couchdb database.")
return
}
// m should have the form:
// {"ok":true,"id":"6e1295ed6c29495e54cc05947f18c8af","rev":"1-2902191555"}
if m["ok"] != true {
respond(http.StatusInternalServerError, "", "Failed to upload the domain model to the Couchdb database.")
return
}
respond(http.StatusOK, id, "Successfully uploaded the new domain model to the Couchdb database")
}
}
// Copyright © 2016 Thomas F. Gordon
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
package web
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"path"
)
func makeDeleteDomainHandler(cdb CouchDBConfig) func(http.ResponseWriter, *http.Request) {
couchdbURL := cdb.URL
domainsDBName := cdb.Domains
return func(w http.ResponseWriter, req *http.Request) {
values := req.URL.Query()
apikey := values.Get("apikey")
user := values.Get("user")
domainId := path.Base(req.URL.Path)
// return a status and JSON encoded message to the client,
// including the id of the newly created domain, if successful
respond := func(status int, body string) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(status)
json.NewEncoder(w).Encode(body)
}
if apikey != cdb.APIKey {
respond(http.StatusUnauthorized, "Incorrect API Key")
return
}
url := couchdbURL + domainsDBName + "/" + domainId
resp, err := http.Get(url)
if err != nil {
respond(http.StatusBadRequest, "No domain model with this id could be retrieved from the Couchdb server.")
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
respond(http.StatusInternalServerError, "Unable to read the body of the response from the Couchdb server.")
return
}
data := make(map[string]interface{})
err = json.Unmarshal(body, &data)
rev := data["_rev"]
owner := data["Owner"]
if user != owner {
respond(http.StatusUnauthorized, "User is not the owner (creator) of the domain model.")
return
}
req2, err := http.NewRequest("DELETE", couchdbURL+domainsDBName+"/"+domainId+"?rev="+rev.(string), nil)
if err != nil {
respond(http.StatusInternalServerError, "Failed to construct the delete request.")
return
}
client := http.Client{}
resp, err = client.Do(req2)
if err != nil {
respond(http.StatusInternalServerError, "Failed to delete the domain model from the Couchdb database.")
return
}
defer resp.Body.Close()
respond(http.StatusOK, fmt.Sprintf("Revision %s of the domain model with the id %s successully deleted.", rev, domainId))
}
}
// Copyright © 2016 Thomas F. Gordon
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
package web
import (
"encoding/json"
"io/ioutil"
"net/http"
"path"
)
func makeReadDomainHandler(cdb CouchDBConfig) func(http.ResponseWriter, *http.Request) {
couchdbURL := cdb.URL
domainsDBName := cdb.Domains
return func(w http.ResponseWriter, req *http.Request) {
domainId := path.Base(req.URL.Path)
url := couchdbURL + domainsDBName + "/" + domainId
// return a status and JSON encoded message to the client,
// including the id of the newly created domain, if successful
respond := func(status int, body string) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(status)
json.NewEncoder(w).Encode(body)
}
resp, err := http.Get(url)
if err != nil {
respond(http.StatusBadRequest, "Could not get the requested domain model from the Couchdb server")
return
}
body, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
respond(http.StatusInternalServerError, "Body of response from the Couchdb server could not be read")
return
}
respond(http.StatusOK, string(body))
}
}
// Copyright © 2016 Thomas F. Gordon
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
package web
import (
"bytes"
"encoding/json"
"fmt"
"git.list.lu/eagle/argumentation-tool/internal/model"
"io/ioutil"
"net/http"
"path"
"strings"
)
type updateDomainResponse struct {
Revision string `json:"rev"` // Revision id of the updated model
Message string `json:"message"`
}
func makeUpdateDomainHandler(cdb CouchDBConfig) func(http.ResponseWriter, *http.Request) {
couchdbURL := cdb.URL
domainsDBName := cdb.Domains
return func(w http.ResponseWriter, req *http.Request) {
values := req.URL.Query()
apikey := values.Get("apikey")
user := values.Get("user")
domainId := path.Base(req.URL.Path)
// return a status and JSON encoded message to the client,
// including the id of the newly created domain, if successful
respond := func(status int, id string, msg string) {
response := createDomainResponse{
Id: id,
Message: msg,
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(status)
json.NewEncoder(w).Encode(response)
}
if apikey != cdb.APIKey {
respond(http.StatusUnauthorized, domainId, "Incorrect API Key")
return
}
body, err := ioutil.ReadAll(req.Body)
if err != nil {
respond(http.StatusBadRequest, domainId, "Body of request could not be read")
return
}
defer req.Body.Close()
// To do: Validation of the body, to check that it is well-formed JSON
// and correctly represents an domain model
// Get the latest revision of the domain model
domain, err := model.GetDomain(couchdbURL, domainsDBName, domainId)
if err != nil {
respond(http.StatusInternalServerError, domainId, "Failed to retrieve a domain model with the given Id from the Couchdb server.")
return
}
fmt.Printf("Domain: Description=%v; Owner=%v\n", domain.Description, domain.Owner)
// Check that the user owns the domain model
if user != domain.Owner {
respond(http.StatusUnauthorized, domainId, "User is not the owner (creator) of the domain model.")
fmt.Printf("user=%v; owner=%v\n", user, domain.Owner)
return
}
// add a field for the revision number to the JSON representation of
// the argument graph
json1 := strings.Replace(string(body), "{", "{\"_rev\":\""+domain.Revision+"\",", 1)
// Add a property recording the user modifying the domain model
// to the JSON file
json1 = strings.Replace(json1, "{", "{\"Owner\":\""+user+"\",", 1)
// Construct the PUT request
req2, err := http.NewRequest("PUT", couchdbURL+domainsDBName+"/"+domainId, bytes.NewBufferString(json1))
if err != nil {
respond(http.StatusInternalServerError, domainId, "Failed to create the PUT request to the Couchdb server.")
return
}
// upload the revised JSON document to Couchdb
client := http.Client{}
resp2, err := client.Do(req2)
if err != nil {
respond(http.StatusInternalServerError, "", "The Couchdb database failed to handle the PUT request.")
return
}
defer resp2.Body.Close()
// check that uploading was successful
m := make(map[string]interface{})
v, err := ioutil.ReadAll(resp2.Body)
if err != nil {
respond(http.StatusInternalServerError, "", "Unable to read the response from the Couchdb database.")
return
}
err = json.Unmarshal(v, &m)
if err != nil {
respond(http.StatusInternalServerError, "", "Failed to unmarshall the response from the Couchdb database.")
return
}
// m should have the form:
// {"ok":true,"id":"6e1295ed6c29495e54cc05947f18c8af","rev":"1-2902191555"}
if m["ok"] != true {
respond(http.StatusInternalServerError, "", "Failed to upload the revised domain model to the Couchdb database.")
return
}
respond(http.StatusOK, domainId, "Successfully uploaded the revised new domain model to the Couchdb database")
}
}
...@@ -32,6 +32,7 @@ type CouchDBConfig struct { ...@@ -32,6 +32,7 @@ type CouchDBConfig struct {
URL string URL string
Domains string Domains string
Cases string Cases string
APIKey string
} }
type TemplatesConfig struct { type TemplatesConfig struct {
...@@ -49,6 +50,12 @@ func ArgumentationToolServer(httpPort string, cdb CouchDBConfig, templatesDir st ...@@ -49,6 +50,12 @@ func ArgumentationToolServer(httpPort string, cdb CouchDBConfig, templatesDir st
http.HandleFunc("/search-form/", makeSearchFormHandler(cdb, tc)) http.HandleFunc("/search-form/", makeSearchFormHandler(cdb, tc))
http.HandleFunc("/search", makeSearchHandler(cdb, tc)) http.HandleFunc("/search", makeSearchHandler(cdb, tc))
// Domain Model CRUD Web Service API
http.HandleFunc("/create-domain", makeCreateDomainHandler(cdb))
http.HandleFunc("/read-domain/", makeReadDomainHandler(cdb))
http.HandleFunc("/update-domain/", makeUpdateDomainHandler(cdb))
http.HandleFunc("/delete-domain/", makeDeleteDomainHandler(cdb))
// Case Commands // Case Commands
http.HandleFunc("/view-case/", makeViewCaseHandler(cdb, tc)) http.HandleFunc("/view-case/", makeViewCaseHandler(cdb, tc))
http.HandleFunc("/new-case-form/", makeNewCaseFormHandler(cdb, tc)) http.HandleFunc("/new-case-form/", makeNewCaseFormHandler(cdb, tc))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment