find.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /*
  2. * EliasDB
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the Mozilla Public
  7. * License, v. 2.0. If a copy of the MPL was not distributed with this
  8. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  9. */
  10. package v1
  11. import (
  12. "encoding/json"
  13. "fmt"
  14. "net/http"
  15. "strings"
  16. "devt.de/krotik/common/stringutil"
  17. "devt.de/krotik/eliasdb/api"
  18. "devt.de/krotik/eliasdb/graph"
  19. "devt.de/krotik/eliasdb/graph/data"
  20. )
  21. /*
  22. EndpointFindQuery is the find endpoint URL (rooted). Handles everything under find/...
  23. */
  24. const EndpointFindQuery = api.APIRoot + APIv1 + "/find/"
  25. /*
  26. FindEndpointInst creates a new endpoint handler.
  27. */
  28. func FindEndpointInst() api.RestEndpointHandler {
  29. return &findEndpoint{}
  30. }
  31. /*
  32. Handler object for find queries.
  33. */
  34. type findEndpoint struct {
  35. *api.DefaultEndpointHandler
  36. }
  37. /*
  38. HandleGET handles a search query REST call.
  39. */
  40. func (ie *findEndpoint) HandleGET(w http.ResponseWriter, r *http.Request, resources []string) {
  41. var err error
  42. ret := make(map[string]map[string][]interface{})
  43. // Check what is queried
  44. text := r.URL.Query().Get("text")
  45. value := r.URL.Query().Get("value")
  46. if text == "" && value == "" {
  47. http.Error(w, "Query string for text (word or phrase) or value (exact match) is required", http.StatusBadRequest)
  48. return
  49. }
  50. lookup := stringutil.IsTrueValue(r.URL.Query().Get("lookup"))
  51. part := r.URL.Query().Get("part")
  52. parts := api.GM.Partitions()
  53. kinds := api.GM.NodeKinds()
  54. if part != "" && stringutil.IndexOf(part, parts) == -1 {
  55. err = fmt.Errorf("Partition %s does not exist", part)
  56. }
  57. if err == nil {
  58. // Go through all partitions
  59. for _, p := range parts {
  60. if strings.HasPrefix(p, "_") || part != "" && part != p {
  61. // Ignore partitions which start with an _ character or if they
  62. // are not searched for.
  63. continue
  64. }
  65. partitionData := make(map[string][]interface{})
  66. ret[p] = partitionData
  67. // Go through all known node kinds
  68. for _, k := range kinds {
  69. var iq graph.IndexQuery
  70. var nodes []interface{}
  71. nodeMap := make(map[string]interface{})
  72. // NodeIndexQuery may return nil nil if the node kind does not exist
  73. // in a partition
  74. if iq, err = api.GM.NodeIndexQuery(p, k); err == nil && iq != nil {
  75. // Go through all known attributes of the node kind
  76. for _, attr := range api.GM.NodeAttrs(k) {
  77. var keys []string
  78. // Run the lookup on all attributes
  79. if text != "" {
  80. keys, err = iq.LookupPhrase(attr, text)
  81. } else {
  82. keys, err = iq.LookupValue(attr, value)
  83. }
  84. // Lookup all nodes
  85. for _, key := range keys {
  86. var node data.Node
  87. if _, ok := nodeMap[key]; !ok && err == nil {
  88. if lookup {
  89. if node, err = api.GM.FetchNode(p, key, k); node != nil {
  90. nodeMap[key] = node.Data()
  91. }
  92. } else {
  93. nodeMap[key] = map[string]interface{}{
  94. data.NodeKey: key,
  95. data.NodeKind: k,
  96. }
  97. }
  98. }
  99. }
  100. }
  101. }
  102. for _, n := range nodeMap {
  103. nodes = append(nodes, n)
  104. }
  105. if nodes != nil {
  106. partitionData[k] = nodes
  107. }
  108. }
  109. }
  110. }
  111. // Check if there was an error
  112. if err != nil {
  113. http.Error(w, err.Error(), http.StatusInternalServerError)
  114. return
  115. }
  116. // Write data
  117. w.Header().Set("content-type", "application/json; charset=utf-8")
  118. e := json.NewEncoder(w)
  119. e.Encode(ret)
  120. }
  121. /*
  122. SwaggerDefs is used to describe the endpoint in swagger.
  123. */
  124. func (ie *findEndpoint) SwaggerDefs(s map[string]interface{}) {
  125. s["paths"].(map[string]interface{})["/v1/find"] = map[string]interface{}{
  126. "get": map[string]interface{}{
  127. "summary": "Run index searches on the EliasDB datastore.",
  128. "description": "The find endpoint should be used to run simple index searches for either a value or a phrase.",
  129. "produces": []string{
  130. "text/plain",
  131. "application/json",
  132. },
  133. "parameters": []map[string]interface{}{
  134. {
  135. "name": "text",
  136. "in": "query",
  137. "description": "A word or phrase to search for.",
  138. "required": false,
  139. "type": "string",
  140. },
  141. {
  142. "name": "value",
  143. "in": "query",
  144. "description": "A node value to search for.",
  145. "required": false,
  146. "type": "string",
  147. },
  148. {
  149. "name": "lookup",
  150. "in": "query",
  151. "description": "Flag if a complete node lookup should be done (otherwise only key and kind are returned).",
  152. "required": false,
  153. "type": "boolean",
  154. },
  155. {
  156. "name": "part",
  157. "in": "query",
  158. "description": "Limit the search to a partition (without the option all partitions are searched).",
  159. "required": false,
  160. "type": "string",
  161. },
  162. },
  163. "responses": map[string]interface{}{
  164. "200": map[string]interface{}{
  165. "description": "An object of search results.",
  166. "schema": map[string]interface{}{
  167. "type": "object",
  168. "description": "Object of results per partition.",
  169. "properties": map[string]interface{}{
  170. "partition": map[string]interface{}{
  171. "type": "object",
  172. "description": "Object of results per kind.",
  173. "properties": map[string]interface{}{
  174. "kind": map[string]interface{}{
  175. "description": "List of found nodes.",
  176. "type": "array",
  177. "items": map[string]interface{}{
  178. "description": "Found node.",
  179. "type": "object",
  180. },
  181. },
  182. },
  183. },
  184. },
  185. },
  186. },
  187. "default": map[string]interface{}{
  188. "description": "Error response",
  189. "schema": map[string]interface{}{
  190. "$ref": "#/definitions/Error",
  191. },
  192. },
  193. },
  194. },
  195. }
  196. // Add generic error object to definition
  197. s["definitions"].(map[string]interface{})["Error"] = map[string]interface{}{
  198. "description": "A human readable error mesage.",
  199. "type": "string",
  200. }
  201. }