#! /usr/bin/env python3 # vim:fenc=utf-8 # # Copyright © 2024 sandvich # # Distributed under terms of the MIT license. from datetime import datetime from typing import Optional from flask import Blueprint, abort, make_response from spectree import Response from sqlalchemy import Row from sqlalchemy.sql import tuple_ from models.player import Player from models.player_event import EventWithPlayerSchema, PlayerEvent, PlayerEventRolesSchema from models.player_team_availability import PlayerTeamAvailability from models.player_team_role import PlayerRoleSchema, PlayerTeamRole from models.team import Team from spec import BaseModel, spec from middleware import assert_team_authority, assert_team_membership, requires_authentication, requires_team_membership from models.event import Event, EventSchema from models.player_team import PlayerTeam from app_db import db api_events = Blueprint("events", __name__, url_prefix="/events") @api_events.get("/") @spec.validate( resp=Response( HTTP_200=EventSchema, ), operation_id="get_event", ) def get_event(event_id: int): event = db.session.query(Event).filter(Event.id == event_id).one_or_none() if not event: abort(404) return EventSchema.from_model(event).dict(by_alias=True) @api_events.get("/team/id/") @spec.validate( resp=Response( HTTP_200=list[EventWithPlayerSchema], ), operation_id="get_team_events", ) @requires_authentication @requires_team_membership() def get_team_events(player_team: PlayerTeam, team_id: int, **_): rows = db.session.query( Event, PlayerEvent ).outerjoin( PlayerEvent, (PlayerEvent.event_id == Event.id) & (PlayerEvent.player_id == player_team.player_id) ).where( Event.team_id == team_id ).order_by( Event.start_time ).all() def map_to_schema(row: Row[tuple[Event, PlayerEvent]]): return EventWithPlayerSchema.from_event_player_event( *row.tuple() ).dict(by_alias=True) return list(map(map_to_schema, rows)) @api_events.get("/user/id/") def get_user_events(user_id: int): raise NotImplementedError() class CreateEventJson(BaseModel): name: str description: Optional[str] start_time: datetime player_roles: list[PlayerRoleSchema] class UpdateEventJson(BaseModel): name: str description: Optional[str] player_roles: list[PlayerRoleSchema] @api_events.post("/team/id/") @spec.validate( resp=Response( HTTP_200=EventSchema, ), operation_id="create_event", ) @requires_authentication @requires_team_membership() def create_event(player_team: PlayerTeam, team_id: int, json: CreateEventJson, **_): event = Event() event.team_id = player_team.team_id event.name = json.name if json.description: event.description = json.description event.start_time = json.start_time assert_team_authority(player_team) db.session.add(event) db.session.flush() db.session.refresh(event) tuples = map(lambda x: (x.player.steam_id, x.role.role), json.player_roles) results = db.session.query( PlayerTeam, PlayerTeamRole.id, PlayerTeamAvailability.availability ).join( PlayerTeamRole ).outerjoin( PlayerTeamAvailability, (PlayerTeamAvailability.player_team_id == PlayerTeam.id) & (PlayerTeamAvailability.start_time <= event.start_time) & (PlayerTeamAvailability.end_time > event.start_time) ).where( PlayerTeam.team_id == team_id ).where( # (player_id, role) in (...) tuple_(PlayerTeam.player_id, PlayerTeamRole.role).in_(tuples) ).all() for player_team, role_id, availability in map(lambda x: x.tuple(), results): player_event = PlayerEvent() player_event.player_id = player_team.player_id player_event.event_id = event.id player_event.player_team_role_id = role_id # autoconfirm if availability = 2 player_event.has_confirmed = (availability == 2) db.session.add(player_event) db.session.commit() event.update_discord_message() return EventSchema.from_model(event).dict(by_alias=True), 200 class AttendanceJson(BaseModel): confirm: bool @api_events.put("//attendance") @spec.validate( resp=Response( HTTP_200=EventWithPlayerSchema, ), operation_id="attend_event", ) @requires_authentication def attend_event(player: Player, event_id: int, json: AttendanceJson, **_): event = db.session.query(Event).where(Event.id == event_id).one_or_none() if not event: abort(404) assert_team_membership(player, event.team) player_event = db.session.query( PlayerEvent ).where( PlayerEvent.event_id == event_id ).where( PlayerEvent.player_id == player.steam_id ).join( Event ).one_or_none() if not player_event: player_event = PlayerEvent() player_event.event_id = event_id player_event.player_id = player.steam_id db.session.add(player_event) player_event.has_confirmed = json.confirm db.session.commit() player_event.event.update_discord_message() return EventWithPlayerSchema.from_event_player_event( player_event.event, player_event, ).dict(by_alias=True) @api_events.delete("//attendance") @spec.validate( resp=Response( HTTP_200=EventWithPlayerSchema, ), operation_id="unattend_event", ) @requires_authentication def unattend_event(player: Player, event_id: int, **_): result = db.session.query( PlayerEvent, Event ).where( PlayerEvent.player_id == player.steam_id ).join( Event ).where( Event.id == event_id ).one_or_none() if not result: abort(404) player_event, event = result.tuple() db.session.delete(player_event) db.session.commit() event.update_discord_message() return EventWithPlayerSchema.from_event_player_event( event, None, ).dict(by_alias=True) class GetEventPlayersResponse(BaseModel): players: list[PlayerEventRolesSchema] @api_events.get("//players") @spec.validate( resp=Response( HTTP_200=GetEventPlayersResponse, ), operation_id="get_event_players", ) @requires_authentication def get_event_players(player: Player, event_id: int, **_): event = db.session.query(Event).where(Event.id == event_id).one_or_none() if not event: abort(404) assert_team_membership(player, event.team) players_events = db.session.query( PlayerEvent ).join( Event, Event.id == PlayerEvent.event_id ).join( PlayerTeam, PlayerTeam.team_id == Event.team_id & PlayerEvent.player_id == PlayerTeam.player_id ).where( PlayerEvent.event_id == event_id ).all() player_event_roles = [ PlayerEventRolesSchema.from_event_player_team( player_event, player_event.player_team ) for player_event in players_events ] return GetEventPlayersResponse( players=player_event_roles ).dict(by_alias=True), 200 @api_events.patch("/") @spec.validate( resp=Response( HTTP_200=EventSchema, ), operation_id="update_event", ) @requires_authentication def update_event(player: Player, event_id: int, json: UpdateEventJson, **_): event = db.session.query(Event).where(Event.id == event_id).one_or_none() if not event: abort(404) player_team = assert_team_membership(player, event.team) assert_team_authority(player_team) for player_event in event.players: player_team = player_event.player_team roles = player_team.player_roles # assign new roles if in json, set to None if not new_role = next((x.role for x in json.player_roles if x.player.steam_id == str(player_event.player_id)), None) #import sys #print(player_event.player_id, file=sys.stderr) # if valid role (new_role exists in roles), update player_event.role if new_role and (role_entity := next((x for x in roles if x.role.name == new_role.role), None)): player_event.player_team_role_id = role_entity.id else: player_event.role = None event.name = json.name event.description = json.description db.session.commit() event.update_discord_message() return EventSchema.from_model(event).dict(by_alias=True), 200 @api_events.delete("/") @spec.validate( resp=Response( HTTP_204=None, ), operation_id="delete_event", ) @requires_authentication def delete_event(player: Player, event_id: int, **_): event = db.session.query(Event).where(Event.id == event_id).one_or_none() if not event: abort(404) player_team = assert_team_membership(player, event.team) assert_team_authority(player_team) db.session.delete(event) db.session.commit() return make_response({ }, 204)