Add functionality for viewing available teammates

master
John Montagu, the 4th Earl of Sandvich 2024-11-11 18:05:27 -08:00
parent 60f96f43f7
commit 325b3529fe
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
15 changed files with 154 additions and 167 deletions

View File

@ -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';

View File

@ -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>;
};

View File

@ -2,7 +2,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ViewAvailablePlayersForm = {
export type ViewAvailablePlayersQuery = {
startTime: string;
teamId: number;
};

View File

@ -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>;
};

View File

@ -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`,
},
});
}
/**

View File

@ -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;
}

View File

@ -26,7 +26,7 @@ const router = createRouter({
component: ScheduleView
},
{
path: "/schedule/roster",
path: "/schedule/roster/:teamId/:startTime",
name: "roster-builder",
component: RosterBuilderView
},

View File

@ -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,
}
});

View File

@ -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>

View File

@ -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():

View File

@ -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

View File

@ -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]

View File

@ -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)

View File

@ -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

View File

@ -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(