/* * EliasDB * * Copyright 2016 Matthias Ladkau. All rights reserved. * * 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 v1 import ( "encoding/json" "fmt" "net/http" "strings" "devt.de/krotik/common/stringutil" "devt.de/krotik/eliasdb/api" "devt.de/krotik/eliasdb/graph" "devt.de/krotik/eliasdb/graph/data" ) /* EndpointFindQuery is the find endpoint URL (rooted). Handles everything under find/... */ const EndpointFindQuery = api.APIRoot + APIv1 + "/find/" /* FindEndpointInst creates a new endpoint handler. */ func FindEndpointInst() api.RestEndpointHandler { return &findEndpoint{} } /* Handler object for find queries. */ type findEndpoint struct { *api.DefaultEndpointHandler } /* HandleGET handles a search query REST call. */ func (ie *findEndpoint) HandleGET(w http.ResponseWriter, r *http.Request, resources []string) { var err error ret := make(map[string]map[string][]interface{}) // Check what is queried text := r.URL.Query().Get("text") value := r.URL.Query().Get("value") if text == "" && value == "" { http.Error(w, "Query string for text (word or phrase) or value (exact match) is required", http.StatusBadRequest) return } lookup := stringutil.IsTrueValue(r.URL.Query().Get("lookup")) part := r.URL.Query().Get("part") parts := api.GM.Partitions() kinds := api.GM.NodeKinds() if part != "" && stringutil.IndexOf(part, parts) == -1 { err = fmt.Errorf("Partition %s does not exist", part) } if err == nil { // Go through all partitions for _, p := range parts { if strings.HasPrefix(p, "_") || part != "" && part != p { // Ignore partitions which start with an _ character or if they // are not searched for. continue } partitionData := make(map[string][]interface{}) ret[p] = partitionData // Go through all known node kinds for _, k := range kinds { var iq graph.IndexQuery var nodes []interface{} nodeMap := make(map[string]interface{}) // NodeIndexQuery may return nil nil if the node kind does not exist // in a partition if iq, err = api.GM.NodeIndexQuery(p, k); err == nil && iq != nil { // Go through all known attributes of the node kind for _, attr := range api.GM.NodeAttrs(k) { var keys []string // Run the lookup on all attributes if text != "" { keys, err = iq.LookupPhrase(attr, text) } else { keys, err = iq.LookupValue(attr, value) } // Lookup all nodes for _, key := range keys { var node data.Node if _, ok := nodeMap[key]; !ok && err == nil { if lookup { if node, err = api.GM.FetchNode(p, key, k); node != nil { nodeMap[key] = node.Data() } } else { nodeMap[key] = map[string]interface{}{ data.NodeKey: key, data.NodeKind: k, } } } } } } for _, n := range nodeMap { nodes = append(nodes, n) } if nodes != nil { partitionData[k] = nodes } } } } // Check if there was an error if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Write data w.Header().Set("content-type", "application/json; charset=utf-8") e := json.NewEncoder(w) e.Encode(ret) } /* SwaggerDefs is used to describe the endpoint in swagger. */ func (ie *findEndpoint) SwaggerDefs(s map[string]interface{}) { s["paths"].(map[string]interface{})["/v1/find"] = map[string]interface{}{ "get": map[string]interface{}{ "summary": "Run index searches on the EliasDB datastore.", "description": "The find endpoint should be used to run simple index searches for either a value or a phrase.", "produces": []string{ "text/plain", "application/json", }, "parameters": []map[string]interface{}{ { "name": "text", "in": "query", "description": "A word or phrase to search for.", "required": false, "type": "string", }, { "name": "value", "in": "query", "description": "A node value to search for.", "required": false, "type": "string", }, { "name": "lookup", "in": "query", "description": "Flag if a complete node lookup should be done (otherwise only key and kind are returned).", "required": false, "type": "boolean", }, { "name": "part", "in": "query", "description": "Limit the search to a partition (without the option all partitions are searched).", "required": false, "type": "string", }, }, "responses": map[string]interface{}{ "200": map[string]interface{}{ "description": "An object of search results.", "schema": map[string]interface{}{ "type": "object", "description": "Object of results per partition.", "properties": map[string]interface{}{ "partition": map[string]interface{}{ "type": "object", "description": "Object of results per kind.", "properties": map[string]interface{}{ "kind": map[string]interface{}{ "description": "List of found nodes.", "type": "array", "items": map[string]interface{}{ "description": "Found node.", "type": "object", }, }, }, }, }, }, }, "default": map[string]interface{}{ "description": "Error response", "schema": map[string]interface{}{ "$ref": "#/definitions/Error", }, }, }, }, } // Add generic error object to definition s["definitions"].(map[string]interface{})["Error"] = map[string]interface{}{ "description": "A human readable error mesage.", "type": "string", } }