Implement leaving team
parent
050a012318
commit
f5bcbb85b5
|
@ -8,8 +8,11 @@
|
|||
"name": "availabili.tf",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@jamescoyle/vue-icon": "^0.1.2",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"axios": "^1.7.7",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"css.gg": "^2.1.4",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.46",
|
||||
"pinia": "^2.2.4",
|
||||
|
@ -838,6 +841,12 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@jamescoyle/vue-icon": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jamescoyle/vue-icon/-/vue-icon-0.1.2.tgz",
|
||||
"integrity": "sha512-KFrImXx5TKIi6iQXlnyLEBl4rNosNKbTeRnr70ucTdUaciVmd9qK9d/pZAaKt1Ob/8xNnX2GMp8LisyHdKtEgw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
|
@ -897,6 +906,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mdi/js": {
|
||||
"version": "7.4.47",
|
||||
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz",
|
||||
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -2816,6 +2831,12 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css.gg": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/css.gg/-/css.gg-2.1.4.tgz",
|
||||
"integrity": "sha512-7eyhXQLNJus5q3AVlYhDFjvVkB1ng1D9EjaBJzvboLfNx60RcFdZ1NinEgJMEA8bkwPwRLfbZ0ADTBXsbdrRgw==",
|
||||
"license": "SEE LICENSE"
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
"openapi-generate": "openapi --input 'http://localhost:8000/apidoc/openapi.json' --output src/client --name AvailabilitfClient"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jamescoyle/vue-icon": "^0.1.2",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"axios": "^1.7.7",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"css.gg": "^2.1.4",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.46",
|
||||
"pinia": "^2.2.4",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
@import url("tf2icons.css");
|
||||
@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css");
|
||||
/*@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css");*/
|
||||
@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/solid.min.css");
|
||||
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
|
@ -39,9 +41,20 @@
|
|||
|
||||
--flamingo: #dd7878;
|
||||
--flamingo-transparent: #f0c6c655;
|
||||
--green: #40a02b;
|
||||
--peach: #fe640b;
|
||||
--green: #40a02b;
|
||||
--yellow: #df8e1d;
|
||||
/*
|
||||
--green: #a6e3a1;
|
||||
--yellow: #f9e2af;
|
||||
*/
|
||||
--green-transparent: color-mix(in srgb, var(--green), transparent 80%);
|
||||
--yellow-transparent: color-mix(in srgb, var(--yellow), transparent 80%);
|
||||
/*
|
||||
--green-transparent-50: #a6e3a1;
|
||||
--yellow-transparent-50: #f9e2af;
|
||||
*/
|
||||
|
||||
--lavender: #7287fd;
|
||||
--accent: var(--lavender);
|
||||
--accent-transparent-80: color-mix(in srgb, var(--accent), transparent 80%);
|
||||
|
|
|
@ -15,6 +15,10 @@ a,
|
|||
padding: 3px;
|
||||
}
|
||||
|
||||
a.button {
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: var(--accent-transparent);
|
||||
|
|
|
@ -13,6 +13,7 @@ export type { OpenAPIConfig } from './core/OpenAPI';
|
|||
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 { PutScheduleForm } from './models/PutScheduleForm';
|
||||
export type { RoleSchema } from './models/RoleSchema';
|
||||
export type { TeamInviteSchema } from './models/TeamInviteSchema';
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type PlayerSchema = {
|
||||
steamId: string;
|
||||
username: string;
|
||||
};
|
||||
|
|
@ -4,8 +4,9 @@
|
|||
/* eslint-disable */
|
||||
import type { RoleSchema } from './RoleSchema';
|
||||
export type ViewTeamMembersResponse = {
|
||||
availability: number;
|
||||
availability: Array<number>;
|
||||
createdAt: string;
|
||||
isTeamLeader?: boolean;
|
||||
playtime: number;
|
||||
roles: Array<RoleSchema>;
|
||||
steamId: string;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import type { AddPlayerJson } from '../models/AddPlayerJson';
|
||||
import type { CreateTeamJson } from '../models/CreateTeamJson';
|
||||
import type { EditMemberRolesJson } from '../models/EditMemberRolesJson';
|
||||
import type { PlayerSchema } from '../models/PlayerSchema';
|
||||
import type { PutScheduleForm } from '../models/PutScheduleForm';
|
||||
import type { TeamInviteSchema } from '../models/TeamInviteSchema';
|
||||
import type { TeamInviteSchemaList } from '../models/TeamInviteSchemaList';
|
||||
|
@ -71,6 +72,21 @@ export class DefaultService {
|
|||
url: '/api/login/authenticate',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* get_user <GET>
|
||||
* @returns PlayerSchema OK
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getUser(): CancelablePromise<PlayerSchema> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/login/get-user',
|
||||
errors: {
|
||||
401: `Unauthorized`,
|
||||
422: `Unprocessable Entity`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* get <GET>
|
||||
* @param windowStart
|
||||
|
@ -361,6 +377,31 @@ export class DefaultService {
|
|||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* remove_player_from_team <DELETE>
|
||||
* @param teamId
|
||||
* @param targetPlayerId
|
||||
* @returns any OK
|
||||
* @throws ApiError
|
||||
*/
|
||||
public removePlayerFromTeam(
|
||||
teamId: string,
|
||||
targetPlayerId: string,
|
||||
): CancelablePromise<any> {
|
||||
return this.httpRequest.request({
|
||||
method: 'DELETE',
|
||||
url: '/api/team/id/{team_id}/player/{target_player_id}/',
|
||||
path: {
|
||||
'team_id': teamId,
|
||||
'target_player_id': targetPlayerId,
|
||||
},
|
||||
errors: {
|
||||
403: `Forbidden`,
|
||||
404: `Not Found`,
|
||||
422: `Unprocessable Entity`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* view_team_members <GET>
|
||||
* @param teamId
|
||||
|
|
|
@ -272,10 +272,12 @@ onUnmounted(() => {
|
|||
}
|
||||
|
||||
.time-slot[selection="1"] {
|
||||
background-color: var(--accent-transparent-50);
|
||||
/*background-color: var(--accent-transparent-50);*/
|
||||
background-color: var(--yellow-transparent);
|
||||
}
|
||||
|
||||
.time-slot[selection="2"] {
|
||||
background-color: var(--accent);
|
||||
/*background-color: var(--accent);*/
|
||||
background-color: var(--green-transparent);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,6 +4,8 @@ import { computed, type PropType, ref, watch } from "vue";
|
|||
import { useTeamsStore } from "../stores/teams";
|
||||
import { useRosterStore } from "../stores/roster";
|
||||
import { type ViewTeamMembersResponse, type TeamSchema } from "@/client";
|
||||
import SvgIcon from "@jamescoyle/vue-icon";
|
||||
import { mdiCrown } from "@mdi/js";
|
||||
import RoleTag from "../components/RoleTag.vue";
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -69,19 +71,34 @@ function updateRoles() {
|
|||
console.log(updatedRoles.value);
|
||||
teamsStore.updateRoles(props.team.id, props.player.steamId, updatedRoles.value);
|
||||
}
|
||||
|
||||
const isUnavailable = computed(() => {
|
||||
return props.player?.availability[0] == 0 &&
|
||||
props.player?.availability[1] == 0;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr class="player-card">
|
||||
<td>
|
||||
<div class="status flex-middle" :availability="player.availability">
|
||||
<div
|
||||
class="status flex-middle"
|
||||
:is-unavailable="isUnavailable"
|
||||
>
|
||||
<div class="status-indicators">
|
||||
<span class="indicator left-indicator" />
|
||||
<span class="indicator right-indicator" />
|
||||
<span
|
||||
class="indicator left-indicator"
|
||||
:availability="player.availability[0]"
|
||||
/>
|
||||
<span
|
||||
class="indicator right-indicator"
|
||||
:availability="player.availability[1]"
|
||||
/>
|
||||
</div>
|
||||
<h3>
|
||||
{{ player.username }}
|
||||
</h3>
|
||||
<svg-icon v-if="player.isTeamLeader" type="mdi" :path="mdiCrown" />
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -164,16 +181,16 @@ function updateRoles() {
|
|||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
.status[availability="0"] h3 {
|
||||
.status[is-unavailable="true"] {
|
||||
color: var(--overlay-0);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.status[availability="1"] .indicator {
|
||||
.status .indicator[availability="1"] {
|
||||
background-color: var(--yellow);
|
||||
}
|
||||
|
||||
.status[availability="2"] .indicator {
|
||||
.status .indicator[availability="2"] {
|
||||
background-color: var(--green);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import RosterBuilderView from "../views/RosterBuilderView.vue";
|
|||
import LoginView from "../views/LoginView.vue";
|
||||
import TeamRegistrationView from "../views/TeamRegistrationView.vue";
|
||||
import TeamDetailsView from "../views/TeamDetailsView.vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
|
@ -40,6 +41,16 @@ const router = createRouter({
|
|||
component: TeamDetailsView
|
||||
},
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export default router
|
||||
|
||||
router
|
||||
.beforeEach(async (to, from) => {
|
||||
const authStore = useAuthStore();
|
||||
console.log("test");
|
||||
if (!authStore.isLoggedIn && !authStore.hasCheckedAuth) {
|
||||
await authStore.getUser();
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -1,11 +1,29 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { useClientStore } from "./client";
|
||||
|
||||
export const useAuthStore = defineStore("auth", () => {
|
||||
const steamId = ref(NaN);
|
||||
const clientStore = useClientStore();
|
||||
const client = clientStore.client;
|
||||
|
||||
const steamId = ref("");
|
||||
const username = ref("");
|
||||
const isLoggedIn = ref(false);
|
||||
const isRegistering = ref(false);
|
||||
const hasCheckedAuth = ref(false);
|
||||
|
||||
async function getUser() {
|
||||
hasCheckedAuth.value = true;
|
||||
return clientStore.call(
|
||||
getUser.name,
|
||||
() => client.default.getUser(),
|
||||
(response) => {
|
||||
steamId.value = response.steamId;
|
||||
username.value = username.value;
|
||||
return response;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function login(queryParams: { [key: string]: string }) {
|
||||
return fetch(import.meta.env.VITE_API_BASE_URL + "/login/authenticate", {
|
||||
|
@ -32,7 +50,9 @@ export const useAuthStore = defineStore("auth", () => {
|
|||
steamId,
|
||||
username,
|
||||
isLoggedIn,
|
||||
hasCheckedAuth,
|
||||
isRegistering,
|
||||
getUser,
|
||||
login,
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AvailabilitfClient } from "@/client";
|
||||
import { AvailabilitfClient, CancelablePromise } from "@/client";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useClientStore = defineStore("client", () => {
|
||||
|
@ -10,7 +10,7 @@ export const useClientStore = defineStore("client", () => {
|
|||
|
||||
function call<T>(
|
||||
key: string,
|
||||
apiCall: () => Promise<T>,
|
||||
apiCall: () => CancelablePromise<T>,
|
||||
thenOnce?: (result: T) => T
|
||||
): Promise<T> {
|
||||
console.log("Fetching call " + key);
|
||||
|
|
|
@ -3,10 +3,13 @@ import { AvailabilitfClient, type TeamInviteSchema, type RoleSchema, type TeamSc
|
|||
import { defineStore } from "pinia";
|
||||
import { computed, reactive, ref, type Reactive, type Ref } from "vue";
|
||||
import { useClientStore } from "./client";
|
||||
import { useAuthStore } from "./auth";
|
||||
|
||||
export type TeamMap = { [id: number]: TeamSchema };
|
||||
|
||||
export const useTeamsStore = defineStore("teams", () => {
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const clientStore = useClientStore();
|
||||
const client = clientStore.client;
|
||||
|
||||
|
@ -14,8 +17,6 @@ export const useTeamsStore = defineStore("teams", () => {
|
|||
const teamMembers: Reactive<{ [id: number]: ViewTeamMembersResponse[] }> = reactive({ });
|
||||
const teamInvites: Reactive<{ [id: number]: TeamInviteSchema[] }> = reactive({ });
|
||||
|
||||
const isFetchingTeams = ref(false);
|
||||
|
||||
async function fetchTeams() {
|
||||
return clientStore.call(
|
||||
fetchTeams.name,
|
||||
|
@ -116,6 +117,11 @@ export const useTeamsStore = defineStore("teams", () => {
|
|||
});
|
||||
}
|
||||
|
||||
async function leaveTeam(teamId: number) {
|
||||
return client.default
|
||||
.removePlayerFromTeam(teamId.toString(), authStore.steamId);
|
||||
}
|
||||
|
||||
return {
|
||||
teams,
|
||||
teamInvites,
|
||||
|
@ -129,5 +135,6 @@ export const useTeamsStore = defineStore("teams", () => {
|
|||
createInvite,
|
||||
consumeInvite,
|
||||
revokeInvite,
|
||||
leaveTeam,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -164,17 +164,27 @@ button.radio:hover {
|
|||
|
||||
button.radio.selected {
|
||||
color: var(--accent);
|
||||
background-color: var(--accent-transparent);
|
||||
color: var(--accent-transparent);
|
||||
}
|
||||
|
||||
button.left {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
button.radio.left.selected {
|
||||
color: var(--yellow);
|
||||
background-color: var(--yellow-transparent);
|
||||
}
|
||||
|
||||
button.right {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
button.right.radio.selected {
|
||||
color: var(--green);
|
||||
background-color: var(--green-transparent);
|
||||
}
|
||||
|
||||
.v-select {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
|
|
|
@ -19,7 +19,12 @@ const invites = computed(() => {
|
|||
|
||||
const availableMembers = computed(() => {
|
||||
return teamsStore.teamMembers[route.params.id]
|
||||
.filter((member) => member.availability > 0);
|
||||
.filter((member) => member.availability[0] > 0);
|
||||
});
|
||||
|
||||
const availableMembersNextHour = computed(() => {
|
||||
return teamsStore.teamMembers[route.params.id]
|
||||
.filter((member) => member.availability[1] > 0);
|
||||
});
|
||||
|
||||
function createInvite() {
|
||||
|
@ -30,17 +35,33 @@ function revokeInvite(key) {
|
|||
teamsStore.revokeInvite(team.value.id, key)
|
||||
}
|
||||
|
||||
function leaveTeam() {
|
||||
teamsStore.leaveTeam(team.value.id)
|
||||
.then(() => {
|
||||
teamsStore.fetchTeams()
|
||||
.then(() => {
|
||||
router.push("/");
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
let key = route.query.key;
|
||||
let teamId = route.params.id;
|
||||
|
||||
let doFetchTeam = () => {
|
||||
teamsStore.fetchTeam(teamId)
|
||||
.then(() => teamsStore.fetchTeamMembers(teamId))
|
||||
.then(() => teamsStore.getInvites(teamId));
|
||||
};
|
||||
|
||||
if (key) {
|
||||
await teamsStore.consumeInvite(teamId, key);
|
||||
teamsStore.consumeInvite(teamId, key)
|
||||
.finally(doFetchTeam);
|
||||
} else {
|
||||
doFetchTeam();
|
||||
}
|
||||
|
||||
teamsStore.fetchTeam(teamId)
|
||||
.then(() => teamsStore.fetchTeamMembers(teamId))
|
||||
.then(() => teamsStore.getInvites(teamId));
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -49,16 +70,23 @@ onMounted(async () => {
|
|||
<template v-if="team">
|
||||
<h1>
|
||||
{{ team.teamName }}
|
||||
<RouterLink :to="'/schedule?teamId=' + team.id">
|
||||
</RouterLink>
|
||||
<em class="aside" v-if="teamsStore.teamMembers[route.params.id]">
|
||||
{{ teamsStore.teamMembers[route.params.id]?.length }} member(s),
|
||||
{{ availableMembers?.length }} currently available
|
||||
{{ availableMembers?.length }} currently available,
|
||||
{{ availableMembersNextHour?.length }} available in the next hour
|
||||
</em>
|
||||
<div class="team-details-button-group">
|
||||
<button class="accent">
|
||||
<i class="bi bi-calendar-fill margin"></i>
|
||||
View schedule
|
||||
<RouterLink class="button" :to="'/schedule?teamId=' + team.id">
|
||||
<button class="accent">
|
||||
<i class="bi bi-calendar-fill margin"></i>
|
||||
View schedule
|
||||
</button>
|
||||
</RouterLink>
|
||||
<button
|
||||
class="destructive"
|
||||
@click="leaveTeam"
|
||||
>
|
||||
Leave
|
||||
</button>
|
||||
</div>
|
||||
</h1>
|
||||
|
@ -175,6 +203,7 @@ th {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.create-invite-group {
|
||||
|
|
|
@ -4,8 +4,10 @@ import string
|
|||
import urllib.parse
|
||||
from flask import Blueprint, abort, make_response, redirect, request, url_for
|
||||
import requests
|
||||
from spectree import Response
|
||||
from spec import spec
|
||||
import models
|
||||
from models import AuthSession, Player, db
|
||||
from models import AuthSession, Player, PlayerSchema, db
|
||||
from middleware import requires_authentication
|
||||
|
||||
api_login = Blueprint("login", __name__, url_prefix="/login")
|
||||
|
@ -16,6 +18,21 @@ STEAM_OPENID_URL = "https://steamcommunity.com/openid/login"
|
|||
def index():
|
||||
return "test"
|
||||
|
||||
@api_login.get("/get-user")
|
||||
@requires_authentication
|
||||
@spec.validate(
|
||||
resp=Response(
|
||||
HTTP_200=PlayerSchema,
|
||||
HTTP_401=None,
|
||||
),
|
||||
operation_id="get_user"
|
||||
)
|
||||
def get_user(player: Player, auth_session: AuthSession):
|
||||
return PlayerSchema(
|
||||
steam_id=str(player.steam_id),
|
||||
username=player.username,
|
||||
).dict(by_alias=True), 200
|
||||
|
||||
@api_login.post("/authenticate")
|
||||
def steam_authenticate():
|
||||
params = request.get_json()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from random import randint, random
|
||||
import sys
|
||||
import time
|
||||
|
@ -116,6 +116,69 @@ def delete_team(player: Player, team_id: int):
|
|||
db.session.commit()
|
||||
return make_response(200)
|
||||
|
||||
@api_team.delete("/id/<team_id>/player/<target_player_id>/")
|
||||
@spec.validate(
|
||||
resp=Response(
|
||||
HTTP_200=None,
|
||||
HTTP_403=None,
|
||||
HTTP_404=None,
|
||||
),
|
||||
operation_id="remove_player_from_team"
|
||||
)
|
||||
@requires_authentication
|
||||
def remove_player_from_team(player: Player, team_id: int, target_player_id: int, **kwargs):
|
||||
player_team = db.session.query(
|
||||
PlayerTeam
|
||||
).where(
|
||||
PlayerTeam.team_id == team_id
|
||||
).where(
|
||||
PlayerTeam.player_id == player.steam_id
|
||||
).one_or_none()
|
||||
|
||||
if not player_team:
|
||||
abort(404)
|
||||
|
||||
target_player_team = db.session.query(
|
||||
PlayerTeam
|
||||
).where(
|
||||
PlayerTeam.team_id == team_id
|
||||
).where(
|
||||
PlayerTeam.player_id == target_player_id
|
||||
).one_or_none()
|
||||
|
||||
if not target_player_team:
|
||||
abort(404)
|
||||
|
||||
is_team_leader = player_team.is_team_leader
|
||||
|
||||
if not is_team_leader and player_team != target_player_team:
|
||||
abort(403)
|
||||
|
||||
team = target_player_team.team
|
||||
|
||||
db.session.delete(target_player_team)
|
||||
db.session.refresh(team)
|
||||
|
||||
if len(team.players) == 0:
|
||||
# delete the team if the only member
|
||||
db.session.delete(team)
|
||||
else:
|
||||
# if there doesn't exist another team leader, promote the first player
|
||||
team_leaders = db.session.query(
|
||||
PlayerTeam
|
||||
).where(
|
||||
PlayerTeam.team_id == team_id
|
||||
).where(
|
||||
PlayerTeam.is_team_leader == True
|
||||
).all()
|
||||
|
||||
if len(team_leaders) == 0:
|
||||
team.players[0].is_team_leader = True
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return make_response({ }, 200)
|
||||
|
||||
class AddPlayerJson(BaseModel):
|
||||
team_role: PlayerTeam.TeamRole = PlayerTeam.TeamRole.Player
|
||||
is_team_leader: bool = False
|
||||
|
@ -238,9 +301,10 @@ class ViewTeamMembersResponse(PlayerSchema):
|
|||
is_main: bool
|
||||
|
||||
roles: list[RoleSchema]
|
||||
availability: int
|
||||
availability: list[int]
|
||||
playtime: float
|
||||
created_at: datetime
|
||||
is_team_leader: bool = False
|
||||
|
||||
@api_team.get("/id/<team_id>/players")
|
||||
@spec.validate(
|
||||
|
@ -254,6 +318,7 @@ class ViewTeamMembersResponse(PlayerSchema):
|
|||
@requires_authentication
|
||||
def view_team_members(player: Player, team_id: int, **kwargs):
|
||||
now = datetime.now(timezone.utc)
|
||||
next_hour = now + timedelta(hours=1)
|
||||
|
||||
player_teams_query = db.session.query(
|
||||
PlayerTeam
|
||||
|
@ -264,7 +329,7 @@ def view_team_members(player: Player, team_id: int, **kwargs):
|
|||
joinedload(PlayerTeam.player),
|
||||
joinedload(PlayerTeam.player_roles),
|
||||
joinedload(PlayerTeam.availability.and_(
|
||||
(PlayerTeamAvailability.start_time <= now) &
|
||||
(PlayerTeamAvailability.start_time <= next_hour) &
|
||||
(PlayerTeamAvailability.end_time > now)
|
||||
)),
|
||||
)
|
||||
|
@ -284,10 +349,13 @@ def view_team_members(player: Player, team_id: int, **kwargs):
|
|||
roles = player_team.player_roles
|
||||
player = player_team.player
|
||||
|
||||
availability = 0
|
||||
if len(player_team.availability) > 0:
|
||||
print(player_team.availability)
|
||||
availability = player_team.availability[0].availability
|
||||
availability = [0, 0]
|
||||
|
||||
for record in player_team.availability:
|
||||
if record.start_time <= now < record.end_time:
|
||||
availability[0] = record.availability
|
||||
if record.start_time <= next_hour < record.end_time:
|
||||
availability[1] = record.availability
|
||||
|
||||
return ViewTeamMembersResponse(
|
||||
username=player.username,
|
||||
|
@ -296,6 +364,7 @@ def view_team_members(player: Player, team_id: int, **kwargs):
|
|||
availability=availability,
|
||||
playtime=player_team.playtime.total_seconds() / 3600,
|
||||
created_at=player_team.created_at,
|
||||
is_team_leader=player_team.is_team_leader,
|
||||
).dict(by_alias=True)
|
||||
|
||||
return list(map(map_to_response, player_teams))
|
||||
|
|
Loading…
Reference in New Issue