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 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",
- }
- }
|