From 913b824b3e2239201fc72ae179ed5b207e52bcc0 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:15:41 +0100 Subject: [PATCH 01/40] add season user total score calculation workflow --- .env.example | 2 + .../Commands/UserSeasonScoresRecalculate.php | 59 ++++++++++++++ app/Models/Multiplayer/Room.php | 14 ++++ app/Models/Season.php | 27 +++++++ app/Models/SeasonScoreFactor.php | 16 ++++ app/Models/User.php | 5 ++ app/Models/UserSeasonScore.php | 78 +++++++++++++++++++ config/osu.php | 8 +- ...190453_create_user_season_scores_table.php | 34 ++++++++ ...24_10_22_194840_add_parent_id_to_rooms.php | 31 ++++++++ ...3228_create_season_score_factors_table.php | 31 ++++++++ resources/lang/en/rankings.php | 3 + 12 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 app/Console/Commands/UserSeasonScoresRecalculate.php create mode 100644 app/Models/SeasonScoreFactor.php create mode 100644 app/Models/UserSeasonScore.php create mode 100644 database/migrations/2024_10_22_190453_create_user_season_scores_table.php create mode 100644 database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php create mode 100644 database/migrations/2024_10_23_173228_create_season_score_factors_table.php diff --git a/.env.example b/.env.example index 4875ae3057e..178701fa43b 100644 --- a/.env.example +++ b/.env.example @@ -292,6 +292,8 @@ CLIENT_CHECK_VERSION=false # SCORES_SUBMISSION_ENABLED=1 # SCORE_INDEX_MAX_ID_DISTANCE=10_000_000 +# SEASONS_FACTORS_CACHE_DURATION=60 + # BANCHO_BOT_USER_ID= # OCTANE_LOCAL_CACHE_EXPIRE_SECOND=60 diff --git a/app/Console/Commands/UserSeasonScoresRecalculate.php b/app/Console/Commands/UserSeasonScoresRecalculate.php new file mode 100644 index 00000000000..4aebfcdb783 --- /dev/null +++ b/app/Console/Commands/UserSeasonScoresRecalculate.php @@ -0,0 +1,59 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +namespace App\Console\Commands; + +use App\Models\Multiplayer\UserScoreAggregate; +use App\Models\Season; +use App\Models\User; +use Illuminate\Console\Command; + +class UserSeasonScoresRecalculate extends Command +{ + protected $signature = 'user-season-scores:recalculate {--season-id=}'; + protected $description = 'Recalculate user scores for all active seasons or a specified season.'; + + public function handle() + { + $seasonId = $this->option('season-id'); + + if (present($seasonId)) { + $this->recalculate(Season::findOrFail(get_int($seasonId))); + } else { + $activeSeasons = Season::active()->get(); + + foreach ($activeSeasons as $season) { + $this->recalculate($season); + } + } + } + + protected function recalculate(Season $season) { + $scoreUserIds = UserScoreAggregate::whereIn('room_id', $season->rooms->pluck('id')) + ->select('user_id') + ->get() + ->pluck('user_id') + ->unique(); + + $bar = $this->output->createProgressBar($scoreUserIds->count()); + + User::whereIn('user_id', $scoreUserIds) + ->chunkById(100, function ($userChunk) use ($bar, $season) { + foreach ($userChunk as $user) { + $seasonScore = $user->seasonScores() + ->where('season_id', $season->getKey()) + ->firstOrNew(); + + $seasonScore->season_id = $season->getKey(); + $seasonScore->calculate(); + $seasonScore->save(); + + $bar->advance(); + } + }); + + $bar->finish(); + } +} diff --git a/app/Models/Multiplayer/Room.php b/app/Models/Multiplayer/Room.php index c50ed5d63e3..d85b3e9b596 100644 --- a/app/Models/Multiplayer/Room.php +++ b/app/Models/Multiplayer/Room.php @@ -37,6 +37,7 @@ * @property int $id * @property int|null $max_attempts * @property string $name + * @property int|null $parent_id * @property int $participant_count * @property \Illuminate\Database\Eloquent\Collection $playlist PlaylistItem * @property \Illuminate\Database\Eloquent\Collection $scoreLinks ScoreLink @@ -446,6 +447,19 @@ public function completePlay(ScoreToken $scoreToken, array $params): ScoreLink $stats->save(); } + // spotlight playlists should always be linked to one season exactly + if ($this->category === 'spotlight' && $this->seasons()->count() === 1 && $agg->total_score > 0) { + $seasonId = $this->seasons()->first()->getKey(); + + $seasonScore = $user->seasonScores() + ->where('season_id', $seasonId) + ->firstOrNew(); + + $seasonScore->season_id = $seasonId; + $seasonScore->calculate(); + $seasonScore->save(); + } + return $scoreLink; }); } diff --git a/app/Models/Season.php b/app/Models/Season.php index f805a4f3e38..52e04f368ac 100644 --- a/app/Models/Season.php +++ b/app/Models/Season.php @@ -10,6 +10,7 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; /** * @property bool $finalised @@ -23,6 +24,11 @@ class Season extends Model 'finalised' => 'boolean', ]; + public function scopeActive($query) + { + return $query->where('finalised', false); + } + public static function latestOrId($id) { if ($id === 'latest') { @@ -45,6 +51,27 @@ public function endDate(): ?Carbon : null; } + public function scoreFactors(): HasMany + { + return $this->hasMany(SeasonScoreFactor::class); + } + + public function scoreFactorsOrderedForCalculation(): array + { + return cache_remember_mutexed( + "score_factors:{$this->id}", + $GLOBALS['cfg']['osu']['seasons']['factors_cache_duration'], + [], + function () { + return $this->scoreFactors() + ->orderByDesc('factor') + ->get() + ->pluck('factor') + ->toArray(); + } + ); + } + public function startDate(): ?Carbon { return $this->rooms->min('starts_at'); diff --git a/app/Models/SeasonScoreFactor.php b/app/Models/SeasonScoreFactor.php new file mode 100644 index 00000000000..0e658b5aed3 --- /dev/null +++ b/app/Models/SeasonScoreFactor.php @@ -0,0 +1,16 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +namespace App\Models; + +/** + * @property int $id + * @property float $factor + * @property int $season_id + */ +class SeasonScoreFactor extends Model +{ + public $timestamps = false; +} diff --git a/app/Models/User.php b/app/Models/User.php index dcee75bbedc..d7dbe0f1aae 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -1359,6 +1359,11 @@ public function country() return $this->belongsTo(Country::class, 'country_acronym'); } + public function seasonScores() + { + return $this->hasMany(UserSeasonScore::class); + } + public function statisticsOsu() { return $this->hasOne(UserStatistics\Osu::class); diff --git a/app/Models/UserSeasonScore.php b/app/Models/UserSeasonScore.php new file mode 100644 index 00000000000..865a1b13097 --- /dev/null +++ b/app/Models/UserSeasonScore.php @@ -0,0 +1,78 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +namespace App\Models; + +use App\Exceptions\InvariantException; +use App\Models\Multiplayer\UserScoreAggregate; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Support\Facades\DB; + +/** + * @property int $season_id + * @property int $total_score + * @property int $user_id + */ +class UserSeasonScore extends Model +{ + public $timestamps = false; + public $incrementing = false; + + protected $primaryKey = ':composite'; + protected $primaryKeys = ['user_id', 'season_id']; + + + public function calculate(): void + { + $userScores = UserScoreAggregate::whereIn('room_id', $this->season->rooms->pluck('id')) + ->where('user_id', $this->user_id) + ->get(); + + $scores = []; + + foreach ($this->season->rooms->where('parent_id', null) as $room) { + $totalScore = $userScores->where('room_id', $room->getKey()) + ->first() + ?->total_score; + + $childRoomId = $this->season->rooms + ->where('parent_id', $room->getKey()) + ->first() + ?->getKey(); + + $totalScoreChild = $userScores->where('room_id', $childRoomId) + ->first() + ?->total_score; + + if ($totalScore == null && $totalScoreChild == null) { + continue; + } + + $scores[] = max([$totalScore, $totalScoreChild]); + } + + rsort($scores); + + $factors = $this->season->scoreFactorsOrderedForCalculation(); + $scoreCount = count($scores); + + if ($scoreCount > count($factors)) { + throw new InvariantException(osu_trans('rankings.seasons.validation.not_enough_factors')); + } + + $total = 0; + + for ($i = 0; $i < $scoreCount; $i++) { + $total += $scores[$i] * $factors[$i]; + } + + $this->total_score = $total; + } + + public function season(): BelongsTo + { + return $this->belongsTo(Season::class); + } +} diff --git a/config/osu.php b/config/osu.php index de037d65fbd..7e24153124a 100644 --- a/config/osu.php +++ b/config/osu.php @@ -18,7 +18,6 @@ 'achievement' => [ 'icon_prefix' => env('USER_ACHIEVEMENT_ICON_PREFIX', 'https://assets.ppy.sh/user-achievements/'), ], - 'api' => [ // changing the throttle rate doesn't reset any existing timers, // changing the prefix key is the only way to invalidate them. @@ -27,7 +26,6 @@ 'scores_download' => env('API_THROTTLE_SCORES_DOWNLOAD', '10,1,api-scores-download'), ], ], - 'avatar' => [ 'cache_purge_prefix' => env('AVATAR_CACHE_PURGE_PREFIX'), 'cache_purge_method' => env('AVATAR_CACHE_PURGE_METHOD'), @@ -35,7 +33,6 @@ 'default' => env('DEFAULT_AVATAR', env('APP_URL', 'http://localhost').'/images/layout/avatar-guest@2x.png'), 'storage' => env('AVATAR_STORAGE', 'local-avatar'), ], - 'bbcode' => [ // this should be random or a config variable. // ...who am I kidding, this shouldn't even exist at all. @@ -193,12 +190,13 @@ 'processing_queue' => presence(env('SCORES_PROCESSING_QUEUE')) ?? 'osu-queue:score-statistics', 'submission_enabled' => get_bool(env('SCORES_SUBMISSION_ENABLED')) ?? true, ], - 'seasonal' => [ 'contest_id' => get_int(env('SEASONAL_CONTEST_ID')), 'ends_at' => env('SEASONAL_ENDS_AT'), ], - + 'seasons' => [ + 'factors_cache_duration' => 60 * (get_float(env('SEASONS_FACTORS_CACHE_DURATION')) ?? 60), // in minutes, converted to seconds + ], 'store' => [ 'notice' => presence(str_replace('\n', "\n", env('STORE_NOTICE') ?? '')), ], diff --git a/database/migrations/2024_10_22_190453_create_user_season_scores_table.php b/database/migrations/2024_10_22_190453_create_user_season_scores_table.php new file mode 100644 index 00000000000..a483dfdee3e --- /dev/null +++ b/database/migrations/2024_10_22_190453_create_user_season_scores_table.php @@ -0,0 +1,34 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + */ + public function up(): void + { + Schema::create('user_season_scores', function (Blueprint $table) { + $table->bigInteger('user_id')->unsigned(); + $table->integer('season_id')->unsigned(); + $table->integer('total_score'); + + $table->primary(['user_id', 'season_id']); + $table->index('total_score'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_season_scores'); + } +}; diff --git a/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php b/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php new file mode 100644 index 00000000000..80055d0c4a8 --- /dev/null +++ b/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php @@ -0,0 +1,31 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + */ + public function up(): void + { + Schema::table('multiplayer_rooms', function (Blueprint $table) { + $table->bigInteger('parent_id')->nullable()->unique(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('multiplayer_rooms', function (Blueprint $table) { + $table->dropColumn('parent_id'); + }); + } +}; diff --git a/database/migrations/2024_10_23_173228_create_season_score_factors_table.php b/database/migrations/2024_10_23_173228_create_season_score_factors_table.php new file mode 100644 index 00000000000..699ab950b29 --- /dev/null +++ b/database/migrations/2024_10_23_173228_create_season_score_factors_table.php @@ -0,0 +1,31 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + */ + public function up(): void + { + Schema::create('season_score_factors', function (Blueprint $table) { + $table->id(); + $table->integer('season_id'); + $table->float('factor'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('season_score_factors'); + } +}; diff --git a/resources/lang/en/rankings.php b/resources/lang/en/rankings.php index e1dc187c861..cdafa9bfd26 100644 --- a/resources/lang/en/rankings.php +++ b/resources/lang/en/rankings.php @@ -49,6 +49,9 @@ 'ongoing' => 'This season is still ongoing (there will be more playlists added).', 'room_count' => 'Playlist count', 'url' => 'Display more informations on that season.', + 'validation' => [ + 'not_enough_factors' => 'there is not enough score factors for proper calculation', + ], ], 'spotlight' => [ From a7041044f81dab074ea1b1d309ccd8d2d364e5d3 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:16:53 +0100 Subject: [PATCH 02/40] return type on user relation --- app/Models/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/User.php b/app/Models/User.php index d7dbe0f1aae..fe8e03b33e9 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -1359,7 +1359,7 @@ public function country() return $this->belongsTo(Country::class, 'country_acronym'); } - public function seasonScores() + public function seasonScores(): HasMany { return $this->hasMany(UserSeasonScore::class); } From 398ad6dab7534370a2ff1517e828556d3ad1c654 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:20:48 +0100 Subject: [PATCH 03/40] check total score before querying season count --- app/Models/Multiplayer/Room.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Multiplayer/Room.php b/app/Models/Multiplayer/Room.php index d85b3e9b596..1009f6326ea 100644 --- a/app/Models/Multiplayer/Room.php +++ b/app/Models/Multiplayer/Room.php @@ -448,7 +448,7 @@ public function completePlay(ScoreToken $scoreToken, array $params): ScoreLink } // spotlight playlists should always be linked to one season exactly - if ($this->category === 'spotlight' && $this->seasons()->count() === 1 && $agg->total_score > 0) { + if ($this->category === 'spotlight' && $agg->total_score > 0 && $this->seasons()->count() === 1) { $seasonId = $this->seasons()->first()->getKey(); $seasonScore = $user->seasonScores() From 1cc8f4267c7c0e7c71e35d1e6212be3f4c800f37 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:23:38 +0100 Subject: [PATCH 04/40] remove unused import --- app/Models/UserSeasonScore.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/Models/UserSeasonScore.php b/app/Models/UserSeasonScore.php index 865a1b13097..1624f6353fc 100644 --- a/app/Models/UserSeasonScore.php +++ b/app/Models/UserSeasonScore.php @@ -8,7 +8,6 @@ use App\Exceptions\InvariantException; use App\Models\Multiplayer\UserScoreAggregate; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Support\Facades\DB; /** * @property int $season_id @@ -23,7 +22,6 @@ class UserSeasonScore extends Model protected $primaryKey = ':composite'; protected $primaryKeys = ['user_id', 'season_id']; - public function calculate(): void { $userScores = UserScoreAggregate::whereIn('room_id', $this->season->rooms->pluck('id')) From db1de9c6ee7c87beb1ec7311c6b76655e52b1586 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:28:19 +0100 Subject: [PATCH 05/40] use triple equal sign --- app/Models/UserSeasonScore.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/UserSeasonScore.php b/app/Models/UserSeasonScore.php index 1624f6353fc..f0a7b4629e1 100644 --- a/app/Models/UserSeasonScore.php +++ b/app/Models/UserSeasonScore.php @@ -44,7 +44,7 @@ public function calculate(): void ->first() ?->total_score; - if ($totalScore == null && $totalScoreChild == null) { + if ($totalScore === null && $totalScoreChild == null) { continue; } From 77a94df03fb9d110c4bcd2e0303ac148c08c253c Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:30:35 +0100 Subject: [PATCH 06/40] one more equal --- app/Models/UserSeasonScore.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/UserSeasonScore.php b/app/Models/UserSeasonScore.php index f0a7b4629e1..74f165e1218 100644 --- a/app/Models/UserSeasonScore.php +++ b/app/Models/UserSeasonScore.php @@ -44,7 +44,7 @@ public function calculate(): void ->first() ?->total_score; - if ($totalScore === null && $totalScoreChild == null) { + if ($totalScore === null && $totalScoreChild === null) { continue; } From cf3bc2461ba834a92fadf4b5e8eb7b5b8d20b297 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:39:01 +0100 Subject: [PATCH 07/40] newline on function opening brace --- app/Console/Commands/UserSeasonScoresRecalculate.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/UserSeasonScoresRecalculate.php b/app/Console/Commands/UserSeasonScoresRecalculate.php index 4aebfcdb783..6ac5d61e9be 100644 --- a/app/Console/Commands/UserSeasonScoresRecalculate.php +++ b/app/Console/Commands/UserSeasonScoresRecalculate.php @@ -30,7 +30,8 @@ public function handle() } } - protected function recalculate(Season $season) { + protected function recalculate(Season $season) + { $scoreUserIds = UserScoreAggregate::whereIn('room_id', $season->rooms->pluck('id')) ->select('user_id') ->get() From dced08a1ae081bab5efc69ff1c67ced92ce1cefc Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Fri, 27 Dec 2024 19:39:03 +0100 Subject: [PATCH 08/40] use float for total score instead --- app/Models/UserSeasonScore.php | 2 +- .../2024_10_22_190453_create_user_season_scores_table.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/UserSeasonScore.php b/app/Models/UserSeasonScore.php index 74f165e1218..382a5862773 100644 --- a/app/Models/UserSeasonScore.php +++ b/app/Models/UserSeasonScore.php @@ -11,7 +11,7 @@ /** * @property int $season_id - * @property int $total_score + * @property float $total_score * @property int $user_id */ class UserSeasonScore extends Model diff --git a/database/migrations/2024_10_22_190453_create_user_season_scores_table.php b/database/migrations/2024_10_22_190453_create_user_season_scores_table.php index a483dfdee3e..9404fe4a539 100644 --- a/database/migrations/2024_10_22_190453_create_user_season_scores_table.php +++ b/database/migrations/2024_10_22_190453_create_user_season_scores_table.php @@ -17,7 +17,7 @@ public function up(): void Schema::create('user_season_scores', function (Blueprint $table) { $table->bigInteger('user_id')->unsigned(); $table->integer('season_id')->unsigned(); - $table->integer('total_score'); + $table->float('total_score'); $table->primary(['user_id', 'season_id']); $table->index('total_score'); From d723a8520041b1a9a6d028824261a5b7623700bd Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Fri, 27 Dec 2024 19:57:41 +0100 Subject: [PATCH 09/40] check factor count before calculation --- app/Models/UserSeasonScore.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/Models/UserSeasonScore.php b/app/Models/UserSeasonScore.php index 382a5862773..8db18613e40 100644 --- a/app/Models/UserSeasonScore.php +++ b/app/Models/UserSeasonScore.php @@ -28,9 +28,16 @@ public function calculate(): void ->where('user_id', $this->user_id) ->get(); + $factors = $this->season->scoreFactorsOrderedForCalculation(); + $parentRooms = $this->season->rooms->where('parent_id', null); + + if ($parentRooms->count() > count($factors)) { + throw new InvariantException(osu_trans('rankings.seasons.validation.not_enough_factors')); + } + $scores = []; - foreach ($this->season->rooms->where('parent_id', null) as $room) { + foreach ($parentRooms as $room) { $totalScore = $userScores->where('room_id', $room->getKey()) ->first() ?->total_score; @@ -53,13 +60,7 @@ public function calculate(): void rsort($scores); - $factors = $this->season->scoreFactorsOrderedForCalculation(); $scoreCount = count($scores); - - if ($scoreCount > count($factors)) { - throw new InvariantException(osu_trans('rankings.seasons.validation.not_enough_factors')); - } - $total = 0; for ($i = 0; $i < $scoreCount; $i++) { From 08b4b3415d746c528b639fa17b5d5ae6359aa089 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Fri, 27 Dec 2024 22:14:06 +0100 Subject: [PATCH 10/40] add tests --- app/Models/SeasonRoom.php | 3 + app/Models/SeasonScoreFactor.php | 4 + database/factories/SeasonRoomFactory.php | 21 ++ .../factories/SeasonScoreFactorFactory.php | 22 ++ tests/Models/UserSeasonScoreTest.php | 202 ++++++++++++++++++ 5 files changed, 252 insertions(+) create mode 100644 database/factories/SeasonRoomFactory.php create mode 100644 database/factories/SeasonScoreFactorFactory.php create mode 100644 tests/Models/UserSeasonScoreTest.php diff --git a/app/Models/SeasonRoom.php b/app/Models/SeasonRoom.php index 86397032a1d..a5bbfc281c3 100644 --- a/app/Models/SeasonRoom.php +++ b/app/Models/SeasonRoom.php @@ -7,6 +7,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; /** @@ -16,5 +17,7 @@ */ class SeasonRoom extends Model { + use HasFactory; + public $timestamps = false; } diff --git a/app/Models/SeasonScoreFactor.php b/app/Models/SeasonScoreFactor.php index 0e658b5aed3..aff70c8fa59 100644 --- a/app/Models/SeasonScoreFactor.php +++ b/app/Models/SeasonScoreFactor.php @@ -5,6 +5,8 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; + /** * @property int $id * @property float $factor @@ -12,5 +14,7 @@ */ class SeasonScoreFactor extends Model { + use HasFactory; + public $timestamps = false; } diff --git a/database/factories/SeasonRoomFactory.php b/database/factories/SeasonRoomFactory.php new file mode 100644 index 00000000000..3492702f4f3 --- /dev/null +++ b/database/factories/SeasonRoomFactory.php @@ -0,0 +1,21 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +declare(strict_types=1); + +namespace Database\Factories; + +use App\Models\SeasonRoom; + +class SeasonRoomFactory extends Factory +{ + protected $model = SeasonRoom::class; + + public function definition(): array + { + // pivot table... + return []; + } +} diff --git a/database/factories/SeasonScoreFactorFactory.php b/database/factories/SeasonScoreFactorFactory.php new file mode 100644 index 00000000000..1df687bdf53 --- /dev/null +++ b/database/factories/SeasonScoreFactorFactory.php @@ -0,0 +1,22 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +declare(strict_types=1); + +namespace Database\Factories; + +use App\Models\SeasonScoreFactor; + +class SeasonScoreFactorFactory extends Factory +{ + protected $model = SeasonScoreFactor::class; + + public function definition(): array + { + return [ + 'factor' => 1, + ]; + } +} diff --git a/tests/Models/UserSeasonScoreTest.php b/tests/Models/UserSeasonScoreTest.php new file mode 100644 index 00000000000..11e5f9d9eab --- /dev/null +++ b/tests/Models/UserSeasonScoreTest.php @@ -0,0 +1,202 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +declare(strict_types=1); + +namespace Tests\Models; + +use App\Models\Multiplayer\PlaylistItem; +use App\Models\Multiplayer\Room; +use App\Models\Season; +use App\Models\SeasonRoom; +use App\Models\SeasonScoreFactor; +use App\Models\User; +use App\Models\UserSeasonScore; +use Tests\TestCase; + +class UserSeasonScoreTest extends TestCase +{ + private Season $season; + private User $user; + + public function testAddMultipleScores(): void + { + foreach ([1, 0.75, 0.5] as $factor) { + SeasonScoreFactor::factory()->create([ + 'factor' => $factor, + 'season_id' => $this->season, + ]); + } + + $this->createRoomWithPlay(10); + + $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + ->where('season_id', $this->season->getKey()) + ->first(); + + $this->assertSame($userScore->total_score, (float) 10); // 10*1 + + $this->createRoomWithPlay(15); + + $userScore->refresh(); + $this->assertSame($userScore->total_score, 22.5); // 15*1 + 10*0.75 + + $this->createRoomWithPlay(25); + + $userScore->refresh(); + $this->assertSame($userScore->total_score, 41.25); // 25*1 + 15*0.75 + 10*0.5 + } + + public function testAddMultipleScoresWithChildrenRooms(): void + { + foreach ([1, 0.75, 0.5] as $factor) { + SeasonScoreFactor::factory()->create([ + 'factor' => $factor, + 'season_id' => $this->season, + ]); + } + + $firstRoom = $this->createRoomWithPlay(10); + + $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + ->where('season_id', $this->season->getKey()) + ->first(); + + $this->assertSame($userScore->total_score, (float) 10); // 10*1 + + $this->createRoomWithPlay(15, $firstRoom->getKey()); + + $userScore->refresh(); + $this->assertSame($userScore->total_score, (float) 15); // 15*1 + + $secondRoom = $this->createRoomWithPlay(20); + + $userScore->refresh(); + $this->assertSame($userScore->total_score, 31.25); // 20*1 + 15*0.75 + + $this->createRoomWithPlay(20, $secondRoom->getKey()); + + $userScore->refresh(); + $this->assertSame($userScore->total_score, 31.25); // 20*1 + 15*0.75 + + $thirdRoom = $this->createRoomWithPlay(10); + + $userScore->refresh(); + $this->assertSame($userScore->total_score, 36.25); // 20*1 + 15*0.75 + 10*0.5 + + $this->createRoomWithPlay(30, $thirdRoom->getKey()); + + $userScore->refresh(); + $this->assertSame($userScore->total_score, 52.5); // 30*1 + 20*0.75 + 15*0.5 + } + + public function testAddHigherScoreInChildRoom(): void + { + SeasonScoreFactor::factory()->create(['season_id' => $this->season]); + + $room = $this->createRoomWithPlay(10); + + $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + ->where('season_id', $this->season->getKey()) + ->first(); + + $this->assertSame($userScore->total_score, (float) 10); + + $this->createRoomWithPlay(15, $room->getKey()); + + $userScore->refresh(); + $this->assertSame($userScore->total_score, (float) 15); + } + + public function testAddHigherScoreInParentRoom(): void + { + SeasonScoreFactor::factory()->create(['season_id' => $this->season]); + + $room = $this->createRoomWithPlay(15); + + $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + ->where('season_id', $this->season->getKey()) + ->first(); + + $this->assertSame($userScore->total_score, (float) 15); + + $this->createRoomWithPlay(10, $room->getKey()); + + $userScore->refresh(); + $this->assertSame($userScore->total_score, (float) 15); + } + + public function testAddSameScoreInChildAndParentRoom(): void + { + SeasonScoreFactor::factory()->create(['season_id' => $this->season]); + + $room = $this->createRoomWithPlay(10); + + $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + ->where('season_id', $this->season->getKey()) + ->first(); + + $this->assertSame($userScore->total_score, (float) 10); + + $this->createRoomWithPlay(10, $room->getKey()); + + $userScore->refresh(); + $this->assertSame($userScore->total_score, (float) 10); + } + + public function testAddScoreInChildRoomOnly(): void + { + SeasonScoreFactor::factory()->create(['season_id' => $this->season]); + + $room = $this->createRoom(); + $this->createRoomWithPlay(10, $room->getKey()); + + $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + ->where('season_id', $this->season->getKey()) + ->first(); + + $this->assertSame($userScore->total_score, (float) 10); + } + + protected function setUp(): void + { + parent::setUp(); + + $this->season = Season::factory()->create(); + $this->user = User::factory()->create(); + } + + private function createRoom(?int $parentId = null): Room + { + $room = Room::factory()->create([ + 'category' => 'spotlight', + 'parent_id' => $parentId, + ]); + + SeasonRoom::factory()->create([ + 'room_id' => $room, + 'season_id' => $this->season, + ]); + + return $room; + } + + private function createRoomWithPlay(float $totalScore, ?int $parentId = null): Room + { + $room = $this->createRoom($parentId); + + $playlistItem = PlaylistItem::factory()->create([ + 'owner_id' => $room->host, + 'room_id' => $room, + ]); + + $this->roomAddPlay($this->user, $playlistItem, [ + 'passed' => true, + 'total_score' => $totalScore, + ]); + + return $room; + } +} From ea6bd9b66b12256ac64521ac7aed3d8efb4bf10b Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Fri, 27 Dec 2024 22:18:35 +0100 Subject: [PATCH 11/40] strict types --- app/Console/Commands/UserSeasonScoresRecalculate.php | 6 ++++-- app/Models/SeasonScoreFactor.php | 2 ++ app/Models/UserSeasonScore.php | 2 ++ .../2024_10_22_190453_create_user_season_scores_table.php | 2 ++ .../migrations/2024_10_22_194840_add_parent_id_to_rooms.php | 2 ++ .../2024_10_23_173228_create_season_score_factors_table.php | 2 ++ 6 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/UserSeasonScoresRecalculate.php b/app/Console/Commands/UserSeasonScoresRecalculate.php index 6ac5d61e9be..7ed541c50b1 100644 --- a/app/Console/Commands/UserSeasonScoresRecalculate.php +++ b/app/Console/Commands/UserSeasonScoresRecalculate.php @@ -3,6 +3,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. +declare(strict_types=1); + namespace App\Console\Commands; use App\Models\Multiplayer\UserScoreAggregate; @@ -15,7 +17,7 @@ class UserSeasonScoresRecalculate extends Command protected $signature = 'user-season-scores:recalculate {--season-id=}'; protected $description = 'Recalculate user scores for all active seasons or a specified season.'; - public function handle() + public function handle(): void { $seasonId = $this->option('season-id'); @@ -30,7 +32,7 @@ public function handle() } } - protected function recalculate(Season $season) + protected function recalculate(Season $season): void { $scoreUserIds = UserScoreAggregate::whereIn('room_id', $season->rooms->pluck('id')) ->select('user_id') diff --git a/app/Models/SeasonScoreFactor.php b/app/Models/SeasonScoreFactor.php index aff70c8fa59..bd270094fa8 100644 --- a/app/Models/SeasonScoreFactor.php +++ b/app/Models/SeasonScoreFactor.php @@ -3,6 +3,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. +declare(strict_types=1); + namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; diff --git a/app/Models/UserSeasonScore.php b/app/Models/UserSeasonScore.php index 8db18613e40..6ae74840caf 100644 --- a/app/Models/UserSeasonScore.php +++ b/app/Models/UserSeasonScore.php @@ -3,6 +3,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. +declare(strict_types=1); + namespace App\Models; use App\Exceptions\InvariantException; diff --git a/database/migrations/2024_10_22_190453_create_user_season_scores_table.php b/database/migrations/2024_10_22_190453_create_user_season_scores_table.php index 9404fe4a539..cd7045105c5 100644 --- a/database/migrations/2024_10_22_190453_create_user_season_scores_table.php +++ b/database/migrations/2024_10_22_190453_create_user_season_scores_table.php @@ -3,6 +3,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. +declare(strict_types=1); + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; diff --git a/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php b/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php index 80055d0c4a8..409b0f77007 100644 --- a/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php +++ b/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php @@ -3,6 +3,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. +declare(strict_types=1); + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; diff --git a/database/migrations/2024_10_23_173228_create_season_score_factors_table.php b/database/migrations/2024_10_23_173228_create_season_score_factors_table.php index 699ab950b29..9adca317b84 100644 --- a/database/migrations/2024_10_23_173228_create_season_score_factors_table.php +++ b/database/migrations/2024_10_23_173228_create_season_score_factors_table.php @@ -3,6 +3,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. +declare(strict_types=1); + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; From cb30c70e1e30df589711368ec08c59802c07fe74 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:19:59 +0100 Subject: [PATCH 12/40] make parent_id unsigned --- .../migrations/2024_10_22_194840_add_parent_id_to_rooms.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php b/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php index 409b0f77007..29bea618cf6 100644 --- a/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php +++ b/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php @@ -17,7 +17,10 @@ public function up(): void { Schema::table('multiplayer_rooms', function (Blueprint $table) { - $table->bigInteger('parent_id')->nullable()->unique(); + $table->bigInteger('parent_id') + ->unsigned() + ->nullable() + ->unique(); }); } From 61ef3dca92852b7aabf205588586e31ac472ddf9 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:21:10 +0100 Subject: [PATCH 13/40] alphabetize --- app/Models/UserSeasonScore.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/UserSeasonScore.php b/app/Models/UserSeasonScore.php index 6ae74840caf..51825ef866c 100644 --- a/app/Models/UserSeasonScore.php +++ b/app/Models/UserSeasonScore.php @@ -18,8 +18,8 @@ */ class UserSeasonScore extends Model { - public $timestamps = false; public $incrementing = false; + public $timestamps = false; protected $primaryKey = ':composite'; protected $primaryKeys = ['user_id', 'season_id']; From 4751d8e119d2861676815ce9da3911e8f6235705 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Sun, 12 Jan 2025 18:45:25 +0100 Subject: [PATCH 14/40] move score factors to seasons database column --- app/Models/Season.php | 24 +++++------------ app/Models/SeasonScoreFactor.php | 22 --------------- .../factories/SeasonScoreFactorFactory.php | 22 --------------- ...2_172229_add_score_factors_on_seasons.php} | 12 ++++----- tests/Models/UserSeasonScoreTest.php | 27 +++---------------- 5 files changed, 14 insertions(+), 93 deletions(-) delete mode 100644 app/Models/SeasonScoreFactor.php delete mode 100644 database/factories/SeasonScoreFactorFactory.php rename database/migrations/{2024_10_23_173228_create_season_score_factors_table.php => 2025_01_12_172229_add_score_factors_on_seasons.php} (67%) diff --git a/app/Models/Season.php b/app/Models/Season.php index 52e04f368ac..9981d7e3f35 100644 --- a/app/Models/Season.php +++ b/app/Models/Season.php @@ -10,18 +10,19 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\Relations\BelongsToMany; -use Illuminate\Database\Eloquent\Relations\HasMany; /** * @property bool $finalised * @property string $name * @property-read Collection $rooms + * @property float[]|null $score_factors * @property string|null $url */ class Season extends Model { protected $casts = [ 'finalised' => 'boolean', + 'score_factors' => 'array', ]; public function scopeActive($query) @@ -51,25 +52,12 @@ public function endDate(): ?Carbon : null; } - public function scoreFactors(): HasMany - { - return $this->hasMany(SeasonScoreFactor::class); - } - public function scoreFactorsOrderedForCalculation(): array { - return cache_remember_mutexed( - "score_factors:{$this->id}", - $GLOBALS['cfg']['osu']['seasons']['factors_cache_duration'], - [], - function () { - return $this->scoreFactors() - ->orderByDesc('factor') - ->get() - ->pluck('factor') - ->toArray(); - } - ); + $factors = $this->score_factors ?? []; + rsort($factors); + + return $factors; } public function startDate(): ?Carbon diff --git a/app/Models/SeasonScoreFactor.php b/app/Models/SeasonScoreFactor.php deleted file mode 100644 index bd270094fa8..00000000000 --- a/app/Models/SeasonScoreFactor.php +++ /dev/null @@ -1,22 +0,0 @@ -. Licensed under the GNU Affero General Public License v3.0. -// See the LICENCE file in the repository root for full licence text. - -declare(strict_types=1); - -namespace App\Models; - -use Illuminate\Database\Eloquent\Factories\HasFactory; - -/** - * @property int $id - * @property float $factor - * @property int $season_id - */ -class SeasonScoreFactor extends Model -{ - use HasFactory; - - public $timestamps = false; -} diff --git a/database/factories/SeasonScoreFactorFactory.php b/database/factories/SeasonScoreFactorFactory.php deleted file mode 100644 index 1df687bdf53..00000000000 --- a/database/factories/SeasonScoreFactorFactory.php +++ /dev/null @@ -1,22 +0,0 @@ -. Licensed under the GNU Affero General Public License v3.0. -// See the LICENCE file in the repository root for full licence text. - -declare(strict_types=1); - -namespace Database\Factories; - -use App\Models\SeasonScoreFactor; - -class SeasonScoreFactorFactory extends Factory -{ - protected $model = SeasonScoreFactor::class; - - public function definition(): array - { - return [ - 'factor' => 1, - ]; - } -} diff --git a/database/migrations/2024_10_23_173228_create_season_score_factors_table.php b/database/migrations/2025_01_12_172229_add_score_factors_on_seasons.php similarity index 67% rename from database/migrations/2024_10_23_173228_create_season_score_factors_table.php rename to database/migrations/2025_01_12_172229_add_score_factors_on_seasons.php index 9adca317b84..cf4a4bf6d91 100644 --- a/database/migrations/2024_10_23_173228_create_season_score_factors_table.php +++ b/database/migrations/2025_01_12_172229_add_score_factors_on_seasons.php @@ -3,8 +3,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. -declare(strict_types=1); - use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; @@ -16,10 +14,8 @@ */ public function up(): void { - Schema::create('season_score_factors', function (Blueprint $table) { - $table->id(); - $table->integer('season_id'); - $table->float('factor'); + Schema::table('seasons', function (Blueprint $table) { + $table->json('score_factors')->nullable(); }); } @@ -28,6 +24,8 @@ public function up(): void */ public function down(): void { - Schema::dropIfExists('season_score_factors'); + Schema::table('seasons', function (Blueprint $table) { + $table->dropColumn('score_factors'); + }); } }; diff --git a/tests/Models/UserSeasonScoreTest.php b/tests/Models/UserSeasonScoreTest.php index 11e5f9d9eab..4276550be2a 100644 --- a/tests/Models/UserSeasonScoreTest.php +++ b/tests/Models/UserSeasonScoreTest.php @@ -11,7 +11,6 @@ use App\Models\Multiplayer\Room; use App\Models\Season; use App\Models\SeasonRoom; -use App\Models\SeasonScoreFactor; use App\Models\User; use App\Models\UserSeasonScore; use Tests\TestCase; @@ -23,13 +22,6 @@ class UserSeasonScoreTest extends TestCase public function testAddMultipleScores(): void { - foreach ([1, 0.75, 0.5] as $factor) { - SeasonScoreFactor::factory()->create([ - 'factor' => $factor, - 'season_id' => $this->season, - ]); - } - $this->createRoomWithPlay(10); $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) @@ -51,13 +43,6 @@ public function testAddMultipleScores(): void public function testAddMultipleScoresWithChildrenRooms(): void { - foreach ([1, 0.75, 0.5] as $factor) { - SeasonScoreFactor::factory()->create([ - 'factor' => $factor, - 'season_id' => $this->season, - ]); - } - $firstRoom = $this->createRoomWithPlay(10); $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) @@ -94,8 +79,6 @@ public function testAddMultipleScoresWithChildrenRooms(): void public function testAddHigherScoreInChildRoom(): void { - SeasonScoreFactor::factory()->create(['season_id' => $this->season]); - $room = $this->createRoomWithPlay(10); $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) @@ -112,8 +95,6 @@ public function testAddHigherScoreInChildRoom(): void public function testAddHigherScoreInParentRoom(): void { - SeasonScoreFactor::factory()->create(['season_id' => $this->season]); - $room = $this->createRoomWithPlay(15); $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) @@ -130,8 +111,6 @@ public function testAddHigherScoreInParentRoom(): void public function testAddSameScoreInChildAndParentRoom(): void { - SeasonScoreFactor::factory()->create(['season_id' => $this->season]); - $room = $this->createRoomWithPlay(10); $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) @@ -148,8 +127,6 @@ public function testAddSameScoreInChildAndParentRoom(): void public function testAddScoreInChildRoomOnly(): void { - SeasonScoreFactor::factory()->create(['season_id' => $this->season]); - $room = $this->createRoom(); $this->createRoomWithPlay(10, $room->getKey()); @@ -164,7 +141,9 @@ protected function setUp(): void { parent::setUp(); - $this->season = Season::factory()->create(); + $this->season = Season::factory()->create([ + 'score_factors' => [1, 0.75, 0.5], + ]); $this->user = User::factory()->create(); } From bade39ad0914b5c5adb67331cc2494762b004b33 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Sun, 12 Jan 2025 22:45:39 +0100 Subject: [PATCH 15/40] group rooms in SeasonRoom instead of parent_id --- app/Models/SeasonRoom.php | 1 + app/Models/UserSeasonScore.php | 33 +++++------- ...0_add_group_indicator_on_season_rooms.php} | 13 ++--- tests/Models/UserSeasonScoreTest.php | 54 +++++++++++-------- 4 files changed, 51 insertions(+), 50 deletions(-) rename database/migrations/{2024_10_22_194840_add_parent_id_to_rooms.php => 2025_01_12_174650_add_group_indicator_on_season_rooms.php} (61%) diff --git a/app/Models/SeasonRoom.php b/app/Models/SeasonRoom.php index a5bbfc281c3..65758e30231 100644 --- a/app/Models/SeasonRoom.php +++ b/app/Models/SeasonRoom.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Model; /** + * @property string|null $group_indicator * @property int $id * @property int $room_id * @property int $season_id diff --git a/app/Models/UserSeasonScore.php b/app/Models/UserSeasonScore.php index 51825ef866c..7a2701c2c66 100644 --- a/app/Models/UserSeasonScore.php +++ b/app/Models/UserSeasonScore.php @@ -26,38 +26,31 @@ class UserSeasonScore extends Model public function calculate(): void { - $userScores = UserScoreAggregate::whereIn('room_id', $this->season->rooms->pluck('id')) + $rooms = $this->season->rooms() + ->withPivot('group_indicator') + ->get(); + + $userScores = UserScoreAggregate::whereIn('room_id', $rooms->pluck('id')) ->where('user_id', $this->user_id) ->get(); $factors = $this->season->scoreFactorsOrderedForCalculation(); - $parentRooms = $this->season->rooms->where('parent_id', null); + $roomsGrouped = $rooms->groupBy('pivot.group_indicator'); - if ($parentRooms->count() > count($factors)) { + if ($roomsGrouped->count() > count($factors)) { throw new InvariantException(osu_trans('rankings.seasons.validation.not_enough_factors')); } - $scores = []; - - foreach ($parentRooms as $room) { - $totalScore = $userScores->where('room_id', $room->getKey()) - ->first() - ?->total_score; - - $childRoomId = $this->season->rooms - ->where('parent_id', $room->getKey()) - ->first() - ?->getKey(); - - $totalScoreChild = $userScores->where('room_id', $childRoomId) - ->first() - ?->total_score; + foreach ($roomsGrouped as $rooms) { + $groupUserScores = $userScores + ->whereIn('room_id', $rooms->pluck('id')) + ->pluck('total_score'); - if ($totalScore === null && $totalScoreChild === null) { + if ($groupUserScores === null) { continue; } - $scores[] = max([$totalScore, $totalScoreChild]); + $scores[] = $groupUserScores->max(); } rsort($scores); diff --git a/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php b/database/migrations/2025_01_12_174650_add_group_indicator_on_season_rooms.php similarity index 61% rename from database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php rename to database/migrations/2025_01_12_174650_add_group_indicator_on_season_rooms.php index 29bea618cf6..547e7cefd2a 100644 --- a/database/migrations/2024_10_22_194840_add_parent_id_to_rooms.php +++ b/database/migrations/2025_01_12_174650_add_group_indicator_on_season_rooms.php @@ -3,8 +3,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. -declare(strict_types=1); - use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; @@ -16,11 +14,8 @@ */ public function up(): void { - Schema::table('multiplayer_rooms', function (Blueprint $table) { - $table->bigInteger('parent_id') - ->unsigned() - ->nullable() - ->unique(); + Schema::table('season_rooms', function (Blueprint $table) { + $table->string('group_indicator')->nullable(); }); } @@ -29,8 +24,8 @@ public function up(): void */ public function down(): void { - Schema::table('multiplayer_rooms', function (Blueprint $table) { - $table->dropColumn('parent_id'); + Schema::table('season_rooms', function (Blueprint $table) { + $table->dropColumn('group_indicator'); }); } }; diff --git a/tests/Models/UserSeasonScoreTest.php b/tests/Models/UserSeasonScoreTest.php index 4276550be2a..35bb10e4493 100644 --- a/tests/Models/UserSeasonScoreTest.php +++ b/tests/Models/UserSeasonScoreTest.php @@ -22,7 +22,7 @@ class UserSeasonScoreTest extends TestCase public function testAddMultipleScores(): void { - $this->createRoomWithPlay(10); + $this->createRoomWithPlay(10, 'A'); $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -30,12 +30,12 @@ public function testAddMultipleScores(): void $this->assertSame($userScore->total_score, (float) 10); // 10*1 - $this->createRoomWithPlay(15); + $this->createRoomWithPlay(15, 'B'); $userScore->refresh(); $this->assertSame($userScore->total_score, 22.5); // 15*1 + 10*0.75 - $this->createRoomWithPlay(25); + $this->createRoomWithPlay(25, 'C'); $userScore->refresh(); $this->assertSame($userScore->total_score, 41.25); // 25*1 + 15*0.75 + 10*0.5 @@ -43,7 +43,7 @@ public function testAddMultipleScores(): void public function testAddMultipleScoresWithChildrenRooms(): void { - $firstRoom = $this->createRoomWithPlay(10); + $this->createRoomWithPlay(10, 'A'); $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -51,27 +51,27 @@ public function testAddMultipleScoresWithChildrenRooms(): void $this->assertSame($userScore->total_score, (float) 10); // 10*1 - $this->createRoomWithPlay(15, $firstRoom->getKey()); + $this->createRoomWithPlay(15, 'A'); $userScore->refresh(); $this->assertSame($userScore->total_score, (float) 15); // 15*1 - $secondRoom = $this->createRoomWithPlay(20); + $this->createRoomWithPlay(20, 'B'); $userScore->refresh(); $this->assertSame($userScore->total_score, 31.25); // 20*1 + 15*0.75 - $this->createRoomWithPlay(20, $secondRoom->getKey()); + $this->createRoomWithPlay(20, 'B'); $userScore->refresh(); $this->assertSame($userScore->total_score, 31.25); // 20*1 + 15*0.75 - $thirdRoom = $this->createRoomWithPlay(10); + $this->createRoomWithPlay(10, 'C'); $userScore->refresh(); $this->assertSame($userScore->total_score, 36.25); // 20*1 + 15*0.75 + 10*0.5 - $this->createRoomWithPlay(30, $thirdRoom->getKey()); + $this->createRoomWithPlay(30, 'C'); $userScore->refresh(); $this->assertSame($userScore->total_score, 52.5); // 30*1 + 20*0.75 + 15*0.5 @@ -79,7 +79,7 @@ public function testAddMultipleScoresWithChildrenRooms(): void public function testAddHigherScoreInChildRoom(): void { - $room = $this->createRoomWithPlay(10); + $this->createRoomWithPlay(10, 'A'); $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -87,7 +87,7 @@ public function testAddHigherScoreInChildRoom(): void $this->assertSame($userScore->total_score, (float) 10); - $this->createRoomWithPlay(15, $room->getKey()); + $this->createRoomWithPlay(15, 'A'); $userScore->refresh(); $this->assertSame($userScore->total_score, (float) 15); @@ -95,7 +95,7 @@ public function testAddHigherScoreInChildRoom(): void public function testAddHigherScoreInParentRoom(): void { - $room = $this->createRoomWithPlay(15); + $this->createRoomWithPlay(15, 'A'); $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -103,7 +103,7 @@ public function testAddHigherScoreInParentRoom(): void $this->assertSame($userScore->total_score, (float) 15); - $this->createRoomWithPlay(10, $room->getKey()); + $this->createRoomWithPlay(10, 'A'); $userScore->refresh(); $this->assertSame($userScore->total_score, (float) 15); @@ -111,7 +111,7 @@ public function testAddHigherScoreInParentRoom(): void public function testAddSameScoreInChildAndParentRoom(): void { - $room = $this->createRoomWithPlay(10); + $this->createRoomWithPlay(10, 'A'); $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -119,7 +119,7 @@ public function testAddSameScoreInChildAndParentRoom(): void $this->assertSame($userScore->total_score, (float) 10); - $this->createRoomWithPlay(10, $room->getKey()); + $this->createRoomWithPlay(10, 'A'); $userScore->refresh(); $this->assertSame($userScore->total_score, (float) 10); @@ -127,8 +127,20 @@ public function testAddSameScoreInChildAndParentRoom(): void public function testAddScoreInChildRoomOnly(): void { - $room = $this->createRoom(); - $this->createRoomWithPlay(10, $room->getKey()); + $this->createRoom('A'); + $this->createRoomWithPlay(10, 'A'); + + $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + ->where('season_id', $this->season->getKey()) + ->first(); + + $this->assertSame($userScore->total_score, (float) 10); + } + + public function testAddScoreInSecondRoomOnly(): void + { + $this->createRoom('A'); + $this->createRoomWithPlay(10, 'B'); $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -147,14 +159,14 @@ protected function setUp(): void $this->user = User::factory()->create(); } - private function createRoom(?int $parentId = null): Room + private function createRoom(string $groupIndicator): Room { $room = Room::factory()->create([ 'category' => 'spotlight', - 'parent_id' => $parentId, ]); SeasonRoom::factory()->create([ + 'group_indicator' => $groupIndicator, 'room_id' => $room, 'season_id' => $this->season, ]); @@ -162,9 +174,9 @@ private function createRoom(?int $parentId = null): Room return $room; } - private function createRoomWithPlay(float $totalScore, ?int $parentId = null): Room + private function createRoomWithPlay(float $totalScore, string $groupIndicator): Room { - $room = $this->createRoom($parentId); + $room = $this->createRoom($groupIndicator); $playlistItem = PlaylistItem::factory()->create([ 'owner_id' => $room->host, From 086461f46fc577d9b29c3ea01c0a5e0acce6280b Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:07:29 +0100 Subject: [PATCH 16/40] UserSeasonScore -> UserSeasonScoreAggregate --- app/Models/User.php | 2 +- ...nScore.php => UserSeasonScoreAggregate.php} | 2 +- ...ate_user_season_score_aggregates_table.php} | 2 +- ...st.php => UserSeasonScoreAggregateTest.php} | 18 +++++++++--------- 4 files changed, 12 insertions(+), 12 deletions(-) rename app/Models/{UserSeasonScore.php => UserSeasonScoreAggregate.php} (97%) rename database/migrations/{2024_10_22_190453_create_user_season_scores_table.php => 2024_10_22_190453_create_user_season_score_aggregates_table.php} (91%) rename tests/Models/{UserSeasonScoreTest.php => UserSeasonScoreAggregateTest.php} (87%) diff --git a/app/Models/User.php b/app/Models/User.php index fe8e03b33e9..9dbf4ed7af6 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -1361,7 +1361,7 @@ public function country() public function seasonScores(): HasMany { - return $this->hasMany(UserSeasonScore::class); + return $this->hasMany(UserSeasonScoreAggregate::class); } public function statisticsOsu() diff --git a/app/Models/UserSeasonScore.php b/app/Models/UserSeasonScoreAggregate.php similarity index 97% rename from app/Models/UserSeasonScore.php rename to app/Models/UserSeasonScoreAggregate.php index 7a2701c2c66..5e2739b0733 100644 --- a/app/Models/UserSeasonScore.php +++ b/app/Models/UserSeasonScoreAggregate.php @@ -16,7 +16,7 @@ * @property float $total_score * @property int $user_id */ -class UserSeasonScore extends Model +class UserSeasonScoreAggregate extends Model { public $incrementing = false; public $timestamps = false; diff --git a/database/migrations/2024_10_22_190453_create_user_season_scores_table.php b/database/migrations/2024_10_22_190453_create_user_season_score_aggregates_table.php similarity index 91% rename from database/migrations/2024_10_22_190453_create_user_season_scores_table.php rename to database/migrations/2024_10_22_190453_create_user_season_score_aggregates_table.php index cd7045105c5..9cd1ce910e4 100644 --- a/database/migrations/2024_10_22_190453_create_user_season_scores_table.php +++ b/database/migrations/2024_10_22_190453_create_user_season_score_aggregates_table.php @@ -16,7 +16,7 @@ */ public function up(): void { - Schema::create('user_season_scores', function (Blueprint $table) { + Schema::create('user_season_score_aggregates', function (Blueprint $table) { $table->bigInteger('user_id')->unsigned(); $table->integer('season_id')->unsigned(); $table->float('total_score'); diff --git a/tests/Models/UserSeasonScoreTest.php b/tests/Models/UserSeasonScoreAggregateTest.php similarity index 87% rename from tests/Models/UserSeasonScoreTest.php rename to tests/Models/UserSeasonScoreAggregateTest.php index 35bb10e4493..cbe9ca5937d 100644 --- a/tests/Models/UserSeasonScoreTest.php +++ b/tests/Models/UserSeasonScoreAggregateTest.php @@ -12,10 +12,10 @@ use App\Models\Season; use App\Models\SeasonRoom; use App\Models\User; -use App\Models\UserSeasonScore; +use App\Models\UserSeasonScoreAggregate; use Tests\TestCase; -class UserSeasonScoreTest extends TestCase +class UserSeasonScoreAggregateTest extends TestCase { private Season $season; private User $user; @@ -24,7 +24,7 @@ public function testAddMultipleScores(): void { $this->createRoomWithPlay(10, 'A'); - $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) ->first(); @@ -45,7 +45,7 @@ public function testAddMultipleScoresWithChildrenRooms(): void { $this->createRoomWithPlay(10, 'A'); - $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) ->first(); @@ -81,7 +81,7 @@ public function testAddHigherScoreInChildRoom(): void { $this->createRoomWithPlay(10, 'A'); - $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) ->first(); @@ -97,7 +97,7 @@ public function testAddHigherScoreInParentRoom(): void { $this->createRoomWithPlay(15, 'A'); - $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) ->first(); @@ -113,7 +113,7 @@ public function testAddSameScoreInChildAndParentRoom(): void { $this->createRoomWithPlay(10, 'A'); - $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) ->first(); @@ -130,7 +130,7 @@ public function testAddScoreInChildRoomOnly(): void $this->createRoom('A'); $this->createRoomWithPlay(10, 'A'); - $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) ->first(); @@ -142,7 +142,7 @@ public function testAddScoreInSecondRoomOnly(): void $this->createRoom('A'); $this->createRoomWithPlay(10, 'B'); - $userScore = UserSeasonScore::where('user_id', $this->user->getKey()) + $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) ->first(); From 68240dccfb0138b9a1d3c5b838cafa0e70604532 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:14:55 +0100 Subject: [PATCH 17/40] mute exception for Room::completePlay() usage --- app/Console/Commands/UserSeasonScoresRecalculate.php | 2 +- app/Models/UserSeasonScoreAggregate.php | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/UserSeasonScoresRecalculate.php b/app/Console/Commands/UserSeasonScoresRecalculate.php index 7ed541c50b1..4629b5a6be4 100644 --- a/app/Console/Commands/UserSeasonScoresRecalculate.php +++ b/app/Console/Commands/UserSeasonScoresRecalculate.php @@ -50,7 +50,7 @@ protected function recalculate(Season $season): void ->firstOrNew(); $seasonScore->season_id = $season->getKey(); - $seasonScore->calculate(); + $seasonScore->calculate(false); $seasonScore->save(); $bar->advance(); diff --git a/app/Models/UserSeasonScoreAggregate.php b/app/Models/UserSeasonScoreAggregate.php index 5e2739b0733..bf8db9abfcc 100644 --- a/app/Models/UserSeasonScoreAggregate.php +++ b/app/Models/UserSeasonScoreAggregate.php @@ -24,7 +24,7 @@ class UserSeasonScoreAggregate extends Model protected $primaryKey = ':composite'; protected $primaryKeys = ['user_id', 'season_id']; - public function calculate(): void + public function calculate(bool $muteExceptions = true): void { $rooms = $this->season->rooms() ->withPivot('group_indicator') @@ -38,7 +38,12 @@ public function calculate(): void $roomsGrouped = $rooms->groupBy('pivot.group_indicator'); if ($roomsGrouped->count() > count($factors)) { - throw new InvariantException(osu_trans('rankings.seasons.validation.not_enough_factors')); + // don't interrupt Room::completePlay() and throw exception only for recalculation command + if ($muteExceptions) { + return; + } else { + throw new InvariantException(osu_trans('rankings.seasons.validation.not_enough_factors')); + } } foreach ($roomsGrouped as $rooms) { From 921b9892cbe2fbc092446f506a09667b6adc756d Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:19:08 +0100 Subject: [PATCH 18/40] clean-up properties --- app/Models/Multiplayer/Room.php | 1 - app/Models/User.php | 1 + app/Models/UserSeasonScoreAggregate.php | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Models/Multiplayer/Room.php b/app/Models/Multiplayer/Room.php index 1009f6326ea..a6bc533c47d 100644 --- a/app/Models/Multiplayer/Room.php +++ b/app/Models/Multiplayer/Room.php @@ -37,7 +37,6 @@ * @property int $id * @property int|null $max_attempts * @property string $name - * @property int|null $parent_id * @property int $participant_count * @property \Illuminate\Database\Eloquent\Collection $playlist PlaylistItem * @property \Illuminate\Database\Eloquent\Collection $scoreLinks ScoreLink diff --git a/app/Models/User.php b/app/Models/User.php index 9dbf4ed7af6..0a40c4ef026 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -119,6 +119,7 @@ * @property-read Collection $scoresMania * @property-read Collection $scoresOsu * @property-read Collection $scoresTaiko + * @property-read Collection $seasonScores * @property-read UserStatistics\Fruits|null $statisticsFruits * @property-read UserStatistics\Mania|null $statisticsMania * @property-read UserStatistics\Mania4k|null $statisticsMania4k diff --git a/app/Models/UserSeasonScoreAggregate.php b/app/Models/UserSeasonScoreAggregate.php index bf8db9abfcc..a8052380029 100644 --- a/app/Models/UserSeasonScoreAggregate.php +++ b/app/Models/UserSeasonScoreAggregate.php @@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; /** + * @property-read Season $season * @property int $season_id * @property float $total_score * @property int $user_id From 66283ac440fe24b829fe84045e6ed5032b9cad76 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:34:30 +0100 Subject: [PATCH 19/40] remove redundant function --- app/Models/Season.php | 8 -------- app/Models/UserSeasonScoreAggregate.php | 3 ++- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/app/Models/Season.php b/app/Models/Season.php index 9981d7e3f35..f3ce08685cf 100644 --- a/app/Models/Season.php +++ b/app/Models/Season.php @@ -52,14 +52,6 @@ public function endDate(): ?Carbon : null; } - public function scoreFactorsOrderedForCalculation(): array - { - $factors = $this->score_factors ?? []; - rsort($factors); - - return $factors; - } - public function startDate(): ?Carbon { return $this->rooms->min('starts_at'); diff --git a/app/Models/UserSeasonScoreAggregate.php b/app/Models/UserSeasonScoreAggregate.php index a8052380029..62bcd1cb96b 100644 --- a/app/Models/UserSeasonScoreAggregate.php +++ b/app/Models/UserSeasonScoreAggregate.php @@ -35,7 +35,7 @@ public function calculate(bool $muteExceptions = true): void ->where('user_id', $this->user_id) ->get(); - $factors = $this->season->scoreFactorsOrderedForCalculation(); + $factors = $this->season->score_factors; $roomsGrouped = $rooms->groupBy('pivot.group_indicator'); if ($roomsGrouped->count() > count($factors)) { @@ -59,6 +59,7 @@ public function calculate(bool $muteExceptions = true): void $scores[] = $groupUserScores->max(); } + rsort($factors); rsort($scores); $scoreCount = count($scores); From a551a1047da0edcd557e2551ed63216279e04fa2 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Sat, 18 Jan 2025 16:01:20 +0100 Subject: [PATCH 20/40] delete factor cache key from config --- config/osu.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/osu.php b/config/osu.php index 7e24153124a..de037d65fbd 100644 --- a/config/osu.php +++ b/config/osu.php @@ -18,6 +18,7 @@ 'achievement' => [ 'icon_prefix' => env('USER_ACHIEVEMENT_ICON_PREFIX', 'https://assets.ppy.sh/user-achievements/'), ], + 'api' => [ // changing the throttle rate doesn't reset any existing timers, // changing the prefix key is the only way to invalidate them. @@ -26,6 +27,7 @@ 'scores_download' => env('API_THROTTLE_SCORES_DOWNLOAD', '10,1,api-scores-download'), ], ], + 'avatar' => [ 'cache_purge_prefix' => env('AVATAR_CACHE_PURGE_PREFIX'), 'cache_purge_method' => env('AVATAR_CACHE_PURGE_METHOD'), @@ -33,6 +35,7 @@ 'default' => env('DEFAULT_AVATAR', env('APP_URL', 'http://localhost').'/images/layout/avatar-guest@2x.png'), 'storage' => env('AVATAR_STORAGE', 'local-avatar'), ], + 'bbcode' => [ // this should be random or a config variable. // ...who am I kidding, this shouldn't even exist at all. @@ -190,13 +193,12 @@ 'processing_queue' => presence(env('SCORES_PROCESSING_QUEUE')) ?? 'osu-queue:score-statistics', 'submission_enabled' => get_bool(env('SCORES_SUBMISSION_ENABLED')) ?? true, ], + 'seasonal' => [ 'contest_id' => get_int(env('SEASONAL_CONTEST_ID')), 'ends_at' => env('SEASONAL_ENDS_AT'), ], - 'seasons' => [ - 'factors_cache_duration' => 60 * (get_float(env('SEASONS_FACTORS_CACHE_DURATION')) ?? 60), // in minutes, converted to seconds - ], + 'store' => [ 'notice' => presence(str_replace('\n', "\n", env('STORE_NOTICE') ?? '')), ], From 84779f521688e4dfe59d8859f0c19dffccba656f Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Mon, 20 Jan 2025 23:34:51 +0100 Subject: [PATCH 21/40] remove factors cache key from .env --- .env.example | 2 -- 1 file changed, 2 deletions(-) diff --git a/.env.example b/.env.example index 178701fa43b..4875ae3057e 100644 --- a/.env.example +++ b/.env.example @@ -292,8 +292,6 @@ CLIENT_CHECK_VERSION=false # SCORES_SUBMISSION_ENABLED=1 # SCORE_INDEX_MAX_ID_DISTANCE=10_000_000 -# SEASONS_FACTORS_CACHE_DURATION=60 - # BANCHO_BOT_USER_ID= # OCTANE_LOCAL_CACHE_EXPIRE_SECOND=60 From eba3c08bf6334cf304502ad728cc1d4b00b07be6 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:39:33 +0100 Subject: [PATCH 22/40] use hasOneThrough relationship on room season --- app/Models/Multiplayer/Room.php | 25 ++++++++++------- ...nique_index_to_room_id_on_season_rooms.php | 28 +++++++++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php diff --git a/app/Models/Multiplayer/Room.php b/app/Models/Multiplayer/Room.php index a6bc533c47d..66c2bc38eb3 100644 --- a/app/Models/Multiplayer/Room.php +++ b/app/Models/Multiplayer/Room.php @@ -23,6 +23,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasOneThrough; use Illuminate\Database\Eloquent\SoftDeletes; /** @@ -40,7 +41,7 @@ * @property int $participant_count * @property \Illuminate\Database\Eloquent\Collection $playlist PlaylistItem * @property \Illuminate\Database\Eloquent\Collection $scoreLinks ScoreLink - * @property-read Collection<\App\Models\Season> $seasons + * @property-read Season $season * @property \Carbon\Carbon $starts_at * @property \Carbon\Carbon|null $updated_at * @property int $user_id @@ -185,7 +186,7 @@ public static function search(array $rawParams, ?int $maxLimit = null) } if (isset($seasonId)) { - $query->whereRelation('seasons', 'seasons.id', $seasonId); + $query->whereRelation('season', 'season_id', $seasonId); } if (in_array($category, static::CATEGORIES, true)) { @@ -258,9 +259,16 @@ public function scoreLinks() return $this->hasMany(ScoreLink::class); } - public function seasons() + public function season(): HasOneThrough { - return $this->belongsToMany(Season::class, SeasonRoom::class); + return $this->hasOneThrough( + Season::class, + SeasonRoom::class, + 'id', + 'id', + 'id', + 'season_id', + ); } public function userHighScores() @@ -446,15 +454,12 @@ public function completePlay(ScoreToken $scoreToken, array $params): ScoreLink $stats->save(); } - // spotlight playlists should always be linked to one season exactly - if ($this->category === 'spotlight' && $agg->total_score > 0 && $this->seasons()->count() === 1) { - $seasonId = $this->seasons()->first()->getKey(); - + if ($this->category === 'spotlight' && $agg->total_score > 0 && $this->season !== null) { $seasonScore = $user->seasonScores() - ->where('season_id', $seasonId) + ->where('season_id', $this->season->getKey()) ->firstOrNew(); - $seasonScore->season_id = $seasonId; + $seasonScore->season()->associate($this->season); $seasonScore->calculate(); $seasonScore->save(); } diff --git a/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php b/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php new file mode 100644 index 00000000000..8a6dc9e10a5 --- /dev/null +++ b/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php @@ -0,0 +1,28 @@ +unique('room_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('season_rooms', function (Blueprint $table) { + $table->dropUnique(['room_id']); + }); + } +}; From 0063dd689c6cc452361ee30cf422fc988e056c8a Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:40:35 +0100 Subject: [PATCH 23/40] various calculate() improvements --- app/Models/UserSeasonScoreAggregate.php | 32 +++++++++---------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/app/Models/UserSeasonScoreAggregate.php b/app/Models/UserSeasonScoreAggregate.php index 62bcd1cb96b..ca40efb7aae 100644 --- a/app/Models/UserSeasonScoreAggregate.php +++ b/app/Models/UserSeasonScoreAggregate.php @@ -27,18 +27,15 @@ class UserSeasonScoreAggregate extends Model public function calculate(bool $muteExceptions = true): void { - $rooms = $this->season->rooms() - ->withPivot('group_indicator') - ->get(); - - $userScores = UserScoreAggregate::whereIn('room_id', $rooms->pluck('id')) + $seasonRooms = SeasonRoom::where('season_id', $this->season->getKey())->get(); + $userScores = UserScoreAggregate::whereIn('room_id', $seasonRooms->pluck('room_id')) ->where('user_id', $this->user_id) ->get(); $factors = $this->season->score_factors; - $roomsGrouped = $rooms->groupBy('pivot.group_indicator'); + $roomGroupCount = $seasonRooms->groupBy('group_indicator')->count(); - if ($roomsGrouped->count() > count($factors)) { + if ($roomGroupCount > count($factors)) { // don't interrupt Room::completePlay() and throw exception only for recalculation command if ($muteExceptions) { return; @@ -47,26 +44,19 @@ public function calculate(bool $muteExceptions = true): void } } - foreach ($roomsGrouped as $rooms) { - $groupUserScores = $userScores - ->whereIn('room_id', $rooms->pluck('id')) - ->pluck('total_score'); - - if ($groupUserScores === null) { - continue; - } - - $scores[] = $groupUserScores->max(); + $roomsById = $seasonRooms->keyBy('room_id'); + $scores = []; + foreach ($userScores as $score) { + $group = $roomsById[$score->room_id]->group_indicator; + $scores[$group] = max($scores[$group] ?? 0, $score->total_score); } rsort($factors); rsort($scores); - $scoreCount = count($scores); $total = 0; - - for ($i = 0; $i < $scoreCount; $i++) { - $total += $scores[$i] * $factors[$i]; + foreach ($scores as $index => $score) { + $total += $score * $factors[$index]; } $this->total_score = $total; From fa929d34d84dd8c97dda253e0cafcb87b82e5b69 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:41:17 +0100 Subject: [PATCH 24/40] use proper base model for SeasonRoom --- app/Models/SeasonRoom.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/Models/SeasonRoom.php b/app/Models/SeasonRoom.php index 65758e30231..d5990ce6ed5 100644 --- a/app/Models/SeasonRoom.php +++ b/app/Models/SeasonRoom.php @@ -7,9 +7,6 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; - /** * @property string|null $group_indicator * @property int $id @@ -18,7 +15,5 @@ */ class SeasonRoom extends Model { - use HasFactory; - public $timestamps = false; } From 0ea73f279c3e7d799862a16a84bef003b3c6cecb Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:46:32 +0100 Subject: [PATCH 25/40] define relations in SeasonRoomFactory --- database/factories/SeasonRoomFactory.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/database/factories/SeasonRoomFactory.php b/database/factories/SeasonRoomFactory.php index 3492702f4f3..81a0d81acf8 100644 --- a/database/factories/SeasonRoomFactory.php +++ b/database/factories/SeasonRoomFactory.php @@ -7,6 +7,8 @@ namespace Database\Factories; +use App\Models\Multiplayer\Room; +use App\Models\Season; use App\Models\SeasonRoom; class SeasonRoomFactory extends Factory @@ -15,7 +17,9 @@ class SeasonRoomFactory extends Factory public function definition(): array { - // pivot table... - return []; + return [ + 'room_id' => Room::factory(), + 'season_id' => Season::factory(), + ]; } } From c7a9801258b8f4da42260a02367f0c13160b3933 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:49:48 +0100 Subject: [PATCH 26/40] simplify score user ids query --- app/Console/Commands/UserSeasonScoresRecalculate.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/Console/Commands/UserSeasonScoresRecalculate.php b/app/Console/Commands/UserSeasonScoresRecalculate.php index 4629b5a6be4..cbc0214dbfe 100644 --- a/app/Console/Commands/UserSeasonScoresRecalculate.php +++ b/app/Console/Commands/UserSeasonScoresRecalculate.php @@ -35,10 +35,8 @@ public function handle(): void protected function recalculate(Season $season): void { $scoreUserIds = UserScoreAggregate::whereIn('room_id', $season->rooms->pluck('id')) - ->select('user_id') - ->get() - ->pluck('user_id') - ->unique(); + ->distinct('user_id') + ->pluck('user_id'); $bar = $this->output->createProgressBar($scoreUserIds->count()); From e51885a9eddd54969b9e18c54899c0e20d4b8eb7 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:52:58 +0100 Subject: [PATCH 27/40] associate season --- app/Console/Commands/UserSeasonScoresRecalculate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/UserSeasonScoresRecalculate.php b/app/Console/Commands/UserSeasonScoresRecalculate.php index cbc0214dbfe..acea6ab605c 100644 --- a/app/Console/Commands/UserSeasonScoresRecalculate.php +++ b/app/Console/Commands/UserSeasonScoresRecalculate.php @@ -47,7 +47,7 @@ protected function recalculate(Season $season): void ->where('season_id', $season->getKey()) ->firstOrNew(); - $seasonScore->season_id = $season->getKey(); + $seasonScore->season()->associate($season); $seasonScore->calculate(false); $seasonScore->save(); From 539a0b268f35cf7b86ac45b0658138bc2c48aab3 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:55:47 +0100 Subject: [PATCH 28/40] missing licence header --- ...1_21_134746_add_unique_index_to_room_id_on_season_rooms.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php b/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php index 8a6dc9e10a5..7451783f329 100644 --- a/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php +++ b/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php @@ -1,5 +1,8 @@ . Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; From 910d77b5a594231e1f0e03d32368f9517907806b Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:57:16 +0100 Subject: [PATCH 29/40] missing strict types declarations --- .../2025_01_12_172229_add_score_factors_on_seasons.php | 2 ++ .../2025_01_12_174650_add_group_indicator_on_season_rooms.php | 2 ++ ...01_21_134746_add_unique_index_to_room_id_on_season_rooms.php | 2 ++ 3 files changed, 6 insertions(+) diff --git a/database/migrations/2025_01_12_172229_add_score_factors_on_seasons.php b/database/migrations/2025_01_12_172229_add_score_factors_on_seasons.php index cf4a4bf6d91..bab1a0e1031 100644 --- a/database/migrations/2025_01_12_172229_add_score_factors_on_seasons.php +++ b/database/migrations/2025_01_12_172229_add_score_factors_on_seasons.php @@ -3,6 +3,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. +declare(strict_types=1); + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; diff --git a/database/migrations/2025_01_12_174650_add_group_indicator_on_season_rooms.php b/database/migrations/2025_01_12_174650_add_group_indicator_on_season_rooms.php index 547e7cefd2a..bdd7b9138c8 100644 --- a/database/migrations/2025_01_12_174650_add_group_indicator_on_season_rooms.php +++ b/database/migrations/2025_01_12_174650_add_group_indicator_on_season_rooms.php @@ -3,6 +3,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. +declare(strict_types=1); + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; diff --git a/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php b/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php index 7451783f329..0421044531d 100644 --- a/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php +++ b/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php @@ -3,6 +3,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. +declare(strict_types=1); + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; From 6643d4b705c05c2b1935612da5adb6650d96090b Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 23 Jan 2025 00:38:27 +0100 Subject: [PATCH 30/40] use proper foreign key for season relationship --- app/Models/Multiplayer/Room.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Multiplayer/Room.php b/app/Models/Multiplayer/Room.php index 7619309d632..dfddc389186 100644 --- a/app/Models/Multiplayer/Room.php +++ b/app/Models/Multiplayer/Room.php @@ -265,7 +265,7 @@ public function season(): HasOneThrough return $this->hasOneThrough( Season::class, SeasonRoom::class, - 'id', + 'room_id', 'id', 'id', 'season_id', From 190cb840e1687afe6819dee486b4cf3130f3a3e5 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 23 Jan 2025 09:15:28 +0100 Subject: [PATCH 31/40] proper argument order for assertSame --- tests/Models/UserSeasonScoreAggregateTest.php | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/Models/UserSeasonScoreAggregateTest.php b/tests/Models/UserSeasonScoreAggregateTest.php index cbe9ca5937d..f05ff820f92 100644 --- a/tests/Models/UserSeasonScoreAggregateTest.php +++ b/tests/Models/UserSeasonScoreAggregateTest.php @@ -28,17 +28,17 @@ public function testAddMultipleScores(): void ->where('season_id', $this->season->getKey()) ->first(); - $this->assertSame($userScore->total_score, (float) 10); // 10*1 + $this->assertSame(10.0, $userScore->total_score); // 10*1 $this->createRoomWithPlay(15, 'B'); $userScore->refresh(); - $this->assertSame($userScore->total_score, 22.5); // 15*1 + 10*0.75 + $this->assertSame(22.5, $userScore->total_score); // 15*1 + 10*0.75 $this->createRoomWithPlay(25, 'C'); $userScore->refresh(); - $this->assertSame($userScore->total_score, 41.25); // 25*1 + 15*0.75 + 10*0.5 + $this->assertSame(41.25, $userScore->total_score); // 25*1 + 15*0.75 + 10*0.5 } public function testAddMultipleScoresWithChildrenRooms(): void @@ -49,32 +49,32 @@ public function testAddMultipleScoresWithChildrenRooms(): void ->where('season_id', $this->season->getKey()) ->first(); - $this->assertSame($userScore->total_score, (float) 10); // 10*1 + $this->assertSame(10.0, $userScore->total_score); // 10*1 $this->createRoomWithPlay(15, 'A'); $userScore->refresh(); - $this->assertSame($userScore->total_score, (float) 15); // 15*1 + $this->assertSame(15.0, $userScore->total_score); // 15*1 $this->createRoomWithPlay(20, 'B'); $userScore->refresh(); - $this->assertSame($userScore->total_score, 31.25); // 20*1 + 15*0.75 + $this->assertSame(31.25, $userScore->total_score); // 20*1 + 15*0.75 $this->createRoomWithPlay(20, 'B'); $userScore->refresh(); - $this->assertSame($userScore->total_score, 31.25); // 20*1 + 15*0.75 + $this->assertSame(31.25, $userScore->total_score); // 20*1 + 15*0.75 $this->createRoomWithPlay(10, 'C'); $userScore->refresh(); - $this->assertSame($userScore->total_score, 36.25); // 20*1 + 15*0.75 + 10*0.5 + $this->assertSame(36.25, $userScore->total_score); // 20*1 + 15*0.75 + 10*0.5 $this->createRoomWithPlay(30, 'C'); $userScore->refresh(); - $this->assertSame($userScore->total_score, 52.5); // 30*1 + 20*0.75 + 15*0.5 + $this->assertSame(52.5, $userScore->total_score); // 30*1 + 20*0.75 + 15*0.5 } public function testAddHigherScoreInChildRoom(): void @@ -85,12 +85,12 @@ public function testAddHigherScoreInChildRoom(): void ->where('season_id', $this->season->getKey()) ->first(); - $this->assertSame($userScore->total_score, (float) 10); + $this->assertSame(10.0, $userScore->total_score); $this->createRoomWithPlay(15, 'A'); $userScore->refresh(); - $this->assertSame($userScore->total_score, (float) 15); + $this->assertSame(15.0, $userScore->total_score); } public function testAddHigherScoreInParentRoom(): void @@ -101,12 +101,12 @@ public function testAddHigherScoreInParentRoom(): void ->where('season_id', $this->season->getKey()) ->first(); - $this->assertSame($userScore->total_score, (float) 15); + $this->assertSame(15.0, $userScore->total_score); $this->createRoomWithPlay(10, 'A'); $userScore->refresh(); - $this->assertSame($userScore->total_score, (float) 15); + $this->assertSame(15.0, $userScore->total_score); } public function testAddSameScoreInChildAndParentRoom(): void @@ -117,12 +117,12 @@ public function testAddSameScoreInChildAndParentRoom(): void ->where('season_id', $this->season->getKey()) ->first(); - $this->assertSame($userScore->total_score, (float) 10); + $this->assertSame(10.0, $userScore->total_score); $this->createRoomWithPlay(10, 'A'); $userScore->refresh(); - $this->assertSame($userScore->total_score, (float) 10); + $this->assertSame(10.0, $userScore->total_score); } public function testAddScoreInChildRoomOnly(): void @@ -134,7 +134,7 @@ public function testAddScoreInChildRoomOnly(): void ->where('season_id', $this->season->getKey()) ->first(); - $this->assertSame($userScore->total_score, (float) 10); + $this->assertSame(10.0, $userScore->total_score); } public function testAddScoreInSecondRoomOnly(): void @@ -146,7 +146,7 @@ public function testAddScoreInSecondRoomOnly(): void ->where('season_id', $this->season->getKey()) ->first(); - $this->assertSame($userScore->total_score, (float) 10); + $this->assertSame(10.0, $userScore->total_score); } protected function setUp(): void From f1bf951b2e58b9beabab35edf63a82b880c1b6e9 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 23 Jan 2025 09:24:54 +0100 Subject: [PATCH 32/40] more logical createRoomWithPlay() argument order --- tests/Models/UserSeasonScoreAggregateTest.php | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/Models/UserSeasonScoreAggregateTest.php b/tests/Models/UserSeasonScoreAggregateTest.php index f05ff820f92..3243fbe423d 100644 --- a/tests/Models/UserSeasonScoreAggregateTest.php +++ b/tests/Models/UserSeasonScoreAggregateTest.php @@ -22,7 +22,7 @@ class UserSeasonScoreAggregateTest extends TestCase public function testAddMultipleScores(): void { - $this->createRoomWithPlay(10, 'A'); + $this->createRoomWithPlay('A', 10); $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -30,12 +30,12 @@ public function testAddMultipleScores(): void $this->assertSame(10.0, $userScore->total_score); // 10*1 - $this->createRoomWithPlay(15, 'B'); + $this->createRoomWithPlay('B', 15); $userScore->refresh(); $this->assertSame(22.5, $userScore->total_score); // 15*1 + 10*0.75 - $this->createRoomWithPlay(25, 'C'); + $this->createRoomWithPlay('C', 25); $userScore->refresh(); $this->assertSame(41.25, $userScore->total_score); // 25*1 + 15*0.75 + 10*0.5 @@ -43,7 +43,7 @@ public function testAddMultipleScores(): void public function testAddMultipleScoresWithChildrenRooms(): void { - $this->createRoomWithPlay(10, 'A'); + $this->createRoomWithPlay('A', 10); $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -51,27 +51,27 @@ public function testAddMultipleScoresWithChildrenRooms(): void $this->assertSame(10.0, $userScore->total_score); // 10*1 - $this->createRoomWithPlay(15, 'A'); + $this->createRoomWithPlay('A', 15); $userScore->refresh(); $this->assertSame(15.0, $userScore->total_score); // 15*1 - $this->createRoomWithPlay(20, 'B'); + $this->createRoomWithPlay('B', 20); $userScore->refresh(); $this->assertSame(31.25, $userScore->total_score); // 20*1 + 15*0.75 - $this->createRoomWithPlay(20, 'B'); + $this->createRoomWithPlay('B', 20); $userScore->refresh(); $this->assertSame(31.25, $userScore->total_score); // 20*1 + 15*0.75 - $this->createRoomWithPlay(10, 'C'); + $this->createRoomWithPlay('C', 10); $userScore->refresh(); $this->assertSame(36.25, $userScore->total_score); // 20*1 + 15*0.75 + 10*0.5 - $this->createRoomWithPlay(30, 'C'); + $this->createRoomWithPlay('C', 30); $userScore->refresh(); $this->assertSame(52.5, $userScore->total_score); // 30*1 + 20*0.75 + 15*0.5 @@ -79,7 +79,7 @@ public function testAddMultipleScoresWithChildrenRooms(): void public function testAddHigherScoreInChildRoom(): void { - $this->createRoomWithPlay(10, 'A'); + $this->createRoomWithPlay('A', 10); $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -87,7 +87,7 @@ public function testAddHigherScoreInChildRoom(): void $this->assertSame(10.0, $userScore->total_score); - $this->createRoomWithPlay(15, 'A'); + $this->createRoomWithPlay('A', 15); $userScore->refresh(); $this->assertSame(15.0, $userScore->total_score); @@ -95,7 +95,7 @@ public function testAddHigherScoreInChildRoom(): void public function testAddHigherScoreInParentRoom(): void { - $this->createRoomWithPlay(15, 'A'); + $this->createRoomWithPlay('A', 15); $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -103,7 +103,7 @@ public function testAddHigherScoreInParentRoom(): void $this->assertSame(15.0, $userScore->total_score); - $this->createRoomWithPlay(10, 'A'); + $this->createRoomWithPlay('A', 10); $userScore->refresh(); $this->assertSame(15.0, $userScore->total_score); @@ -111,7 +111,7 @@ public function testAddHigherScoreInParentRoom(): void public function testAddSameScoreInChildAndParentRoom(): void { - $this->createRoomWithPlay(10, 'A'); + $this->createRoomWithPlay('A', 10); $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -119,7 +119,7 @@ public function testAddSameScoreInChildAndParentRoom(): void $this->assertSame(10.0, $userScore->total_score); - $this->createRoomWithPlay(10, 'A'); + $this->createRoomWithPlay('A', 10); $userScore->refresh(); $this->assertSame(10.0, $userScore->total_score); @@ -128,7 +128,7 @@ public function testAddSameScoreInChildAndParentRoom(): void public function testAddScoreInChildRoomOnly(): void { $this->createRoom('A'); - $this->createRoomWithPlay(10, 'A'); + $this->createRoomWithPlay('A', 10); $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -140,7 +140,7 @@ public function testAddScoreInChildRoomOnly(): void public function testAddScoreInSecondRoomOnly(): void { $this->createRoom('A'); - $this->createRoomWithPlay(10, 'B'); + $this->createRoomWithPlay('B', 10); $userScore = UserSeasonScoreAggregate::where('user_id', $this->user->getKey()) ->where('season_id', $this->season->getKey()) @@ -174,7 +174,7 @@ private function createRoom(string $groupIndicator): Room return $room; } - private function createRoomWithPlay(float $totalScore, string $groupIndicator): Room + private function createRoomWithPlay(string $groupIndicator, float $totalScore): Room { $room = $this->createRoom($groupIndicator); From e8f0dc1aa3ee212324c69b19ba12e3adac5e23a5 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 23 Jan 2025 09:25:43 +0100 Subject: [PATCH 33/40] use static instead --- tests/Models/UserSeasonScoreAggregateTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Models/UserSeasonScoreAggregateTest.php b/tests/Models/UserSeasonScoreAggregateTest.php index 3243fbe423d..86470f7b4c2 100644 --- a/tests/Models/UserSeasonScoreAggregateTest.php +++ b/tests/Models/UserSeasonScoreAggregateTest.php @@ -183,7 +183,7 @@ private function createRoomWithPlay(string $groupIndicator, float $totalScore): 'room_id' => $room, ]); - $this->roomAddPlay($this->user, $playlistItem, [ + static::roomAddPlay($this->user, $playlistItem, [ 'passed' => true, 'total_score' => $totalScore, ]); From 1b72c3d0ec2a1dc067f44b9afb7a15d99b6c1451 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 23 Jan 2025 09:28:09 +0100 Subject: [PATCH 34/40] redate migrations --- ..._01_22_134746_add_unique_index_to_room_id_on_season_rooms.php} | 0 ...ons.php => 2025_01_22_172229_add_score_factors_on_seasons.php} | 0 ... => 2025_01_22_174650_add_group_indicator_on_season_rooms.php} | 0 ...25_01_22_190453_create_user_season_score_aggregates_table.php} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename database/migrations/{2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php => 2025_01_22_134746_add_unique_index_to_room_id_on_season_rooms.php} (100%) rename database/migrations/{2025_01_12_172229_add_score_factors_on_seasons.php => 2025_01_22_172229_add_score_factors_on_seasons.php} (100%) rename database/migrations/{2025_01_12_174650_add_group_indicator_on_season_rooms.php => 2025_01_22_174650_add_group_indicator_on_season_rooms.php} (100%) rename database/migrations/{2024_10_22_190453_create_user_season_score_aggregates_table.php => 2025_01_22_190453_create_user_season_score_aggregates_table.php} (100%) diff --git a/database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php b/database/migrations/2025_01_22_134746_add_unique_index_to_room_id_on_season_rooms.php similarity index 100% rename from database/migrations/2025_01_21_134746_add_unique_index_to_room_id_on_season_rooms.php rename to database/migrations/2025_01_22_134746_add_unique_index_to_room_id_on_season_rooms.php diff --git a/database/migrations/2025_01_12_172229_add_score_factors_on_seasons.php b/database/migrations/2025_01_22_172229_add_score_factors_on_seasons.php similarity index 100% rename from database/migrations/2025_01_12_172229_add_score_factors_on_seasons.php rename to database/migrations/2025_01_22_172229_add_score_factors_on_seasons.php diff --git a/database/migrations/2025_01_12_174650_add_group_indicator_on_season_rooms.php b/database/migrations/2025_01_22_174650_add_group_indicator_on_season_rooms.php similarity index 100% rename from database/migrations/2025_01_12_174650_add_group_indicator_on_season_rooms.php rename to database/migrations/2025_01_22_174650_add_group_indicator_on_season_rooms.php diff --git a/database/migrations/2024_10_22_190453_create_user_season_score_aggregates_table.php b/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php similarity index 100% rename from database/migrations/2024_10_22_190453_create_user_season_score_aggregates_table.php rename to database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php From b859bd56005ce6bb3578b34cd447588c2a076d42 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Thu, 23 Jan 2025 09:31:50 +0100 Subject: [PATCH 35/40] one space too much --- tests/Models/UserSeasonScoreAggregateTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Models/UserSeasonScoreAggregateTest.php b/tests/Models/UserSeasonScoreAggregateTest.php index 86470f7b4c2..510fa13219a 100644 --- a/tests/Models/UserSeasonScoreAggregateTest.php +++ b/tests/Models/UserSeasonScoreAggregateTest.php @@ -28,7 +28,7 @@ public function testAddMultipleScores(): void ->where('season_id', $this->season->getKey()) ->first(); - $this->assertSame(10.0, $userScore->total_score); // 10*1 + $this->assertSame(10.0, $userScore->total_score); // 10*1 $this->createRoomWithPlay('B', 15); From 57b7441d4813a5f423ec6703bf4f4341c3148112 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Sat, 25 Jan 2025 13:35:49 +0100 Subject: [PATCH 36/40] use double for total_score column instead --- ...5_01_22_190453_create_user_season_score_aggregates_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php b/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php index 9cd1ce910e4..3a20cfb29c7 100644 --- a/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php +++ b/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php @@ -19,7 +19,7 @@ public function up(): void Schema::create('user_season_score_aggregates', function (Blueprint $table) { $table->bigInteger('user_id')->unsigned(); $table->integer('season_id')->unsigned(); - $table->float('total_score'); + $table->double('total_score'); $table->primary(['user_id', 'season_id']); $table->index('total_score'); From 0638d90ce62f60f4b7beae5d4d851d7fc1ee196b Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Sat, 25 Jan 2025 13:36:35 +0100 Subject: [PATCH 37/40] use proper table name for drop --- ...5_01_22_190453_create_user_season_score_aggregates_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php b/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php index 3a20cfb29c7..84e20da9400 100644 --- a/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php +++ b/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php @@ -31,6 +31,6 @@ public function up(): void */ public function down(): void { - Schema::dropIfExists('user_season_scores'); + Schema::dropIfExists('user_season_score_aggregates'); } }; From 3cc049ec5ef28c063aabe273562823bd38a9067f Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Sat, 25 Jan 2025 13:41:46 +0100 Subject: [PATCH 38/40] use empty array if factors are not set --- app/Models/UserSeasonScoreAggregate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/UserSeasonScoreAggregate.php b/app/Models/UserSeasonScoreAggregate.php index ca40efb7aae..6bb43a890a3 100644 --- a/app/Models/UserSeasonScoreAggregate.php +++ b/app/Models/UserSeasonScoreAggregate.php @@ -32,7 +32,7 @@ public function calculate(bool $muteExceptions = true): void ->where('user_id', $this->user_id) ->get(); - $factors = $this->season->score_factors; + $factors = $this->season->score_factors ?? []; $roomGroupCount = $seasonRooms->groupBy('group_indicator')->count(); if ($roomGroupCount > count($factors)) { From 5a008049c6366d6dd414a1e62d6a2b31cba1cf98 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:28:47 +0100 Subject: [PATCH 39/40] recalculate command improvements --- app/Console/Commands/UserSeasonScoresRecalculate.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/UserSeasonScoresRecalculate.php b/app/Console/Commands/UserSeasonScoresRecalculate.php index acea6ab605c..732af09447f 100644 --- a/app/Console/Commands/UserSeasonScoresRecalculate.php +++ b/app/Console/Commands/UserSeasonScoresRecalculate.php @@ -49,12 +49,15 @@ protected function recalculate(Season $season): void $seasonScore->season()->associate($season); $seasonScore->calculate(false); - $seasonScore->save(); + if ($seasonScore->total_score > 0) { + $seasonScore->save(); + } $bar->advance(); } }); $bar->finish(); + $this->newLine(); } } From 106781a5fdc677ce0ef335a13ac26187767e5094 Mon Sep 17 00:00:00 2001 From: Venix <30481900+venix12@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:31:58 +0100 Subject: [PATCH 40/40] use drop instead of dropIfExists --- ...5_01_22_190453_create_user_season_score_aggregates_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php b/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php index 84e20da9400..eae232d40fe 100644 --- a/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php +++ b/database/migrations/2025_01_22_190453_create_user_season_score_aggregates_table.php @@ -31,6 +31,6 @@ public function up(): void */ public function down(): void { - Schema::dropIfExists('user_season_score_aggregates'); + Schema::drop('user_season_score_aggregates'); } };