Skip to content

Commit 86c8ac1

Browse files
authored
Merge pull request #11010 from nanaya/score-pin-solo
Limit score pinning to new score table
2 parents a5a5bd7 + 442ecc2 commit 86c8ac1

File tree

16 files changed

+182
-233
lines changed

16 files changed

+182
-233
lines changed

.env.example

-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,6 @@ CLIENT_CHECK_VERSION=false
246246

247247
# USER_MAX_SCORE_PINS=10
248248
# USER_MAX_SCORE_PINS_SUPPORTER=50
249-
# USER_HIDE_PINNED_SOLO_SCORES=true
250249

251250
# the content is in markdown format
252251
# USER_PROFILE_SCORES_NOTICE=

app/Http/Controllers/ScorePinsController.php

+30-37
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
namespace App\Http\Controllers;
77

88
use App\Jobs\RenumberUserScorePins;
9-
use App\Libraries\MorphMap;
10-
use App\Models\Beatmap;
119
use App\Models\ScorePin;
1210
use App\Models\Solo;
1311
use Exception;
@@ -23,29 +21,31 @@ public function __construct()
2321

2422
public function destroy()
2523
{
26-
auth()->user()->scorePins()->where($this->getScoreParams(request()->all()))->delete();
24+
\Auth::user()->scorePins()->whereKey(get_int(request('score_id')))->delete();
2725

2826
return response()->noContent();
2927
}
3028

3129
public function reorder()
3230
{
33-
$rawParams = request()->all();
34-
$targetParams = $this->getScoreParams($rawParams);
31+
$rawParams = \Request::all();
32+
$targetId = get_int($rawParams['score_id'] ?? null);
3533

36-
$pinsQuery = auth()->user()->scorePins();
37-
$target = $pinsQuery->clone()->where($targetParams)->firstOrFail();
34+
$pinsQuery = \Auth::user()->scorePins();
35+
$target = $pinsQuery->clone()->findOrFail($targetId);
36+
$rulesetId = $target->ruleset_id;
37+
$pinsQuery->where('ruleset_id', $rulesetId);
3838

3939
$adjacentScores = [];
4040
foreach (['order1', 'order3'] as $position) {
41-
$adjacentScores[$position] = $this->getScoreParams(get_arr($rawParams[$position] ?? null) ?? []);
41+
$adjacentScoreIds[$position] = get_int($rawParams[$position]['score_id'] ?? null);
4242
}
4343

44-
$order1Item = isset($adjacentScores['order1']['score_id'])
45-
? $pinsQuery->clone()->where($adjacentScores['order1'])->first()
44+
$order1Item = isset($adjacentScoreIds['order1'])
45+
? $pinsQuery->clone()->find($adjacentScoreIds['order1'])
4646
: null;
47-
$order3Item = $order1Item === null && isset($adjacentScores['order3']['score_id'])
48-
? $pinsQuery->clone()->where($adjacentScores['order3'])->first()
47+
$order3Item = $order1Item === null && isset($adjacentScoreIds['order3'])
48+
? $pinsQuery->clone()->find($adjacentScoreIds['order3'])
4949
: null;
5050

5151
abort_if($order1Item === null && $order3Item === null, 422, 'no valid pinned score reference is specified');
@@ -63,7 +63,7 @@ public function reorder()
6363
$order2 = ($order1 + $order3) / 2;
6464

6565
if ($order3 - $order1 < 0.1) {
66-
dispatch(new RenumberUserScorePins($target->user_id, $target->score_type));
66+
dispatch(new RenumberUserScorePins($target->user_id, $target->ruleset_id));
6767
}
6868

6969
$target->update(['display_order' => $order2]);
@@ -73,33 +73,36 @@ public function reorder()
7373

7474
public function store()
7575
{
76-
$params = $this->getScoreParams(request()->all());
77-
78-
abort_if(!ScorePin::isValidType($params['score_type']), 422, 'invalid score_type');
79-
80-
$score = MorphMap::getClass($params['score_type'])::find($params['score_id']);
76+
$id = get_int(request('score_id'));
77+
$score = Solo\Score::find($id);
8178

8279
abort_if($score === null, 422, "specified score couldn't be found");
8380

84-
$user = auth()->user();
81+
$user = \Auth::user();
8582

86-
$pin = ScorePin::where(['user_id' => $user->getKey()])->whereMorphedTo('score', $score)->first();
83+
$pin = $user->scorePins()->find($id);
8784

8885
if ($pin === null) {
8986
priv_check('ScorePin', $score)->ensureCan();
9087

91-
$rulesetId = Beatmap::MODES[$score->getMode()];
88+
$rulesetId = $score->ruleset_id;
9289
$currentMinDisplayOrder = $user->scorePins()->where('ruleset_id', $rulesetId)->min('display_order') ?? 2500;
9390

94-
$soloScore = $score instanceof Solo\Score
95-
? $score
96-
: Solo\Score::firstWhere(['ruleset_id' => $rulesetId, 'legacy_score_id' => $score->getKey()]);
97-
9891
try {
9992
(new ScorePin([
10093
'display_order' => $currentMinDisplayOrder - 100,
10194
'ruleset_id' => $rulesetId,
102-
'new_score_id' => $soloScore?->getKey(),
95+
96+
/**
97+
* TODO:
98+
* 1. update score_id = new_score_id
99+
* 2. remove duplicated score_id
100+
* 3. use score_id as primary key (both model and database)
101+
* 4. remove setting score_type below
102+
* 5. remove new_score_id and score_type columns
103+
*/
104+
'score_id' => $score->getKey(),
105+
'score_type' => $score->getMorphClass(),
103106
]))->user()->associate($user)
104107
->score()->associate($score)
105108
->saveOrExplode();
@@ -109,19 +112,9 @@ public function store()
109112
}
110113
}
111114

112-
if ($score instanceof Solo\Score) {
113-
$score->update(['preserve' => true]);
114-
}
115+
$score->update(['preserve' => true]);
115116
}
116117

117118
return response()->noContent();
118119
}
119-
120-
private function getScoreParams(array $form)
121-
{
122-
return get_params($form, null, [
123-
'score_type:string',
124-
'score_id:int',
125-
], ['null_missing' => true]);
126-
}
127120
}

app/Http/Controllers/ScoresController.php

+8-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
namespace App\Http\Controllers;
77

8+
use App\Enums\Ruleset;
89
use App\Models\Score\Best\Model as ScoreBest;
910
use App\Models\Solo\Score as SoloScore;
1011
use App\Transformers\ScoreTransformer;
@@ -90,10 +91,13 @@ public function download($rulesetOrSoloId, $id = null)
9091

9192
public function show($rulesetOrSoloId, $legacyId = null)
9293
{
93-
[$scoreClass, $id] = $legacyId === null
94-
? [SoloScore::class, $rulesetOrSoloId]
95-
: [ScoreBest::getClass($rulesetOrSoloId), $legacyId];
96-
$score = $scoreClass::whereHas('beatmap.beatmapset')->visibleUsers()->findOrFail($id);
94+
$scoreQuery = $legacyId === null
95+
? SoloScore::whereKey($rulesetOrSoloId)
96+
: SoloScore::where([
97+
'ruleset_id' => Ruleset::tryFromName($rulesetOrSoloId) ?? abort(404, 'unknown ruleset name'),
98+
'legacy_score_id' => $legacyId,
99+
]);
100+
$score = $scoreQuery->whereHas('beatmap.beatmapset')->visibleUsers()->firstOrFail();
97101

98102
$userIncludes = array_map(function ($include) {
99103
return "user.{$include}";

app/Jobs/RenumberUserScorePins.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class RenumberUserScorePins implements ShouldQueue
1515
{
1616
use InteractsWithQueue, Queueable;
1717

18-
public function __construct(private int $userId, private string $scoreType)
18+
public function __construct(private int $userId, private int $rulesetId)
1919
{
2020
}
2121

@@ -24,7 +24,7 @@ public function handle()
2424
DB::transaction(function () {
2525
$pins = ScorePin
2626
::where([
27-
'score_type' => $this->scoreType,
27+
'ruleset_id' => $this->rulesetId,
2828
'user_id' => $this->userId,
2929
])->orderBy('display_order', 'asc')
3030
->lockForUpdate()

app/Models/ScorePin.php

+6-30
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,19 @@
77

88
namespace App\Models;
99

10-
use App\Libraries\MorphMap;
11-
use App\Models\Score\Best as ScoreBest;
12-
use Ds\Set;
1310
use Illuminate\Database\Eloquent\Builder;
1411
use Illuminate\Database\Eloquent\Relations\BelongsTo;
15-
use Illuminate\Database\Eloquent\Relations\MorphTo;
1612

1713
/**
1814
* @property \Carbon\Carbon|null $created_at
19-
* @property \App\Models\Score\Best\Model $score
15+
* @property \App\Models\Solo\Score $score
2016
* @property \Carbon\Carbon|null $updated_at
2117
*/
2218
class ScorePin extends Model
2319
{
24-
const SCORES = [
25-
MorphMap::MAP[ScoreBest\Fruits::class],
26-
MorphMap::MAP[ScoreBest\Mania::class],
27-
MorphMap::MAP[ScoreBest\Osu::class],
28-
MorphMap::MAP[ScoreBest\Taiko::class],
29-
MorphMap::MAP[Solo\Score::class],
30-
];
20+
public $incrementing = false;
3121

32-
public static function isValidType(string|null $type): bool
33-
{
34-
static $lookup;
35-
36-
$lookup ??= new Set(static::SCORES);
37-
38-
return $lookup->contains($type);
39-
}
22+
protected $primaryKey = 'new_score_id';
4023

4124
public function scopeForRuleset($query, string $ruleset): Builder
4225
{
@@ -45,19 +28,12 @@ public function scopeForRuleset($query, string $ruleset): Builder
4528

4629
public function scopeWithVisibleScore($query): Builder
4730
{
48-
$scoreModels = static::SCORES;
49-
50-
if ($GLOBALS['cfg']['osu']['user']['hide_pinned_solo_scores']) {
51-
$soloScoreIndex = array_search_null(MorphMap::MAP[Solo\Score::class], $scoreModels);
52-
array_splice($scoreModels, $soloScoreIndex, 1);
53-
}
54-
55-
return $query->whereHasMorph('score', $scoreModels, fn ($q) => $q->whereHas('beatmap.beatmapset'));
31+
return $query->whereHas('score', fn ($q) => $q->whereHas('beatmap.beatmapset'));
5632
}
5733

58-
public function score(): MorphTo
34+
public function score(): BelongsTo
5935
{
60-
return $this->morphTo();
36+
return $this->belongsTo(Solo\Score::class, 'new_score_id');
6137
}
6238

6339
public function user(): BelongsTo

app/Singletons/OsuAuthorize.php

-4
Original file line numberDiff line numberDiff line change
@@ -1943,10 +1943,6 @@ public function checkScorePin(?User $user, ScoreBest|Solo\Score $score): string
19431943
return $prefix.'failed';
19441944
}
19451945

1946-
if ($score instanceof Solo\Score && $GLOBALS['cfg']['osu']['user']['hide_pinned_solo_scores']) {
1947-
return $prefix.'disabled_type';
1948-
}
1949-
19501946
$pinned = $user->scorePins()->forRuleset($score->getMode())->withVisibleScore()->count();
19511947

19521948
if ($pinned >= $user->maxScorePins()) {

app/Singletons/UserScorePins.php

+12-29
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,31 @@
77

88
namespace App\Singletons;
99

10-
use App\Libraries\MorphMap;
11-
use App\Models\Beatmap;
12-
use App\Models\Score;
13-
use App\Models\ScorePin;
1410
use App\Models\Solo;
11+
use Ds\Set;
1512

1613
class UserScorePins
1714
{
18-
const REQUEST_ATTRIBUTE_KEY_PREFIX = 'current_user_score_pins:';
15+
const REQUEST_ATTRIBUTE_KEY = 'current_user_score_pins';
1916

20-
public function isPinned(Score\Best\Model|Solo\Score $score): bool
17+
public function isPinned(Solo\Score $score): bool
2118
{
22-
$type = $score->getMorphClass();
23-
$key = static::REQUEST_ATTRIBUTE_KEY_PREFIX.$type;
24-
$pins = request()->attributes->get($key);
19+
$attributes = \Request::instance()->attributes;
20+
$pins = $attributes->get(static::REQUEST_ATTRIBUTE_KEY);
2521

2622
if ($pins === null) {
27-
$user = auth()->user();
28-
$pins = $user === null
29-
? []
30-
: $user->scorePins()
31-
->select('score_id')
32-
->where(['score_type' => $type])
33-
->get()
34-
->keyBy(fn (ScorePin $p) => $p->score_id);
35-
36-
request()->attributes->set($key, $pins);
23+
$pins = new Set(\Auth::user()?->scorePins()->pluck('new_score_id') ?? []);
24+
25+
$attributes->set(static::REQUEST_ATTRIBUTE_KEY, $pins);
3726
}
3827

39-
return isset($pins[$score->getKey()]);
28+
return $pins->contains($score->getKey());
4029
}
4130

4231
public function reset(): void
4332
{
44-
$prefix = static::REQUEST_ATTRIBUTE_KEY_PREFIX;
45-
$attributes = request()->attributes;
46-
47-
$attributes->remove($prefix.MorphMap::getType(Solo\Score::class));
48-
49-
foreach (Beatmap::MODES as $ruleset => $rulesetId) {
50-
$type = MorphMap::getType(Score\Best\Model::getClass($ruleset));
51-
$attributes->remove("{$prefix}{$type}");
52-
}
33+
\Request::instance()
34+
->attributes
35+
->remove(static::REQUEST_ATTRIBUTE_KEY);
5336
}
5437
}

app/Transformers/Score/CurrentUserAttributesTransformer.php

+1-4
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ class CurrentUserAttributesTransformer extends TransformerAbstract
1717
{
1818
public function transform(LegacyMatch\Score|MultiplayerScoreLink|ScoreModel|SoloScore $score): array
1919
{
20-
if ($score instanceof ScoreModel) {
21-
$pinnable = $score->best;
22-
} elseif ($score instanceof SoloScore) {
20+
if ($score instanceof SoloScore) {
2321
$pinnable = $score;
2422
} elseif ($score instanceof MultiplayerScoreLink) {
2523
$pinnable = $score->score;
@@ -32,7 +30,6 @@ public function transform(LegacyMatch\Score|MultiplayerScoreLink|ScoreModel|Solo
3230
? [
3331
'is_pinned' => app('score-pins')->isPinned($pinnable),
3432
'score_id' => $pinnable->getKey(),
35-
'score_type' => $pinnable->getMorphClass(),
3633
] : null,
3734
];
3835
}

config/osu.php

-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,6 @@
246246
'allow_registration' => get_bool(env('ALLOW_REGISTRATION')) ?? true,
247247
'allowed_rename_groups' => explode(' ', env('USER_ALLOWED_RENAME_GROUPS', 'default')),
248248
'bypass_verification' => get_bool(env('USER_BYPASS_VERIFICATION')) ?? false,
249-
'hide_pinned_solo_scores' => get_bool(env('USER_HIDE_PINNED_SOLO_SCORES')) ?? true,
250249
'inactive_force_password_reset' => get_bool(env('USER_INACTIVE_FORCE_PASSWORD_RESET') ?? false),
251250
'inactive_seconds_verification' => (get_int(env('USER_INACTIVE_DAYS_VERIFICATION')) ?? 180) * 86400,
252251
'min_plays_for_posting' => get_int(env('USER_MIN_PLAYS_FOR_POSTING')) ?? 10,

database/factories/ScorePinFactory.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
namespace Database\Factories;
99

1010
use App\Models\Beatmap;
11-
use App\Models\Score\Best\Model as ScoreBestModel;
1211
use App\Models\ScorePin;
1312
use App\Models\Solo;
1413
use App\Models\User;
@@ -25,11 +24,13 @@ public function definition(): array
2524
];
2625
}
2726

28-
public function withScore(ScoreBestModel|Solo\Score $score): static
27+
public function withScore(Solo\Score $score): static
2928
{
3029
return $this
3130
->state([
3231
'ruleset_id' => Beatmap::MODES[$score->getMode()],
32+
'score_id' => $score->getKey(),
33+
'score_type' => $score->getMorphClass(),
3334
'user_id' => $score->user,
3435
])->for($score, 'score');
3536
}

database/factories/Solo/ScoreFactory.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@ public function definition(): array
2121
{
2222
return [
2323
'accuracy' => fn (): float => $this->faker->randomFloat(1, 0, 1),
24-
'beatmap_id' => Beatmap::factory()->ranked(),
2524
'ended_at' => new \DateTime(),
2625
'pp' => fn (): float => $this->faker->randomFloat(4, 0, 1000),
2726
'rank' => fn () => array_rand_val(ScoreRank::cases())->value,
2827
'total_score' => fn (): int => $this->faker->randomNumber(7),
2928
'user_id' => User::factory(),
3029

30+
'beatmap_id' => fn (array $attr) => is_int($attr['ruleset_id'] ?? null)
31+
? Beatmap::factory()->state(['playmode' => $attr['ruleset_id']])->ranked()
32+
: Beatmap::factory()->ranked(),
33+
3134
// depends on beatmap_id
3235
'ruleset_id' => fn (array $attr) => Beatmap::find($attr['beatmap_id'])->playmode,
3336

0 commit comments

Comments
 (0)