From 8a00c53479b483a8d4e2d3bb753c38b4f3cc6dbf Mon Sep 17 00:00:00 2001 From: HumanoidSandvichDispenser Date: Mon, 18 Nov 2024 18:28:24 -0800 Subject: [PATCH] Add team integration endpoints - Add new endpoints for managing team integrations: - GET /id//integrations - POST /id//integrations/ - DELETE /id//integrations/ - PATCH /id//integrations/ - Introduce schemas for TeamIntegration and TeamDiscordIntegration - Update models to include nullable webhook_url --- backend-flask/models/team_integration.py | 29 +++++- backend-flask/team.py | 127 +++++++++++++++++++++++ 2 files changed, 155 insertions(+), 1 deletion(-) diff --git a/backend-flask/models/team_integration.py b/backend-flask/models/team_integration.py index b48d0c2..32c50a7 100644 --- a/backend-flask/models/team_integration.py +++ b/backend-flask/models/team_integration.py @@ -1,8 +1,10 @@ +#from typing import cast, override from sqlalchemy.orm import mapped_column, relationship from sqlalchemy.orm.attributes import Mapped from sqlalchemy.orm.properties import ForeignKey from sqlalchemy.types import Integer, String import app_db +import spec class TeamIntegration(app_db.BaseModel): @@ -23,10 +25,35 @@ class TeamDiscordIntegration(TeamIntegration): __tablename__ = "team_discord_integrations" integration_id: Mapped[int] = mapped_column(ForeignKey("team_integrations.id"), primary_key=True) - webhook_url: Mapped[str] = mapped_column(String(255)) + webhook_url: Mapped[str] = mapped_column(String(255), nullable=True) __mapper_args__ = { "polymorphic_identity": "team_discord_integrations", } +class TeamIntegrationSchema(spec.BaseModel): + id: int + team_id: int + integration_type: str + + @classmethod + def from_model(cls, model: TeamIntegration): + if model.integration_type == "team_discord_integrations": + if isinstance(model, TeamDiscordIntegration): + return TeamDiscordIntegrationSchema._from_model_discord(model) + raise TypeError() + +class TeamDiscordIntegrationSchema(TeamIntegrationSchema): + webhook_url: str + + @classmethod + def _from_model_discord(cls, model: TeamDiscordIntegration): + assert model.integration_id != None + return cls( + id=model.integration_id, + team_id=model.team_id, + integration_type=model.integration_type, + webhook_url=model.webhook_url + ) + from models.team import Team diff --git a/backend-flask/team.py b/backend-flask/team.py index 1e63e51..1cb05f9 100644 --- a/backend-flask/team.py +++ b/backend-flask/team.py @@ -14,6 +14,7 @@ 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 TeamDiscordIntegration, TeamDiscordIntegrationSchema, TeamIntegration, TeamIntegrationSchema from middleware import assert_team_authority, requires_authentication, requires_team_membership import models from spec import spec, BaseModel @@ -561,3 +562,129 @@ def revoke_invite(player: Player, team_id: int, key: str, **kwargs): db.session.delete(invite) db.session.commit() return make_response({ }, 204) + +@api_team.get("/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//integrations/") +@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//integrations/") +@spec.validate( + resp=Response( + HTTP_204=None, + ), + operation_id="delete_integration" +) +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//integrations/") +@spec.validate( + resp=Response( + HTTP_200=TeamIntegrationSchema, + ), + operation_id="update_integration" +) +def update_integration( + player_team: PlayerTeam, + integration_id: int, + json: TeamIntegrationSchema, + **_ +): + 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 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