Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

/bettors command #90

Merged
merged 6 commits into from
Jul 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
365 changes: 189 additions & 176 deletions api/bettor/v1alpha/bettor.pb.go

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions api/bettor/v1alpha/bettor.pb.validate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions api/bettor/v1alpha/bettor.proto
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ message ListUsersRequest {
string page_token = 2;
string book = 3 [(validate.rules).string.min_len = 1];
repeated string users = 4;
// valid options: "name" asc (default), "total_centipoints" desc
// NOTE: "total_centipoints" cannot be paginated at the moment
string order_by = 5 [(validate.rules).string = {
in: [
"",
"name",
"total_centipoints"
]
}];
}

message ListUsersResponse {
Expand Down
1 change: 1 addition & 0 deletions docs/bettor.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ A user's bet on a betting market.
| page_token | [string](#string) | | |
| book | [string](#string) | | |
| users | [string](#string) | repeated | |
| order_by | [string](#string) | | valid options: "name" asc (default), "total_centipoints" desc |



Expand Down
18 changes: 18 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,13 @@ <h3 id="bettor.v1alpha.ListUsersRequest">ListUsersRequest</h3>
<td><p> </p></td>
</tr>

<tr>
<td>order_by</td>
<td><a href="#string">string</a></td>
<td></td>
<td><p>valid options: &#34;name&#34; asc (default), &#34;total_centipoints&#34; desc </p></td>
</tr>

</tbody>
</table>

Expand Down Expand Up @@ -1370,6 +1377,17 @@ <h4>Validated Fields</h4>
</td>
</tr>

<tr>
<td>order_by</td>
<td>
<ul>

<li>string.in: [ name total_centipoints]</li>

</ul>
</td>
</tr>

</tbody>
</table>

Expand Down
2 changes: 1 addition & 1 deletion internal/app/bettor/discord/bettor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var getBettorCommand = &discordgo.ApplicationCommand{
Description: "Get your bettor stats",
}

// GetBettor is the handler for the /get-bet command.
// GetBettor is the handler for the /bettor command.
func GetBettor(ctx context.Context, client bettorClient) Handler {
return func(s *discordgo.Session, event *discordgo.InteractionCreate) (*discordgo.InteractionResponseData, error) {
guildID, discordUserID, _, err := commandArgs(event)
Expand Down
59 changes: 59 additions & 0 deletions internal/app/bettor/discord/bettors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package discord

import (
"context"
"fmt"
"strings"

"github.com/bufbuild/connect-go"
"github.com/bwmarrin/discordgo"
api "github.com/elh/bettor/api/bettor/v1alpha"
)

var getBettorsCommand = &discordgo.ApplicationCommand{
Name: "bettors",
Description: "Get bettor leaderboard",
}

// GetBettors is the handler for the /get-bet command.
func GetBettors(ctx context.Context, client bettorClient) Handler {
return func(s *discordgo.Session, event *discordgo.InteractionCreate) (*discordgo.InteractionResponseData, error) {
guildID, _, _, err := commandArgs(event)
if err != nil {
return nil, CErr("Failed to handle command", err)
}

listUsersResp, err := client.ListUsers(ctx, &connect.Request[api.ListUsersRequest]{Msg: &api.ListUsersRequest{
Book: guildBookName(guildID),
PageSize: 10,
OrderBy: "total_centipoints",
}})
if err != nil {
return nil, CErr("Failed to lookup bettors", err)
}

var formattedUsers []string
var args []interface{}
for i, user := range listUsersResp.Msg.GetUsers() {
msgformat, margs := formatUser(user, user.UnsettledCentipoints)
// hack
msgformat = strings.TrimSuffix(msgformat, "\n")

msgformat = fmt.Sprintf("%d", i+1) + ") " + msgformat
switch i {
case 0:
msgformat += " 🥇"
case 1:
msgformat += " 🥈"
case 2:
msgformat += " 🥉"
}

formattedUsers = append(formattedUsers, msgformat)
args = append(args, margs...)
}

msgformat := "🎲 👤\n\n" + strings.Join(formattedUsers, "\n")
return &discordgo.InteractionResponseData{Content: localized.Sprintf(msgformat, args...)}, nil
}
}
4 changes: 4 additions & 0 deletions internal/app/bettor/discord/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ func initCommands(ctx context.Context, client bettorClient, logger log.Logger) m
Def: getBettorCommand,
Handler: GetBettor(ctx, client),
},
"bettors": {
Def: getBettorsCommand,
Handler: GetBettors(ctx, client),
},
}

out := map[string]*DGCommand{}
Expand Down
35 changes: 29 additions & 6 deletions internal/app/bettor/repo/mem/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,36 @@ func (r *Repo) ListUsers(ctx context.Context, args *repo.ListUsersArgs) (users [
r.userMtx.RLock()
defer r.userMtx.RUnlock()
bookID := entity.BooksIDs(args.Book)
var out []*api.User //nolint:prealloc

// hydrate
var hydratedUsers []*api.User //nolint:prealloc
for _, u := range r.Users {
u, err := r.hydrateUser(ctx, u)
if err != nil {
return nil, false, connect.NewError(connect.CodeInternal, err)
}
hydratedUsers = append(hydratedUsers, u)
}

var orderedUsers []*api.User
switch args.OrderBy {
case "", "name":
orderedUsers = hydratedUsers
case "total_centipoints":
if args.GreaterThanName != "" {
return nil, false, connect.NewError(connect.CodeInvalidArgument, errors.New("cannot use GreaterThanName with total_centipoints order"))
}

sort.SliceStable(hydratedUsers, func(i, j int) bool {
return hydratedUsers[i].Centipoints+hydratedUsers[i].UnsettledCentipoints > hydratedUsers[j].Centipoints+hydratedUsers[j].UnsettledCentipoints
})
orderedUsers = hydratedUsers
default:
return nil, false, connect.NewError(connect.CodeInvalidArgument, errors.New("invalid order by"))
}

var out []*api.User //nolint:prealloc
for _, u := range orderedUsers {
uBookID, _ := entity.UserIDs(u.GetName())
if uBookID != bookID {
continue
Expand All @@ -142,11 +170,6 @@ func (r *Repo) ListUsers(ctx context.Context, args *repo.ListUsersArgs) (users [
if len(args.Users) > 0 && !containsStr(args.Users, u.GetName()) {
continue
}
// hydrate
u, err := r.hydrateUser(ctx, u)
if err != nil {
return nil, false, connect.NewError(connect.CodeInternal, errors.New("failed to compute unsettled points"))
}

out = append(out, u)
if len(out) >= args.Limit+1 {
Expand Down
2 changes: 2 additions & 0 deletions internal/app/bettor/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type ListUsersArgs struct {
GreaterThanName string
Users []string
Limit int
// valid options: "name" asc (default), "total_centipoints" desc
OrderBy string
}

// ListMarketsArgs are the arguments for listing markets.
Expand Down
41 changes: 31 additions & 10 deletions internal/app/bettor/server/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func (s *Server) GetUserByUsername(ctx context.Context, in *connect.Request[api.
}

// ListUsers lists users by filters.
// NOTE: "total_centipoints" cannot be paginated at the moment.
func (s *Server) ListUsers(ctx context.Context, in *connect.Request[api.ListUsersRequest]) (*connect.Response[api.ListUsersResponse], error) {
if err := in.Msg.Validate(); err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, err)
Expand All @@ -112,21 +113,41 @@ func (s *Server) ListUsers(ctx context.Context, in *connect.Request[api.ListUser
}
}

users, hasMore, err := s.Repo.ListUsers(ctx, &repo.ListUsersArgs{Book: in.Msg.GetBook(), GreaterThanName: cursor, Users: in.Msg.GetUsers(), Limit: pageSize})
if err != nil {
return nil, err
}

var users []*api.User
var nextPageToken string
if hasMore {
nextPageToken, err = pagination.ToToken(pagination.Pagination{
Cursor: users[len(users)-1].GetName(),
ListRequest: in.Msg,
})
switch in.Msg.GetOrderBy() {
case "", "name":
var hasMore bool
var err error
users, hasMore, err = s.Repo.ListUsers(ctx, &repo.ListUsersArgs{Book: in.Msg.GetBook(), GreaterThanName: cursor, Users: in.Msg.GetUsers(), Limit: pageSize, OrderBy: in.Msg.GetOrderBy()})
if err != nil {
return nil, err
}

if hasMore {
nextPageToken, err = pagination.ToToken(pagination.Pagination{
Cursor: users[len(users)-1].GetName(),
ListRequest: in.Msg,
})
if err != nil {
return nil, err
}
}
case "total_centipoints":
// NOTE: "total_centipoints" cannot be paginated at the moment.
if cursor != "" {
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("page token is not supported for order by total centipoints"))
}

var err error
users, _, err = s.Repo.ListUsers(ctx, &repo.ListUsersArgs{Book: in.Msg.GetBook(), Users: in.Msg.GetUsers(), Limit: pageSize, OrderBy: in.Msg.GetOrderBy()})
if err != nil {
return nil, err
}
default:
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("invalid order by"))
}

return connect.NewResponse(&api.ListUsersResponse{
Users: users,
NextPageToken: nextPageToken,
Expand Down
22 changes: 20 additions & 2 deletions internal/app/bettor/server/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,12 @@ func TestListUsers(t *testing.T) {
user3 := &api.User{
Name: entity.UserN(bookID, "c"),
Username: "linus",
Centipoints: 300,
Centipoints: 50,
}
unsettledBet := &api.Bet{
Name: entity.BetN(bookID, "a"),
User: user3.Name,
Centipoints: 200,
Centipoints: 100,
}
user3Hydrated := proto.Clone(user3).(*api.User)
user3Hydrated.UnsettledCentipoints += unsettledBet.Centipoints
Expand Down Expand Up @@ -325,6 +325,24 @@ func TestListUsers(t *testing.T) {
expected: []*api.User{user1, user2},
expectedCalls: 1,
},
// order by total_centipoints desc
{
desc: "order by total_centipoints desc",
req: &api.ListUsersRequest{Book: entity.BookN(bookID), OrderBy: "total_centipoints"},
expected: []*api.User{user2, user3Hydrated, user1},
expectedCalls: 1,
},
{
desc: "order by total_centipoints limit",
req: &api.ListUsersRequest{Book: entity.BookN(bookID), OrderBy: "total_centipoints", PageSize: 2},
expected: []*api.User{user2, user3Hydrated},
expectedCalls: 1,
},
{
desc: "order by invalid",
req: &api.ListUsersRequest{Book: entity.BookN(bookID), OrderBy: "bad"},
expectErr: true,
},
}
for _, tC := range testCases {
tC := tC
Expand Down