the browser-facing portion of osu!
at master 6.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\Exceptions\InvariantException; 9use App\Models\Contest; 10use App\Models\ContestEntry; 11use App\Models\UserContestEntry; 12use App\Transformers\ContestTransformer; 13use Auth; 14use Ds\Set; 15use Illuminate\Support\Facades\DB; 16use Request; 17 18class ContestEntriesController extends Controller 19{ 20 public function judgeResults($id) 21 { 22 $entry = ContestEntry::with('contest')->findOrFail($id); 23 24 abort_if(!$entry->contest->isJudged() || !$entry->contest->show_votes, 404); 25 26 $entry->load([ 27 'contest.entries', 28 'contest.scoringCategories', 29 'judgeVotes.scores', 30 'judgeVotes.user', 31 'user', 32 ])->loadSum('scores', 'value'); 33 34 $contest = $entry->contest 35 ->loadCount('judges') 36 ->loadSum('scoringCategories', 'max_value'); 37 38 $contestJson = json_item( 39 $contest, 40 new ContestTransformer(), 41 [ 42 'max_judging_score', 43 'max_total_score', 44 'scoring_categories', 45 ], 46 ); 47 48 $entryJson = json_item($entry, 'ContestEntry', [ 49 'judge_votes.scores', 50 'judge_votes.total_score', 51 'judge_votes.user', 52 'results', 53 'user', 54 ]); 55 56 $entriesJson = json_collection($entry->contest->entries, 'ContestEntry'); 57 58 return ext_view('contest_entries.judge-results', [ 59 'contestJson' => $contestJson, 60 'entryJson' => $entryJson, 61 'entriesJson' => $entriesJson, 62 ]); 63 } 64 65 public function judgeVote($id) 66 { 67 $entry = ContestEntry::with('contest.scoringCategories')->findOrFail($id); 68 69 priv_check('ContestJudge', $entry->contest)->ensureCan(); 70 71 $params = get_params(request()->all(), null, [ 72 'scores:array', 73 'comment', 74 ], ['null_missing' => true]); 75 76 $scoresByCategoryId = collect($params['scores']) 77 ->keyBy('contest_scoring_category_id'); 78 79 $expectedCategoryIds = new Set($entry->contest->scoringCategories->pluck('id')); 80 $givenCategoryIds = new Set($scoresByCategoryId->keys()); 81 82 if ($expectedCategoryIds->diff($givenCategoryIds)->count() > 0) { 83 throw new InvariantException(osu_trans('contest.judge.validation.missing_score')); 84 } 85 86 DB::transaction(function () use ($entry, $params, $scoresByCategoryId) { 87 $vote = $entry->judgeVotes()->firstOrNew(['user_id' => Auth::user()->getKey()]); 88 $vote->fill(['comment' => $params['comment']])->save(); 89 90 foreach ($entry->contest->scoringCategories as $category) { 91 $score = $scoresByCategoryId[$category->getKey()]; 92 $value = \Number::clamp($score['value'], 0, $category->max_value); 93 94 $vote->scores()->firstOrNew([ 95 'contest_judge_vote_id' => $vote->getKey(), 96 'contest_scoring_category_id' => $category->getKey(), 97 ])->fill(['value' => $value])->save(); 98 } 99 }); 100 101 $updatedEntry = $entry->refresh()->load('judgeVotes.scores'); 102 103 return json_item($updatedEntry, 'ContestEntry', ['current_user_judge_vote.scores']); 104 } 105 106 public function vote($id) 107 { 108 $user = Auth::user(); 109 $entry = ContestEntry::findOrFail($id); 110 $contest = Contest::with('entries')->with('entries.contest')->findOrFail($entry->contest_id); 111 112 if ($contest->isJudged()) { 113 throw new InvariantException(osu_trans('contest.judge.validation.contest_vote_judged')); 114 } 115 116 priv_check('ContestVote', $contest)->ensureCan(); 117 118 $contest->vote($user, $entry); 119 120 return $contest->defaultJson($user); 121 } 122 123 public function store() 124 { 125 if (Request::hasFile('entry') !== true) { 126 abort(422, 'No file uploaded'); 127 } 128 129 $user = Auth::user(); 130 $contest = Contest::findOrFail(Request::input('contest_id')); 131 $file = Request::file('entry'); 132 133 priv_check('ContestEntryStore', $contest)->ensureCan(); 134 135 $allowedExtensions = []; 136 $maxFilesize = 0; 137 switch ($contest->type) { 138 case 'art': 139 $allowedExtensions[] = 'jpg'; 140 $allowedExtensions[] = 'jpeg'; 141 $allowedExtensions[] = 'png'; 142 $maxFilesize = 8 * 1024 * 1024; 143 break; 144 case 'beatmap': 145 $allowedExtensions[] = 'osu'; 146 $allowedExtensions[] = 'osz'; 147 $maxFilesize = 32 * 1024 * 1024; 148 break; 149 case 'music': 150 $allowedExtensions[] = 'mp3'; 151 $maxFilesize = 16 * 1024 * 1024; 152 break; 153 } 154 155 if (!in_array(strtolower($file->getClientOriginalExtension()), $allowedExtensions, true)) { 156 abort( 157 422, 158 'Files for this contest must have one of the following extensions: '.implode(', ', $allowedExtensions) 159 ); 160 } 161 162 if ($file->getSize() > $maxFilesize) { 163 abort(413, 'File exceeds max size'); 164 } 165 166 if ($contest->type === 'art' && !is_null($contest->getForcedWidth()) && !is_null($contest->getForcedHeight())) { 167 if (empty($file->getContent())) { 168 abort(422, 'File must not be empty'); 169 } 170 171 [$width, $height] = read_image_properties_from_string($file->getContent()) ?? [null, null]; 172 173 if ($contest->getForcedWidth() !== $width || $contest->getForcedHeight() !== $height) { 174 abort( 175 422, 176 "Images for this contest must be {$contest->getForcedWidth()}x{$contest->getForcedHeight()}" 177 ); 178 } 179 } 180 181 UserContestEntry::upload($file, $user, $contest); 182 183 return $contest->userEntries($user); 184 } 185 186 public function destroy($id) 187 { 188 $user = Auth::user(); 189 $entry = UserContestEntry::where(['user_id' => $user->user_id])->findOrFail($id); 190 $contest = Contest::findOrFail($entry->contest_id); 191 192 priv_check('ContestEntryDestroy', $entry)->ensureCan(); 193 194 $entry->delete(); 195 196 return $contest->userEntries($user); 197 } 198}