diff options
Diffstat (limited to 'svc/query.go')
-rw-r--r-- | svc/query.go | 146 |
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 +} |