Make dashboard endpoint unique (#454)

This commit is contained in:
Erik Vroon authored and GitHub committed 2024-02-10 16:15:27 +01:00
1 parent 8ff8831e90
commit 14728a62bb
8 files changed
+104 -7

No files matched your search

@@ -0,0 +1,29 @@
"""tournaments.dashboard_endpoint unique
Revision ID: c08e04993dd7
Revises: 39ec08a054af
Create Date: 2024-02-09 18:44:32.138133
"""
from alembic import op
# revision identifiers, used by Alembic.
revision: str | None = "c08e04993dd7"
down_revision: str | None = "39ec08a054af"
branch_labels: str | None = None
depends_on: str | None = None
def upgrade() -> None:
op.create_index(
op.f("ix_tournaments_dashboard_endpoint"),
"tournaments",
["dashboard_endpoint"],
unique=True,
)
def downgrade() -> None:
op.drop_index(op.f("ix_tournaments_dashboard_endpoint"), table_name="tournaments")
+10 -4
View File
@@ -1,3 +1,4 @@
import asyncpg # type: ignore[import-untyped]
from fastapi import APIRouter, Depends, HTTPException, UploadFile
from heliclockter import datetime_utc
from starlette import status
@@ -27,6 +28,7 @@ from bracket.sql.tournaments import (
)
from bracket.sql.users import get_user_access_to_club, get_which_clubs_has_user_access_to
from bracket.utils.db import fetch_one_parsed_certain
from bracket.utils.errors import check_constraint_and_raise_http_exception
from bracket.utils.types import assert_some
router = APIRouter()
@@ -79,10 +81,14 @@ async def update_tournament_by_id(
tournament_body: TournamentUpdateBody,
_: UserPublic = Depends(user_authenticated_for_tournament),
) -> SuccessResponse:
await database.execute(
query=tournaments.update().where(tournaments.c.id == tournament_id),
values=tournament_body.model_dump(),
)
try:
await database.execute(
query=tournaments.update().where(tournaments.c.id == tournament_id),
values=tournament_body.model_dump(),
)
except asyncpg.exceptions.UniqueViolationError as exc:
check_constraint_and_raise_http_exception(exc)
await update_start_times_of_matches(tournament_id)
return SuccessResponse()
+1 -1
View File
@@ -24,7 +24,7 @@ tournaments = Table(
Column("club_id", BigInteger, ForeignKey("clubs.id"), index=True, nullable=False),
Column("dashboard_public", Boolean, nullable=False),
Column("logo_path", String, nullable=True),
Column("dashboard_endpoint", String, nullable=True),
Column("dashboard_endpoint", String, nullable=True, index=True, unique=True),
Column("players_can_be_in_multiple_teams", Boolean, nullable=False, server_default="f"),
Column("auto_assign_courts", Boolean, nullable=False, server_default="f"),
Column("duration_minutes", Integer, nullable=False, server_default="15"),
+34
View File
@@ -0,0 +1,34 @@
from enum import auto
from typing import NoReturn
import asyncpg # type: ignore[import-untyped]
from fastapi import HTTPException
from starlette import status
from bracket.utils.types import EnumAutoStr
class UniqueIndex(EnumAutoStr):
ix_tournaments_dashboard_endpoint = auto()
ix_users_email = auto()
unique_index_violation_error_lookup = {
UniqueIndex.ix_tournaments_dashboard_endpoint: "This dashboard link is already taken",
UniqueIndex.ix_users_email: "This email is already taken",
}
def check_constraint_and_raise_http_exception(
exc: asyncpg.exceptions.UniqueViolationError,
) -> NoReturn:
constraint_name = exc.as_dict()["constraint_name"]
assert constraint_name, "UniqueViolationError occurred but no constraint_name defined"
if constraint_name in unique_index_violation_error_lookup:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=unique_index_violation_error_lookup[constraint_name],
)
raise exc
+1
View File
@@ -68,6 +68,7 @@ disable = [
'wrong-import-position',
'fixme',
'broad-except',
'consider-iterating-dictionary',
]
[tool.bandit]
@@ -83,7 +83,9 @@ async def test_not_authenticated_for_tournament(
) -> None:
async with inserted_club(DUMMY_CLUB) as club_inserted:
async with inserted_tournament(
DUMMY_TOURNAMENT.model_copy(update={"club_id": club_inserted.id})
DUMMY_TOURNAMENT.model_copy(
update={"club_id": club_inserted.id, "dashboard_endpoint": "some-slug"}
)
) as tournament_inserted:
response = JsonDict(
await send_auth_request(
@@ -0,0 +1,25 @@
from bracket.database import database
from bracket.utils.errors import (
unique_index_violation_error_lookup,
)
async def test_all_unique_indices_in_lookup() -> None:
query = """
SELECT
idx.relname AS index_name
FROM pg_index pgi
JOIN pg_class idx ON idx.oid = pgi.indexrelid
JOIN pg_namespace insp ON insp.oid = idx.relnamespace
JOIN pg_class tbl ON tbl.oid = pgi.indrelid
JOIN pg_namespace tnsp ON tnsp.oid = tbl.relnamespace
WHERE pgi.indisunique
AND tnsp.nspname = 'public'
AND idx.relname NOT LIKE '%_pkey'
AND idx.relname !='alembic_version_pkc'
"""
result = await database.fetch_all(query)
indices = {ix.index_name for ix in result} # type: ignore[attr-defined]
expected_indices = {ix.name for ix in unique_index_violation_error_lookup.keys()}
assert indices == expected_indices