mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-11-13 23:56:00 -06:00
Replace LobbyRepository's three single-use edit writes with one transactional edit
This commit is contained in:
@@ -13,6 +13,7 @@ import * as LobbySet from "./core/LobbySet";
|
||||
import "./core/LobbyProgression.server";
|
||||
import * as LobbyMatchRepository from "./LobbyMatchRepository.server";
|
||||
import * as LobbyRepository from "./LobbyRepository.server";
|
||||
import { editArgs } from "./tests/fixtures";
|
||||
|
||||
const users = UserFactory.pool();
|
||||
|
||||
@@ -205,14 +206,12 @@ describe("LobbyRepository visibility", () => {
|
||||
expect(Boolean(found)).toBe(expected);
|
||||
});
|
||||
|
||||
test("updateVisibility: opening a lobby to everyone shows it to outsiders", async () => {
|
||||
test("edit: opening a lobby to everyone shows it to outsiders", async () => {
|
||||
const lobby = await associationLobby();
|
||||
|
||||
await LobbyRepository.updateVisibility({
|
||||
id: lobby.id,
|
||||
visibility: null,
|
||||
ownerId: hostId(),
|
||||
});
|
||||
await LobbyRepository.edit(
|
||||
editArgs({ id: lobby.id, ownerId: hostId(), visibility: null }),
|
||||
);
|
||||
|
||||
const found = await withUserId(outsiderId(), () =>
|
||||
LobbyRepository.findById(lobby.id),
|
||||
@@ -220,15 +219,13 @@ describe("LobbyRepository visibility", () => {
|
||||
expect(found?.isPrivate).toBe(false);
|
||||
});
|
||||
|
||||
test("updateVisibility: making a lobby link-only keeps its share key", async () => {
|
||||
test("edit: making a lobby link-only keeps its share key", async () => {
|
||||
const lobby = await LobbyFactory.create({ hostUserId: hostId() });
|
||||
const shareKey = await hostShareKeyOf(lobby.id);
|
||||
|
||||
await LobbyRepository.updateVisibility({
|
||||
id: lobby.id,
|
||||
visibility: "LINK_ONLY",
|
||||
ownerId: hostId(),
|
||||
});
|
||||
await LobbyRepository.edit(
|
||||
editArgs({ id: lobby.id, ownerId: hostId(), visibility: "LINK_ONLY" }),
|
||||
);
|
||||
|
||||
expect(await LobbyRepository.isLinkOnlyById(lobby.id)).toBe(true);
|
||||
expect(await hostShareKeyOf(lobby.id)).toBe(shareKey);
|
||||
|
||||
@@ -752,8 +752,7 @@ export function insert({
|
||||
.insertInto("Lobby")
|
||||
.values({
|
||||
...args,
|
||||
modes: JSON.stringify(modes),
|
||||
mapPool: mapPool ? JSON.stringify(mapPool) : null,
|
||||
...serializeMaps({ modes, mapPool }),
|
||||
isApplicationRequired: toDBBoolean(isApplicationRequired),
|
||||
chatRoomId: chatRoom.id,
|
||||
shareKey: shortNanoid(),
|
||||
@@ -761,16 +760,14 @@ export function insert({
|
||||
.returning(["id", "createdAt"])
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
if (visibility !== "LINK_ONLY") {
|
||||
await trx
|
||||
.insertInto("ContentVisibility")
|
||||
.values(
|
||||
Visibility.toRows(visibility, {
|
||||
createdAt: lobby.createdAt,
|
||||
ownerId: args.hostUserId,
|
||||
}).map((row) => ({ ...row, lobbyId: lobby.id })),
|
||||
)
|
||||
.execute();
|
||||
const rows = visibilityRows({
|
||||
visibility,
|
||||
lobbyId: lobby.id,
|
||||
ownerId: args.hostUserId,
|
||||
createdAt: lobby.createdAt,
|
||||
});
|
||||
if (rows.length > 0) {
|
||||
await trx.insertInto("ContentVisibility").values(rows).execute();
|
||||
}
|
||||
|
||||
await trx
|
||||
@@ -984,73 +981,59 @@ export function kickParticipant(args: ParticipantRemoval) {
|
||||
});
|
||||
}
|
||||
|
||||
/** Updates the settings the host can change after creating the lobby. */
|
||||
export async function updateSettings({
|
||||
id,
|
||||
isApplicationRequired,
|
||||
...values
|
||||
}: Pick<
|
||||
type EditArgs = Pick<
|
||||
Tables["Lobby"],
|
||||
"id" | "name" | "description" | "label" | "bestOf" | "weaponRandomizer"
|
||||
| "id"
|
||||
| "name"
|
||||
| "description"
|
||||
| "label"
|
||||
| "bestOf"
|
||||
| "weaponRandomizer"
|
||||
| "modes"
|
||||
| "mapPool"
|
||||
| "mapsTournamentId"
|
||||
> & {
|
||||
isApplicationRequired: boolean;
|
||||
}) {
|
||||
await db
|
||||
.updateTable("Lobby")
|
||||
.set({
|
||||
...values,
|
||||
isApplicationRequired: toDBBoolean(isApplicationRequired),
|
||||
})
|
||||
.where("Lobby.id", "=", id)
|
||||
.execute();
|
||||
}
|
||||
|
||||
/** Replaces where the lobby's sets get their maps from: the modes of the players' preferences or a map pool. */
|
||||
export async function updateMaps({
|
||||
id,
|
||||
modes,
|
||||
mapPool,
|
||||
mapsTournamentId,
|
||||
}: Pick<Tables["Lobby"], "id" | "modes" | "mapPool" | "mapsTournamentId">) {
|
||||
await db
|
||||
.updateTable("Lobby")
|
||||
.set({
|
||||
modes: JSON.stringify(modes),
|
||||
mapPool: mapPool ? JSON.stringify(mapPool) : null,
|
||||
mapsTournamentId,
|
||||
})
|
||||
.where("Lobby.id", "=", id)
|
||||
.execute();
|
||||
}
|
||||
|
||||
/** Replaces who sees the lobby, its new audience in effect right away. Its share key keeps working. */
|
||||
export function updateVisibility({
|
||||
id,
|
||||
visibility,
|
||||
ownerId,
|
||||
}: {
|
||||
id: number;
|
||||
visibility: InsertArgs["visibility"];
|
||||
/** The host, whose friends a friends only lobby is visible to */
|
||||
ownerId: number;
|
||||
}) {
|
||||
};
|
||||
|
||||
/** Saves the settings the host can change after creating the lobby, the new audience in effect right away. Its share key keeps working. */
|
||||
export function edit({
|
||||
id,
|
||||
ownerId,
|
||||
visibility,
|
||||
modes,
|
||||
mapPool,
|
||||
isApplicationRequired,
|
||||
...values
|
||||
}: EditArgs) {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
await trx
|
||||
.updateTable("Lobby")
|
||||
.set({
|
||||
...values,
|
||||
...serializeMaps({ modes, mapPool }),
|
||||
isApplicationRequired: toDBBoolean(isApplicationRequired),
|
||||
})
|
||||
.where("Lobby.id", "=", id)
|
||||
.execute();
|
||||
|
||||
await trx
|
||||
.deleteFrom("ContentVisibility")
|
||||
.where("ContentVisibility.lobbyId", "=", id)
|
||||
.execute();
|
||||
|
||||
if (visibility === "LINK_ONLY") return;
|
||||
|
||||
await trx
|
||||
.insertInto("ContentVisibility")
|
||||
.values(
|
||||
Visibility.toRows(visibility, {
|
||||
createdAt: databaseTimestampNow(),
|
||||
ownerId,
|
||||
}).map((row) => ({ ...row, lobbyId: id })),
|
||||
)
|
||||
.execute();
|
||||
const rows = visibilityRows({
|
||||
visibility,
|
||||
lobbyId: id,
|
||||
ownerId,
|
||||
createdAt: databaseTimestampNow(),
|
||||
});
|
||||
if (rows.length > 0) {
|
||||
await trx.insertInto("ContentVisibility").values(rows).execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1264,3 +1247,32 @@ async function deleteParticipantEndingSet(
|
||||
|
||||
return setEnding;
|
||||
}
|
||||
|
||||
function serializeMaps({
|
||||
modes,
|
||||
mapPool,
|
||||
}: Pick<Tables["Lobby"], "modes" | "mapPool">) {
|
||||
return {
|
||||
modes: JSON.stringify(modes),
|
||||
mapPool: mapPool ? JSON.stringify(mapPool) : null,
|
||||
};
|
||||
}
|
||||
|
||||
function visibilityRows({
|
||||
visibility,
|
||||
lobbyId,
|
||||
ownerId,
|
||||
createdAt,
|
||||
}: {
|
||||
visibility: InsertArgs["visibility"];
|
||||
lobbyId: number;
|
||||
ownerId: number;
|
||||
createdAt: number;
|
||||
}) {
|
||||
if (visibility === "LINK_ONLY") return [];
|
||||
|
||||
return Visibility.toRows(visibility, { createdAt, ownerId }).map((row) => ({
|
||||
...row,
|
||||
lobbyId,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -312,7 +312,9 @@ export const action = defineAction(
|
||||
});
|
||||
if (issue) return { fieldErrors: { [issue.path[0]]: issue.message } };
|
||||
|
||||
await LobbyRepository.updateSettings({
|
||||
const maps = await mapsFromForm(body);
|
||||
|
||||
await LobbyRepository.edit({
|
||||
id,
|
||||
name: body.name ?? `${lobby.hostUsername}'s lobby`,
|
||||
description: body.description,
|
||||
@@ -320,13 +322,7 @@ export const action = defineAction(
|
||||
bestOf: Number(body.bestOf) as LobbyBestOf,
|
||||
weaponRandomizer: body.weaponRandomizer,
|
||||
isApplicationRequired: body.isApplicationRequired,
|
||||
});
|
||||
await LobbyRepository.updateMaps({
|
||||
id,
|
||||
...(await mapsFromForm(body)),
|
||||
});
|
||||
await LobbyRepository.updateVisibility({
|
||||
id,
|
||||
...maps,
|
||||
visibility: visibilityFromForm(body),
|
||||
ownerId: lobby.hostUserId,
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { withUserId } from "~/utils/Test";
|
||||
import * as LobbyMatchRepository from "../LobbyMatchRepository.server";
|
||||
import * as LobbyRepository from "../LobbyRepository.server";
|
||||
import type { LobbyWeaponRandomizer } from "../lobbies-constants";
|
||||
import { editArgs } from "../tests/fixtures";
|
||||
import * as LobbyProgression from "./LobbyProgression.server";
|
||||
import * as LobbySet from "./LobbySet";
|
||||
|
||||
@@ -186,15 +187,9 @@ describe("LobbyProgression.advance", () => {
|
||||
});
|
||||
const set = await latestSet(lobbyId);
|
||||
|
||||
await LobbyRepository.updateSettings({
|
||||
id: lobbyId,
|
||||
name: "Lobby",
|
||||
description: null,
|
||||
label: "CASUAL",
|
||||
bestOf: 3,
|
||||
weaponRandomizer: "OFF",
|
||||
isApplicationRequired: false,
|
||||
});
|
||||
await LobbyRepository.edit(
|
||||
editArgs({ id: lobbyId, ownerId: hostId(), weaponRandomizer: "OFF" }),
|
||||
);
|
||||
await LobbyProgression.advance(lobbyId);
|
||||
|
||||
expect((await latestSet(lobbyId)).rolledWeapons).toEqual(set.rolledWeapons);
|
||||
@@ -212,12 +207,9 @@ describe("LobbyProgression.advance", () => {
|
||||
{ mode: "TC" as const, stageId: 3 as const },
|
||||
];
|
||||
|
||||
await LobbyRepository.updateMaps({
|
||||
id: lobbyId,
|
||||
modes: ["TC"],
|
||||
mapPool,
|
||||
mapsTournamentId: null,
|
||||
});
|
||||
await LobbyRepository.edit(
|
||||
editArgs({ id: lobbyId, ownerId: hostId(), modes: ["TC"], mapPool }),
|
||||
);
|
||||
await playOut(lobbyId);
|
||||
|
||||
const { games } = await latestSet(lobbyId);
|
||||
|
||||
26
app/features/lobbies/tests/fixtures.ts
Normal file
26
app/features/lobbies/tests/fixtures.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type * as LobbyRepository from "../LobbyRepository.server";
|
||||
|
||||
type EditArgs = Parameters<typeof LobbyRepository.edit>[0];
|
||||
|
||||
/** `LobbyRepository.edit` args of a public lobby with `LobbyFactory`'s default settings, overridden by the given ones. */
|
||||
export function editArgs({
|
||||
id,
|
||||
ownerId,
|
||||
...overrides
|
||||
}: Pick<EditArgs, "id" | "ownerId"> & Partial<EditArgs>): EditArgs {
|
||||
return {
|
||||
id,
|
||||
ownerId,
|
||||
name: "Lobby",
|
||||
description: null,
|
||||
label: "CASUAL",
|
||||
bestOf: 3,
|
||||
weaponRandomizer: "OFF",
|
||||
isApplicationRequired: false,
|
||||
modes: ["SZ", "TC", "RM", "CB"],
|
||||
mapPool: null,
|
||||
mapsTournamentId: null,
|
||||
visibility: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user