diff --git a/db/elasticsearch_mapsets.go b/db/elasticsearch_mapsets.go index 2940de6..961c18a 100644 --- a/db/elasticsearch_mapsets.go +++ b/db/elasticsearch_mapsets.go @@ -90,14 +90,34 @@ func (options *ElasticMapsetSearchOptions) BindAndValidate() { }) } +// ElasticMap stores a difficulty document; MapsetPlayCount is duplicated on each difficulty so collapsed mapset searches can sort by the mapset total. type ElasticMap struct { *MapQua Explicit bool `json:"explicit"` PackageMD5 string `json:"package_md5"` + MapsetPlayCount int64 `json:"mapset_play_count"` DateSubmitted int64 `json:"date_submitted"` DateLastUpdated int64 `json:"date_last_updated"` } +func getMapsetPlayCount(mapset Mapset) int64 { + var playCount int64 + + for _, mapQua := range mapset.Maps { + playCount += int64(mapQua.PlayCount) + } + + return playCount +} + +func elasticMapsetSortField(sort string) string { + if sort == "play_count" { + return "mapset_play_count" + } + + return sort +} + var tagSearchTerms = []string{ "scroll velocity", "sv", @@ -156,12 +176,15 @@ func IndexElasticSearchMapset(mapset Mapset) error { return err } + mapsetPlayCount := getMapsetPlayCount(mapset) + for _, mapQua := range mapset.Maps { elasticMap := ElasticMap{ MapQua: mapQua, DateSubmitted: mapset.DateSubmitted, DateLastUpdated: mapset.DateLastUpdated, Explicit: mapset.IsExplicit, + MapsetPlayCount: mapsetPlayCount, } data, err := json.Marshal(&elasticMap) @@ -185,12 +208,15 @@ func IndexElasticSearchMapset(mapset Mapset) error { // UpdateElasticSearchMapset Updates an individual mapset in elastic func UpdateElasticSearchMapset(mapset Mapset) error { + mapsetPlayCount := getMapsetPlayCount(mapset) + for _, mapQua := range mapset.Maps { elasticMap := ElasticMap{ MapQua: mapQua, DateSubmitted: mapset.DateSubmitted, DateLastUpdated: mapset.DateLastUpdated, Explicit: mapset.IsExplicit, + MapsetPlayCount: mapsetPlayCount, } data, err := json.Marshal(&elasticMap) @@ -265,10 +291,13 @@ func IndexAllElasticSearchMapsets(deletePrevious bool) error { // Put all mapsets into the task queue for _, mapset := range mapsets { + mapsetPlayCount := getMapsetPlayCount(*mapset) + for _, mapQua := range mapset.Maps { elasticMap := ElasticMap{ MapQua: mapQua, PackageMD5: mapset.PackageMD5, + MapsetPlayCount: mapsetPlayCount, DateSubmitted: mapset.DateSubmitted, DateLastUpdated: mapset.DateLastUpdated, Explicit: mapset.IsExplicit, @@ -483,6 +512,8 @@ func SearchElasticMapsets(options *ElasticMapsetSearchOptions) ([]*Mapset, int, sortOrder = options.SortOrder } + sortField := elasticMapsetSortField(sort) + if options.IsClanRanked { clanRankedTerm := TermCustom{} clanRankedTerm.Term.IsClanRanked = &Term{ @@ -497,11 +528,11 @@ func SearchElasticMapsets(options *ElasticMapsetSearchOptions) ([]*Mapset, int, // Prioritize relevance first, then fall back to the requested sort. sortFields = []map[string]SortOrder{ {"_score": {Order: "desc"}}, - {sort: {Order: sortOrder}}, + {sortField: {Order: sortOrder}}, } } else { sortFields = []map[string]SortOrder{ - {sort: {Order: sortOrder}}, + {sortField: {Order: sortOrder}}, } } @@ -579,6 +610,7 @@ func SearchElasticMapsets(options *ElasticMapsetSearchOptions) ([]*Mapset, int, DateLastUpdated: firstHit.DateLastUpdated, DateLastUpdatedJSON: time.UnixMilli(firstHit.DateLastUpdated), IsVisible: true, + IsExplicit: firstHit.Explicit, } mapsets = append(mapsets, mapset) diff --git a/db/elasticsearch_mapsets_test.go b/db/elasticsearch_mapsets_test.go new file mode 100644 index 0000000..649b9a6 --- /dev/null +++ b/db/elasticsearch_mapsets_test.go @@ -0,0 +1,27 @@ +package db + +import "testing" + +func TestGetMapsetPlayCount(t *testing.T) { + mapset := Mapset{ + Maps: []*MapQua{ + {PlayCount: 12}, + {PlayCount: 345}, + {PlayCount: 6789}, + }, + } + + if got := getMapsetPlayCount(mapset); got != 7146 { + t.Fatalf("getMapsetPlayCount() = %d, want %d", got, 7146) + } +} + +func TestElasticMapsetSortField(t *testing.T) { + if got := elasticMapsetSortField("play_count"); got != "mapset_play_count" { + t.Fatalf("elasticMapsetSortField(play_count) = %q, want %q", got, "mapset_play_count") + } + + if got := elasticMapsetSortField("date_last_updated"); got != "date_last_updated" { + t.Fatalf("elasticMapsetSortField(date_last_updated) = %q, want %q", got, "date_last_updated") + } +} diff --git a/db/users.go b/db/users.go index 28a0d4e..aa19b86 100644 --- a/db/users.go +++ b/db/users.go @@ -69,6 +69,7 @@ type UserInformation struct { Twitter string `json:"twitter,omitempty"` Twitch string `json:"twitch,omitempty"` Youtube string `json:"youtube,omitempty"` + DefaultCover string `json:"default_cover,omitempty"` NotifyMapsetActions bool `json:"notif_action_mapset,omitempty"` DefaultMode enums.GameMode `json:"default_mode,omitempty"` } diff --git a/handlers/users.go b/handlers/users.go index 4b24a02..138a507 100644 --- a/handlers/users.go +++ b/handlers/users.go @@ -121,8 +121,8 @@ func UpdateUserAboutMe(c *gin.Context) *APIError { return APIErrorBadRequest("Invalid request body") } - if len(body.AboutMe) > 3000 { - return APIErrorBadRequest("Your about me must not be longer than 2,000 characters.") + if len(body.AboutMe) > 5000 { + return APIErrorBadRequest("Your about me must not be longer than 5,000 characters.") } body.AboutMe = stringutil.SanitizeHTML(body.AboutMe) diff --git a/handlers/users_test.go b/handlers/users_test.go index 06c8b87..81940db 100644 --- a/handlers/users_test.go +++ b/handlers/users_test.go @@ -31,6 +31,7 @@ func TestParseUserInformationAcceptsAllFields(t *testing.T) { "twitter":"twitter", "twitch":"twitch", "youtube":"youtube", + "default_cover":"cover.jpg", "notif_action_mapset":false, "default_mode":2 }`)) @@ -40,11 +41,23 @@ func TestParseUserInformationAcceptsAllFields(t *testing.T) { if information.Discord != "discord" || information.Twitter != "twitter" || information.Twitch != "twitch" || information.Youtube != "youtube" || - information.NotifyMapsetActions || information.DefaultMode != enums.GameModeKeys7 { + information.DefaultCover != "cover.jpg" || information.NotifyMapsetActions || + information.DefaultMode != enums.GameModeKeys7 { t.Fatalf("unexpected information: %#v", information) } } +func TestParseUserInformationAcceptsEmptyDefaultCover(t *testing.T) { + information, err := parseUserInformation(strings.NewReader(`{"default_cover":""}`)) + if err != nil { + t.Fatal(err) + } + + if information.DefaultCover != "" { + t.Fatalf("expected empty default cover, got %q", information.DefaultCover) + } +} + func TestParseUserInformationAcceptsValuesUpTo100Characters(t *testing.T) { value := strings.Repeat("a", maxUserInformationValueLength) body := fmt.Sprintf(`{