Add backend stuff
parent
67567046b8
commit
4d76cdce44
|
@ -0,0 +1,22 @@
|
||||||
|
# Use an official Python runtime as a parent image
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Set the working directory in the container
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
COPY requirements.txt ./
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy the entire app into the container
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose the Flask development server port
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
# Set the Flask environment to development
|
||||||
|
ENV FLASK_APP=app.py
|
||||||
|
ENV FLASK_ENV=development
|
||||||
|
|
||||||
|
# Command to run the Flask application
|
||||||
|
CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"]
|
|
@ -8,9 +8,12 @@ from models import init_db
|
||||||
from spec import spec
|
from spec import spec
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app, origins=["http://localhost:5173"], supports_credentials=True)
|
#CORS(app, origins=["http://localhost:5173"], supports_credentials=True)
|
||||||
CORS(login.api_login, origins=["http://localhost:5173"], supports_credentials=True)
|
#CORS(login.api_login, origins=["http://localhost:5173"], supports_credentials=True)
|
||||||
CORS(schedule.api_schedule, origins=["http://localhost:5173"], supports_credentials=True)
|
#CORS(schedule.api_schedule, origins=["http://localhost:5173"], supports_credentials=True)
|
||||||
|
CORS(app, origins=["*"], supports_credentials=True)
|
||||||
|
CORS(login.api_login, origins=["*"], supports_credentials=True)
|
||||||
|
CORS(schedule.api_schedule, origins=["*"], supports_credentials=True)
|
||||||
|
|
||||||
init_db(app)
|
init_db(app)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
"""Add Team.tz_timezone
|
||||||
|
|
||||||
|
Revision ID: ea359b0e46d7
|
||||||
|
Revises: 2b2f3ae2ec7f
|
||||||
|
Create Date: 2024-11-03 16:53:37.904012
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlalchemy_utc
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'ea359b0e46d7'
|
||||||
|
down_revision = '2b2f3ae2ec7f'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('players_teams_availability', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column('start_time',
|
||||||
|
existing_type=sa.TIMESTAMP(),
|
||||||
|
type_=sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True),
|
||||||
|
existing_nullable=False)
|
||||||
|
batch_op.alter_column('end_time',
|
||||||
|
existing_type=sa.TIMESTAMP(),
|
||||||
|
type_=sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True),
|
||||||
|
existing_nullable=False)
|
||||||
|
|
||||||
|
with op.batch_alter_table('teams', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('tz_timezone', sa.String(length=31), nullable=False, default='Etc/UTC', server_default='0'))
|
||||||
|
batch_op.add_column(sa.Column('minute_offset', sa.SmallInteger(), nullable=False, default=0, server_default='0'))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('teams', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('minute_offset')
|
||||||
|
batch_op.drop_column('tz_timezone')
|
||||||
|
|
||||||
|
with op.batch_alter_table('players_teams_availability', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column('end_time',
|
||||||
|
existing_type=sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True),
|
||||||
|
type_=sa.TIMESTAMP(),
|
||||||
|
existing_nullable=False)
|
||||||
|
batch_op.alter_column('start_time',
|
||||||
|
existing_type=sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True),
|
||||||
|
type_=sa.TIMESTAMP(),
|
||||||
|
existing_nullable=False)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
|
@ -0,0 +1,32 @@
|
||||||
|
"""Add PlayerTeam.is_team_leader
|
||||||
|
|
||||||
|
Revision ID: f50a79c4ae22
|
||||||
|
Revises: ea359b0e46d7
|
||||||
|
Create Date: 2024-11-03 17:11:35.956743
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'f50a79c4ae22'
|
||||||
|
down_revision = 'ea359b0e46d7'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('players_teams', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('is_team_leader', sa.Boolean(), nullable=False, server_default='0'))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('players_teams', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('is_team_leader')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
|
@ -4,7 +4,7 @@ from typing import List
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from sqlalchemy import TIMESTAMP, BigInteger, Boolean, Enum, ForeignKey, ForeignKeyConstraint, Integer, Interval, MetaData, String, func
|
from sqlalchemy import TIMESTAMP, BigInteger, Boolean, Enum, ForeignKey, ForeignKeyConstraint, Integer, Interval, MetaData, SmallInteger, String, func
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
||||||
from sqlalchemy_utc import UtcDateTime
|
from sqlalchemy_utc import UtcDateTime
|
||||||
|
|
||||||
|
@ -36,10 +36,9 @@ class Player(db.Model):
|
||||||
|
|
||||||
created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now())
|
created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now())
|
||||||
|
|
||||||
class PlayerSpec(spec.BaseModel):
|
class PlayerSchema(spec.BaseModel):
|
||||||
steam_id: str
|
steam_id: str
|
||||||
username: str
|
username: str
|
||||||
#teams: list["PlayerTeamSpec"]
|
|
||||||
|
|
||||||
class Team(db.Model):
|
class Team(db.Model):
|
||||||
__tablename__ = "teams"
|
__tablename__ = "teams"
|
||||||
|
@ -47,12 +46,14 @@ class Team(db.Model):
|
||||||
id: Mapped[int] = mapped_column(Integer, autoincrement=True, primary_key=True)
|
id: Mapped[int] = mapped_column(Integer, autoincrement=True, primary_key=True)
|
||||||
team_name: Mapped[str] = mapped_column(String(63), unique=True)
|
team_name: Mapped[str] = mapped_column(String(63), unique=True)
|
||||||
discord_webhook_url: Mapped[str] = mapped_column(String(255), nullable=True)
|
discord_webhook_url: Mapped[str] = mapped_column(String(255), nullable=True)
|
||||||
|
tz_timezone: Mapped[str] = mapped_column(String(31), default="Etc/UTC")
|
||||||
|
minute_offset: Mapped[int] = mapped_column(SmallInteger, default=0)
|
||||||
|
|
||||||
players: Mapped[List["PlayerTeam"]] = relationship(back_populates="team")
|
players: Mapped[List["PlayerTeam"]] = relationship(back_populates="team")
|
||||||
|
|
||||||
created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now())
|
created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now())
|
||||||
|
|
||||||
class TeamSpec(spec.BaseModel):
|
class TeamSchema(spec.BaseModel):
|
||||||
id: int
|
id: int
|
||||||
team_name: str
|
team_name: str
|
||||||
discord_webhook_url: str | None
|
discord_webhook_url: str | None
|
||||||
|
@ -71,13 +72,18 @@ class PlayerTeam(db.Model):
|
||||||
player: Mapped["Player"] = relationship(back_populates="teams")
|
player: Mapped["Player"] = relationship(back_populates="teams")
|
||||||
team: Mapped["Team"] = relationship(back_populates="players")
|
team: Mapped["Team"] = relationship(back_populates="players")
|
||||||
|
|
||||||
player_roles: Mapped[List["PlayerTeamRole"]] = relationship(back_populates="player_team")
|
player_roles: Mapped[List["PlayerTeamRole"]] = relationship("PlayerTeamRole", back_populates="player_team")
|
||||||
availability: Mapped[List["PlayerTeamAvailability"]] = relationship(back_populates="player_team")
|
availability: Mapped[List["PlayerTeamAvailability"]] = relationship(back_populates="player_team")
|
||||||
|
|
||||||
team_role: Mapped[TeamRole] = mapped_column(Enum(TeamRole), default=TeamRole.Player)
|
team_role: Mapped[TeamRole] = mapped_column(Enum(TeamRole), default=TeamRole.Player)
|
||||||
playtime: Mapped[timedelta] = mapped_column(Interval)
|
playtime: Mapped[timedelta] = mapped_column(Interval, default=timedelta(0))
|
||||||
|
is_team_leader: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||||
created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now())
|
created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now())
|
||||||
|
|
||||||
|
class PlayerTeamSchema(spec.BaseModel):
|
||||||
|
player: PlayerSchema
|
||||||
|
team: TeamSchema
|
||||||
|
|
||||||
class PlayerTeamRole(db.Model):
|
class PlayerTeamRole(db.Model):
|
||||||
__tablename__ = "players_teams_roles"
|
__tablename__ = "players_teams_roles"
|
||||||
|
|
||||||
|
@ -107,7 +113,7 @@ class PlayerTeamRole(db.Model):
|
||||||
|
|
||||||
#player: Mapped["Player"] = relationship(back_populates="teams")
|
#player: Mapped["Player"] = relationship(back_populates="teams")
|
||||||
|
|
||||||
role: Mapped[Role] = mapped_column(Enum(Role))
|
role: Mapped[Role] = mapped_column(Enum(Role), primary_key=True)
|
||||||
is_main: Mapped[bool] = mapped_column(Boolean)
|
is_main: Mapped[bool] = mapped_column(Boolean)
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
|
@ -125,7 +131,7 @@ class PlayerTeamAvailability(db.Model):
|
||||||
start_time: Mapped[datetime] = mapped_column(UtcDateTime, primary_key=True)
|
start_time: Mapped[datetime] = mapped_column(UtcDateTime, primary_key=True)
|
||||||
|
|
||||||
player_team: Mapped["PlayerTeam"] = relationship(
|
player_team: Mapped["PlayerTeam"] = relationship(
|
||||||
"PlayerTeam",back_populates="availability")
|
"PlayerTeam", back_populates="availability")
|
||||||
|
|
||||||
availability: Mapped[int] = mapped_column(Integer, default=2)
|
availability: Mapped[int] = mapped_column(Integer, default=2)
|
||||||
end_time: Mapped[datetime] = mapped_column(UtcDateTime)
|
end_time: Mapped[datetime] = mapped_column(UtcDateTime)
|
||||||
|
@ -154,6 +160,7 @@ class AuthSession(db.Model):
|
||||||
|
|
||||||
def init_db(app: Flask):
|
def init_db(app: Flask):
|
||||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db.sqlite3"
|
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db.sqlite3"
|
||||||
|
#app.config["SQLALCHEMY_ECHO"] = True
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
migrate.init_app(app, db)
|
migrate.init_app(app, db)
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -18,3 +18,5 @@ alembic
|
||||||
Flask-Migrate
|
Flask-Migrate
|
||||||
|
|
||||||
requests
|
requests
|
||||||
|
|
||||||
|
pytz # timezone handling
|
||||||
|
|
|
@ -2,6 +2,7 @@ import datetime
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from flask import Blueprint, abort, jsonify, make_response, request
|
from flask import Blueprint, abort, jsonify, make_response, request
|
||||||
from flask_pydantic import validate
|
from flask_pydantic import validate
|
||||||
|
from spectree import Response
|
||||||
from models import Player, PlayerTeam, PlayerTeamAvailability, PlayerTeamRole, db
|
from models import Player, PlayerTeam, PlayerTeamAvailability, PlayerTeamRole, db
|
||||||
from middleware import requires_authentication
|
from middleware import requires_authentication
|
||||||
from spec import spec, BaseModel
|
from spec import spec, BaseModel
|
||||||
|
@ -14,8 +15,15 @@ class ViewScheduleForm(BaseModel):
|
||||||
team_id: int
|
team_id: int
|
||||||
window_size_days: int = 7
|
window_size_days: int = 7
|
||||||
|
|
||||||
|
class ViewScheduleResponse(BaseModel):
|
||||||
|
availability: list[int]
|
||||||
|
|
||||||
@api_schedule.get("/")
|
@api_schedule.get("/")
|
||||||
@spec.validate()
|
@spec.validate(
|
||||||
|
resp=Response(
|
||||||
|
HTTP_200=ViewScheduleResponse
|
||||||
|
)
|
||||||
|
)
|
||||||
@requires_authentication
|
@requires_authentication
|
||||||
def get(query: ViewScheduleForm, player: Player, **kwargs):
|
def get(query: ViewScheduleForm, player: Player, **kwargs):
|
||||||
window_start = query.window_start
|
window_start = query.window_start
|
||||||
|
@ -174,7 +182,7 @@ def put(json: PutScheduleForm, player: Player, **kwargs):
|
||||||
|
|
||||||
db.session.add_all(availability_blocks)
|
db.session.add_all(availability_blocks)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return make_response({ }, 300)
|
return make_response({ }, 200)
|
||||||
|
|
||||||
class ViewAvailablePlayersForm(BaseModel):
|
class ViewAvailablePlayersForm(BaseModel):
|
||||||
start_time: datetime.datetime
|
start_time: datetime.datetime
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import pydantic.v1
|
||||||
|
from spectree import SpecTree
|
||||||
|
from pydantic.alias_generators import to_camel
|
||||||
|
from spectree.plugins.flask_plugin import FlaskPlugin
|
||||||
|
|
||||||
|
# Naming convention:
|
||||||
|
# https://github.com/0b01001001/spectree/issues/300
|
||||||
|
# https://github.com/0b01001001/spectree/pull/302
|
||||||
|
def naming_strategy(model):
|
||||||
|
return model.__name__
|
||||||
|
|
||||||
|
# https://github.com/0b01001001/spectree/issues/304#issuecomment-1519961668
|
||||||
|
def nested_naming_strategy(_, child):
|
||||||
|
return child
|
||||||
|
|
||||||
|
spec = SpecTree(
|
||||||
|
"flask",
|
||||||
|
annotations=True,
|
||||||
|
naming_strategy=naming_strategy,
|
||||||
|
nested_naming_strategy=nested_naming_strategy
|
||||||
|
)
|
||||||
|
|
||||||
|
class BaseModel(pydantic.v1.BaseModel):
|
||||||
|
class Config:
|
||||||
|
alias_generator = to_camel
|
||||||
|
allow_population_by_field_name = True
|
||||||
|
|
|
@ -1,27 +1,174 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
import time
|
||||||
from typing import List
|
from typing import List
|
||||||
from flask import Blueprint, abort, jsonify, make_response, request
|
from flask import Blueprint, abort, jsonify, make_response, request
|
||||||
import pydantic
|
import pydantic
|
||||||
|
from pydantic.v1 import validator
|
||||||
from flask_pydantic import validate
|
from flask_pydantic import validate
|
||||||
from spectree import Response
|
from spectree import Response
|
||||||
from models import Player, PlayerTeam, Team, TeamSpec, db
|
from sqlalchemy.orm import joinedload, subqueryload
|
||||||
|
from models import Player, PlayerSchema, PlayerTeam, PlayerTeamAvailability, PlayerTeamRole, PlayerTeamSchema, Team, TeamSchema, db
|
||||||
from middleware import requires_authentication
|
from middleware import requires_authentication
|
||||||
import models
|
import models
|
||||||
from spec import spec, BaseModel
|
from spec import spec, BaseModel
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
|
||||||
api_team = Blueprint("team", __name__, url_prefix="/team")
|
api_team = Blueprint("team", __name__, url_prefix="/team")
|
||||||
|
|
||||||
|
def map_team_to_schema(team: Team):
|
||||||
|
return TeamSchema(
|
||||||
|
id=team.id,
|
||||||
|
team_name=team.team_name,
|
||||||
|
discord_webhook_url=None
|
||||||
|
)
|
||||||
|
|
||||||
|
def map_player_to_schema(player: Player):
|
||||||
|
return PlayerSchema(
|
||||||
|
steam_id=str(player.steam_id),
|
||||||
|
username=player.username,
|
||||||
|
)
|
||||||
|
|
||||||
class CreateTeamJson(BaseModel):
|
class CreateTeamJson(BaseModel):
|
||||||
team_name: str
|
team_name: str
|
||||||
webhook_url: str
|
discord_webhook_url: str | None = None
|
||||||
timezone: str
|
league_timezone: str
|
||||||
|
|
||||||
|
@validator("league_timezone")
|
||||||
|
@classmethod
|
||||||
|
def validate_timezone(cls, v):
|
||||||
|
if v not in pytz.all_timezones:
|
||||||
|
raise ValueError(v + " is not a valid timezone")
|
||||||
|
return v
|
||||||
|
|
||||||
|
@validator("team_name")
|
||||||
|
@classmethod
|
||||||
|
def validate_team_name(cls, v: str):
|
||||||
|
if not v:
|
||||||
|
raise ValueError("Team name can not be blank")
|
||||||
|
return v
|
||||||
|
|
||||||
class ViewTeamResponse(BaseModel):
|
class ViewTeamResponse(BaseModel):
|
||||||
team: models.TeamSpec
|
team: models.TeamSchema
|
||||||
|
|
||||||
class ViewTeamsResponse(BaseModel):
|
class ViewTeamsResponse(BaseModel):
|
||||||
teams: list[models.TeamSpec]
|
teams: list[models.TeamSchema]
|
||||||
|
|
||||||
|
@api_team.post("/")
|
||||||
|
@spec.validate(
|
||||||
|
resp=Response(
|
||||||
|
HTTP_200=ViewTeamResponse,
|
||||||
|
HTTP_403=None,
|
||||||
|
),
|
||||||
|
operation_id="create_team"
|
||||||
|
)
|
||||||
|
@requires_authentication
|
||||||
|
def create_team(json: CreateTeamJson, player: Player, **kwargs):
|
||||||
|
team = Team(
|
||||||
|
team_name=json.team_name,
|
||||||
|
tz_timezone=json.league_timezone,
|
||||||
|
)
|
||||||
|
if json.discord_webhook_url:
|
||||||
|
team.discord_webhook_url = json.discord_webhook_url
|
||||||
|
|
||||||
|
db.session.add(team)
|
||||||
|
db.session.flush() # flush, so we can get autoincremented id
|
||||||
|
|
||||||
|
player_team = PlayerTeam(
|
||||||
|
player_id=player.steam_id,
|
||||||
|
team_id=team.id,
|
||||||
|
is_team_leader=True
|
||||||
|
)
|
||||||
|
db.session.add(player_team)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
response = ViewTeamResponse(team=map_team_to_schema(team))
|
||||||
|
return jsonify(response.dict())
|
||||||
|
|
||||||
|
@api_team.delete("/id/<team_id>/")
|
||||||
|
@spec.validate(
|
||||||
|
resp=Response(
|
||||||
|
HTTP_200=None,
|
||||||
|
HTTP_403=None,
|
||||||
|
HTTP_404=None,
|
||||||
|
),
|
||||||
|
operation_id="delete_team"
|
||||||
|
)
|
||||||
|
def delete_team(player: Player, team_id: int):
|
||||||
|
player_team = db.session.query(
|
||||||
|
PlayerTeam
|
||||||
|
).where(
|
||||||
|
PlayerTeam.team_id == team_id
|
||||||
|
).where(
|
||||||
|
PlayerTeam.player_id == player.steam_id
|
||||||
|
).one_or_none()
|
||||||
|
|
||||||
|
if not player_team:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
if not player_team.is_team_leader:
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
db.session.delete(player_team.team)
|
||||||
|
db.session.commit()
|
||||||
|
return make_response(200)
|
||||||
|
|
||||||
|
class AddPlayerJson(BaseModel):
|
||||||
|
team_role: PlayerTeam.TeamRole = PlayerTeam.TeamRole.Player
|
||||||
|
is_team_leader: bool = False
|
||||||
|
|
||||||
|
@api_team.put("/id/<team_id>/player/<player_id>/")
|
||||||
|
@spec.validate(
|
||||||
|
resp=Response(
|
||||||
|
HTTP_200=None,
|
||||||
|
HTTP_403=None,
|
||||||
|
HTTP_404=None,
|
||||||
|
),
|
||||||
|
operation_id="create_or_update_player"
|
||||||
|
)
|
||||||
|
def add_player(player: Player, team_id: int, player_id: int, json: AddPlayerJson):
|
||||||
|
player_team = db.session.query(
|
||||||
|
PlayerTeam
|
||||||
|
).where(
|
||||||
|
PlayerTeam.player_id == player.steam_id
|
||||||
|
).where(
|
||||||
|
PlayerTeam.team_id == team_id
|
||||||
|
).one_or_none()
|
||||||
|
|
||||||
|
if not player_team:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
if not player_team.is_team_leader:
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
target_player_team = db.session.query(
|
||||||
|
PlayerTeam
|
||||||
|
).where(
|
||||||
|
PlayerTeam.player_id == player_id
|
||||||
|
).where(
|
||||||
|
PlayerTeam.team_id == team_id
|
||||||
|
).one_or_none()
|
||||||
|
|
||||||
|
if not target_player_team:
|
||||||
|
target_player = db.session.query(
|
||||||
|
Player
|
||||||
|
).where(
|
||||||
|
Player.steam_id == player_id
|
||||||
|
).one_or_none()
|
||||||
|
|
||||||
|
if not target_player:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
target_player_team = PlayerTeam()
|
||||||
|
target_player_team.player_id = player_id
|
||||||
|
target_player_team.team_id = player_team.team_id
|
||||||
|
|
||||||
|
target_player_team.team_role = json.team_role
|
||||||
|
target_player_team.is_team_leader = json.is_team_leader
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
return make_response(200)
|
||||||
|
|
||||||
@api_team.get("/all/")
|
@api_team.get("/all/")
|
||||||
@spec.validate(
|
@spec.validate(
|
||||||
|
@ -29,7 +176,8 @@ class ViewTeamsResponse(BaseModel):
|
||||||
HTTP_200=ViewTeamsResponse,
|
HTTP_200=ViewTeamsResponse,
|
||||||
HTTP_403=None,
|
HTTP_403=None,
|
||||||
HTTP_404=None,
|
HTTP_404=None,
|
||||||
)
|
),
|
||||||
|
operation_id="get_teams"
|
||||||
)
|
)
|
||||||
@requires_authentication
|
@requires_authentication
|
||||||
def view_teams(**kwargs):
|
def view_teams(**kwargs):
|
||||||
|
@ -45,7 +193,8 @@ def view_teams(**kwargs):
|
||||||
HTTP_200=ViewTeamResponse,
|
HTTP_200=ViewTeamResponse,
|
||||||
HTTP_403=None,
|
HTTP_403=None,
|
||||||
HTTP_404=None,
|
HTTP_404=None,
|
||||||
)
|
),
|
||||||
|
operation_id="get_team"
|
||||||
)
|
)
|
||||||
@requires_authentication
|
@requires_authentication
|
||||||
def view_team(team_id: int, **kwargs):
|
def view_team(team_id: int, **kwargs):
|
||||||
|
@ -69,21 +218,82 @@ def fetch_teams_for_player(player: Player, team_id: int | None):
|
||||||
if team_id is not None:
|
if team_id is not None:
|
||||||
q = q.where(PlayerTeam.team_id == team_id)
|
q = q.where(PlayerTeam.team_id == team_id)
|
||||||
|
|
||||||
def map_team_to_spec(team: Team) -> TeamSpec:
|
|
||||||
return TeamSpec(
|
|
||||||
id=team.id,
|
|
||||||
team_name=team.team_name,
|
|
||||||
discord_webhook_url=None
|
|
||||||
)
|
|
||||||
|
|
||||||
if team_id is None:
|
if team_id is None:
|
||||||
teams = q.all()
|
teams = q.all()
|
||||||
return ViewTeamsResponse(
|
return ViewTeamsResponse(
|
||||||
teams=list(map(map_team_to_spec, teams))
|
teams=list(map(map_team_to_schema, teams))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
team = q.one_or_none()
|
team = q.one_or_none()
|
||||||
if team:
|
if team:
|
||||||
return ViewTeamResponse(
|
return ViewTeamResponse(
|
||||||
team=map_team_to_spec(team)
|
team=map_team_to_schema(team)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class ViewTeamMembersResponse(PlayerSchema):
|
||||||
|
class RoleSchema(BaseModel):
|
||||||
|
role: str
|
||||||
|
is_main: bool
|
||||||
|
|
||||||
|
roles: list[RoleSchema]
|
||||||
|
availability: int
|
||||||
|
playtime: float
|
||||||
|
created_at: datetime.datetime
|
||||||
|
|
||||||
|
@api_team.get("/id/<team_id>/players")
|
||||||
|
@spec.validate(
|
||||||
|
resp=Response(
|
||||||
|
HTTP_200=list[ViewTeamMembersResponse],
|
||||||
|
HTTP_403=None,
|
||||||
|
HTTP_404=None,
|
||||||
|
),
|
||||||
|
operation_id="get_team_members"
|
||||||
|
)
|
||||||
|
@requires_authentication
|
||||||
|
def view_team_members(player: Player, team_id: int, **kwargs):
|
||||||
|
now = datetime.datetime.now(datetime.timezone.utc)
|
||||||
|
|
||||||
|
player_teams_query = db.session.query(
|
||||||
|
PlayerTeam
|
||||||
|
).where(
|
||||||
|
PlayerTeam.team_id == team_id
|
||||||
|
).options(
|
||||||
|
# eager load so SQLAlchemy does not make a second query just to join
|
||||||
|
joinedload(PlayerTeam.player),
|
||||||
|
joinedload(PlayerTeam.player_roles),
|
||||||
|
joinedload(PlayerTeam.availability.and_(
|
||||||
|
(PlayerTeamAvailability.start_time <= now) &
|
||||||
|
(PlayerTeamAvailability.end_time > now)
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
player_teams = player_teams_query.all()
|
||||||
|
|
||||||
|
if not next(filter(lambda x: x.player_id == player.steam_id, player_teams)):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
def map_role_to_schema(player_team_role: PlayerTeamRole):
|
||||||
|
return ViewTeamMembersResponse.RoleSchema(
|
||||||
|
role=str(player_team_role.role),
|
||||||
|
is_main=player_team_role.is_main,
|
||||||
|
)
|
||||||
|
|
||||||
|
def map_to_response(player_team: PlayerTeam):
|
||||||
|
roles = player_team.player_roles
|
||||||
|
player = player_team.player
|
||||||
|
|
||||||
|
availability = 0
|
||||||
|
if len(player_team.availability) > 0:
|
||||||
|
print(player_team.availability)
|
||||||
|
availability = player_team.availability[0].availability
|
||||||
|
|
||||||
|
return ViewTeamMembersResponse(
|
||||||
|
username=player.username,
|
||||||
|
steam_id=str(player.steam_id),
|
||||||
|
roles=list(map(map_role_to_schema, roles)),
|
||||||
|
availability=availability,
|
||||||
|
playtime=player_team.playtime.total_seconds() / 3600,
|
||||||
|
created_at=player_team.created_at,
|
||||||
|
)
|
||||||
|
|
||||||
|
return list(map(map_to_response, player_teams))
|
||||||
|
|
Loading…
Reference in New Issue