Skip to content

Commit 004d9db

Browse files
authored
Merge branch 'master' into artist-originals
2 parents cffb309 + 6b5c8ac commit 004d9db

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+752
-377
lines changed

Diff for: .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=

Diff for: app/Exceptions/ImageProcessorServiceException.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99

1010
class ImageProcessorServiceException extends Exception
1111
{
12-
// doesn't really contain anything
12+
const INVALID_IMAGE = 1;
1313
}

Diff for: app/Http/Controllers/Forum/TopicsController.php

+1
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ public function show($id)
428428

429429
$pollSummary = PollOption::summary($topic, $currentUser);
430430

431+
$topic->incrementViewCount($currentUser, \Request::ip());
431432
$posts->last()->markRead($currentUser);
432433

433434
$coverModel = $topic->cover ?? new TopicCover();

Diff for: 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
}

Diff for: 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}";

Diff for: 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()

Diff for: app/Libraries/ImageProcessorService.php

+14-8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
use App\Exceptions\ImageProcessorServiceException;
99
use App\Models\Beatmapset;
10+
use GuzzleHttp\Client;
11+
use GuzzleHttp\Exception\GuzzleException;
1012

1113
class ImageProcessorService
1214
{
@@ -42,15 +44,19 @@ public function process($method, $src)
4244
$src = preg_replace('/https?:\/\//', '', $src);
4345
try {
4446
$tmpFile = tmpfile();
45-
$bytesWritten = fwrite($tmpFile, file_get_contents($this->endpoint."/{$method}/{$src}"));
46-
} catch (\ErrorException $e) {
47-
if (strpos($e->getMessage(), 'HTTP request failed!') !== false) {
48-
throw new ImageProcessorServiceException('HTTP request failed!');
49-
} elseif (strpos($e->getMessage(), 'Connection refused') !== false) {
50-
throw new ImageProcessorServiceException('Connection refused.');
51-
} else {
52-
throw $e;
47+
$bytesWritten = fwrite(
48+
$tmpFile,
49+
(new Client())->request('GET', "{$this->endpoint}/{$method}/{$src}")->getBody()->getContents(),
50+
);
51+
} catch (GuzzleException $e) {
52+
if (str_contains($e->getMessage(), 'VipsJpeg: Premature end of input file')) {
53+
throw new ImageProcessorServiceException(
54+
'Invalid image file',
55+
ImageProcessorServiceException::INVALID_IMAGE,
56+
$e,
57+
);
5358
}
59+
throw new ImageProcessorServiceException('HTTP request failed!', 0, $e);
5460
}
5561

5662
if ($bytesWritten === false || $bytesWritten < 100) {

Diff for: app/Models/Beatmapset.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use App\Enums\Ruleset;
99
use App\Exceptions\BeatmapProcessorException;
10+
use App\Exceptions\ImageProcessorServiceException;
1011
use App\Exceptions\InvariantException;
1112
use App\Jobs\CheckBeatmapsetCovers;
1213
use App\Jobs\EsDocument;
@@ -553,7 +554,14 @@ public function regenerateCovers(array $sizesToRegenerate = null)
553554
$processor = new ImageProcessorService();
554555

555556
// upload optimized full-size version
556-
$optimized = $processor->optimize($this->coverURL('raw', $timestamp));
557+
try {
558+
$optimized = $processor->optimize($this->coverURL('raw', $timestamp));
559+
} catch (ImageProcessorServiceException $e) {
560+
if ($e->getCode() === ImageProcessorServiceException::INVALID_IMAGE) {
561+
return false;
562+
}
563+
throw $e;
564+
}
557565
$this->storeCover('fullsize.jpg', get_stream_filename($optimized));
558566

559567
// use thumbnailer to generate (and then upload) all our variants

Diff for: app/Models/Forum/Topic.php

+14-2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ class Topic extends Model implements AfterCommit
103103
'topic_title' => 100,
104104
];
105105

106+
const VIEW_COUNT_INTERVAL = 86400; // 1 day
107+
106108
protected $table = 'phpbb_topics';
107109
protected $primaryKey = 'topic_id';
108110

@@ -541,8 +543,6 @@ public function markRead($user, $markTime)
541543

542544
throw $ex;
543545
}
544-
545-
$this->incrementInstance('topic_views');
546546
} elseif ($status->mark_time < $markTime) {
547547
$status->update(['mark_time' => $markTime]);
548548
}
@@ -555,6 +555,18 @@ public function markRead($user, $markTime)
555555
DB::commit();
556556
}
557557

558+
public function incrementViewCount(?User $user, string $ipAddr): void
559+
{
560+
$lockKey = "view:forum_topic:{$this->getKey()}:";
561+
$lockKey .= $user === null
562+
? "guest:{$ipAddr}"
563+
: "user:{$user->getKey()}";
564+
565+
if (\Cache::lock($lockKey, static::VIEW_COUNT_INTERVAL)->get()) {
566+
$this->incrementInstance('topic_views');
567+
}
568+
}
569+
558570
public function isIssue()
559571
{
560572
return in_array($this->forum_id, $GLOBALS['cfg']['osu']['forum']['issue_forum_ids'], true);

Diff for: 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

Diff for: app/Models/Solo/Score.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,12 @@ public static function extractParams(array $rawParams, ScoreToken|MultiplayerSco
118118
$params['started_at'] = $scoreToken->created_at;
119119
$params['user_id'] = $scoreToken->user_id;
120120

121+
$params['passed'] ??= false;
122+
$params['preserve'] = $params['passed'];
123+
121124
$beatmap = $scoreToken->beatmap;
122125
// anything that have leaderboard
123-
$params['ranked'] = $beatmap !== null && $beatmap->approved > 0;
124-
125-
$params['preserve'] = $params['passed'] ?? false;
126+
$params['ranked'] = $params['passed'] && $beatmap !== null && $beatmap->approved > 0;
126127

127128
return $params;
128129
}

Diff for: app/Models/UserProfileCustomization.php

-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
99

1010
/**
11-
* @property array|null $cover_json
1211
* @property \Carbon\Carbon $created_at
1312
* @property string|null $extras_order
1413
* @property int $id
@@ -62,7 +61,6 @@ class UserProfileCustomization extends Model
6261
public $incrementing = false;
6362

6463
protected $casts = [
65-
'cover_json' => 'array',
6664
'options' => AsArrayObject::class,
6765
];
6866
protected $primaryKey = 'user_id';

0 commit comments

Comments
 (0)