Skip to content

Commit d660916

Browse files
committed
Update old schemas and routes to use new ORM layout
1 parent e67fc93 commit d660916

17 files changed

+306
-143
lines changed

api/main.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from starlette.middleware.authentication import AuthenticationMiddleware
33

44
from api.middleware import TokenAuthentication, on_auth_error
5-
from api.routers.old import old_routes_router
5+
from api.routers.v1 import v1_routes_router
66
from api.settings import Server
77

88
app = FastAPI(redoc_url="/", docs_url="/swagger")
@@ -13,4 +13,4 @@
1313
on_error=on_auth_error,
1414
)
1515

16-
app.include_router(old_routes_router)
16+
app.include_router(v1_routes_router)

api/models/orm/infraction.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class Infraction(Base):
1717
user_id: Mapped[int] = mapped_column(ForeignKey("users.user_id"))
1818
issued_in_jam_id: Mapped[int] = mapped_column(ForeignKey("jams.jam_id"))
1919
infraction_type: Mapped[InfractionType] = mapped_column(
20-
Enum(*InfractionType.__args__, name="infraction_type_enum"),
20+
Enum(*InfractionType.__args__, name="infraction_type"),
2121
nullable=False,
2222
)
2323
reason: Mapped[str] = mapped_column(String(), nullable=False)

api/models/schemas/old/infraction.py

-31
This file was deleted.

api/models/schemas/utils.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def discord_ids_must_be_snowflake(field_to_check: int) -> int:
2+
"""Ensure the ids are valid Discord snowflakes."""
3+
if field_to_check and field_to_check.bit_length() > 64:
4+
raise ValueError("Field must fit within a 64 bit int.")
5+
return field_to_check

api/models/schemas/v1/infraction.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from pydantic import BaseModel, validator
2+
3+
from api.models.orm.infraction import InfractionType
4+
from api.models.schemas.utils import discord_ids_must_be_snowflake
5+
6+
7+
class InfractionBase(BaseModel):
8+
"""Base model for all infraction types."""
9+
10+
user_id: int
11+
jam_id: int
12+
reason: str
13+
infraction_type: InfractionType
14+
15+
# validators
16+
_ensure_valid_discord_id = validator("user_id", allow_reuse=True)(discord_ids_must_be_snowflake)
17+
18+
19+
class InfractionCreate(InfractionBase):
20+
"""The expected fields to create a new infraction."""
21+
22+
23+
class Infraction(InfractionBase):
24+
"""A model representing an infraction."""
25+
26+
id: int
27+
28+
class Config:
29+
"""Sets ORM mode to true so that pydantic will validate the objects returned by SQLAlchemy."""
30+
31+
orm_mode = True

api/models/schemas/old/jam.py renamed to api/models/schemas/v1/jam.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
from pydantic import BaseModel
22

3-
from api.models.schemas.old import infraction, team, winner
3+
from api.models.schemas.v1 import infraction, team, winner
44

55

6-
class CodeJam(BaseModel):
7-
"""A model representing a codejam."""
6+
class CodeJamBase(BaseModel):
7+
"""A Base model representing a codejam."""
88

99
name: str
1010
teams: list[team.Team]
1111
ongoing: bool = False
1212

1313

14-
class CodeJamResponse(CodeJam):
14+
class CodeJamCreate(CodeJamBase):
15+
"""The expected fields to create a new Code Jam."""
16+
17+
18+
class CodeJam(CodeJamBase):
1519
"""Response model representing a code jam."""
1620

1721
id: int
18-
teams: list[team.TeamResponse]
19-
infractions: list[infraction.InfractionResponse]
22+
infractions: list[infraction.Infraction]
2023
winners: list[winner.Winner]
2124

2225
class Config:

api/models/schemas/old/team.py renamed to api/models/schemas/v1/team.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22

33
from pydantic import BaseModel
44

5-
from api.models.schemas.old import user
5+
from api.models.schemas.v1 import user
66

77

8-
class Team(BaseModel):
9-
"""A model representing a team for a codejam."""
8+
class TeamBase(BaseModel):
9+
"""A Base model representing a team for a codejam."""
1010

1111
name: str
1212
users: list[user.User]
1313
discord_role_id: Optional[int] = None
1414
discord_channel_id: Optional[int] = None
1515

1616

17-
class TeamResponse(Team):
17+
class Team(TeamBase):
1818
"""Response model representing a team."""
1919

2020
id: int
@@ -26,11 +26,11 @@ class Config:
2626
orm_mode = True
2727

2828

29-
class UserTeamResponse(BaseModel):
30-
"""Response model representing user and team relationship."""
29+
class UserTeam(BaseModel):
30+
"""A model representing user and team relationship."""
3131

3232
user_id: int
33-
team: TeamResponse
33+
team: Team
3434
is_leader: bool
3535

3636
class Config:

api/models/schemas/old/user.py renamed to api/models/schemas/v1/user.py

+9-16
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
from pydantic import BaseModel
22

3-
from api.models.schemas.old import infraction
4-
5-
6-
class User(BaseModel):
7-
"""A model representing a user for a codejam."""
8-
9-
user_id: int
10-
is_leader: bool
11-
12-
class Config:
13-
"""Sets ORM mode to true so that pydantic will validate the objects returned by SQLAlchemy."""
14-
15-
orm_mode = True
3+
from api.models.schemas.v1 import infraction
164

175

186
class ParticipationHistory(BaseModel):
@@ -23,18 +11,23 @@ class ParticipationHistory(BaseModel):
2311
first_place: bool
2412
team_id: int
2513
is_leader: bool
26-
infractions: list[infraction.InfractionResponse]
14+
infractions: list[infraction.Infraction]
2715

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

3119
orm_mode = True
3220

3321

34-
class UserResponse(BaseModel):
35-
"""Response model representing a user."""
22+
class UserBase(BaseModel):
23+
"""A Base model representing core data about a user."""
3624

3725
id: int
26+
27+
28+
class User(UserBase):
29+
"""Response model representing everything about a user."""
30+
3831
participation_history: list[ParticipationHistory]
3932

4033
class Config:
File renamed without changes.

api/routers/old/__init__.py

-10
This file was deleted.

api/routers/v1/__init__.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from fastapi import APIRouter
2+
3+
from api.routers.v1 import codejams, infractions, teams, users, winners
4+
5+
v1_routes_router = APIRouter()
6+
v1_routes_router.include_router(codejams.router)
7+
v1_routes_router.include_router(infractions.router)
8+
v1_routes_router.include_router(teams.router)
9+
v1_routes_router.include_router(users.router)
10+
v1_routes_router.include_router(winners.router)

api/routers/old/codejams.py renamed to api/routers/v1/codejams.py

+28-25
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@
44
from sqlalchemy import desc, update
55
from sqlalchemy.future import select
66

7-
from api.models import CodeJam, CodeJamResponse
87
from api.models.orm import Jam, Team, User
8+
from api.models.schemas.v1 import jam
99
from api.settings import DBSession
1010

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

1313

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

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

42-
jam_result = await session.execute(select(Jam).where(Jam.id == codejam_id))
42+
jam_result = await session.execute(select(Jam).where(Jam.jam_id == codejam_id))
4343
jam_result.unique()
4444

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

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

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

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

73-
jam_result = await session.execute(select(Jam).where(Jam.id == codejam_id))
73+
jam_result = await session.execute(select(Jam).where(Jam.jam_id == codejam_id))
7474
jam_result.unique()
7575

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

8080

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

9696
for raw_team in codejam.teams:
97-
team = Team(
98-
jam_id=jam.id,
99-
name=raw_team.name,
100-
discord_role_id=raw_team.discord_role_id,
101-
discord_channel_id=raw_team.discord_channel_id,
102-
)
103-
session.add(team)
104-
# Flush here to receive team ID
105-
await session.flush()
106-
97+
created_users = []
10798
for raw_user in raw_team.users:
99+
if raw_user.is_leader:
100+
team_leader_id = raw_user.user_id
108101
if (
109-
not (await session.execute(select(User).where(User.id == raw_user.user_id)))
102+
not (await session.execute(select(User).where(User.user_id == raw_user.user_id)))
110103
.unique()
111104
.scalars()
112105
.one_or_none()
113106
):
114107
user = User(id=raw_user.user_id)
108+
created_users.append(user)
115109
session.add(user)
116110

117-
team_user = TeamUser(team_id=team.id, user_id=raw_user.user_id, is_leader=raw_user.is_leader)
118-
session.add(team_user)
111+
team = Team(
112+
jam_id=jam.jam_id,
113+
name=raw_team.name,
114+
discord_role_id=raw_team.discord_role_id,
115+
discord_channel_id=raw_team.discord_channel_id,
116+
team_leader_id=team_leader_id,
117+
)
118+
team.users = created_users
119+
session.add(team)
120+
# Flush here to receive team ID
121+
await session.flush()
119122

120123
await session.flush()
121124

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

127130
jam = jam_result.scalars().one()

0 commit comments

Comments
 (0)