320 lines
8.0 KiB
TypeScript
320 lines
8.0 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { lobbies, lobbyMembers, players, type Lobby } from "@/server/db/schema";
|
|
import {
|
|
createTRPCRouter,
|
|
protectedProcedure,
|
|
publicProcedure,
|
|
} from "@/server/api/trpc";
|
|
import {
|
|
LobbyMemberRoleSchema,
|
|
lobbyPatchSchema,
|
|
} from "@/lib/validations/lobby";
|
|
import { and, eq } from "drizzle-orm";
|
|
import { tracked } from "@trpc/server";
|
|
import type { EventArgs } from "@/server/sse/events";
|
|
import {
|
|
decreaseLobbyMemberCount,
|
|
increaseLobbyMemberCount,
|
|
handleLobbyAfterLeave,
|
|
} from "@/server/api/mutation-utils/lobby-utils";
|
|
import { createGameConfig } from "../mutation-utils/game-config";
|
|
import { ee } from "@/server/sse";
|
|
|
|
export const lobbyRouter = createTRPCRouter({
|
|
// queries
|
|
getCurrentLobby: protectedProcedure.query(async ({ ctx }) => {
|
|
const reuslt = await ctx.db.query.lobbyMembers.findFirst({
|
|
where: eq(lobbyMembers.playerId, ctx.session.user.id),
|
|
with: {
|
|
lobby: {
|
|
with: {
|
|
gameConfig: true,
|
|
members: {
|
|
with: {
|
|
player: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
return reuslt?.lobby!;
|
|
}),
|
|
|
|
get: publicProcedure
|
|
.input(
|
|
z.object({
|
|
id: z.string(),
|
|
}),
|
|
)
|
|
.query(
|
|
async ({ ctx, input }) =>
|
|
await ctx.db.query.lobbies.findFirst({
|
|
where: eq(lobbies.id, input.id),
|
|
with: {
|
|
gameConfig: true,
|
|
members: {
|
|
with: {
|
|
player: true,
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
),
|
|
|
|
// mutations
|
|
create: protectedProcedure
|
|
.input(
|
|
z.object({
|
|
lobby: lobbyPatchSchema,
|
|
}),
|
|
)
|
|
.mutation(async ({ ctx, input }) => {
|
|
const [lobby] = await ctx.db
|
|
.insert(lobbies)
|
|
.values({
|
|
...input.lobby,
|
|
createdById: ctx.session.user.id,
|
|
})
|
|
.returning({ id: lobbies.id });
|
|
if (!lobby) throw new Error("Error creating lobby");
|
|
const [member] = await ctx.db
|
|
.insert(lobbyMembers)
|
|
.values({
|
|
lobbyId: lobby.id,
|
|
playerId: ctx.session.user.id,
|
|
isReady: false,
|
|
role: "admin",
|
|
})
|
|
.returning({
|
|
id: lobbyMembers.playerId,
|
|
});
|
|
if (member && lobby) {
|
|
increaseLobbyMemberCount(lobby.id);
|
|
createGameConfig(lobby.id, ctx.db);
|
|
}
|
|
return lobby;
|
|
}),
|
|
update: protectedProcedure
|
|
.input(
|
|
z.object({
|
|
lobby: lobbyPatchSchema,
|
|
lobbyId: z.string(),
|
|
}),
|
|
)
|
|
.mutation(async ({ ctx, input }) => {
|
|
console.log("Check if user is admin");
|
|
|
|
const [lobby] = await ctx.db
|
|
.update(lobbies)
|
|
.set(input.lobby)
|
|
.where(
|
|
and(
|
|
eq(lobbies.id, input.lobbyId),
|
|
eq(lobbies.createdById, ctx.session.user.id),
|
|
),
|
|
)
|
|
.returning();
|
|
|
|
if (lobby) ee.emit("lobby:update", lobby);
|
|
return lobby;
|
|
}),
|
|
delete: protectedProcedure
|
|
.input(
|
|
z.object({
|
|
lobbyId: z.string(),
|
|
}),
|
|
)
|
|
.mutation(async ({ ctx, input }) => {
|
|
console.log("Check if user is admin");
|
|
|
|
const [lobby] = await ctx.db
|
|
.delete(lobbies)
|
|
.where(
|
|
and(
|
|
eq(lobbies.id, input.lobbyId),
|
|
eq(lobbies.createdById, ctx.session.user.id),
|
|
),
|
|
)
|
|
.returning({ id: lobbies.id });
|
|
return lobby;
|
|
}),
|
|
|
|
membership: protectedProcedure
|
|
.input(
|
|
z.object({
|
|
lobbyId: z.string(),
|
|
join: z.boolean(),
|
|
}),
|
|
)
|
|
.mutation(async ({ ctx, input }) => {
|
|
if (input.join) {
|
|
try {
|
|
const [member] = await ctx.db
|
|
.insert(lobbyMembers)
|
|
.values({
|
|
lobbyId: input.lobbyId,
|
|
playerId: ctx.session.user.id,
|
|
isReady: false,
|
|
role: "player",
|
|
})
|
|
.returning();
|
|
const player = member
|
|
? await ctx.db.query.players.findFirst({
|
|
where: eq(players.id, member.playerId),
|
|
})
|
|
: undefined;
|
|
if (member) {
|
|
ee.emit("lobby:member:membership", input.lobbyId, true, {
|
|
...member,
|
|
player,
|
|
});
|
|
increaseLobbyMemberCount(input.lobbyId);
|
|
return { success: true, member };
|
|
}
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
if (
|
|
e.message.includes(
|
|
"duplicate key value violates unique constraint",
|
|
)
|
|
)
|
|
return {
|
|
knownError: true,
|
|
succes: false,
|
|
error: "You can only be in one lobby at a time.",
|
|
};
|
|
} else {
|
|
return {
|
|
knownError: true,
|
|
succes: false,
|
|
error: "Error joining lobby",
|
|
};
|
|
}
|
|
}
|
|
} else {
|
|
const [member] = await ctx.db
|
|
.delete(lobbyMembers)
|
|
.where(
|
|
and(
|
|
eq(lobbyMembers.lobbyId, input.lobbyId),
|
|
eq(lobbyMembers.playerId, ctx.session.user.id),
|
|
),
|
|
)
|
|
.returning();
|
|
if (member) {
|
|
ee.emit(
|
|
"lobby:member:membership",
|
|
input.lobbyId,
|
|
false,
|
|
member.playerId,
|
|
);
|
|
decreaseLobbyMemberCount(input.lobbyId);
|
|
handleLobbyAfterLeave(input.lobbyId);
|
|
return { success: true, member };
|
|
}
|
|
}
|
|
}),
|
|
// admin mutaions
|
|
changeRole: protectedProcedure
|
|
.input(
|
|
z.object({
|
|
lobbyId: z.string(),
|
|
playerId: z.string(),
|
|
role: LobbyMemberRoleSchema,
|
|
}),
|
|
)
|
|
.mutation(async ({ ctx, input }) => {
|
|
console.log("Check if user is admin");
|
|
|
|
const [member] = await ctx.db
|
|
.update(lobbyMembers)
|
|
.set(input.role === "admin" ? { role: "admin" } : { role: "player" })
|
|
.where(
|
|
and(
|
|
eq(lobbyMembers.lobbyId, input.lobbyId),
|
|
eq(lobbyMembers.playerId, input.playerId),
|
|
),
|
|
)
|
|
.returning();
|
|
if (member) ee.emit("lobby:member:update", member);
|
|
return member;
|
|
}),
|
|
|
|
kick: protectedProcedure
|
|
.input(
|
|
z.object({
|
|
lobbyId: z.string(),
|
|
playerId: z.string(),
|
|
}),
|
|
)
|
|
.mutation(async ({ ctx, input }) => {
|
|
console.log("Check if user is admin");
|
|
|
|
const [member] = await ctx.db
|
|
.delete(lobbyMembers)
|
|
.where(
|
|
and(
|
|
eq(lobbyMembers.lobbyId, input.lobbyId),
|
|
eq(lobbyMembers.playerId, input.playerId),
|
|
),
|
|
)
|
|
.returning();
|
|
if (member)
|
|
ee.emit(
|
|
"lobby:member:membership",
|
|
input.lobbyId,
|
|
false,
|
|
member.playerId,
|
|
true,
|
|
);
|
|
decreaseLobbyMemberCount(input.lobbyId);
|
|
return member;
|
|
}),
|
|
|
|
// subscriptions
|
|
onUpdate: publicProcedure
|
|
.input(z.object({ lobbyId: z.string() }))
|
|
.subscription(async function* (opts) {
|
|
const iterable = ee.toIterable("lobby:update", {
|
|
signal: opts.signal,
|
|
});
|
|
|
|
function* maybeYield([lobby, deleted]: EventArgs["lobby:update"]) {
|
|
if (lobby.id !== opts.input.lobbyId) {
|
|
return;
|
|
}
|
|
yield tracked(lobby.id, { lobby, deleted });
|
|
}
|
|
|
|
for await (const args of iterable) {
|
|
yield* maybeYield(args);
|
|
}
|
|
}),
|
|
|
|
onMemberUpdate: publicProcedure
|
|
.input(z.object({ lobbyId: z.string() }))
|
|
.subscription(async function* (opts) {
|
|
const iterable = ee.toIterable("lobby:member:membership", {
|
|
signal: opts.signal,
|
|
});
|
|
|
|
function* maybeYield([
|
|
lobbyId,
|
|
joined,
|
|
member,
|
|
kicked,
|
|
]: EventArgs["lobby:member:membership"]) {
|
|
if (lobbyId !== opts.input.lobbyId) {
|
|
return;
|
|
}
|
|
yield tracked(lobbyId, { member, joined, kicked });
|
|
}
|
|
|
|
for await (const args of iterable) {
|
|
yield* maybeYield(args);
|
|
}
|
|
}),
|
|
});
|