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
6use App\Http\Middleware\ThrottleRequests;
7
8Route::get('wiki/images/{path}', 'WikiController@image')->name('wiki.image')->where('path', '.+');
9Route::get('media-url', 'ProxyMediaController')->name('media-url');
10
11Route::group(['middleware' => ['web']], function () {
12 Route::group(['as' => 'admin.', 'prefix' => 'admin', 'namespace' => 'Admin'], function () {
13 Route::get('/beatmapsets/{beatmapset}/covers', 'BeatmapsetsController@covers')->name('beatmapsets.covers');
14 Route::post('/beatmapsets/{beatmapset}/covers/regenerate', 'BeatmapsetsController@regenerateCovers')->name('beatmapsets.covers.regenerate');
15 Route::post('/beatmapsets/{beatmapset}/covers/remove', 'BeatmapsetsController@removeCovers')->name('beatmapsets.covers.remove');
16 Route::resource('beatmapsets', 'BeatmapsetsController', ['only' => ['show', 'update']]);
17
18 Route::post('contests/{contest}/zip', 'ContestsController@gimmeZip')->name('contests.get-zip');
19 Route::resource('contests', 'ContestsController', ['only' => ['index', 'show']]);
20
21 Route::resource('user-contest-entries', 'UserContestEntriesController', ['only' => ['destroy']]);
22 Route::post('user-contest-entries/{user_contest_entry}/restore', 'UserContestEntriesController@restore')->name('user-contest-entries.restore');
23
24 Route::resource('logs', 'LogsController', ['only' => ['index']]);
25
26 Route::get('/', 'PagesController@root')->name('root');
27
28 Route::group(['as' => 'forum.', 'prefix' => 'forum', 'namespace' => 'Forum'], function () {
29 Route::resource('forum-covers', 'ForumCoversController', ['only' => ['index', 'store', 'update']]);
30 });
31 });
32
33 Route::group(['prefix' => 'beatmaps'], function () {
34 // featured artists
35 Route::group(['as' => 'artists.', 'prefix' => 'artists'], function () {
36 Route::resource('tracks', 'ArtistTracksController', ['only' => ['index']]);
37 });
38 Route::resource('artists', 'ArtistsController', ['only' => ['index', 'show']]);
39 Route::resource('artists/tracks', 'ArtistTracksController', ['only' => 'show']);
40
41 Route::resource('packs', 'BeatmapPacksController', ['only' => ['index', 'show']]);
42
43 Route::group(['as' => 'beatmaps.', 'prefix' => '{beatmap}'], function () {
44 Route::get('scores', 'BeatmapsController@scores')->name('scores');
45 Route::get('solo-scores', 'BeatmapsController@soloScores')->name('solo-scores');
46 Route::post('update-owner', 'BeatmapsController@updateOwner')->name('update-owner');
47 });
48 });
49
50 Route::resource('beatmaps', 'BeatmapsController', ['only' => ['show']]);
51
52 Route::group(['prefix' => 'beatmapsets'], function () {
53 route_redirect('beatmap-discussions', 'beatmapsets.discussions.index');
54 route_redirect('beatmap-discussions/{beatmap_discussion}', 'beatmapsets.discussions.destroy', 'delete');
55 route_redirect('beatmap-discussions/{beatmap_discussion}', 'beatmapsets.discussions.show');
56 route_redirect('beatmap-discussions/{beatmap_discussion}/vote', 'beatmapsets.discussions.vote', 'put');
57 route_redirect('beatmap-discussions/{beatmap_discussion}/restore', 'beatmapsets.discussions.restore', 'post');
58 route_redirect('beatmap-discussions/{beatmap_discussion}/deny-kudosu', 'beatmapsets.discussions.deny-kudosu', 'post');
59 route_redirect('beatmap-discussions/{beatmap_discussion}/allow-kudosu', 'beatmapsets.discussions.allow-kudosu', 'post');
60
61 route_redirect('beatmap-discussion-posts/{beatmap_discussion_post}', 'beatmapsets.discussions.posts.destroy', 'delete');
62 route_redirect('beatmap-discussion-posts/{beatmap_discussion_post}', 'beatmapsets.discussions.posts.store', 'post');
63 route_redirect('beatmap-discussion-posts/{beatmap_discussion_post}', 'beatmapsets.discussions.posts.update', 'put');
64 route_redirect('beatmap-discussion-posts/{beatmap_discussion_post}/restore', 'beatmapsets.discussions.posts.restore', 'post');
65 route_redirect('beatmap-discussion-posts', 'beatmapsets.discussions.posts.index');
66 });
67
68 Route::group(['prefix' => 'beatmapsets', 'as' => 'beatmapsets.'], function () {
69 Route::resource('events', 'BeatmapsetEventsController', ['only' => ['index']]);
70 // keeping old link alive
71 route_redirect('watches', 'follows.index');
72 Route::resource('watches', 'BeatmapsetWatchesController', ['only' => ['update', 'destroy']]);
73
74 Route::group(['prefix' => 'discussions', 'as' => 'discussions.'], function () {
75 Route::put('{discussion}/vote', 'BeatmapDiscussionsController@vote')->name('vote');
76 Route::post('{discussion}/restore', 'BeatmapDiscussionsController@restore')->name('restore');
77 Route::post('{discussion}/deny-kudosu', 'BeatmapDiscussionsController@denyKudosu')->name('deny-kudosu');
78 Route::post('{discussion}/allow-kudosu', 'BeatmapDiscussionsController@allowKudosu')->name('allow-kudosu');
79
80 Route::group(['prefix' => 'posts', 'as' => 'posts.'], function () {
81 Route::post('{post}/restore', 'BeatmapDiscussionPostsController@restore')->name('restore');
82 });
83
84 Route::resource('posts', 'BeatmapDiscussionPostsController', ['only' => ['destroy', 'index', 'show', 'store', 'update']]);
85 Route::resource('votes', 'BeatmapsetDiscussionVotesController', ['only' => ['index']]);
86 });
87
88 Route::resource('discussions', 'BeatmapDiscussionsController', ['only' => ['destroy', 'index', 'show']]);
89
90 Route::group(['namespace' => 'Beatmapsets'], function () {
91 Route::apiResource('{beatmapset}/favourites', 'FavouritesController', ['only' => ['store']]);
92 });
93 });
94 Route::get('beatmapsets/search', 'BeatmapsetsController@search')->name('beatmapsets.search');
95 Route::get('beatmapsets/{beatmapset}/discussion/{beatmap?}/{mode?}/{filter?}', 'BeatmapsetsController@discussion')->name('beatmapsets.discussion');
96 Route::post('beatmapsets/{beatmapset}/discussion/review', 'BeatmapDiscussionsController@review')->name('beatmapsets.discussion.review');
97 Route::get('beatmapsets/{beatmapset}/discussion-last-update', 'BeatmapsetsController@discussionLastUpdate')->name('beatmapsets.discussion-last-update');
98 Route::post('beatmapsets/{beatmapset}/discussion-lock', 'BeatmapsetsController@discussionLock')->name('beatmapsets.discussion-lock');
99 Route::post('beatmapsets/{beatmapset}/discussion-unlock', 'BeatmapsetsController@discussionUnlock')->name('beatmapsets.discussion-unlock');
100 Route::get('beatmapsets/{beatmapset}/download', 'BeatmapsetsController@download')->name('beatmapsets.download');
101 Route::put('beatmapsets/{beatmapset}/love', 'BeatmapsetsController@love')->name('beatmapsets.love');
102 Route::delete('beatmapsets/{beatmapset}/love', 'BeatmapsetsController@removeFromLoved')->name('beatmapsets.remove-from-loved');
103 Route::put('beatmapsets/{beatmapset}/nominate', 'BeatmapsetsController@nominate')->name('beatmapsets.nominate');
104 Route::resource('beatmapsets', 'BeatmapsetsController', ['only' => ['destroy', 'index', 'show', 'update']]);
105
106 Route::group(['prefix' => 'scores', 'as' => 'scores.'], function () {
107 Route::get('{score}/download', 'ScoresController@download')->name('download');
108 Route::get('{rulesetOrScore}/{score}/download', 'ScoresController@download')->name('download-legacy');
109
110 Route::get('{rulesetOrScore}/{score?}', 'ScoresController@show')->name('show');
111 });
112
113 Route::delete('score-pins', 'ScorePinsController@destroy')->name('score-pins.destroy');
114 Route::put('score-pins', 'ScorePinsController@reorder')->name('score-pins.reorder');
115 Route::resource('score-pins', 'ScorePinsController', ['only' => ['store']]);
116
117 Route::resource('client-verifications', 'ClientVerificationsController', ['only' => ['create', 'store']]);
118
119 Route::resource('comments', 'CommentsController', ['except' => ['create', 'edit']]);
120 Route::post('comments/{comment}/pin', 'CommentsController@pinStore')->name('comments.pin');
121 Route::delete('comments/{comment}/pin', 'CommentsController@pinDestroy');
122 Route::post('comments/{comment}/restore', 'CommentsController@restore')->name('comments.restore');
123 Route::post('comments/{comment}/vote', 'CommentsController@voteStore')->name('comments.vote');
124 Route::delete('comments/{comment}/vote', 'CommentsController@voteDestroy');
125
126 Route::group(['prefix' => 'community'], function () {
127 Route::resource('contests', 'ContestsController', ['only' => ['index', 'show']]);
128 Route::get('contests/{contest}/judge', 'ContestsController@judge')->name('contests.judge');
129
130
131 Route::get('contest-entries/{contest_entry}/results', 'ContestEntriesController@judgeResults')->name('contest-entries.judge-results');
132 Route::put('contest-entries/{contest_entry}/judge-vote', 'ContestEntriesController@judgeVote')->name('contest-entries.judge-vote');
133 Route::put('contest-entries/{contest_entry}/vote', 'ContestEntriesController@vote')->name('contest-entries.vote');
134 Route::resource('contest-entries', 'ContestEntriesController', ['only' => ['store', 'destroy']]);
135
136 Route::post('livestreams/promote', 'LivestreamsController@promote')->name('livestreams.promote');
137 Route::resource('livestreams', 'LivestreamsController', ['only' => ['index']]);
138
139 Route::resource('matches', 'MatchesController', ['only' => ['show']]);
140
141 Route::post('tournaments/{tournament}/unregister', 'TournamentsController@unregister')->name('tournaments.unregister');
142 Route::post('tournaments/{tournament}/register', 'TournamentsController@register')->name('tournaments.register');
143 Route::resource('tournaments', 'TournamentsController', ['only' => ['index', 'show']]);
144
145 Route::group(['as' => 'forum.', 'namespace' => 'Forum'], function () {
146 Route::group(['prefix' => 'forums'], function () {
147 Route::resource('forum-covers', 'ForumCoversController', ['only' => ['store', 'update', 'destroy']]);
148
149 Route::get('posts/{post}/raw', 'PostsController@raw')->name('posts.raw');
150 Route::post('posts/{post}/restore', 'PostsController@restore')->name('posts.restore');
151 Route::resource('posts', 'PostsController', ['only' => ['destroy', 'edit', 'show', 'update']]);
152
153 Route::group(['as' => 'topics.', 'prefix' => 'topics/{topic}'], function () {
154 Route::resource('logs', 'TopicLogsController', ['only' => 'index']);
155
156 Route::post('edit-poll', 'TopicsController@editPollPost')->name('edit-poll.store');
157 Route::get('edit-poll', 'TopicsController@editPollGet')->name('edit-poll');
158
159 Route::post('issue-tag', 'TopicsController@issueTag')->name('issue-tag');
160 Route::post('lock', 'TopicsController@lock')->name('lock');
161 Route::post('move', 'TopicsController@move')->name('move');
162 Route::post('pin', 'TopicsController@pin')->name('pin');
163 Route::post('reply', 'TopicsController@reply')->name('reply');
164 Route::post('restore', 'TopicsController@restore')->name('restore');
165 Route::post('vote', 'TopicsController@vote')->name('vote');
166 Route::post('vote-feature', 'TopicsController@voteFeature')->name('vote-feature');
167 });
168
169 Route::post('topics/preview', 'TopicsController@preview')->name('preview');
170 Route::resource('topics', 'TopicsController', ['only' => ['create', 'destroy', 'show', 'store', 'update']]);
171
172 Route::resource('topic-covers', 'TopicCoversController', ['only' => ['store', 'update', 'destroy']]);
173
174 // keeping old link alive
175 route_redirect('topic-watches', 'follows.index');
176 Route::resource('topic-watches', 'TopicWatchesController', ['only' => ['update']]);
177 });
178
179 Route::post('forums/mark-as-read', 'ForumsController@markAsRead')->name('forums.mark-as-read');
180 Route::resource('forums', 'ForumsController', ['only' => ['index', 'show']]);
181 });
182
183 Route::group(['as' => 'chat.', 'prefix' => 'chat', 'namespace' => 'Chat'], function () {
184 Route::post('ack', 'ChatController@ack')->name('ack');
185 Route::post('new', 'ChatController@newConversation')->name('new');
186 Route::get('presence', 'ChatController@presence')->name('presence');
187 Route::get('updates', 'ChatController@updates')->name('updates');
188 Route::group(['as' => 'channels.', 'prefix' => 'channels'], function () {
189 Route::apiResource('{channel}/messages', 'Channels\MessagesController', ['only' => ['index', 'store']]);
190 Route::apiResource('{channel}/users', 'Channels\UsersController', ['only' => ['index']]);
191 Route::put('{channel}/users/{user}', 'ChannelsController@join')->name('join');
192 Route::delete('{channel}/users/{user}', 'ChannelsController@part')->name('part');
193 Route::put('{channel}/mark-as-read/{message}', 'ChannelsController@markAsRead')->name('mark-as-read');
194 });
195 Route::apiResource('channels', 'ChannelsController', ['only' => ['index', 'show', 'store']]);
196 });
197 Route::resource('chat', 'ChatController', ['only' => ['index']]);
198 });
199
200 Route::get('groups/history', 'GroupHistoryController@index')->name('group-history.index');
201 Route::resource('groups', 'GroupsController', ['only' => ['show']]);
202
203 Route::group(['prefix' => 'home'], function () {
204 Route::group(['as' => 'account.', 'prefix' => 'account'], function () {
205 Route::get('edit', 'AccountController@edit')->name('edit');
206 // Uploading file doesn't quite work with PUT/PATCH.
207 // Reference: https://bugs.php.net/bug.php?id=55815
208 // Note that hhvm behaves differently (the same as POST).
209 Route::post('avatar', 'AccountController@avatar')->name('avatar');
210 Route::put('country', 'AccountController@updateCountry')->name('country');
211 Route::post('cover', 'AccountController@cover')->name('cover');
212 Route::put('email', 'AccountController@updateEmail')->name('email');
213 Route::put('notification-options', 'AccountController@updateNotificationOptions')->name('notification-options');
214 Route::put('options', 'AccountController@updateOptions')->name('options');
215 Route::put('password', 'AccountController@updatePassword')->name('password');
216 Route::post('reissue-code', 'AccountController@reissueCode')->name('reissue-code');
217 Route::resource('sessions', 'Account\SessionsController', ['only' => ['destroy']]);
218 Route::get('verify', 'AccountController@verifyLink');
219 Route::post('verify', 'AccountController@verify')->name('verify');
220 Route::put('/', 'AccountController@update')->name('update');
221
222 Route::get('github-users/callback', 'Account\GithubUsersController@callback')->name('github-users.callback');
223 Route::resource('github-users', 'Account\GithubUsersController', ['only' => ['create']]);
224 Route::delete('github-users', 'Account\GithubUsersController@destroy')->name('github-users.destroy');
225 });
226
227 Route::get('quick-search', 'HomeController@quickSearch')->name('quick-search');
228 Route::get('search', 'HomeController@search')->name('search');
229 Route::post('bbcode-preview', 'HomeController@bbcodePreview')->name('bbcode-preview');
230 Route::get('changelog/{stream}/{build}', 'ChangelogController@build')->name('changelog.build');
231 Route::post('changelog/github', 'ChangelogController@github');
232 Route::resource('changelog', 'ChangelogController', ['only' => ['index', 'show']]);
233 Route::get('download', 'HomeController@getDownload')->name('download');
234 Route::post('set-locale', 'HomeController@setLocale')->name('set-locale');
235 Route::get('support', 'HomeController@supportTheGame')->name('support-the-game');
236 Route::get('testflight', 'HomeController@testflight')->name('testflight');
237
238 Route::get('password-reset', 'PasswordResetController@index')->name('password-reset');
239 Route::post('password-reset', 'PasswordResetController@create');
240 Route::put('password-reset', 'PasswordResetController@update');
241 Route::delete('password-reset', 'PasswordResetController@destroy');
242 Route::get('password-reset/reset', 'PasswordResetController@reset')->name('password-reset.reset');
243 Route::post('password-reset/resend-mail', 'PasswordResetController@resendMail')->name('password-reset.resend-mail');
244
245 Route::get('download-quota-check', 'HomeController@downloadQuotaCheck')->name('download-quota-check');
246
247 Route::resource('blocks', 'BlocksController', ['only' => ['store', 'destroy']]);
248 Route::resource('friends', 'FriendsController', ['only' => ['index', 'store', 'destroy']]);
249 Route::resource('news', 'NewsController', ['only' => ['index', 'show', 'store', 'update']]);
250
251 Route::get('messages/users/{user}', 'HomeController@messageUser')->name('messages.users.show');
252
253 Route::resource('follows', 'FollowsController', ['only' => ['store']]);
254 Route::get('follows/{subtype?}', 'FollowsController@index')->name('follows.index');
255 Route::delete('follows', 'FollowsController@destroy')->name('follows.destroy');
256 });
257
258 Route::resource('legacy-api-key', 'LegacyApiKeyController', ['only' => ['store']]);
259 Route::delete('legacy-api-key', 'LegacyApiKeyController@destroy')->name('legacy-api-key.destroy');
260
261 Route::resource('legacy-irc-key', 'LegacyIrcKeyController', ['only' => ['store']]);
262 Route::delete('legacy-irc-key', 'LegacyIrcKeyController@destroy')->name('legacy-irc-key.destroy');
263
264 Route::resource('notifications', 'NotificationsController', ['only' => ['index']]);
265 Route::get('notifications/endpoint', 'NotificationsController@endpoint')->name('notifications.endpoint');
266 Route::post('notifications/mark-read', 'NotificationsController@markRead')->name('notifications.mark-read');
267 Route::delete('notifications', 'NotificationsController@batchDestroy');
268
269 Route::get('legal/{locale?}/{path?}', 'LegalController@show')->name('legal');
270 Route::put('legal/{locale}/{path}', 'LegalController@update');
271
272 Route::group(['prefix' => 'multiplayer', 'as' => 'multiplayer.', 'namespace' => 'Multiplayer'], function () {
273 Route::resource('rooms', 'RoomsController', ['only' => ['show']]);
274 });
275
276 Route::get('news/{tumblrId}', 'NewsController@redirect');
277
278 Route::group(['as' => 'oauth.', 'prefix' => 'oauth', 'namespace' => 'OAuth'], function () {
279 Route::resource('authorized-clients', 'AuthorizedClientsController', ['only' => ['destroy']]);
280 Route::resource('clients', 'ClientsController', ['except' => ['create', 'edit', 'show']]);
281 Route::post('clients/{client}/reset-secret', 'ClientsController@resetSecret')->name('clients.reset-secret');
282 });
283
284 Route::get('rankings/kudosu', 'RankingController@kudosu')->name('rankings.kudosu');
285 Route::resource('rankings/daily-challenge', 'Ranking\DailyChallengeController', ['only' => ['index', 'show']]);
286 Route::get('rankings/{mode?}/{type?}', 'RankingController@index')->name('rankings');
287
288 Route::resource('reports', 'ReportsController', ['only' => ['store']]);
289
290 Route::resource('seasons', 'SeasonsController', ['only' => 'show']);
291 Route::get('seasons/{season}/rooms', 'SeasonsController@rooms')->name('seasons.rooms');
292
293 Route::post('session', 'SessionsController@store')->name('login');
294 Route::delete('session', 'SessionsController@destroy')->name('logout');
295
296 Route::post('user-cover-presets/batch-activate', 'UserCoverPresetsController@batchActivate')->name('user-cover-presets.batch-activate');
297 Route::resource('user-cover-presets', 'UserCoverPresetsController', ['only' => ['index', 'store', 'update']]);
298
299 Route::group(['as' => 'teams.', 'prefix' => 'teams/{team}'], function () {
300 Route::post('part', 'TeamsController@part')->name('part');
301 Route::resource('members', 'Teams\MembersController', ['only' => ['destroy', 'index']]);
302 });
303 Route::resource('teams', 'TeamsController', ['only' => ['destroy', 'edit', 'show', 'update']]);
304
305 Route::post('users/check-username-availability', 'UsersController@checkUsernameAvailability')->name('users.check-username-availability');
306 Route::get('users/lookup', 'Users\LookupController@index')->name('users.lookup');
307 Route::get('users/disabled', 'UsersController@disabled')->name('users.disabled');
308 Route::get('users/create', 'UsersController@create')->name('users.create');
309 Route::post('users/store-web', 'UsersController@storeWeb')->name('users.store-web');
310
311 Route::group(['as' => 'users.', 'prefix' => 'users/{user}'], function () {
312 Route::get('extra-pages/{page}', 'UsersController@extraPages')->name('extra-page');
313 Route::put('page', 'UsersController@updatePage')->name('page');
314 Route::group(['namespace' => 'Users'], function () {
315 Route::resource('{typeGroup}', 'MultiplayerController', ['only' => 'index'])->where(['typeGroup' => 'multiplayer|playlists|realtime'])->names('multiplayer');
316
317 Route::group(['as' => 'modding.', 'prefix' => 'modding'], function () {
318 Route::get('/', 'ModdingHistoryController@index')->name('index');
319 Route::get('/posts', 'ModdingHistoryController@posts')->name('posts');
320 Route::get('/votes-given', 'ModdingHistoryController@votesGiven')->name('votes-given');
321 Route::get('/votes-received', 'ModdingHistoryController@votesReceived')->name('votes-received');
322 });
323 });
324
325 Route::get('kudosu', 'UsersController@kudosu')->name('kudosu');
326 Route::get('recent_activity', 'UsersController@recentActivity')->name('recent-activity');
327 Route::get('scores/{type}', 'UsersController@scores')->name('scores');
328 Route::get('beatmapsets/{type}', 'UsersController@beatmapsets')->name('beatmapsets');
329 });
330
331 Route::get('users/{user}/posts', 'UsersController@posts')->name('users.posts');
332 Route::get('users/{user}/{mode?}', 'UsersController@show')->name('users.show');
333 Route::resource('users', 'UsersController', ['only' => ['store']]);
334
335 Route::get('wiki/{locale}/Sitemap', 'WikiController@sitemap')->name('wiki.sitemap');
336 Route::get('wiki/{locale?}/{path?}', 'WikiController@show')->name('wiki.show')->where('path', '.+');
337 Route::put('wiki/{locale}/{path}', 'WikiController@update')->where('path', '.+');
338 Route::get('wiki-suggestions', 'WikiController@suggestions')->name('wiki-suggestions');
339
340 // FIXME: someone split this crap up into proper controllers
341 Route::group(['as' => 'store.', 'prefix' => 'store'], function () {
342 route_redirect('/', 'store.products.index');
343
344 Route::get('listing', 'StoreController@getListing')->name('products.index');
345 Route::get('invoice/{invoice}', 'StoreController@getInvoice')->name('invoice.show');
346
347 Route::group(['namespace' => 'Store'], function () {
348 Route::post('products/{product}/notification-request', 'NotificationRequestsController@store')->name('notification-request');
349 Route::delete('products/{product}/notification-request', 'NotificationRequestsController@destroy');
350
351 // Store splitting starts here
352 Route::get('cart', 'CartController@show')->name('cart.show');
353 Route::delete('cart', 'CartController@empty')->name('cart.empty');
354 Route::resource('cart', 'CartController', ['only' => ['store']]);
355
356 Route::resource('checkout', 'CheckoutController', ['only' => ['show', 'store']]);
357
358 Route::resource('orders', 'OrdersController', ['only' => ['destroy', 'index']]);
359
360 route_redirect('product/{product}', 'store.products.show');
361 Route::resource('products', 'ProductsController', ['only' => ['show']]);
362 });
363 });
364
365 Route::group(['as' => 'payments.', 'prefix' => 'payments', 'namespace' => 'Payments'], function () {
366 Route::group(['as' => 'paypal.', 'prefix' => 'paypal'], function () {
367 Route::get('approved', 'PaypalController@approved')->name('approved');
368 Route::get('declined', 'PaypalController@declined')->name('declined');
369 Route::post('create', 'PaypalController@create')->name('create');
370 Route::post('ipn', 'PaypalController@ipn')->name('ipn');
371 });
372
373 Route::group(['as' => 'xsolla.', 'prefix' => 'xsolla'], function () {
374 Route::get('completed', 'XsollaController@completed')->name('completed');
375 Route::post('token', 'XsollaController@token')->name('token');
376 Route::post('callback', 'XsollaController@callback')->name('callback');
377 });
378
379 Route::group(['as' => 'shopify.', 'prefix' => 'shopify'], function () {
380 Route::post('callback', 'ShopifyController@callback')->name('callback');
381 });
382 });
383
384 // TODO: update to redirect to root later
385 Route::get('/home', 'HomeController@index');
386
387 Route::get('/', 'HomeController@index')->name('home');
388
389 // redirects go here
390 route_redirect('forum/p/{post}', 'forum.posts.show');
391 route_redirect('po/{post}', 'forum.posts.show:');
392 route_redirect('forum/t/{topic}', 'forum.topics.show');
393 route_redirect('forum/{forum}', 'forum.forums.show');
394 // redirects to beatmapset anyways so there's no point
395 // in having an another redirect on top of that
396 Route::get('b/{beatmap}', 'BeatmapsController@show')->name('redirect:beatmaps.show');
397 route_redirect('g/{group}', 'groups.show');
398 route_redirect('s/{beatmapset}', 'beatmapsets.show');
399 route_redirect('u/{user}', 'users.show');
400 route_redirect('forum', 'forum.forums.index');
401 route_redirect('mp/{match}', 'matches.show');
402 route_redirect('help/wiki/{path?}', 'wiki.show')->where('path', '.+');
403});
404
405// API
406// require-scopes is not in the api group at the moment to reduce the number of things that need immediate fixing.
407// There's also a different group which skips throttle middleware.
408Route::group(['as' => 'api.', 'prefix' => 'api', 'middleware' => ['api', ThrottleRequests::getApiThrottle(), 'require-scopes']], function () {
409 Route::group(['prefix' => 'v2'], function () {
410 Route::group(['middleware' => ['require-scopes:any']], function () {
411 Route::post('session/verify', 'AccountController@verify')->name('verify');
412 Route::post('session/verify/reissue', 'AccountController@reissueCode')->name('verify.reissue');
413 });
414
415 Route::group(['as' => 'beatmaps.', 'prefix' => 'beatmaps'], function () {
416 Route::get('lookup', 'BeatmapsController@lookup')->name('lookup');
417
418 Route::apiResource('packs', 'BeatmapPacksController', ['only' => ['index', 'show']]);
419
420 Route::group(['prefix' => '{beatmap}'], function () {
421 Route::get('scores/users/{user}', 'BeatmapsController@userScore')->name('user.score');
422 Route::get('scores/users/{user}/all', 'BeatmapsController@userScoreAll')->name('user.scores');
423 Route::get('scores', 'BeatmapsController@scores')->name('scores');
424 Route::get('solo-scores', 'BeatmapsController@soloScores')->name('solo-scores');
425
426 Route::group(['as' => 'solo.', 'prefix' => 'solo'], function () {
427 Route::post('scores', 'ScoreTokensController@store')->name('score-tokens.store');
428
429 Route::group(['namespace' => 'Solo'], function () {
430 Route::put('scores/{token}', 'ScoresController@store')->name('scores.store');
431 });
432 });
433
434 Route::apiResource('tags', 'BeatmapTagsController', ['only' => ['store', 'destroy']]);
435 });
436 });
437
438 Route::resource('beatmaps', 'BeatmapsController', ['only' => ['index', 'show']]);
439 Route::post('beatmaps/{beatmap}/attributes', 'BeatmapsController@attributes')->name('beatmaps.attributes');
440
441 Route::group(['as' => 'beatmapsets.', 'prefix' => 'beatmapsets'], function () {
442 Route::apiResource('events', 'BeatmapsetEventsController', ['only' => ['index']]);
443
444 Route::group(['as' => 'discussions.', 'prefix' => 'discussions'], function () {
445 Route::apiResource('posts', 'BeatmapDiscussionPostsController', ['only' => ['index']]);
446 Route::apiResource('votes', 'BeatmapsetDiscussionVotesController', ['only' => ['index']]);
447 });
448
449 Route::resource('discussions', 'BeatmapDiscussionsController', ['only' => ['index']]);
450
451 // TODO: move other beatmapset routes here
452 Route::group(['namespace' => 'Beatmapsets'], function () {
453 Route::apiResource('{beatmapset}/favourites', 'FavouritesController', ['only' => ['store']]);
454 });
455 });
456
457 Route::resource('blocks', 'BlocksController', ['only' => ['destroy', 'index', 'store']]);
458
459 Route::apiResource('comments', 'CommentsController');
460 Route::post('comments/{comment}/vote', 'CommentsController@voteStore')->name('comments.vote');
461 Route::delete('comments/{comment}/vote', 'CommentsController@voteDestroy');
462
463 Route::group(['as' => 'chat.', 'prefix' => 'chat', 'namespace' => 'Chat'], function () {
464 Route::post('ack', 'ChatController@ack')->name('ack');
465 Route::post('new', 'ChatController@newConversation')->name('new');
466 Route::get('updates', 'ChatController@updates')->name('updates');
467 Route::get('presence', 'ChatController@presence')->name('presence');
468 Route::group(['as' => 'channels.', 'prefix' => 'channels'], function () {
469 Route::apiResource('{channel}/messages', 'Channels\MessagesController', ['only' => ['index', 'store']]);
470 Route::put('{channel}/users/{user}', 'ChannelsController@join')->name('join');
471 Route::delete('{channel}/users/{user}', 'ChannelsController@part')->name('part');
472 Route::put('{channel}/mark-as-read/{message}', 'ChannelsController@markAsRead')->name('mark-as-read');
473 });
474 Route::apiResource('channels', 'ChannelsController', ['only' => ['index', 'show', 'store']]);
475 });
476
477 Route::get('changelog/{stream}/{build}', 'ChangelogController@build')->name('changelog.build');
478 Route::resource('changelog', 'ChangelogController', ['only' => ['index', 'show']]);
479
480 Route::apiResource('events', 'EventsController', ['only' => ['index']]);
481
482 Route::group(['as' => 'forum.', 'namespace' => 'Forum'], function () {
483 Route::group(['prefix' => 'forums'], function () {
484 Route::post('topics/{topic}/reply', 'TopicsController@reply')->name('topics.reply');
485 Route::resource('topics', 'TopicsController', ['only' => ['show', 'store', 'update']]);
486 Route::resource('posts', 'PostsController', ['only' => ['update']]);
487 });
488 });
489 Route::resource('matches', 'MatchesController', ['only' => ['index', 'show']]);
490
491 Route::resource('reports', 'ReportsController', ['only' => ['store']]);
492
493 Route::group(['as' => 'rooms.', 'prefix' => 'rooms'], function () {
494 Route::put('{room}/users/{user}', 'Multiplayer\RoomsController@join')->name('join');
495 Route::delete('{room}/users/{user}', 'Multiplayer\RoomsController@part')->name('part');
496 Route::get('{room}/leaderboard', 'Multiplayer\RoomsController@leaderboard');
497 Route::group(['as' => 'playlist.', 'prefix' => '{room}/playlist'], function () {
498 Route::get('{playlist}/scores/users/{user}', 'Multiplayer\Rooms\Playlist\ScoresController@showUser');
499 Route::apiResource('{playlist}/scores', 'Multiplayer\Rooms\Playlist\ScoresController', ['only' => ['index', 'show', 'store', 'update']]);
500 });
501 });
502
503 Route::apiResource('rooms', 'Multiplayer\RoomsController', ['only' => ['index', 'show', 'store', 'destroy']]);
504
505 Route::apiResource('seasonal-backgrounds', 'SeasonalBackgroundsController', ['only' => ['index']]);
506
507 Route::group(['prefix' => 'scores', 'as' => 'scores.'], function () {
508 Route::get('{score}/download', 'ScoresController@download')->middleware(ThrottleRequests::getApiThrottle('scores_download'))->name('download');
509 Route::get('{rulesetOrScore}/{score}/download', 'ScoresController@download')->middleware(ThrottleRequests::getApiThrottle('scores_download'))->name('download-legacy');
510
511 Route::get('{rulesetOrScore}/{score?}', 'ScoresController@show')->name('show');
512
513 Route::get('/', 'ScoresController@index');
514 });
515
516 // Beatmapsets
517 // GET /api/v2/beatmapsets/search/:filters
518 Route::get('beatmapsets/search', 'BeatmapsetsController@search');
519 // GET /api/v2/beatmapsets/lookup
520 Route::get('beatmapsets/lookup', 'BeatmapsetsController@lookup');
521 // GET /api/v2/beatmapsets/:beatmapset_id
522 Route::resource('beatmapsets', 'BeatmapsetsController', ['only' => ['show']]);
523
524 // Friends
525 Route::resource('friends', 'FriendsController', ['only' => ['index', 'store', 'destroy']]);
526
527 // GET /api/v2/me/download-quota-check
528 Route::get('me/download-quota-check', 'HomeController@downloadQuotaCheck')->name('download-quota-check');
529 // GET /api/v2/me
530 Route::get('me/{mode?}', 'UsersController@me')->name('me');
531
532 Route::delete('oauth/tokens/current', 'OAuth\TokensController@destroyCurrent')->name('oauth.tokens.current');
533
534 Route::apiResource('news', 'NewsController', ['only' => ['index', 'show']]);
535
536 // Notifications
537 // GET /api/v2/notifications
538 Route::resource('notifications', 'NotificationsController', ['only' => ['index']]);
539 // POST /api/v2/notifications/mark-read
540 Route::post('notifications/mark-read', 'NotificationsController@markRead')->name('notifications.mark-read');
541
542 Route::get('rankings/kudosu', 'RankingController@kudosu');
543 // GET /api/v2/rankings/:mode/:type
544 Route::get('rankings/{mode}/{type}', 'RankingController@index')->name('rankings');
545 Route::resource('spotlights', 'SpotlightsController', ['only' => ['index']]);
546
547 Route::get('search', 'HomeController@search');
548
549 Route::get('users/lookup', 'Users\LookupController@index')->name('users.lookup');
550 // GET /api/v2/users/:user_id/kudosu
551 Route::get('users/{user}/kudosu', 'UsersController@kudosu');
552 // GET /api/v2/users/:user_id/scores/:type [best, firsts, recent]
553 Route::get('users/{user}/scores/{type}', 'UsersController@scores');
554 // GET /api/v2/users/:user_id/beatmapsets/:type [most_played, favourite, ranked, pending, graveyard]
555 Route::get('users/{user}/beatmapsets/{type}', 'UsersController@beatmapsets');
556 // GET /api/v2/users/:user_id/recent_activity
557 Route::get('users/{user}/recent_activity', 'UsersController@recentActivity');
558 // GET /api/v2/users/:user_id/:mode [osu, taiko, fruits, mania]
559 Route::get('users/{user}/{mode?}', 'UsersController@show')->name('users.show');
560 Route::resource('users', 'UsersController', ['only' => ['index']]);
561
562 Route::get('wiki/{locale}/{path}', 'WikiController@show')->name('wiki.show')->where('path', '.+');
563
564 // Tags
565 Route::apiResource('tags', 'TagsController', ['only' => ['index']]);
566 });
567});
568
569// Duplicate of the other api group but without throttle.
570Route::group(['as' => 'api.', 'prefix' => 'api/v2', 'middleware' => ['api', 'require-scopes']], function () {
571 // GET /api/v2/beatmapsets/:beatmapset/download
572 Route::get('beatmapsets/{beatmapset}/download', 'BeatmapsetsController@download');
573});
574
575// Callbacks for legacy systems to interact with
576Route::group(['prefix' => '_lio', 'middleware' => 'lio', 'as' => 'interop.'], function () {
577 Route::post('generate-notification', 'LegacyInterOpController@generateNotification');
578 Route::post('index-beatmapset/{beatmapset}', 'LegacyInterOpController@indexBeatmapset');
579 Route::post('/refresh-beatmapset-cache/{beatmapset}', 'LegacyInterOpController@refreshBeatmapsetCache');
580 Route::post('user-send-message', 'LegacyInterOpController@userSendMessage');
581 Route::post('user-batch-mark-channel-as-read', 'LegacyInterOpController@userBatchMarkChannelAsRead');
582 Route::post('user-batch-send-message', 'LegacyInterOpController@userBatchSendMessage');
583 Route::delete('/user-sessions/{user}', 'LegacyInterOpController@userSessionsDestroy');
584 Route::post('user-index/{user}', 'LegacyInterOpController@userIndex');
585 Route::post('user-recalculate-ranked-scores/{user}', 'LegacyInterOpController@userRecalculateRankedScores');
586 Route::get('/news', 'LegacyInterOpController@news');
587
588 Route::group(['namespace' => 'InterOp'], function () {
589 Route::post('artist-tracks/reindex-all', 'ArtistTracksController@reindexAll');
590
591 Route::group(['as' => 'beatmapsets.', 'prefix' => 'beatmapsets'], function () {
592 Route::group(['prefix' => '{beatmapset}'], function () {
593 Route::post('broadcast-new', 'BeatmapsetsController@broadcastNew')->name('broadcast-new');
594 Route::post('broadcast-revive', 'BeatmapsetsController@broadcastRevive')->name('broadcast-revive');
595 Route::post('broadcast-update', 'BeatmapsetsController@broadcastUpdate')->name('broadcast-update');
596 Route::post('disqualify', 'BeatmapsetsController@disqualify')->name('disqualify');
597 });
598 });
599 Route::resource('beatmapsets', 'BeatmapsetsController', ['only' => ['destroy']]);
600
601 Route::group(['as' => 'indexing.', 'prefix' => 'indexing'], function () {
602 Route::apiResource('bulk', 'Indexing\BulkController', ['only' => ['store']]);
603 });
604
605 Route::group(['as' => 'multiplayer.', 'namespace' => 'Multiplayer', 'prefix' => 'multiplayer'], function () {
606 Route::put('rooms/{room}/users/{user}', 'RoomsController@join')->name('rooms.join');
607 Route::delete('rooms/{room}/users/{user}', 'RoomsController@part')->name('rooms.part');
608 Route::apiResource('rooms', 'RoomsController', ['only' => ['store']]);
609 });
610
611 Route::post('user-achievement/{user}/{achievement}/{beatmap?}', 'UsersController@achievement')->name('users.achievement');
612
613 Route::group(['as' => 'user-group.'], function () {
614 Route::put('users/{user}/groups/{group}', 'UserGroupsController@update')->name('update');
615 Route::delete('users/{user}/groups/{group}', 'UserGroupsController@destroy')->name('destroy');
616 Route::post('users/{user}/groups/{group}/default', 'UserGroupsController@setDefault')->name('set-default');
617 });
618
619 Route::apiResource('users', 'UsersController', ['only' => ['store']]);
620 });
621});
622
623Route::get('opensearch.xml', 'HomeController@opensearch');
624Route::any('{catchall}', 'FallbackController@index')->where('catchall', '.*')->fallback();