Add updating events

master
John Montagu, the 4th Earl of Sandvich 2024-11-30 13:08:42 -08:00
parent 6f49053dac
commit 7043877a9f
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
11 changed files with 164 additions and 70 deletions

View File

@ -33,6 +33,7 @@ export type { TeamInviteSchemaList } from './models/TeamInviteSchemaList';
export type { TeamLogsTfIntegrationSchema } from './models/TeamLogsTfIntegrationSchema'; export type { TeamLogsTfIntegrationSchema } from './models/TeamLogsTfIntegrationSchema';
export { TeamRole } from './models/TeamRole'; export { TeamRole } from './models/TeamRole';
export type { TeamSchema } from './models/TeamSchema'; export type { TeamSchema } from './models/TeamSchema';
export type { UpdateEventJson } from './models/UpdateEventJson';
export type { ValidationError } from './models/ValidationError'; export type { ValidationError } from './models/ValidationError';
export type { ValidationErrorElement } from './models/ValidationErrorElement'; export type { ValidationErrorElement } from './models/ValidationErrorElement';
export type { ViewAvailablePlayersQuery } from './models/ViewAvailablePlayersQuery'; export type { ViewAvailablePlayersQuery } from './models/ViewAvailablePlayersQuery';

View File

@ -4,7 +4,7 @@
/* eslint-disable */ /* eslint-disable */
import type { PlayerRoleSchema } from './PlayerRoleSchema'; import type { PlayerRoleSchema } from './PlayerRoleSchema';
export type CreateEventJson = { export type CreateEventJson = {
description: string; description: (string | null);
name: string; name: string;
playerRoles: Array<PlayerRoleSchema>; playerRoles: Array<PlayerRoleSchema>;
startTime: string; startTime: string;

View File

@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { PlayerRoleSchema } from './PlayerRoleSchema';
export type UpdateEventJson = {
description: (string | null);
name: string;
playerRoles: Array<PlayerRoleSchema>;
};

View File

@ -16,6 +16,7 @@ import type { SetUsernameJson } from '../models/SetUsernameJson';
import type { TeamIntegrationSchema } from '../models/TeamIntegrationSchema'; import type { TeamIntegrationSchema } from '../models/TeamIntegrationSchema';
import type { TeamInviteSchema } from '../models/TeamInviteSchema'; import type { TeamInviteSchema } from '../models/TeamInviteSchema';
import type { TeamInviteSchemaList } from '../models/TeamInviteSchemaList'; import type { TeamInviteSchemaList } from '../models/TeamInviteSchemaList';
import type { UpdateEventJson } from '../models/UpdateEventJson';
import type { ViewAvailablePlayersResponse } from '../models/ViewAvailablePlayersResponse'; import type { ViewAvailablePlayersResponse } from '../models/ViewAvailablePlayersResponse';
import type { ViewScheduleResponse } from '../models/ViewScheduleResponse'; import type { ViewScheduleResponse } from '../models/ViewScheduleResponse';
import type { ViewTeamMembersResponseList } from '../models/ViewTeamMembersResponseList'; import type { ViewTeamMembersResponseList } from '../models/ViewTeamMembersResponseList';
@ -129,6 +130,30 @@ export class DefaultService {
}, },
}); });
} }
/**
* update_event <PATCH>
* @param eventId
* @param requestBody
* @returns EventSchema OK
* @throws ApiError
*/
public updateEvent(
eventId: number,
requestBody?: UpdateEventJson,
): CancelablePromise<EventSchema> {
return this.httpRequest.request({
method: 'PATCH',
url: '/api/events/{event_id}',
path: {
'event_id': eventId,
},
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Unprocessable Content`,
},
});
}
/** /**
* unattend_event <DELETE> * unattend_event <DELETE>
* @param eventId * @param eventId
@ -189,23 +214,6 @@ export class DefaultService {
}, },
}); });
} }
/**
* set_event_players <PATCH>
* @param eventId
* @returns void
* @throws ApiError
*/
public patchApiEventsEventIdPlayers(
eventId: number,
): CancelablePromise<void> {
return this.httpRequest.request({
method: 'PATCH',
url: '/api/events/{event_id}/players',
path: {
'event_id': eventId,
},
});
}
/** /**
* logout <DELETE> * logout <DELETE>
* @returns void * @returns void

View File

@ -5,6 +5,7 @@ import { useTeamsStore } from "@/stores/teams";
import { useTeamsEventsStore } from "@/stores/teams/events"; import { useTeamsEventsStore } from "@/stores/teams/events";
import moment from "moment"; import moment from "moment";
import { computed } from "vue"; import { computed } from "vue";
import { RouterLink } from "vue-router";
const teamsStore = useTeamsStore(); const teamsStore = useTeamsStore();
const rosterStore = useRosterStore(); const rosterStore = useRosterStore();
@ -72,6 +73,15 @@ function attendOrUnattend() {
<em v-else class="subtext">No description provided.</em> <em v-else class="subtext">No description provided.</em>
</div> </div>
<div class="button-group"> <div class="button-group">
<RouterLink class="button" :to="{
name: 'roster-builder-event',
params: { eventId: event.event.id }
}">
<button>
<i class="bi bi-pencil" />
Edit
</button>
</RouterLink>
<button <button
@click="attendOrUnattend()" @click="attendOrUnattend()"
v-if="event.playerEvent" v-if="event.playerEvent"

View File

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useEventForm } from "@/composables/event-form";
import { useRosterStore } from "@/stores/roster"; import { useRosterStore } from "@/stores/roster";
import moment from "moment"; import moment from "moment";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
@ -8,16 +9,22 @@ const router = useRouter();
const rosterStore = useRosterStore(); const rosterStore = useRosterStore();
const { eventId } = useEventForm();
function saveRoster() { function saveRoster() {
rosterStore.saveRoster(Number(route.params.teamId)) if (eventId.value) {
.then(() => { rosterStore.updateRoster(eventId.value);
router.push({ } else {
name: "team-details", rosterStore.saveRoster(Number(route.params.teamId))
params: { .then(() => {
id: route.params.teamId router.push({
} name: "team-details",
params: {
id: route.params.teamId
}
});
}); });
}); }
} }
</script> </script>

View File

@ -1,14 +1,20 @@
import type { PlayerRoleSchema } from "@/client"; import type { PlayerRoleSchema } from "@/client";
import { ref } from "vue"; import { computed, ref } from "vue";
import { useRoute } from "vue-router";
export function useEventForm() { export function useEventForm() {
const route = useRoute();
const title = ref(""); const title = ref("");
const description = ref(""); const description = ref<string | null>("");
const players = ref<PlayerRoleSchema[]>([]); const players = ref<PlayerRoleSchema[]>([]);
const eventId = computed<number | undefined>(() => Number(route.params.eventId));
return { return {
title, title,
description, description,
players, players,
eventId,
} }
} }

View File

@ -2,7 +2,7 @@ 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 { type EventSchema, type CreateEventJson, type PlayerRoleSchema, type UpdateEventJson } from "@/client";
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";
@ -14,6 +14,8 @@ export const useRosterStore = defineStore("roster", () => {
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
// TODO: move roster state to a composable
const neededRoles: Reactive<Array<String>> = reactive([ const neededRoles: Reactive<Array<String>> = reactive([
"PocketScout", "PocketScout",
"FlankScout", "FlankScout",
@ -78,12 +80,12 @@ export const useRosterStore = defineStore("roster", () => {
}); });
const roleNames = reactive<{ [key: string]: string }>({ const roleNames = reactive<{ [key: string]: string }>({
"Scout": "Scout (HL)", "Scout": "Scout",
"PocketScout": "Pocket Scout (6s)", "PocketScout": "Pocket Scout",
"FlankScout": "Flank Scout (6s)", "FlankScout": "Flank Scout",
"Soldier": "Soldier (HL)", "Soldier": "Soldier",
"PocketSoldier": "Pocket Soldier (6s)", "PocketSoldier": "Pocket Soldier",
"Roamer": "Roamer (6s)", "Roamer": "Roamer",
"Pyro": "Pyro", "Pyro": "Pyro",
"Demoman": "Demoman", "Demoman": "Demoman",
"HeavyWeapons": "Heavy", "HeavyWeapons": "Heavy",
@ -177,28 +179,42 @@ export const useRosterStore = defineStore("roster", () => {
throw new Error("No start time set"); throw new Error("No start time set");
} }
if (!currentEvent.value) { const body: CreateEventJson = {
const body: CreateEventJson = { name: title.value,
name: title.value, description: description.value,
description: description.value, startTime: startTime.value.toString(),
startTime: startTime.value.toString(), playerRoles: Object.values(selectedPlayers).map((player) => ({
playerRoles: Object.values(selectedPlayers).map((player) => ({ player: {
player: { steamId: player.steamId,
steamId: player.steamId, username: player.name,
username: player.name, },
}, role: {
role: { role: player.role,
role: player.role, isMain: player.isMain,
isMain: player.isMain, },
}, })),
})), };
};
return clientStore.client.default.createEvent(teamId, body); return client.default.createEvent(teamId, body);
} else { }
// TODO: update event
throw "Not implemented"; function updateRoster(eventId: number) {
} const body: UpdateEventJson = {
name: title.value,
description: description.value,
playerRoles: Object.values(selectedPlayers).map((player) => ({
player: {
steamId: player.steamId,
username: player.name,
},
role: {
role: player.role,
isMain: player.isMain,
},
})),
};
return client.default.updateEvent(eventId, body);
} }
return { return {
@ -218,6 +234,7 @@ export const useRosterStore = defineStore("roster", () => {
fetchPlayersFromEvent, fetchPlayersFromEvent,
startTime, startTime,
saveRoster, saveRoster,
updateRoster,
title, title,
description, description,
} }

View File

@ -6,6 +6,7 @@ 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"; import EventSchedulerForm from "@/components/EventSchedulerForm.vue";
import { useEventForm } from "@/composables/event-form";
const rosterStore = useRosterStore(); const rosterStore = useRosterStore();
const eventsStore = useEventsStore(); const eventsStore = useEventsStore();
@ -20,12 +21,19 @@ const hasAlternates = computed(() => {
return rosterStore.alternateRoles.length > 0; return rosterStore.alternateRoles.length > 0;
}); });
const eventId = computed<number | undefined>(() => Number(route.params.eventId)); const { eventId } = useEventForm();
function closeSelection() {
rosterStore.selectedRole = undefined;
}
onMounted(async () => { onMounted(async () => {
if (eventId.value) { if (eventId.value) {
const event = await eventsStore.fetchEvent(eventId.value); const event = await eventsStore.fetchEvent(eventId.value);
rosterStore.startTime = moment(event.startTime).unix(); rosterStore.startTime = moment(event.startTime).unix();
rosterStore.title = event.name;
rosterStore.description = event.description;
Object.assign(rosterStore.selectedPlayers, { });
rosterStore.fetchPlayersFromEvent(eventId.value); rosterStore.fetchPlayersFromEvent(eventId.value);
} else { } else {
rosterStore.startTime = Number(route.params.startTime); rosterStore.startTime = Number(route.params.startTime);
@ -64,7 +72,7 @@ onMounted(async () => {
is-ringer is-ringer
:role-title="rosterStore.selectedRole" /> :role-title="rosterStore.selectedRole" />
<div class="action-buttons"> <div class="action-buttons">
<button class="accent"> <button class="accent" @click="closeSelection">
<i class="bi bi-check" /> <i class="bi bi-check" />
Done Done
</button> </button>

View File

@ -76,10 +76,15 @@ def get_user_events(user_id: int):
class CreateEventJson(BaseModel): class CreateEventJson(BaseModel):
name: str name: str
description: str description: Optional[str]
start_time: datetime start_time: datetime
player_roles: list[PlayerRoleSchema] player_roles: list[PlayerRoleSchema]
class UpdateEventJson(BaseModel):
name: str
description: Optional[str]
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(
@ -93,7 +98,8 @@ 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
event.description = json.description if json.description:
event.description = json.description
event.start_time = json.start_time event.start_time = json.start_time
db.session.add(event) db.session.add(event)
@ -252,13 +258,33 @@ def get_event_players(player: Player, event_id: int, **_):
players=player_event_roles players=player_event_roles
).dict(by_alias=True), 200 ).dict(by_alias=True), 200
@api_events.patch("/<int:event_id>/players") @api_events.patch("/<int:event_id>")
@spec.validate(
resp=Response(
HTTP_200=EventSchema,
),
operation_id="update_event",
)
@requires_authentication @requires_authentication
@requires_team_membership() def update_event(player: Player, event_id: int, json: UpdateEventJson, **_):
def set_event_players(player_team: PlayerTeam, event_id: int, **_): event = db.session.query(Event).where(Event.id == event_id).one_or_none()
assert_team_authority(player_team, None) if not event:
abort(404)
assert_team_membership(player, event.team)
# merge players into event for player_event in event.players:
db.session.query(Event).filter(Event.id == event_id).update({"players": []}) player_team = player_event.player_team
roles = player_team.player_roles
# assign new roles if in json, set to None if not
new_role = next((x.role for x in json.player_roles if x.player.steam_id == str(player_event.player_id)), None)
#import sys
#print(player_event.player_id, file=sys.stderr)
# if valid role (new_role exists in roles), update player_event.role
if new_role and (role_entity := next((x for x in roles if x.role.name == new_role.role), None)):
player_event.player_team_role_id = role_entity.id
else:
player_event.role = None
raise NotImplementedError() event.update_discord_message()
return EventSchema.from_model(event).dict(by_alias=True), 200

View File

@ -12,7 +12,7 @@ class PlayerEvent(app_db.BaseModel):
event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), primary_key=True) event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), primary_key=True)
player_id: Mapped[int] = mapped_column(ForeignKey("players.steam_id"), primary_key=True) player_id: Mapped[int] = mapped_column(ForeignKey("players.steam_id"), primary_key=True)
player_team_role_id: Mapped[int] = mapped_column(ForeignKey("players_teams_roles.id"), nullable=True) player_team_role_id: Mapped[int | None] = mapped_column(ForeignKey("players_teams_roles.id"), nullable=True)
has_confirmed: Mapped[bool] = mapped_column(Boolean, default=False) has_confirmed: Mapped[bool] = mapped_column(Boolean, default=False)
event: Mapped["Event"] = relationship("Event", back_populates="players") event: Mapped["Event"] = relationship("Event", back_populates="players")
@ -21,10 +21,10 @@ class PlayerEvent(app_db.BaseModel):
"PlayerTeam", "PlayerTeam",
secondary="players", secondary="players",
primaryjoin="PlayerEvent.player_id == Player.steam_id", primaryjoin="PlayerEvent.player_id == Player.steam_id",
secondaryjoin="PlayerTeam.player_id == Player.steam_id", secondaryjoin="(PlayerTeam.player_id == Player.steam_id) & (PlayerTeam.team_id == Event.team_id)",
viewonly=True, viewonly=True,
) )
role: Mapped["PlayerTeamRole"] = relationship("PlayerTeamRole") role: Mapped["PlayerTeamRole | None"] = relationship("PlayerTeamRole")
class EventWithPlayerSchema(spec.BaseModel): class EventWithPlayerSchema(spec.BaseModel):
event: "EventSchema" event: "EventSchema"