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