diff --git a/cutt/player.py b/cutt/player.py
index 9ac6f39..ce91d8c 100644
--- a/cutt/player.py
+++ b/cutt/player.py
@@ -1,19 +1,61 @@
from typing import Annotated
-from fastapi import APIRouter, Depends, Security
+from fastapi import APIRouter, Depends, HTTPException, Security, status
from fastapi.responses import PlainTextResponse
+from pydantic import BaseModel
from sqlmodel import Session, select
-from cutt.db import Player, Team, engine
-from cutt.security import change_password, get_current_active_user, read_player_me
+from cutt.db import Player, PlayerTeamLink, Team, engine
+from cutt.security import (
+ TeamScopedRequest,
+ change_password,
+ get_current_active_user,
+ read_player_me,
+ verify_team_scope,
+)
P = Player
player_router = APIRouter(prefix="/player", tags=["player"])
-def add_player(player: P):
+class AddPlayerRequest(BaseModel):
+ display_name: str
+ username: str
+ number: str
+ email: str
+
+
+def add_player(
+ r: AddPlayerRequest,
+ request: Annotated[TeamScopedRequest, Depends(verify_team_scope)],
+):
with Session(engine) as session:
- session.add(player)
+ if session.exec(select(P).where(P.username == r.username)).one_or_none():
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST, detail="username not available"
+ )
+
+ stmt = (
+ select(P)
+ .join(PlayerTeamLink)
+ .join(Team)
+ .where(Team.id == request.team_id, P.display_name == r.display_name)
+ )
+ if session.exec(stmt).one_or_none():
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="the name is already taken on this team",
+ )
+
+ team = session.exec(select(Team).where(Team.id == request.team_id)).one()
+ new_player = Player(
+ username=r.username,
+ display_name=r.display_name,
+ email=r.email if r.email else None,
+ number=r.number,
+ teams=[team],
+ )
+ session.add(new_player)
session.commit()
@@ -48,8 +90,9 @@ async def list_players(team_id: int):
players = [t.players for t in session.exec(statement)][0]
if players:
return [
- player.model_dump(include={"id", "display_name", "number"})
+ player.model_dump(include={"id", "display_name", "username", "number"})
for player in players
+ if not player.disabled
]
@@ -59,10 +102,9 @@ def read_teams_me(user: Annotated[P, Depends(get_current_active_user)]):
player_router.add_api_route(
- "/add",
+ "/add/{team_id}",
endpoint=add_player,
methods=["POST"],
- dependencies=[Security(get_current_active_user, scopes=["admin"])],
)
player_router.add_api_route(
"/list/{team_id}",
diff --git a/src/App.css b/src/App.css
index f45e125..f744301 100644
--- a/src/App.css
+++ b/src/App.css
@@ -480,6 +480,7 @@ button {
/*========TEAM PANEL========*/
.team-panel {
+ max-width: 800px;
padding: 1em;
border: 3px solid black;
box-shadow: 8px 8px black;
@@ -492,44 +493,47 @@ button {
}
.team-player {
- cursor: pointer;
color: black;
background-color: #36c4;
border: 1px solid black;
- border-radius: 1em;
+ border-radius: 1.4em;
margin: 4px;
- padding: 0 0.5em;
+ padding: 0.2em 0.5em;
&:hover {
background-color: #36c8;
}
+
+ &.new-player {
+ background-color: #3838;
+ }
}
.new-player-inputs {
margin: auto;
div {
- display: flex;
- flex-wrap: wrap;
- margin: auto;
+ display: grid;
+ grid-template-columns: 20ch auto;
+
+ @media only screen and (max-width: 768px) {
+ grid-template-columns: auto;
+ place-items: center;
+ }
label {
- text-align: right;
- margin-left: 2em;
- margin-right: auto;
+ text-align: left;
+ width: 20ch;
+ margin: auto 1em;
}
input {
- width: 200px;
- margin: 2px 1em;
+ width: 90%;
+ margin: 4px 0;
}
}
}
-#new-team-player {
- background-color: #3838;
- font-weight: bold;
-}
@keyframes blink {
diff --git a/src/Login.tsx b/src/Login.tsx
index 62aa361..4a336ef 100644
--- a/src/Login.tsx
+++ b/src/Login.tsx
@@ -114,7 +114,7 @@ export const Login = ({ onLogin }: LoginProps) => {
{visible ?