Skip to content

Commit

Permalink
Add admin mode to games page, and allow admins to kick users
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminpjones committed Jan 1, 2025
1 parent df4a42a commit 18d8bc1
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 9 deletions.
6 changes: 3 additions & 3 deletions packages/server/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ router.post("/games/:gameId/move", async (req, res) => {
});

router.post("/games/:gameId/sit/:seat", async (req, res) => {
const user = req.user as User | undefined;
const user = req.user as UserResponse | undefined;

try {
const players: User[] = await takeSeat(
Expand All @@ -135,12 +135,12 @@ router.post("/games/:gameId/sit/:seat", async (req, res) => {

router.post("/games/:gameId/leave/:seat", async (req, res) => {
// TODO: make sure this is set to a valid id once we have user auth
const user_id = (req.user as User)?.id;
const user = req.user as UserResponse | undefined;

const players: User[] = await leaveSeat(
req.params.gameId,
Number(req.params.seat),
user_id,
user,
);

io().emit(`game/${req.params.gameId}/seats`, players);
Expand Down
26 changes: 20 additions & 6 deletions packages/server/src/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
GameStateResponse,
sanitizeConfig,
ITimeControlBase,
UserResponse,
} from "@ogfcommunity/variants-shared";
import { ObjectId, WithId, Document, Filter } from "mongodb";
import { getDb } from "./db";
Expand Down Expand Up @@ -242,10 +243,18 @@ function emitGame(
}
}

/**
*
* @param game_id
* @param seat The seat to be updated (0..N)
* @param user The user who submitted the request.
* @param new_user The user to place in the seat. The seat will be vacated if undefined.
* @returns
*/
async function updateSeat(
game_id: string,
seat: number,
user_id: string,
user: UserResponse,
new_user: User | undefined,
) {
const game = await getGame(game_id);
Expand All @@ -257,7 +266,12 @@ async function updateSeat(
}

// If the seat is occupied by another player, throw an error.
if (game.players[seat] != null && game.players[seat].id != user_id) {
if (
game.players[seat] != null &&
game.players[seat].id != user.id &&
// Admins may do as they please
user.role !== "admin"
) {
throw new Error("Seat taken!");
}

Expand All @@ -271,16 +285,16 @@ async function updateSeat(
return game.players;
}

export function takeSeat(game_id: string, seat: number, user: User) {
return updateSeat(game_id, seat, user.id, user);
export function takeSeat(game_id: string, seat: number, user: UserResponse) {
return updateSeat(game_id, seat, user, user);
}

export async function leaveSeat(
game_id: string,
seat: number,
user_id: string,
user: UserResponse,
) {
return updateSeat(game_id, seat, user_id, undefined);
return updateSeat(game_id, seat, user, undefined);
}

export async function getGameState(
Expand Down
15 changes: 15 additions & 0 deletions packages/vue-client/src/components/GameView/SeatComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import PlayerSymbol from "./PlayerSymbol.vue";
const props = defineProps<{
user_id?: string;
admin_mode?: boolean;
occupant?: User;
player_n: number;
selected?: number;
Expand Down Expand Up @@ -67,6 +68,13 @@ const time_config = computed(
>
Leave Seat
</button>
<button
v-if="hasLeaveCallback && occupant.id !== user_id && admin_mode"
class="btn-danger"
@click.stop="$emit('leave')"
>
Kick
</button>
</div>
</div>
<PlayerSymbol
Expand Down Expand Up @@ -119,4 +127,11 @@ const time_config = computed(
margin-left: 0.5rem;
}
}
/* stolen from bootstrap */
.btn-danger {
color: #fff;
background-color: #d9534f;
border-color: #d43f3a;
}
</style>
8 changes: 8 additions & 0 deletions packages/vue-client/src/views/GameView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const user = useCurrentUser();
const playing_as = ref<undefined | number>(undefined);
const displayed_round = computed(() => view_round.value ?? current_round.value);
const time_control = ref<ITimeControlBase | null>(null);
// Admins probably don't want to do admin stuff most of the time. Let's hide
// admin interface behind a toggle.
const adminMode = ref<boolean>(false);
function setNewState(stateResponse: GameStateResponse): void {
const { timeControl: timeControl, ...state } = stateResponse;
Expand Down Expand Up @@ -254,6 +257,7 @@ const createTimeControlPreview = (
<div v-for="(player, idx) in players" :key="idx">
<SeatComponent
:user_id="user?.id"
:admin_mode="adminMode"
:occupant="player"
:player_n="idx"
@sit="sit(idx)"
Expand Down Expand Up @@ -289,6 +293,10 @@ const createTimeControlPreview = (
>
Result: {{ game_state?.result }}
</div>
<div v-if="user?.role === 'admin'">
<input type="checkbox" v-model="adminMode" id="admin" />
<label for="admin">Admin Mode</label>
</div>
</div>
</div>
</main>
Expand Down

0 comments on commit 18d8bc1

Please sign in to comment.