]> git.localhorst.tv Git - alttp.git/commitdiff
limits for huge tournaments
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Sat, 24 Feb 2024 13:38:58 +0000 (14:38 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Sat, 24 Feb 2024 13:38:58 +0000 (14:38 +0100)
app/Http/Controllers/ProtocolController.php
app/Http/Controllers/TournamentController.php
resources/js/components/rounds/List.js
resources/js/components/rounds/LoadMore.js [new file with mode: 0644]
resources/js/components/tournament/Detail.js
resources/js/helpers/Tournament.js
resources/js/i18n/de.js
resources/js/i18n/en.js
resources/js/pages/Tournament.js
routes/api.php

index e4f028042212c9bede6d19fa09ccf378b2cce6d8..6b5e48cbe4eb8fdd78c6961aec1fdd5bd6d710fe 100644 (file)
@@ -14,6 +14,7 @@ class ProtocolController extends Controller
                        ->protocols()
                        ->with('user')
                        ->orderBy('created_at', 'desc')
+                       ->limit(150)
                        ->get();
                return $protocol->values()->toJson();
        }
index 9b21736d8ea4a111eece00198e6cd858ca856773..f53dad4b644e5aee03481c5e670424ea6e8fe1ac 100644 (file)
@@ -28,21 +28,21 @@ class TournamentController extends Controller
                $tournament = Tournament::with(
                        'applications',
                        'applications.user',
-                       'rounds',
-                       'rounds.results',
-                       'rounds.results.user',
                        'participants',
                        'participants.user',
                )->findOrFail($id);
                $this->authorize('view', $tournament);
-               foreach ($tournament->rounds as $round) {
+               $rounds = $tournament->rounds()->with(['results', 'results.user'])->limit(25)->get();
+               foreach ($rounds as $round) {
                        try {
                                $this->authorize('seeResults', $round);
                        } catch (AuthorizationException) {
                                $round->hideResults();
                        }
                }
-               return $tournament->toJson();
+               $json = $tournament->toArray();
+               $json['rounds'] = $rounds->toArray();
+               return $json;
        }
 
        public function discord(Request $request, Tournament $tournament) {
@@ -81,6 +81,27 @@ class TournamentController extends Controller
                return $tournament->toJson();
        }
 
+       public function moreRounds(Request $request, Tournament $tournament) {
+               $this->authorize('view', $tournament);
+
+               $validatedData = $request->validate([
+                       'last_known' => 'integer|required',
+               ]);
+
+               $rounds = $tournament->rounds()
+                       ->where('number', '<', $validatedData['last_known'])
+                       ->with(['results', 'results.user'])
+                       ->limit(25)->get();
+               foreach ($rounds as $round) {
+                       try {
+                               $this->authorize('seeResults', $round);
+                       } catch (AuthorizationException) {
+                               $round->hideResults();
+                       }
+               }
+               return $rounds->toArray();
+       }
+
        public function open(Request $request, Tournament $tournament) {
                $this->authorize('update', $tournament);
                $tournament->accept_applications = true;
index 2ac8f1701d9074094e705035c928f326dd5522f4..5aa2a4c77eece85242122452edf11c6c7070c787 100644 (file)
@@ -4,12 +4,14 @@ import { Alert } from 'react-bootstrap';
 import { withTranslation } from 'react-i18next';
 
 import Item from './Item';
+import LoadMore from './LoadMore';
 import i18n from '../../i18n';
 
 const List = ({
+       loadMore,
        rounds,
        tournament,
-}) => rounds && rounds.length ?
+}) => rounds && rounds.length ? <>
        <ol className="rounds">
                {rounds.map(round =>
                        <Item
@@ -19,13 +21,17 @@ const List = ({
                        />
                )}
        </ol>
-:
+       {loadMore ?
+               <LoadMore loadMore={loadMore} />
+       : null}
+</> :
        <Alert variant="info">
                {i18n.t('rounds.empty')}
        </Alert>
 ;
 
 List.propTypes = {
+       loadMore: PropTypes.func,
        rounds: PropTypes.arrayOf(PropTypes.shape({
                id: PropTypes.number,
        })),
diff --git a/resources/js/components/rounds/LoadMore.js b/resources/js/components/rounds/LoadMore.js
new file mode 100644 (file)
index 0000000..d0ea365
--- /dev/null
@@ -0,0 +1,31 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+const LoadMore = ({ loadMore }) => {
+       const [loading, setLoading] = React.useState(false);
+
+       const { t } = useTranslation();
+
+       const handleLoadMore = React.useCallback(async () => {
+               setLoading(true);
+               try {
+                       await loadMore();
+               } finally {
+                       setLoading(false);
+               }
+       }, [loadMore]);
+
+       return <div className="d-grid mt-2">
+               <Button disabled={loading} onClick={handleLoadMore} variant="outline-secondary">
+               {t('rounds.loadMore')}
+               </Button>
+       </div>;
+};
+
+LoadMore.propTypes = {
+       loadMore: PropTypes.func,
+};
+
+export default LoadMore;
index 84592f6beb1a525a5f2886cf6661600ad2c975ec..318e67757d395fdc2dce9507dfe955d875e14802 100644 (file)
@@ -42,6 +42,7 @@ const getClassName = (tournament, user) => {
 
 const Detail = ({
        addRound,
+       moreRounds,
        tournament,
 }) => {
        const { t } = useTranslation();
@@ -111,7 +112,11 @@ const Detail = ({
                                        : null}
                                </div>
                                {tournament.rounds ?
-                                       <Rounds rounds={tournament.rounds} tournament={tournament} />
+                                       <Rounds
+                                               loadMore={moreRounds}
+                                               rounds={tournament.rounds}
+                                               tournament={tournament}
+                                       />
                                : null}
                        </Col>
                </Row>
@@ -120,6 +125,7 @@ const Detail = ({
 
 Detail.propTypes = {
        addRound: PropTypes.func,
+       moreRounds: PropTypes.func,
        tournament: PropTypes.shape({
                id: PropTypes.number,
                participants: PropTypes.arrayOf(PropTypes.shape({
index 9d2962d27c5e3f4a63ee2a6cc58f526b068bff22..7270351512938a2f3481244d9dca91bf0c8679dd 100644 (file)
@@ -30,6 +30,16 @@ export const getRunners = tournament => {
                .sort(Participant.compareUsername);
 };
 
+export const getLastRound = tournament => {
+       if (!tournament || !tournament.rounds || !tournament.rounds.length) return null;
+       return tournament.rounds.slice(-1)[0];
+};
+
+export const canLoadMoreRounds = tournament => {
+       const last_round = getLastRound(tournament);
+       return last_round && last_round.number > 1;
+};
+
 export const hasScoreboard = tournament => !!(tournament && tournament.type === 'signup-async');
 
 export const hasSignup = tournament => !!(tournament && tournament.type === 'signup-async');
index 308ca7f3267c66731eef384fd67d0a17ca390b9a..0d17d73e71ebf32832b66e34825f5e891f0bdcc8 100644 (file)
@@ -399,6 +399,7 @@ export default {
                        noSeed: 'Noch kein Seed',
                        numberOfResults_one: '{{ count }} Ergebnis',
                        numberOfResults_other: '{{ count }} Ergebnisse',
+                       loadMore: 'weitere Runden laden',
                        lock: 'Runde sperren',
                        lockDescription: 'Wenn die Runde gesperrt wird, können Runner keine Änderungen an ihrem Ergebnis mehr vornehmen.',
                        locked: 'Die Runde ist für weitere Änderungen am Ergebnis gesperrt.',
index a146ca267c91aa6bbd28bb47329876ba3d2e33b7..37229910076de037692957d672ccb8ad0fca00ad 100644 (file)
@@ -399,6 +399,7 @@ export default {
                        noSeed: 'No seed set',
                        numberOfResults_one: '{{ count }} submission',
                        numberOfResults_other: '{{ count }} submissions',
+                       loadMore: 'load more rounds',
                        lock: 'Lock round',
                        lockDescription: 'When a round is locked, runners cannot submit or change results.',
                        locked: 'Results for this round have been locked.',
index 5de5dbe32758c1215cc48d30c37db0dea946b79c..ecaab6c97807bf1968b7e1732cf8c7d624a0a85b 100644 (file)
@@ -10,6 +10,8 @@ import Loading from '../components/common/Loading';
 import NotFound from '../pages/NotFound';
 import Detail from '../components/tournament/Detail';
 import {
+       canLoadMoreRounds,
+       getLastRound,
        patchApplication,
        patchParticipant,
        patchResult,
@@ -97,6 +99,21 @@ export const Component = () => {
                };
        }, [id]);
 
+       const moreRounds = React.useCallback(async () => {
+               const last_round = getLastRound(tournament);
+               if (!last_round) return;
+               console.log(last_round);
+               const last_known = last_round.number;
+               const rsp = await axios.get(
+                       `/api/tournaments/${id}/more-rounds`,
+                       { params: { last_known } },
+               );
+               setTournament(tournament => ({
+                       ...tournament,
+                       rounds: [...tournament.rounds, ...rsp.data],
+               }));
+       }, [id, tournament]);
+
        useEffect(() => {
                const cb = (e) => {
                        if (e.user) {
@@ -132,6 +149,10 @@ export const Component = () => {
                        <title>{tournament.title}</title>
                </Helmet>
                <CanonicalLinks base={`/tournaments/${tournament.id}`} />
-               <Detail addRound={addRound} tournament={tournament} />
+               <Detail
+                       addRound={addRound}
+                       moreRounds={canLoadMoreRounds(tournament) ? moreRounds : null}
+                       tournament={tournament}
+               />
        </ErrorBoundary>;
 };
index 2b8b6827113cf3fa5d0ffec411d35916d4f37475..ca02e369cc8652608556a9e6ca16a7db728d8cad 100644 (file)
@@ -70,6 +70,7 @@ Route::get('tech', 'App\Http\Controllers\TechniqueController@search');
 Route::get('tech/{tech:name}', 'App\Http\Controllers\TechniqueController@single');
 
 Route::get('tournaments/{id}', 'App\Http\Controllers\TournamentController@single');
+Route::get('tournaments/{tournament}/more-rounds', 'App\Http\Controllers\TournamentController@moreRounds');
 Route::post('tournaments/{tournament}/apply', 'App\Http\Controllers\TournamentController@apply');
 Route::post('tournaments/{tournament}/close', 'App\Http\Controllers\TournamentController@close');
 Route::post('tournaments/{tournament}/discord', 'App\Http\Controllers\TournamentController@discord');