the browser-facing portion of osu!
at master 7.5 kB view raw
1<?php 2 3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 4// See the LICENCE file in the repository root for full licence text. 5 6namespace App\Http\Controllers; 7 8use App\Models\LegacyMatch\LegacyMatch; 9use App\Models\User; 10use App\Transformers\LegacyMatch\EventTransformer; 11use App\Transformers\UserCompactTransformer; 12 13/** 14 * @group Matches 15 */ 16class MatchesController extends Controller 17{ 18 public function __construct() 19 { 20 $this->middleware('require-scopes:public', ['only' => ['index', 'show']]); 21 } 22 23 /** 24 * Get Matches Listing 25 * 26 * Returns a list of matches. 27 * 28 * --- 29 * 30 * ### Response Format 31 * 32 * Field | Type | Notes 33 * ------------- | ----------------------------- | ----- 34 * cursor | [Cursor](#cursor) | | 35 * cursor_string | [CursorString](#cursorstring) | | 36 * matches | [Match](#match)[] | | 37 * params.limit | integer | | 38 * params.sort | string | | 39 * 40 * @usesCursor 41 * @queryParam limit integer Maximum number of matches (50 default, 1 minimum, 50 maximum). No-example 42 * @queryParam sort string `id_desc` for newest first; `id_asc` for oldest first. Defaults to `id_desc`. No-example 43 * @response { 44 * "matches": [ 45 * { 46 * "id": 114428685, 47 * "start_time": "2024-06-25T00:55:30+00:00", 48 * "end_time": null, 49 * "name": "peppy's game" 50 * }, 51 * // ... 52 * ], 53 * "params": { 54 * "limit": 50, 55 * "sort": "id_desc" 56 * }, 57 * "cursor": { 58 * "match_id": 114428685 59 * }, 60 * "cursor_string": "eyJtYXRjaF9pZCI6MTE0NDI4Njg1fQ" 61 * } 62 */ 63 public function index() 64 { 65 $params = request()->all(); 66 $limit = \Number::clamp(get_int($params['limit'] ?? null) ?? 50, 1, 50); 67 $cursorHelper = LegacyMatch::makeDbCursorHelper($params['sort'] ?? null); 68 69 [$matches, $hasMore] = LegacyMatch 70 ::where('private', false) 71 ->cursorSort($cursorHelper, cursor_from_params($params)) 72 ->limit($limit) 73 ->getWithHasMore(); 74 75 return [ 76 'matches' => json_collection($matches, 'LegacyMatch\LegacyMatch'), 77 'params' => ['limit' => $limit, 'sort' => $cursorHelper->getSortName()], 78 ...cursor_for_response($cursorHelper->next($matches, $hasMore)), 79 ]; 80 } 81 82 /** 83 * Get Match 84 * 85 * Returns details of the specified match. 86 * 87 * --- 88 * 89 * ### Response Format 90 * 91 * Field | Type | Notes 92 * --------------- | --------------------------- | ----- 93 * match | [Match](#match) | | 94 * events | [MatchEvent](#matchevent)[] | | 95 * users | [User](#user)[] | Includes `country`. 96 * first_event_id | integer | ID of the first [MatchEvent](#matchevent) in the match. 97 * latest_event_id | integer | ID of the lastest [MatchEvent](#matchevent) in the match. 98 * 99 * @urlParam match integer required Match ID. No-example 100 * @queryParam before integer Filter for match events before the specified [MatchEvent.id](#matchevent). No-example 101 * @queryParam after integer Filter for match events after the specified [MatchEvent.id](#matchevent). No-example 102 * @queryParam limit integer Maximum number of match events (100 default, 1 minimum, 101 maximum). No-example 103 * @response { 104 * "match": { 105 * "id": 16155689, 106 * "start_time": "2015-05-16T09:44:51+00:00", 107 * "end_time": "2015-05-16T10:55:08+00:00", 108 * "name": "CWC 2015: (Australia) vs (Poland)" 109 * }, 110 * "events": [ 111 * { 112 * "id": 484385927, 113 * "detail": { 114 * "type": "match-created" 115 * }, 116 * "timestamp": "2015-05-16T09:44:51+00:00", 117 * "user_id": null 118 * }, 119 * // ... 120 * ], 121 * "users": [], 122 * "first_event_id": 484385927, 123 * "latest_event_id": 484410607, 124 * "current_game_id": null 125 * } 126 */ 127 public function show($id) 128 { 129 $match = LegacyMatch::findOrFail($id); 130 131 $params = get_params(request()->all(), null, ['after:int', 'before:int', 'limit:int']); 132 $params['match'] = $match; 133 134 priv_check('MatchView', $match)->ensureCan(); 135 136 $eventsJson = $this->eventsJson($params); 137 138 if (is_json_request()) { 139 return $eventsJson; 140 } else { 141 return ext_view('matches.index', compact('match', 'eventsJson')); 142 } 143 } 144 145 private function eventsJson($params) 146 { 147 $match = $params['match']; 148 $after = $params['after'] ?? null; 149 $before = $params['before'] ?? null; 150 $limit = \Number::clamp($params['limit'] ?? 100, 1, 101); 151 152 $events = $match->events() 153 ->with([ 154 'game.beatmap.beatmapset', 155 'game.scores' => fn ($q) => $q->default(), 156 ])->limit($limit); 157 158 if (isset($after)) { 159 $events 160 ->where('event_id', '>', $after) 161 ->orderBy('event_id', 'ASC'); 162 } else { 163 if (isset($before)) { 164 $events->where('event_id', '<', $before); 165 } 166 167 $events->orderBy('event_id', 'DESC'); 168 $reverseOrder = true; 169 } 170 171 $events = $events->get(); 172 foreach ($events as $event) { 173 $game = $event->game; 174 if ($game !== null) { 175 foreach ($game->scores as $score) { 176 $score->setRelation('game', $game); 177 } 178 } 179 } 180 181 if ($reverseOrder ?? false) { 182 $events = $events->reverse(); 183 } 184 185 $users = User::with('country')->whereIn('user_id', $this->usersFromEvents($events))->get(); 186 187 $users = json_collection( 188 $users, 189 new UserCompactTransformer(), 190 'country' 191 ); 192 193 $events = json_collection( 194 $events, 195 new EventTransformer(), 196 ['game.beatmap.beatmapset', 'game.scores.match'] 197 ); 198 199 $eventEndIds = $match 200 ->events() 201 ->selectRaw('MIN(event_id) first_event_id, MAX(event_id) latest_event_id') 202 ->first(); 203 204 return [ 205 'match' => json_item($match, 'LegacyMatch\LegacyMatch'), 206 'events' => $events, 207 'users' => $users, 208 'first_event_id' => $eventEndIds->first_event_id ?? 0, 209 'latest_event_id' => $eventEndIds->latest_event_id ?? 0, 210 'current_game_id' => optional($match->currentGame())->getKey(), 211 ]; 212 } 213 214 private function usersFromEvents($events) 215 { 216 $userIds = []; 217 218 foreach ($events as $event) { 219 if ($event->user_id) { 220 $userIds[] = $event->user_id; 221 } 222 223 if ($event->game) { 224 foreach ($event->game->scores as $score) { 225 $userIds[] = $score->user_id; 226 } 227 } 228 } 229 230 return array_unique($userIds); 231 } 232}