Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api/registry, api/search, api/version): add api endpoints to get closer to command line functionality #475

Merged
merged 9 commits into from
Jul 5, 2018
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