summary refs log tree commit diff stats
path: root/svc/query.go
diff options
context:
space:
mode:
Diffstat (limited to 'svc/query.go')
-rw-r--r--svc/query.go146
1 files changed, 146 insertions, 0 deletions
diff --git a/svc/query.go b/svc/query.go
new file mode 100644
index 0000000..037b0e5
--- /dev/null
+++ b/svc/query.go
@@ -0,0 +1,146 @@
+package svc // import "github.com/getwtxt/getwtxt/svc"
+
+import (
+	"crypto/sha256"
+	"fmt"
+	"log"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/getwtxt/registry"
+	"github.com/gorilla/mux"
+)
+
+func apiErrCheck(err error, r *http.Request) {
+	if err != nil {
+		uip := getIPFromCtx(r.Context())
+		log.Printf("*** %v :: %v %v :: %v\n", uip, r.Method, r.URL, err.Error())
+	}
+}
+
+// Takes the output of queries and formats it for
+// an HTTP response. Iterates over the string slice,
+// appending each entry to a byte slice, and adding
+// newlines where appropriate.
+func parseQueryOut(out []string) []byte {
+	var data []byte
+
+	for i, e := range out {
+		data = append(data, []byte(e)...)
+
+		if !strings.HasSuffix(e, "\n") && i != len(out)-1 {
+			data = append(data, byte('\n'))
+		}
+	}
+
+	return data
+}
+
+// Removes duplicate statuses from query output
+func uniq(str []string) []string {
+	keys := make(map[string]bool)
+	out := []string{}
+	for _, e := range str {
+		if _, ok := keys[e]; !ok {
+			keys[e] = true
+			out = append(out, e)
+		}
+	}
+	return out
+}
+
+// apiUserQuery is called via apiEndpointHandler when
+// the endpoint is "users" and r.FormValue("q") is not empty.
+// It queries the registry cache for users or user URLs
+// matching the term supplied via r.FormValue("q")
+func apiEndpointQuery(w http.ResponseWriter, r *http.Request) error {
+	query := r.FormValue("q")
+	urls := r.FormValue("url")
+	pageVal := r.FormValue("page")
+	var out []string
+	var err error
+
+	pageVal = strings.TrimSpace(pageVal)
+	page, err := strconv.Atoi(pageVal)
+	if err != nil {
+		log.Printf("%v\n", err.Error())
+	}
+
+	vars := mux.Vars(r)
+	endpoint := vars["endpoint"]
+
+	// Handle user URL queries first, then nickname queries.
+	// Concatenate both outputs if they're both set.
+	// Also handle mention queries and status queries.
+	// If we made it this far and 'default' is matched,
+	// something went very wrong.
+	switch endpoint {
+	case "users":
+		var out2 []string
+		if query != "" {
+			out, err = twtxtCache.QueryUser(query)
+			apiErrCheck(err, r)
+		}
+		if urls != "" {
+			out2, err = twtxtCache.QueryUser(urls)
+			apiErrCheck(err, r)
+		}
+
+		if query != "" && urls != "" {
+			out = joinQueryOuts(out2)
+		}
+
+	case "mentions":
+		if urls == "" {
+			return fmt.Errorf("missing URL in mention query")
+		}
+		urls += ">"
+		out, err = twtxtCache.QueryInStatus(urls)
+		apiErrCheck(err, r)
+
+	case "tweets":
+		out = compositeStatusQuery(query, r)
+
+	default:
+		return fmt.Errorf("endpoint query, no cases match")
+	}
+
+	out = registry.ReduceToPage(page, out)
+	data := parseQueryOut(out)
+
+	etag := fmt.Sprintf("%x", sha256.Sum256(data))
+	w.Header().Set("ETag", etag)
+	w.Header().Set("Content-Type", txtutf8)
+
+	_, err = w.Write(data)
+
+	return err
+}
+
+func joinQueryOuts(data ...[]string) []string {
+	single := []string{}
+	for _, e := range data {
+		single = append(single, e...)
+	}
+	single = uniq(single)
+
+	return single
+}
+
+func compositeStatusQuery(query string, r *http.Request) []string {
+	query = strings.ToLower(query)
+	out, err := twtxtCache.QueryInStatus(query)
+	apiErrCheck(err, r)
+
+	query = strings.Title(query)
+	out2, err := twtxtCache.QueryInStatus(query)
+	apiErrCheck(err, r)
+
+	query = strings.ToUpper(query)
+	out3, err := twtxtCache.QueryInStatus(query)
+	apiErrCheck(err, r)
+
+	final := joinQueryOuts(out, out2, out3)
+	return final
+}