From 5e3762d2add05e9d084a44dc3b7bd14e9e304ef8 Mon Sep 17 00:00:00 2001 From: AiAe Date: Mon, 7 Sep 2026 17:34:19 +0300 Subject: [PATCH 1/3] Change how we return playlist, and optimize add/remove --- db/playlists.go | 38 ++++++++++++++++++-- db/playlists_maps.go | 12 +++++++ handlers/limits.go | 22 +++++++++--- handlers/playlists.go | 80 ++++++++++++++++++++++++++++--------------- 4 files changed, 117 insertions(+), 35 deletions(-) diff --git a/db/playlists.go b/db/playlists.go index 721bf28..42b12ab 100644 --- a/db/playlists.go +++ b/db/playlists.go @@ -133,12 +133,44 @@ func GetPlaylist(id int) (*Playlist, error) { // GetPlaylistFull Gets an individual playlist with mapsets/maps included func GetPlaylistFull(id int) (*Playlist, error) { + return getPlaylistWithMapsets(id, nil) +} + +// GetPlaylistPage Gets an individual playlist with a page of mapsets/maps included +func GetPlaylistPage(id int, page int, limit int) (*Playlist, error) { + return getPlaylistWithMapsets(id, playlistMapsetPageScope(page, limit)) +} + +func playlistMapsetPageScope(page int, limit int) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db. + Order("playlists_mapsets.id ASC"). + Limit(limit). + Offset(page * limit) + } +} + +func playlistMapsetOrderScope(db *gorm.DB) *gorm.DB { + return db.Order("playlists_mapsets.id ASC") +} + +func playlistMapOrderScope(db *gorm.DB) *gorm.DB { + return db.Order("playlists_maps.id ASC") +} + +func getPlaylistWithMapsets(id int, mapsetScope func(*gorm.DB) *gorm.DB) (*Playlist, error) { var playlist *Playlist - result := SQL. - Preload("Mapsets"). + query := SQL + + if mapsetScope == nil { + mapsetScope = playlistMapsetOrderScope + } + + result := query. + Preload("Mapsets", mapsetScope). Preload("Mapsets.Mapset"). - Preload("Mapsets.Maps"). + Preload("Mapsets.Maps", playlistMapOrderScope). Preload("Mapsets.Maps.Map"). Joins("User"). Where("playlists.id = ? AND playlists.visible = 1", id). diff --git a/db/playlists_maps.go b/db/playlists_maps.go index 81a6c22..40a40c5 100644 --- a/db/playlists_maps.go +++ b/db/playlists_maps.go @@ -35,6 +35,18 @@ func DoesPlaylistContainMap(playlistId int, mapId int) (bool, error) { return true, nil } +// CountPlaylistMapsForMapset Counts the maps in a playlist mapset +func CountPlaylistMapsForMapset(playlistId int, playlistMapsetId int) (int64, error) { + var count int64 + + result := SQL. + Model(&PlaylistMap{}). + Where("playlist_id = ? AND playlists_mapsets_id = ?", playlistId, playlistMapsetId). + Count(&count) + + return count, result.Error +} + // Insert Inserts a playlist map into the db func (pm *PlaylistMap) Insert() error { if err := SQL.Create(&pm).Error; err != nil { diff --git a/handlers/limits.go b/handlers/limits.go index 7be79d6..1957de2 100644 --- a/handlers/limits.go +++ b/handlers/limits.go @@ -7,11 +7,12 @@ import ( ) const ( - defaultUserScoreLimit = 50 - defaultClanScoreLimit = 50 - defaultMostPlayedLimit = 10 - defaultUserActivityLimit = 50 - defaultUserPlaylistLimit = 50 + defaultUserScoreLimit = 50 + defaultClanScoreLimit = 50 + defaultMostPlayedLimit = 10 + defaultUserActivityLimit = 50 + defaultUserPlaylistLimit = 50 + defaultPlaylistMapsetLimit = 25 ) // getQueryLimit returns a requested limit bounded by the endpoint's default limit. @@ -29,3 +30,14 @@ func getQueryLimit(c *gin.Context, defaultLimit int) int { return limit } + +// getQueryPage returns a requested zero-based page. Invalid and negative values use page zero. +func getQueryPage(c *gin.Context) int { + page, err := strconv.Atoi(c.Query("page")) + + if err != nil || page < 0 { + return 0 + } + + return page +} diff --git a/handlers/playlists.go b/handlers/playlists.go index 8917f55..182ade4 100644 --- a/handlers/playlists.go +++ b/handlers/playlists.go @@ -64,7 +64,10 @@ func GetPlaylist(c *gin.Context) *APIError { return APIErrorBadRequest("Invalid id") } - playlist, err := db.GetPlaylistFull(id) + page := getQueryPage(c) + limit := getQueryLimit(c, defaultPlaylistMapsetLimit) + + playlist, err := db.GetPlaylistPage(id, page, limit) if err != nil && err != gorm.ErrRecordNotFound { return APIErrorServerError("Error retrieving playlist from db", err) @@ -74,7 +77,13 @@ func GetPlaylist(c *gin.Context) *APIError { return APIErrorNotFound("Playlist") } - c.JSON(http.StatusOK, gin.H{"playlist": playlist}) + mapsets := playlist.Mapsets + + if mapsets == nil { + mapsets = make([]*db.PlaylistMapset, 0) + } + + c.JSON(http.StatusOK, gin.H{"playlist": playlistPageResponse{Playlist: playlist, Mapsets: mapsets}}) return nil } @@ -263,6 +272,11 @@ type addRemoveMapPlaylistData struct { Map *db.MapQua } +type playlistPageResponse struct { + *db.Playlist + Mapsets []*db.PlaylistMapset `json:"mapsets"` +} + // Parses any ids, performs validation and returns data to be used when adding/removing maps from playlists func validateAddRemoveMapFromPlaylist(c *gin.Context) (*addRemoveMapPlaylistData, *APIError) { playlistId, err := strconv.Atoi(c.Param("id")) @@ -283,7 +297,7 @@ func validateAddRemoveMapFromPlaylist(c *gin.Context) (*addRemoveMapPlaylistData return nil, APIErrorUnauthorized("User not authenticated") } - playlist, err := db.GetPlaylistFull(playlistId) + playlist, err := db.GetPlaylist(playlistId) if err != nil && err != gorm.ErrRecordNotFound { return nil, APIErrorServerError("Error retrieving playlist from database", err) @@ -327,19 +341,24 @@ func AddMapToPlaylist(c *gin.Context) *APIError { return apiErr } - var existingMapset *db.PlaylistMapset + containsMap, err := db.DoesPlaylistContainMap(data.Playlist.Id, data.Map.Id) - for _, mapset := range data.Playlist.Mapsets { - for _, playlistMap := range mapset.Maps { - if playlistMap.MapId == data.Map.Id { - return APIErrorBadRequest("This map already exists in the playlist.") - } - } + if err != nil { + return APIErrorServerError("Error checking if map exists in playlist", err) + } - // Set existing mapset - if mapset.MapsetId == data.Map.MapsetId { - existingMapset = mapset - } + if containsMap { + return APIErrorBadRequest("This map already exists in the playlist.") + } + + existingMapset, err := db.GetPlaylistMapsetByIds(data.Playlist.Id, data.Map.MapsetId) + + if err != nil && err != gorm.ErrRecordNotFound { + return APIErrorServerError("Error retrieving playlist mapset from database", err) + } + + if err == gorm.ErrRecordNotFound { + existingMapset = nil } // Create new playlist mapset @@ -381,30 +400,37 @@ func RemoveMapFromPlaylist(c *gin.Context) *APIError { return apiErr } - var deleteMap bool - var deleteMapset bool + containsMap, err := db.DoesPlaylistContainMap(data.Playlist.Id, data.Map.Id) + + if err != nil { + return APIErrorServerError("Error checking if map exists in playlist", err) + } + + if !containsMap { + return APIErrorBadRequest("This map is not in your playlist.") + } - for _, playlistMapset := range data.Playlist.Mapsets { - for _, playlistMap := range playlistMapset.Maps { - if playlistMap.MapId == data.Map.Id { - deleteMap = true - } + playlistMapset, err := db.GetPlaylistMapsetByIds(data.Playlist.Id, data.Map.MapsetId) - if len(playlistMapset.Maps) == 1 { - deleteMapset = true - } + if err != nil { + if err == gorm.ErrRecordNotFound { + return APIErrorBadRequest("This map is not in your playlist.") } + + return APIErrorServerError("Error retrieving playlist mapset from database", err) } - if !deleteMap { - return APIErrorBadRequest("This map is not in your playlist.") + mapCount, err := db.CountPlaylistMapsForMapset(data.Playlist.Id, playlistMapset.Id) + + if err != nil { + return APIErrorServerError("Error counting maps in playlist mapset", err) } if err := db.DeletePlaylistMap(data.Playlist.Id, data.Map.Id); err != nil { return APIErrorServerError("Error removing playlist map from db", err) } - if deleteMapset { + if mapCount == 1 { if err := db.DeletePlaylistMapset(data.Playlist.Id, data.Map.MapsetId); err != nil { return APIErrorServerError("Error removing playlist mapset from db", err) } From ac27fae73cb0f0a11fea31503d1b9f1deb76107c Mon Sep 17 00:00:00 2001 From: AiAe Date: Wed, 9 Sep 2026 13:46:38 +0300 Subject: [PATCH 2/3] Match user search to how v1 returned results --- db/users.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/users.go b/db/users.go index aa19b86..8d59d42 100644 --- a/db/users.go +++ b/db/users.go @@ -373,7 +373,7 @@ func SearchUsersByName(searchQuery string) ([]*User, error) { Joins("StatsKeys7"). Where("username LIKE ? AND allowed = 1", fmt.Sprintf("%v%%", searchQuery)). Limit(50). - Order("id ASC"). + Order("username ASC, id ASC"). Find(&users) if result.Error != nil { From cfa56c05f965b1da534a793ed8a56d6310728369 Mon Sep 17 00:00:00 2001 From: AiAe Date: Wed, 9 Sep 2026 15:16:52 +0300 Subject: [PATCH 3/3] Community Manager group --- enums/usergroups.go | 1 + 1 file changed, 1 insertion(+) diff --git a/enums/usergroups.go b/enums/usergroups.go index 07f4b2b..12d9a25 100644 --- a/enums/usergroups.go +++ b/enums/usergroups.go @@ -15,6 +15,7 @@ const ( UserGroupTrialRankingSupervisor UserGroupGraphicsDesigner UserGroupHeadRankingSupervisor + CommunityManager ) // HasUserGroup Returns if a combination of user groups contains a single group