refactor: Split team routes

The team routes have been split into separate blueprints for better
modularity and maintainability. The team invite and team integration
routes are now handled by their respective blueprints.
master
John Montagu, the 4th Earl of Sandvich 2024-11-19 16:34:51 -08:00
parent 36591726b9
commit 3394f2271e
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
3 changed files with 312 additions and 288 deletions

View File

@ -1,27 +1,25 @@
from datetime import UTC, datetime, timedelta, timezone
from random import randint, random
import sys
import time
from typing import List, cast
from flask import Blueprint, abort, jsonify, make_response, request
from datetime import datetime, timedelta, timezone
from typing import cast
from flask import Blueprint, abort, make_response
from pydantic.v1 import validator
from spectree import Response
from sqlalchemy.orm import joinedload, subqueryload
from sqlalchemy.orm import joinedload
from app_db import db
from models.player import Player, PlayerSchema
from models.player_team import PlayerTeam
from models.player_team_availability import PlayerTeamAvailability
from models.player_team_role import PlayerTeamRole, RoleSchema
from models.team import Team, TeamSchema
from models.team_invite import TeamInvite, TeamInviteSchema
from models.team_integration import AbstractTeamIntegrationSchema, TeamDiscordIntegration, TeamDiscordIntegrationSchema, TeamIntegration, TeamIntegrationSchema
from middleware import assert_team_authority, requires_authentication, requires_team_membership
import models
from middleware import requires_authentication
from spec import spec, BaseModel
from team_invite import api_team_invite
from team_integration import api_team_integration
import pytz
api_team = Blueprint("team", __name__, url_prefix="/team")
api_team.register_blueprint(api_team_invite)
api_team.register_blueprint(api_team_integration)
def map_player_to_schema(player: Player):
return PlayerSchema(
@ -419,280 +417,3 @@ def edit_member_roles(
db.session.commit()
return make_response({ }, 204)
@api_team.get("/id/<team_id>/invite")
@spec.validate(
resp=Response(
HTTP_200=list[TeamInviteSchema],
HTTP_404=None,
),
operation_id="get_invites"
)
@requires_authentication
@requires_team_membership
def get_invites(team_id: int, **_):
invites = db.session.query(
TeamInvite
).where(
TeamInvite.team_id == team_id
).all()
def map_invite_to_schema(invite: TeamInvite):
return TeamInviteSchema(
key=invite.key,
team_id=invite.team_id,
created_at=invite.created_at,
).dict(by_alias=True)
return list(map(map_invite_to_schema, invites)), 200
@api_team.post("/id/<team_id>/invite")
@spec.validate(
resp=Response(
HTTP_200=TeamInviteSchema,
HTTP_404=None,
),
operation_id="create_invite"
)
@requires_authentication
@requires_team_membership
def create_invite(team_id: int, **_):
team_id_shifted = int(team_id) << 48
random_value_shifted = int(randint(0, (1 << 16) - 1)) << 32
timestamp = int(time.time()) & ((1 << 32) - 1)
key_int = timestamp | team_id_shifted | random_value_shifted
key_hex = "%0.16X" % key_int
invite = TeamInvite()
invite.team_id = team_id
invite.key = key_hex
db.session.add(invite)
db.session.flush()
db.session.refresh(invite)
response = TeamInviteSchema(
key=key_hex,
team_id=team_id,
created_at=invite.created_at
)
db.session.commit()
return response.dict(by_alias=True), 200
@api_team.post("/id/<team_id>/consume-invite/<key>")
@spec.validate(
resp=Response(
HTTP_204=None,
HTTP_404=None,
),
operation_id="consume_invite"
)
@requires_authentication
def consume_invite(player: Player, team_id: int, key: str, **kwargs):
invite = db.session.query(
TeamInvite
).where(
TeamInvite.team_id == team_id
).where(
TeamInvite.key == key
).one_or_none()
if not invite:
abort(404)
player_team = db.session.query(
PlayerTeam
).where(
PlayerTeam.player_id == player.steam_id
).where(
PlayerTeam.team_id == team_id
).one_or_none()
if player_team:
abort(409)
player_team = PlayerTeam()
player_team.player = player
player_team.team_id = team_id
db.session.add(player_team)
if invite.delete_on_use:
db.session.delete(invite)
db.session.commit()
return make_response({ }, 204)
@api_team.delete("/id/<team_id>/invite/<key>")
@spec.validate(
resp=Response(
HTTP_204=None,
HTTP_404=None,
),
operation_id="revoke_invite"
)
@requires_authentication
def revoke_invite(player: Player, team_id: int, key: str, **kwargs):
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)
invite = db.session.query(
TeamInvite
).where(
TeamInvite.team_id == team_id
).where(
TeamInvite.key == key
).one_or_none()
if not invite:
abort(404)
db.session.delete(invite)
db.session.commit()
return make_response({ }, 204)
@api_team.get("/id/<team_id>/integrations")
@spec.validate(
resp=Response(
HTTP_200=list[TeamIntegrationSchema],
HTTP_404=None,
),
operation_id="get_integrations"
)
@requires_authentication
def get_integrations(player: Player, team_id: int, **kwargs):
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)
integrations = db.session.query(
TeamIntegration
).where(
TeamIntegration.team_id == team_id
).all()
def map_integration_to_schema(integration: TeamIntegration):
return TeamIntegrationSchema.from_model(
integration
).dict(by_alias=True)
return list(map(map_integration_to_schema, integrations))
@api_team.post("/id/<team_id>/integrations/<integration_type>")
@spec.validate(
resp=Response(
HTTP_200=TeamIntegrationSchema,
),
operation_id="create_integration"
)
@requires_authentication
@requires_team_membership
def create_integration(player_team: PlayerTeam, integration_type: str, **_):
assert_team_authority(player_team)
if integration_type == "discord":
integration = TeamDiscordIntegration()
integration.team_id = player_team.team_id
integration.webhook_url = ""
else:
abort(404)
db.session.add(integration)
db.session.commit()
return TeamIntegrationSchema.from_model(
integration
).dict(by_alias=True), 200
@api_team.delete("/id/<team_id>/integrations/<integration_id>")
@spec.validate(
resp=Response(
HTTP_204=None,
),
operation_id="delete_integration"
)
@requires_authentication
@requires_team_membership
def delete_integration(player_team: PlayerTeam, integration_id: int, **_):
assert_team_authority(player_team)
integration = db.session.query(
TeamIntegration
).where(
TeamIntegration.team_id == player_team.team_id
).where(
TeamIntegration.id == integration_id
).one_or_none()
if not integration:
abort(404)
db.session.delete(integration)
db.session.commit()
return make_response({ }, 204)
@api_team.patch("/id/<team_id>/integrations/<integration_id>")
@spec.validate(
resp=Response(
HTTP_200=TeamIntegrationSchema,
),
operation_id="update_integration"
)
@requires_authentication
@requires_team_membership
def update_integration(
player_team: PlayerTeam,
integration_id: int,
json: AbstractTeamIntegrationSchema,
**_
):
assert_team_authority(player_team)
integration = db.session.query(
TeamIntegration
).where(
TeamIntegration.team_id == player_team.team_id
).where(
TeamIntegration.id == integration_id
).one_or_none()
if not integration:
abort(404)
if isinstance(integration, TeamDiscordIntegration):
print(json.dict(), file=sys.stderr)
if json.__root__.integration_type == "team_discord_integrations":
discord_integration = cast(TeamDiscordIntegration, json.__root__)
integration.webhook_url = discord_integration.webhook_url
#if isinstance(json, TeamDiscordIntegrationSchema):
# integration.webhook_url = json.webhook_url
else:
abort(400)
else:
abort(404)
db.session.commit()
return TeamIntegrationSchema.from_model(
integration
).dict(by_alias=True), 200

View File

@ -0,0 +1,145 @@
from flask import Blueprint, abort, make_response
from spectree import Response
from typing import cast
from app_db import db
from middleware import assert_team_authority, requires_authentication, requires_team_membership
from models.player import Player
from models.player_team import PlayerTeam
from models.team_integration import AbstractTeamIntegrationSchema, TeamDiscordIntegration, TeamIntegration, TeamIntegrationSchema
from spec import spec
api_team_integration = Blueprint("team_integration", __name__)
@api_team_integration.get("/id/<team_id>/integrations")
@spec.validate(
resp=Response(
HTTP_200=list[TeamIntegrationSchema],
HTTP_404=None,
),
operation_id="get_integrations"
)
@requires_authentication
def get_integrations(player: Player, team_id: int, **_):
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)
integrations = db.session.query(
TeamIntegration
).where(
TeamIntegration.team_id == team_id
).all()
def map_integration_to_schema(integration: TeamIntegration):
return TeamIntegrationSchema.from_model(
integration
).dict(by_alias=True)
return list(map(map_integration_to_schema, integrations))
@api_team_integration.post("/id/<team_id>/integrations/<integration_type>")
@spec.validate(
resp=Response(
HTTP_200=TeamIntegrationSchema,
),
operation_id="create_integration"
)
@requires_authentication
@requires_team_membership
def create_integration(player_team: PlayerTeam, integration_type: str, **_):
assert_team_authority(player_team)
if integration_type == "discord":
integration = TeamDiscordIntegration()
integration.team_id = player_team.team_id
integration.webhook_url = ""
else:
abort(404)
db.session.add(integration)
db.session.commit()
return TeamIntegrationSchema.from_model(
integration
).dict(by_alias=True), 200
@api_team_integration.delete("/id/<team_id>/integrations/<integration_id>")
@spec.validate(
resp=Response(
HTTP_204=None,
),
operation_id="delete_integration"
)
@requires_authentication
@requires_team_membership
def delete_integration(player_team: PlayerTeam, integration_id: int, **_):
assert_team_authority(player_team)
integration = db.session.query(
TeamIntegration
).where(
TeamIntegration.team_id == player_team.team_id
).where(
TeamIntegration.id == integration_id
).one_or_none()
if not integration:
abort(404)
db.session.delete(integration)
db.session.commit()
return make_response({ }, 204)
@api_team_integration.patch("/id/<team_id>/integrations/<integration_id>")
@spec.validate(
resp=Response(
HTTP_200=TeamIntegrationSchema,
),
operation_id="update_integration"
)
@requires_authentication
@requires_team_membership
def update_integration(
player_team: PlayerTeam,
integration_id: int,
json: AbstractTeamIntegrationSchema,
**_
):
assert_team_authority(player_team)
integration = db.session.query(
TeamIntegration
).where(
TeamIntegration.team_id == player_team.team_id
).where(
TeamIntegration.id == integration_id
).one_or_none()
if not integration:
abort(404)
if isinstance(integration, TeamDiscordIntegration):
if json.__root__.integration_type == "team_discord_integrations":
discord_integration = cast(TeamDiscordIntegration, json.__root__)
integration.webhook_url = discord_integration.webhook_url
else:
abort(400)
else:
abort(404)
db.session.commit()
return TeamIntegrationSchema.from_model(
integration
).dict(by_alias=True), 200

View File

@ -0,0 +1,158 @@
from random import randint
from flask import Blueprint, abort, make_response
from spectree import Response
import time
from app_db import db
from middleware import requires_authentication, requires_team_membership
from models.player import Player
from models.player_team import PlayerTeam
from models.team_invite import TeamInvite, TeamInviteSchema
from spec import spec
api_team_invite = Blueprint("team_invite", __name__)
@api_team_invite.get("/id/<team_id>/invite")
@spec.validate(
resp=Response(
HTTP_200=list[TeamInviteSchema],
HTTP_404=None,
),
operation_id="get_invites"
)
@requires_authentication
@requires_team_membership
def get_invites(team_id: int, **_):
invites = db.session.query(
TeamInvite
).where(
TeamInvite.team_id == team_id
).all()
def map_invite_to_schema(invite: TeamInvite):
return TeamInviteSchema(
key=invite.key,
team_id=invite.team_id,
created_at=invite.created_at,
).dict(by_alias=True)
return list(map(map_invite_to_schema, invites)), 200
@api_team_invite.post("/id/<team_id>/invite")
@spec.validate(
resp=Response(
HTTP_200=TeamInviteSchema,
HTTP_404=None,
),
operation_id="create_invite"
)
@requires_authentication
@requires_team_membership
def create_invite(team_id: int, **_):
team_id_shifted = int(team_id) << 48
random_value_shifted = int(randint(0, (1 << 16) - 1)) << 32
timestamp = int(time.time()) & ((1 << 32) - 1)
key_int = timestamp | team_id_shifted | random_value_shifted
key_hex = "%0.16X" % key_int
invite = TeamInvite()
invite.team_id = team_id
invite.key = key_hex
db.session.add(invite)
db.session.flush()
db.session.refresh(invite)
response = TeamInviteSchema(
key=key_hex,
team_id=team_id,
created_at=invite.created_at
)
db.session.commit()
return response.dict(by_alias=True), 200
@api_team_invite.post("/id/<team_id>/consume-invite/<key>")
@spec.validate(
resp=Response(
HTTP_204=None,
HTTP_404=None,
),
operation_id="consume_invite"
)
@requires_authentication
def consume_invite(player: Player, team_id: int, key: str, **_):
invite = db.session.query(
TeamInvite
).where(
TeamInvite.team_id == team_id
).where(
TeamInvite.key == key
).one_or_none()
if not invite:
abort(404)
player_team = db.session.query(
PlayerTeam
).where(
PlayerTeam.player_id == player.steam_id
).where(
PlayerTeam.team_id == team_id
).one_or_none()
if player_team:
abort(409)
player_team = PlayerTeam()
player_team.player = player
player_team.team_id = team_id
db.session.add(player_team)
if invite.delete_on_use:
db.session.delete(invite)
db.session.commit()
return make_response({ }, 204)
@api_team_invite.delete("/id/<team_id>/invite/<key>")
@spec.validate(
resp=Response(
HTTP_204=None,
HTTP_404=None,
),
operation_id="revoke_invite"
)
@requires_authentication
def revoke_invite(player: Player, team_id: int, key: str, **_):
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)
invite = db.session.query(
TeamInvite
).where(
TeamInvite.team_id == team_id
).where(
TeamInvite.key == key
).one_or_none()
if not invite:
abort(404)
db.session.delete(invite)
db.session.commit()
return make_response({ }, 204)