| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 | /* * 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 v1import (	"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",	}}
 |