From: Daniel Karbach Date: Wed, 19 Nov 2025 15:21:58 +0000 (+0100) Subject: track seed downloads X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=70cbfb92d2d57e54489fbf285b6c506207eb35fc;p=alttp.git track seed downloads --- diff --git a/app/Http/Controllers/RoundController.php b/app/Http/Controllers/RoundController.php index 589ab8d..1459f52 100644 --- a/app/Http/Controllers/RoundController.php +++ b/app/Http/Controllers/RoundController.php @@ -117,6 +117,20 @@ class RoundController extends Controller return $round->toJson(); } + public function getSeed(Request $request, Round $round) { + $this->authorize('getSeed', $round); + + if ($request->user()) { + Protocol::roundSeedGet( + $round->tournament, + $round, + $request->user(), + ); + } + + return redirect($round->seed); + } + public function uploadSeed(Request $request, Round $round) { $this->authorize('update', $round); diff --git a/app/Models/Protocol.php b/app/Models/Protocol.php index 73dca93..4eddf71 100644 --- a/app/Models/Protocol.php +++ b/app/Models/Protocol.php @@ -132,6 +132,19 @@ class Protocol extends Model ProtocolAdded::dispatch($protocol); } + public static function roundSeedGet(Tournament $tournament, Round $round, User $user) { + $protocol = static::create([ + 'tournament_id' => $tournament->id, + 'user_id' => $user->id, + 'type' => 'round.getseed', + 'details' => [ + 'tournament' => static::tournamentMemo($tournament), + 'round' => static::roundMemo($round), + ], + ]); + ProtocolAdded::dispatch($protocol); + } + public static function roundSeedSet(Tournament $tournament, Round $round, User $user) { $protocol = static::create([ 'tournament_id' => $tournament->id, diff --git a/app/Models/Round.php b/app/Models/Round.php index 4808322..0519577 100644 --- a/app/Models/Round.php +++ b/app/Models/Round.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Broadcasting\Channel; use Illuminate\Database\Eloquent\BroadcastsEvents; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -107,6 +108,12 @@ class Round extends Model } } + protected function hasSeed(): Attribute { + return Attribute::make( + get: fn () => !!$this->seed, + ); + } + public function results() { return $this->hasMany(Result::class); @@ -120,6 +127,9 @@ class Round extends Model return $this->belongsTo(Tournament::class); } + protected $appends = [ + 'has_seed', + ]; protected $casts = [ 'code' => 'array', diff --git a/app/Policies/RoundPolicy.php b/app/Policies/RoundPolicy.php index 7f53e0f..0d928dd 100644 --- a/app/Policies/RoundPolicy.php +++ b/app/Policies/RoundPolicy.php @@ -132,6 +132,24 @@ class RoundPolicy return !$round->locked && ($user->isRunner($round->tournament) || $user->isTournamentAdmin($round->tournament)); } + /** + * Determine whether the user can get the seed for this round. + * + * @param \App\Models\User $user + * @param \App\Models\Round $round + * @return \Illuminate\Auth\Access\Response|bool + */ + public function getSeed(User $user = null, Round $round) + { + if ($round->locked) { + return true; + } + if (!$round->tournament->require_auth) { + return true; + } + return !!$user; + } + /** * Determine whether the user can lock this round. * diff --git a/database/migrations/2025_11_19_150210_tournament_require_auth.php b/database/migrations/2025_11_19_150210_tournament_require_auth.php new file mode 100644 index 0000000..93afc54 --- /dev/null +++ b/database/migrations/2025_11_19_150210_tournament_require_auth.php @@ -0,0 +1,29 @@ +boolean('require_auth')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('tournaments', function (Blueprint $table) { + $table->dropColumn('require_auth'); + }); + } + +}; diff --git a/resources/js/components/common/Icon.jsx b/resources/js/components/common/Icon.jsx index 35ecd1a..e5dbb9d 100644 --- a/resources/js/components/common/Icon.jsx +++ b/resources/js/components/common/Icon.jsx @@ -60,6 +60,7 @@ Icon.CHART = makePreset('ChartIcon', 'chart-line'); Icon.CROSSHAIRS = makePreset('CrosshairsIcon', 'crosshairs'); Icon.DELETE = makePreset('DeleteIcon', 'user-xmark'); Icon.DISCORD = makePreset('DiscordIcon', ['fab', 'discord']); +Icon.DOWNLOAD = makePreset('DownloadIcon', 'download'); Icon.EDIT = makePreset('EditIcon', 'edit'); Icon.ERROR = makePreset('ErrorIcon', 'triangle-exclamation'); Icon.FILTER = makePreset('FilterIcon', 'filter'); diff --git a/resources/js/components/protocol/Item.jsx b/resources/js/components/protocol/Item.jsx index 17dd744..eb875df 100644 --- a/resources/js/components/protocol/Item.jsx +++ b/resources/js/components/protocol/Item.jsx @@ -69,6 +69,7 @@ const getEntryDescription = (entry, t) => { case 'round.create': case 'round.delete': case 'round.edit': + case 'round.getseed': case 'round.lock': case 'round.seed': case 'round.unlock': @@ -102,6 +103,8 @@ const getEntryIcon = entry => { return ; case 'round.delete': return ; + case 'round.getseed': + return ; case 'round.lock': case 'tournament.close': case 'tournament.lock': diff --git a/resources/js/components/rounds/SeedButton.jsx b/resources/js/components/rounds/SeedButton.jsx index 59bc3ba..18a3959 100644 --- a/resources/js/components/rounds/SeedButton.jsx +++ b/resources/js/components/rounds/SeedButton.jsx @@ -4,7 +4,7 @@ import { Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import SeedDialog from './SeedDialog'; -import { maySetSeed } from '../../helpers/permissions'; +import { mayGetSeed, maySetSeed } from '../../helpers/permissions'; import { useUser } from '../../hooks/user'; const SeedButton = ({ round, tournament }) => { @@ -13,9 +13,12 @@ const SeedButton = ({ round, tournament }) => { const { t } = useTranslation(); const { user } = useUser(); - if (round.seed) { + if (round.has_seed) { + if (!mayGetSeed(user, tournament, round)) { + return t('rounds.loginForSeed'); + } return <> - {round.spoiler ? @@ -47,6 +50,8 @@ const SeedButton = ({ round, tournament }) => { SeedButton.propTypes = { round: PropTypes.shape({ + has_seed: PropTypes.bool, + id: PropTypes.number, seed: PropTypes.string, spoiler: PropTypes.string, }), diff --git a/resources/js/helpers/permissions.js b/resources/js/helpers/permissions.js index df61b30..6a4ca0d 100644 --- a/resources/js/helpers/permissions.js +++ b/resources/js/helpers/permissions.js @@ -170,6 +170,9 @@ export const mayEditRound = (user, tournament) => export const mayLockRound = (user, tournament) => !tournament.locked && isTournamentAdmin(user, tournament); +export const mayGetSeed = (user, tournament, round) => + round.locked || !tournament.require_auth || !!user; + export const maySetSeed = (user, tournament, round) => !round.locked && (isRunner(user, tournament) || isTournamentAdmin(user, tournament)); diff --git a/resources/js/i18n/de.js b/resources/js/i18n/de.js index 738e0a3..461aa50 100644 --- a/resources/js/i18n/de.js +++ b/resources/js/i18n/de.js @@ -564,6 +564,7 @@ export default { create: 'Runde #{{number}} hinzugefügt', delete: 'Runde #{{number}} gelöscht', edit: 'Runde #{{number}} bearbeitet', + getseed: 'Seed für Runde #{{number}} bezogen', lock: 'Runde #{{number}} gesperrt', seed: 'Seed für Runde #{{number}} eingetragen', unlock: 'Runde #{{number}} entsperrt', @@ -627,6 +628,7 @@ export default { lockError: 'Fehler beim Sperren', lockIncompleteWarning: 'Achtung: Noch nicht alle Runner haben ihr Ergebnis für diese Runde eingereicht!', lockSuccess: 'Runde gesperrt', + loginForSeed: 'Seed verfügbar nach Login', rolled_by: 'Gerollt von', rolledBy: 'Gerollt von {{name}}', seed: 'Seed', diff --git a/resources/js/i18n/en.js b/resources/js/i18n/en.js index 13c41c7..0cba64a 100644 --- a/resources/js/i18n/en.js +++ b/resources/js/i18n/en.js @@ -564,6 +564,7 @@ export default { create: 'Added round #{{number}}', delete: 'Deleted round #{{number}}', edit: 'Edited round #{{number}}', + getseed: 'Got seed for round #{{number}}', lock: 'Round #{{number}} locked', seed: 'Set seed for round #{{number}}', unlock: 'Round #{{number}} unlocked', @@ -627,6 +628,7 @@ export default { lockError: 'Error locking round', lockIncompleteWarning: 'Warning: Not all runners have submitted their results for this round yet!', lockSuccess: 'Round locked', + loginForSeed: 'Seed available after login', rolled_by: 'Rolled by', rolledBy: 'Rolled by {{name}}', seed: 'Seed', diff --git a/routes/web.php b/routes/web.php index 0e01006..da56bbb 100644 --- a/routes/web.php +++ b/routes/web.php @@ -36,6 +36,8 @@ Route::get('/modes/{name}', function ($name) { return app()->call('App\Http\Controllers\TechniqueController@web', ['type' => 'mode', 'name' => $name]); }); +Route::get('/rounds/{round}/get-seed', 'App\Http\Controllers\RoundController@getSeed'); + Route::get('/rulesets/{name}', function ($name) { return app()->call('App\Http\Controllers\TechniqueController@web', ['type' => 'ruleset', 'name' => $name]); });