diff --git a/CHANGELOG.md b/CHANGELOG.md index 55acad3478199dffefdafa59cd7a05c6f0819ee1..439425edb0392fbe2b1b935135f256dad5d016a4 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr ## [Unreleased] ### Added - Allow HTTP key to be read from an HTTP request's Basic auth header if present. +- Add prefix search for storage keys in console (key%). ### Changed - Use Steam partner API instead of public API for Steam profiles and friends requests. @@ -18,7 +19,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr - Fixed multiple issues found by linter. - Fix storage index listing results sometimes being returned with incorrect order. - Fixes calculation of leaderboard and tournament times for rare types of CRON expressions that don't execute at a fixed interval. -- Improved how start and end times are calculated for tournaments occuring in the future. +- Improved how start and end times are calculated for tournaments occurring in the future. - Fix users receiving friend request notifications when added by users who have blocked them. ### [3.17.1] - 2023-08-23 diff --git a/server/console_storage.go b/server/console_storage.go index 190f1b3b9347df73b074c483a875fc65185eb105..794966c1de6041526f19e416b19b61feadba8cb0 100644 --- a/server/console_storage.go +++ b/server/console_storage.go @@ -22,6 +22,7 @@ import ( "encoding/gob" "encoding/json" "strconv" + "strings" "sync/atomic" "github.com/gofrs/uuid/v5" @@ -157,7 +158,7 @@ func (s *ConsoleServer) ListStorage(ctx context.Context, in *console.ListStorage return nil, status.Error(codes.InvalidArgument, "Cursor not allowed when filter only contains user ID.") } // Pagination not allowed when filtering by collection, key, and user ID all at once. Don't process the cursor further. - if in.Collection != "" && in.Key != "" && userID != nil { + if in.Collection != "" && in.Key != "" && userID != nil && !isPrefixSearch(in.Key) { return nil, status.Error(codes.InvalidArgument, "Cursor not allowed when filter only contains collection, key, and user ID.") } @@ -176,7 +177,7 @@ func (s *ConsoleServer) ListStorage(ctx context.Context, in *console.ListStorage if in.Collection != "" && in.Collection != cursor.Collection { return nil, status.Error(codes.InvalidArgument, "Requires a matching cursor and collection filter property.") } - if in.Key != "" && in.Key != cursor.Key { + if in.Key != "" && (!isPrefixSearch(in.Key) && in.Key != cursor.Key) { return nil, status.Error(codes.InvalidArgument, "Requires a matching cursor and key filter property.") } if in.UserId != "" && in.UserId != cursor.UserID.String() { @@ -193,8 +194,10 @@ func (s *ConsoleServer) ListStorage(ctx context.Context, in *console.ListStorage // - user_id // - collection // - collection + key + // - collection + key% (prefix search) // - collection + user_id // - collection + key + user_id + // - collection + key% + user_id switch { case in.Collection == "" && in.Key == "" && userID == nil: // No filter. Querying and paginating on primary key (collection, read, key, user_id). @@ -220,6 +223,16 @@ func (s *ConsoleServer) ListStorage(ctx context.Context, in *console.ListStorage } params = append(params, limit+1) query += " ORDER BY read ASC, key ASC, user_id ASC LIMIT $" + strconv.Itoa(len(params)) + case in.Collection != "" && in.Key != "" && userID == nil && isPrefixSearch(in.Key): + // Collection and key%. Querying and paginating on unique index (collection, key, user_id). + params = []interface{}{in.Collection, in.Key} + query = "SELECT collection, key, user_id, value, version, read, write, create_time, update_time FROM storage WHERE collection = $1 AND key LIKE $2" + if cursor != nil { + params = append(params, cursor.Key, cursor.UserID) + query += " AND (collection, key, user_id) > ($1, $3, $4)" + } + params = append(params, limit+1) + query += " ORDER BY collection ASC, key ASC, user_id ASC LIMIT $" + strconv.Itoa(len(params)) case in.Collection != "" && in.Key != "" && userID == nil: // Collection and key. Querying and paginating on unique index (collection, key, user_id). params = []interface{}{in.Collection, in.Key} @@ -240,6 +253,16 @@ func (s *ConsoleServer) ListStorage(ctx context.Context, in *console.ListStorage } params = append(params, limit+1) query += " ORDER BY read ASC, key ASC LIMIT $" + strconv.Itoa(len(params)) + case in.Collection != "" && in.Key != "" && userID != nil && isPrefixSearch(in.Key): + // Collection, key%, user ID. Querying and paginating on unique index (collection, key, user_id). + params = []interface{}{in.Collection, in.Key, *userID} + query = "SELECT collection, key, user_id, value, version, read, write, create_time, update_time FROM storage WHERE collection = $1 AND key LIKE $2 AND user_id = $3" + if cursor != nil { + params = append(params, cursor.Key) + query += " AND (collection, key, user_id) > ($1, $4, $3)" + } + params = append(params, limit+1) + query += " ORDER BY collection ASC, key ASC, user_id ASC LIMIT $" + strconv.Itoa(len(params)) case in.Collection != "" && in.Key != "" && userID != nil: // Filtering by collection, key, user ID returns 0 or 1 results, no pagination or limit. Querying on unique index (collection, key, user_id). limit = 0 @@ -404,3 +427,7 @@ func countDatabase(ctx context.Context, logger *zap.Logger, db *sql.DB, tableNam } return int32(count.Int64) } + +func isPrefixSearch(key string) bool { + return strings.HasSuffix(key, "%") && strings.Count(key, "%") == 1 +}