diff --git a/handlers/exploration.go b/handlers/exploration.go new file mode 100644 index 000000000..a54eb2f0d --- /dev/null +++ b/handlers/exploration.go @@ -0,0 +1,216 @@ +package handlers + +import ( + "fmt" + "log" + "strconv" + + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/influxdata/mrfusion" + "github.com/influxdata/mrfusion/models" + "golang.org/x/net/context" + + op "github.com/influxdata/mrfusion/restapi/operations" +) + +type ExplorationStore struct { + ExplorationStore mrfusion.ExplorationStore +} + +func (h *ExplorationStore) Explorations(ctx context.Context, params op.GetSourcesIDUsersUserIDExplorationsParams) middleware.Responder { + uID, err := strconv.Atoi(params.UserID) + if err != nil { + log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err) + errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"} + return op.NewGetSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg) + } + + exs, err := h.ExplorationStore.Query(ctx, mrfusion.UserID(uID)) + if err != nil { + log.Printf("Error: Unknown response from store while querying UserID: %s: %v", params.UserID, err) + errMsg := &models.Error{Code: 500, Message: "Error: Unknown response from store while querying UserID"} + return op.NewGetSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg) + } + + res := &models.Explorations{} + for _, e := range exs { + rel := "self" + href := fmt.Sprintf("/chronograf/v1/sources/1/users/%d/explorations/%d", uID, e.ID) + res.Explorations = append(res.Explorations, &models.Exploration{ + Data: e.Data, + Name: e.Name, + UpdatedAt: strfmt.DateTime(e.UpdatedAt), + CreatedAt: strfmt.DateTime(e.CreatedAt), + Link: &models.Link{ + Rel: &rel, + Href: &href, + }, + }, + ) + } + return op.NewGetSourcesIDUsersUserIDExplorationsOK().WithPayload(res) +} + +func (h *ExplorationStore) Exploration(ctx context.Context, params op.GetSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder { + eID, err := strconv.Atoi(params.ExplorationID) + if err != nil { + log.Printf("Error: Unable to convert ExplorationID: %s: %v", params.ExplorationID, err) + errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert ExplorationID"} + return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg) + } + + uID, err := strconv.Atoi(params.UserID) + if err != nil { + log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err) + errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"} + return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg) + } + + e, err := h.ExplorationStore.Get(ctx, mrfusion.ExplorationID(eID)) + if err != nil { + log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err) + errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"} + return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg) + } + + if e.UserID != mrfusion.UserID(uID) { + log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err) + errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"} + return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg) + } + + rel := "self" + href := fmt.Sprintf("/chronograf/v1/sources/1/users/%d/explorations/%d", uID, eID) + res := &models.Exploration{ + Name: e.Name, + Data: e.Data, + UpdatedAt: strfmt.DateTime(e.UpdatedAt), + CreatedAt: strfmt.DateTime(e.CreatedAt), + Link: &models.Link{ + Rel: &rel, + Href: &href, + }, + } + return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDOK().WithPayload(res) +} + +func (h *ExplorationStore) UpdateExploration(ctx context.Context, params op.PatchSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder { + if params.Exploration == nil { + log.Printf("Error: Exploration is nil") + errMsg := &models.Error{Code: 400, Message: "Error: Exploration is nil"} + return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(400).WithPayload(errMsg) + } + + eID, err := strconv.Atoi(params.ExplorationID) + if err != nil { + log.Printf("Error: Unable to convert ExplorationID: %s: %v", params.ExplorationID, err) + errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert ExplorationID"} + return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg) + } + + uID, err := strconv.Atoi(params.UserID) + if err != nil { + log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err) + errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"} + return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg) + } + + e, err := h.ExplorationStore.Get(ctx, mrfusion.ExplorationID(eID)) + if err != nil || e.UserID != mrfusion.UserID(uID) { + log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err) + errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"} + return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg) + } + + var ok bool + if e.Data, ok = params.Exploration.Data.(string); !ok { + log.Printf("Error: Exploration data is not a string") + errMsg := &models.Error{Code: 400, Message: "Error: Exploration data is not a string"} + return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(400).WithPayload(errMsg) + } + e.Name = params.Exploration.Name + + if err := h.ExplorationStore.Update(ctx, e); err != nil { + log.Printf("Error: Failed to update Exploration: %v: %v", e, err) + errMsg := &models.Error{Code: 500, Message: "Error: Failed to update Exploration"} + return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg) + } + + return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDNoContent() +} + +func (h *ExplorationStore) NewExploration(ctx context.Context, params op.PostSourcesIDUsersUserIDExplorationsParams) middleware.Responder { + if params.Exploration == nil { + log.Printf("Error: Exploration is nil") + errMsg := &models.Error{Code: 400, Message: "Error: Exploration is nil"} + return op.NewPostSourcesIDUsersUserIDExplorationsDefault(400).WithPayload(errMsg) + } + + uID, err := strconv.Atoi(params.UserID) + if err != nil { + log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err) + errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"} + return op.NewPostSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg) + } + + // TODO: Check user if user exists. + + e := &mrfusion.Exploration{ + Name: params.Exploration.Name, + UserID: mrfusion.UserID(uID), + Data: params.Exploration.Data.(string), + } + + e, err = h.ExplorationStore.Add(ctx, e) + if err != nil { + log.Printf("Error: Failed to save Exploration: %v: %v", e, err) + errMsg := &models.Error{Code: 500, Message: "Error: Failed to save Exploration"} + return op.NewPostSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg) + } + + rel := "self" + href := fmt.Sprintf("/chronograf/v1/sources/1/users/%d/explorations/%d", uID, e.ID) + res := &models.Exploration{ + Name: e.Name, + Data: e.Data, + UpdatedAt: strfmt.DateTime(e.UpdatedAt), + CreatedAt: strfmt.DateTime(e.CreatedAt), + Link: &models.Link{ + Rel: &rel, + Href: &href, + }, + } + return op.NewPostSourcesIDUsersUserIDExplorationsCreated().WithLocation(href).WithPayload(res) + +} + +func (h *ExplorationStore) DeleteExploration(ctx context.Context, params op.DeleteSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder { + eID, err := strconv.Atoi(params.ExplorationID) + if err != nil { + log.Printf("Error: Unable to convert ExplorationID: %s: %v", params.ExplorationID, err) + errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert ExplorationID"} + return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg) + } + + uID, err := strconv.Atoi(params.UserID) + if err != nil { + log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err) + errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"} + return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg) + } + + e, err := h.ExplorationStore.Get(ctx, mrfusion.ExplorationID(eID)) + if err != nil || e.UserID != mrfusion.UserID(uID) { + log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err) + errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"} + return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg) + } + + if err := h.ExplorationStore.Delete(ctx, &mrfusion.Exploration{ID: mrfusion.ExplorationID(eID)}); err != nil { + log.Printf("Error: Failed to delete Exploration: %v: %v", params.ExplorationID, err) + errMsg := &models.Error{Code: 500, Message: "Error: Failed to delete Exploration"} + return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg) + } + return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDNoContent() +} diff --git a/restapi/configure_mr_fusion.go b/restapi/configure_mr_fusion.go index ca5ed5447..33afbcdcb 100644 --- a/restapi/configure_mr_fusion.go +++ b/restapi/configure_mr_fusion.go @@ -13,6 +13,7 @@ import ( "golang.org/x/net/context" "github.com/influxdata/mrfusion" + "github.com/influxdata/mrfusion/bolt" "github.com/influxdata/mrfusion/dist" "github.com/influxdata/mrfusion/handlers" "github.com/influxdata/mrfusion/influx" @@ -32,6 +33,10 @@ var influxFlags = struct { Server string `short:"s" long:"server" description:"Full URL of InfluxDB server (http://localhost:8086)" env:"INFLUX_HOST"` }{} +var storeFlags = struct { + BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (/Users/somebody/mrfusion.db)" env:"BOLT_PATH"` +}{} + func configureFlags(api *operations.MrFusionAPI) { api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{ swag.CommandLineOptionsGroup{ @@ -41,9 +46,14 @@ func configureFlags(api *operations.MrFusionAPI) { }, swag.CommandLineOptionsGroup{ ShortDescription: "Default Time Series Backend", - LongDescription: "Specify the url of an InfxluDB server", + LongDescription: "Specify the url of an InfluxDB server", Options: &influxFlags, }, + swag.CommandLineOptionsGroup{ + ShortDescription: "Default Store Backend", + LongDescription: "Specify the path to a BoltDB file", + Options: &storeFlags, + }, } } @@ -76,11 +86,27 @@ func configureAPI(api *operations.MrFusionAPI) http.Handler { mockHandler := mock.NewHandler() - api.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.DeleteExploration) - api.GetSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.GetSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.Exploration) - api.GetSourcesIDUsersUserIDExplorationsHandler = operations.GetSourcesIDUsersUserIDExplorationsHandlerFunc(mockHandler.Explorations) - api.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.UpdateExploration) - api.PostSourcesIDUsersUserIDExplorationsHandler = operations.PostSourcesIDUsersUserIDExplorationsHandlerFunc(mockHandler.NewExploration) + if len(storeFlags.BoltPath) > 0 { + c := bolt.NewClient() + c.Path = storeFlags.BoltPath + if err := c.Open(); err != nil { + panic(err) + } + h := handlers.ExplorationStore{ + ExplorationStore: c.ExplorationStore, + } + api.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(h.DeleteExploration) + api.GetSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.GetSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(h.Exploration) + api.GetSourcesIDUsersUserIDExplorationsHandler = operations.GetSourcesIDUsersUserIDExplorationsHandlerFunc(h.Explorations) + api.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(h.UpdateExploration) + api.PostSourcesIDUsersUserIDExplorationsHandler = operations.PostSourcesIDUsersUserIDExplorationsHandlerFunc(h.NewExploration) + } else { + api.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.DeleteExploration) + api.GetSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.GetSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.Exploration) + api.GetSourcesIDUsersUserIDExplorationsHandler = operations.GetSourcesIDUsersUserIDExplorationsHandlerFunc(mockHandler.Explorations) + api.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.UpdateExploration) + api.PostSourcesIDUsersUserIDExplorationsHandler = operations.PostSourcesIDUsersUserIDExplorationsHandlerFunc(mockHandler.NewExploration) + } api.DeleteDashboardsIDHandler = operations.DeleteDashboardsIDHandlerFunc(func(ctx context.Context, params operations.DeleteDashboardsIDParams) middleware.Responder { return middleware.NotImplemented("operation .DeleteDashboardsID has not yet been implemented")