Files
sendou.ink/app/features/availability/core/Commitments.server.ts
Kalle 9b82d577e2
Some checks failed
E2E Tests / e2e (push) Has been cancelled
Tests and checks on push / run-checks-and-tests (push) Has been cancelled
Updates translation progress / update-translation-progress-issue (push) Has been cancelled
Availability tool (#3375)
2026-08-30 17:35:14 +03:00

103 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as R from "remeda";
import * as ScrimPostRepository from "~/features/scrims/ScrimPostRepository.server";
import * as TournamentTeamRepository from "~/features/tournament/TournamentTeamRepository.server";
import * as SeriesTeamCount from "~/features/tournament-organization/core/SeriesTeamCount.server";
import * as AvailabilityRepository from "../AvailabilityRepository.server";
import { AVAILABILITY } from "../availability-constants";
import type { BusyBlock } from "../availability-types";
import * as Availability from "./Availability";
import * as TournamentDuration from "./TournamentDuration";
import { estimatedEndsAtWith } from "./TournamentDuration.server";
/**
* The busy blocks of the given users within the given window, keyed by user
* id and sorted by start. A busy block overrides whatever availability the
* user reported: effective availability = reported − busy blocks.
*
* Sourced from tournament registrations (start + estimated duration, see
* {@link TournamentDuration.estimateSeconds}), accepted scrims (start + an
* assumed length) and team events (their actual span). League registrations
* are not blocks — a league runs over weeks and its matches are scheduled
* separately. `excludeTournamentId` leaves that tournament's registrations
* out, for surfaces asking "busy elsewhere" while looking at that tournament.
*/
export async function busyBlocksByUserIds({
userIds,
startsAt,
endsAt,
excludeTournamentId,
}: {
userIds: Array<number>;
startsAt: number;
endsAt: number;
excludeTournamentId?: number;
}): Promise<Map<number, Array<BusyBlock>>> {
if (userIds.length === 0) return new Map();
const registrations =
await TournamentTeamRepository.findAllRegistrationsByUserIds({
userIds,
startsAt: startsAt - TournamentDuration.MAX_ESTIMATE_SECONDS,
endsAt,
excludeTournamentId,
});
const scrims = await ScrimPostRepository.findAllAcceptedByUserIds({
userIds,
startsAt: startsAt - AVAILABILITY.SCRIM_COMMITMENT_SECONDS,
endsAt,
});
const teamEvents = await AvailabilityRepository.findAllTeamEventsByUserIds({
userIds,
startsAt,
endsAt,
});
const expectedTeamCount = await SeriesTeamCount.lookup();
const blocks: Array<BusyBlock & { userId: number }> = [
...registrations
.filter((registration) => !registration.settings.isLeague)
.map((registration) => ({
userId: registration.userId,
type: "tournament" as const,
name: registration.name,
startsAt: registration.startsAt,
endsAt: estimatedEndsAtWith(
{
...registration,
minMembersPerTeam: registration.settings.minMembersPerTeam ?? 4,
bracketTypes: registration.settings.bracketProgression.map(
(bracket) => bracket.type,
),
},
expectedTeamCount,
),
})),
...scrims.map((scrim) => ({
userId: scrim.userId,
type: "scrim" as const,
name: null,
startsAt: scrim.startsAt,
endsAt: scrim.startsAt + AVAILABILITY.SCRIM_COMMITMENT_SECONDS,
})),
...teamEvents.map((event) => ({
userId: event.userId,
type: "teamEvent" as const,
name: event.name,
startsAt: event.startsAt,
endsAt: event.endsAt,
})),
].filter((block) => Availability.overlaps(block, { startsAt, endsAt }));
return new Map(
Object.entries(R.groupBy(blocks, (block) => block.userId)).map(
([userId, userBlocks]) => [
Number(userId),
R.sortBy(
userBlocks.map((block) => R.omit(block, ["userId"])),
(block) => block.startsAt,
),
],
),
);
}