Skip to content

Commit 3706926

Browse files
authored
Merge branch 'master' into recent-ended-at
2 parents 1695401 + a1e27dd commit 3706926

File tree

347 files changed

+3307
-611
lines changed

Some content is hidden

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

347 files changed

+3307
-611
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/Hashing/OsuHasher.php renamed to app/Hashing/OsuBcryptHasher.php

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

88
use Illuminate\Contracts\Hashing\Hasher;
99

10-
class OsuHasher implements Hasher
10+
class OsuBcryptHasher implements Hasher
1111
{
1212
/**
1313
* The number of rounds to hash, as 2^n.
@@ -37,12 +37,10 @@ public function info($hashedValue)
3737
*/
3838
public function make($value, array $options = [])
3939
{
40-
$cost = array_get($options, 'cost', $this->rounds);
41-
4240
// When we originally moved to bcrypt (quite a few years ago),
4341
// we had to migrate everything without waiting for every user to
4442
// change their passwords, hence the md5 still being there.
45-
$hash = password_hash(md5($value), PASSWORD_BCRYPT, ['cost' => $cost]);
43+
$hash = password_hash(md5($value), PASSWORD_BCRYPT, ['cost' => $this->cost($options)]);
4644

4745
// see static::check()
4846
return str_replace('$2y$', '$2a$', $hash);
@@ -83,9 +81,13 @@ public function check($value, $hashedValue, array $options = [])
8381
*/
8482
public function needsRehash($hashedValue, array $options = [])
8583
{
86-
$cost = array_get($options, 'cost', $this->rounds);
8784
$hashedValue = str_replace('$2a$', '$2y$', $hashedValue);
8885

89-
return password_needs_rehash($hashedValue, PASSWORD_BCRYPT, ['cost' => $cost]);
86+
return password_needs_rehash($hashedValue, PASSWORD_BCRYPT, ['cost' => $this->cost($options)]);
87+
}
88+
89+
protected function cost(array $options): int
90+
{
91+
return $options['rounds'] ?? $this->rounds;
9092
}
9193
}

Diff for: app/Hashing/OsuHashManager.php

-16
This file was deleted.

Diff for: app/Http/Controllers/BeatmapDiscussionsController.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,11 @@ public function index()
120120

121121
public function mediaUrl()
122122
{
123-
$url = get_string(request('url'));
123+
$url = presence(get_string(request('url')));
124+
125+
if (!isset($url)) {
126+
return response('Missing url parameter', 422);
127+
}
124128

125129
// Tell browser not to request url for a while.
126130
return redirect(proxy_media($url))->header('Cache-Control', 'max-age=600');

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/Http/Controllers/WikiController.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,16 @@ public function image($path)
123123
return response('Invalid file format', 422);
124124
}
125125

126-
$image = Wiki\Image::lookupForController($path, Request::url(), Request::header('referer'));
127-
128-
request()->attributes->set('strip_cookies', true);
126+
$image = (new Wiki\Image($path))->sync();
129127

130128
if (!$image->isVisible()) {
131129
return response('Not found', 404);
132130
}
133131

134-
return response($image->get()['content'], 200)
135-
->header('Content-Type', $image->get()['type'])
132+
$imageData = $image->get();
133+
134+
return response($imageData['content'], 200)
135+
->header('Content-Type', $imageData['type'])
136136
// 10 years max-age
137137
->header('Cache-Control', 'max-age=315360000, public');
138138
}

Diff for: app/Http/Kernel.php

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ class Kernel extends HttpKernel
2727
Middleware\VerifyUserAlways::class,
2828
],
2929
'web' => [
30-
Middleware\StripCookies::class,
3130
Middleware\EncryptCookies::class,
3231
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
3332
\Illuminate\Session\Middleware\StartSession::class,

Diff for: app/Http/Middleware/StripCookies.php

-23
This file was deleted.

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/Libraries/Search/ArtistTrackSearch.php

+8
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,18 @@ public function getQuery()
3333
$this->addQueryStringFilter($query);
3434
$this->addSimpleFilters($query);
3535
$this->addTextFilters($query);
36+
$this->addExclusiveOnlyFilter($query);
3637

3738
return $query;
3839
}
3940

41+
private function addExclusiveOnlyFilter(BoolQuery $query): void
42+
{
43+
if ($this->params->exclusiveOnly) {
44+
$query->filter(['term' => ['exclusive' => true]]);
45+
}
46+
}
47+
4048
private function addQueryStringFilter($query): void
4149
{
4250
$value = $this->params->queryString;

Diff for: app/Libraries/Search/ArtistTrackSearchParams.php

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ArtistTrackSearchParams extends SearchParams
2727
public ?string $album;
2828
public ?string $artist;
2929
public ?array $bpm;
30+
public bool $exclusiveOnly = false;
3031
public ?string $genre;
3132
public bool $isDefaultSort = false;
3233
public ?array $length;

0 commit comments

Comments
 (0)