Compare commits
	
		
			No commits in common. "6f49053dacdfa2d858d0ac851d02f96f7770b88f" and "e1c6a7bb14a568eeb8814b85039c96a3a4f9db7e" have entirely different histories. 
		
	
	
		
			6f49053dac
			...
			e1c6a7bb14
		
	
		
	| 
						 | 
					@ -16,8 +16,7 @@ export type { CreateEventJson } from './models/CreateEventJson';
 | 
				
			||||||
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 { EventSchema } from './models/EventSchema';
 | 
					export type { EventSchema } from './models/EventSchema';
 | 
				
			||||||
export type { EventWithPlayerSchema } from './models/EventWithPlayerSchema';
 | 
					export type { EventSchemaList } from './models/EventSchemaList';
 | 
				
			||||||
export type { EventWithPlayerSchemaList } from './models/EventWithPlayerSchemaList';
 | 
					 | 
				
			||||||
export type { GetEventPlayersResponse } from './models/GetEventPlayersResponse';
 | 
					export type { GetEventPlayersResponse } from './models/GetEventPlayersResponse';
 | 
				
			||||||
export type { PlayerEventRolesSchema } from './models/PlayerEventRolesSchema';
 | 
					export type { PlayerEventRolesSchema } from './models/PlayerEventRolesSchema';
 | 
				
			||||||
export type { PlayerRoleSchema } from './models/PlayerRoleSchema';
 | 
					export type { PlayerRoleSchema } from './models/PlayerRoleSchema';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					import type { EventSchema } from './EventSchema';
 | 
				
			||||||
 | 
					export type EventSchemaList = Array<EventSchema>;
 | 
				
			||||||
| 
						 | 
					@ -1,11 +0,0 @@
 | 
				
			||||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
					 | 
				
			||||||
/* istanbul ignore file */
 | 
					 | 
				
			||||||
/* tslint:disable */
 | 
					 | 
				
			||||||
/* eslint-disable */
 | 
					 | 
				
			||||||
import type { EventSchema } from './EventSchema';
 | 
					 | 
				
			||||||
import type { PlayerEventRolesSchema } from './PlayerEventRolesSchema';
 | 
					 | 
				
			||||||
export type EventWithPlayerSchema = {
 | 
					 | 
				
			||||||
    event: EventSchema;
 | 
					 | 
				
			||||||
    playerEvent: (PlayerEventRolesSchema | null);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
					 | 
				
			||||||
/* istanbul ignore file */
 | 
					 | 
				
			||||||
/* tslint:disable */
 | 
					 | 
				
			||||||
/* eslint-disable */
 | 
					 | 
				
			||||||
import type { EventWithPlayerSchema } from './EventWithPlayerSchema';
 | 
					 | 
				
			||||||
export type EventWithPlayerSchemaList = Array<EventWithPlayerSchema>;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,6 @@
 | 
				
			||||||
/* eslint-disable */
 | 
					/* eslint-disable */
 | 
				
			||||||
export type TeamDiscordIntegrationSchema = {
 | 
					export type TeamDiscordIntegrationSchema = {
 | 
				
			||||||
    webhookBotName: string;
 | 
					    webhookBotName: string;
 | 
				
			||||||
    webhookBotProfilePicture: (string | null);
 | 
					 | 
				
			||||||
    webhookUrl: string;
 | 
					    webhookUrl: string;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,8 +7,7 @@ import type { CreateEventJson } from '../models/CreateEventJson';
 | 
				
			||||||
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 { EventSchema } from '../models/EventSchema';
 | 
					import type { EventSchema } from '../models/EventSchema';
 | 
				
			||||||
import type { EventWithPlayerSchema } from '../models/EventWithPlayerSchema';
 | 
					import type { EventSchemaList } from '../models/EventSchemaList';
 | 
				
			||||||
import type { EventWithPlayerSchemaList } from '../models/EventWithPlayerSchemaList';
 | 
					 | 
				
			||||||
import type { GetEventPlayersResponse } from '../models/GetEventPlayersResponse';
 | 
					import type { GetEventPlayersResponse } from '../models/GetEventPlayersResponse';
 | 
				
			||||||
import type { PlayerSchema } from '../models/PlayerSchema';
 | 
					import type { PlayerSchema } from '../models/PlayerSchema';
 | 
				
			||||||
import type { PutScheduleForm } from '../models/PutScheduleForm';
 | 
					import type { PutScheduleForm } from '../models/PutScheduleForm';
 | 
				
			||||||
| 
						 | 
					@ -51,12 +50,12 @@ export class DefaultService {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * get_team_events <GET>
 | 
					     * get_team_events <GET>
 | 
				
			||||||
     * @param teamId
 | 
					     * @param teamId
 | 
				
			||||||
     * @returns EventWithPlayerSchemaList OK
 | 
					     * @returns EventSchemaList OK
 | 
				
			||||||
     * @throws ApiError
 | 
					     * @throws ApiError
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public getTeamEvents(
 | 
					    public getTeamEvents(
 | 
				
			||||||
        teamId: number,
 | 
					        teamId: number,
 | 
				
			||||||
    ): CancelablePromise<EventWithPlayerSchemaList> {
 | 
					    ): CancelablePromise<EventSchemaList> {
 | 
				
			||||||
        return this.httpRequest.request({
 | 
					        return this.httpRequest.request({
 | 
				
			||||||
            method: 'GET',
 | 
					            method: 'GET',
 | 
				
			||||||
            url: '/api/events/team/id/{team_id}',
 | 
					            url: '/api/events/team/id/{team_id}',
 | 
				
			||||||
| 
						 | 
					@ -129,46 +128,6 @@ export class DefaultService {
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * unattend_event <DELETE>
 | 
					 | 
				
			||||||
     * @param eventId
 | 
					 | 
				
			||||||
     * @returns EventWithPlayerSchema OK
 | 
					 | 
				
			||||||
     * @throws ApiError
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public unattendEvent(
 | 
					 | 
				
			||||||
        eventId: number,
 | 
					 | 
				
			||||||
    ): CancelablePromise<EventWithPlayerSchema> {
 | 
					 | 
				
			||||||
        return this.httpRequest.request({
 | 
					 | 
				
			||||||
            method: 'DELETE',
 | 
					 | 
				
			||||||
            url: '/api/events/{event_id}/attendance',
 | 
					 | 
				
			||||||
            path: {
 | 
					 | 
				
			||||||
                'event_id': eventId,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            errors: {
 | 
					 | 
				
			||||||
                422: `Unprocessable Content`,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * attend_event <PUT>
 | 
					 | 
				
			||||||
     * @param eventId
 | 
					 | 
				
			||||||
     * @returns EventWithPlayerSchema OK
 | 
					 | 
				
			||||||
     * @throws ApiError
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public attendEvent(
 | 
					 | 
				
			||||||
        eventId: number,
 | 
					 | 
				
			||||||
    ): CancelablePromise<EventWithPlayerSchema> {
 | 
					 | 
				
			||||||
        return this.httpRequest.request({
 | 
					 | 
				
			||||||
            method: 'PUT',
 | 
					 | 
				
			||||||
            url: '/api/events/{event_id}/attendance',
 | 
					 | 
				
			||||||
            path: {
 | 
					 | 
				
			||||||
                'event_id': eventId,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            errors: {
 | 
					 | 
				
			||||||
                422: `Unprocessable Content`,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * get_event_players <GET>
 | 
					     * get_event_players <GET>
 | 
				
			||||||
     * @param eventId
 | 
					     * @param eventId
 | 
				
			||||||
| 
						 | 
					@ -206,6 +165,52 @@ export class DefaultService {
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * unattend_event <DELETE>
 | 
				
			||||||
 | 
					     * @param eventId
 | 
				
			||||||
 | 
					     * @param teamId
 | 
				
			||||||
 | 
					     * @returns void
 | 
				
			||||||
 | 
					     * @throws ApiError
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public deleteApiEventsEventIdTeamIdTeamIdAttendance(
 | 
				
			||||||
 | 
					        eventId: number,
 | 
				
			||||||
 | 
					        teamId: number,
 | 
				
			||||||
 | 
					    ): CancelablePromise<void> {
 | 
				
			||||||
 | 
					        return this.httpRequest.request({
 | 
				
			||||||
 | 
					            method: 'DELETE',
 | 
				
			||||||
 | 
					            url: '/api/events/{event_id}/team/id/{team_id}/attendance',
 | 
				
			||||||
 | 
					            path: {
 | 
				
			||||||
 | 
					                'event_id': eventId,
 | 
				
			||||||
 | 
					                'team_id': teamId,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            errors: {
 | 
				
			||||||
 | 
					                422: `Unprocessable Content`,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * attend_event <PUT>
 | 
				
			||||||
 | 
					     * @param eventId
 | 
				
			||||||
 | 
					     * @param teamId
 | 
				
			||||||
 | 
					     * @returns void
 | 
				
			||||||
 | 
					     * @throws ApiError
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public putApiEventsEventIdTeamIdTeamIdAttendance(
 | 
				
			||||||
 | 
					        eventId: number,
 | 
				
			||||||
 | 
					        teamId: number,
 | 
				
			||||||
 | 
					    ): CancelablePromise<void> {
 | 
				
			||||||
 | 
					        return this.httpRequest.request({
 | 
				
			||||||
 | 
					            method: 'PUT',
 | 
				
			||||||
 | 
					            url: '/api/events/{event_id}/team/id/{team_id}/attendance',
 | 
				
			||||||
 | 
					            path: {
 | 
				
			||||||
 | 
					                'event_id': eventId,
 | 
				
			||||||
 | 
					                'team_id': teamId,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            errors: {
 | 
				
			||||||
 | 
					                422: `Unprocessable Content`,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * logout <DELETE>
 | 
					     * logout <DELETE>
 | 
				
			||||||
     * @returns void
 | 
					     * @returns void
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,6 @@ function enableIntegration() {
 | 
				
			||||||
  model.value = {
 | 
					  model.value = {
 | 
				
			||||||
    webhookUrl: "",
 | 
					    webhookUrl: "",
 | 
				
			||||||
    webhookBotName: "",
 | 
					    webhookBotName: "",
 | 
				
			||||||
    webhookBotProfilePicture: null,
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  saveIntegration();
 | 
					  saveIntegration();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -39,10 +38,6 @@ function disableIntegration() {
 | 
				
			||||||
      <h3>Webhook Bot Name</h3>
 | 
					      <h3>Webhook Bot Name</h3>
 | 
				
			||||||
      <input v-model="model.webhookBotName">
 | 
					      <input v-model="model.webhookBotName">
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div class="form-group margin">
 | 
					 | 
				
			||||||
      <h3>Webhook Bot Profile Picture URL (optional)</h3>
 | 
					 | 
				
			||||||
      <input v-model="model.webhookBotProfilePicture">
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <div class="form-group margin">
 | 
					    <div class="form-group margin">
 | 
				
			||||||
      <div class="action-buttons">
 | 
					      <div class="action-buttons">
 | 
				
			||||||
        <button class="destructive-on-hover" @click="disableIntegration">
 | 
					        <button class="destructive-on-hover" @click="disableIntegration">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,19 +1,15 @@
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import type { EventSchema, EventWithPlayerSchema } from "@/client";
 | 
					import type { EventSchema } from "@/client";
 | 
				
			||||||
import { useRosterStore } from "@/stores/roster";
 | 
					 | 
				
			||||||
import { useTeamsStore } from "@/stores/teams";
 | 
					import { useTeamsStore } from "@/stores/teams";
 | 
				
			||||||
import { useTeamsEventsStore } from "@/stores/teams/events";
 | 
					 | 
				
			||||||
import moment from "moment";
 | 
					import moment from "moment";
 | 
				
			||||||
import { computed } from "vue";
 | 
					import { computed } from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const teamsStore = useTeamsStore();
 | 
					const teamsStore = useTeamsStore();
 | 
				
			||||||
const rosterStore = useRosterStore();
 | 
					 | 
				
			||||||
const teamEventsStore = useTeamsEventsStore();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const date = computed(() => moment(props.event.event.startTime));
 | 
					const date = computed(() => moment(props.event.startTime));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const formattedTime = computed(() => {
 | 
					const formattedTime = computed(() => {
 | 
				
			||||||
  const team = teamsStore.teams[props.event.event.teamId];
 | 
					  const team = teamsStore.teams[props.event.teamId];
 | 
				
			||||||
  const offsetDate = date.value.clone().tz(team.tzTimezone);
 | 
					  const offsetDate = date.value.clone().tz(team.tzTimezone);
 | 
				
			||||||
  return `${date.value.format("LT")} (${offsetDate.format("LT z")})`;
 | 
					  return `${date.value.format("LT")} (${offsetDate.format("LT z")})`;
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -27,24 +23,8 @@ const shortMonth = computed(() => {
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps<{
 | 
					const props = defineProps<{
 | 
				
			||||||
  event: EventWithPlayerSchema;
 | 
					  event: EventSchema;
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					 | 
				
			||||||
function attend() {
 | 
					 | 
				
			||||||
  teamEventsStore.attendEvent(props.event.event.id);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function unattend() {
 | 
					 | 
				
			||||||
  teamEventsStore.unattendEvent(props.event.event.id);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function attendOrUnattend() {
 | 
					 | 
				
			||||||
  if (props.event.playerEvent?.hasConfirmed) {
 | 
					 | 
				
			||||||
    unattend();
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    attend();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
| 
						 | 
					@ -59,7 +39,7 @@ function attendOrUnattend() {
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div class="details">
 | 
					    <div class="details">
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
        <h3>{{ event.event.name }}</h3>
 | 
					        <h3>{{ event.name }}</h3>
 | 
				
			||||||
        <div>
 | 
					        <div>
 | 
				
			||||||
          <span>
 | 
					          <span>
 | 
				
			||||||
            <i class="bi bi-clock-fill margin" />
 | 
					            <i class="bi bi-clock-fill margin" />
 | 
				
			||||||
| 
						 | 
					@ -68,33 +48,11 @@ function attendOrUnattend() {
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="subdetails">
 | 
					      <div class="subdetails">
 | 
				
			||||||
        <span v-if="event.event.description">{{ event.event.description }}</span>
 | 
					        <span v-if="event.description">{{ event.description }}</span>
 | 
				
			||||||
        <em v-else class="subtext">No description provided.</em>
 | 
					        <em v-else class="subtext">No description provided.</em>
 | 
				
			||||||
      </div>
 | 
					        <button class="class-info">
 | 
				
			||||||
      <div class="button-group">
 | 
					          <i class="tf2class tf2-PocketScout margin" />
 | 
				
			||||||
        <button
 | 
					          Accept as Pocket Scout
 | 
				
			||||||
          @click="attendOrUnattend()"
 | 
					 | 
				
			||||||
          v-if="event.playerEvent"
 | 
					 | 
				
			||||||
          :class="{
 | 
					 | 
				
			||||||
            'class-info': true,
 | 
					 | 
				
			||||||
            'confirmed': event.playerEvent.hasConfirmed,
 | 
					 | 
				
			||||||
          }"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <template v-if="!event.playerEvent.hasConfirmed">
 | 
					 | 
				
			||||||
            <i class="bi bi-check2" />
 | 
					 | 
				
			||||||
            Confirm
 | 
					 | 
				
			||||||
          </template>
 | 
					 | 
				
			||||||
          <template v-else>
 | 
					 | 
				
			||||||
            <i class="bi bi-check2-all" />
 | 
					 | 
				
			||||||
            Confirmed
 | 
					 | 
				
			||||||
          </template>
 | 
					 | 
				
			||||||
          <span v-if="event.playerEvent.role">
 | 
					 | 
				
			||||||
            as {{ rosterStore.roleNames[event.playerEvent.role.role] }}
 | 
					 | 
				
			||||||
          </span>
 | 
					 | 
				
			||||||
        </button>
 | 
					 | 
				
			||||||
        <button @click="attend" v-else>
 | 
					 | 
				
			||||||
          <i class="bi bi-check2" />
 | 
					 | 
				
			||||||
          Attend
 | 
					 | 
				
			||||||
        </button>
 | 
					        </button>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
| 
						 | 
					@ -104,8 +62,11 @@ function attendOrUnattend() {
 | 
				
			||||||
<style scoped>
 | 
					<style scoped>
 | 
				
			||||||
.event-card {
 | 
					.event-card {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  padding: 1rem;
 | 
				
			||||||
  align-items: center;
 | 
					  align-items: center;
 | 
				
			||||||
  /*background-color: white;*/
 | 
					  /*background-color: white;*/
 | 
				
			||||||
 | 
					  border: 1px solid var(--text);
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
  align-items: stretch;
 | 
					  align-items: stretch;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,10 +77,6 @@ function attendOrUnattend() {
 | 
				
			||||||
  justify-content: center;
 | 
					  justify-content: center;
 | 
				
			||||||
  line-height: 1;
 | 
					  line-height: 1;
 | 
				
			||||||
  flex-basis: 4rem;
 | 
					  flex-basis: 4rem;
 | 
				
			||||||
  padding: 1rem;
 | 
					 | 
				
			||||||
  background-color: var(--text);
 | 
					 | 
				
			||||||
  color: var(--base);
 | 
					 | 
				
			||||||
  border-radius: 8px 0 0 8px;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.date .month {
 | 
					.date .month {
 | 
				
			||||||
| 
						 | 
					@ -134,12 +91,8 @@ function attendOrUnattend() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.details {
 | 
					.details {
 | 
				
			||||||
  padding: 1rem;
 | 
					 | 
				
			||||||
  border: 1px solid var(--text);
 | 
					 | 
				
			||||||
  border-radius: 0 8px 8px 0;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  flex-direction: column;
 | 
					  flex-direction: column;
 | 
				
			||||||
  width: 100%;
 | 
					 | 
				
			||||||
  gap: 0.5rem;
 | 
					  gap: 0.5rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -157,15 +110,4 @@ function attendOrUnattend() {
 | 
				
			||||||
  font-size: 1.2rem;
 | 
					  font-size: 1.2rem;
 | 
				
			||||||
  vertical-align: middle;
 | 
					  vertical-align: middle;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
.button-group {
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  gap: 0.5rem;
 | 
					 | 
				
			||||||
  justify-content: flex-end;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
button.confirmed {
 | 
					 | 
				
			||||||
  background-color: var(--text);
 | 
					 | 
				
			||||||
  color: var(--base);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,15 @@
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import type { EventWithPlayerSchema } from "@/client";
 | 
					import type { EventSchema } from "@/client";
 | 
				
			||||||
import EventCard from "./EventCard.vue";
 | 
					import EventCard from "./EventCard.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps<{
 | 
					const props = defineProps<{
 | 
				
			||||||
  events: EventWithPlayerSchema[];
 | 
					  events: EventSchema[];
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="events-list" v-if="props.events?.length > 0">
 | 
					  <div class="events-list" v-if="props.events?.length > 0">
 | 
				
			||||||
    <EventCard v-for="event in props.events" :key="event.event.id" :event="event" />
 | 
					    <EventCard v-for="event in props.events" :key="event.id" :event="event" />
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <div class="events-list" v-else>
 | 
					  <div class="events-list" v-else>
 | 
				
			||||||
    <em class="subtext">No upcoming events.</em>
 | 
					    <em class="subtext">No upcoming events.</em>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,54 +0,0 @@
 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { useRosterStore } from "@/stores/roster";
 | 
					 | 
				
			||||||
import moment from "moment";
 | 
					 | 
				
			||||||
import { useRoute, useRouter } from "vue-router";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const route = useRoute();
 | 
					 | 
				
			||||||
const router = useRouter();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const rosterStore = useRosterStore();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function saveRoster() {
 | 
					 | 
				
			||||||
  rosterStore.saveRoster(Number(route.params.teamId))
 | 
					 | 
				
			||||||
    .then(() => {
 | 
					 | 
				
			||||||
      router.push({
 | 
					 | 
				
			||||||
        name: "team-details",
 | 
					 | 
				
			||||||
        params: {
 | 
					 | 
				
			||||||
          id: route.params.teamId
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
  <div class="event-scheduler-container">
 | 
					 | 
				
			||||||
    <h1 class="roster-title">
 | 
					 | 
				
			||||||
      Roster for Snus Brotherhood
 | 
					 | 
				
			||||||
    </h1>
 | 
					 | 
				
			||||||
    <div v-if="rosterStore.startTime">
 | 
					 | 
				
			||||||
      <span class="aside date">
 | 
					 | 
				
			||||||
        {{ moment.unix(rosterStore.startTime).format("LL LT") }}
 | 
					 | 
				
			||||||
      </span>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <div class="form-group margin">
 | 
					 | 
				
			||||||
      <h3>Event Name</h3>
 | 
					 | 
				
			||||||
      <input v-model="rosterStore.title" />
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <div class="form-group margin">
 | 
					 | 
				
			||||||
      <h3>Description (optional)</h3>
 | 
					 | 
				
			||||||
      <input v-model="rosterStore.description" />
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <div class="form-group margin">
 | 
					 | 
				
			||||||
      <div class="action-buttons">
 | 
					 | 
				
			||||||
        <button class="accent" @click="saveRoster">Save roster</button>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped>
 | 
					 | 
				
			||||||
em.aside.date {
 | 
					 | 
				
			||||||
  font-size: 11pt;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import type { PlayerRoleSchema } from "@/client";
 | 
					 | 
				
			||||||
import { ref } from "vue";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function useEventForm() {
 | 
					 | 
				
			||||||
  const title = ref("");
 | 
					 | 
				
			||||||
  const description = ref("");
 | 
					 | 
				
			||||||
  const players = ref<PlayerRoleSchema[]>([]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    title,
 | 
					 | 
				
			||||||
    description,
 | 
					 | 
				
			||||||
    players,
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,6 @@ import { type EventSchema, type CreateEventJson, type PlayerRoleSchema } from "@
 | 
				
			||||||
import { useTeamDetails } from "@/composables/team-details";
 | 
					import { useTeamDetails } from "@/composables/team-details";
 | 
				
			||||||
import moment from "moment";
 | 
					import moment from "moment";
 | 
				
			||||||
import { useRoute, useRouter } from "vue-router";
 | 
					import { useRoute, useRouter } from "vue-router";
 | 
				
			||||||
import { useEventForm } from "@/composables/event-form";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useRosterStore = defineStore("roster", () => {
 | 
					export const useRosterStore = defineStore("roster", () => {
 | 
				
			||||||
  const clientStore = useClientStore();
 | 
					  const clientStore = useClientStore();
 | 
				
			||||||
| 
						 | 
					@ -170,8 +169,6 @@ export const useRosterStore = defineStore("roster", () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const startTime = ref<number>();
 | 
					  const startTime = ref<number>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { title, description } = useEventForm();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function saveRoster(teamId: number) {
 | 
					  function saveRoster(teamId: number) {
 | 
				
			||||||
    if (startTime.value == undefined) {
 | 
					    if (startTime.value == undefined) {
 | 
				
			||||||
      throw new Error("No start time set");
 | 
					      throw new Error("No start time set");
 | 
				
			||||||
| 
						 | 
					@ -179,8 +176,8 @@ export const useRosterStore = defineStore("roster", () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!currentEvent.value) {
 | 
					    if (!currentEvent.value) {
 | 
				
			||||||
      const body: CreateEventJson = {
 | 
					      const body: CreateEventJson = {
 | 
				
			||||||
        name: title.value,
 | 
					        name: "Test",
 | 
				
			||||||
        description: description.value,
 | 
					        description: "test description",
 | 
				
			||||||
        startTime: startTime.value.toString(),
 | 
					        startTime: startTime.value.toString(),
 | 
				
			||||||
        playerRoles: Object.values(selectedPlayers).map((player) => ({
 | 
					        playerRoles: Object.values(selectedPlayers).map((player) => ({
 | 
				
			||||||
          player: {
 | 
					          player: {
 | 
				
			||||||
| 
						 | 
					@ -194,10 +191,12 @@ export const useRosterStore = defineStore("roster", () => {
 | 
				
			||||||
        })),
 | 
					        })),
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return clientStore.client.default.createEvent(teamId, body);
 | 
					      clientStore.client.default.createEvent(teamId, body)
 | 
				
			||||||
 | 
					        .then(() => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // TODO: update event
 | 
					      // TODO: update event
 | 
				
			||||||
      throw "Not implemented";
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -218,7 +217,5 @@ export const useRosterStore = defineStore("roster", () => {
 | 
				
			||||||
    fetchPlayersFromEvent,
 | 
					    fetchPlayersFromEvent,
 | 
				
			||||||
    startTime,
 | 
					    startTime,
 | 
				
			||||||
    saveRoster,
 | 
					    saveRoster,
 | 
				
			||||||
    title,
 | 
					 | 
				
			||||||
    description,
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,62 +1,45 @@
 | 
				
			||||||
import { defineStore } from "pinia";
 | 
					import { defineStore } from "pinia";
 | 
				
			||||||
import { useClientStore } from "../client";
 | 
					import { useClientStore } from "../client";
 | 
				
			||||||
import type { EventWithPlayerSchema } from "@/client";
 | 
					import type { EventSchema, EventSchemaList } from "@/client";
 | 
				
			||||||
import { useEventsStore } from "../events";
 | 
					import { useEventsStore } from "../events";
 | 
				
			||||||
import { computed, reactive } from "vue";
 | 
					import { computed } from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useTeamsEventsStore = defineStore("teamsEvents", () => {
 | 
					export const useTeamsEventsStore = defineStore("teamsEvents", () => {
 | 
				
			||||||
  const clientStore = useClientStore();
 | 
					  const clientStore = useClientStore();
 | 
				
			||||||
  const client = clientStore.client;
 | 
					  const client = clientStore.client;
 | 
				
			||||||
  //const eventsStore = useEventsStore();
 | 
					  const eventsStore = useEventsStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const teamEvents = reactive<{ [teamId: number]: EventWithPlayerSchema[] }>({ });
 | 
					  const teamEvents = computed(() => {
 | 
				
			||||||
  //const teamEvents = computed(() => {
 | 
					    console.log("Recomputing teamEvents");
 | 
				
			||||||
  //  console.log("Recomputing teamEvents");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  //  // map events to objects with teamId as key, and array of events as value
 | 
					    // map events to objects with teamId as key, and array of events as value
 | 
				
			||||||
  //  return eventsStore.events
 | 
					    return eventsStore.events
 | 
				
			||||||
  //    .reduce((acc, event) => {
 | 
					      .reduce((acc, event) => {
 | 
				
			||||||
  //      if (!acc[event.teamId]) {
 | 
					        if (!acc[event.teamId]) {
 | 
				
			||||||
  //        acc[event.teamId] = [];
 | 
					          acc[event.teamId] = [];
 | 
				
			||||||
  //      }
 | 
					        }
 | 
				
			||||||
  //      acc[event.teamId].push(event);
 | 
					        acc[event.teamId].push(event);
 | 
				
			||||||
  //      return acc;
 | 
					        return acc;
 | 
				
			||||||
  //    }, { } as { [teamId: number]: EventSchema[] });
 | 
					      }, { } as { [teamId: number]: EventSchema[] });
 | 
				
			||||||
  //});
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function fetchTeamEvents(teamId: number) {
 | 
					  function fetchTeamEvents(teamId: number) {
 | 
				
			||||||
    return clientStore.call(
 | 
					    return clientStore.call(
 | 
				
			||||||
      fetchTeamEvents.name,
 | 
					      fetchTeamEvents.name,
 | 
				
			||||||
      () => client.default.getTeamEvents(teamId),
 | 
					      () => client.default.getTeamEvents(teamId),
 | 
				
			||||||
      (result: EventWithPlayerSchema[]) => {
 | 
					      (result: EventSchemaList) => {
 | 
				
			||||||
        teamEvents[teamId] = result;
 | 
					        result.forEach((event) => {
 | 
				
			||||||
 | 
					          // insert into event store
 | 
				
			||||||
 | 
					          //eventsStore.events[event.id] = event;
 | 
				
			||||||
 | 
					          eventsStore.events.push(event);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        return result;
 | 
					        return result;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function attendEvent(eventId: number) {
 | 
					 | 
				
			||||||
    client.default.attendEvent(eventId)
 | 
					 | 
				
			||||||
      .then((response) => {
 | 
					 | 
				
			||||||
        let index = teamEvents[response.event.teamId]
 | 
					 | 
				
			||||||
          .findIndex((event) => event.event.id == response.event.id);
 | 
					 | 
				
			||||||
        teamEvents[response.event.teamId][index] = response;
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function unattendEvent(eventId: number) {
 | 
					 | 
				
			||||||
    client.default.unattendEvent(eventId)
 | 
					 | 
				
			||||||
      .then((response) => {
 | 
					 | 
				
			||||||
        let index = teamEvents[response.event.teamId]
 | 
					 | 
				
			||||||
          .findIndex((event) => event.event.id == response.event.id);
 | 
					 | 
				
			||||||
        teamEvents[response.event.teamId][index].playerEvent = null;
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    teamEvents,
 | 
					    teamEvents,
 | 
				
			||||||
    fetchTeamEvents,
 | 
					    fetchTeamEvents,
 | 
				
			||||||
    attendEvent,
 | 
					 | 
				
			||||||
    unattendEvent,
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,12 @@
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import PlayerCard from "../components/PlayerCard.vue";
 | 
					import PlayerCard from "../components/PlayerCard.vue";
 | 
				
			||||||
 | 
					import RoleSlot from "../components/RoleSlot.vue";
 | 
				
			||||||
 | 
					import PlayerTeamRole from "../player.ts";
 | 
				
			||||||
import { computed, reactive, onMounted } from "vue";
 | 
					import { computed, reactive, onMounted } from "vue";
 | 
				
			||||||
import { useRosterStore } from "../stores/roster";
 | 
					import { useRosterStore } from "../stores/roster";
 | 
				
			||||||
import { useRoute } from "vue-router";
 | 
					import { useRoute } from "vue-router";
 | 
				
			||||||
import moment from "moment";
 | 
					import moment from "moment";
 | 
				
			||||||
import { useEventsStore } from "@/stores/events";
 | 
					import { useEventsStore } from "@/stores/events";
 | 
				
			||||||
import EventSchedulerForm from "@/components/EventSchedulerForm.vue";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rosterStore = useRosterStore();
 | 
					const rosterStore = useRosterStore();
 | 
				
			||||||
const eventsStore = useEventsStore();
 | 
					const eventsStore = useEventsStore();
 | 
				
			||||||
| 
						 | 
					@ -22,6 +23,10 @@ const hasAlternates = computed(() => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const eventId = computed<number | undefined>(() => Number(route.params.eventId));
 | 
					const eventId = computed<number | undefined>(() => Number(route.params.eventId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function saveRoster() {
 | 
				
			||||||
 | 
					  rosterStore.saveRoster(Number(route.params.teamId));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
  if (eventId.value) {
 | 
					  if (eventId.value) {
 | 
				
			||||||
    const event = await eventsStore.fetchEvent(eventId.value);
 | 
					    const event = await eventsStore.fetchEvent(eventId.value);
 | 
				
			||||||
| 
						 | 
					@ -37,19 +42,26 @@ onMounted(async () => {
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <main>
 | 
					  <main>
 | 
				
			||||||
    <div class="top">
 | 
					    <div class="top">
 | 
				
			||||||
      <a>
 | 
					      <h1 class="roster-title">
 | 
				
			||||||
        <i class="bi bi-arrow-left" />
 | 
					        Roster for Snus Brotherhood
 | 
				
			||||||
        Back
 | 
					        <em class="aside date" v-if="rosterStore.startTime">
 | 
				
			||||||
      </a>
 | 
					          @
 | 
				
			||||||
 | 
					          {{ moment.unix(rosterStore.startTime).format("L LT") }}
 | 
				
			||||||
 | 
					        </em>
 | 
				
			||||||
 | 
					      </h1>
 | 
				
			||||||
 | 
					      <div class="button-group">
 | 
				
			||||||
 | 
					        <button>Cancel</button>
 | 
				
			||||||
 | 
					        <button class="accent" @click="saveRoster">Save Roster</button>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div class="columns">
 | 
					    <div class="columns">
 | 
				
			||||||
      <div class="form-group margin column">
 | 
					      <div class="column">
 | 
				
			||||||
        <PlayerCard v-for="role in rosterStore.neededRoles"
 | 
					        <PlayerCard v-for="role in rosterStore.neededRoles"
 | 
				
			||||||
                    :player="rosterStore.selectedPlayers[role]"
 | 
					                    :player="rosterStore.selectedPlayers[role]"
 | 
				
			||||||
                    :role-title="role"
 | 
					                    :role-title="role"
 | 
				
			||||||
                    is-roster />
 | 
					                    is-roster />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="form-group margin column" v-if="rosterStore.selectedRole">
 | 
					      <div class="column">
 | 
				
			||||||
        <PlayerCard v-for="player in rosterStore.mainRoles"
 | 
					        <PlayerCard v-for="player in rosterStore.mainRoles"
 | 
				
			||||||
                    :player="player"
 | 
					                    :player="player"
 | 
				
			||||||
                    :role-title="player.role" />
 | 
					                    :role-title="player.role" />
 | 
				
			||||||
| 
						 | 
					@ -63,15 +75,6 @@ onMounted(async () => {
 | 
				
			||||||
        <PlayerCard v-if="rosterStore.selectedRole"
 | 
					        <PlayerCard v-if="rosterStore.selectedRole"
 | 
				
			||||||
                    is-ringer
 | 
					                    is-ringer
 | 
				
			||||||
                    :role-title="rosterStore.selectedRole" />
 | 
					                    :role-title="rosterStore.selectedRole" />
 | 
				
			||||||
        <div class="action-buttons">
 | 
					 | 
				
			||||||
          <button class="accent">
 | 
					 | 
				
			||||||
            <i class="bi bi-check" />
 | 
					 | 
				
			||||||
            Done
 | 
					 | 
				
			||||||
          </button>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div class="column" v-else>
 | 
					 | 
				
			||||||
        <EventSchedulerForm />
 | 
					 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </main>
 | 
					  </main>
 | 
				
			||||||
| 
						 | 
					@ -117,4 +120,9 @@ onMounted(async () => {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  gap: 0.5em;
 | 
					  gap: 0.5em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					em.aside.date {
 | 
				
			||||||
 | 
					  font-size: 14px;
 | 
				
			||||||
 | 
					  vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,14 +7,12 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
from typing import Optional
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from flask import Blueprint, abort, make_response
 | 
					from flask import Blueprint, abort, make_response
 | 
				
			||||||
from spectree import Response
 | 
					from spectree import Response
 | 
				
			||||||
from sqlalchemy import Row
 | 
					 | 
				
			||||||
from sqlalchemy.sql import tuple_
 | 
					from sqlalchemy.sql import tuple_
 | 
				
			||||||
from models.player import Player
 | 
					from models.player import Player
 | 
				
			||||||
from models.player_event import EventWithPlayerSchema, PlayerEvent, PlayerEventRolesSchema
 | 
					from models.player_event import PlayerEvent, PlayerEventRolesSchema
 | 
				
			||||||
from models.player_team_availability import PlayerTeamAvailability
 | 
					from models.player_team_availability import PlayerTeamAvailability
 | 
				
			||||||
from models.player_team_role import PlayerRoleSchema, PlayerTeamRole
 | 
					from models.player_team_role import PlayerRoleSchema, PlayerTeamRole
 | 
				
			||||||
from models.team import Team
 | 
					from models.team import Team
 | 
				
			||||||
| 
						 | 
					@ -45,30 +43,23 @@ def get_event(event_id: int):
 | 
				
			||||||
@api_events.get("/team/id/<int:team_id>")
 | 
					@api_events.get("/team/id/<int:team_id>")
 | 
				
			||||||
@spec.validate(
 | 
					@spec.validate(
 | 
				
			||||||
    resp=Response(
 | 
					    resp=Response(
 | 
				
			||||||
        HTTP_200=list[EventWithPlayerSchema],
 | 
					        HTTP_200=list[EventSchema],
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    operation_id="get_team_events",
 | 
					    operation_id="get_team_events",
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@requires_authentication
 | 
					def get_team_events(team_id: int):
 | 
				
			||||||
@requires_team_membership()
 | 
					    events = db.session.query(
 | 
				
			||||||
def get_team_events(player_team: PlayerTeam, team_id: int, **_):
 | 
					        Event
 | 
				
			||||||
    rows = db.session.query(
 | 
					    ).filter(
 | 
				
			||||||
        Event, PlayerEvent
 | 
					 | 
				
			||||||
    ).outerjoin(
 | 
					 | 
				
			||||||
        PlayerEvent,
 | 
					 | 
				
			||||||
        (PlayerEvent.event_id == Event.id) & (PlayerEvent.player_id == player_team.player_id)
 | 
					 | 
				
			||||||
    ).where(
 | 
					 | 
				
			||||||
        Event.team_id == team_id
 | 
					        Event.team_id == team_id
 | 
				
			||||||
    ).order_by(
 | 
					    ).order_by(
 | 
				
			||||||
        Event.start_time
 | 
					        Event.start_time
 | 
				
			||||||
    ).all()
 | 
					    ).all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def map_to_schema(row: Row[tuple[Event, PlayerEvent]]):
 | 
					    def map_to_schema(event: Event):
 | 
				
			||||||
        return EventWithPlayerSchema.from_event_player_event(
 | 
					        return EventSchema.from_model(event).dict(by_alias=True)
 | 
				
			||||||
            *row.tuple()
 | 
					 | 
				
			||||||
        ).dict(by_alias=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return list(map(map_to_schema, rows))
 | 
					    return list(map(map_to_schema, events))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_events.get("/user/id/<int:user_id>")
 | 
					@api_events.get("/user/id/<int:user_id>")
 | 
				
			||||||
def get_user_events(user_id: int):
 | 
					def get_user_events(user_id: int):
 | 
				
			||||||
| 
						 | 
					@ -138,33 +129,28 @@ def create_event(player_team: PlayerTeam, team_id: int, json: CreateEventJson, *
 | 
				
			||||||
@api_events.put("/<int:event_id>/attendance")
 | 
					@api_events.put("/<int:event_id>/attendance")
 | 
				
			||||||
@spec.validate(
 | 
					@spec.validate(
 | 
				
			||||||
    resp=Response(
 | 
					    resp=Response(
 | 
				
			||||||
        HTTP_200=EventWithPlayerSchema,
 | 
					        HTTP_204=None,
 | 
				
			||||||
    ),
 | 
					    )
 | 
				
			||||||
    operation_id="attend_event",
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@requires_authentication
 | 
					@requires_authentication
 | 
				
			||||||
def attend_event(player: Player, event_id: int, **_):
 | 
					@requires_team_membership()
 | 
				
			||||||
    event = db.session.query(Event).where(Event.id == event_id).one_or_none()
 | 
					def attend_event(player_team: PlayerTeam, event_id: int, **_):
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not event:
 | 
					 | 
				
			||||||
        abort(404)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    assert_team_membership(player, event.team)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    player_event = db.session.query(
 | 
					    player_event = db.session.query(
 | 
				
			||||||
        PlayerEvent
 | 
					        PlayerEvent
 | 
				
			||||||
    ).where(
 | 
					    ).where(
 | 
				
			||||||
        PlayerEvent.event_id == event_id
 | 
					        PlayerEvent.event_id == event_id
 | 
				
			||||||
    ).where(
 | 
					    ).where(
 | 
				
			||||||
        PlayerEvent.player_id == player.steam_id
 | 
					        PlayerEvent.player_id == player_team.player_id
 | 
				
			||||||
    ).join(
 | 
					    ).join(
 | 
				
			||||||
        Event
 | 
					        Event
 | 
				
			||||||
 | 
					    ).where(
 | 
				
			||||||
 | 
					        Event.team_id == player_team.team_id
 | 
				
			||||||
    ).one_or_none()
 | 
					    ).one_or_none()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not player_event:
 | 
					    if not player_event:
 | 
				
			||||||
        player_event = PlayerEvent()
 | 
					        player_event = PlayerEvent()
 | 
				
			||||||
        player_event.event_id = event_id
 | 
					        player_event.event_id = event_id
 | 
				
			||||||
        player_event.player_id = player.steam_id
 | 
					        player_event.player_id = player_team.player_id
 | 
				
			||||||
        db.session.add(player_event)
 | 
					        db.session.add(player_event)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    player_event.has_confirmed = True
 | 
					    player_event.has_confirmed = True
 | 
				
			||||||
| 
						 | 
					@ -173,28 +159,27 @@ def attend_event(player: Player, event_id: int, **_):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    player_event.event.update_discord_message()
 | 
					    player_event.event.update_discord_message()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return EventWithPlayerSchema.from_event_player_event(
 | 
					    return make_response({ }, 204)
 | 
				
			||||||
        player_event.event,
 | 
					 | 
				
			||||||
        player_event,
 | 
					 | 
				
			||||||
    ).dict(by_alias=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_events.delete("/<int:event_id>/attendance")
 | 
					@api_events.delete("/<int:event_id>/attendance")
 | 
				
			||||||
@spec.validate(
 | 
					@spec.validate(
 | 
				
			||||||
    resp=Response(
 | 
					    resp=Response(
 | 
				
			||||||
        HTTP_200=EventWithPlayerSchema,
 | 
					        HTTP_204=None,
 | 
				
			||||||
    ),
 | 
					    )
 | 
				
			||||||
    operation_id="unattend_event",
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@requires_authentication
 | 
					@requires_authentication
 | 
				
			||||||
def unattend_event(player: Player, event_id: int, **_):
 | 
					@requires_team_membership()
 | 
				
			||||||
 | 
					def unattend_event(player_team: PlayerTeam, event_id: int, **_):
 | 
				
			||||||
    result = db.session.query(
 | 
					    result = db.session.query(
 | 
				
			||||||
        PlayerEvent, Event
 | 
					        PlayerEvent, Event
 | 
				
			||||||
    ).where(
 | 
					    ).where(
 | 
				
			||||||
        PlayerEvent.player_id == player.steam_id
 | 
					        PlayerEvent.event_id == event_id
 | 
				
			||||||
 | 
					    ).where(
 | 
				
			||||||
 | 
					        PlayerEvent.player_id == player_team.player_id
 | 
				
			||||||
    ).join(
 | 
					    ).join(
 | 
				
			||||||
        Event
 | 
					        Event
 | 
				
			||||||
    ).where(
 | 
					    ).where(
 | 
				
			||||||
        Event.id == event_id
 | 
					        Event.team_id == player_team.team_id
 | 
				
			||||||
    ).one_or_none()
 | 
					    ).one_or_none()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not result:
 | 
					    if not result:
 | 
				
			||||||
| 
						 | 
					@ -207,10 +192,7 @@ def unattend_event(player: Player, event_id: int, **_):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    event.update_discord_message()
 | 
					    event.update_discord_message()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return EventWithPlayerSchema.from_event_player_event(
 | 
					    return make_response({ }, 204)
 | 
				
			||||||
        event,
 | 
					 | 
				
			||||||
        None,
 | 
					 | 
				
			||||||
    ).dict(by_alias=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GetEventPlayersResponse(BaseModel):
 | 
					class GetEventPlayersResponse(BaseModel):
 | 
				
			||||||
    players: list[PlayerEventRolesSchema]
 | 
					    players: list[PlayerEventRolesSchema]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,32 +0,0 @@
 | 
				
			||||||
"""Add webhook profile picture
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Revision ID: c242e3f99c64
 | 
					 | 
				
			||||||
Revises: 286ee26b9e5d
 | 
					 | 
				
			||||||
Create Date: 2024-11-27 10:40:39.027786
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
from alembic import op
 | 
					 | 
				
			||||||
import sqlalchemy as sa
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# revision identifiers, used by Alembic.
 | 
					 | 
				
			||||||
revision = 'c242e3f99c64'
 | 
					 | 
				
			||||||
down_revision = '286ee26b9e5d'
 | 
					 | 
				
			||||||
branch_labels = None
 | 
					 | 
				
			||||||
depends_on = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def upgrade():
 | 
					 | 
				
			||||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
					 | 
				
			||||||
    with op.batch_alter_table('team_discord_integrations', schema=None) as batch_op:
 | 
					 | 
				
			||||||
        batch_op.add_column(sa.Column('webhook_bot_profile_picture', sa.String(), nullable=True))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # ### end Alembic commands ###
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def downgrade():
 | 
					 | 
				
			||||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
					 | 
				
			||||||
    with op.batch_alter_table('team_discord_integrations', schema=None) as batch_op:
 | 
					 | 
				
			||||||
        batch_op.drop_column('webhook_bot_profile_picture')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # ### end Alembic commands ###
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,4 @@
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
import threading
 | 
					 | 
				
			||||||
from sqlalchemy.orm import mapped_column, relationship
 | 
					from sqlalchemy.orm import mapped_column, relationship
 | 
				
			||||||
from sqlalchemy.orm.attributes import Mapped
 | 
					from sqlalchemy.orm.attributes import Mapped
 | 
				
			||||||
from sqlalchemy.orm.properties import ForeignKey
 | 
					from sqlalchemy.orm.properties import ForeignKey
 | 
				
			||||||
| 
						 | 
					@ -64,7 +63,7 @@ class Event(app_db.BaseModel):
 | 
				
			||||||
            f"<t:{start_timestamp}:f>",
 | 
					            f"<t:{start_timestamp}:f>",
 | 
				
			||||||
            "\n".join(players_info),
 | 
					            "\n".join(players_info),
 | 
				
			||||||
            "",
 | 
					            "",
 | 
				
			||||||
            "[Confirm attendance here]" +
 | 
					            "[Confirm availability here]" +
 | 
				
			||||||
                f"(https://availabili.tf/team/id/{self.team.id}/events/{self.id})",
 | 
					                f"(https://availabili.tf/team/id/{self.team.id}/events/{self.id})",
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,23 +81,16 @@ class Event(app_db.BaseModel):
 | 
				
			||||||
            return DiscordWebhook(
 | 
					            return DiscordWebhook(
 | 
				
			||||||
                integration.webhook_url,
 | 
					                integration.webhook_url,
 | 
				
			||||||
                id=str(self.discord_message_id),
 | 
					                id=str(self.discord_message_id),
 | 
				
			||||||
                username=integration.webhook_bot_name,
 | 
					 | 
				
			||||||
                avatar_url=integration.webhook_bot_profile_picture,
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return DiscordWebhook(
 | 
					            return DiscordWebhook(integration.webhook_url)
 | 
				
			||||||
                integration.webhook_url,
 | 
					 | 
				
			||||||
                username=integration.webhook_bot_name,
 | 
					 | 
				
			||||||
                avatar_url=integration.webhook_bot_profile_picture,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update_discord_message(self):
 | 
					    def update_discord_message(self):
 | 
				
			||||||
        webhook = self.get_or_create_webhook()
 | 
					        webhook = self.get_or_create_webhook()
 | 
				
			||||||
        if webhook:
 | 
					        if webhook:
 | 
				
			||||||
            webhook.content = self.get_discord_content()
 | 
					            webhook.content = self.get_discord_content()
 | 
				
			||||||
            if webhook.id:
 | 
					            if webhook.id:
 | 
				
			||||||
                # fire and forget
 | 
					                webhook.edit()
 | 
				
			||||||
                threading.Thread(target=webhook.edit).start()
 | 
					 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                webhook.execute()
 | 
					                webhook.execute()
 | 
				
			||||||
                if webhook_id := webhook.id:
 | 
					                if webhook_id := webhook.id:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,24 +26,6 @@ class PlayerEvent(app_db.BaseModel):
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    role: Mapped["PlayerTeamRole"] = relationship("PlayerTeamRole")
 | 
					    role: Mapped["PlayerTeamRole"] = relationship("PlayerTeamRole")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EventWithPlayerSchema(spec.BaseModel):
 | 
					 | 
				
			||||||
    event: "EventSchema"
 | 
					 | 
				
			||||||
    player_event: Optional["PlayerEventRolesSchema"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def from_event_player_event(cls, event: "Event", player_event: Optional["PlayerEvent"]):
 | 
					 | 
				
			||||||
        res = cls(
 | 
					 | 
				
			||||||
            event=EventSchema.from_model(event),
 | 
					 | 
				
			||||||
            player_event=None,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if player_event:
 | 
					 | 
				
			||||||
            res.player_event = PlayerEventRolesSchema.from_event_player_team(
 | 
					 | 
				
			||||||
                player_event, player_event.player_team
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return res
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class PlayerEventRolesSchema(spec.BaseModel):
 | 
					class PlayerEventRolesSchema(spec.BaseModel):
 | 
				
			||||||
    player: "PlayerSchema"
 | 
					    player: "PlayerSchema"
 | 
				
			||||||
    role: Optional["RoleSchema"]
 | 
					    role: Optional["RoleSchema"]
 | 
				
			||||||
| 
						 | 
					@ -62,7 +44,7 @@ class PlayerEventRolesSchema(spec.BaseModel):
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from models.event import Event, EventSchema
 | 
					from models.event import Event
 | 
				
			||||||
from models.player import Player, PlayerSchema
 | 
					from models.player import Player, PlayerSchema
 | 
				
			||||||
from models.player_team_role import PlayerTeamRole, RoleSchema
 | 
					from models.player_team_role import PlayerTeamRole, RoleSchema
 | 
				
			||||||
from models.player_team import PlayerTeam
 | 
					from models.player_team import PlayerTeam
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,6 +36,7 @@ class Team(app_db.BaseModel):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update_integrations(self, integrations: "TeamIntegrationSchema"):
 | 
					    def update_integrations(self, integrations: "TeamIntegrationSchema"):
 | 
				
			||||||
        if integrations.discord_integration:
 | 
					        if integrations.discord_integration:
 | 
				
			||||||
 | 
					            print("DISCORD!!!")
 | 
				
			||||||
            discord_integration = self.discord_integration \
 | 
					            discord_integration = self.discord_integration \
 | 
				
			||||||
                or TeamDiscordIntegration()
 | 
					                or TeamDiscordIntegration()
 | 
				
			||||||
            discord_integration.webhook_url = integrations \
 | 
					            discord_integration.webhook_url = integrations \
 | 
				
			||||||
| 
						 | 
					@ -43,10 +44,6 @@ class Team(app_db.BaseModel):
 | 
				
			||||||
            discord_integration.webhook_bot_name = integrations \
 | 
					            discord_integration.webhook_bot_name = integrations \
 | 
				
			||||||
                .discord_integration.webhook_bot_name
 | 
					                .discord_integration.webhook_bot_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if integrations.discord_integration.webhook_bot_profile_picture:
 | 
					 | 
				
			||||||
                discord_integration.webhook_bot_profile_picture = integrations \
 | 
					 | 
				
			||||||
                    .discord_integration.webhook_bot_profile_picture
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if discord_integration.team_id is None:
 | 
					            if discord_integration.team_id is None:
 | 
				
			||||||
                discord_integration.team_id = self.id
 | 
					                discord_integration.team_id = self.id
 | 
				
			||||||
                app_db.db.session.add(discord_integration)
 | 
					                app_db.db.session.add(discord_integration)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,3 @@
 | 
				
			||||||
from typing import Optional
 | 
					 | 
				
			||||||
from sqlalchemy.orm import mapped_column, relationship
 | 
					from sqlalchemy.orm import mapped_column, relationship
 | 
				
			||||||
from sqlalchemy.orm.attributes import Mapped
 | 
					from sqlalchemy.orm.attributes import Mapped
 | 
				
			||||||
from sqlalchemy.orm.properties import ForeignKey
 | 
					from sqlalchemy.orm.properties import ForeignKey
 | 
				
			||||||
| 
						 | 
					@ -13,21 +12,18 @@ class TeamDiscordIntegration(app_db.BaseModel):
 | 
				
			||||||
    team_id: Mapped[int] = mapped_column(ForeignKey("teams.id"), primary_key=True)
 | 
					    team_id: Mapped[int] = mapped_column(ForeignKey("teams.id"), primary_key=True)
 | 
				
			||||||
    webhook_url: Mapped[str] = mapped_column(String)
 | 
					    webhook_url: Mapped[str] = mapped_column(String)
 | 
				
			||||||
    webhook_bot_name: Mapped[str] = mapped_column(String)
 | 
					    webhook_bot_name: Mapped[str] = mapped_column(String)
 | 
				
			||||||
    webhook_bot_profile_picture: Mapped[str] = mapped_column(String(255), nullable=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    team: Mapped["Team"] = relationship("Team", back_populates="discord_integration")
 | 
					    team: Mapped["Team"] = relationship("Team", back_populates="discord_integration")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TeamDiscordIntegrationSchema(spec.BaseModel):
 | 
					class TeamDiscordIntegrationSchema(spec.BaseModel):
 | 
				
			||||||
    webhook_url: str
 | 
					    webhook_url: str
 | 
				
			||||||
    webhook_bot_name: str
 | 
					    webhook_bot_name: str
 | 
				
			||||||
    webhook_bot_profile_picture: Optional[str]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def from_model(cls, model: TeamDiscordIntegration) -> "TeamDiscordIntegrationSchema":
 | 
					    def from_model(cls, model: TeamDiscordIntegration) -> "TeamDiscordIntegrationSchema":
 | 
				
			||||||
        return cls(
 | 
					        return cls(
 | 
				
			||||||
            webhook_url=model.webhook_url,
 | 
					            webhook_url=model.webhook_url,
 | 
				
			||||||
            webhook_bot_name=model.webhook_bot_name,
 | 
					            webhook_bot_name=model.webhook_bot_name,
 | 
				
			||||||
            webhook_bot_profile_picture=model.webhook_bot_profile_picture,
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TeamLogsTfIntegration(app_db.BaseModel):
 | 
					class TeamLogsTfIntegration(app_db.BaseModel):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue