mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-26 20:43:36 -05:00
Progress incl. create random team names, db rework
This commit is contained in:
@@ -40,6 +40,7 @@ import * as SQMatchRepository from "~/features/sendouq-match/SQMatchRepository.s
|
||||
import { BANNED_MAPS } from "~/features/sendouq-settings/banned-maps";
|
||||
import * as QSettingsRepository from "~/features/sendouq-settings/QSettingsRepository.server";
|
||||
import { AMOUNT_OF_MAPS_IN_POOL_PER_MODE } from "~/features/sendouq-settings/q-settings-constants";
|
||||
import { joinTeam } from "~/features/tournament/queries/joinLeaveTeam.server";
|
||||
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
|
||||
import { clearAllTournamentDataCache } from "~/features/tournament-bracket/core/Tournament.server";
|
||||
import * as TournamentLFGRepository from "~/features/tournament-lfg/TournamentLFGRepository.server";
|
||||
@@ -283,8 +284,6 @@ function wipeDB() {
|
||||
"TournamentMatchGameResult",
|
||||
"TournamentTeamCheckIn",
|
||||
"TournamentLFGLike",
|
||||
"TournamentLFGGroupMember",
|
||||
"TournamentLFGGroup",
|
||||
"TournamentTeam",
|
||||
"TournamentStage",
|
||||
"TournamentResult",
|
||||
@@ -1647,168 +1646,115 @@ async function tournamentLfgGroups() {
|
||||
{ tournamentId: 3, teamId: 201 },
|
||||
];
|
||||
|
||||
const insertMember = sql.prepare(
|
||||
/* sql */ `INSERT INTO "TournamentLFGGroupMember" ("groupId", "tournamentId", "userId", "role") VALUES (@groupId, @tournamentId, @userId, @role)`,
|
||||
);
|
||||
const updateNote = sql.prepare(
|
||||
/* sql */ `UPDATE "TournamentLFGGroupMember" SET "note" = @note WHERE "groupId" = @groupId AND "userId" = @userId`,
|
||||
);
|
||||
const insertLike = sql.prepare(
|
||||
/* sql */ `INSERT INTO "TournamentLFGLike" ("likerGroupId", "targetGroupId") VALUES (@likerGroupId, @targetGroupId)`,
|
||||
);
|
||||
const insertTeam = sql.prepare(
|
||||
/* sql */ `INSERT INTO "TournamentTeam" ("name", "createdAt", "tournamentId", "inviteCode") VALUES (@name, @createdAt, @tournamentId, @inviteCode)`,
|
||||
);
|
||||
const insertTeamMember = sql.prepare(
|
||||
/* sql */ `INSERT INTO "TournamentTeamMember" ("tournamentTeamId", "userId", "isOwner", "createdAt") VALUES (@tournamentTeamId, @userId, @isOwner, @createdAt)`,
|
||||
);
|
||||
const updateGroupTeamId = sql.prepare(
|
||||
/* sql */ `UPDATE "TournamentLFGGroup" SET "tournamentTeamId" = @teamId WHERE "id" = @groupId`,
|
||||
);
|
||||
|
||||
const createTeamForGroup = (
|
||||
groupId: number,
|
||||
tournamentId: number,
|
||||
ownerUserId: number,
|
||||
memberUserIds: number[],
|
||||
) => {
|
||||
const now = dateToDatabaseTimestamp(new Date());
|
||||
const { lastInsertRowid } = insertTeam.run({
|
||||
name: validTournamentTeamName(),
|
||||
createdAt: now,
|
||||
tournamentId,
|
||||
inviteCode: shortNanoid(),
|
||||
});
|
||||
const teamId = Number(lastInsertRowid);
|
||||
|
||||
insertTeamMember.run({
|
||||
tournamentTeamId: teamId,
|
||||
userId: ownerUserId,
|
||||
isOwner: 1,
|
||||
createdAt: now,
|
||||
});
|
||||
for (const userId of memberUserIds) {
|
||||
insertTeamMember.run({
|
||||
tournamentTeamId: teamId,
|
||||
userId,
|
||||
isOwner: 0,
|
||||
createdAt: now,
|
||||
});
|
||||
}
|
||||
|
||||
updateGroupTeamId.run({ teamId, groupId });
|
||||
};
|
||||
|
||||
let userIndex = 0;
|
||||
for (const { tournamentId, teamId } of tournaments) {
|
||||
const teamOwner = (
|
||||
sql
|
||||
.prepare(
|
||||
/* sql */ `SELECT "userId" FROM "TournamentTeamMember" WHERE "tournamentTeamId" = @teamId AND "isOwner" = 1`,
|
||||
)
|
||||
.get({ teamId }) as { userId: number }
|
||||
).userId;
|
||||
|
||||
const users = availableUsers.slice(userIndex, userIndex + 12);
|
||||
userIndex += 12;
|
||||
|
||||
// Group 1: solo, has note, isStayAsSub=1
|
||||
const group1 = await TournamentLFGRepository.createGroup({
|
||||
tournamentId,
|
||||
userId: users[0],
|
||||
isStayAsSub: true,
|
||||
});
|
||||
updateNote.run({
|
||||
note: "Looking for a team, can play any role",
|
||||
groupId: group1.id,
|
||||
userId: users[0],
|
||||
});
|
||||
|
||||
// Group 2: duo, one member has note
|
||||
const group2 = await TournamentLFGRepository.createGroup({
|
||||
tournamentId,
|
||||
userId: users[1],
|
||||
});
|
||||
insertMember.run({
|
||||
groupId: group2.id,
|
||||
tournamentId,
|
||||
userId: users[2],
|
||||
role: "REGULAR",
|
||||
});
|
||||
updateNote.run({
|
||||
note: "Prefer backline weapons",
|
||||
groupId: group2.id,
|
||||
userId: users[2],
|
||||
});
|
||||
createTeamForGroup(group2.id, tournamentId, users[1], [users[2]]);
|
||||
|
||||
// Group 3: trio
|
||||
const group3 = await TournamentLFGRepository.createGroup({
|
||||
tournamentId,
|
||||
userId: users[3],
|
||||
});
|
||||
for (const userId of [users[4], users[5]]) {
|
||||
insertMember.run({
|
||||
groupId: group3.id,
|
||||
// Group 1: solo placeholder, has note, isStayAsSub=1
|
||||
const { id: team1Id } = await TournamentLFGRepository.createPlaceholderTeam(
|
||||
{
|
||||
tournamentId,
|
||||
userId,
|
||||
role: "REGULAR",
|
||||
});
|
||||
}
|
||||
createTeamForGroup(group3.id, tournamentId, users[3], [users[4], users[5]]);
|
||||
|
||||
// Group 4: full (4 members)
|
||||
const group4 = await TournamentLFGRepository.createGroup({
|
||||
tournamentId,
|
||||
userId: users[6],
|
||||
userId: users[0],
|
||||
isStayAsSub: true,
|
||||
},
|
||||
);
|
||||
await TournamentLFGRepository.updateTeamNote({
|
||||
teamId: team1Id,
|
||||
value: "Looking for a team, can play any role",
|
||||
});
|
||||
for (const userId of [users[7], users[8], users[9]]) {
|
||||
insertMember.run({
|
||||
groupId: group4.id,
|
||||
|
||||
// Group 2: duo placeholder
|
||||
const { id: team2Id } = await TournamentLFGRepository.createPlaceholderTeam(
|
||||
{
|
||||
tournamentId,
|
||||
userId,
|
||||
role: "REGULAR",
|
||||
});
|
||||
}
|
||||
createTeamForGroup(group4.id, tournamentId, users[6], [
|
||||
users[7],
|
||||
users[8],
|
||||
users[9],
|
||||
]);
|
||||
|
||||
// Group 5: solo
|
||||
const group5 = await TournamentLFGRepository.createGroup({
|
||||
userId: users[1],
|
||||
},
|
||||
);
|
||||
joinTeam({
|
||||
newTeamId: team2Id,
|
||||
userId: users[2],
|
||||
inGameName: null,
|
||||
tournamentId,
|
||||
userId: users[10],
|
||||
});
|
||||
|
||||
// Group 6: duo linked to existing team
|
||||
const group6 = await TournamentLFGRepository.createGroup({
|
||||
// Group 3: trio placeholder
|
||||
const { id: team3Id } = await TournamentLFGRepository.createPlaceholderTeam(
|
||||
{
|
||||
tournamentId,
|
||||
userId: users[3],
|
||||
},
|
||||
);
|
||||
joinTeam({
|
||||
newTeamId: team3Id,
|
||||
userId: users[4],
|
||||
inGameName: null,
|
||||
tournamentId,
|
||||
userId: teamOwner,
|
||||
});
|
||||
updateGroupTeamId.run({ teamId, groupId: group6.id });
|
||||
insertMember.run({
|
||||
groupId: group6.id,
|
||||
joinTeam({
|
||||
newTeamId: team3Id,
|
||||
userId: users[5],
|
||||
inGameName: null,
|
||||
tournamentId,
|
||||
});
|
||||
|
||||
// Group 4: full (4 members) placeholder
|
||||
const { id: team4Id } = await TournamentLFGRepository.createPlaceholderTeam(
|
||||
{
|
||||
tournamentId,
|
||||
userId: users[6],
|
||||
},
|
||||
);
|
||||
joinTeam({
|
||||
newTeamId: team4Id,
|
||||
userId: users[7],
|
||||
inGameName: null,
|
||||
tournamentId,
|
||||
});
|
||||
joinTeam({
|
||||
newTeamId: team4Id,
|
||||
userId: users[8],
|
||||
inGameName: null,
|
||||
tournamentId,
|
||||
});
|
||||
joinTeam({
|
||||
newTeamId: team4Id,
|
||||
userId: users[9],
|
||||
inGameName: null,
|
||||
tournamentId,
|
||||
});
|
||||
|
||||
// Group 5: solo placeholder
|
||||
const { id: team5Id } = await TournamentLFGRepository.createPlaceholderTeam(
|
||||
{
|
||||
tournamentId,
|
||||
userId: users[10],
|
||||
},
|
||||
);
|
||||
|
||||
// Group 6: existing team set to looking
|
||||
await TournamentLFGRepository.startLooking(teamId);
|
||||
joinTeam({
|
||||
newTeamId: teamId,
|
||||
userId: users[11],
|
||||
role: "REGULAR",
|
||||
inGameName: null,
|
||||
tournamentId,
|
||||
});
|
||||
|
||||
// Group 1 -> Group 2 (one-way like)
|
||||
insertLike.run({
|
||||
likerGroupId: group1.id,
|
||||
targetGroupId: group2.id,
|
||||
// Team 1 -> Team 2 (one-way like)
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerTeamId: team1Id,
|
||||
targetTeamId: team2Id,
|
||||
});
|
||||
// Group 2 -> Group 1 (mutual — tests invitation UI)
|
||||
insertLike.run({
|
||||
likerGroupId: group2.id,
|
||||
targetGroupId: group1.id,
|
||||
// Team 2 -> Team 1 (mutual — tests invitation UI)
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerTeamId: team2Id,
|
||||
targetTeamId: team1Id,
|
||||
});
|
||||
// Group 3 -> Group 5 (one-way like)
|
||||
insertLike.run({
|
||||
likerGroupId: group3.id,
|
||||
targetGroupId: group5.id,
|
||||
// Team 3 -> Team 5 (one-way like)
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerTeamId: team3Id,
|
||||
targetTeamId: team5Id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,29 +693,9 @@ export interface TournamentSub {
|
||||
visibility: "+1" | "+2" | "+3" | "ALL";
|
||||
}
|
||||
|
||||
export interface TournamentLFGGroup {
|
||||
id: GeneratedAlways<number>;
|
||||
tournamentId: number;
|
||||
tournamentTeamId: number | null;
|
||||
visibility: JSONColumnTypeNullable<AssociationVisibility>;
|
||||
chatCode: string;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
// xxx: duplicate state with tournament team member... keeping these up to date might be annoying
|
||||
export interface TournamentLFGGroupMember {
|
||||
groupId: number;
|
||||
tournamentId: number;
|
||||
userId: number;
|
||||
role: "OWNER" | "MANAGER" | "REGULAR";
|
||||
note: string | null;
|
||||
isStayAsSub: Generated<DBBoolean>;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
export interface TournamentLFGLike {
|
||||
likerGroupId: number;
|
||||
targetGroupId: number;
|
||||
likerTeamId: number;
|
||||
targetTeamId: number;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
@@ -739,6 +719,12 @@ export interface TournamentTeam {
|
||||
tournamentId: number;
|
||||
teamId: number | null;
|
||||
avatarImgId: number | null;
|
||||
isLooking: Generated<DBBoolean>;
|
||||
isPlaceholder: Generated<DBBoolean>;
|
||||
lfgVisibility: JSONColumnTypeNullable<AssociationVisibility>;
|
||||
lfgNote: string | null;
|
||||
// xxx: do we provide chat at this point? if not then remove
|
||||
chatCode: string | null;
|
||||
}
|
||||
|
||||
export interface TournamentTeamCheckIn {
|
||||
@@ -756,6 +742,8 @@ export interface TournamentTeamMember {
|
||||
inGameName: string | null;
|
||||
tournamentTeamId: number;
|
||||
userId: number;
|
||||
role: Generated<"OWNER" | "MANAGER" | "REGULAR">;
|
||||
isStayAsSub: Generated<DBBoolean>;
|
||||
}
|
||||
|
||||
export interface TournamentOrganization {
|
||||
@@ -1232,8 +1220,6 @@ export interface DB {
|
||||
Tournament: Tournament;
|
||||
TournamentStaff: TournamentStaff;
|
||||
TournamentGroup: TournamentGroup;
|
||||
TournamentLFGGroup: TournamentLFGGroup;
|
||||
TournamentLFGGroupMember: TournamentLFGGroupMember;
|
||||
TournamentLFGLike: TournamentLFGLike;
|
||||
TournamentMatch: TournamentMatch;
|
||||
TournamentMatchPickBanEvent: TournamentMatchPickBanEvent;
|
||||
|
||||
@@ -13,10 +13,10 @@ const createTournament = () =>
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
const createGroup = (tournamentId: number, userId: number) =>
|
||||
TournamentLFGRepository.createGroup({ tournamentId, userId });
|
||||
const createPlaceholder = (tournamentId: number, userId: number) =>
|
||||
TournamentLFGRepository.createPlaceholderTeam({ tournamentId, userId });
|
||||
|
||||
describe("createGroup", () => {
|
||||
describe("createPlaceholderTeam", () => {
|
||||
beforeEach(async () => {
|
||||
await dbInsertUsers(2);
|
||||
});
|
||||
@@ -25,23 +25,23 @@ describe("createGroup", () => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("creates a group with owner member", async () => {
|
||||
test("creates a placeholder team with owner member", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group = await createGroup(tournament.id, 1);
|
||||
const team = await createPlaceholder(tournament.id, 1);
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
|
||||
expect(groups).toHaveLength(1);
|
||||
expect(groups[0].id).toBe(group.id);
|
||||
expect(groups[0].id).toBe(team.id);
|
||||
});
|
||||
|
||||
test("owner has OWNER role", async () => {
|
||||
const tournament = await createTournament();
|
||||
await createGroup(tournament.id, 1);
|
||||
await createPlaceholder(tournament.id, 1);
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
const members = groups[0].members;
|
||||
@@ -50,7 +50,7 @@ describe("createGroup", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("findGroupsByTournamentId", () => {
|
||||
describe("findLookingTeamsByTournamentId", () => {
|
||||
beforeEach(async () => {
|
||||
await dbInsertUsers(3);
|
||||
});
|
||||
@@ -59,22 +59,22 @@ describe("findGroupsByTournamentId", () => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("returns groups for given tournament", async () => {
|
||||
test("returns looking teams for given tournament", async () => {
|
||||
const tournament = await createTournament();
|
||||
await createGroup(tournament.id, 1);
|
||||
await createGroup(tournament.id, 2);
|
||||
await createPlaceholder(tournament.id, 1);
|
||||
await createPlaceholder(tournament.id, 2);
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
|
||||
expect(groups).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("returns empty array when no groups exist", async () => {
|
||||
test("returns empty array when no looking teams exist", async () => {
|
||||
const tournament = await createTournament();
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
|
||||
@@ -83,9 +83,9 @@ describe("findGroupsByTournamentId", () => {
|
||||
|
||||
test("returns groups with member data", async () => {
|
||||
const tournament = await createTournament();
|
||||
await createGroup(tournament.id, 1);
|
||||
await createPlaceholder(tournament.id, 1);
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
const members = groups[0].members;
|
||||
@@ -95,13 +95,13 @@ describe("findGroupsByTournamentId", () => {
|
||||
expect(members[0].role).toBe("OWNER");
|
||||
});
|
||||
|
||||
test("does not return groups from other tournaments", async () => {
|
||||
test("does not return teams from other tournaments", async () => {
|
||||
const tournament1 = await createTournament();
|
||||
const tournament2 = await createTournament();
|
||||
await createGroup(tournament1.id, 1);
|
||||
await createGroup(tournament2.id, 2);
|
||||
await createPlaceholder(tournament1.id, 1);
|
||||
await createPlaceholder(tournament2.id, 2);
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament1.id,
|
||||
);
|
||||
|
||||
@@ -118,62 +118,62 @@ describe("addLike / deleteLike", () => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("adds a like between two groups", async () => {
|
||||
test("adds a like between two teams", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group1 = await createGroup(tournament.id, 1);
|
||||
const group2 = await createGroup(tournament.id, 2);
|
||||
const team1 = await createPlaceholder(tournament.id, 1);
|
||||
const team2 = await createPlaceholder(tournament.id, 2);
|
||||
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerGroupId: group1.id,
|
||||
targetGroupId: group2.id,
|
||||
likerTeamId: team1.id,
|
||||
targetTeamId: team2.id,
|
||||
});
|
||||
|
||||
const likes = await TournamentLFGRepository.allLikesByGroupId(group1.id);
|
||||
const likes = await TournamentLFGRepository.allLikesByTeamId(team1.id);
|
||||
|
||||
expect(likes.given).toHaveLength(1);
|
||||
expect(likes.given[0].groupId).toBe(group2.id);
|
||||
expect(likes.given[0].teamId).toBe(team2.id);
|
||||
});
|
||||
|
||||
test("duplicate like does not throw", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group1 = await createGroup(tournament.id, 1);
|
||||
const group2 = await createGroup(tournament.id, 2);
|
||||
const team1 = await createPlaceholder(tournament.id, 1);
|
||||
const team2 = await createPlaceholder(tournament.id, 2);
|
||||
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerGroupId: group1.id,
|
||||
targetGroupId: group2.id,
|
||||
likerTeamId: team1.id,
|
||||
targetTeamId: team2.id,
|
||||
});
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerGroupId: group1.id,
|
||||
targetGroupId: group2.id,
|
||||
likerTeamId: team1.id,
|
||||
targetTeamId: team2.id,
|
||||
});
|
||||
|
||||
const likes = await TournamentLFGRepository.allLikesByGroupId(group1.id);
|
||||
const likes = await TournamentLFGRepository.allLikesByTeamId(team1.id);
|
||||
|
||||
expect(likes.given).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("deleteLike removes the like", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group1 = await createGroup(tournament.id, 1);
|
||||
const group2 = await createGroup(tournament.id, 2);
|
||||
const team1 = await createPlaceholder(tournament.id, 1);
|
||||
const team2 = await createPlaceholder(tournament.id, 2);
|
||||
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerGroupId: group1.id,
|
||||
targetGroupId: group2.id,
|
||||
likerTeamId: team1.id,
|
||||
targetTeamId: team2.id,
|
||||
});
|
||||
await TournamentLFGRepository.deleteLike({
|
||||
likerGroupId: group1.id,
|
||||
targetGroupId: group2.id,
|
||||
likerTeamId: team1.id,
|
||||
targetTeamId: team2.id,
|
||||
});
|
||||
|
||||
const likes = await TournamentLFGRepository.allLikesByGroupId(group1.id);
|
||||
const likes = await TournamentLFGRepository.allLikesByTeamId(team1.id);
|
||||
|
||||
expect(likes.given).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("allLikesByGroupId", () => {
|
||||
describe("allLikesByTeamId", () => {
|
||||
beforeEach(async () => {
|
||||
await dbInsertUsers(3);
|
||||
});
|
||||
@@ -184,39 +184,39 @@ describe("allLikesByGroupId", () => {
|
||||
|
||||
test("separates likes into given and received", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group1 = await createGroup(tournament.id, 1);
|
||||
const group2 = await createGroup(tournament.id, 2);
|
||||
const group3 = await createGroup(tournament.id, 3);
|
||||
const team1 = await createPlaceholder(tournament.id, 1);
|
||||
const team2 = await createPlaceholder(tournament.id, 2);
|
||||
const team3 = await createPlaceholder(tournament.id, 3);
|
||||
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerGroupId: group1.id,
|
||||
targetGroupId: group2.id,
|
||||
likerTeamId: team1.id,
|
||||
targetTeamId: team2.id,
|
||||
});
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerGroupId: group3.id,
|
||||
targetGroupId: group1.id,
|
||||
likerTeamId: team3.id,
|
||||
targetTeamId: team1.id,
|
||||
});
|
||||
|
||||
const likes = await TournamentLFGRepository.allLikesByGroupId(group1.id);
|
||||
const likes = await TournamentLFGRepository.allLikesByTeamId(team1.id);
|
||||
|
||||
expect(likes.given).toHaveLength(1);
|
||||
expect(likes.given[0].groupId).toBe(group2.id);
|
||||
expect(likes.given[0].teamId).toBe(team2.id);
|
||||
expect(likes.received).toHaveLength(1);
|
||||
expect(likes.received[0].groupId).toBe(group3.id);
|
||||
expect(likes.received[0].teamId).toBe(team3.id);
|
||||
});
|
||||
|
||||
test("returns empty given/received when no likes", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group = await createGroup(tournament.id, 1);
|
||||
const team = await createPlaceholder(tournament.id, 1);
|
||||
|
||||
const likes = await TournamentLFGRepository.allLikesByGroupId(group.id);
|
||||
const likes = await TournamentLFGRepository.allLikesByTeamId(team.id);
|
||||
|
||||
expect(likes.given).toHaveLength(0);
|
||||
expect(likes.received).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("morphGroups", () => {
|
||||
describe("mergeTeams", () => {
|
||||
beforeEach(async () => {
|
||||
await dbInsertUsers(5);
|
||||
});
|
||||
@@ -225,41 +225,39 @@ describe("morphGroups", () => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("merges two groups, other group is deleted", async () => {
|
||||
test("merges two teams, other team is deleted", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group1 = await createGroup(tournament.id, 1);
|
||||
const group2 = await createGroup(tournament.id, 2);
|
||||
const team1 = await createPlaceholder(tournament.id, 1);
|
||||
const team2 = await createPlaceholder(tournament.id, 2);
|
||||
|
||||
await TournamentLFGRepository.morphGroups({
|
||||
survivingGroupId: group1.id,
|
||||
otherGroupId: group2.id,
|
||||
await TournamentLFGRepository.mergeTeams({
|
||||
survivingTeamId: team1.id,
|
||||
otherTeamId: team2.id,
|
||||
maxGroupSize: 4,
|
||||
tournamentId: tournament.id,
|
||||
});
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
|
||||
expect(groups).toHaveLength(1);
|
||||
expect(groups[0].id).toBe(group1.id);
|
||||
expect(groups[0].id).toBe(team1.id);
|
||||
const members = groups[0].members;
|
||||
expect(members).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("demotes other group's OWNER to MANAGER", async () => {
|
||||
test("demotes other team's OWNER to MANAGER", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group1 = await createGroup(tournament.id, 1);
|
||||
const group2 = await createGroup(tournament.id, 2);
|
||||
const team1 = await createPlaceholder(tournament.id, 1);
|
||||
const team2 = await createPlaceholder(tournament.id, 2);
|
||||
|
||||
await TournamentLFGRepository.morphGroups({
|
||||
survivingGroupId: group1.id,
|
||||
otherGroupId: group2.id,
|
||||
await TournamentLFGRepository.mergeTeams({
|
||||
survivingTeamId: team1.id,
|
||||
otherTeamId: team2.id,
|
||||
maxGroupSize: 4,
|
||||
tournamentId: tournament.id,
|
||||
});
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
const members = groups[0].members;
|
||||
@@ -270,49 +268,47 @@ describe("morphGroups", () => {
|
||||
|
||||
test("throws when merged size exceeds maxGroupSize", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group1 = await createGroup(tournament.id, 1);
|
||||
const group2 = await createGroup(tournament.id, 2);
|
||||
const team1 = await createPlaceholder(tournament.id, 1);
|
||||
const team2 = await createPlaceholder(tournament.id, 2);
|
||||
|
||||
await expect(
|
||||
TournamentLFGRepository.morphGroups({
|
||||
survivingGroupId: group1.id,
|
||||
otherGroupId: group2.id,
|
||||
TournamentLFGRepository.mergeTeams({
|
||||
survivingTeamId: team1.id,
|
||||
otherTeamId: team2.id,
|
||||
maxGroupSize: 1,
|
||||
tournamentId: tournament.id,
|
||||
}),
|
||||
).rejects.toThrow("Group has too many members after merge");
|
||||
});
|
||||
|
||||
test("clears likes on surviving group after merge", async () => {
|
||||
test("clears likes on surviving team after merge", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group1 = await createGroup(tournament.id, 1);
|
||||
const group2 = await createGroup(tournament.id, 2);
|
||||
const group3 = await createGroup(tournament.id, 3);
|
||||
const team1 = await createPlaceholder(tournament.id, 1);
|
||||
const team2 = await createPlaceholder(tournament.id, 2);
|
||||
const team3 = await createPlaceholder(tournament.id, 3);
|
||||
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerGroupId: group1.id,
|
||||
targetGroupId: group3.id,
|
||||
likerTeamId: team1.id,
|
||||
targetTeamId: team3.id,
|
||||
});
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerGroupId: group3.id,
|
||||
targetGroupId: group1.id,
|
||||
likerTeamId: team3.id,
|
||||
targetTeamId: team1.id,
|
||||
});
|
||||
|
||||
await TournamentLFGRepository.morphGroups({
|
||||
survivingGroupId: group1.id,
|
||||
otherGroupId: group2.id,
|
||||
await TournamentLFGRepository.mergeTeams({
|
||||
survivingTeamId: team1.id,
|
||||
otherTeamId: team2.id,
|
||||
maxGroupSize: 4,
|
||||
tournamentId: tournament.id,
|
||||
});
|
||||
|
||||
const likes = await TournamentLFGRepository.allLikesByGroupId(group1.id);
|
||||
const likes = await TournamentLFGRepository.allLikesByTeamId(team1.id);
|
||||
|
||||
expect(likes.given).toHaveLength(0);
|
||||
expect(likes.received).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateMemberNote", () => {
|
||||
describe("updateTeamNote", () => {
|
||||
beforeEach(async () => {
|
||||
await dbInsertUsers(1);
|
||||
});
|
||||
@@ -321,33 +317,29 @@ describe("updateMemberNote", () => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("sets and clears a member note", async () => {
|
||||
test("sets and clears a team note", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group = await createGroup(tournament.id, 1);
|
||||
const team = await createPlaceholder(tournament.id, 1);
|
||||
|
||||
await TournamentLFGRepository.updateMemberNote({
|
||||
groupId: group.id,
|
||||
userId: 1,
|
||||
await TournamentLFGRepository.updateTeamNote({
|
||||
teamId: team.id,
|
||||
value: "Looking for support",
|
||||
});
|
||||
|
||||
let groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
let groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
let members = groups[0].members;
|
||||
expect(members[0].note).toBe("Looking for support");
|
||||
expect(groups[0].note).toBe("Looking for support");
|
||||
|
||||
await TournamentLFGRepository.updateMemberNote({
|
||||
groupId: group.id,
|
||||
userId: 1,
|
||||
await TournamentLFGRepository.updateTeamNote({
|
||||
teamId: team.id,
|
||||
value: null,
|
||||
});
|
||||
|
||||
groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
members = groups[0].members;
|
||||
expect(members[0].note).toBeNull();
|
||||
expect(groups[0].note).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -362,25 +354,25 @@ describe("updateMemberRole", () => {
|
||||
|
||||
test("changes role from REGULAR to MANAGER", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group = await createGroup(tournament.id, 1);
|
||||
const team = await createPlaceholder(tournament.id, 1);
|
||||
|
||||
await db
|
||||
.insertInto("TournamentLFGGroupMember")
|
||||
.insertInto("TournamentTeamMember")
|
||||
.values({
|
||||
groupId: group.id,
|
||||
tournamentId: tournament.id,
|
||||
tournamentTeamId: team.id,
|
||||
userId: 2,
|
||||
isOwner: 0,
|
||||
role: "REGULAR",
|
||||
})
|
||||
.execute();
|
||||
|
||||
await TournamentLFGRepository.updateMemberRole({
|
||||
userId: 2,
|
||||
groupId: group.id,
|
||||
teamId: team.id,
|
||||
role: "MANAGER",
|
||||
});
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
const members = groups[0].members;
|
||||
@@ -393,7 +385,7 @@ describe("updateMemberRole", () => {
|
||||
expect(() =>
|
||||
TournamentLFGRepository.updateMemberRole({
|
||||
userId: 1,
|
||||
groupId: 1,
|
||||
teamId: 1,
|
||||
role: "OWNER",
|
||||
}),
|
||||
).toThrow("Can't set role to OWNER with this function");
|
||||
@@ -411,10 +403,10 @@ describe("updateStayAsSub", () => {
|
||||
|
||||
test("toggles isStayAsSub on/off", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group = await createGroup(tournament.id, 1);
|
||||
const team = await createPlaceholder(tournament.id, 1);
|
||||
|
||||
await TournamentLFGRepository.updateStayAsSub({
|
||||
groupId: group.id,
|
||||
teamId: team.id,
|
||||
userId: 1,
|
||||
value: true,
|
||||
});
|
||||
@@ -425,7 +417,7 @@ describe("updateStayAsSub", () => {
|
||||
expect(subs).toContain(1);
|
||||
|
||||
await TournamentLFGRepository.updateStayAsSub({
|
||||
groupId: group.id,
|
||||
teamId: team.id,
|
||||
userId: 1,
|
||||
value: false,
|
||||
});
|
||||
@@ -435,7 +427,7 @@ describe("updateStayAsSub", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("leaveGroup", () => {
|
||||
describe("leaveLfg", () => {
|
||||
beforeEach(async () => {
|
||||
await dbInsertUsers(3);
|
||||
});
|
||||
@@ -444,26 +436,26 @@ describe("leaveGroup", () => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("removes member from group", async () => {
|
||||
test("removes member from placeholder team", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group = await createGroup(tournament.id, 1);
|
||||
const team = await createPlaceholder(tournament.id, 1);
|
||||
|
||||
await db
|
||||
.insertInto("TournamentLFGGroupMember")
|
||||
.insertInto("TournamentTeamMember")
|
||||
.values({
|
||||
groupId: group.id,
|
||||
tournamentId: tournament.id,
|
||||
tournamentTeamId: team.id,
|
||||
userId: 2,
|
||||
isOwner: 0,
|
||||
role: "REGULAR",
|
||||
})
|
||||
.execute();
|
||||
|
||||
await TournamentLFGRepository.leaveGroup({
|
||||
await TournamentLFGRepository.leaveLfg({
|
||||
userId: 2,
|
||||
tournamentId: tournament.id,
|
||||
});
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
const members = groups[0].members;
|
||||
@@ -472,52 +464,52 @@ describe("leaveGroup", () => {
|
||||
expect(members[0].id).toBe(1);
|
||||
});
|
||||
|
||||
test("deletes group when last member leaves", async () => {
|
||||
test("deletes placeholder team when last member leaves", async () => {
|
||||
const tournament = await createTournament();
|
||||
await createGroup(tournament.id, 1);
|
||||
await createPlaceholder(tournament.id, 1);
|
||||
|
||||
await TournamentLFGRepository.leaveGroup({
|
||||
await TournamentLFGRepository.leaveLfg({
|
||||
userId: 1,
|
||||
tournamentId: tournament.id,
|
||||
});
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
|
||||
expect(groups).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("promotes MANAGER to OWNER when owner leaves", async () => {
|
||||
test("promotes MANAGER to OWNER when owner leaves placeholder", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group = await createGroup(tournament.id, 1);
|
||||
const team = await createPlaceholder(tournament.id, 1);
|
||||
|
||||
await db
|
||||
.insertInto("TournamentLFGGroupMember")
|
||||
.insertInto("TournamentTeamMember")
|
||||
.values({
|
||||
groupId: group.id,
|
||||
tournamentId: tournament.id,
|
||||
tournamentTeamId: team.id,
|
||||
userId: 2,
|
||||
isOwner: 0,
|
||||
role: "MANAGER",
|
||||
})
|
||||
.execute();
|
||||
|
||||
await db
|
||||
.insertInto("TournamentLFGGroupMember")
|
||||
.insertInto("TournamentTeamMember")
|
||||
.values({
|
||||
groupId: group.id,
|
||||
tournamentId: tournament.id,
|
||||
tournamentTeamId: team.id,
|
||||
userId: 3,
|
||||
isOwner: 0,
|
||||
role: "REGULAR",
|
||||
})
|
||||
.execute();
|
||||
|
||||
await TournamentLFGRepository.leaveGroup({
|
||||
await TournamentLFGRepository.leaveLfg({
|
||||
userId: 1,
|
||||
tournamentId: tournament.id,
|
||||
});
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
const members = groups[0].members;
|
||||
@@ -526,31 +518,50 @@ describe("leaveGroup", () => {
|
||||
expect(newOwner?.role).toBe("OWNER");
|
||||
});
|
||||
|
||||
test("promotes first member to OWNER when owner leaves and no manager exists", async () => {
|
||||
test("sets isLooking=0 for non-placeholder team", async () => {
|
||||
const tournament = await createTournament();
|
||||
const group = await createGroup(tournament.id, 1);
|
||||
|
||||
const team = await db
|
||||
.insertInto("TournamentTeam")
|
||||
.values({
|
||||
tournamentId: tournament.id,
|
||||
name: "Real Team",
|
||||
inviteCode: "abc",
|
||||
isLooking: 1,
|
||||
isPlaceholder: 0,
|
||||
chatCode: "chat123",
|
||||
})
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
await db
|
||||
.insertInto("TournamentLFGGroupMember")
|
||||
.insertInto("TournamentTeamMember")
|
||||
.values({
|
||||
groupId: group.id,
|
||||
tournamentId: tournament.id,
|
||||
userId: 2,
|
||||
role: "REGULAR",
|
||||
tournamentTeamId: team.id,
|
||||
userId: 1,
|
||||
isOwner: 1,
|
||||
role: "OWNER",
|
||||
})
|
||||
.execute();
|
||||
|
||||
await TournamentLFGRepository.leaveGroup({
|
||||
await TournamentLFGRepository.leaveLfg({
|
||||
userId: 1,
|
||||
tournamentId: tournament.id,
|
||||
});
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
const groups = await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
const members = groups[0].members;
|
||||
|
||||
expect(members[0].role).toBe("OWNER");
|
||||
expect(groups).toHaveLength(0);
|
||||
|
||||
const teamRow = await db
|
||||
.selectFrom("TournamentTeam")
|
||||
.select("isLooking")
|
||||
.where("id", "=", team.id)
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
expect(teamRow.isLooking).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -565,12 +576,12 @@ describe("getSubsForTournament", () => {
|
||||
|
||||
test("returns userIds with isStayAsSub", async () => {
|
||||
const tournament = await createTournament();
|
||||
await TournamentLFGRepository.createGroup({
|
||||
await TournamentLFGRepository.createPlaceholderTeam({
|
||||
tournamentId: tournament.id,
|
||||
userId: 1,
|
||||
isStayAsSub: true,
|
||||
});
|
||||
await createGroup(tournament.id, 2);
|
||||
await createPlaceholder(tournament.id, 2);
|
||||
|
||||
const subs = await TournamentLFGRepository.getSubsForTournament(
|
||||
tournament.id,
|
||||
@@ -581,7 +592,7 @@ describe("getSubsForTournament", () => {
|
||||
|
||||
test("returns empty when nobody opted in", async () => {
|
||||
const tournament = await createTournament();
|
||||
await createGroup(tournament.id, 1);
|
||||
await createPlaceholder(tournament.id, 1);
|
||||
|
||||
const subs = await TournamentLFGRepository.getSubsForTournament(
|
||||
tournament.id,
|
||||
@@ -590,46 +601,3 @@ describe("getSubsForTournament", () => {
|
||||
expect(subs).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cleanupForTournamentStart", () => {
|
||||
beforeEach(async () => {
|
||||
await dbInsertUsers(2);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("deletes all groups for a tournament", async () => {
|
||||
const tournament = await createTournament();
|
||||
await createGroup(tournament.id, 1);
|
||||
await createGroup(tournament.id, 2);
|
||||
|
||||
await TournamentLFGRepository.cleanupForTournamentStart(tournament.id);
|
||||
|
||||
const groups = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
tournament.id,
|
||||
);
|
||||
|
||||
expect(groups).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("does not affect groups from other tournaments", async () => {
|
||||
const tournament1 = await createTournament();
|
||||
const tournament2 = await createTournament();
|
||||
await createGroup(tournament1.id, 1);
|
||||
await createGroup(tournament2.id, 2);
|
||||
|
||||
await TournamentLFGRepository.cleanupForTournamentStart(tournament1.id);
|
||||
|
||||
const groups1 = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
tournament1.id,
|
||||
);
|
||||
const groups2 = await TournamentLFGRepository.findGroupsByTournamentId(
|
||||
tournament2.id,
|
||||
);
|
||||
|
||||
expect(groups1).toHaveLength(0);
|
||||
expect(groups2).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,68 +9,48 @@ import {
|
||||
userChatNameColorForJson,
|
||||
} from "~/utils/kysely.server";
|
||||
import { errorIsSqliteForeignKeyConstraintFailure } from "~/utils/sql";
|
||||
import { randomTeamName } from "~/utils/team-name";
|
||||
|
||||
type CreateGroupFromTeamArgs = {
|
||||
tournamentId: number;
|
||||
tournamentTeamId: number;
|
||||
members: Array<{ userId: number; isOwner: boolean }>;
|
||||
};
|
||||
export function createGroupFromTeam(args: CreateGroupFromTeamArgs) {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
const createdGroup = await trx
|
||||
.insertInto("TournamentLFGGroup")
|
||||
.values({
|
||||
tournamentId: args.tournamentId,
|
||||
tournamentTeamId: args.tournamentTeamId,
|
||||
chatCode: shortNanoid(),
|
||||
})
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
for (const member of args.members) {
|
||||
await trx
|
||||
.insertInto("TournamentLFGGroupMember")
|
||||
.values({
|
||||
groupId: createdGroup.id,
|
||||
tournamentId: args.tournamentId,
|
||||
userId: member.userId,
|
||||
role: member.isOwner ? "OWNER" : "REGULAR",
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
return createdGroup;
|
||||
});
|
||||
export function startLooking(teamId: number) {
|
||||
return db
|
||||
.updateTable("TournamentTeam")
|
||||
.set({ isLooking: 1, chatCode: shortNanoid() })
|
||||
.where("id", "=", teamId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
type CreateGroupArgs = {
|
||||
type CreatePlaceholderTeamArgs = {
|
||||
tournamentId: number;
|
||||
userId: number;
|
||||
isStayAsSub?: boolean;
|
||||
};
|
||||
export function createGroup(args: CreateGroupArgs) {
|
||||
export function createPlaceholderTeam(args: CreatePlaceholderTeamArgs) {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
const createdGroup = await trx
|
||||
.insertInto("TournamentLFGGroup")
|
||||
const createdTeam = await trx
|
||||
.insertInto("TournamentTeam")
|
||||
.values({
|
||||
tournamentId: args.tournamentId,
|
||||
name: randomTeamName(),
|
||||
inviteCode: shortNanoid(),
|
||||
isPlaceholder: 1,
|
||||
isLooking: 1,
|
||||
chatCode: shortNanoid(),
|
||||
})
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
await trx
|
||||
.insertInto("TournamentLFGGroupMember")
|
||||
.insertInto("TournamentTeamMember")
|
||||
.values({
|
||||
groupId: createdGroup.id,
|
||||
tournamentId: args.tournamentId,
|
||||
tournamentTeamId: createdTeam.id,
|
||||
userId: args.userId,
|
||||
isOwner: 1,
|
||||
role: "OWNER",
|
||||
isStayAsSub: args.isStayAsSub ? 1 : 0,
|
||||
})
|
||||
.execute();
|
||||
|
||||
return createdGroup;
|
||||
return createdTeam;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,39 +63,34 @@ type TournamentLFGMemberObject = {
|
||||
languages: Tables["User"]["languages"];
|
||||
vc: Tables["User"]["vc"];
|
||||
pronouns: Tables["User"]["pronouns"];
|
||||
role: Tables["TournamentLFGGroupMember"]["role"];
|
||||
note: Tables["TournamentLFGGroupMember"]["note"];
|
||||
isStayAsSub: Tables["TournamentLFGGroupMember"]["isStayAsSub"];
|
||||
role: Tables["TournamentTeamMember"]["role"];
|
||||
isStayAsSub: Tables["TournamentTeamMember"]["isStayAsSub"];
|
||||
weapons: Tables["User"]["qWeaponPool"];
|
||||
chatNameColor: string | null;
|
||||
plusTier: Tables["PlusTier"]["tier"] | null;
|
||||
};
|
||||
|
||||
export async function findGroupsByTournamentId(tournamentId: number) {
|
||||
export async function findLookingTeamsByTournamentId(tournamentId: number) {
|
||||
return db
|
||||
.selectFrom("TournamentLFGGroup")
|
||||
.selectFrom("TournamentTeam")
|
||||
.innerJoin(
|
||||
"TournamentLFGGroupMember",
|
||||
"TournamentLFGGroupMember.groupId",
|
||||
"TournamentLFGGroup.id",
|
||||
)
|
||||
.innerJoin("User", "User.id", "TournamentLFGGroupMember.userId")
|
||||
.leftJoin("PlusTier", "PlusTier.userId", "User.id")
|
||||
.leftJoin(
|
||||
"TournamentTeam",
|
||||
"TournamentTeamMember",
|
||||
"TournamentTeamMember.tournamentTeamId",
|
||||
"TournamentTeam.id",
|
||||
"TournamentLFGGroup.tournamentTeamId",
|
||||
)
|
||||
.innerJoin("User", "User.id", "TournamentTeamMember.userId")
|
||||
.leftJoin("PlusTier", "PlusTier.userId", "User.id")
|
||||
.leftJoin(
|
||||
"UserSubmittedImage",
|
||||
"UserSubmittedImage.id",
|
||||
"TournamentTeam.avatarImgId",
|
||||
)
|
||||
.select(({ fn, eb }) => [
|
||||
"TournamentLFGGroup.id",
|
||||
"TournamentLFGGroup.chatCode",
|
||||
"TournamentLFGGroup.tournamentTeamId",
|
||||
"TournamentLFGGroup.visibility",
|
||||
"TournamentTeam.id",
|
||||
"TournamentTeam.chatCode",
|
||||
"TournamentTeam.isPlaceholder",
|
||||
"TournamentTeam.lfgVisibility as visibility",
|
||||
"TournamentTeam.lfgNote as note",
|
||||
"TournamentTeam.name as teamName",
|
||||
concatUserSubmittedImagePrefix(eb.ref("UserSubmittedImage.url")).as(
|
||||
"teamAvatarUrl",
|
||||
@@ -131,9 +106,8 @@ export async function findGroupsByTournamentId(tournamentId: number) {
|
||||
languages: eb.ref("User.languages"),
|
||||
vc: eb.ref("User.vc"),
|
||||
pronouns: eb.ref("User.pronouns"),
|
||||
role: eb.ref("TournamentLFGGroupMember.role"),
|
||||
note: eb.ref("TournamentLFGGroupMember.note"),
|
||||
isStayAsSub: eb.ref("TournamentLFGGroupMember.isStayAsSub"),
|
||||
role: eb.ref("TournamentTeamMember.role"),
|
||||
isStayAsSub: eb.ref("TournamentTeamMember.isStayAsSub"),
|
||||
weapons: eb.ref("User.qWeaponPool"),
|
||||
chatNameColor: userChatNameColorForJson,
|
||||
plusTier: eb.ref("PlusTier.tier"),
|
||||
@@ -142,101 +116,74 @@ export async function findGroupsByTournamentId(tournamentId: number) {
|
||||
.$castTo<TournamentLFGMemberObject[]>()
|
||||
.as("members"),
|
||||
])
|
||||
.where("TournamentLFGGroup.tournamentId", "=", tournamentId)
|
||||
.groupBy("TournamentLFGGroup.id")
|
||||
.where("TournamentTeam.tournamentId", "=", tournamentId)
|
||||
.where("TournamentTeam.isLooking", "=", 1)
|
||||
.groupBy("TournamentTeam.id")
|
||||
.execute();
|
||||
}
|
||||
|
||||
export function morphGroups({
|
||||
survivingGroupId,
|
||||
otherGroupId,
|
||||
// xxx: ensure that the team size does not exceed the max, also filter out in frontend
|
||||
export function mergeTeams({
|
||||
survivingTeamId,
|
||||
otherTeamId,
|
||||
maxGroupSize,
|
||||
tournamentId,
|
||||
}: {
|
||||
survivingGroupId: number;
|
||||
otherGroupId: number;
|
||||
survivingTeamId: number;
|
||||
otherTeamId: number;
|
||||
maxGroupSize: number;
|
||||
tournamentId: number;
|
||||
}) {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
const survivingGroup = await trx
|
||||
.selectFrom("TournamentLFGGroup")
|
||||
.select("tournamentTeamId")
|
||||
.where("id", "=", survivingGroupId)
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
if (!survivingGroup.tournamentTeamId) {
|
||||
const createdTeam = await trx
|
||||
.insertInto("TournamentTeam")
|
||||
.values({
|
||||
tournamentId,
|
||||
name: `LFG Team ${shortNanoid()}`,
|
||||
inviteCode: "",
|
||||
})
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
await trx
|
||||
.updateTable("TournamentLFGGroup")
|
||||
.set({ tournamentTeamId: createdTeam.id })
|
||||
.where("id", "=", survivingGroupId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
await trx
|
||||
.updateTable("TournamentLFGGroup")
|
||||
.updateTable("TournamentTeam")
|
||||
.set({ chatCode: shortNanoid() })
|
||||
.where("TournamentLFGGroup.id", "=", survivingGroupId)
|
||||
.where("TournamentTeam.id", "=", survivingTeamId)
|
||||
.execute();
|
||||
|
||||
const otherGroupMembers = await trx
|
||||
.selectFrom("TournamentLFGGroupMember")
|
||||
.select([
|
||||
"TournamentLFGGroupMember.userId",
|
||||
"TournamentLFGGroupMember.role",
|
||||
])
|
||||
.where("TournamentLFGGroupMember.groupId", "=", otherGroupId)
|
||||
const otherMembers = await trx
|
||||
.selectFrom("TournamentTeamMember")
|
||||
.select(["TournamentTeamMember.userId", "TournamentTeamMember.role"])
|
||||
.where("TournamentTeamMember.tournamentTeamId", "=", otherTeamId)
|
||||
.execute();
|
||||
|
||||
for (const member of otherGroupMembers) {
|
||||
for (const member of otherMembers) {
|
||||
await trx
|
||||
.updateTable("TournamentLFGGroupMember")
|
||||
.updateTable("TournamentTeamMember")
|
||||
.set({
|
||||
role: member.role === "OWNER" ? "MANAGER" : member.role,
|
||||
groupId: survivingGroupId,
|
||||
tournamentTeamId: survivingTeamId,
|
||||
})
|
||||
.where("TournamentLFGGroupMember.groupId", "=", otherGroupId)
|
||||
.where("TournamentLFGGroupMember.userId", "=", member.userId)
|
||||
.where("TournamentTeamMember.tournamentTeamId", "=", otherTeamId)
|
||||
.where("TournamentTeamMember.userId", "=", member.userId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
await deleteLikesByGroupId(survivingGroupId, trx);
|
||||
await deleteLikesByTeamId(survivingTeamId, trx);
|
||||
|
||||
await trx
|
||||
.deleteFrom("TournamentLFGGroup")
|
||||
.where("TournamentLFGGroup.id", "=", otherGroupId)
|
||||
.deleteFrom("TournamentTeam")
|
||||
.where("TournamentTeam.id", "=", otherTeamId)
|
||||
.execute();
|
||||
|
||||
invariant(
|
||||
await isGroupCorrect(survivingGroupId, trx, maxGroupSize),
|
||||
await isTeamCorrect(survivingTeamId, trx, maxGroupSize),
|
||||
"Group has too many members after merge",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function addLike({
|
||||
likerGroupId,
|
||||
targetGroupId,
|
||||
likerTeamId,
|
||||
targetTeamId,
|
||||
}: {
|
||||
likerGroupId: number;
|
||||
targetGroupId: number;
|
||||
likerTeamId: number;
|
||||
targetTeamId: number;
|
||||
}) {
|
||||
try {
|
||||
await db
|
||||
.insertInto("TournamentLFGLike")
|
||||
.values({ likerGroupId, targetGroupId })
|
||||
.values({ likerTeamId, targetTeamId })
|
||||
.onConflict((oc) =>
|
||||
oc.columns(["likerGroupId", "targetGroupId"]).doNothing(),
|
||||
oc.columns(["likerTeamId", "targetTeamId"]).doNothing(),
|
||||
)
|
||||
.execute();
|
||||
} catch (error) {
|
||||
@@ -248,100 +195,94 @@ export async function addLike({
|
||||
}
|
||||
|
||||
export function deleteLike({
|
||||
likerGroupId,
|
||||
targetGroupId,
|
||||
likerTeamId,
|
||||
targetTeamId,
|
||||
}: {
|
||||
likerGroupId: number;
|
||||
targetGroupId: number;
|
||||
likerTeamId: number;
|
||||
targetTeamId: number;
|
||||
}) {
|
||||
return db
|
||||
.deleteFrom("TournamentLFGLike")
|
||||
.where("likerGroupId", "=", likerGroupId)
|
||||
.where("targetGroupId", "=", targetGroupId)
|
||||
.where("likerTeamId", "=", likerTeamId)
|
||||
.where("targetTeamId", "=", targetTeamId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function allLikesByGroupId(groupId: number) {
|
||||
export async function allLikesByTeamId(teamId: number) {
|
||||
const rows = await db
|
||||
.selectFrom("TournamentLFGLike")
|
||||
.select([
|
||||
"TournamentLFGLike.likerGroupId",
|
||||
"TournamentLFGLike.targetGroupId",
|
||||
])
|
||||
.select(["TournamentLFGLike.likerTeamId", "TournamentLFGLike.targetTeamId"])
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb("TournamentLFGLike.likerGroupId", "=", groupId),
|
||||
eb("TournamentLFGLike.targetGroupId", "=", groupId),
|
||||
eb("TournamentLFGLike.likerTeamId", "=", teamId),
|
||||
eb("TournamentLFGLike.targetTeamId", "=", teamId),
|
||||
]),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
given: rows
|
||||
.filter((row) => row.likerGroupId === groupId)
|
||||
.map((row) => ({ groupId: row.targetGroupId })),
|
||||
.filter((row) => row.likerTeamId === teamId)
|
||||
.map((row) => ({ teamId: row.targetTeamId })),
|
||||
received: rows
|
||||
.filter((row) => row.targetGroupId === groupId)
|
||||
.map((row) => ({ groupId: row.likerGroupId })),
|
||||
.filter((row) => row.targetTeamId === teamId)
|
||||
.map((row) => ({ teamId: row.likerTeamId })),
|
||||
};
|
||||
}
|
||||
|
||||
export function updateMemberNote({
|
||||
groupId,
|
||||
userId,
|
||||
export function updateTeamNote({
|
||||
teamId,
|
||||
value,
|
||||
}: {
|
||||
groupId: number;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
value: string | null;
|
||||
}) {
|
||||
return db
|
||||
.updateTable("TournamentLFGGroupMember")
|
||||
.set({ note: value })
|
||||
.where("groupId", "=", groupId)
|
||||
.where("userId", "=", userId)
|
||||
.updateTable("TournamentTeam")
|
||||
.set({ lfgNote: value })
|
||||
.where("id", "=", teamId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export function updateMemberRole({
|
||||
userId,
|
||||
groupId,
|
||||
teamId,
|
||||
role,
|
||||
}: {
|
||||
userId: number;
|
||||
groupId: number;
|
||||
role: Tables["TournamentLFGGroupMember"]["role"];
|
||||
teamId: number;
|
||||
role: Tables["TournamentTeamMember"]["role"];
|
||||
}) {
|
||||
if (role === "OWNER") {
|
||||
throw new Error("Can't set role to OWNER with this function");
|
||||
}
|
||||
|
||||
return db
|
||||
.updateTable("TournamentLFGGroupMember")
|
||||
.updateTable("TournamentTeamMember")
|
||||
.set({ role })
|
||||
.where("userId", "=", userId)
|
||||
.where("groupId", "=", groupId)
|
||||
.where("tournamentTeamId", "=", teamId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export function updateStayAsSub({
|
||||
groupId,
|
||||
teamId,
|
||||
userId,
|
||||
value,
|
||||
}: {
|
||||
groupId: number;
|
||||
teamId: number;
|
||||
userId: number;
|
||||
value: boolean;
|
||||
}) {
|
||||
return db
|
||||
.updateTable("TournamentLFGGroupMember")
|
||||
.updateTable("TournamentTeamMember")
|
||||
.set({ isStayAsSub: value ? 1 : 0 })
|
||||
.where("groupId", "=", groupId)
|
||||
.where("tournamentTeamId", "=", teamId)
|
||||
.where("userId", "=", userId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export function leaveGroup({
|
||||
export function leaveLfg({
|
||||
userId,
|
||||
tournamentId,
|
||||
}: {
|
||||
@@ -349,90 +290,106 @@ export function leaveGroup({
|
||||
tournamentId: number;
|
||||
}) {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
const userGroup = await trx
|
||||
.selectFrom("TournamentLFGGroupMember")
|
||||
const userTeam = await trx
|
||||
.selectFrom("TournamentTeamMember")
|
||||
.innerJoin(
|
||||
"TournamentTeam",
|
||||
"TournamentTeam.id",
|
||||
"TournamentTeamMember.tournamentTeamId",
|
||||
)
|
||||
.select([
|
||||
"TournamentLFGGroupMember.groupId",
|
||||
"TournamentLFGGroupMember.role",
|
||||
"TournamentTeamMember.tournamentTeamId",
|
||||
"TournamentTeamMember.role",
|
||||
"TournamentTeam.isPlaceholder",
|
||||
])
|
||||
.where("TournamentLFGGroupMember.userId", "=", userId)
|
||||
.where("TournamentLFGGroupMember.tournamentId", "=", tournamentId)
|
||||
.where("TournamentTeamMember.userId", "=", userId)
|
||||
.where("TournamentTeam.tournamentId", "=", tournamentId)
|
||||
.where("TournamentTeam.isLooking", "=", 1)
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
await trx
|
||||
.deleteFrom("TournamentLFGGroupMember")
|
||||
.where("userId", "=", userId)
|
||||
.where("groupId", "=", userGroup.groupId)
|
||||
.execute();
|
||||
|
||||
const remainingMembers = await trx
|
||||
.selectFrom("TournamentLFGGroupMember")
|
||||
.select(["userId", "role"])
|
||||
.where("groupId", "=", userGroup.groupId)
|
||||
.execute();
|
||||
|
||||
if (remainingMembers.length === 0) {
|
||||
if (!userTeam.isPlaceholder) {
|
||||
await trx
|
||||
.deleteFrom("TournamentLFGGroup")
|
||||
.where("id", "=", userGroup.groupId)
|
||||
.updateTable("TournamentTeam")
|
||||
.set({ isLooking: 0 })
|
||||
.where("id", "=", userTeam.tournamentTeamId)
|
||||
.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
if (userGroup.role === "OWNER") {
|
||||
// xxx: or just call TournamentTeam.unregister?
|
||||
// xxx: also leaving team should not be possible via this so remove that code
|
||||
await trx
|
||||
.deleteFrom("TournamentTeamMember")
|
||||
.where("userId", "=", userId)
|
||||
.where("tournamentTeamId", "=", userTeam.tournamentTeamId)
|
||||
.execute();
|
||||
|
||||
const remainingMembers = await trx
|
||||
.selectFrom("TournamentTeamMember")
|
||||
.select(["userId", "role"])
|
||||
.where("tournamentTeamId", "=", userTeam.tournamentTeamId)
|
||||
.execute();
|
||||
|
||||
if (remainingMembers.length === 0) {
|
||||
await trx
|
||||
.deleteFrom("TournamentTeam")
|
||||
.where("id", "=", userTeam.tournamentTeamId)
|
||||
.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
if (userTeam.role === "OWNER") {
|
||||
const newOwner =
|
||||
remainingMembers.find((m) => m.role === "MANAGER") ??
|
||||
remainingMembers[0];
|
||||
|
||||
await trx
|
||||
.updateTable("TournamentLFGGroupMember")
|
||||
.updateTable("TournamentTeamMember")
|
||||
.set({ role: "OWNER" })
|
||||
.where("userId", "=", newOwner.userId)
|
||||
.where("groupId", "=", userGroup.groupId)
|
||||
.where("tournamentTeamId", "=", userTeam.tournamentTeamId)
|
||||
.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function cleanupForTournamentStart(tournamentId: number) {
|
||||
return db
|
||||
.deleteFrom("TournamentLFGGroup")
|
||||
.where("TournamentLFGGroup.tournamentId", "=", tournamentId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function getSubsForTournament(tournamentId: number) {
|
||||
const rows = await db
|
||||
.selectFrom("TournamentLFGGroupMember")
|
||||
.select("TournamentLFGGroupMember.userId")
|
||||
.where("TournamentLFGGroupMember.tournamentId", "=", tournamentId)
|
||||
.where("TournamentLFGGroupMember.isStayAsSub", "=", 1)
|
||||
.selectFrom("TournamentTeamMember")
|
||||
.innerJoin(
|
||||
"TournamentTeam",
|
||||
"TournamentTeam.id",
|
||||
"TournamentTeamMember.tournamentTeamId",
|
||||
)
|
||||
.select("TournamentTeamMember.userId")
|
||||
.where("TournamentTeam.tournamentId", "=", tournamentId)
|
||||
.where("TournamentTeamMember.isStayAsSub", "=", 1)
|
||||
.execute();
|
||||
|
||||
return rows.map((row) => row.userId);
|
||||
}
|
||||
|
||||
function deleteLikesByGroupId(groupId: number, trx: Transaction<DB>) {
|
||||
function deleteLikesByTeamId(teamId: number, trx: Transaction<DB>) {
|
||||
return trx
|
||||
.deleteFrom("TournamentLFGLike")
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb("TournamentLFGLike.likerGroupId", "=", groupId),
|
||||
eb("TournamentLFGLike.targetGroupId", "=", groupId),
|
||||
eb("TournamentLFGLike.likerTeamId", "=", teamId),
|
||||
eb("TournamentLFGLike.targetTeamId", "=", teamId),
|
||||
]),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
async function isGroupCorrect(
|
||||
groupId: number,
|
||||
async function isTeamCorrect(
|
||||
teamId: number,
|
||||
trx: Transaction<DB>,
|
||||
maxGroupSize: number,
|
||||
): Promise<boolean> {
|
||||
const members = await trx
|
||||
.selectFrom("TournamentLFGGroupMember")
|
||||
.select("TournamentLFGGroupMember.userId")
|
||||
.where("TournamentLFGGroupMember.groupId", "=", groupId)
|
||||
.selectFrom("TournamentTeamMember")
|
||||
.select("TournamentTeamMember.userId")
|
||||
.where("TournamentTeamMember.tournamentTeamId", "=", teamId)
|
||||
.execute();
|
||||
|
||||
return members.length <= maxGroupSize;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { assertUnreachable } from "~/utils/types";
|
||||
import { idObject } from "~/utils/zod";
|
||||
import * as TournamentLFGRepository from "../TournamentLFGRepository.server";
|
||||
import { lookingSchema } from "../tournament-lfg-schemas.server";
|
||||
import { survivingGroupId } from "../tournament-lfg-utils";
|
||||
import { survivingTeamId } from "../tournament-lfg-utils";
|
||||
|
||||
// xxx: prevent actions in certain cases? like when tournament has started
|
||||
export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
@@ -23,7 +23,9 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
|
||||
const findOwnGroup = async () => {
|
||||
const groups =
|
||||
await TournamentLFGRepository.findGroupsByTournamentId(tournamentId);
|
||||
await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournamentId,
|
||||
);
|
||||
return groups.find((g) => g.members.some((m) => m.id === user.id));
|
||||
};
|
||||
|
||||
@@ -49,28 +51,9 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
const team = tournament.teamMemberOfByUser(user);
|
||||
|
||||
if (team) {
|
||||
const groups =
|
||||
await TournamentLFGRepository.findGroupsByTournamentId(tournamentId);
|
||||
const teamAlreadyLinked = groups.some(
|
||||
(g) => g.tournamentTeamId === team.id,
|
||||
);
|
||||
if (teamAlreadyLinked) return null;
|
||||
|
||||
const memberAlreadyInGroup = team.members.some((m) =>
|
||||
groups.some((g) => g.members.some((gm) => gm.id === m.userId)),
|
||||
);
|
||||
if (memberAlreadyInGroup) return null;
|
||||
|
||||
await TournamentLFGRepository.createGroupFromTeam({
|
||||
tournamentId,
|
||||
tournamentTeamId: team.id,
|
||||
members: team.members.map((m) => ({
|
||||
userId: m.userId,
|
||||
isOwner: Boolean(m.isOwner),
|
||||
})),
|
||||
});
|
||||
await TournamentLFGRepository.startLooking(team.id);
|
||||
} else {
|
||||
await TournamentLFGRepository.createGroup({
|
||||
await TournamentLFGRepository.createPlaceholderTeam({
|
||||
tournamentId,
|
||||
userId: user.id,
|
||||
isStayAsSub: data.stayAsSub ?? false,
|
||||
@@ -84,8 +67,8 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
if (!ownGroup || !isGroupManager(ownGroup)) return null;
|
||||
|
||||
await TournamentLFGRepository.addLike({
|
||||
likerGroupId: ownGroup.id,
|
||||
targetGroupId: data.targetGroupId,
|
||||
likerTeamId: ownGroup.id,
|
||||
targetTeamId: data.targetTeamId,
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -95,44 +78,48 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
if (!ownGroup || !isGroupManager(ownGroup)) return null;
|
||||
|
||||
await TournamentLFGRepository.deleteLike({
|
||||
likerGroupId: ownGroup.id,
|
||||
targetGroupId: data.targetGroupId,
|
||||
likerTeamId: ownGroup.id,
|
||||
targetTeamId: data.targetTeamId,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case "ACCEPT": {
|
||||
const groups =
|
||||
await TournamentLFGRepository.findGroupsByTournamentId(tournamentId);
|
||||
await TournamentLFGRepository.findLookingTeamsByTournamentId(
|
||||
tournamentId,
|
||||
);
|
||||
const ownGroup = groups.find((g) =>
|
||||
g.members.some((m) => m.id === user.id),
|
||||
);
|
||||
if (!ownGroup || !isGroupManager(ownGroup)) return null;
|
||||
|
||||
const theirGroup = groups.find((g) => g.id === data.targetGroupId);
|
||||
const theirGroup = groups.find((g) => g.id === data.targetTeamId);
|
||||
if (!theirGroup) return null;
|
||||
|
||||
const theirLikes = await TournamentLFGRepository.allLikesByGroupId(
|
||||
data.targetGroupId,
|
||||
const theirLikes = await TournamentLFGRepository.allLikesByTeamId(
|
||||
data.targetTeamId,
|
||||
);
|
||||
if (!theirLikes.given.some((like) => like.groupId === ownGroup.id)) {
|
||||
if (!theirLikes.given.some((like) => like.teamId === ownGroup.id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const surviving = survivingGroupId({
|
||||
const surviving = survivingTeamId({
|
||||
ourGroup: {
|
||||
id: ownGroup.id,
|
||||
tournamentTeamId: ownGroup.tournamentTeamId,
|
||||
isPlaceholder: Boolean(ownGroup.isPlaceholder),
|
||||
teamName: null,
|
||||
teamAvatarUrl: null,
|
||||
note: null,
|
||||
members: [],
|
||||
usersRole: null,
|
||||
},
|
||||
theirGroup: {
|
||||
id: theirGroup.id,
|
||||
tournamentTeamId: theirGroup.tournamentTeamId,
|
||||
isPlaceholder: Boolean(theirGroup.isPlaceholder),
|
||||
teamName: null,
|
||||
teamAvatarUrl: null,
|
||||
note: null,
|
||||
members: [],
|
||||
usersRole: null,
|
||||
},
|
||||
@@ -145,11 +132,10 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
user,
|
||||
});
|
||||
|
||||
await TournamentLFGRepository.morphGroups({
|
||||
survivingGroupId: surviving,
|
||||
otherGroupId: otherGroup.id,
|
||||
await TournamentLFGRepository.mergeTeams({
|
||||
survivingTeamId: surviving,
|
||||
otherTeamId: otherGroup.id,
|
||||
maxGroupSize: tournament.maxMembersPerTeam,
|
||||
tournamentId,
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -159,7 +145,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
errorToastIfFalsy(ownGroup && isGroupOwner(ownGroup), "Not owner");
|
||||
|
||||
await TournamentLFGRepository.updateMemberRole({
|
||||
groupId: ownGroup!.id,
|
||||
teamId: ownGroup!.id,
|
||||
userId: data.userId,
|
||||
role: "MANAGER",
|
||||
});
|
||||
@@ -171,7 +157,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
errorToastIfFalsy(ownGroup && isGroupOwner(ownGroup), "Not owner");
|
||||
|
||||
await TournamentLFGRepository.updateMemberRole({
|
||||
groupId: ownGroup!.id,
|
||||
teamId: ownGroup!.id,
|
||||
userId: data.userId,
|
||||
role: "REGULAR",
|
||||
});
|
||||
@@ -182,9 +168,8 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
const ownGroup = await findOwnGroup();
|
||||
if (!ownGroup) return null;
|
||||
|
||||
await TournamentLFGRepository.updateMemberNote({
|
||||
groupId: ownGroup.id,
|
||||
userId: user.id,
|
||||
await TournamentLFGRepository.updateTeamNote({
|
||||
teamId: ownGroup.id,
|
||||
value: data.value,
|
||||
});
|
||||
|
||||
@@ -195,7 +180,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
if (!ownGroup) return null;
|
||||
|
||||
await TournamentLFGRepository.updateStayAsSub({
|
||||
groupId: ownGroup.id,
|
||||
teamId: ownGroup.id,
|
||||
userId: user.id,
|
||||
value: data.value,
|
||||
});
|
||||
@@ -203,7 +188,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
break;
|
||||
}
|
||||
case "LEAVE_GROUP": {
|
||||
await TournamentLFGRepository.leaveGroup({
|
||||
await TournamentLFGRepository.leaveLfg({
|
||||
userId: user.id,
|
||||
tournamentId,
|
||||
});
|
||||
|
||||
@@ -31,7 +31,6 @@ export type LFGGroupMember = {
|
||||
vc: "YES" | "NO" | "LISTEN_ONLY" | null;
|
||||
pronouns: Pronouns | null;
|
||||
role: "OWNER" | "MANAGER" | "REGULAR";
|
||||
note: string | null;
|
||||
isStayAsSub: boolean;
|
||||
weapons: Array<{ weaponSplId: MainWeaponId; isFavorite: boolean }> | null;
|
||||
chatNameColor: string | null;
|
||||
@@ -40,13 +39,15 @@ export type LFGGroupMember = {
|
||||
|
||||
export type LFGGroup = {
|
||||
id: number;
|
||||
tournamentTeamId: number | null;
|
||||
isPlaceholder: boolean;
|
||||
teamName: string | null;
|
||||
teamAvatarUrl: string | null;
|
||||
note: string | null;
|
||||
members: LFGGroupMember[];
|
||||
usersRole: "OWNER" | "MANAGER" | "REGULAR" | null;
|
||||
};
|
||||
|
||||
// xxx: make sure we don't show team name if isPlaceholder
|
||||
// xxx: "cancel" & "save" buttons off
|
||||
export function LFGGroupCard({
|
||||
group,
|
||||
@@ -65,14 +66,6 @@ export function LFGGroupCard({
|
||||
|
||||
return (
|
||||
<section className={styles.group}>
|
||||
{/* {group.teamName ? (
|
||||
<div className={styles.teamHeader}>
|
||||
{group.teamAvatarUrl ? (
|
||||
<Avatar size="xxs" url={group.teamAvatarUrl} />
|
||||
) : null}
|
||||
<div className={styles.teamName}>{group.teamName}</div>
|
||||
</div>
|
||||
) : null} */}
|
||||
{group.teamName ? (
|
||||
<Divider smallText className={styles.teamHeader}>
|
||||
{group.teamAvatarUrl ? (
|
||||
@@ -91,10 +84,13 @@ export function LFGGroupCard({
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{isOwnGroup ? (
|
||||
<LFGTeamNote note={group.note} editable={group.usersRole === "OWNER"} />
|
||||
) : null}
|
||||
{action &&
|
||||
(ownGroup?.usersRole === "OWNER" || ownGroup?.usersRole === "MANAGER") ? (
|
||||
<fetcher.Form className="stack items-center" method="post">
|
||||
<input type="hidden" name="targetGroupId" value={group.id} />
|
||||
<input type="hidden" name="targetTeamId" value={group.id} />
|
||||
<SubmitButton
|
||||
size="small"
|
||||
variant={action === "UNLIKE" ? "destructive" : "outlined"}
|
||||
@@ -116,14 +112,11 @@ export function LFGGroupCard({
|
||||
function LFGGroupMemberRow({
|
||||
member,
|
||||
showActions,
|
||||
isOwnGroup,
|
||||
}: {
|
||||
member: LFGGroupMember;
|
||||
showActions: boolean;
|
||||
isOwnGroup: boolean;
|
||||
}) {
|
||||
const user = useUser();
|
||||
|
||||
return (
|
||||
<div className="stack xxs">
|
||||
<div className={styles.member}>
|
||||
@@ -171,14 +164,11 @@ function LFGGroupMemberRow({
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{isOwnGroup ? (
|
||||
<LFGMemberNote note={member.note} editable={user?.id === member.id} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LFGMemberNote({
|
||||
function LFGTeamNote({
|
||||
note,
|
||||
editable,
|
||||
}: {
|
||||
|
||||
@@ -13,16 +13,17 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
||||
const { id: tournamentId } = parseParams({ params, schema: idObject });
|
||||
|
||||
const rawGroups =
|
||||
await TournamentLFGRepository.findGroupsByTournamentId(tournamentId);
|
||||
await TournamentLFGRepository.findLookingTeamsByTournamentId(tournamentId);
|
||||
|
||||
const groups: LFGGroup[] = rawGroups.map((group) => {
|
||||
const members = transformMembers(group.members);
|
||||
|
||||
return {
|
||||
id: group.id,
|
||||
tournamentTeamId: group.tournamentTeamId,
|
||||
isPlaceholder: Boolean(group.isPlaceholder),
|
||||
teamName: group.teamName ?? null,
|
||||
teamAvatarUrl: group.teamAvatarUrl ?? null,
|
||||
note: group.note ?? null,
|
||||
members,
|
||||
usersRole: members.find((m) => m.id === user?.id)?.role ?? null,
|
||||
};
|
||||
@@ -34,14 +35,13 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
||||
const otherGroups = groups.filter((g) => g.id !== ownGroup?.id);
|
||||
|
||||
const likes = ownGroup
|
||||
? await TournamentLFGRepository.allLikesByGroupId(ownGroup.id)
|
||||
? await TournamentLFGRepository.allLikesByTeamId(ownGroup.id)
|
||||
: { given: [], received: [] };
|
||||
|
||||
const ownTeam = await resolveOwnTeam({
|
||||
user,
|
||||
tournamentId,
|
||||
ownGroup,
|
||||
groups,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -58,12 +58,10 @@ async function resolveOwnTeam({
|
||||
user,
|
||||
tournamentId,
|
||||
ownGroup,
|
||||
groups,
|
||||
}: {
|
||||
user: ReturnType<typeof getUser>;
|
||||
tournamentId: number;
|
||||
ownGroup: LFGGroup | null;
|
||||
groups: LFGGroup[];
|
||||
}): Promise<LFGGroup | null> {
|
||||
if (!user) return null;
|
||||
if (ownGroup) return null;
|
||||
@@ -76,9 +74,6 @@ async function resolveOwnTeam({
|
||||
const team = tournament.teamMemberOfByUser(user);
|
||||
if (!team) return null;
|
||||
|
||||
const teamAlreadyLinked = groups.some((g) => g.tournamentTeamId === team.id);
|
||||
if (teamAlreadyLinked) return null;
|
||||
|
||||
const members: LFGGroupMember[] = team.members.map((m) => ({
|
||||
id: m.userId,
|
||||
username: m.username,
|
||||
@@ -89,7 +84,6 @@ async function resolveOwnTeam({
|
||||
vc: null,
|
||||
pronouns: null,
|
||||
role: m.isOwner ? "OWNER" : "REGULAR",
|
||||
note: null,
|
||||
isStayAsSub: false,
|
||||
weapons: null,
|
||||
chatNameColor: null,
|
||||
@@ -97,10 +91,11 @@ async function resolveOwnTeam({
|
||||
}));
|
||||
|
||||
return {
|
||||
id: -1,
|
||||
tournamentTeamId: team.id,
|
||||
id: team.id,
|
||||
isPlaceholder: false,
|
||||
teamName: team.name,
|
||||
teamAvatarUrl: team.pickupAvatarUrl,
|
||||
note: null,
|
||||
members,
|
||||
usersRole: members.find((m) => m.id === user.id)?.role ?? null,
|
||||
};
|
||||
@@ -108,7 +103,7 @@ async function resolveOwnTeam({
|
||||
|
||||
function transformMembers(
|
||||
rawMembers: Awaited<
|
||||
ReturnType<typeof TournamentLFGRepository.findGroupsByTournamentId>
|
||||
ReturnType<typeof TournamentLFGRepository.findLookingTeamsByTournamentId>
|
||||
>[number]["members"],
|
||||
): LFGGroupMember[] {
|
||||
return rawMembers.map((m) => {
|
||||
@@ -130,7 +125,6 @@ function transformMembers(
|
||||
vc: m.vc,
|
||||
pronouns,
|
||||
role: m.role,
|
||||
note: m.note,
|
||||
isStayAsSub: m.isStayAsSub === 1,
|
||||
weapons,
|
||||
chatNameColor: m.chatNameColor,
|
||||
|
||||
@@ -58,17 +58,17 @@ function Groups() {
|
||||
|
||||
const neutralGroups = data.groups.filter(
|
||||
(group) =>
|
||||
!data.likes.given.some((like) => like.groupId === group.id) &&
|
||||
!data.likes.received.some((like) => like.groupId === group.id),
|
||||
!data.likes.given.some((like) => like.teamId === group.id) &&
|
||||
!data.likes.received.some((like) => like.teamId === group.id),
|
||||
);
|
||||
const likedGroups = data.groups.filter((group) =>
|
||||
data.likes.given.some((like) => like.groupId === group.id),
|
||||
data.likes.given.some((like) => like.teamId === group.id),
|
||||
);
|
||||
const groupsReceivedLikesFrom = data.groups.filter((group) =>
|
||||
data.likes.received.some((like) => like.groupId === group.id),
|
||||
data.likes.received.some((like) => like.teamId === group.id),
|
||||
);
|
||||
|
||||
const flipKey = `${neutralGroups.map((g) => `${g.id}-${isMobile ? true : data.likes.given.some((l) => l.groupId === g.id)}`).join(":")}:${groupsReceivedLikesFrom.map((g) => g.id).join(":")}`;
|
||||
const flipKey = `${neutralGroups.map((g) => `${g.id}-${isMobile ? true : data.likes.given.some((l) => l.teamId === g.id)}`).join(":")}:${groupsReceivedLikesFrom.map((g) => g.id).join(":")}`;
|
||||
|
||||
const invitedGroupsDesktop = (
|
||||
<div className="stack sm">
|
||||
@@ -157,7 +157,7 @@ function Groups() {
|
||||
? data.groups.filter(
|
||||
(group) =>
|
||||
!data.likes.received.some(
|
||||
(like) => like.groupId === group.id,
|
||||
(like) => like.teamId === group.id,
|
||||
),
|
||||
)
|
||||
: neutralGroups
|
||||
@@ -166,7 +166,7 @@ function Groups() {
|
||||
key={group.id}
|
||||
group={group}
|
||||
action={
|
||||
data.likes.given.some((like) => like.groupId === group.id)
|
||||
data.likes.given.some((like) => like.teamId === group.id)
|
||||
? "UNLIKE"
|
||||
: "LIKE"
|
||||
}
|
||||
|
||||
@@ -7,15 +7,15 @@ export const lookingSchema = z.union([
|
||||
joinQueueFormSchema,
|
||||
z.object({
|
||||
_action: _action("LIKE"),
|
||||
targetGroupId: id,
|
||||
targetTeamId: id,
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("UNLIKE"),
|
||||
targetGroupId: id,
|
||||
targetTeamId: id,
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("ACCEPT"),
|
||||
targetGroupId: id,
|
||||
targetTeamId: id,
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("GIVE_MANAGER"),
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import type { LFGGroup } from "./components/LFGGroupCard";
|
||||
|
||||
export function findOwnGroup(groups: LFGGroup[], userId: number) {
|
||||
return groups.find((g) => g.members.some((m) => m.id === userId)) ?? null;
|
||||
}
|
||||
|
||||
export function survivingGroupId({
|
||||
export function survivingTeamId({
|
||||
ourGroup,
|
||||
theirGroup,
|
||||
}: {
|
||||
ourGroup: LFGGroup;
|
||||
theirGroup: LFGGroup;
|
||||
}) {
|
||||
if (ourGroup.tournamentTeamId && !theirGroup.tournamentTeamId) {
|
||||
if (!ourGroup.isPlaceholder && theirGroup.isPlaceholder) {
|
||||
return ourGroup.id;
|
||||
}
|
||||
if (!ourGroup.tournamentTeamId && theirGroup.tournamentTeamId) {
|
||||
if (ourGroup.isPlaceholder && !theirGroup.isPlaceholder) {
|
||||
return theirGroup.id;
|
||||
}
|
||||
|
||||
|
||||
@@ -251,6 +251,7 @@ export async function findById(id: number) {
|
||||
).as("team"),
|
||||
])
|
||||
.where("TournamentTeam.tournamentId", "=", id)
|
||||
.where("TournamentTeam.isPlaceholder", "=", 0)
|
||||
.orderBy("TournamentTeam.seed", "asc")
|
||||
.orderBy("TournamentTeam.createdAt", "asc")
|
||||
.orderBy("TournamentTeam.id", "asc"),
|
||||
@@ -484,6 +485,7 @@ export function forShowcase() {
|
||||
),
|
||||
)
|
||||
.whereRef("TournamentTeam.tournamentId", "=", "Tournament.id")
|
||||
.where("TournamentTeam.isPlaceholder", "=", 0)
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb("TournamentTeamCheckIn.checkedInAt", "is not", null),
|
||||
|
||||
@@ -69,6 +69,8 @@ import {
|
||||
import { useTournament } from "./to.$id";
|
||||
export { loader, action };
|
||||
|
||||
// xxx: handle placeholder team made
|
||||
// xxx: allow promoting members to managers
|
||||
export default function TournamentRegisterPage() {
|
||||
const isMounted = useIsMounted();
|
||||
const tournament = useTournament();
|
||||
|
||||
2037
app/utils/team-name-data.ts
Normal file
2037
app/utils/team-name-data.ts
Normal file
File diff suppressed because it is too large
Load Diff
12
app/utils/team-name.ts
Normal file
12
app/utils/team-name.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ADJECTIVES, SUBTITLES_PLURALIZED } from "./team-name-data";
|
||||
|
||||
/** Generates a random team name by combining a Splatoon 3 title adjective with a pluralized subtitle e.g. "Prestigious Heat Haters" */
|
||||
export function randomTeamName() {
|
||||
const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
|
||||
const subtitle =
|
||||
SUBTITLES_PLURALIZED[
|
||||
Math.floor(Math.random() * SUBTITLES_PLURALIZED.length)
|
||||
];
|
||||
|
||||
return `${adjective} ${subtitle}`;
|
||||
}
|
||||
BIN
db-test.sqlite3
BIN
db-test.sqlite3
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
// xxx: update tournament lfg stuff etc.
|
||||
|
||||
Note: some simple features omitted with only a few relations and no special notes
|
||||
|
||||
See `tables.ts` for some more documentation on column-level.
|
||||
|
||||
@@ -1,69 +1,51 @@
|
||||
export function up(db) {
|
||||
db.transaction(() => {
|
||||
db.prepare(
|
||||
/*sql*/ `
|
||||
create table "TournamentLFGGroup" (
|
||||
"id" integer primary key,
|
||||
"tournamentId" integer not null,
|
||||
"tournamentTeamId" integer,
|
||||
"visibility" text,
|
||||
"chatCode" text not null,
|
||||
"createdAt" integer default (strftime('%s', 'now')) not null,
|
||||
foreign key ("tournamentId") references "Tournament"("id") on delete cascade,
|
||||
foreign key ("tournamentTeamId") references "TournamentTeam"("id") on delete cascade
|
||||
) strict
|
||||
`,
|
||||
/*sql*/ `alter table "TournamentTeam" add column "isLooking" integer not null default 0`,
|
||||
).run();
|
||||
// xxx: index on isLooking too to get efficient queries? problem -> we query tournamentTeamMember.userId (different table)
|
||||
db.prepare(
|
||||
/*sql*/ `alter table "TournamentTeam" add column "isPlaceholder" integer not null default 0`,
|
||||
).run();
|
||||
db.prepare(
|
||||
/*sql*/ `alter table "TournamentTeam" add column "lfgVisibility" text`,
|
||||
).run();
|
||||
db.prepare(
|
||||
/*sql*/ `alter table "TournamentTeam" add column "lfgNote" text`,
|
||||
).run();
|
||||
db.prepare(
|
||||
/*sql*/ `alter table "TournamentTeam" add column "chatCode" text`,
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
/*sql*/ `create index tournament_lfg_group_tournament_id on "TournamentLFGGroup"("tournamentId")`,
|
||||
/*sql*/ `alter table "TournamentTeamMember" add column "role" text not null default 'REGULAR'`,
|
||||
).run();
|
||||
db.prepare(
|
||||
/*sql*/ `create index tournament_lfg_group_tournament_team_id on "TournamentLFGGroup"("tournamentTeamId")`,
|
||||
/*sql*/ `alter table "TournamentTeamMember" add column "isStayAsSub" integer not null default 0`,
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
/*sql*/ `
|
||||
create table "TournamentLFGGroupMember" (
|
||||
"groupId" integer not null,
|
||||
"tournamentId" integer not null,
|
||||
"userId" integer not null,
|
||||
"role" text not null,
|
||||
"note" text,
|
||||
"isStayAsSub" integer default 0 not null,
|
||||
"createdAt" integer default (strftime('%s', 'now')) not null,
|
||||
foreign key ("groupId") references "TournamentLFGGroup"("id") on delete cascade,
|
||||
foreign key ("userId") references "User"("id") on delete cascade,
|
||||
unique("tournamentId", "userId") on conflict rollback
|
||||
) strict
|
||||
`,
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
/*sql*/ `create index tournament_lfg_group_member_group_id on "TournamentLFGGroupMember"("groupId")`,
|
||||
).run();
|
||||
db.prepare(
|
||||
/*sql*/ `create index tournament_lfg_group_member_user_id on "TournamentLFGGroupMember"("userId")`,
|
||||
/*sql*/ `update "TournamentTeamMember" set "role" = 'OWNER' where "isOwner" = 1`,
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
/*sql*/ `
|
||||
create table "TournamentLFGLike" (
|
||||
"likerGroupId" integer not null,
|
||||
"targetGroupId" integer not null,
|
||||
"likerTeamId" integer not null,
|
||||
"targetTeamId" integer not null,
|
||||
"createdAt" integer default (strftime('%s', 'now')) not null,
|
||||
foreign key ("likerGroupId") references "TournamentLFGGroup"("id") on delete cascade,
|
||||
foreign key ("targetGroupId") references "TournamentLFGGroup"("id") on delete cascade,
|
||||
unique("likerGroupId", "targetGroupId") on conflict rollback
|
||||
foreign key ("likerTeamId") references "TournamentTeam"("id") on delete cascade,
|
||||
foreign key ("targetTeamId") references "TournamentTeam"("id") on delete cascade,
|
||||
unique("likerTeamId", "targetTeamId") on conflict rollback
|
||||
) strict
|
||||
`,
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
/*sql*/ `create index tournament_lfg_like_liker_group_id on "TournamentLFGLike"("likerGroupId")`,
|
||||
/*sql*/ `create index tournament_lfg_like_liker_team_id on "TournamentLFGLike"("likerTeamId")`,
|
||||
).run();
|
||||
db.prepare(
|
||||
/*sql*/ `create index tournament_lfg_like_target_group_id on "TournamentLFGLike"("targetGroupId")`,
|
||||
/*sql*/ `create index tournament_lfg_like_target_team_id on "TournamentLFGLike"("targetTeamId")`,
|
||||
).run();
|
||||
})();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user