Implement event stuff
parent
e11bcc2a08
commit
e1c6a7bb14
|
@ -66,6 +66,35 @@
|
||||||
--vs-dropdown-option--active-bg: var(--accent)!important;
|
--vs-dropdown-option--active-bg: var(--accent)!important;
|
||||||
--vs-dropdown-option--active-color: var(--base)!important;
|
--vs-dropdown-option--active-color: var(--base)!important;
|
||||||
--vs-border-color: var(--overlay-0)!important;
|
--vs-border-color: var(--overlay-0)!important;
|
||||||
|
|
||||||
|
/*
|
||||||
|
--rosewater: #f4dbd6;
|
||||||
|
--flamingo: #f0c6c6;
|
||||||
|
--pink: #f5bde6;
|
||||||
|
--mauve: #c6a0f6;
|
||||||
|
--red: #ed8796;
|
||||||
|
--maroon: #ee99a0;
|
||||||
|
--peach: #f5a97f;
|
||||||
|
--yellow: #eed49f;
|
||||||
|
--green: #a6da95;
|
||||||
|
--teal: #8bd5ca;
|
||||||
|
--sky: #91d7e3;
|
||||||
|
--sapphire: #7dc4e4;
|
||||||
|
--blue: #8aadf4;
|
||||||
|
--lavender: #b7bdf8;
|
||||||
|
--text: #cad3f5;
|
||||||
|
--subtext-1: #b8c0e0;
|
||||||
|
--subtext-0: #a5adcb;
|
||||||
|
--overlay-2: #939ab7;
|
||||||
|
--overlay-1: #8087a2;
|
||||||
|
--overlay-0: #6e738d;
|
||||||
|
--surface-2: #5b6078;
|
||||||
|
--surface-1: #494d64;
|
||||||
|
--surface-0: #363a4f;
|
||||||
|
--base: #24273a;
|
||||||
|
--mantle: #1e2030;
|
||||||
|
--crust: #181926;
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/* semantic color variables for this project */
|
/* semantic color variables for this project */
|
||||||
|
|
|
@ -17,6 +17,9 @@ 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 { EventSchemaList } from './models/EventSchemaList';
|
export type { EventSchemaList } from './models/EventSchemaList';
|
||||||
|
export type { GetEventPlayersResponse } from './models/GetEventPlayersResponse';
|
||||||
|
export type { PlayerEventRolesSchema } from './models/PlayerEventRolesSchema';
|
||||||
|
export type { PlayerRoleSchema } from './models/PlayerRoleSchema';
|
||||||
export type { PlayerSchema } from './models/PlayerSchema';
|
export type { PlayerSchema } from './models/PlayerSchema';
|
||||||
export type { PlayerTeamAvailabilityRoleSchema } from './models/PlayerTeamAvailabilityRoleSchema';
|
export type { PlayerTeamAvailabilityRoleSchema } from './models/PlayerTeamAvailabilityRoleSchema';
|
||||||
export type { PutScheduleForm } from './models/PutScheduleForm';
|
export type { PutScheduleForm } from './models/PutScheduleForm';
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
import type { PlayerRoleSchema } from './PlayerRoleSchema';
|
||||||
export type CreateEventJson = {
|
export type CreateEventJson = {
|
||||||
description: string;
|
description: string;
|
||||||
name: string;
|
name: string;
|
||||||
playerIds: Array<number>;
|
playerRoles: Array<PlayerRoleSchema>;
|
||||||
startTime: string;
|
startTime: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export type CreateTeamJson = {
|
export type CreateTeamJson = {
|
||||||
discordWebhookUrl?: string;
|
discordWebhookUrl?: (string | null);
|
||||||
leagueTimezone: string;
|
leagueTimezone: string;
|
||||||
minuteOffset?: number;
|
minuteOffset?: number;
|
||||||
teamName: string;
|
teamName: string;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export type EventSchema = {
|
export type EventSchema = {
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
description?: string;
|
description: (string | null);
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
startTime: string;
|
startTime: string;
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { PlayerEventRolesSchema } from './PlayerEventRolesSchema';
|
||||||
|
export type GetEventPlayersResponse = {
|
||||||
|
players: Array<PlayerEventRolesSchema>;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { PlayerSchema } from './PlayerSchema';
|
||||||
|
import type { RoleSchema } from './RoleSchema';
|
||||||
|
export type PlayerEventRolesSchema = {
|
||||||
|
hasConfirmed: boolean;
|
||||||
|
player: PlayerSchema;
|
||||||
|
playtime: number;
|
||||||
|
role: (RoleSchema | null);
|
||||||
|
roles: Array<RoleSchema>;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { PlayerSchema } from './PlayerSchema';
|
||||||
|
import type { RoleSchema } from './RoleSchema';
|
||||||
|
export type PlayerRoleSchema = {
|
||||||
|
player: PlayerSchema;
|
||||||
|
role: RoleSchema;
|
||||||
|
};
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import type { TeamDiscordIntegrationSchema } from './TeamDiscordIntegrationSchema';
|
import type { TeamDiscordIntegrationSchema } from './TeamDiscordIntegrationSchema';
|
||||||
import type { TeamLogsTfIntegrationSchema } from './TeamLogsTfIntegrationSchema';
|
import type { TeamLogsTfIntegrationSchema } from './TeamLogsTfIntegrationSchema';
|
||||||
export type TeamIntegrationSchema = {
|
export type TeamIntegrationSchema = {
|
||||||
discordIntegration?: TeamDiscordIntegrationSchema;
|
discordIntegration: (TeamDiscordIntegrationSchema | null);
|
||||||
logsTfIntegration?: TeamLogsTfIntegrationSchema;
|
logsTfIntegration: (TeamLogsTfIntegrationSchema | null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export type TeamLogsTfIntegrationSchema = {
|
export type TeamLogsTfIntegrationSchema = {
|
||||||
logsTfApiKey: string;
|
logsTfApiKey: (string | null);
|
||||||
minTeamMemberCount: number;
|
minTeamMemberCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/**
|
|
||||||
* An enumeration.
|
|
||||||
*/
|
|
||||||
export enum TeamRole {
|
export enum TeamRole {
|
||||||
'_0' = 0,
|
'_0' = 0,
|
||||||
'_1' = 1,
|
'_1' = 1,
|
||||||
|
|
|
@ -8,6 +8,7 @@ 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 { EventSchemaList } from '../models/EventSchemaList';
|
import type { EventSchemaList } from '../models/EventSchemaList';
|
||||||
|
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';
|
||||||
import type { SetUsernameJson } from '../models/SetUsernameJson';
|
import type { SetUsernameJson } from '../models/SetUsernameJson';
|
||||||
|
@ -62,7 +63,7 @@ export class DefaultService {
|
||||||
'team_id': teamId,
|
'team_id': teamId,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -73,7 +74,7 @@ export class DefaultService {
|
||||||
* @returns EventSchema OK
|
* @returns EventSchema OK
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public postApiEventsTeamIdTeamId(
|
public createEvent(
|
||||||
teamId: number,
|
teamId: number,
|
||||||
requestBody?: CreateEventJson,
|
requestBody?: CreateEventJson,
|
||||||
): CancelablePromise<EventSchema> {
|
): CancelablePromise<EventSchema> {
|
||||||
|
@ -86,7 +87,7 @@ export class DefaultService {
|
||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
errors: {
|
errors: {
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -123,7 +124,27 @@ export class DefaultService {
|
||||||
'event_id': eventId,
|
'event_id': eventId,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* get_event_players <GET>
|
||||||
|
* @param eventId
|
||||||
|
* @returns GetEventPlayersResponse OK
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public getEventPlayers(
|
||||||
|
eventId: number,
|
||||||
|
): CancelablePromise<GetEventPlayersResponse> {
|
||||||
|
return this.httpRequest.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/events/{event_id}/players',
|
||||||
|
path: {
|
||||||
|
'event_id': eventId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -144,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
|
||||||
|
@ -188,7 +255,7 @@ export class DefaultService {
|
||||||
url: '/api/login/get-user',
|
url: '/api/login/get-user',
|
||||||
errors: {
|
errors: {
|
||||||
401: `Unauthorized`,
|
401: `Unauthorized`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -214,7 +281,7 @@ export class DefaultService {
|
||||||
'windowSizeDays': windowSizeDays,
|
'windowSizeDays': windowSizeDays,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -256,7 +323,7 @@ export class DefaultService {
|
||||||
'windowSizeDays': windowSizeDays,
|
'windowSizeDays': windowSizeDays,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -279,7 +346,7 @@ export class DefaultService {
|
||||||
'teamId': teamId,
|
'teamId': teamId,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -299,7 +366,7 @@ export class DefaultService {
|
||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
errors: {
|
errors: {
|
||||||
403: `Forbidden`,
|
403: `Forbidden`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -315,7 +382,7 @@ export class DefaultService {
|
||||||
errors: {
|
errors: {
|
||||||
403: `Forbidden`,
|
403: `Forbidden`,
|
||||||
404: `Not Found`,
|
404: `Not Found`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -337,7 +404,7 @@ export class DefaultService {
|
||||||
errors: {
|
errors: {
|
||||||
403: `Forbidden`,
|
403: `Forbidden`,
|
||||||
404: `Not Found`,
|
404: `Not Found`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -359,7 +426,7 @@ export class DefaultService {
|
||||||
errors: {
|
errors: {
|
||||||
403: `Forbidden`,
|
403: `Forbidden`,
|
||||||
404: `Not Found`,
|
404: `Not Found`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -383,7 +450,7 @@ export class DefaultService {
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
404: `Not Found`,
|
404: `Not Found`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -412,7 +479,7 @@ export class DefaultService {
|
||||||
errors: {
|
errors: {
|
||||||
403: `Forbidden`,
|
403: `Forbidden`,
|
||||||
404: `Not Found`,
|
404: `Not Found`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -432,12 +499,12 @@ export class DefaultService {
|
||||||
'team_id': teamId,
|
'team_id': teamId,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* update_integration <PUT>
|
* update_integrations <PUT>
|
||||||
* @param teamId
|
* @param teamId
|
||||||
* @param requestBody
|
* @param requestBody
|
||||||
* @returns TeamIntegrationSchema OK
|
* @returns TeamIntegrationSchema OK
|
||||||
|
@ -456,7 +523,7 @@ export class DefaultService {
|
||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
errors: {
|
errors: {
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -477,7 +544,7 @@ export class DefaultService {
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
404: `Not Found`,
|
404: `Not Found`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -498,7 +565,7 @@ export class DefaultService {
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
404: `Not Found`,
|
404: `Not Found`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -522,7 +589,7 @@ export class DefaultService {
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
404: `Not Found`,
|
404: `Not Found`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -551,7 +618,7 @@ export class DefaultService {
|
||||||
errors: {
|
errors: {
|
||||||
403: `Forbidden`,
|
403: `Forbidden`,
|
||||||
404: `Not Found`,
|
404: `Not Found`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -576,7 +643,7 @@ export class DefaultService {
|
||||||
errors: {
|
errors: {
|
||||||
403: `Forbidden`,
|
403: `Forbidden`,
|
||||||
404: `Not Found`,
|
404: `Not Found`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -598,7 +665,7 @@ export class DefaultService {
|
||||||
errors: {
|
errors: {
|
||||||
403: `Forbidden`,
|
403: `Forbidden`,
|
||||||
404: `Not Found`,
|
404: `Not Found`,
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -617,7 +684,7 @@ export class DefaultService {
|
||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
errors: {
|
errors: {
|
||||||
422: `Unprocessable Entity`,
|
422: `Unprocessable Content`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,19 +41,19 @@ const props = defineProps<{
|
||||||
<div>
|
<div>
|
||||||
<h3>{{ event.name }}</h3>
|
<h3>{{ event.name }}</h3>
|
||||||
<div>
|
<div>
|
||||||
<span v-if="event.description">{{ event.description }}</span>
|
<span>
|
||||||
<em v-else class="subtext">No description provided.</em>
|
<i class="bi bi-clock-fill margin" />
|
||||||
|
{{ formattedTime }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="subdetails">
|
<div class="subdetails">
|
||||||
<span>
|
<span v-if="event.description">{{ event.description }}</span>
|
||||||
<i class="bi bi-clock-fill margin" />
|
<em v-else class="subtext">No description provided.</em>
|
||||||
{{ formattedTime }}
|
<button class="class-info">
|
||||||
</span>
|
|
||||||
<span class="class-info">
|
|
||||||
<i class="tf2class tf2-PocketScout margin" />
|
<i class="tf2class tf2-PocketScout margin" />
|
||||||
Pocket Scout
|
Accept as Pocket Scout
|
||||||
</span>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -35,6 +35,11 @@ const router = createRouter({
|
||||||
name: "roster-builder",
|
name: "roster-builder",
|
||||||
component: RosterBuilderView
|
component: RosterBuilderView
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/schedule/roster/event/:eventId",
|
||||||
|
name: "roster-builder-event",
|
||||||
|
component: RosterBuilderView
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/team/register",
|
path: "/team/register",
|
||||||
name: "team-registration",
|
name: "team-registration",
|
||||||
|
|
|
@ -2,10 +2,16 @@ import { type Player, type PlayerTeamRoleFlat } from "@/player";
|
||||||
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 { type EventSchema, type CreateEventJson, type PlayerRoleSchema } from "@/client";
|
||||||
|
import { useTeamDetails } from "@/composables/team-details";
|
||||||
|
import moment from "moment";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
export const useRosterStore = defineStore("roster", () => {
|
export const useRosterStore = defineStore("roster", () => {
|
||||||
const clientStore = useClientStore();
|
const clientStore = useClientStore();
|
||||||
const client = clientStore.client;
|
const client = clientStore.client;
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const neededRoles: Reactive<Array<String>> = reactive([
|
const neededRoles: Reactive<Array<String>> = reactive([
|
||||||
"PocketScout",
|
"PocketScout",
|
||||||
|
@ -106,22 +112,94 @@ export const useRosterStore = defineStore("roster", () => {
|
||||||
fetchAvailablePlayers.name,
|
fetchAvailablePlayers.name,
|
||||||
() => client.default.viewAvailableAtTime(startTime.toString(), teamId),
|
() => client.default.viewAvailableAtTime(startTime.toString(), teamId),
|
||||||
(response) => {
|
(response) => {
|
||||||
availablePlayers.value = response.players.flatMap((schema) => {
|
availablePlayers.value = response.players
|
||||||
return schema.roles.map((role) => ({
|
.flatMap((schema) => {
|
||||||
steamId: schema.player.steamId,
|
return schema.roles.map((role) => ({
|
||||||
name: schema.player.username,
|
steamId: schema.player.steamId,
|
||||||
role: role.role,
|
name: schema.player.username,
|
||||||
isMain: role.isMain,
|
role: role.role,
|
||||||
availability: schema.availability,
|
isMain: role.isMain,
|
||||||
playtime: schema.playtime,
|
availability: schema.availability,
|
||||||
}));
|
playtime: schema.playtime,
|
||||||
});
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fetchPlayersFromEvent(eventId: number) {
|
||||||
|
return clientStore.call(
|
||||||
|
fetchPlayersFromEvent.name,
|
||||||
|
() => client.default.getEventPlayers(eventId),
|
||||||
|
(response) => {
|
||||||
|
availablePlayers.value = response.players
|
||||||
|
.flatMap((schema) => {
|
||||||
|
return schema.roles.map((role) => ({
|
||||||
|
steamId: schema.player.steamId,
|
||||||
|
name: schema.player.username,
|
||||||
|
role: role.role,
|
||||||
|
isMain: role.isMain,
|
||||||
|
availability: schema.hasConfirmed ? 2 : 1,
|
||||||
|
playtime: schema.playtime,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
response.players
|
||||||
|
.forEach((schema) => {
|
||||||
|
if (schema.role) {
|
||||||
|
selectedPlayers[schema.role.role] = {
|
||||||
|
steamId: schema.player.steamId,
|
||||||
|
name: schema.player.username,
|
||||||
|
role: schema.role.role,
|
||||||
|
isMain: schema.role.isMain,
|
||||||
|
availability: schema.hasConfirmed ? 2 : 1,
|
||||||
|
playtime: schema.playtime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentEvent = ref<EventSchema | undefined>(undefined);
|
||||||
|
|
||||||
|
const startTime = ref<number>();
|
||||||
|
|
||||||
|
function saveRoster(teamId: number) {
|
||||||
|
if (startTime.value == undefined) {
|
||||||
|
throw new Error("No start time set");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentEvent.value) {
|
||||||
|
const body: CreateEventJson = {
|
||||||
|
name: "Test",
|
||||||
|
description: "test description",
|
||||||
|
startTime: startTime.value.toString(),
|
||||||
|
playerRoles: Object.values(selectedPlayers).map((player) => ({
|
||||||
|
player: {
|
||||||
|
steamId: player.steamId,
|
||||||
|
username: player.name,
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
role: player.role,
|
||||||
|
isMain: player.isMain,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
clientStore.client.default.createEvent(teamId, body)
|
||||||
|
.then(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// TODO: update event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
neededRoles,
|
neededRoles,
|
||||||
selectedPlayers,
|
selectedPlayers,
|
||||||
|
@ -136,5 +214,8 @@ export const useRosterStore = defineStore("roster", () => {
|
||||||
mainRoles,
|
mainRoles,
|
||||||
alternateRoles,
|
alternateRoles,
|
||||||
fetchAvailablePlayers,
|
fetchAvailablePlayers,
|
||||||
|
fetchPlayersFromEvent,
|
||||||
|
startTime,
|
||||||
|
saveRoster,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,8 +6,10 @@ 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";
|
||||||
|
|
||||||
const rosterStore = useRosterStore();
|
const rosterStore = useRosterStore();
|
||||||
|
const eventsStore = useEventsStore();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
|
@ -19,8 +21,21 @@ const hasAlternates = computed(() => {
|
||||||
return rosterStore.alternateRoles.length > 0;
|
return rosterStore.alternateRoles.length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
const eventId = computed<number | undefined>(() => Number(route.params.eventId));
|
||||||
rosterStore.fetchAvailablePlayers(route.params.startTime, route.params.teamId);
|
|
||||||
|
function saveRoster() {
|
||||||
|
rosterStore.saveRoster(Number(route.params.teamId));
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (eventId.value) {
|
||||||
|
const event = await eventsStore.fetchEvent(eventId.value);
|
||||||
|
rosterStore.startTime = moment(event.startTime).unix();
|
||||||
|
rosterStore.fetchPlayersFromEvent(eventId.value);
|
||||||
|
} else {
|
||||||
|
rosterStore.startTime = Number(route.params.startTime);
|
||||||
|
rosterStore.fetchAvailablePlayers(rosterStore.startTime, Number(route.params.teamId));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -29,14 +44,14 @@ onMounted(() => {
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<h1 class="roster-title">
|
<h1 class="roster-title">
|
||||||
Roster for Snus Brotherhood
|
Roster for Snus Brotherhood
|
||||||
<em class="aside date">
|
<em class="aside date" v-if="rosterStore.startTime">
|
||||||
@
|
@
|
||||||
{{ moment.unix(route.params.startTime).format("L LT") }}
|
{{ moment.unix(rosterStore.startTime).format("L LT") }}
|
||||||
</em>
|
</em>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button>Cancel</button>
|
<button>Cancel</button>
|
||||||
<button class="accent">Save Roster</button>
|
<button class="accent" @click="saveRoster">Save Roster</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
|
|
|
@ -23,4 +23,4 @@ def connect_db_with_app():
|
||||||
metadata = MetaData(naming_convention=convention)
|
metadata = MetaData(naming_convention=convention)
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
db = SQLAlchemy(model_class=BaseModel, metadata=metadata)
|
db = SQLAlchemy(model_class=BaseModel, metadata=metadata)
|
||||||
migrate = Migrate(render_as_batch=True)
|
migrate = Migrate(app, db, render_as_batch=True)
|
||||||
|
|
|
@ -8,12 +8,16 @@
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from flask import Blueprint, abort
|
from flask import Blueprint, abort, make_response
|
||||||
from spectree import Response
|
from spectree import Response
|
||||||
from models.player_event import PlayerEvent
|
from sqlalchemy.sql import tuple_
|
||||||
from models.player import Player
|
from models.player import Player
|
||||||
|
from models.player_event import PlayerEvent, PlayerEventRolesSchema
|
||||||
|
from models.player_team_availability import PlayerTeamAvailability
|
||||||
|
from models.player_team_role import PlayerRoleSchema, PlayerTeamRole
|
||||||
|
from models.team import Team
|
||||||
from spec import BaseModel, spec
|
from spec import BaseModel, spec
|
||||||
from middleware import assert_team_authority, requires_authentication, requires_team_membership
|
from middleware import assert_team_authority, assert_team_membership, requires_authentication, requires_team_membership
|
||||||
from models.event import Event, EventSchema
|
from models.event import Event, EventSchema
|
||||||
from models.player_team import PlayerTeam
|
from models.player_team import PlayerTeam
|
||||||
from app_db import db
|
from app_db import db
|
||||||
|
@ -65,17 +69,18 @@ class CreateEventJson(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
start_time: datetime
|
start_time: datetime
|
||||||
player_ids: list[int]
|
player_roles: list[PlayerRoleSchema]
|
||||||
|
|
||||||
@api_events.post("/team/id/<int:team_id>")
|
@api_events.post("/team/id/<int:team_id>")
|
||||||
@spec.validate(
|
@spec.validate(
|
||||||
resp=Response(
|
resp=Response(
|
||||||
HTTP_200=EventSchema,
|
HTTP_200=EventSchema,
|
||||||
)
|
),
|
||||||
|
operation_id="create_event",
|
||||||
)
|
)
|
||||||
@requires_authentication
|
@requires_authentication
|
||||||
@requires_team_membership()
|
@requires_team_membership()
|
||||||
def create_event(player_team: PlayerTeam, json: CreateEventJson, **_):
|
def create_event(player_team: PlayerTeam, team_id: int, json: CreateEventJson, **_):
|
||||||
event = Event()
|
event = Event()
|
||||||
event.team_id = player_team.team_id
|
event.team_id = player_team.team_id
|
||||||
event.name = json.name
|
event.name = json.name
|
||||||
|
@ -86,27 +91,149 @@ def create_event(player_team: PlayerTeam, json: CreateEventJson, **_):
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
db.session.refresh(event)
|
db.session.refresh(event)
|
||||||
|
|
||||||
players_teams = db.session.query(
|
tuples = map(lambda x: (x.player.steam_id, x.role.role), json.player_roles)
|
||||||
PlayerTeam
|
|
||||||
|
results = db.session.query(
|
||||||
|
PlayerTeam, PlayerTeamRole.id, PlayerTeamAvailability.availability
|
||||||
).join(
|
).join(
|
||||||
Player
|
PlayerTeamRole
|
||||||
|
).outerjoin(
|
||||||
|
PlayerTeamAvailability,
|
||||||
|
(PlayerTeamAvailability.player_team_id == PlayerTeam.id) &
|
||||||
|
(PlayerTeamAvailability.start_time <= event.start_time) &
|
||||||
|
(PlayerTeamAvailability.end_time > event.start_time)
|
||||||
).where(
|
).where(
|
||||||
PlayerTeam.team_id == player_team.team_id
|
PlayerTeam.team_id == team_id
|
||||||
).where(
|
).where(
|
||||||
PlayerTeam.player_id.in_(json.player_ids)
|
# (player_id, role) in (...)
|
||||||
|
tuple_(PlayerTeam.player_id, PlayerTeamRole.role).in_(tuples)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
for player_team in players_teams:
|
for player_team, role_id, availability in map(lambda x: x.tuple(), results):
|
||||||
player = player_team.player
|
|
||||||
player_event = PlayerEvent()
|
player_event = PlayerEvent()
|
||||||
player_event.player_id = player.steam_id
|
player_event.player_id = player_team.player_id
|
||||||
player_event.event_id = event.id
|
player_event.event_id = event.id
|
||||||
|
player_event.player_team_role_id = role_id
|
||||||
|
|
||||||
|
# autoconfirm if availability = 2
|
||||||
|
player_event.has_confirmed = (availability == 2)
|
||||||
|
|
||||||
db.session.add(player_event)
|
db.session.add(player_event)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
event.update_discord_message()
|
||||||
|
|
||||||
return EventSchema.from_model(event).dict(by_alias=True), 200
|
return EventSchema.from_model(event).dict(by_alias=True), 200
|
||||||
|
|
||||||
|
@api_events.put("/<int:event_id>/attendance")
|
||||||
|
@spec.validate(
|
||||||
|
resp=Response(
|
||||||
|
HTTP_204=None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@requires_authentication
|
||||||
|
@requires_team_membership()
|
||||||
|
def attend_event(player_team: PlayerTeam, event_id: int, **_):
|
||||||
|
player_event = db.session.query(
|
||||||
|
PlayerEvent
|
||||||
|
).where(
|
||||||
|
PlayerEvent.event_id == event_id
|
||||||
|
).where(
|
||||||
|
PlayerEvent.player_id == player_team.player_id
|
||||||
|
).join(
|
||||||
|
Event
|
||||||
|
).where(
|
||||||
|
Event.team_id == player_team.team_id
|
||||||
|
).one_or_none()
|
||||||
|
|
||||||
|
if not player_event:
|
||||||
|
player_event = PlayerEvent()
|
||||||
|
player_event.event_id = event_id
|
||||||
|
player_event.player_id = player_team.player_id
|
||||||
|
db.session.add(player_event)
|
||||||
|
|
||||||
|
player_event.has_confirmed = True
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
player_event.event.update_discord_message()
|
||||||
|
|
||||||
|
return make_response({ }, 204)
|
||||||
|
|
||||||
|
@api_events.delete("/<int:event_id>/attendance")
|
||||||
|
@spec.validate(
|
||||||
|
resp=Response(
|
||||||
|
HTTP_204=None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@requires_authentication
|
||||||
|
@requires_team_membership()
|
||||||
|
def unattend_event(player_team: PlayerTeam, event_id: int, **_):
|
||||||
|
result = db.session.query(
|
||||||
|
PlayerEvent, Event
|
||||||
|
).where(
|
||||||
|
PlayerEvent.event_id == event_id
|
||||||
|
).where(
|
||||||
|
PlayerEvent.player_id == player_team.player_id
|
||||||
|
).join(
|
||||||
|
Event
|
||||||
|
).where(
|
||||||
|
Event.team_id == player_team.team_id
|
||||||
|
).one_or_none()
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
player_event, event = result.tuple()
|
||||||
|
|
||||||
|
db.session.delete(player_event)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
event.update_discord_message()
|
||||||
|
|
||||||
|
return make_response({ }, 204)
|
||||||
|
|
||||||
|
class GetEventPlayersResponse(BaseModel):
|
||||||
|
players: list[PlayerEventRolesSchema]
|
||||||
|
|
||||||
|
@api_events.get("/<int:event_id>/players")
|
||||||
|
@spec.validate(
|
||||||
|
resp=Response(
|
||||||
|
HTTP_200=GetEventPlayersResponse,
|
||||||
|
),
|
||||||
|
operation_id="get_event_players",
|
||||||
|
)
|
||||||
|
@requires_authentication
|
||||||
|
def get_event_players(player: Player, event_id: int, **_):
|
||||||
|
event = db.session.query(Event).where(Event.id == event_id).one_or_none()
|
||||||
|
if not event:
|
||||||
|
abort(404)
|
||||||
|
assert_team_membership(player, event.team)
|
||||||
|
|
||||||
|
players_events = db.session.query(
|
||||||
|
PlayerEvent
|
||||||
|
).join(
|
||||||
|
Event,
|
||||||
|
Event.id == PlayerEvent.event_id
|
||||||
|
).join(
|
||||||
|
PlayerTeam,
|
||||||
|
PlayerTeam.team_id == Event.team_id & PlayerEvent.player_id == PlayerTeam.player_id
|
||||||
|
).where(
|
||||||
|
PlayerEvent.event_id == event_id
|
||||||
|
).all()
|
||||||
|
|
||||||
|
player_event_roles = [
|
||||||
|
PlayerEventRolesSchema.from_event_player_team(
|
||||||
|
player_event, player_event.player_team
|
||||||
|
)
|
||||||
|
for player_event in players_events
|
||||||
|
]
|
||||||
|
|
||||||
|
return GetEventPlayersResponse(
|
||||||
|
players=player_event_roles
|
||||||
|
).dict(by_alias=True), 200
|
||||||
|
|
||||||
@api_events.patch("/<int:event_id>/players")
|
@api_events.patch("/<int:event_id>/players")
|
||||||
@requires_authentication
|
@requires_authentication
|
||||||
@requires_team_membership()
|
@requires_team_membership()
|
||||||
|
|
|
@ -6,6 +6,7 @@ from app_db import db
|
||||||
from models.auth_session import AuthSession
|
from models.auth_session import AuthSession
|
||||||
from models.player import Player
|
from models.player import Player
|
||||||
from models.player_team import PlayerTeam
|
from models.player_team import PlayerTeam
|
||||||
|
from models.team import Team
|
||||||
|
|
||||||
|
|
||||||
def requires_authentication(f):
|
def requires_authentication(f):
|
||||||
|
@ -72,6 +73,20 @@ def requires_team_membership(
|
||||||
return decorator
|
return decorator
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
def assert_team_membership(player: Player, team: Team):
|
||||||
|
player_team = db.session.query(
|
||||||
|
PlayerTeam
|
||||||
|
).where(
|
||||||
|
PlayerTeam.player == player
|
||||||
|
).where(
|
||||||
|
PlayerTeam.team == team
|
||||||
|
).one_or_none()
|
||||||
|
|
||||||
|
if not player_team:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
return player_team
|
||||||
|
|
||||||
def assert_team_authority(
|
def assert_team_authority(
|
||||||
player_team: PlayerTeam,
|
player_team: PlayerTeam,
|
||||||
target_player_team: PlayerTeam | None = None,
|
target_player_team: PlayerTeam | None = None,
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
"""Add event.discord_message_id
|
||||||
|
|
||||||
|
Revision ID: 286ee26b9e5d
|
||||||
|
Revises: 392454b91293
|
||||||
|
Create Date: 2024-11-25 21:00:08.444434
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '286ee26b9e5d'
|
||||||
|
down_revision = '392454b91293'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('events', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('discord_message_id', sa.BigInteger(), nullable=True))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('events', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('discord_message_id')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
|
@ -3,9 +3,10 @@ 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
|
||||||
from sqlalchemy.schema import UniqueConstraint
|
from sqlalchemy.schema import UniqueConstraint
|
||||||
from sqlalchemy.types import TIMESTAMP, Integer, String, Text
|
from sqlalchemy.types import TIMESTAMP, BigInteger, Integer, String, Text
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
from sqlalchemy_utc import UtcDateTime
|
from sqlalchemy_utc import UtcDateTime
|
||||||
|
from discord_webhook import DiscordWebhook
|
||||||
import app_db
|
import app_db
|
||||||
import spec
|
import spec
|
||||||
|
|
||||||
|
@ -23,14 +24,81 @@ class Event(app_db.BaseModel):
|
||||||
|
|
||||||
description: Mapped[str] = mapped_column(Text, nullable=True)
|
description: Mapped[str] = mapped_column(Text, nullable=True)
|
||||||
created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now())
|
created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now())
|
||||||
|
discord_message_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
|
||||||
|
|
||||||
team: Mapped["Team"] = relationship("Team", back_populates="events")
|
team: Mapped["Team"] = relationship("Team", back_populates="events")
|
||||||
players: Mapped["PlayerEvent"] = relationship("PlayerEvent", back_populates="event")
|
players: Mapped[list["PlayerEvent"]] = relationship("PlayerEvent", back_populates="event")
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint("team_id", "name", "start_time"),
|
UniqueConstraint("team_id", "name", "start_time"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_discord_content(self):
|
||||||
|
start_timestamp = int(self.start_time.timestamp())
|
||||||
|
players = list(self.players)
|
||||||
|
# players with a role should be sorted first
|
||||||
|
players.sort(key=lambda p: p.role is not None, reverse=True)
|
||||||
|
players_info = []
|
||||||
|
|
||||||
|
for player in players:
|
||||||
|
player_info = "- "
|
||||||
|
|
||||||
|
if player.role:
|
||||||
|
player_info += f"**{player.role.role.name}:** "
|
||||||
|
|
||||||
|
player_info += f"{player.player.username}"
|
||||||
|
|
||||||
|
if player.has_confirmed:
|
||||||
|
player_info += " ✅"
|
||||||
|
else:
|
||||||
|
player_info += " ⏳"
|
||||||
|
|
||||||
|
players_info.append(player_info)
|
||||||
|
|
||||||
|
return "\n".join([
|
||||||
|
f"# {self.name}",
|
||||||
|
"",
|
||||||
|
self.description or "*No description.*",
|
||||||
|
"",
|
||||||
|
f"<t:{start_timestamp}:f>",
|
||||||
|
"\n".join(players_info),
|
||||||
|
"",
|
||||||
|
"[Confirm availability here]" +
|
||||||
|
f"(https://availabili.tf/team/id/{self.team.id}/events/{self.id})",
|
||||||
|
])
|
||||||
|
|
||||||
|
def get_or_create_webhook(self):
|
||||||
|
integration = app_db.db.session.query(
|
||||||
|
TeamDiscordIntegration
|
||||||
|
).where(
|
||||||
|
TeamDiscordIntegration.team_id == self.team_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not integration:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.discord_message_id:
|
||||||
|
return DiscordWebhook(
|
||||||
|
integration.webhook_url,
|
||||||
|
id=str(self.discord_message_id),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return DiscordWebhook(integration.webhook_url)
|
||||||
|
|
||||||
|
def update_discord_message(self):
|
||||||
|
webhook = self.get_or_create_webhook()
|
||||||
|
if webhook:
|
||||||
|
webhook.content = self.get_discord_content()
|
||||||
|
if webhook.id:
|
||||||
|
webhook.edit()
|
||||||
|
else:
|
||||||
|
webhook.execute()
|
||||||
|
if webhook_id := webhook.id:
|
||||||
|
self.discord_message_id = int(webhook_id)
|
||||||
|
app_db.db.session.commit()
|
||||||
|
else:
|
||||||
|
raise Exception("Failed to create webhook")
|
||||||
|
|
||||||
class EventSchema(spec.BaseModel):
|
class EventSchema(spec.BaseModel):
|
||||||
id: int
|
id: int
|
||||||
team_id: int
|
team_id: int
|
||||||
|
@ -50,6 +118,17 @@ class EventSchema(spec.BaseModel):
|
||||||
created_at=model.created_at,
|
created_at=model.created_at,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class EventPlayersSchema(spec.BaseModel):
|
||||||
|
players: list["PlayerEventRolesSchema"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_model(cls, model: Event) -> "EventPlayersSchema":
|
||||||
|
return cls(
|
||||||
|
players=[PlayerEventRolesSchema.from_model(player) for player in model.players],
|
||||||
|
roles=[RoleSchema.from_model(player.role.role) for player in model.players if player.role],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
from models.team import Team
|
from models.team import Team
|
||||||
from models.player_event import PlayerEvent
|
from models.player_event import PlayerEvent
|
||||||
|
from models.team_integration import TeamDiscordIntegration
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
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
|
||||||
from sqlalchemy.types import Boolean
|
from sqlalchemy.types import Boolean
|
||||||
import app_db
|
import app_db
|
||||||
|
import spec
|
||||||
|
|
||||||
|
|
||||||
class PlayerEvent(app_db.BaseModel):
|
class PlayerEvent(app_db.BaseModel):
|
||||||
|
@ -15,9 +17,34 @@ class PlayerEvent(app_db.BaseModel):
|
||||||
|
|
||||||
event: Mapped["Event"] = relationship("Event", back_populates="players")
|
event: Mapped["Event"] = relationship("Event", back_populates="players")
|
||||||
player: Mapped["Player"] = relationship("Player", back_populates="events")
|
player: Mapped["Player"] = relationship("Player", back_populates="events")
|
||||||
|
player_team: Mapped["PlayerTeam"] = relationship(
|
||||||
|
"PlayerTeam",
|
||||||
|
secondary="players",
|
||||||
|
primaryjoin="PlayerEvent.player_id == Player.steam_id",
|
||||||
|
secondaryjoin="PlayerTeam.player_id == Player.steam_id",
|
||||||
|
viewonly=True,
|
||||||
|
)
|
||||||
role: Mapped["PlayerTeamRole"] = relationship("PlayerTeamRole")
|
role: Mapped["PlayerTeamRole"] = relationship("PlayerTeamRole")
|
||||||
|
|
||||||
|
class PlayerEventRolesSchema(spec.BaseModel):
|
||||||
|
player: "PlayerSchema"
|
||||||
|
role: Optional["RoleSchema"]
|
||||||
|
roles: list["RoleSchema"]
|
||||||
|
has_confirmed: bool
|
||||||
|
playtime: int
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_event_player_team(cls, player_event: "PlayerEvent", player_team: "PlayerTeam"):
|
||||||
|
return cls(
|
||||||
|
player=PlayerSchema.from_model(player_event.player),
|
||||||
|
role=RoleSchema.from_model(player_event.role) if player_event.role else None,
|
||||||
|
roles=[RoleSchema.from_model(role) for role in player_team.player_roles],
|
||||||
|
has_confirmed=player_event.has_confirmed,
|
||||||
|
playtime=int(player_team.playtime.total_seconds()),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
from models.event import Event
|
from models.event import Event
|
||||||
from models.player import Player
|
from models.player import Player, PlayerSchema
|
||||||
from models.player_team_role import PlayerTeamRole
|
from models.player_team_role import PlayerTeamRole, RoleSchema
|
||||||
|
from models.player_team import PlayerTeam
|
||||||
|
|
|
@ -53,7 +53,6 @@ class PlayerTeamRole(app_db.BaseModel):
|
||||||
UniqueConstraint("player_team_id", "role"),
|
UniqueConstraint("player_team_id", "role"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RoleSchema(spec.BaseModel):
|
class RoleSchema(spec.BaseModel):
|
||||||
role: str
|
role: str
|
||||||
is_main: bool
|
is_main: bool
|
||||||
|
@ -62,5 +61,10 @@ class RoleSchema(spec.BaseModel):
|
||||||
def from_model(cls, role: PlayerTeamRole):
|
def from_model(cls, role: PlayerTeamRole):
|
||||||
return cls(role=role.role.name, is_main=role.is_main)
|
return cls(role=role.role.name, is_main=role.is_main)
|
||||||
|
|
||||||
|
class PlayerRoleSchema(spec.BaseModel):
|
||||||
|
player: "PlayerSchema"
|
||||||
|
role: RoleSchema
|
||||||
|
|
||||||
|
|
||||||
from models.player_team import PlayerTeam
|
from models.player_team import PlayerTeam
|
||||||
|
from models.player import PlayerSchema
|
||||||
|
|
|
@ -20,3 +20,5 @@ Flask-Migrate
|
||||||
requests
|
requests
|
||||||
|
|
||||||
pytz # timezone handling
|
pytz # timezone handling
|
||||||
|
|
||||||
|
discord-webhook # for sending messages to Discord webhooks
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import datetime
|
import datetime
|
||||||
from typing import cast
|
from typing import Optional, cast
|
||||||
from flask import Blueprint, abort, jsonify, make_response, request
|
from flask import Blueprint, abort, jsonify, make_response, request
|
||||||
from spectree import Response
|
from spectree import Response
|
||||||
|
from sqlalchemy import Row
|
||||||
from sqlalchemy.orm import contains_eager, joinedload
|
from sqlalchemy.orm import contains_eager, joinedload
|
||||||
from sqlalchemy.sql import and_, select
|
from sqlalchemy.sql import and_, select
|
||||||
from app_db import db
|
from app_db import db
|
||||||
from models.player import Player, PlayerSchema
|
from models.player import Player, PlayerSchema
|
||||||
|
from models.player_event import PlayerEvent, PlayerEventRolesSchema
|
||||||
from models.player_team import PlayerTeam
|
from models.player_team import PlayerTeam
|
||||||
from models.player_team_availability import AvailabilitySchema, PlayerTeamAvailability, PlayerTeamAvailabilityRoleSchema
|
from models.player_team_availability import AvailabilitySchema, PlayerTeamAvailability, PlayerTeamAvailabilityRoleSchema
|
||||||
from models.player_team_role import PlayerTeamRole, RoleSchema
|
from models.player_team_role import PlayerTeamRole, RoleSchema
|
||||||
|
|
|
@ -397,22 +397,47 @@ def edit_member_roles(
|
||||||
if not target_player:
|
if not target_player:
|
||||||
abort(401)
|
abort(401)
|
||||||
|
|
||||||
# TODO: change this to a MERGE statement
|
|
||||||
|
"""
|
||||||
|
MERGE INTO players_teams_roles AS target
|
||||||
|
USING (
|
||||||
|
VALUES
|
||||||
|
('PocketScout', 1),
|
||||||
|
('PocketScout', 0),
|
||||||
|
) AS source(role, is_main)
|
||||||
|
ON (target.player_team_id = :player_team_id AND target.role = source.role)
|
||||||
|
WHEN MATCHED THEN
|
||||||
|
UPDATE SET
|
||||||
|
target.role = source.role,
|
||||||
|
target.is_main = source.is_main
|
||||||
|
WHEN NOT MATCHED BY TARGET THEN
|
||||||
|
INSERT (player_team_id, role, is_main)
|
||||||
|
VALUES (:player_team_id, source.role, source.is_main)
|
||||||
|
WHEN NOT MATCHED BY SOURCE THEN
|
||||||
|
DELETE;
|
||||||
|
"""
|
||||||
|
|
||||||
for role in target_player.player_roles:
|
for role in target_player.player_roles:
|
||||||
# delete role if not found in json
|
# delete role if not found in json
|
||||||
f = filter(lambda x: x.role == role.role.name, json.roles)
|
f = filter(lambda x: x.role == role.role.name, json.roles)
|
||||||
matched_role = next(f, None)
|
matched_role = next(f, None)
|
||||||
|
|
||||||
if not matched_role:
|
if matched_role:
|
||||||
|
# update
|
||||||
|
role.is_main = matched_role.is_main
|
||||||
|
else:
|
||||||
db.session.delete(role)
|
db.session.delete(role)
|
||||||
|
|
||||||
for schema in json.roles:
|
for schema in json.roles:
|
||||||
role = PlayerTeamRole()
|
# insert if not found in target
|
||||||
role.player_team = target_player
|
f = filter(lambda x: x.role.name == schema.role, target_player.player_roles)
|
||||||
role.role = PlayerTeamRole.Role[schema.role]
|
|
||||||
role.is_main = schema.is_main
|
if not next(f, None):
|
||||||
db.session.merge(role)
|
role = PlayerTeamRole()
|
||||||
|
role.player_team_id = target_player.id
|
||||||
|
role.role = PlayerTeamRole.Role[schema.role]
|
||||||
|
role.is_main = schema.is_main
|
||||||
|
db.session.add(role)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue