diff --git a/app/server/src/api/api.go b/app/server/src/api/api.go index 5ee24a7e54..69cbfd1660 100644 --- a/app/server/src/api/api.go +++ b/app/server/src/api/api.go @@ -2,15 +2,31 @@ package api import ( + "encoding/json" "fmt" "net/http" ) +type ApiError struct { + Code int64 `json:"code,omitempty"` + Msg string `json:"msg,omitempty"` +} + // HandleAPIError handles any error that bubbles up to the controller layer // TODO: Make the error generic enough with HTTP status codes and messages as well func HandleAPIError(w http.ResponseWriter, r *http.Request, err error) { // Write content-type, statuscode, payload w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) - fmt.Fprintf(w, "%s", err) + fmt.Fprintf(w, "%s", createErrorBody(err)) +} + +func createErrorBody(err error) []byte { + apiError := ApiError{ + Code: -1, + Msg: err.Error(), + } + + errorJSON, _ := json.Marshal(apiError) + return errorJSON } diff --git a/app/server/src/api/query.go b/app/server/src/api/query.go index fa4edf1a4b..9990241b77 100644 --- a/app/server/src/api/query.go +++ b/app/server/src/api/query.go @@ -1,6 +1,83 @@ package api +import ( + "encoding/json" + "fmt" + "internal-tools-server/models" + "internal-tools-server/services" + "net/http" + + "github.com/julienschmidt/httprouter" +) + /* This file contains the APIs for the client to invoke in order to fetch data or perform an action on the middleware server */ + +// PostQuery executes a custom sql query on the client database +func PostQuery(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + queryBody := models.ExecQuery{} + err := json.NewDecoder(r.Body).Decode(&queryBody) + if err != nil { + HandleAPIError(w, r, err) + return + } + + var mapArray []map[string]interface{} + mapArray, err = services.ExecuteQuery(queryBody) + if err != nil { + HandleAPIError(w, r, err) + return + } + + // Write content-type, statuscode, payload + mapJSON, _ := json.Marshal(mapArray) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + fmt.Fprintf(w, "%s", mapJSON) +} + +// CreateQuery creates a new query for the user in the table +func CreateQuery(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + queryBody := models.Query{} + err := json.NewDecoder(r.Body).Decode(&queryBody) + if err != nil { + HandleAPIError(w, r, err) + return + } + + queryBody, err = services.CreateQuery(queryBody) + if err != nil { + HandleAPIError(w, r, err) + return + } + + // Write content-type, statuscode, payload + queryJSON, _ := json.Marshal(queryBody) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + fmt.Fprintf(w, "%s", queryJSON) +} + +// UpdateQuery updates a given query in the database for a given account +func UpdateQuery(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + queryBody := models.Query{} + err := json.NewDecoder(r.Body).Decode(&queryBody) + if err != nil { + HandleAPIError(w, r, err) + return + } + + queryBody, err = services.UpdateQuery(queryBody) + if err != nil { + HandleAPIError(w, r, err) + return + } + + // Write content-type, statuscode, payload + queryJSON, _ := json.Marshal(queryBody) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + fmt.Fprintf(w, "%s", queryJSON) +} diff --git a/app/server/src/models/query.go b/app/server/src/models/query.go new file mode 100644 index 0000000000..57018a7a25 --- /dev/null +++ b/app/server/src/models/query.go @@ -0,0 +1,29 @@ +package models + +type ( + Query struct { + ID int64 `json:"id" sql:"id"` + Name string `json:"name" sql:"name"` + QueryType string `json:"query_type,omitempty" sql:"query_type"` + Executable string `json:"executable,omitempty" sql:"executable"` + ResourceName string `json:"resource_name,omitempty" sql:"resource_name"` + ConfirmationMsg string `json:"confirmation_msg,omitempty" sql:"confirmation_msg"` + } + + ExecQuery struct { + QueryType string `json:"query_type,omitempty"` + Name string `json:"name"` + Params Params `json:"params,omitempty"` + } + + Params struct { + QueryParams []KeyValue `json:"query_params,omitempty"` + HeaderParams []KeyValue `json:"header_params,omitempty"` + CookieParams []KeyValue `json:"cookie_params,omitempty"` + } + + KeyValue struct { + Key string `json:"key"` + Value string `json:"value"` + } +) diff --git a/app/server/src/server.go b/app/server/src/server.go index 87f550df6a..0387d7fcd5 100644 --- a/app/server/src/server.go +++ b/app/server/src/server.go @@ -47,7 +47,9 @@ func intializeServer() *httprouter.Router { // Page CRUD Endpoints // Query CRUD Endpoints - + router.POST(baseURL+apiVersion+url.QueryURL+"/execute", api.PostQuery) + router.POST(baseURL+apiVersion+url.QueryURL, api.CreateQuery) + router.PUT(baseURL+apiVersion+url.QueryURL, api.UpdateQuery) return router } @@ -76,6 +78,7 @@ func runMigrations() { &models.User{}, &models.Role{}, &models.Page{}, + &models.Query{}, ) log.Println("Successfully run all migrations") } diff --git a/app/server/src/services/query.go b/app/server/src/services/query.go new file mode 100644 index 0000000000..e1cb3529d2 --- /dev/null +++ b/app/server/src/services/query.go @@ -0,0 +1,54 @@ +package services + +import ( + "fmt" + "internal-tools-server/models" + "internal-tools-server/storage" + + "github.com/jinzhu/gorm" +) + +// ExecuteQuery runs a custom SQL query on the client database +func ExecuteQuery(queryBody models.ExecQuery) ([]map[string]interface{}, error) { + if queryBody.QueryType == "sql" { + // Get the actual query from the DB + datastore := storage.StorageEngine.GetDatastore() + queryDAO := &models.Query{} + + if err := datastore.Where("name = ?", queryBody.Name).First(queryDAO).Error; gorm.IsRecordNotFoundError(err) { + return nil, fmt.Errorf("Invalid queryName: %s provided", queryBody.Name) + } + + queryStr := queryDAO.Executable + mapArray, err := storage.StorageEngine.ExecuteQuery(queryStr) + + if err != nil { + return nil, err + } + return mapArray, nil + } + + return nil, fmt.Errorf("QueryType: %s not supported", queryBody.QueryType) +} + +// CreateQuery creates a new query that can be executed by name at runtime +func CreateQuery(queryBody models.Query) (models.Query, error) { + datastore := storage.StorageEngine.GetDatastore() + if err := datastore.Create(&queryBody).Error; err != nil { + return models.Query{}, err + } + + return queryBody, nil +} + +// UpdateQuery updates an existing query in the database +func UpdateQuery(query models.Query) (models.Query, error) { + datastore := storage.StorageEngine.GetDatastore() + + // Update only the non-nil values in the struct + datastore.Model(&query).Updates(query) + + // Select the updated record to return back to the client + datastore.First(&query) + return query, nil +} diff --git a/app/server/src/url/constants.go b/app/server/src/url/constants.go index 91652559c2..309ba94236 100644 --- a/app/server/src/url/constants.go +++ b/app/server/src/url/constants.go @@ -1,3 +1,4 @@ package url const ComponentURL = "/components" +const QueryURL = "/query"