Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
06e6654
invalidate slices of model keys
someweisguy Jan 20, 2026
9eecb79
upgrade dependencies
someweisguy Jan 22, 2026
e2da418
make db id column a private python variable
someweisguy Jan 22, 2026
1613f1f
make database keys private python variables
someweisguy Jan 22, 2026
0c81eb5
make foreign keys private
someweisguy Jan 22, 2026
771b54d
make cache_key() awaitable
someweisguy Jan 22, 2026
33a1949
add uuid4 to appropriate models
someweisguy Jan 22, 2026
208b90f
fix uuid
someweisguy Jan 22, 2026
27abe60
add get all bouts endpoint and cleanup tags
someweisguy Jan 23, 2026
26afa8c
docs
someweisguy Jan 23, 2026
7c72f49
implement team num system
someweisguy Jan 23, 2026
4b14eb7
update series schema and add all bouts endpoint
someweisguy Jan 23, 2026
25c78c6
get Bout by UUID
someweisguy Jan 23, 2026
3ff7992
index on uuid
someweisguy Jan 23, 2026
f737539
make UUID primary key for all objects
someweisguy Jan 23, 2026
954af61
make method unaware of database schema
someweisguy Jan 23, 2026
36a83c8
update dependencies for simplification
someweisguy Jan 23, 2026
b5b0786
switch to UUID based db
someweisguy Jan 23, 2026
58e1bb0
Merge branch 'fix/query-invalidation' of https://github.com/someweisg…
someweisguy Jan 23, 2026
7fbb053
log initially created bout
someweisguy Jan 23, 2026
c7ea2b1
rename endpoint args
someweisguy Jan 23, 2026
0b40ae2
update schemas
someweisguy Jan 23, 2026
854857b
add roster uuid
someweisguy Jan 23, 2026
af70e3b
show team num in team_jam
someweisguy Jan 23, 2026
be63aac
make jam relationship public
someweisguy Jan 23, 2026
375feca
add more fields
someweisguy Jan 23, 2026
a458a9b
rename dependency injection
someweisguy Jan 23, 2026
9a02ff3
fix series dependency
someweisguy Jan 23, 2026
995dd4a
standardize error messages
someweisguy Jan 23, 2026
cf7e418
update ruff
someweisguy Jan 23, 2026
52d1be5
upgrade dependencies
someweisguy Jan 24, 2026
c1fceed
rename module to better reflect main object
someweisguy Jan 24, 2026
04ab770
remove dependency
someweisguy Jan 24, 2026
d771a11
attach skaters to team
someweisguy Jan 24, 2026
3cd14c7
remove roster dependency
someweisguy Jan 24, 2026
90dc952
add constraint
someweisguy Jan 24, 2026
ab3558b
remove unused files
someweisguy Jan 24, 2026
ef06ca7
remove roster object
someweisguy Jan 24, 2026
f68af0c
add stub skater schema
someweisguy Jan 24, 2026
dad5fb2
don't eagerly initialize timeouts
someweisguy Jan 26, 2026
455ee3a
add method
someweisguy Jan 26, 2026
bc18597
remove roster hook
someweisguy Jan 26, 2026
4bb4d98
update models for backend changes
someweisguy Jan 27, 2026
0f6b95c
remove unused file
someweisguy Jan 27, 2026
86173c5
remove old id
someweisguy Jan 27, 2026
5b86da3
update models to match backend
someweisguy Jan 27, 2026
e75a13f
update Timeout model to match backend
someweisguy Jan 27, 2026
0a13814
all timeouts require a jam reference
someweisguy Jan 27, 2026
a158fe8
formatting fixes
someweisguy Jan 27, 2026
23dcff6
add period num and jam num to timeout
someweisguy Jan 27, 2026
b639c99
docs
someweisguy Jan 27, 2026
223e45d
upgrade deps
someweisguy Jan 27, 2026
d5ab1c4
restore bout hooks
someweisguy Jan 28, 2026
bc0ffbd
fix series api call
someweisguy Jan 28, 2026
f293936
update api params
someweisguy Jan 28, 2026
438c858
clarify suspense queries and add series hooks
someweisguy Jan 28, 2026
e7aedde
use bout getter strategy
someweisguy Jan 28, 2026
117259f
make arguments alias to camelCase
someweisguy Jan 29, 2026
76023b7
update API to match backend
someweisguy Jan 29, 2026
0b6c15d
comment out bad function calls
someweisguy Jan 29, 2026
d30041d
remove field from type
someweisguy Jan 29, 2026
e3ea5dc
organize features
someweisguy Jan 29, 2026
04e36ff
remove arg
someweisguy Jan 29, 2026
72d9b1c
update format
someweisguy Jan 29, 2026
8590598
use simplegrids
someweisguy Jan 29, 2026
d721826
refactor components for simplicity
someweisguy Jan 29, 2026
640f6a9
refactor components to reduce prop drilling
someweisguy Jan 29, 2026
c827746
remove context
someweisguy Jan 29, 2026
20964fc
fix typo
someweisguy Feb 2, 2026
ddbf666
fix trip buttons
someweisguy Feb 2, 2026
f4d4041
fix buttons
someweisguy Feb 2, 2026
3879ddf
add more checkbox logic
someweisguy Feb 2, 2026
04ef06d
remove ruleset context
someweisguy Feb 2, 2026
30fbda0
consolidate feature logic
someweisguy Feb 2, 2026
4c1b61d
consolidate logic
someweisguy Feb 2, 2026
f4d62cc
remove args
someweisguy Feb 2, 2026
867bd54
move component to feature
someweisguy Feb 2, 2026
b43a44d
allow custom timeout queries
someweisguy Feb 2, 2026
f7f9184
use custom queries
someweisguy Feb 2, 2026
ed483bd
fix field name
someweisguy Feb 2, 2026
c9eea36
correct blink behavior
someweisguy Feb 2, 2026
f8a1130
update mutation methods
someweisguy Feb 2, 2026
107ee54
cleanup setTeam endpoint
someweisguy Feb 2, 2026
a4c6c4f
remove field
someweisguy Feb 2, 2026
1b9d7d3
make control buttons contextual
someweisguy Feb 3, 2026
296cc20
update deps
someweisguy Feb 4, 2026
7dc39b3
fix blink behavior
someweisguy Feb 4, 2026
bdb525c
make team_num optional
someweisguy Feb 4, 2026
d892f58
catch correct error
someweisguy Feb 4, 2026
85aa58a
fix api call
someweisguy Feb 4, 2026
e59c4e7
timeouts can be orphaned by team
someweisguy Feb 4, 2026
7a6520d
add bout picker component
someweisguy Feb 4, 2026
73ff5b2
add TODO
someweisguy Feb 4, 2026
e2064af
cache each bout individually when fetching all bouts
someweisguy Feb 7, 2026
44721a9
don't create multiple venv
someweisguy Feb 8, 2026
6dfd710
correctly activate venv
someweisguy Feb 8, 2026
2830152
sync packages
someweisguy Feb 8, 2026
760f903
remove second venv
someweisguy Feb 8, 2026
62b5267
actually lint backend
someweisguy Feb 8, 2026
f5bfa4f
correctly sync dependencies
someweisguy Feb 8, 2026
06c1097
simplify default factory
someweisguy Feb 8, 2026
045150d
fix column type
someweisguy Feb 8, 2026
6943d83
upgrade deps
someweisguy Feb 8, 2026
5aa0ce7
remove shebang
someweisguy Feb 8, 2026
0341c4e
remove setup backend job
someweisguy Feb 8, 2026
8d03dbc
fix job dependencies
someweisguy Feb 8, 2026
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
60 changes: 13 additions & 47 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,79 +70,49 @@ jobs:
path: www
key: ${{ inputs.runs-on }}-www

setup_backend:
name: Setup Backend
runs-on: ${{ inputs.runs-on }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version-file: pyproject.toml
- uses: astral-sh/setup-uv@v7
with:
activate-environment: true
enable-cache: true
- run: uv venv
- run: uv sync
- uses: actions/cache@v5
with:
path: .venv
key: ${{ inputs.runs-on }}-.venv
lint_backend:
name: Lint Backend
needs: setup_backend
runs-on: ${{ inputs.runs-on }}
steps:
- uses: actions/checkout@v6
- uses: actions/cache@v5
with:
path: .venv
key: ${{ inputs.runs-on }}-.venv
- uses: actions/setup-python@v6
with:
python-version-file: pyproject.toml

- uses: astral-sh/setup-uv@v7
with:
activate-environment: true
enable-cache: true
- run: uv sync
- run: ruff check
- run: ty check
test_backend:
name: Test Backend
needs: [setup_backend, build_frontend]
needs: build_frontend
runs-on: ${{ inputs.runs-on }}
steps:
- uses: actions/checkout@v6
- uses: actions/cache@v5
- uses: astral-sh/setup-uv@v7
with:
path: .venv
key: ${{ inputs.runs-on }}-.venv
activate-environment: true
enable-cache: true
- run: uv sync
- uses: actions/cache@v5
with:
path: www
key: ${{ inputs.runs-on }}-www
fail-on-cache-miss: true
- uses: actions/setup-python@v6
with:
python-version-file: pyproject.toml
- uses: astral-sh/setup-uv@v7
with:
activate-environment: true
enable-cache: true
- run: pytest backend/tests --tag-name ${{ github.ref_name }}
if: ${{ inputs.test-release }}
- run: pytest
if: ${{ !inputs.test-release }}
build_backend:
name: Build backend
needs: [setup_backend, build_frontend]
needs: build_frontend
runs-on: ${{ inputs.runs-on }}
steps:
- uses: actions/checkout@v6
- uses: actions/cache@v5
- uses: astral-sh/setup-uv@v7
with:
path: .venv
key: ${{ inputs.runs-on }}-.venv
fail-on-cache-miss: true
activate-environment: true
enable-cache: true
- run: uv sync
- uses: actions/cache@v5
with:
path: www
Expand All @@ -151,10 +121,6 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version-file: pyproject.toml
- uses: astral-sh/setup-uv@v7
with:
activate-environment: true
enable-cache: true
- run: pyinstaller onedir.spec
- run: rm -rf "dist/NSO Bridge"
if: ${{ inputs.runs-on == 'macos-latest' }}
Expand Down
4 changes: 2 additions & 2 deletions backend/src/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

from .database import CASCADE_CHILD, CASCADE_OTHER, BaseSQLModel, DatabaseEngine
from .dependencies import AsyncSessionDepends, EngineFactory
from .dependencies import EngineFactory, GetAsyncSession
from .protocols import Memento
from .router import api_router, assets, pages_router
from .schemas import APIResponseClass, ClientSchema, ServerSchema
Expand All @@ -21,7 +21,6 @@
'api_router',
'APIResponseClass',
'assets',
'AsyncSessionDepends',
'BaseSQLModel',
'CASCADE_CHILD',
'CASCADE_OTHER',
Expand All @@ -33,6 +32,7 @@
'get_default_route',
'get_resource_path',
'get_server',
'GetAsyncSession',
'Memento',
'pages_router',
'ServerSchema',
Expand Down
3 changes: 2 additions & 1 deletion backend/src/core/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
ClassVar,
Final,
)
from uuid import UUID, uuid4

from sqlalchemy.engine import URL
from sqlalchemy.ext.asyncio import (
Expand Down Expand Up @@ -45,7 +46,7 @@ class BaseSQLModel(AsyncAttrs, DeclarativeBase):

"""

id: Mapped[int | None] = mapped_column(nullable=False, primary_key=True)
uuid: Mapped[UUID] = mapped_column(default=uuid4, primary_key=True)

__abstract__: bool = True
__type_annotation_map__: dict = {timedelta: _TimedeltaAsMilliseconds}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/core/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async def yield_async_session(cls) -> AsyncGenerator[AsyncSession, None]:
await session.commit() # Automatically commit after each session


AsyncSessionDepends: TypeAlias = Annotated[
GetAsyncSession: TypeAlias = Annotated[
AsyncSession,
Depends(EngineFactory.yield_async_session),
]
2 changes: 1 addition & 1 deletion backend/src/core/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,5 @@ def render(self, content: Any) -> bytes:
allow_nan=False,
indent=None,
separators=(',', ':'),
default=(lambda dt: str(dt)), # Serialize datetime objects
default=(str), # Serialize datetime objects
).encode('utf-8')
6 changes: 2 additions & 4 deletions backend/src/game/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,16 @@
from .bouts.router import router as bout_router
from .jams.router import router as jam_router
from .models import CacheableSQLModel, CacheKey
from .rosters.models import Roster
from .rosters.router import router as roster_router
from .rulesets import wftda_2025
from .series.models import Series
from .series.router import router as series_router
from .skaters.router import router as skater_router
from .timeouts.router import router as timeout_router

routers: Final[tuple[APIRouter, ...]] = (
bout_router,
jam_router,
roster_router,
skater_router,
series_router,
timeout_router,
)
Expand All @@ -45,7 +44,6 @@
__all__ = (
'CacheKey',
'CacheableSQLModel',
'Roster', # Non-rule-bound objects can be exported
'routers',
'Series', # Non-rule-bound objects can be exported
'wftda_2025',
Expand Down
16 changes: 9 additions & 7 deletions backend/src/game/bouts/dependencies.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""The FastAPI dependencies methods for Bouts."""

from typing import TYPE_CHECKING, Annotated, TypeAlias
from uuid import UUID

from core import AsyncSessionDepends
from core import GetAsyncSession
from core.exceptions import ModelLookupError
from fastapi import Depends, Query, Request
from sqlalchemy import select
Expand All @@ -18,22 +19,23 @@
async def _get_bout(
request: Request,
user: GetUser,
session: AsyncSessionDepends,
bout_id: Annotated[int, Query(alias='boutId')],
session: GetAsyncSession,
bout_uuid: Annotated[UUID, Query(alias='boutUuid')],
) -> BaseBout:
# Query the database for the desired Bout
statement: Select[tuple[BaseBout]] = select(BaseBout).where(BaseBout.id == bout_id)
statement: Select[tuple[BaseBout]] = select(BaseBout).where(
BaseBout.uuid == bout_uuid
)
results: Result[tuple[BaseBout]] = await session.execute(statement)

try:
bout: BaseBout = results.scalar_one()
except NoResultFound as e:
raise ModelLookupError(f'Could not find Bout with ID {bout_id}') from e
raise ModelLookupError(f'Could not find Bout ({bout_uuid=})') from e

# Optionally take a snapshot of the Bout state and return the Bout
if request.method != 'GET':
user.stage(bout.get_memento())
return bout


BoutDepends: TypeAlias = Annotated[BaseBout, Depends(_get_bout)]
GetBout: TypeAlias = Annotated[BaseBout, Depends(_get_bout)]
39 changes: 25 additions & 14 deletions backend/src/game/bouts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from datetime import datetime # noqa: TC003
from typing import TYPE_CHECKING, Any, ClassVar, Final, Literal, final, override
from uuid import UUID # noqa: TC003

from core import CASCADE_CHILD, CASCADE_OTHER
from game.clocks.models import Clock
Expand Down Expand Up @@ -32,8 +33,10 @@ class BaseBout(CacheableSQLModel):

ruleset: ClassVar[Ruleset]

series_id: Mapped[int] = mapped_column(ForeignKey('series.id'))
clock_id: Mapped[int] = mapped_column(ForeignKey('clocks.id', ondelete='RESTRICT'))
_clock_uuid: Mapped[UUID] = mapped_column(
ForeignKey('clocks.uuid', ondelete='RESTRICT')
)
series_uuid: Mapped[UUID] = mapped_column(ForeignKey('series.uuid'))

start_countdown: Mapped[datetime | None] = mapped_column(default=None)
is_final: Mapped[bool] = mapped_column(default=False)
Expand All @@ -43,18 +46,19 @@ class BaseBout(CacheableSQLModel):
_series: Mapped[Series] = relationship(
back_populates='bouts',
cascade=CASCADE_OTHER,
foreign_keys=[series_id],
foreign_keys=[series_uuid],
)
clock: Mapped[Clock] = relationship(
cascade=CASCADE_CHILD,
foreign_keys=[clock_id],
foreign_keys=[_clock_uuid],
lazy='joined',
single_parent=True,
)
teams: Mapped[list[BaseTeam]] = relationship(
back_populates='_bout',
cascade=CASCADE_CHILD,
lazy='selectin',
order_by=[column('num')],
)
jams: Mapped[list[BaseJam]] = relationship(
back_populates='_bout',
Expand All @@ -66,7 +70,7 @@ class BaseBout(CacheableSQLModel):
back_populates='_bout',
cascade=CASCADE_CHILD,
lazy='selectin',
order_by=[column('id')],
order_by=[column('num')],
)

__tablename__: str = 'bouts'
Expand All @@ -82,7 +86,7 @@ def __str__(self) -> str:
str: a str representation of this Bout.

"""
return f'[Bout ID: {self.id}]'
return f'[Bout UUID: {self.uuid}]'

def __init__(self, ruleset_name: str, *teams: BaseTeam) -> None:
"""Instantiate a Bout.
Expand All @@ -96,8 +100,8 @@ def __init__(self, ruleset_name: str, *teams: BaseTeam) -> None:
super().__init__(clock=Clock(), ruleset_name=ruleset_name, teams=list(teams))

@override
def cache_key(self) -> CacheKey:
return (self.__tablename__, self.id)
async def cache_key(self) -> CacheKey:
return (self.__tablename__, self.uuid)

@override
async def get_parents(self) -> tuple[BaseSQLModel, ...]:
Expand Down Expand Up @@ -136,6 +140,15 @@ def state(self) -> Literal['final', 'jam', 'lineup', 'stopped', 'timeout']:
else:
return 'stopped'

def get_active_jam(self) -> BaseJam:
"""Get the most recently started Jam or upcoming Jam.

Returns:
BaseJam: the active Jam.

"""
return next((j for j in self.jams if j.is_started()), self.jams[-1])

def get_running_jam(self) -> BaseJam | None:
"""Get the running Jam if there is one.

Expand Down Expand Up @@ -165,16 +178,14 @@ def get_running_timeout(self) -> BaseTimeout | None:
"""
return next((t for t in self.timeouts if t.is_running()), None)

def get_upcoming_timeout(self) -> BaseTimeout | None:
"""Get the upcoming Timeout if there is one.

The upcoming Timeout is the first Timeout that is not started.
def get_last_timeout(self) -> BaseTimeout | None:
"""Get most recently complete Timeout if there is one.

Returns:
BaseTimeout | None: the upcoming Timeout or None.
BaseTimeout | None: the most recently complete Timeout or None.

"""
return next((t for t in self.timeouts if not t.is_started()), None)
return next((t for t in reversed(self.timeouts) if not t.is_running()), None)

async def begin_period(self, timestamp: datetime) -> None:
"""Begin the next Period.
Expand Down
Loading