Skip to content

Commit 489fc2b

Browse files
authored
Feature: archived tournaments (#1112)
fixes #690
1 parent 1da8fd3 commit 489fc2b

File tree

27 files changed

+433
-63
lines changed

27 files changed

+433
-63
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""add tournaments.status
2+
3+
Revision ID: c1ab44651e79
4+
Revises: e6e2718365dc
5+
Create Date: 2025-02-09 11:06:32.622324
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects.postgresql import ENUM
11+
12+
from alembic import op
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str | None = "c1ab44651e79"
16+
down_revision: str | None = "e6e2718365dc"
17+
branch_labels: str | None = None
18+
depends_on: str | None = None
19+
20+
enum = ENUM("OPEN", "ARCHIVED", name="tournament_status", create_type=True)
21+
22+
23+
def upgrade() -> None:
24+
enum.create(op.get_bind(), checkfirst=True)
25+
op.add_column(
26+
"tournaments", sa.Column("status", enum, server_default="OPEN", nullable=False, index=True)
27+
)
28+
29+
30+
def downgrade() -> None:
31+
op.drop_column("tournaments", "status")
32+
enum.drop(op.get_bind())

backend/bracket/models/db/tournament.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
from enum import auto
2+
13
from heliclockter import datetime_utc
24
from pydantic import Field
35

46
from bracket.models.db.shared import BaseModelORM
57
from bracket.utils.id_types import ClubId, TournamentId
68
from bracket.utils.pydantic import EmptyStrToNone
9+
from bracket.utils.types import EnumAutoStr
10+
11+
12+
class TournamentStatus(EnumAutoStr):
13+
OPEN = auto()
14+
ARCHIVED = auto()
715

816

917
class TournamentInsertable(BaseModelORM):
@@ -18,6 +26,7 @@ class TournamentInsertable(BaseModelORM):
1826
logo_path: str | None = None
1927
players_can_be_in_multiple_teams: bool
2028
auto_assign_courts: bool
29+
status: TournamentStatus = TournamentStatus.OPEN
2130

2231

2332
class Tournament(TournamentInsertable):
@@ -35,5 +44,9 @@ class TournamentUpdateBody(BaseModelORM):
3544
margin_minutes: int = Field(..., ge=0)
3645

3746

47+
class TournamentChangeStatusBody(BaseModelORM):
48+
status: TournamentStatus
49+
50+
3851
class TournamentBody(TournamentUpdateBody):
3952
club_id: ClubId

backend/bracket/routes/courts.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
from bracket.database import database
66
from bracket.logic.subscriptions import check_requirement
77
from bracket.models.db.court import Court, CourtBody, CourtToInsert
8+
from bracket.models.db.tournament import Tournament
89
from bracket.models.db.user import UserPublic
910
from bracket.routes.auth import (
1011
user_authenticated_for_tournament,
1112
user_authenticated_or_public_dashboard,
1213
)
1314
from bracket.routes.models import CourtsResponse, SingleCourtResponse, SuccessResponse
15+
from bracket.routes.util import disallow_archived_tournament
1416
from bracket.schema import courts
1517
from bracket.sql.courts import get_all_courts_in_tournament, sql_delete_court, update_court
1618
from bracket.sql.stages import get_full_tournament_details
@@ -35,6 +37,7 @@ async def update_court_by_id(
3537
court_id: CourtId,
3638
court_body: CourtBody,
3739
_: UserPublic = Depends(user_authenticated_for_tournament),
40+
__: Tournament = Depends(disallow_archived_tournament),
3841
) -> SingleCourtResponse:
3942
await update_court(
4043
tournament_id=tournament_id,
@@ -59,6 +62,7 @@ async def delete_court(
5962
tournament_id: TournamentId,
6063
court_id: CourtId,
6164
_: UserPublic = Depends(user_authenticated_for_tournament),
65+
__: Tournament = Depends(disallow_archived_tournament),
6266
) -> SuccessResponse:
6367
stages = await get_full_tournament_details(tournament_id, no_draft_rounds=False)
6468
used_in_matches_count = 0
@@ -84,6 +88,7 @@ async def create_court(
8488
court_body: CourtBody,
8589
tournament_id: TournamentId,
8690
user: UserPublic = Depends(user_authenticated_for_tournament),
91+
_: Tournament = Depends(disallow_archived_tournament),
8792
) -> SingleCourtResponse:
8893
existing_courts = await get_all_courts_in_tournament(tournament_id)
8994
check_requirement(existing_courts, user, "max_courts")

backend/bracket/routes/matches.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@
2525
MatchRescheduleBody,
2626
)
2727
from bracket.models.db.stage_item import StageType
28+
from bracket.models.db.tournament import Tournament
2829
from bracket.models.db.user import UserPublic
2930
from bracket.routes.auth import user_authenticated_for_tournament
3031
from bracket.routes.models import SingleMatchResponse, SuccessResponse, UpcomingMatchesResponse
31-
from bracket.routes.util import match_dependency
32+
from bracket.routes.util import disallow_archived_tournament, match_dependency
3233
from bracket.sql.courts import get_all_courts_in_tournament
3334
from bracket.sql.matches import sql_create_match, sql_delete_match, sql_update_match
3435
from bracket.sql.rounds import get_round_by_id
@@ -76,6 +77,7 @@ async def get_matches_to_schedule(
7677
async def delete_match(
7778
tournament_id: TournamentId,
7879
_: UserPublic = Depends(user_authenticated_for_tournament),
80+
__: Tournament = Depends(disallow_archived_tournament),
7981
match: Match = Depends(match_dependency),
8082
) -> SuccessResponse:
8183
round_ = await get_round_by_id(tournament_id, match.round_id)
@@ -100,6 +102,7 @@ async def create_match(
100102
tournament_id: TournamentId,
101103
match_body: MatchCreateBodyFrontend,
102104
_: UserPublic = Depends(user_authenticated_for_tournament),
105+
__: Tournament = Depends(disallow_archived_tournament),
103106
) -> SingleMatchResponse:
104107
await check_foreign_keys_belong_to_tournament(match_body, tournament_id)
105108

@@ -126,6 +129,7 @@ async def create_match(
126129
async def schedule_matches(
127130
tournament_id: TournamentId,
128131
_: UserPublic = Depends(user_authenticated_for_tournament),
132+
__: Tournament = Depends(disallow_archived_tournament),
129133
) -> SuccessResponse:
130134
stages = await get_full_tournament_details(tournament_id)
131135
await schedule_all_unscheduled_matches(tournament_id, stages)
@@ -140,6 +144,7 @@ async def reschedule_match(
140144
match_id: MatchId,
141145
body: MatchRescheduleBody,
142146
_: UserPublic = Depends(user_authenticated_for_tournament),
147+
__: Tournament = Depends(disallow_archived_tournament),
143148
) -> SuccessResponse:
144149
await check_foreign_keys_belong_to_tournament(body, tournament_id)
145150
await handle_match_reschedule(tournament_id, body, match_id)
@@ -153,6 +158,7 @@ async def update_match_by_id(
153158
match_id: MatchId,
154159
match_body: MatchBody,
155160
_: UserPublic = Depends(user_authenticated_for_tournament),
161+
__: Tournament = Depends(disallow_archived_tournament),
156162
match: Match = Depends(match_dependency),
157163
) -> SuccessResponse:
158164
await check_foreign_keys_belong_to_tournament(match_body, tournament_id)

backend/bracket/routes/players.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from bracket.database import database
44
from bracket.logic.subscriptions import check_requirement
55
from bracket.models.db.player import Player, PlayerBody, PlayerMultiBody
6+
from bracket.models.db.tournament import Tournament
67
from bracket.models.db.user import UserPublic
78
from bracket.routes.auth import user_authenticated_for_tournament
89
from bracket.routes.models import (
@@ -11,6 +12,7 @@
1112
SinglePlayerResponse,
1213
SuccessResponse,
1314
)
15+
from bracket.routes.util import disallow_archived_tournament
1416
from bracket.schema import players
1517
from bracket.sql.players import (
1618
get_all_players_in_tournament,
@@ -49,6 +51,7 @@ async def update_player_by_id(
4951
player_id: PlayerId,
5052
player_body: PlayerBody,
5153
_: UserPublic = Depends(user_authenticated_for_tournament),
54+
__: Tournament = Depends(disallow_archived_tournament),
5255
) -> SinglePlayerResponse:
5356
await database.execute(
5457
query=players.update().where(
@@ -74,6 +77,7 @@ async def delete_player(
7477
tournament_id: TournamentId,
7578
player_id: PlayerId,
7679
_: UserPublic = Depends(user_authenticated_for_tournament),
80+
__: Tournament = Depends(disallow_archived_tournament),
7781
) -> SuccessResponse:
7882
await sql_delete_player(tournament_id, player_id)
7983
return SuccessResponse()
@@ -84,6 +88,7 @@ async def create_single_player(
8488
player_body: PlayerBody,
8589
tournament_id: TournamentId,
8690
user: UserPublic = Depends(user_authenticated_for_tournament),
91+
_: Tournament = Depends(disallow_archived_tournament),
8792
) -> SuccessResponse:
8893
existing_players = await get_all_players_in_tournament(tournament_id)
8994
check_requirement(existing_players, user, "max_players")
@@ -96,6 +101,7 @@ async def create_multiple_players(
96101
player_body: PlayerMultiBody,
97102
tournament_id: TournamentId,
98103
user: UserPublic = Depends(user_authenticated_for_tournament),
104+
_: Tournament = Depends(disallow_archived_tournament),
99105
) -> SuccessResponse:
100106
player_names = [player.strip() for player in player_body.names.split("\n") if len(player) > 0]
101107
existing_players = await get_all_players_in_tournament(tournament_id)

backend/bracket/routes/rankings.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from bracket.logic.subscriptions import check_requirement
88
from bracket.models.db.ranking import RankingBody, RankingCreateBody
99
from bracket.models.db.stage_item import StageType
10+
from bracket.models.db.tournament import Tournament
1011
from bracket.models.db.user import UserPublic
1112
from bracket.routes.auth import (
1213
user_authenticated_for_tournament,
@@ -16,6 +17,7 @@
1617
RankingsResponse,
1718
SuccessResponse,
1819
)
20+
from bracket.routes.util import disallow_archived_tournament
1921
from bracket.sql.rankings import (
2022
get_all_rankings_in_tournament,
2123
sql_create_ranking,
@@ -43,6 +45,7 @@ async def update_ranking_by_id(
4345
ranking_id: RankingId,
4446
ranking_body: RankingBody,
4547
_: UserPublic = Depends(user_authenticated_for_tournament),
48+
__: Tournament = Depends(disallow_archived_tournament),
4649
) -> SuccessResponse:
4750
await sql_update_ranking(
4851
tournament_id=tournament_id,
@@ -64,6 +67,7 @@ async def delete_ranking(
6467
tournament_id: TournamentId,
6568
ranking_id: RankingId,
6669
_: UserPublic = Depends(user_authenticated_for_tournament),
70+
__: Tournament = Depends(disallow_archived_tournament),
6771
) -> SuccessResponse:
6872
await sql_delete_ranking(tournament_id, ranking_id)
6973
return SuccessResponse()
@@ -74,6 +78,7 @@ async def create_ranking(
7478
ranking_body: RankingCreateBody,
7579
tournament_id: TournamentId,
7680
user: UserPublic = Depends(user_authenticated_for_tournament),
81+
_: Tournament = Depends(disallow_archived_tournament),
7782
) -> SuccessResponse:
7883
existing_rankings = await get_all_rankings_in_tournament(tournament_id)
7984
check_requirement(existing_rankings, user, "max_rankings")

backend/bracket/routes/rounds.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
RoundInsertable,
1313
RoundUpdateBody,
1414
)
15+
from bracket.models.db.tournament import Tournament
1516
from bracket.models.db.user import UserPublic
1617
from bracket.models.db.util import RoundWithMatches
1718
from bracket.routes.auth import user_authenticated_for_tournament
1819
from bracket.routes.models import SuccessResponse
1920
from bracket.routes.util import (
21+
disallow_archived_tournament,
2022
round_dependency,
2123
round_with_matches_dependency,
2224
)
@@ -41,6 +43,7 @@ async def delete_round(
4143
tournament_id: TournamentId,
4244
round_id: RoundId,
4345
_: UserPublic = Depends(user_authenticated_for_tournament),
46+
__: Tournament = Depends(disallow_archived_tournament),
4447
round_with_matches: RoundWithMatches = Depends(round_with_matches_dependency),
4548
) -> SuccessResponse:
4649
for match in round_with_matches.matches:
@@ -58,6 +61,7 @@ async def create_round(
5861
tournament_id: TournamentId,
5962
round_body: RoundCreateBody,
6063
user: UserPublic = Depends(user_authenticated_for_tournament),
64+
_: Tournament = Depends(disallow_archived_tournament),
6165
) -> SuccessResponse:
6266
await check_foreign_keys_belong_to_tournament(round_body, tournament_id)
6367

@@ -98,6 +102,7 @@ async def update_round_by_id(
98102
round_body: RoundUpdateBody,
99103
_: UserPublic = Depends(user_authenticated_for_tournament),
100104
__: Round = Depends(round_dependency),
105+
___: Tournament = Depends(disallow_archived_tournament),
101106
) -> SuccessResponse:
102107
query = """
103108
UPDATE rounds

backend/bracket/routes/stage_item_inputs.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
StageItemInputUpdateBodyFinal,
99
StageItemInputUpdateBodyTentative,
1010
)
11+
from bracket.models.db.tournament import Tournament
1112
from bracket.models.db.user import UserPublic
1213
from bracket.models.db.util import StageItemWithRounds
1314
from bracket.routes.auth import (
1415
user_authenticated_for_tournament,
1516
)
1617
from bracket.routes.models import SuccessResponse
17-
from bracket.routes.util import stage_item_dependency
18+
from bracket.routes.util import disallow_archived_tournament, stage_item_dependency
1819
from bracket.sql.stage_item_inputs import get_stage_item_input_by_id
1920
from bracket.sql.stages import get_full_tournament_details
2021
from bracket.sql.teams import get_team_by_id
@@ -72,6 +73,7 @@ async def update_stage_item_input(
7273
stage_item_body: StageItemInputUpdateBody,
7374
_: UserPublic = Depends(user_authenticated_for_tournament),
7475
__: StageItemWithRounds = Depends(stage_item_dependency),
76+
___: Tournament = Depends(disallow_archived_tournament),
7577
) -> SuccessResponse:
7678
stage_item_input = await get_stage_item_input_by_id(tournament_id, stage_item_input_id)
7779
await validate_stage_item_update(stage_item_input, stage_item_body, tournament_id)

backend/bracket/routes/stage_items.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@
2727
StageItemUpdateBody,
2828
StageType,
2929
)
30+
from bracket.models.db.tournament import Tournament
3031
from bracket.models.db.user import UserPublic
3132
from bracket.models.db.util import StageItemWithRounds
3233
from bracket.routes.auth import (
3334
user_authenticated_for_tournament,
3435
)
3536
from bracket.routes.models import SuccessResponse
36-
from bracket.routes.util import stage_item_dependency
37+
from bracket.routes.util import disallow_archived_tournament, stage_item_dependency
3738
from bracket.sql.courts import get_all_courts_in_tournament
3839
from bracket.sql.matches import sql_create_match
3940
from bracket.sql.rounds import (
@@ -101,6 +102,7 @@ async def update_stage_item(
101102
stage_item_id: StageItemId,
102103
stage_item_body: StageItemUpdateBody,
103104
_: UserPublic = Depends(user_authenticated_for_tournament),
105+
__: Tournament = Depends(disallow_archived_tournament),
104106
stage_item: StageItemWithRounds = Depends(stage_item_dependency),
105107
) -> SuccessResponse:
106108
if stage_item is None:
@@ -137,6 +139,7 @@ async def start_next_round(
137139
elo_diff_threshold: int = 200,
138140
iterations: int = 2_000,
139141
only_recommended: bool = False,
142+
_: Tournament = Depends(disallow_archived_tournament),
140143
) -> SuccessResponse:
141144
draft_round = get_draft_round(stage_item)
142145
if draft_round is not None:

backend/bracket/routes/stages.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
)
1111
from bracket.logic.subscriptions import check_requirement
1212
from bracket.models.db.stage import Stage, StageActivateBody, StageUpdateBody
13+
from bracket.models.db.tournament import Tournament
1314
from bracket.models.db.user import UserPublic
1415
from bracket.models.db.util import StageWithStageItems
1516
from bracket.routes.auth import (
@@ -22,7 +23,7 @@
2223
StagesWithStageItemsResponse,
2324
SuccessResponse,
2425
)
25-
from bracket.routes.util import stage_dependency
26+
from bracket.routes.util import disallow_archived_tournament, stage_dependency
2627
from bracket.sql.stages import (
2728
get_full_tournament_details,
2829
get_next_stage_in_tournament,
@@ -57,6 +58,7 @@ async def delete_stage(
5758
tournament_id: TournamentId,
5859
stage_id: StageId,
5960
_: UserPublic = Depends(user_authenticated_for_tournament),
61+
__: Tournament = Depends(disallow_archived_tournament),
6062
stage: StageWithStageItems = Depends(stage_dependency),
6163
) -> SuccessResponse:
6264
if len(stage.stage_items) > 0:
@@ -80,6 +82,7 @@ async def delete_stage(
8082
async def create_stage(
8183
tournament_id: TournamentId,
8284
user: UserPublic = Depends(user_authenticated_for_tournament),
85+
_: Tournament = Depends(disallow_archived_tournament),
8386
) -> SuccessResponse:
8487
existing_stages = await get_full_tournament_details(tournament_id)
8588
check_requirement(existing_stages, user, "max_stages")
@@ -94,6 +97,7 @@ async def update_stage(
9497
stage_id: StageId,
9598
stage_body: StageUpdateBody,
9699
_: UserPublic = Depends(user_authenticated_for_tournament),
100+
__: Tournament = Depends(disallow_archived_tournament),
97101
stage: Stage = Depends(stage_dependency), # pylint: disable=redefined-builtin
98102
) -> SuccessResponse:
99103
values = {"tournament_id": tournament_id, "stage_id": stage_id}
@@ -115,6 +119,7 @@ async def activate_next_stage(
115119
tournament_id: TournamentId,
116120
stage_body: StageActivateBody,
117121
_: UserPublic = Depends(user_authenticated_for_tournament),
122+
__: Tournament = Depends(disallow_archived_tournament),
118123
) -> SuccessResponse:
119124
new_active_stage_id = await get_next_stage_in_tournament(tournament_id, stage_body.direction)
120125
if new_active_stage_id is None:

0 commit comments

Comments
 (0)