Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions backend/bracket/models/db/stage_item.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from enum import auto
from typing import Any
from typing import Any, Literal

from heliclockter import datetime_utc
from pydantic import Field, model_validator
from pydantic import BaseModel, Field, model_validator

from bracket.models.db.shared import BaseModelORM
from bracket.models.db.stage_item_inputs import StageItemInputCreateBody
Expand Down Expand Up @@ -53,6 +53,16 @@
return self.name if self.name is not None else self.type.value.replace("_", " ").title()


class StageItemCreateWithoutStageIdBody(BaseModelORM):
name: str | None = None
type: StageType
team_count: int = Field(ge=2, le=64)
ranking_id: RankingId | None = None

def get_name_or_default_name(self) -> str:
return self.name if self.name is not None else self.type.value.replace("_", " ").title()

Check warning on line 63 in backend/bracket/models/db/stage_item.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/models/db/stage_item.py#L63

Added line #L63 was not covered by tests


class StageItemWithInputsCreate(StageItemCreateBody):
inputs: list[StageItemInputCreateBody]

Expand All @@ -66,3 +76,16 @@
):
raise ValueError("team_count doesn't match length of inputs")
return values


class StageItemsTemplateCreate(BaseModel):
fill_teams: bool
second_stage_type: StageType


class StageItemsRoundRobinTemplateCreate(StageItemsTemplateCreate):
groups_count: int = Field(ge=2, le=16)
teams_per_group: int = Field(ge=2, le=32)
advances_count: Literal[2, 4, 8, 16, 32, 64] = Field(
description="Number of teams that advanced in the first stage to the second stage in total"
)
92 changes: 90 additions & 2 deletions backend/bracket/routes/stage_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
from bracket.models.db.stage_item import (
StageItemActivateNextBody,
StageItemCreateBody,
StageItemsRoundRobinTemplateCreate,
StageItemUpdateBody,
StageType,
StageType, StageItemCreateWithoutStageIdBody,
)
from bracket.models.db.tournament import Tournament
from bracket.models.db.user import UserPublic
Expand All @@ -51,7 +52,11 @@
get_stage_item,
sql_create_stage_item_with_empty_inputs,
)
from bracket.sql.stages import get_full_tournament_details
from bracket.sql.stages import (
does_tournament_have_no_stages,
get_full_tournament_details,
sql_create_stage,
)
from bracket.sql.tournaments import sql_get_tournament
from bracket.sql.validation import check_foreign_keys_belong_to_tournament
from bracket.utils.errors import (
Expand Down Expand Up @@ -97,6 +102,89 @@
return SuccessResponse()


@router.post("/tournaments/{tournament_id}/stage_items/from_single_template", response_model=SuccessResponse)
async def create_stage_item_from_single_template(
tournament_id: TournamentId,
stage_body: StageItemCreateWithoutStageIdBody,
user: UserPublic = Depends(user_authenticated_for_tournament),
) -> SuccessResponse:
"""
Same as POST /tournaments/{tournament_id}/stage_items, but also creates a new stage.

Use only when the tournament has no stages yet.
"""
await check_foreign_keys_belong_to_tournament(stage_body, tournament_id)

Check warning on line 116 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L116

Added line #L116 was not covered by tests

if not await does_tournament_have_no_stages(tournament_id):
raise HTTPException(

Check warning on line 119 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L118-L119

Added lines #L118 - L119 were not covered by tests
status_code=status.HTTP_400_BAD_REQUEST,
detail="Can only create stage items from a template if there aren't stages already",
)

check_requirement([], user, "max_stages", additions=2)
await sql_create_stage(tournament_id)

Check warning on line 125 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L124-L125

Added lines #L124 - L125 were not covered by tests

check_requirement([], user, "max_stage_items")

Check warning on line 127 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L127

Added line #L127 was not covered by tests

stage_item = await sql_create_stage_item_with_empty_inputs(tournament_id, stage_body)
await build_matches_for_stage_item(stage_item, tournament_id)
return SuccessResponse()

Check warning on line 131 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L129-L131

Added lines #L129 - L131 were not covered by tests


@router.post(
"/tournaments/{tournament_id}/stage_items/from_round_robin_template",
response_model=SuccessResponse,
)
async def create_stage_items_from_template(
tournament_id: TournamentId,
body: StageItemsRoundRobinTemplateCreate,
user: UserPublic = Depends(user_authenticated_for_tournament),
) -> SuccessResponse:
await check_foreign_keys_belong_to_tournament(body, tournament_id)

Check warning on line 143 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L143

Added line #L143 was not covered by tests

if not await does_tournament_have_no_stages(tournament_id):
raise HTTPException(

Check warning on line 146 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L145-L146

Added lines #L145 - L146 were not covered by tests
status_code=status.HTTP_400_BAD_REQUEST,
detail="Can only create stage items from a template if there aren't stages already",
)

created_stages, created_stage_items = [], []

Check warning on line 151 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L151

Added line #L151 was not covered by tests

async with database.transaction():
check_requirement(created_stages, user, "max_stages", additions=2)
created_stages.append(await sql_create_stage(tournament_id))
created_stages.append(await sql_create_stage(tournament_id))

Check warning on line 156 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L153-L156

Added lines #L153 - L156 were not covered by tests

check_requirement(

Check warning on line 158 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L158

Added line #L158 was not covered by tests
created_stage_items, user, "max_stage_items", additions=body.groups_count + 1
)

for i in range(body.groups_count):
stage_item = await sql_create_stage_item_with_empty_inputs(

Check warning on line 163 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L162-L163

Added lines #L162 - L163 were not covered by tests
tournament_id,
StageItemCreateBody(
stage_id=created_stages[0].id,
type=StageType.ROUND_ROBIN,
team_count=body.teams_per_group,
),
)
created_stage_items.append(stage_item)
await build_matches_for_stage_item(stage_item, tournament_id)

Check warning on line 172 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L171-L172

Added lines #L171 - L172 were not covered by tests

stage_item = await sql_create_stage_item_with_empty_inputs(

Check warning on line 174 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L174

Added line #L174 was not covered by tests
tournament_id,
StageItemCreateBody(
stage_id=created_stages[1].id,
type=StageType.SINGLE_ELIMINATION,
team_count=body.advances_count,
),
)
created_stage_items.append(stage_item)
await build_matches_for_stage_item(stage_item, tournament_id)

Check warning on line 183 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L182-L183

Added lines #L182 - L183 were not covered by tests

return SuccessResponse()

Check warning on line 185 in backend/bracket/routes/stage_items.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/routes/stage_items.py#L185

Added line #L185 was not covered by tests


@router.put(
"/tournaments/{tournament_id}/stage_items/{stage_item_id}", response_model=SuccessResponse
)
Expand Down
5 changes: 5 additions & 0 deletions backend/bracket/sql/stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@
return [StageWithStageItems.model_validate(dict(x._mapping)) for x in result]


async def does_tournament_have_no_stages(tournament_id: TournamentId) -> bool:
stages = await get_full_tournament_details(tournament_id)
return len(stages) == 0

Check warning on line 119 in backend/bracket/sql/stages.py

View check run for this annotation

Codecov / codecov/patch

backend/bracket/sql/stages.py#L118-L119

Added lines #L118 - L119 were not covered by tests


async def sql_delete_stage(tournament_id: TournamentId, stage_id: StageId) -> None:
async with database.transaction():
query = """
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/builder/builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { deleteStage } from '../../services/stage';
import { deleteStageItem } from '../../services/stage_item';
import { updateStageItemInput } from '../../services/stage_item_input';
import CreateStageButton from '../buttons/create_stage';
import { CreateStageItemModal } from '../modals/create_stage_item';
import { CreateStageItemModalWithButton } from '../modals/create_stage_item';
import { UpdateStageModal } from '../modals/update_stage';
import { UpdateStageItemModal } from '../modals/update_stage_item';
import RequestErrorAlert from '../utils/error_alert';
Expand Down Expand Up @@ -450,7 +450,7 @@ function StageColumn({
</Menu>
</Group>
{rows}
<CreateStageItemModal
<CreateStageItemModalWithButton
key={-1}
tournament={tournament}
stage={stage}
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/components/buttons/create_stage.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.socialLink {
padding: 1rem;
border-radius: 1rem;
text-align: center;
font-size: 30px;
font-weight: bold;
text-decoration: none;
display: block;
border: 3px solid #333;
}

.socialLink:hover {
border: 3px solid #8545c7;
}
.title {
font-family:
Greycliff CF,
var(--mantine-font-family);
text-align: center;
font-weight: 900;
font-size: rem(38px);
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));

@media (max-width: $mantine-breakpoint-sm) {
font-size: rem(32px);
}
}

.description {
max-width: rem(1000px);
margin: auto;
margin-top: var(--mantine-spacing-xl);
margin-bottom: calc(var(--mantine-spacing-xl) * 1.5);
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
}
Loading
Loading