Skip to content

Commit

Permalink
Merge branches 'score-solo-index', 'beatmap-pack-es', 'user-scores', …
Browse files Browse the repository at this point in the history
…'solo-profile-recent' and 'solo-profile' into private-staging
  • Loading branch information
nanaya committed Sep 13, 2023
6 parents 056a44e + 880caf3 + b250d8c + 1870026 + f4f8369 + bd7ae73 commit c97c78c
Show file tree
Hide file tree
Showing 18 changed files with 461 additions and 603 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ CLIENT_CHECK_VERSION=false
# TWITCH_CLIENT_SECRET=

# SCORES_ES_CACHE_DURATION=
# SCORES_ES_ENABLE_LEGACY_FILTER=true
# SCORES_EXPERIMENTAL_RANK_AS_DEFAULT=false
# SCORES_EXPERIMENTAL_RANK_AS_EXTRA=false
# SCORES_RANK_CACHE_LOCAL_SERVER=0
Expand Down
209 changes: 92 additions & 117 deletions app/Http/Controllers/BeatmapsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
use App\Jobs\Notifications\BeatmapOwnerChange;
use App\Libraries\BeatmapDifficultyAttributes;
use App\Libraries\Score\BeatmapScores;
use App\Libraries\Score\UserRank;
use App\Libraries\Search\ScoreSearch;
use App\Libraries\Search\ScoreSearchParams;
use App\Models\Beatmap;
use App\Models\BeatmapsetEvent;
use App\Models\Score\Best\Model as BestModel;
Expand Down Expand Up @@ -50,6 +53,66 @@ private static function baseScoreQuery(Beatmap $beatmap, $mode, $mods, $type = n
return $query;
}

private static function beatmapScores(string $id, ?string $scoreTransformerType, bool $isLegacy): array
{
$beatmap = Beatmap::findOrFail($id);
if ($beatmap->approved <= 0) {
return ['scores' => []];
}

$params = get_params(request()->all(), null, [
'limit:int',
'mode',
'mods:string[]',
'type:string',
], ['null_missing' => true]);

if ($params['mode'] !== null) {
$rulesetId = Beatmap::MODES[$params['mode']] ?? null;
if ($rulesetId === null) {
throw new InvariantException('invalid mode specified');
}
}
$rulesetId ??= $beatmap->playmode;
$mods = array_values(array_filter($params['mods'] ?? []));
$type = presence($params['type'], 'global');
$currentUser = auth()->user();

static::assertSupporterOnlyOptions($currentUser, $type, $mods);

$esFetch = new BeatmapScores([
'beatmap_ids' => [$beatmap->getKey()],
'is_legacy' => $isLegacy,
'limit' => $params['limit'],
'mods' => $mods,
'ruleset_id' => $rulesetId,
'type' => $type,
'user' => $currentUser,
]);
$scores = $esFetch->all()->loadMissing(['beatmap', 'performance', 'user.country', 'user.userProfileCustomization']);
$userScore = $esFetch->userBest();
$scoreTransformer = new ScoreTransformer($scoreTransformerType);

$results = [
'scores' => json_collection(
$scores,
$scoreTransformer,
static::DEFAULT_SCORE_INCLUDES
),
];

if (isset($userScore)) {
$results['user_score'] = [
'position' => $esFetch->rank($userScore),
'score' => json_item($userScore, $scoreTransformer, static::DEFAULT_SCORE_INCLUDES),
];
// TODO: remove this old camelCased json field
$results['userScore'] = $results['user_score'];
}

return $results;
}

public function __construct()
{
parent::__construct();
Expand Down Expand Up @@ -273,7 +336,7 @@ public function show($id)
}

/**
* Get Beatmap scores
* Get Beatmap scores (legacy)
*
* Returns the top scores for a beatmap
*
Expand All @@ -291,61 +354,14 @@ public function show($id)
*/
public function scores($id)
{
$beatmap = Beatmap::findOrFail($id);
if ($beatmap->approved <= 0) {
return ['scores' => []];
}

$params = get_params(request()->all(), null, [
'limit:int',
'mode:string',
'mods:string[]',
'type:string',
], ['null_missing' => true]);

$mode = presence($params['mode']) ?? $beatmap->mode;
$mods = array_values(array_filter($params['mods'] ?? []));
$type = presence($params['type']) ?? 'global';
$currentUser = auth()->user();

static::assertSupporterOnlyOptions($currentUser, $type, $mods);

$query = static::baseScoreQuery($beatmap, $mode, $mods, $type);

if ($currentUser !== null) {
// own score shouldn't be filtered by visibleUsers()
$userScore = (clone $query)->where('user_id', $currentUser->user_id)->first();
}

$scoreTransformer = new ScoreTransformer();

$results = [
'scores' => json_collection(
$query->visibleUsers()->forListing($params['limit']),
$scoreTransformer,
static::DEFAULT_SCORE_INCLUDES
),
];

if (isset($userScore)) {
$results['user_score'] = [
'position' => $userScore->userRank(compact('type', 'mods')),
'score' => json_item($userScore, $scoreTransformer, static::DEFAULT_SCORE_INCLUDES),
];
// TODO: remove this old camelCased json field
$results['userScore'] = $results['user_score'];
}

return $results;
return static::beatmapScores($id, null, true);
}

/**
* Get Beatmap scores (temp)
* Get Beatmap scores (non-legacy)
*
* Returns the top scores for a beatmap from newer client.
*
* This is a temporary endpoint.
*
* ---
*
* ### Response Format
Expand All @@ -360,62 +376,7 @@ public function scores($id)
*/
public function soloScores($id)
{
$beatmap = Beatmap::findOrFail($id);
if ($beatmap->approved <= 0) {
return ['scores' => []];
}

$params = get_params(request()->all(), null, [
'limit:int',
'mode',
'mods:string[]',
'type:string',
], ['null_missing' => true]);

if ($params['mode'] !== null) {
$rulesetId = Beatmap::MODES[$params['mode']] ?? null;
if ($rulesetId === null) {
throw new InvariantException('invalid mode specified');
}
}
$rulesetId ??= $beatmap->playmode;
$mods = array_values(array_filter($params['mods'] ?? []));
$type = presence($params['type'], 'global');
$currentUser = auth()->user();

static::assertSupporterOnlyOptions($currentUser, $type, $mods);

$esFetch = new BeatmapScores([
'beatmap_ids' => [$beatmap->getKey()],
'is_legacy' => false,
'limit' => $params['limit'],
'mods' => $mods,
'ruleset_id' => $rulesetId,
'type' => $type,
'user' => $currentUser,
]);
$scores = $esFetch->all()->loadMissing(['beatmap', 'performance', 'user.country', 'user.userProfileCustomization']);
$userScore = $esFetch->userBest();
$scoreTransformer = new ScoreTransformer(ScoreTransformer::TYPE_SOLO);

$results = [
'scores' => json_collection(
$scores,
$scoreTransformer,
static::DEFAULT_SCORE_INCLUDES
),
];

if (isset($userScore)) {
$results['user_score'] = [
'position' => $esFetch->rank($userScore),
'score' => json_item($userScore, $scoreTransformer, static::DEFAULT_SCORE_INCLUDES),
];
// TODO: remove this old camelCased json field
$results['userScore'] = $results['user_score'];
}

return $results;
return static::beatmapScores($id, ScoreTransformer::TYPE_SOLO, false);
}

public function updateOwner($id)
Expand Down Expand Up @@ -476,13 +437,25 @@ public function userScore($beatmapId, $userId)
$mode = presence($params['mode'] ?? null, $beatmap->mode);
$mods = array_values(array_filter($params['mods'] ?? []));

$score = static::baseScoreQuery($beatmap, $mode, $mods)
->visibleUsers()
->where('user_id', $userId)
->firstOrFail();
$baseParams = ScoreSearchParams::fromArray([
'beatmap_ids' => [$beatmap->getKey()],
'is_legacy' => true,
'limit' => 1,
'mods' => $mods,
'ruleset_id' => Beatmap::MODES[$mode],
'sort' => 'score_desc',
'user_id' => (int) $userId,
]);
$score = (new ScoreSearch($baseParams))->records()->first();
abort_if($score === null, 404);

$rankParams = clone $baseParams;
$rankParams->beforeScore = $score;
$rankParams->userId = null;
$rank = UserRank::getRank($rankParams);

return [
'position' => $score->userRank(compact('mods')),
'position' => $rank,
'score' => json_item(
$score,
new ScoreTransformer(),
Expand Down Expand Up @@ -513,12 +486,14 @@ public function userScoreAll($beatmapId, $userId)
{
$beatmap = Beatmap::scoreable()->findOrFail($beatmapId);
$mode = presence(get_string(request('mode'))) ?? $beatmap->mode;
$scores = BestModel::getClass($mode)
::default()
->where([
'beatmap_id' => $beatmap->getKey(),
'user_id' => $userId,
])->get();
$params = ScoreSearchParams::fromArray([
'beatmap_ids' => [$beatmap->getKey()],
'is_legacy' => true,
'ruleset_id' => Beatmap::MODES[$mode],
'sort' => 'score_desc',
'user_id' => (int) $userId,
]);
$scores = (new ScoreSearch($params))->records();

return [
'scores' => json_collection($scores, new ScoreTransformer()),
Expand Down
24 changes: 18 additions & 6 deletions app/Http/Controllers/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
use App\Models\Country;
use App\Models\IpBan;
use App\Models\Log;
use App\Models\Solo\Score as SoloScore;
use App\Models\Solo\ScoreLegacyIdMap;
use App\Models\User;
use App\Models\UserAccountHistory;
use App\Models\UserNotFound;
Expand Down Expand Up @@ -176,7 +178,7 @@ public function extraPages($_id, $page)
'monthly_playcounts' => json_collection($this->user->monthlyPlaycounts, new UserMonthlyPlaycountTransformer()),
'recent' => $this->getExtraSection(
'scoresRecent',
$this->user->scores($this->mode, true)->includeFails(false)->count()
$this->user->recentScoreCount($this->mode)
),
'replays_watched_counts' => json_collection($this->user->replaysWatchedCounts, new UserReplaysWatchedCountTransformer()),
];
Expand Down Expand Up @@ -786,9 +788,16 @@ private function getExtra($page, array $options, int $perPage = 10, int $offset
case 'scoresFirsts':
$transformer = new ScoreTransformer();
$includes = ScoreTransformer::USER_PROFILE_INCLUDES;
$query = $this->user->scoresFirst($this->mode, true)
->visibleUsers()
->reorderBy('score_id', 'desc')
$scoreQuery = $this->user->scoresFirst($this->mode, true)->unorder();
$userFirstsQuery = $scoreQuery->select($scoreQuery->qualifyColumn('score_id'));
$soloMappingQuery = ScoreLegacyIdMap
::where('ruleset_id', Beatmap::MODES[$this->mode])
->whereIn('old_score_id', $userFirstsQuery)
->select('score_id');
$query = SoloScore
::whereIn('id', $soloMappingQuery)
->default()
->reorderBy('id', 'desc')
->with(ScoreTransformer::USER_PROFILE_INCLUDES_PRELOAD);
$userRelationColumn = 'user';
break;
Expand All @@ -807,9 +816,12 @@ private function getExtra($page, array $options, int $perPage = 10, int $offset
case 'scoresRecent':
$transformer = new ScoreTransformer();
$includes = ScoreTransformer::USER_PROFILE_INCLUDES;
$query = $this->user->scores($this->mode, true)
$query = $this->user->soloScores()
->default()
->forRuleset($this->mode)
->includeFails($options['includeFails'] ?? false)
->with([...ScoreTransformer::USER_PROFILE_INCLUDES_PRELOAD, 'best']);
->reorderBy('id', 'desc')
->with(ScoreTransformer::USER_PROFILE_INCLUDES_PRELOAD);
$userRelationColumn = 'user';
break;
}
Expand Down
4 changes: 1 addition & 3 deletions app/Libraries/Elasticsearch/Search.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ abstract class Search extends HasSearch implements Queryable
/**
* A tag to use when logging timing of fetches.
* FIXME: context-based tagging would be nicer.
*
* @var string|null
*/
public $loggingTag;
public ?string $loggingTag;

protected $aggregations;
protected $index;
Expand Down
8 changes: 6 additions & 2 deletions app/Libraries/Score/FetchDedupedScores.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@ class FetchDedupedScores
private int $limit;
private array $result;

public function __construct(private string $dedupeColumn, private ScoreSearchParams $params)
{
public function __construct(
private string $dedupeColumn,
private ScoreSearchParams $params,
private ?string $searchLoggingTag = null
) {
$this->limit = $this->params->size;
}

public function all(): array
{
$this->params->size = $this->limit + 50;
$search = new ScoreSearch($this->params);
$search->loggingTag = $this->searchLoggingTag;

$nextCursor = null;
$hasNext = true;
Expand Down
8 changes: 7 additions & 1 deletion app/Libraries/Search/ScoreSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function getQuery(): BoolQuery
{
$query = new BoolQuery();

if ($this->params->isLegacy !== null) {
if (config('osu.scores.es_enable_legacy_filter') && $this->params->isLegacy !== null) {
$query->filter(['term' => ['is_legacy' => $this->params->isLegacy]]);
}
if ($this->params->rulesetId !== null) {
Expand All @@ -48,6 +48,9 @@ public function getQuery(): BoolQuery
if ($this->params->userId !== null) {
$query->filter(['term' => ['user_id' => $this->params->userId]]);
}
if ($this->params->excludeConverts) {
$query->filter(['term' => ['convert' => false]]);
}
if ($this->params->excludeMods !== null && count($this->params->excludeMods) > 0) {
foreach ($this->params->excludeMods as $excludedMod) {
$query->mustNot(['term' => ['mods' => $excludedMod]]);
Expand Down Expand Up @@ -141,6 +144,9 @@ private function addModsFilter(BoolQuery $query): void
? $modsHelper->allIds
: new Set(array_keys($modsHelper->mods[$this->params->rulesetId]));
$allMods->remove('PF', 'SD', 'MR');
if ($this->params->isLegacy || !config('osu.scores.es_enable_legacy_filter')) {
$allMods->remove('CL');
}

$allSearchMods = [];
foreach ($mods as $mod) {
Expand Down
Loading

0 comments on commit c97c78c

Please sign in to comment.