Skip to content

Commit

Permalink
Merge pull request #43 from tapglue/improved-user-search
Browse files Browse the repository at this point in the history
Improve user search
  • Loading branch information
xla authored Jun 8, 2017
2 parents ea21c29 + e240997 commit 93072af
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 191 deletions.
1 change: 1 addition & 0 deletions core/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ func UserSearch(
Firstnames: t,
Lastnames: t,
Limit: opts.Limit,
Query: query,
Usernames: t,
})
if err != nil {
Expand Down
8 changes: 7 additions & 1 deletion error/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ var (

// User errors.
var (
ErrUserExists = errors.New("user not unique")
ErrInvalidQuery = errors.New("invalid query param")
ErrUserExists = errors.New("user not unique")
)

// Error wrapper.
Expand Down Expand Up @@ -74,6 +75,11 @@ func IsReactionNotFound(err error) bool {
return unwrapError(err) == ErrReactionNotFound
}

// IsInvalidQuery indicates if err is ErrInvalidQuery.
func IsInvalidQuery(err error) bool {
return unwrapError(err) == ErrInvalidQuery
}

// IsUserExists indicates if err is ErrUserExists.
func IsUserExists(err error) bool {
return unwrapError(err) == ErrUserExists
Expand Down
28 changes: 28 additions & 0 deletions handler/http/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,30 @@ func extractLimit(r *http.Request) (int, error) {
return limit, nil
}

func extractOffsetCursorBefore(r *http.Request) (uint, error) {
var (
param = r.URL.Query().Get(keyCursorBefore)

offset uint
)

if param == "" {
return offset, nil
}

cursor, err := cursorEncoding.DecodeString(param)
if err != nil {
return offset, err
}

o, err := strconv.ParseUint(string(cursor), 10, 64)
if err != nil {
return offset, err
}

return uint(o), nil
}

func extractReactionType(r *http.Request) (reaction.Type, error) {
t, ok := map[string]reaction.Type{
"like": reaction.TypeLike,
Expand Down Expand Up @@ -377,6 +401,10 @@ func toIDCursor(id uint64) string {
return cursorEncoding.EncodeToString([]byte(strconv.FormatUint(id, 10)))
}

func toOffsetCursor(offset uint) string {
return cursorEncoding.EncodeToString([]byte(strconv.FormatUint(uint64(offset), 10)))
}

func toTimeCursor(t time.Time) string {
return cursorEncoding.EncodeToString([]byte(t.Format(cursorTimeFormat)))
}
18 changes: 15 additions & 3 deletions handler/http/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,13 @@ func UserSearch(fn core.UserSearchFunc) Handler {
return
}

opts.Before, err = extractIDCursorBefore(r)
opts.Limit, err = extractLimit(r)
if err != nil {
respondError(w, 0, wrapError(ErrBadRequest, err.Error()))
return
}

opts.Limit, err = extractLimit(r)
opts.Offset, err = extractOffsetCursorBefore(r)
if err != nil {
respondError(w, 0, wrapError(ErrBadRequest, err.Error()))
return
Expand All @@ -230,7 +230,7 @@ func UserSearch(fn core.UserSearchFunc) Handler {
r,
opts.Limit,
userCursorAfter(us, opts.Limit),
userCursorBefore(us, opts.Limit),
userSearchCursorBefore(us, opts.Limit, opts.Offset),
keyUserQuery, query,
),
users: us,
Expand Down Expand Up @@ -601,3 +601,15 @@ func userCursorBefore(us user.List, limit int) string {

return before
}

func userSearchCursorAfter(us user.List, limit int, offset uint) string {
if offset == 0 || offset <= uint(limit) {
return toOffsetCursor(0)
}

return toOffsetCursor(offset - uint(limit))
}

func userSearchCursorBefore(us user.List, limit int, offset uint) string {
return toOffsetCursor(uint(limit) + offset)
}
8 changes: 4 additions & 4 deletions infrastructure/scripts/build-container
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ set -e
echo "|> prepare env"
export PROJECT=/home/ubuntu/.go_workspace/src/github.com/tapglue/snaas
export REVISION=$(git rev-parse --short HEAD)
source ~/.gimme/envs/go1.7.5.env
source ~/.gimme/envs/go1.8.3.env

echo "|> build console"
docker run \
-e GODEBUG=netdns=go \
--rm \
-v ~/.go_workspace:/go \
-w /go/src/github.com/tapglue/snaas \
golang:1.7.5-alpine3.5 \
golang:1.8.3-alpine3.6 \
go build \
-ldflags "-X main.revision=${REVISION}" \
-o console_${CIRCLE_BUILD_NUM} \
Expand All @@ -25,7 +25,7 @@ docker run \
--rm \
-v ~/.go_workspace:/go \
-w /go/src/github.com/tapglue/snaas \
golang:1.7.5-alpine3.5 \
golang:1.8.3-alpine3.6 \
go build \
-ldflags "-X main.revision=${REVISION}" \
-o gateway-http_${CIRCLE_BUILD_NUM} \
Expand All @@ -37,7 +37,7 @@ docker run \
--rm \
-v ~/.go_workspace:/go \
-w /go/src/github.com/tapglue/snaas \
golang:1.7.5-alpine3.5 \
golang:1.8.3-alpine3.6 \
go build \
-ldflags "-X main.revision=${REVISION}" \
-o sims_${CIRCLE_BUILD_NUM} \
Expand Down
6 changes: 3 additions & 3 deletions infrastructure/scripts/dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ sudo curl -sL -o /usr/local/bin/gimme https://raw.githubusercontent.com/travis-c
sudo chmod +x /usr/local/bin/gimme

echo "|> install Go"
gimme 1.7.5
source ~/.gimme/envs/go1.7.5.env
gimme 1.8.3
source ~/.gimme/envs/go1.8.3.env

echo "|> install static asset tool"
go get github.com/mjibson/esc
Expand Down Expand Up @@ -39,4 +39,4 @@ cp -R /home/ubuntu/snaas /home/ubuntu/.go_workspace/src/github.com/tapglue/

echo "|> install Go packages"
cd /home/ubuntu/.go_workspace/src/github.com/tapglue/snaas
go get -d -v ./...
go get -d -v -t ./...
2 changes: 1 addition & 1 deletion infrastructure/scripts/execute-go-tests
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ set -o pipefail

echo "|> prepare env"
export CWD=$(pwd)
source ~/.gimme/envs/go1.7.5.env
source ~/.gimme/envs/go1.8.3.env

echo "|> execute Go tests"
cd /home/ubuntu/.go_workspace/src/github.com/tapglue/snaas
Expand Down
66 changes: 12 additions & 54 deletions service/user/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import (
"fmt"
"math/rand"
"reflect"
"strings"
"testing"
"time"

"github.com/drhodes/golorem"

serr "github.com/tapglue/snaas/error"
"github.com/tapglue/snaas/platform/generate"
)
Expand Down Expand Up @@ -431,19 +434,15 @@ func testServiceSearch(t *testing.T, p prepareFunc) {
service = p(t, namespace)
)

us, err := service.Search(namespace, QueryOptions{})
if err != nil {
t.Fatal(err)
}

if have, want := len(us), 0; have != want {
t.Errorf("have %v, want %v", have, want)
_, err := service.Search(namespace, QueryOptions{})
if err == nil {
t.Errorf("expected error when query is empty")
}

u := testUser()
u.Firstname = generate.RandomString(12)
u.Lastname = generate.RandomString(12)
u.Username = generate.RandomString(8)
u.Username = "time"

created, err := service.Put(namespace, u)
if err != nil {
Expand All @@ -457,57 +456,16 @@ func testServiceSearch(t *testing.T, p prepareFunc) {
}
}

us, err = service.Search(namespace, QueryOptions{
Emails: []string{
created.Email[0 : len(u.Email)-3],
},
})
if err != nil {
t.Fatal(err)
}

if have, want := len(us), 1; have != want {
t.Errorf("have %v, want %v", have, want)
}

us, err = service.Search(namespace, QueryOptions{
Firstnames: []string{
created.Firstname[1:10],
},
})
if err != nil {
t.Fatal(err)
}

if have, want := len(us), 1; have != want {
t.Errorf("have %v, want %v", have, want)
}

us, err = service.Search(namespace, QueryOptions{
Lastnames: []string{
created.Lastname[1:10],
},
})
if err != nil {
t.Fatal(err)
}

if have, want := len(us), 1; have != want {
t.Errorf("have %v, want %v", have, want)
}

us, err = service.Search(namespace, QueryOptions{
us, err := service.Search(namespace, QueryOptions{
Enabled: &defaultEnabled,
Usernames: []string{
created.Username[3:7],
},
Query: created.Username[:3],
})
if err != nil {
t.Fatal(err)
}

if have, want := len(us), 1; have != want {
t.Errorf("have %v, want %v", have, want)
if have, want := us[0], created; !reflect.DeepEqual(have, want) {
t.Errorf("\nhave %v\nwant %v", have, want)
}
}

Expand All @@ -520,7 +478,7 @@ func testUser() *User {
),
Enabled: true,
Password: generate.RandomString(8),
Username: generate.RandomString(8),
Username: strings.Join([]string{lorem.Word(4, 16), generate.RandomString(4)}, ""),
}
}

Expand Down
53 changes: 28 additions & 25 deletions service/user/mem.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package user

import (
"sort"
"strings"
"time"

"github.com/arbovm/levenshtein"

serr "github.com/tapglue/snaas/error"
"github.com/tapglue/snaas/platform/flake"
)

Expand Down Expand Up @@ -103,17 +107,33 @@ func (s *memService) Search(ns string, opts QueryOptions) (List, error) {
return nil, err
}

sOpts := opts
if opts.Query == "" {
return nil, serr.Wrap(serr.ErrInvalidQuery, "param is empty")
}

opts.Emails = nil
opts.Firstnames = nil
opts.Lastnames = nil
opts.Usernames = nil
us := s.users[ns].ToList()

us := filterList(s.users[ns].ToList(), opts)
us = searchUsers(us, sOpts)
sort.SliceStable(us, func(i, j int) bool {
return levenshtein.Distance(opts.Query, us[i].Username) < levenshtein.Distance(opts.Query, us[j].Username)
})

return us, nil
fs := List{}

for _, u := range us {
if levenshtein.Distance(opts.Query, u.Username) < 8 {
fs = append(fs, u)
}
}

if int(opts.Offset) > len(us) {
return List{}, nil
}

if opts.Limit == 0 && opts.Offset == 0 {
return us, nil
}

return us[int(opts.Offset) : int(opts.Offset)+opts.Limit], nil
}

func (s *memService) Setup(ns string) error {
Expand Down Expand Up @@ -240,20 +260,3 @@ func inTypes(ty string, ts []string) bool {

return keep
}

func searchUsers(is List, opts QueryOptions) List {
us := List{}

for _, u := range is {
if !contains(u.Email, opts.Emails...) ||
!contains(u.Firstname, opts.Firstnames...) ||
!contains(u.Lastname, opts.Lastnames...) ||
!contains(u.Username, opts.Usernames...) {
continue
}

us = append(us, u)
}

return us
}
Loading

0 comments on commit 93072af

Please sign in to comment.