sendou.ink/app/features/sendouq/actions/q.server.ts
2026-01-03 13:47:32 +02:00

129 lines
3.7 KiB
TypeScript

import type { ActionFunction } from "react-router";
import { redirect } from "react-router";
import * as AdminRepository from "~/features/admin/AdminRepository.server";
import { requireUser } from "~/features/auth/core/user.server";
import { refreshBannedCache } from "~/features/ban/core/banned.server";
import * as Seasons from "~/features/mmr/core/Seasons";
import * as SQGroupRepository from "~/features/sendouq/SQGroupRepository.server";
import { giveTrust } from "~/features/tournament/queries/giveTrust.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import invariant from "~/utils/invariant";
import { errorToastIfFalsy, parseRequestPayload } from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";
import {
SENDOUQ_LOOKING_PAGE,
SENDOUQ_PREPARING_PAGE,
SUSPENDED_PAGE,
} from "~/utils/urls";
import { refreshSendouQInstance, SendouQ } from "../core/SendouQ.server";
import { JOIN_CODE_SEARCH_PARAM_KEY } from "../q-constants";
import { frontPageSchema } from "../q-schemas.server";
import { userCanJoinQueueAt } from "../q-utils";
export const action: ActionFunction = async ({ request }) => {
const user = requireUser();
const data = await parseRequestPayload({
request,
schema: frontPageSchema,
});
switch (data._action) {
case "JOIN_QUEUE": {
await validateCanJoinQ(user);
await SQGroupRepository.createGroup({
status: data.direct === "true" ? "ACTIVE" : "PREPARING",
userId: user.id,
});
await refreshSendouQInstance();
return redirect(
data.direct === "true" ? SENDOUQ_LOOKING_PAGE : SENDOUQ_PREPARING_PAGE,
);
}
case "JOIN_TEAM_WITH_TRUST":
case "JOIN_TEAM": {
await validateCanJoinQ(user);
const code = new URL(request.url).searchParams.get(
JOIN_CODE_SEARCH_PARAM_KEY,
);
const groupInvitedTo =
code && user ? SendouQ.findGroupByInviteCode(code) : undefined;
errorToastIfFalsy(
groupInvitedTo,
"Invite code doesn't match any active team",
);
await SQGroupRepository.addMember(groupInvitedTo.id, {
userId: user.id,
role: "MANAGER",
});
if (data._action === "JOIN_TEAM_WITH_TRUST") {
const owner = groupInvitedTo.members.find((m) => m.role === "OWNER");
invariant(owner, "Owner not found");
giveTrust({
trustGiverUserId: user.id,
trustReceiverUserId: owner.id,
});
}
await refreshSendouQInstance();
return redirect(
groupInvitedTo.status === "PREPARING"
? SENDOUQ_PREPARING_PAGE
: SENDOUQ_LOOKING_PAGE,
);
}
case "ADD_FRIEND_CODE": {
errorToastIfFalsy(
!(await UserRepository.currentFriendCodeByUserId(user.id)),
"Friend code already set",
);
const isTakenFriendCode = (
await UserRepository.allCurrentFriendCodes()
).has(data.friendCode);
await UserRepository.insertFriendCode({
userId: user.id,
friendCode: data.friendCode,
submitterUserId: user.id,
});
if (isTakenFriendCode) {
await AdminRepository.banUser({
userId: user.id,
banned: 1,
bannedReason:
"[automatic ban] This friend code is already in use by some other account. Please contact staff on our Discord helpdesk for resolution including merging accounts.",
bannedByUserId: null,
});
await refreshBannedCache();
throw redirect(SUSPENDED_PAGE);
}
return null;
}
default: {
assertUnreachable(data);
}
}
};
async function validateCanJoinQ(user: { id: number; discordId: string }) {
const friendCode = await UserRepository.currentFriendCodeByUserId(user.id);
errorToastIfFalsy(friendCode, "No friend code");
const canJoinQueue = userCanJoinQueueAt(user, friendCode) === "NOW";
errorToastIfFalsy(Seasons.current(), "Season is not active");
errorToastIfFalsy(canJoinQueue, "Can't join queue right now");
}