Skip to content

Commit

Permalink
Update old schemas and routes to use new ORM layout
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisLovering committed Jun 10, 2023
1 parent ab5f01e commit 3b3a862
Show file tree
Hide file tree
Showing 17 changed files with 347 additions and 143 deletions.
4 changes: 2 additions & 2 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from starlette.middleware.authentication import AuthenticationMiddleware

from api.middleware import TokenAuthentication, on_auth_error
from api.routers.old import old_routes_router
from api.routers.v1 import v1_routes_router
from api.settings import Server

app = FastAPI(redoc_url="/", docs_url="/swagger")
Expand All @@ -13,4 +13,4 @@
on_error=on_auth_error,
)

app.include_router(old_routes_router)
app.include_router(v1_routes_router)
2 changes: 1 addition & 1 deletion api/models/orm/infraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Infraction(Base):
user_id: Mapped[int] = mapped_column(ForeignKey("users.user_id"))
issued_in_jam_id: Mapped[int] = mapped_column(ForeignKey("jams.jam_id"))
infraction_type: Mapped[InfractionType] = mapped_column(
Enum(*InfractionType.__args__, name="infraction_type_enum"),
Enum(*InfractionType.__args__, name="infraction_type"),
nullable=False,
)
reason: Mapped[str] = mapped_column(String(), nullable=False)
31 changes: 0 additions & 31 deletions api/models/schemas/old/infraction.py

This file was deleted.

5 changes: 5 additions & 0 deletions api/models/schemas/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def discord_ids_must_be_snowflake(field_to_check: int) -> int:
"""Ensure the ids are valid Discord snowflakes."""
if field_to_check and field_to_check.bit_length() > 64:
raise ValueError("Field must fit within a 64 bit int.")
return field_to_check
31 changes: 31 additions & 0 deletions api/models/schemas/v1/infraction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from pydantic import BaseModel, validator

from api.models.orm.infraction import InfractionType
from api.models.schemas.utils import discord_ids_must_be_snowflake


class InfractionBase(BaseModel):
"""Base model for all infraction types."""

user_id: int
jam_id: int
reason: str
infraction_type: InfractionType

# validators
_ensure_valid_discord_id = validator("user_id", allow_reuse=True)(discord_ids_must_be_snowflake)


class InfractionCreate(InfractionBase):
"""The expected fields to create a new infraction."""


class Infraction(InfractionBase):
"""A model representing an infraction."""

id: int

class Config:
"""Sets ORM mode to true so that pydantic will validate the objects returned by SQLAlchemy."""

orm_mode = True
15 changes: 9 additions & 6 deletions api/models/schemas/old/jam.py → api/models/schemas/v1/jam.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
from pydantic import BaseModel

from api.models.schemas.old import infraction, team, winner
from api.models.schemas.v1 import infraction, team, winner


class CodeJam(BaseModel):
"""A model representing a codejam."""
class CodeJamBase(BaseModel):
"""A Base model representing a codejam."""

name: str
teams: list[team.Team]
ongoing: bool = False


class CodeJamResponse(CodeJam):
class CodeJamCreate(CodeJamBase):
"""The expected fields to create a new Code Jam."""


class CodeJam(CodeJamBase):
"""Response model representing a code jam."""

id: int
teams: list[team.TeamResponse]
infractions: list[infraction.InfractionResponse]
infractions: list[infraction.Infraction]
winners: list[winner.Winner]

class Config:
Expand Down
14 changes: 7 additions & 7 deletions api/models/schemas/old/team.py → api/models/schemas/v1/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

from pydantic import BaseModel

from api.models.schemas.old import user
from api.models.schemas.v1 import user


class Team(BaseModel):
"""A model representing a team for a codejam."""
class TeamBase(BaseModel):
"""A Base model representing a team for a codejam."""

name: str
users: list[user.User]
discord_role_id: Optional[int] = None
discord_channel_id: Optional[int] = None


class TeamResponse(Team):
class Team(TeamBase):
"""Response model representing a team."""

id: int
Expand All @@ -26,11 +26,11 @@ class Config:
orm_mode = True


class UserTeamResponse(BaseModel):
"""Response model representing user and team relationship."""
class UserTeam(BaseModel):
"""A model representing user and team relationship."""

user_id: int
team: TeamResponse
team: Team
is_leader: bool

class Config:
Expand Down
25 changes: 9 additions & 16 deletions api/models/schemas/old/user.py → api/models/schemas/v1/user.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
from pydantic import BaseModel

from api.models.schemas.old import infraction


class User(BaseModel):
"""A model representing a user for a codejam."""

user_id: int
is_leader: bool

class Config:
"""Sets ORM mode to true so that pydantic will validate the objects returned by SQLAlchemy."""

orm_mode = True
from api.models.schemas.v1 import infraction


class ParticipationHistory(BaseModel):
Expand All @@ -23,18 +11,23 @@ class ParticipationHistory(BaseModel):
first_place: bool
team_id: int
is_leader: bool
infractions: list[infraction.InfractionResponse]
infractions: list[infraction.Infraction]

class Config:
"""Sets ORM mode to true so that pydantic will validate the objects returned by SQLAlchemy."""

orm_mode = True


class UserResponse(BaseModel):
"""Response model representing a user."""
class UserBase(BaseModel):
"""A Base model representing core data about a user."""

id: int


class User(UserBase):
"""Response model representing everything about a user."""

participation_history: list[ParticipationHistory]

class Config:
Expand Down
File renamed without changes.
10 changes: 0 additions & 10 deletions api/routers/old/__init__.py

This file was deleted.

10 changes: 10 additions & 0 deletions api/routers/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from fastapi import APIRouter

from api.routers.v1 import codejams, infractions, teams, users, winners

v1_routes_router = APIRouter()
v1_routes_router.include_router(codejams.router)
v1_routes_router.include_router(infractions.router)
v1_routes_router.include_router(teams.router)
v1_routes_router.include_router(users.router)
v1_routes_router.include_router(winners.router)
53 changes: 28 additions & 25 deletions api/routers/old/codejams.py → api/routers/v1/codejams.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
from sqlalchemy import desc, update
from sqlalchemy.future import select

from api.models import CodeJam, CodeJamResponse
from api.models.orm import Jam, Team, User
from api.models.schemas.v1 import jam
from api.settings import DBSession

router = APIRouter(prefix="/codejams", tags=["codejams"])


@router.get("/")
async def get_codejams(session: DBSession) -> list[CodeJamResponse]:
async def get_codejams(session: DBSession) -> list[jam.CodeJam]:
"""Get all the codejams stored in the database."""
codejams = await session.execute(select(Jam).order_by(desc(Jam.id)))
codejams = await session.execute(select(Jam).order_by(desc(Jam.jam_id)))
codejams.unique()

return codejams.scalars().all()
Expand All @@ -24,7 +24,7 @@ async def get_codejams(session: DBSession) -> list[CodeJamResponse]:
"/{codejam_id}",
responses={404: {"description": "CodeJam could not be found or there is no ongoing code jam."}},
)
async def get_codejam(codejam_id: int, session: DBSession) -> CodeJamResponse:
async def get_codejam(codejam_id: int, session: DBSession) -> jam.CodeJam:
"""
Get a specific codejam stored in the database by ID.
Expand All @@ -39,7 +39,7 @@ async def get_codejam(codejam_id: int, session: DBSession) -> CodeJamResponse:
# With the current implementation, there should only be one ongoing codejam.
return ongoing_jams[0]

jam_result = await session.execute(select(Jam).where(Jam.id == codejam_id))
jam_result = await session.execute(select(Jam).where(Jam.jam_id == codejam_id))
jam_result.unique()

if not (jam := jam_result.scalars().one_or_none()):
Expand All @@ -54,23 +54,23 @@ async def modify_codejam(
session: DBSession,
name: Optional[str] = None,
ongoing: Optional[bool] = None,
) -> CodeJamResponse:
) -> jam.CodeJam:
"""Modify the specified codejam to change its name and/or whether it's the ongoing code jam."""
codejam = await session.execute(select(Jam).where(Jam.id == codejam_id))
codejam = await session.execute(select(Jam).where(Jam.jam_id == codejam_id))
codejam.unique()

if not codejam.scalars().one_or_none():
raise HTTPException(status_code=404, detail="Code Jam with specified ID does not exist.")

if name is not None:
await session.execute(update(Jam).where(Jam.id == codejam_id).values(name=name))
await session.execute(update(Jam).where(Jam.jam_id == codejam_id).values(name=name))

if ongoing is not None:
# Make sure no other Jams are ongoing, and set the specified codejam to ongoing.
await session.execute(update(Jam).where(Jam.ongoing == True).values(ongoing=False))
await session.execute(update(Jam).where(Jam.id == codejam_id).values(ongoing=True))
await session.execute(update(Jam).where(Jam.jam_id == codejam_id).values(ongoing=True))

jam_result = await session.execute(select(Jam).where(Jam.id == codejam_id))
jam_result = await session.execute(select(Jam).where(Jam.jam_id == codejam_id))
jam_result.unique()

jam = jam_result.scalars().one()
Expand All @@ -79,7 +79,7 @@ async def modify_codejam(


@router.post("/")
async def create_codejam(codejam: CodeJam, session: DBSession) -> CodeJamResponse:
async def create_codejam(codejam: jam.CodeJamCreate, session: DBSession) -> jam.CodeJam:
"""
Create a new codejam and get back the one just created.
Expand All @@ -94,34 +94,37 @@ async def create_codejam(codejam: CodeJam, session: DBSession) -> CodeJamRespons
await session.flush()

for raw_team in codejam.teams:
team = Team(
jam_id=jam.id,
name=raw_team.name,
discord_role_id=raw_team.discord_role_id,
discord_channel_id=raw_team.discord_channel_id,
)
session.add(team)
# Flush here to receive team ID
await session.flush()

created_users = []
for raw_user in raw_team.users:
if raw_user.is_leader:
team_leader_id = raw_user.user_id
if (
not (await session.execute(select(User).where(User.id == raw_user.user_id)))
not (await session.execute(select(User).where(User.user_id == raw_user.user_id)))
.unique()
.scalars()
.one_or_none()
):
user = User(id=raw_user.user_id)
created_users.append(user)
session.add(user)

team_user = TeamUser(team_id=team.id, user_id=raw_user.user_id, is_leader=raw_user.is_leader)
session.add(team_user)
team = Team(
jam_id=jam.jam_id,
name=raw_team.name,
discord_role_id=raw_team.discord_role_id,
discord_channel_id=raw_team.discord_channel_id,
team_leader_id=team_leader_id,
)
team.users = created_users
session.add(team)
# Flush here to receive team ID
await session.flush()

await session.flush()

# Pydantic, what is synchronous, may attempt to call async methods if current jam
# object is returned. To avoid this, fetch all data here, in async context.
jam_result = await session.execute(select(Jam).where(Jam.id == jam.id))
jam_result = await session.execute(select(Jam).where(Jam.jam_id == jam.jam_id))
jam_result.unique()

jam = jam_result.scalars().one()
Expand Down
Loading

0 comments on commit 3b3a862

Please sign in to comment.