Add functionality for viewing available teammates
parent
60f96f43f7
commit
325b3529fe
|
@ -14,6 +14,7 @@ export type { AddPlayerJson } from './models/AddPlayerJson';
|
|||
export type { CreateTeamJson } from './models/CreateTeamJson';
|
||||
export type { EditMemberRolesJson } from './models/EditMemberRolesJson';
|
||||
export type { PlayerSchema } from './models/PlayerSchema';
|
||||
export type { PlayerTeamAvailabilityRoleSchema } from './models/PlayerTeamAvailabilityRoleSchema';
|
||||
export type { PutScheduleForm } from './models/PutScheduleForm';
|
||||
export type { RoleSchema } from './models/RoleSchema';
|
||||
export type { TeamInviteSchema } from './models/TeamInviteSchema';
|
||||
|
@ -22,7 +23,8 @@ export { TeamRole } from './models/TeamRole';
|
|||
export type { TeamSchema } from './models/TeamSchema';
|
||||
export type { ValidationError } from './models/ValidationError';
|
||||
export type { ValidationErrorElement } from './models/ValidationErrorElement';
|
||||
export type { ViewAvailablePlayersForm } from './models/ViewAvailablePlayersForm';
|
||||
export type { ViewAvailablePlayersQuery } from './models/ViewAvailablePlayersQuery';
|
||||
export type { ViewAvailablePlayersResponse } from './models/ViewAvailablePlayersResponse';
|
||||
export type { ViewScheduleForm } from './models/ViewScheduleForm';
|
||||
export type { ViewScheduleResponse } from './models/ViewScheduleResponse';
|
||||
export type { ViewTeamMembersResponse } from './models/ViewTeamMembersResponse';
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { PlayerSchema } from './PlayerSchema';
|
||||
import type { RoleSchema } from './RoleSchema';
|
||||
export type PlayerTeamAvailabilityRoleSchema = {
|
||||
availability: number;
|
||||
player: PlayerSchema;
|
||||
playtime: number;
|
||||
roles: Array<RoleSchema>;
|
||||
};
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type ViewAvailablePlayersForm = {
|
||||
export type ViewAvailablePlayersQuery = {
|
||||
startTime: string;
|
||||
teamId: number;
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { PlayerTeamAvailabilityRoleSchema } from './PlayerTeamAvailabilityRoleSchema';
|
||||
export type ViewAvailablePlayersResponse = {
|
||||
players: Array<PlayerTeamAvailabilityRoleSchema>;
|
||||
};
|
||||
|
|
@ -9,6 +9,7 @@ import type { PlayerSchema } from '../models/PlayerSchema';
|
|||
import type { PutScheduleForm } from '../models/PutScheduleForm';
|
||||
import type { TeamInviteSchema } from '../models/TeamInviteSchema';
|
||||
import type { TeamInviteSchemaList } from '../models/TeamInviteSchemaList';
|
||||
import type { ViewAvailablePlayersResponse } from '../models/ViewAvailablePlayersResponse';
|
||||
import type { ViewScheduleResponse } from '../models/ViewScheduleResponse';
|
||||
import type { ViewTeamMembersResponseList } from '../models/ViewTeamMembersResponseList';
|
||||
import type { ViewTeamResponse } from '../models/ViewTeamResponse';
|
||||
|
@ -130,16 +131,16 @@ export class DefaultService {
|
|||
});
|
||||
}
|
||||
/**
|
||||
* view_available <GET>
|
||||
* view_available_at_time <GET>
|
||||
* @param startTime
|
||||
* @param teamId
|
||||
* @returns void
|
||||
* @returns ViewAvailablePlayersResponse OK
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getApiScheduleViewAvailable(
|
||||
public viewAvailableAtTime(
|
||||
startTime: string,
|
||||
teamId: number,
|
||||
): CancelablePromise<void> {
|
||||
): CancelablePromise<ViewAvailablePlayersResponse> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/schedule/view-available',
|
||||
|
@ -147,6 +148,9 @@ export class DefaultService {
|
|||
'startTime': startTime,
|
||||
'teamId': teamId,
|
||||
},
|
||||
errors: {
|
||||
422: `Unprocessable Entity`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
|
|
|
@ -3,11 +3,11 @@ export interface Player {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export interface PlayerTeamRole {
|
||||
steamId: number;
|
||||
export interface PlayerTeamRoleFlat {
|
||||
steamId: string;
|
||||
name: string;
|
||||
role: string;
|
||||
main: boolean;
|
||||
isMain: boolean;
|
||||
availability: number;
|
||||
playtime: number;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ const router = createRouter({
|
|||
component: ScheduleView
|
||||
},
|
||||
{
|
||||
path: "/schedule/roster",
|
||||
path: "/schedule/roster/:teamId/:startTime",
|
||||
name: "roster-builder",
|
||||
component: RosterBuilderView
|
||||
},
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { type Player, type PlayerTeamRole } from "@/player";
|
||||
import { type Player, type PlayerTeamRoleFlat } from "@/player";
|
||||
import { defineStore } from "pinia";
|
||||
import { computed, reactive, ref, type Reactive, type Ref } from "vue";
|
||||
import { useClientStore } from "./client";
|
||||
|
||||
export const useRosterStore = defineStore("roster", () => {
|
||||
const clientStore = useClientStore();
|
||||
const client = clientStore.client;
|
||||
|
||||
const neededRoles: Reactive<Array<String>> = reactive([
|
||||
"PocketScout",
|
||||
"FlankScout",
|
||||
|
@ -12,134 +16,31 @@ export const useRosterStore = defineStore("roster", () => {
|
|||
"Medic",
|
||||
]);
|
||||
|
||||
const selectedPlayers: Reactive<{ [key: string]: PlayerTeamRole }> = reactive({});
|
||||
const selectedPlayers: Reactive<{ [key: string]: PlayerTeamRoleFlat }> = reactive({});
|
||||
|
||||
const selectedRole: Ref<String | undefined> = ref(undefined);
|
||||
|
||||
const availablePlayers: Reactive<Array<PlayerTeamRole>> = reactive([
|
||||
const availablePlayers: Ref<Array<PlayerTeamRoleFlat>> = ref([
|
||||
{
|
||||
steamId: 2840,
|
||||
steamId: "342534598",
|
||||
name: "Wesker U",
|
||||
role: "Flank Scout",
|
||||
main: true,
|
||||
isMain: true,
|
||||
availability: 1,
|
||||
playtime: 35031,
|
||||
},
|
||||
{
|
||||
steamId: 2839,
|
||||
steamId: "342534298",
|
||||
name: "JustGetAHouse",
|
||||
role: "Flank Scout",
|
||||
main: false,
|
||||
isMain: false,
|
||||
availability: 1,
|
||||
playtime: 28811,
|
||||
},
|
||||
{
|
||||
steamId: 2839,
|
||||
name: "JustGetAHouse",
|
||||
role: "Pocket Scout",
|
||||
main: true,
|
||||
availability: 1,
|
||||
playtime: 28811,
|
||||
},
|
||||
{
|
||||
steamId: 2841,
|
||||
name: "VADIKUS007",
|
||||
role: "Pocket Soldier",
|
||||
main: true,
|
||||
availability: 2,
|
||||
playtime: 98372,
|
||||
},
|
||||
{
|
||||
steamId: 2841,
|
||||
name: "VADIKUS007",
|
||||
role: "Roamer",
|
||||
main: false,
|
||||
availability: 2,
|
||||
playtime: 98372,
|
||||
},
|
||||
{
|
||||
steamId: 2282,
|
||||
name: "Bergman777",
|
||||
role: "Demoman",
|
||||
main: true,
|
||||
availability: 2,
|
||||
playtime: 47324,
|
||||
},
|
||||
{
|
||||
steamId: 2083,
|
||||
name: "IF_YOU_READ_THIS_",
|
||||
role: "Demoman",
|
||||
main: true,
|
||||
availability: 1,
|
||||
playtime: 32812,
|
||||
},
|
||||
{
|
||||
steamId: 2842,
|
||||
name: "BossOfThisGym",
|
||||
role: "Roamer",
|
||||
main: false,
|
||||
availability: 2,
|
||||
playtime: 12028,
|
||||
},
|
||||
{
|
||||
steamId: 2842,
|
||||
name: "BossOfThisGym",
|
||||
role: "Demoman",
|
||||
main: false,
|
||||
availability: 2,
|
||||
playtime: 12028,
|
||||
},
|
||||
{
|
||||
steamId: 2842,
|
||||
name: "BossOfThisGym",
|
||||
role: "Pocket Scout",
|
||||
main: false,
|
||||
availability: 2,
|
||||
playtime: 12028,
|
||||
},
|
||||
//{
|
||||
// steamId: 2843,
|
||||
// name: "samme1g",
|
||||
// role: "Medic",
|
||||
// main: true,
|
||||
// availability: 2,
|
||||
//},
|
||||
{
|
||||
steamId: 2843,
|
||||
name: "samme1g",
|
||||
role: "Pocket Soldier",
|
||||
main: false,
|
||||
availability: 2,
|
||||
playtime: 50201,
|
||||
},
|
||||
{
|
||||
steamId: 2843,
|
||||
name: "samme1g",
|
||||
role: "Demoman",
|
||||
main: true,
|
||||
availability: 2,
|
||||
playtime: 50201,
|
||||
},
|
||||
{
|
||||
steamId: 2844,
|
||||
name: "FarbrorBarbro",
|
||||
role: "Roamer",
|
||||
main: true,
|
||||
availability: 1,
|
||||
playtime: 4732,
|
||||
},
|
||||
{
|
||||
steamId: 2844,
|
||||
name: "FarbrorBarbro",
|
||||
role: "Pocket Soldier",
|
||||
main: false,
|
||||
availability: 1,
|
||||
playtime: 4732,
|
||||
},
|
||||
]);
|
||||
|
||||
const availablePlayerRoles = computed(() => {
|
||||
return availablePlayers.filter((player) => player.role == selectedRole.value);
|
||||
return availablePlayers.value.filter((player) => player.role == selectedRole.value);
|
||||
});
|
||||
|
||||
const definitelyAvailable = computed(() => {
|
||||
|
@ -150,7 +51,7 @@ export const useRosterStore = defineStore("roster", () => {
|
|||
return availablePlayerRoles.value.filter((player) => player.availability == 1);
|
||||
});
|
||||
|
||||
function comparator(p1: PlayerTeamRole, p2: PlayerTeamRole) {
|
||||
function comparator(p1: PlayerTeamRoleFlat, p2: PlayerTeamRoleFlat) {
|
||||
// definitely available > can be available
|
||||
let availabilityDiff = p1.availability - p2.availability;
|
||||
|
||||
|
@ -161,12 +62,12 @@ export const useRosterStore = defineStore("roster", () => {
|
|||
}
|
||||
|
||||
const mainRoles = computed(() => {
|
||||
return availablePlayerRoles.value.filter((player) => player.main)
|
||||
return availablePlayerRoles.value.filter((player) => player.isMain)
|
||||
.sort(comparator);
|
||||
});
|
||||
|
||||
const alternateRoles = computed(() => {
|
||||
return availablePlayerRoles.value.filter((player) => !player.main)
|
||||
return availablePlayerRoles.value.filter((player) => !player.isMain)
|
||||
.sort(comparator);
|
||||
});
|
||||
|
||||
|
@ -188,8 +89,8 @@ export const useRosterStore = defineStore("roster", () => {
|
|||
"Medic": "Medic",
|
||||
});
|
||||
|
||||
function selectPlayerForRole(player: PlayerTeamRole, role: string) {
|
||||
if (player && player.steamId > 0) {
|
||||
function selectPlayerForRole(player: PlayerTeamRoleFlat, role: string) {
|
||||
if (player && player.steamId) {
|
||||
const existingRole = Object.keys(selectedPlayers).find((selectedRole) => {
|
||||
return selectedPlayers[selectedRole]?.steamId == player.steamId &&
|
||||
role != selectedRole;
|
||||
|
@ -203,6 +104,27 @@ export const useRosterStore = defineStore("roster", () => {
|
|||
selectedPlayers[role] = player;
|
||||
}
|
||||
|
||||
function fetchAvailablePlayers(startTime: number, teamId: number) {
|
||||
clientStore.call(
|
||||
fetchAvailablePlayers.name,
|
||||
() => client.default.viewAvailableAtTime(startTime.toString(), teamId),
|
||||
(response) => {
|
||||
availablePlayers.value = response.players.flatMap((schema) => {
|
||||
return schema.roles.map((role) => ({
|
||||
steamId: schema.player.steamId,
|
||||
name: schema.player.username,
|
||||
role: role.role,
|
||||
isMain: role.isMain,
|
||||
availability: schema.availability,
|
||||
playtime: schema.playtime,
|
||||
}));
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
neededRoles,
|
||||
selectedPlayers,
|
||||
|
@ -216,5 +138,6 @@ export const useRosterStore = defineStore("roster", () => {
|
|||
roleNames,
|
||||
mainRoles,
|
||||
alternateRoles,
|
||||
fetchAvailablePlayers,
|
||||
}
|
||||
});
|
||||
|
|
|
@ -2,11 +2,15 @@
|
|||
import PlayerCard from "../components/PlayerCard.vue";
|
||||
import RoleSlot from "../components/RoleSlot.vue";
|
||||
import PlayerTeamRole from "../player.ts";
|
||||
import { computed, reactive } from "vue";
|
||||
import { computed, reactive, onMounted } from "vue";
|
||||
import { useRosterStore } from "../stores/roster";
|
||||
import { useRoute } from "vue-router";
|
||||
import moment from "moment";
|
||||
|
||||
const rosterStore = useRosterStore();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const hasAvailablePlayers = computed(() => {
|
||||
return rosterStore.availablePlayerRoles.length > 0;
|
||||
});
|
||||
|
@ -14,6 +18,10 @@ const hasAvailablePlayers = computed(() => {
|
|||
const hasAlternates = computed(() => {
|
||||
return rosterStore.alternateRoles.length > 0;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
rosterStore.fetchAvailablePlayers(route.params.startTime, route.params.teamId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -21,7 +29,10 @@ const hasAlternates = computed(() => {
|
|||
<div class="top">
|
||||
<h1 class="roster-title">
|
||||
Roster for Snus Brotherhood
|
||||
<em class="aside date">Aug. 13, 2036 @ 11:30 PM EST</em>
|
||||
<em class="aside date">
|
||||
@
|
||||
{{ moment(startTime).format("L LT") }}
|
||||
</em>
|
||||
</h1>
|
||||
<div class="button-group">
|
||||
<button>Cancel</button>
|
||||
|
|
|
@ -21,7 +21,6 @@ def index():
|
|||
return "test"
|
||||
|
||||
@api_login.get("/get-user")
|
||||
@requires_authentication
|
||||
@spec.validate(
|
||||
resp=Response(
|
||||
HTTP_200=PlayerSchema,
|
||||
|
@ -29,11 +28,9 @@ def index():
|
|||
),
|
||||
operation_id="get_user"
|
||||
)
|
||||
@requires_authentication
|
||||
def get_user(player: Player, auth_session: AuthSession):
|
||||
return PlayerSchema(
|
||||
steam_id=str(player.steam_id),
|
||||
username=player.username,
|
||||
).dict(by_alias=True), 200
|
||||
return PlayerSchema.from_model(player).dict(by_alias=True)
|
||||
|
||||
@api_login.post("/authenticate")
|
||||
def steam_authenticate():
|
||||
|
|
|
@ -22,6 +22,10 @@ class PlayerSchema(spec.BaseModel):
|
|||
steam_id: str
|
||||
username: str
|
||||
|
||||
@classmethod
|
||||
def from_model(cls, player: Player):
|
||||
return cls(steam_id=str(player.steam_id), username=player.username)
|
||||
|
||||
|
||||
from models.auth_session import AuthSession
|
||||
from models.player_team import PlayerTeam
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
import spec
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.schema import ForeignKeyConstraint
|
||||
from sqlalchemy.types import Integer
|
||||
|
@ -28,3 +29,12 @@ class PlayerTeamAvailability(app_db.BaseModel):
|
|||
[PlayerTeam.player_id, PlayerTeam.team_id]
|
||||
),
|
||||
)
|
||||
|
||||
class PlayerTeamAvailabilityRoleSchema(spec.BaseModel):
|
||||
from models.player import PlayerSchema
|
||||
from models.player_team_role import RoleSchema
|
||||
|
||||
player: PlayerSchema
|
||||
playtime: int
|
||||
availability: int
|
||||
roles: list[RoleSchema]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import enum
|
||||
|
||||
import spec
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.schema import ForeignKeyConstraint
|
||||
from sqlalchemy.types import Boolean, Enum
|
||||
|
@ -46,3 +47,11 @@ class PlayerTeamRole(app_db.BaseModel):
|
|||
[PlayerTeam.player_id, PlayerTeam.team_id]
|
||||
),
|
||||
)
|
||||
|
||||
class RoleSchema(spec.BaseModel):
|
||||
role: str
|
||||
is_main: bool
|
||||
|
||||
@classmethod
|
||||
def from_model(cls, role: PlayerTeamRole):
|
||||
return cls(role=role.role.name, is_main=role.is_main)
|
||||
|
|
|
@ -2,11 +2,12 @@ import datetime
|
|||
from typing import cast
|
||||
from flask import Blueprint, abort, jsonify, make_response, request
|
||||
from spectree import Response
|
||||
from sqlalchemy.orm import joinedload
|
||||
from app_db import db
|
||||
from models.player import Player
|
||||
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
|
||||
from models.player_team_availability import PlayerTeamAvailability, PlayerTeamAvailabilityRoleSchema
|
||||
from models.player_team_role import PlayerTeamRole, RoleSchema
|
||||
from middleware import requires_authentication
|
||||
from spec import spec, BaseModel
|
||||
|
||||
|
@ -184,20 +185,32 @@ def put(json: PutScheduleForm, player: Player, **kwargs):
|
|||
db.session.commit()
|
||||
return make_response({ }, 200)
|
||||
|
||||
class ViewAvailablePlayersForm(BaseModel):
|
||||
class ViewAvailablePlayersQuery(BaseModel):
|
||||
start_time: datetime.datetime
|
||||
team_id: int
|
||||
|
||||
class ViewAvailablePlayersResponse(BaseModel):
|
||||
players: list[PlayerTeamAvailabilityRoleSchema]
|
||||
|
||||
@api_schedule.get("/view-available")
|
||||
@spec.validate()
|
||||
@spec.validate(
|
||||
resp=Response(
|
||||
HTTP_200=ViewAvailablePlayersResponse,
|
||||
),
|
||||
operation_id="view_available_at_time"
|
||||
)
|
||||
@requires_authentication
|
||||
def view_available(query: ViewAvailablePlayersForm, player: Player, **kwargs):
|
||||
def view_available_at_time(query: ViewAvailablePlayersQuery, player: Player, **kwargs):
|
||||
start_time = query.start_time
|
||||
|
||||
availability = db.session.query(
|
||||
records = db.session.query(
|
||||
PlayerTeamAvailability
|
||||
).where(
|
||||
PlayerTeamAvailability.player_id == player.steam_id
|
||||
).join(
|
||||
PlayerTeam
|
||||
).join(
|
||||
Player
|
||||
).join(
|
||||
PlayerTeamRole
|
||||
).where(
|
||||
PlayerTeamAvailability.team_id == query.team_id
|
||||
).where(
|
||||
|
@ -205,22 +218,18 @@ def view_available(query: ViewAvailablePlayersForm, player: Player, **kwargs):
|
|||
(PlayerTeamAvailability.end_time > start_time)
|
||||
).all()
|
||||
|
||||
def map_roles_to_json(roles: list[PlayerTeamRole],
|
||||
player_team: PlayerTeam,
|
||||
entry: PlayerTeamAvailability):
|
||||
for role in roles:
|
||||
yield {
|
||||
"steamId": entry.player_id,
|
||||
"username": entry.player_team.player.username,
|
||||
"role": role.role.name,
|
||||
"isMain": role.is_main,
|
||||
"availability": entry.availability,
|
||||
"playtime": int(player_team.playtime.total_seconds()),
|
||||
}
|
||||
|
||||
def map_availability_to_json(entry: PlayerTeamAvailability):
|
||||
player_team = entry.player_team
|
||||
def map_to_response(player_avail: PlayerTeamAvailability):
|
||||
player_team = player_avail.player_team
|
||||
player = player_team.player
|
||||
player_roles = player_team.player_roles
|
||||
return list(map_roles_to_json(player_roles, player_team, entry))
|
||||
|
||||
return jsonify(list(map(map_availability_to_json, availability)))
|
||||
return PlayerTeamAvailabilityRoleSchema(
|
||||
player=PlayerSchema.from_model(player),
|
||||
playtime=int(player_team.playtime.total_seconds()),
|
||||
availability=player_avail.availability,
|
||||
roles=list(map(RoleSchema.from_model, player_roles)),
|
||||
)
|
||||
|
||||
return ViewAvailablePlayersResponse(
|
||||
players=list(map(map_to_response, records))
|
||||
).dict(by_alias=True), 200
|
||||
|
|
|
@ -11,7 +11,7 @@ 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
|
||||
from models.player_team_role import PlayerTeamRole, RoleSchema
|
||||
from models.team import Team, TeamSchema
|
||||
from models.team_invite import TeamInvite, TeamInviteSchema
|
||||
from middleware import requires_authentication
|
||||
|
@ -302,10 +302,6 @@ def fetch_teams_for_player(player: Player, team_id: int | None):
|
|||
)
|
||||
|
||||
class ViewTeamMembersResponse(PlayerSchema):
|
||||
class RoleSchema(BaseModel):
|
||||
role: str
|
||||
is_main: bool
|
||||
|
||||
roles: list[RoleSchema]
|
||||
availability: list[int]
|
||||
playtime: float
|
||||
|
@ -346,7 +342,7 @@ def view_team_members(player: Player, team_id: int, **kwargs):
|
|||
abort(404)
|
||||
|
||||
def map_role_to_schema(player_team_role: PlayerTeamRole):
|
||||
return ViewTeamMembersResponse.RoleSchema(
|
||||
return RoleSchema(
|
||||
role=player_team_role.role.name,
|
||||
is_main=player_team_role.is_main,
|
||||
)
|
||||
|
@ -376,7 +372,7 @@ def view_team_members(player: Player, team_id: int, **kwargs):
|
|||
return list(map(map_to_response, player_teams))
|
||||
|
||||
class EditMemberRolesJson(BaseModel):
|
||||
roles: list[ViewTeamMembersResponse.RoleSchema]
|
||||
roles: list[RoleSchema]
|
||||
|
||||
@api_team.patch("/id/<team_id>/edit-player/<target_player_id>")
|
||||
@spec.validate(
|
||||
|
|
Loading…
Reference in New Issue