mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-11 21:56:11 -05:00
Optimize queries, fix isLooking tournament teams stale bug
This commit is contained in:
@@ -70,31 +70,45 @@ function withLfgJoins<QB extends SelectQueryBuilder<any, any, any>>(qb: QB) {
|
||||
.leftJoin("TournamentTeamMember", (join) =>
|
||||
join
|
||||
.onRef("TournamentTeamMember.userId", "=", "User.id")
|
||||
.on("TournamentTeamMember.isLooking", "=", 1),
|
||||
.on("TournamentTeamMember.isLooking", "=", 1)
|
||||
.on((eb) =>
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom("TournamentTeam as LookingTeam")
|
||||
.innerJoin(
|
||||
"Tournament as LookingTournament",
|
||||
"LookingTournament.id",
|
||||
"LookingTeam.tournamentId",
|
||||
)
|
||||
.select("LookingTeam.id")
|
||||
.whereRef(
|
||||
"LookingTeam.id",
|
||||
"=",
|
||||
"TournamentTeamMember.tournamentTeamId",
|
||||
)
|
||||
.where((innerEb) =>
|
||||
innerEb.or([
|
||||
innerEb(
|
||||
sql`json_extract("LookingTournament"."settings", '$.regClosesAt')`,
|
||||
"is",
|
||||
null,
|
||||
),
|
||||
innerEb(
|
||||
sql<number>`json_extract("LookingTournament"."settings", '$.regClosesAt')`,
|
||||
">",
|
||||
nowTimestamp,
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.leftJoin(
|
||||
"TournamentTeam",
|
||||
"TournamentTeam.id",
|
||||
"TournamentTeamMember.tournamentTeamId",
|
||||
)
|
||||
.leftJoin("Tournament", (join) =>
|
||||
join
|
||||
.onRef("Tournament.id", "=", "TournamentTeam.tournamentId")
|
||||
.on((eb) =>
|
||||
eb.or([
|
||||
eb(
|
||||
sql`json_extract("Tournament"."settings", '$.regClosesAt')`,
|
||||
"is",
|
||||
null,
|
||||
),
|
||||
eb(
|
||||
sql<number>`json_extract("Tournament"."settings", '$.regClosesAt')`,
|
||||
">",
|
||||
nowTimestamp,
|
||||
),
|
||||
]),
|
||||
),
|
||||
)
|
||||
.leftJoin("Tournament", "Tournament.id", "TournamentTeam.tournamentId")
|
||||
.leftJoin("CalendarEvent", "CalendarEvent.tournamentId", "Tournament.id")
|
||||
.leftJoin(
|
||||
"CalendarEventDate",
|
||||
|
||||
@@ -715,16 +715,12 @@ export async function findUserScrims(userId: number): Promise<SidebarScrim[]> {
|
||||
const rows = await baseFindQuery
|
||||
.where("ScrimPost.canceledAt", "is", null)
|
||||
.where(bookedStartsAt, ">=", now)
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom("ScrimPostUser")
|
||||
.select("ScrimPostUser.scrimPostId")
|
||||
.whereRef("ScrimPostUser.scrimPostId", "=", "ScrimPost.id")
|
||||
.where("ScrimPostUser.userId", "=", userId),
|
||||
),
|
||||
eb.exists(
|
||||
.where("ScrimPost.id", "in", (eb) =>
|
||||
eb
|
||||
.selectFrom("ScrimPostUser")
|
||||
.select("ScrimPostUser.scrimPostId")
|
||||
.where("ScrimPostUser.userId", "=", userId)
|
||||
.union(
|
||||
eb
|
||||
.selectFrom("ScrimPostRequest")
|
||||
.innerJoin(
|
||||
@@ -733,10 +729,8 @@ export async function findUserScrims(userId: number): Promise<SidebarScrim[]> {
|
||||
"ScrimPostRequest.id",
|
||||
)
|
||||
.select("ScrimPostRequest.scrimPostId")
|
||||
.whereRef("ScrimPostRequest.scrimPostId", "=", "ScrimPost.id")
|
||||
.where("ScrimPostRequestUser.userId", "=", userId),
|
||||
),
|
||||
]),
|
||||
)
|
||||
.orderBy(bookedStartsAt, "asc")
|
||||
.execute();
|
||||
|
||||
@@ -163,6 +163,13 @@ export function insertBracket(args: {
|
||||
.returning(["id"])
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
// no team can join once a bracket has started, so none is looking for members anymore
|
||||
await trx
|
||||
.updateTable("TournamentTeam")
|
||||
.set({ isLooking: 0 })
|
||||
.where("tournamentId", "=", args.tournamentId)
|
||||
.execute();
|
||||
|
||||
if (
|
||||
args.bracket.group.length === 0 ||
|
||||
args.bracket.round.length === 0 ||
|
||||
|
||||
@@ -762,7 +762,7 @@ const nameLikeExpr = (column: string, name: string) => {
|
||||
return sql<boolean>`${sql.ref(column)} like ${pattern} escape '\\'`;
|
||||
};
|
||||
|
||||
export function findResultsByUserId(
|
||||
export async function findResultsByUserId(
|
||||
userId: number,
|
||||
{
|
||||
limit,
|
||||
@@ -773,95 +773,144 @@ export function findResultsByUserId(
|
||||
offset?: number;
|
||||
} = {},
|
||||
) {
|
||||
const page =
|
||||
limit !== undefined
|
||||
? await findResultPageKeys(userId, filters, { limit, offset })
|
||||
: null;
|
||||
|
||||
const calendarEventResultsQuery = baseCalendarEventResultsQuery(
|
||||
userId,
|
||||
filters,
|
||||
).select(({ eb, fn }) => [
|
||||
"CalendarEvent.id as eventId",
|
||||
sql<number>`null`.as("tournamentId"),
|
||||
"CalendarEventResultTeam.placement",
|
||||
"CalendarEvent.participantCount",
|
||||
sql<Tables["TournamentResult"]["setResults"]>`null`.as("setResults"),
|
||||
sql<string | null>`null`.as("div"),
|
||||
sql<string | null>`null`.as("logoUrl"),
|
||||
"CalendarEvent.name as eventName",
|
||||
"CalendarEventResultTeam.id as teamId",
|
||||
"CalendarEventResultTeam.name as teamName",
|
||||
fn<number | null>("iif", ["UserResultHighlight.userId", sql`1`, sql`0`]).as(
|
||||
"isHighlight",
|
||||
),
|
||||
sql<number | null>`null`.as("tier"),
|
||||
withMaxEventStartTime(eb),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("CalendarEventResultPlayer")
|
||||
.leftJoin("User", "User.id", "CalendarEventResultPlayer.userId")
|
||||
.select((mateEb) => [
|
||||
...commonUserSelect(mateEb),
|
||||
"CalendarEventResultPlayer.name",
|
||||
])
|
||||
.whereRef(
|
||||
"CalendarEventResultPlayer.teamId",
|
||||
"=",
|
||||
"CalendarEventResultTeam.id",
|
||||
)
|
||||
.where((mateEb) =>
|
||||
mateEb.or([
|
||||
mateEb("CalendarEventResultPlayer.userId", "is", null),
|
||||
mateEb("CalendarEventResultPlayer.userId", "!=", userId),
|
||||
]),
|
||||
),
|
||||
).as("mates"),
|
||||
]);
|
||||
)
|
||||
.$if(page !== null, (qb) =>
|
||||
qb.where("CalendarEventResultTeam.id", "in", page!.calendarEventTeamIds),
|
||||
)
|
||||
.select(({ eb, fn }) => [
|
||||
"CalendarEvent.id as eventId",
|
||||
sql<number>`null`.as("tournamentId"),
|
||||
"CalendarEventResultTeam.placement",
|
||||
"CalendarEvent.participantCount",
|
||||
sql<Tables["TournamentResult"]["setResults"]>`null`.as("setResults"),
|
||||
sql<string | null>`null`.as("div"),
|
||||
sql<string | null>`null`.as("logoUrl"),
|
||||
"CalendarEvent.name as eventName",
|
||||
"CalendarEventResultTeam.id as teamId",
|
||||
"CalendarEventResultTeam.name as teamName",
|
||||
fn<number | null>("iif", [
|
||||
"UserResultHighlight.userId",
|
||||
sql`1`,
|
||||
sql`0`,
|
||||
]).as("isHighlight"),
|
||||
sql<number | null>`null`.as("tier"),
|
||||
withMaxEventStartTime(eb),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("CalendarEventResultPlayer")
|
||||
.leftJoin("User", "User.id", "CalendarEventResultPlayer.userId")
|
||||
.select((mateEb) => [
|
||||
...commonUserSelect(mateEb),
|
||||
"CalendarEventResultPlayer.name",
|
||||
])
|
||||
.whereRef(
|
||||
"CalendarEventResultPlayer.teamId",
|
||||
"=",
|
||||
"CalendarEventResultTeam.id",
|
||||
)
|
||||
.where((mateEb) =>
|
||||
mateEb.or([
|
||||
mateEb("CalendarEventResultPlayer.userId", "is", null),
|
||||
mateEb("CalendarEventResultPlayer.userId", "!=", userId),
|
||||
]),
|
||||
),
|
||||
).as("mates"),
|
||||
]);
|
||||
|
||||
const tournamentResultsQuery = baseTournamentResultsQuery(
|
||||
userId,
|
||||
filters,
|
||||
).select(({ eb }) => [
|
||||
sql<number>`null`.as("eventId"),
|
||||
"TournamentResult.tournamentId",
|
||||
"TournamentResult.placement",
|
||||
"TournamentResult.participantCount",
|
||||
"TournamentResult.setResults",
|
||||
"TournamentResult.div",
|
||||
tournamentLogoOrNull(eb).as("logoUrl"),
|
||||
"CalendarEvent.name as eventName",
|
||||
"TournamentTeam.id as teamId",
|
||||
"TournamentTeam.name as teamName",
|
||||
"TournamentResult.isHighlight",
|
||||
RESULT_TIER.as("tier"),
|
||||
withMaxEventStartTime(eb),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("TournamentResult as TournamentResult2")
|
||||
.innerJoin("User", "User.id", "TournamentResult2.userId")
|
||||
.select((mateEb) => [
|
||||
...commonUserSelect(mateEb),
|
||||
sql<string | null>`null`.as("name"),
|
||||
])
|
||||
.whereRef(
|
||||
"TournamentResult2.tournamentTeamId",
|
||||
"=",
|
||||
"TournamentResult.tournamentTeamId",
|
||||
)
|
||||
.where("TournamentResult2.userId", "!=", userId),
|
||||
).as("mates"),
|
||||
]);
|
||||
const tournamentResultsQuery = baseTournamentResultsQuery(userId, filters)
|
||||
.$if(page !== null, (qb) =>
|
||||
qb.where(
|
||||
"TournamentResult.tournamentTeamId",
|
||||
"in",
|
||||
page!.tournamentTeamIds,
|
||||
),
|
||||
)
|
||||
.select(({ eb }) => [
|
||||
sql<number>`null`.as("eventId"),
|
||||
"TournamentResult.tournamentId",
|
||||
"TournamentResult.placement",
|
||||
"TournamentResult.participantCount",
|
||||
"TournamentResult.setResults",
|
||||
"TournamentResult.div",
|
||||
tournamentLogoOrNull(eb).as("logoUrl"),
|
||||
"CalendarEvent.name as eventName",
|
||||
"TournamentTeam.id as teamId",
|
||||
"TournamentTeam.name as teamName",
|
||||
"TournamentResult.isHighlight",
|
||||
RESULT_TIER.as("tier"),
|
||||
withMaxEventStartTime(eb),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("TournamentResult as TournamentResult2")
|
||||
.innerJoin("User", "User.id", "TournamentResult2.userId")
|
||||
.select((mateEb) => [
|
||||
...commonUserSelect(mateEb),
|
||||
sql<string | null>`null`.as("name"),
|
||||
])
|
||||
.whereRef(
|
||||
"TournamentResult2.tournamentTeamId",
|
||||
"=",
|
||||
"TournamentResult.tournamentTeamId",
|
||||
)
|
||||
.where("TournamentResult2.userId", "!=", userId),
|
||||
).as("mates"),
|
||||
]);
|
||||
|
||||
let query = calendarEventResultsQuery
|
||||
return calendarEventResultsQuery
|
||||
.unionAll(tournamentResultsQuery)
|
||||
.orderBy("startsAt", "desc")
|
||||
.$narrowType<{ startsAt: NotNull }>();
|
||||
.$narrowType<{ startsAt: NotNull }>()
|
||||
.execute();
|
||||
}
|
||||
|
||||
if (limit !== undefined) {
|
||||
query = query.limit(limit);
|
||||
}
|
||||
/**
|
||||
* Identities of the results on one page, newest first. Resolved on their own because the
|
||||
* per-row columns of {@link findResultsByUserId} (mates, logos) would otherwise be computed
|
||||
* for the user's every result before the sort and limit.
|
||||
*/
|
||||
async function findResultPageKeys(
|
||||
userId: number,
|
||||
filters: ResultsFilters,
|
||||
{ limit, offset }: { limit: number; offset?: number },
|
||||
) {
|
||||
const rows = await baseCalendarEventResultsQuery(userId, filters)
|
||||
.select((eb) => [
|
||||
sql<number | null>`"CalendarEventResultTeam"."id"`.as(
|
||||
"calendarEventTeamId",
|
||||
),
|
||||
sql<number | null>`null`.as("tournamentTeamId"),
|
||||
withMaxEventStartTime(eb),
|
||||
])
|
||||
.unionAll(
|
||||
baseTournamentResultsQuery(userId, filters).select((eb) => [
|
||||
sql<number | null>`null`.as("calendarEventTeamId"),
|
||||
sql<number | null>`"TournamentResult"."tournamentTeamId"`.as(
|
||||
"tournamentTeamId",
|
||||
),
|
||||
withMaxEventStartTime(eb),
|
||||
]),
|
||||
)
|
||||
.orderBy("startsAt", "desc")
|
||||
.limit(limit)
|
||||
.$if(offset !== undefined, (qb) => qb.offset(offset!))
|
||||
.execute();
|
||||
|
||||
if (offset !== undefined) {
|
||||
query = query.offset(offset);
|
||||
}
|
||||
|
||||
return query.execute();
|
||||
return {
|
||||
calendarEventTeamIds: rows.flatMap((row) =>
|
||||
row.calendarEventTeamId !== null ? [row.calendarEventTeamId] : [],
|
||||
),
|
||||
tournamentTeamIds: rows.flatMap((row) =>
|
||||
row.tournamentTeamId !== null ? [row.tournamentTeamId] : [],
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export async function countResultsByUserId(
|
||||
|
||||
4
changelog/2026-09-08-sidebar-friend-lfg-activity.md
Normal file
4
changelog/2026-09-08-sidebar-friend-lfg-activity.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
type: bug
|
||||
---
|
||||
Friends looking for a pickup team in a tournament with open registration show up as such in the sidebar again
|
||||
12
migrations/20260908124343-reset-stale-is-looking.ts
Normal file
12
migrations/20260908124343-reset-stale-is-looking.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { type Kysely, sql } from "kysely";
|
||||
|
||||
/**
|
||||
* `TournamentTeam.isLooking` used to stay set after the tournament had started. Starting a
|
||||
* bracket clears it from now on, so the sidebar friend activity no longer joins every past
|
||||
* pickup registration.
|
||||
*/
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`update "TournamentTeam" set "isLooking" = 0 where "isLooking" = 1 and "tournamentId" in (select "tournamentId" from "TournamentStage")`.execute(
|
||||
db,
|
||||
);
|
||||
}
|
||||
@@ -1368,6 +1368,12 @@ export function buildCases(fx: Fixtures): {
|
||||
add("UserRepository.findResultsByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findResultsByUserId(user.id, {}),
|
||||
);
|
||||
add("UserRepository.findResultsByUserId.page", fx.heavyUser, (user) =>
|
||||
UserRepository.findResultsByUserId(user.id, { limit: 25, offset: 25 }),
|
||||
);
|
||||
add("UserRepository.findResultsByUserId.widget", fx.heavyUser, (user) =>
|
||||
UserRepository.findResultsByUserId(user.id, { limit: 3 }),
|
||||
);
|
||||
add("UserRepository.countResultsByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.countResultsByUserId(user.id),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user