Skip to content

Commit

Permalink
feat(api/registry, api/search, api/version): add api endpoints to get…
Browse files Browse the repository at this point in the history
… closer to command line functionality

Merge pull request #475 from qri-io/api
  • Loading branch information
ramfox authored Jul 5, 2018
2 parents 2a7d8d7 + a0f0735 commit abb36a4
Show file tree
Hide file tree
Showing 31 changed files with 344 additions and 203 deletions.
45 changes: 35 additions & 10 deletions api/datasets.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"github.com/qri-io/qri/repo/profile"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
Expand Down Expand Up @@ -350,28 +351,52 @@ func (h *DatasetHandlers) initHandler(w http.ResponseWriter, r *http.Request) {
}

default:
dsp = &dataset.DatasetPod{
Peername: r.FormValue("peername"),
Name: r.FormValue("name"),
BodyPath: r.FormValue("body_path"),
datafile, dataHeader, err := r.FormFile("file")
if err != nil && err != http.ErrMissingFile {
util.WriteErrResponse(w, http.StatusBadRequest, fmt.Errorf("error opening dataset file: %s", err))
return
}
if datafile != nil {
switch strings.ToLower(filepath.Ext(dataHeader.Filename)) {
case ".yaml", ".yml":
data, err := ioutil.ReadAll(datafile)
if err != nil {
util.WriteErrResponse(w, http.StatusBadRequest, fmt.Errorf("error reading dataset file: %s", err))
return
}
if err = dsutil.UnmarshalYAMLDatasetPod(data, dsp); err != nil {
util.WriteErrResponse(w, http.StatusBadRequest, fmt.Errorf("error unmarshaling yaml file: %s", err))
return
}
case ".json":
if err = json.NewDecoder(datafile).Decode(dsp); err != nil {
util.WriteErrResponse(w, http.StatusBadRequest, fmt.Errorf("error decoding json file: %s", err))
return
}
}
}

infile, fileHeader, err := r.FormFile("file")
dsp.Peername = r.FormValue("peername")
dsp.Name = r.FormValue("name")
dsp.BodyPath = r.FormValue("body_path")

bodyfile, bodyHeader, err := r.FormFile("body")
if err != nil && err != http.ErrMissingFile {
util.WriteErrResponse(w, http.StatusBadRequest, fmt.Errorf("error opening data file: %s", err))
util.WriteErrResponse(w, http.StatusBadRequest, fmt.Errorf("error opening body file: %s", err))
return
}
if infile != nil {
path := filepath.Join(os.TempDir(), fileHeader.Filename)
if bodyfile != nil {
path := filepath.Join(os.TempDir(), bodyHeader.Filename)
f, err := os.Create(path)
if err != nil {
util.WriteErrResponse(w, http.StatusInternalServerError, fmt.Errorf("writing data file: %s", err.Error()))
util.WriteErrResponse(w, http.StatusInternalServerError, fmt.Errorf("error writing body file: %s", err.Error()))
}
defer os.Remove(path)
io.Copy(f, infile)
io.Copy(f, bodyfile)
f.Close()
dsp.BodyPath = path
}

}

res := &repo.DatasetRef{}
Expand Down
90 changes: 90 additions & 0 deletions api/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package api

import (
"fmt"
"net/http"

util "github.com/datatogether/api/apiutil"
"github.com/qri-io/qri/lib"
"github.com/qri-io/qri/repo"
)

// RegistryHandlers wraps a requests struct to interface with http.HandlerFunc
type RegistryHandlers struct {
lib.RegistryRequests
}

// NewRegistryHandlers allocates a RegistryHandlers pointer
func NewRegistryHandlers(r repo.Repo) *RegistryHandlers {
req := lib.NewRegistryRequests(r, nil)
h := RegistryHandlers{*req}
return &h
}

// RegistryHandler is the endpoint to call to the registry
func (h *RegistryHandlers) RegistryHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "OPTIONS":
util.EmptyOkHandler(w, r)
// case "GET":
// // get status of dataset, is it published or not
// h.statusRegistryHandler(w, r)
case "POST", "PUT":
// publish a dataset to the registry
h.publishRegistryHandler(w, r)
case "DELETE":
// unpublish a dataset from the registry
h.unpublishRegistryHandler(w, r)
default:
util.NotFoundHandler(w, r)
}
}

func (h *RegistryHandlers) statusRegistryHandler(w http.ResponseWriter, r *http.Request) {
ref, err := DatasetRefFromPath(r.URL.Path[len("/registry"):])
if err != nil {
util.WriteErrResponse(w, http.StatusBadRequest, err)
return
}
var res bool
if err := h.RegistryRequests.Status(&ref, &res); err != nil {
util.WriteResponse(w, fmt.Sprintf("error getting status from registry: %s", err))
return
}

util.WriteResponse(w, fmt.Sprintf("dataset %s is published to the registry", ref))
}

func (h *RegistryHandlers) publishRegistryHandler(w http.ResponseWriter, r *http.Request) {
ref, err := DatasetRefFromPath(r.URL.Path[len("/registry"):])
if err != nil {
util.WriteErrResponse(w, http.StatusBadRequest, err)
return
}
var res bool
p := &lib.PublishParams{
Ref: ref,
Pin: true,
}
if err = h.RegistryRequests.Publish(p, &res); err != nil {
util.WriteErrResponse(w, http.StatusInternalServerError, err)
return
}

util.WriteResponse(w, fmt.Sprintf("published dataset %s", ref))
}

func (h *RegistryHandlers) unpublishRegistryHandler(w http.ResponseWriter, r *http.Request) {
ref, err := DatasetRefFromPath(r.URL.Path[len("/registry"):])
if err != nil {
util.WriteErrResponse(w, http.StatusBadRequest, err)
return
}
var res bool
if err = h.RegistryRequests.Unpublish(&ref, &res); err != nil {
util.WriteErrResponse(w, http.StatusInternalServerError, err)
return
}

util.WriteResponse(w, fmt.Sprintf("unpublished dataset %s", ref))
}
2 changes: 1 addition & 1 deletion api/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func NewRootHandler(dsh *DatasetHandlers, ph *PeerHandlers) *RootHandler {
func (mh *RootHandler) Handler(w http.ResponseWriter, r *http.Request) {
ref := DatasetRefFromCtx(r.Context())
if ref.IsEmpty() {
util.HealthCheckHandler(w, r)
HealthCheckHandler(w, r)
return
}

Expand Down
59 changes: 59 additions & 0 deletions api/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package api

import (
"encoding/json"
"net/http"

util "github.com/datatogether/api/apiutil"
"github.com/qri-io/qri/lib"
"github.com/qri-io/qri/repo"
)

// SearchHandlers wraps a requests struct to interface with http.HandlerFunc
type SearchHandlers struct {
lib.SearchRequests
}

// NewSearchHandlers allocates a SearchHandlers pointer
func NewSearchHandlers(r repo.Repo) *SearchHandlers {
req := lib.NewSearchRequests(r, nil)
return &SearchHandlers{*req}
}

// SearchHandler is the endpoint for searching qri
func (h *SearchHandlers) SearchHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "OPTIONS":
util.EmptyOkHandler(w, r)
case "GET":
h.searchHandler(w, r)
default:
util.NotFoundHandler(w, r)
}
}

func (h *SearchHandlers) searchHandler(w http.ResponseWriter, r *http.Request) {
// p := util.PageFromRequest(r)
sp := &lib.SearchParams{
QueryString: r.FormValue("q"),
Limit: 100,
Offset: 0,
}

if r.Header.Get("Content-Type") == "application/json" {
if err := json.NewDecoder(r.Body).Decode(sp); err != nil {
util.WriteErrResponse(w, http.StatusBadRequest, err)
return
}
}

results := []lib.SearchResult{}

if err := h.SearchRequests.Search(sp, &results); err != nil {
log.Infof("search error: %s", err.Error())
util.WriteErrResponse(w, http.StatusInternalServerError, err)
return
}

util.WriteResponse(w, results)
}
15 changes: 14 additions & 1 deletion api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,18 @@ func readOnlyResponse(w http.ResponseWriter, endpoint string) {
apiutil.WriteErrResponse(w, http.StatusForbidden, fmt.Errorf("qri server is in read-only mode, access to '%s' endpoint is forbidden", endpoint))
}

// HealthCheckHandler is a basic ok response for load balancers & co
// returns the version of qri this node is running, pulled from the lib package
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{ "meta": { "code": 200, "status": "ok", "version":"` + lib.VersionNumber + `" }, "data": [] }`))
}

// NewServerRoutes returns a Muxer that has all API routes
func NewServerRoutes(s *Server) *http.ServeMux {
m := http.NewServeMux()

m.Handle("/status", s.middleware(apiutil.HealthCheckHandler))
m.Handle("/status", s.middleware(HealthCheckHandler))
m.Handle("/ipfs/", s.middleware(s.HandleIPFSPath))
m.Handle("/ipns/", s.middleware(s.HandleIPNSPath))

Expand Down Expand Up @@ -260,6 +267,12 @@ func NewServerRoutes(s *Server) *http.ServeMux {
hh.HistoryRequests.Node = s.qriNode
m.Handle("/history/", s.middleware(hh.LogHandler))

rgh := NewRegistryHandlers(s.qriNode.Repo)
m.Handle("/registry/", s.middleware(rgh.RegistryHandler))

sh := NewSearchHandlers(s.qriNode.Repo)
m.Handle("/search", s.middleware(sh.SearchHandler))

rh := NewRootHandler(dsh, ph)
m.Handle("/", s.datasetRefMiddleware(s.middleware(rh.Handler)))

Expand Down
Loading

0 comments on commit abb36a4

Please sign in to comment.