sendou.ink/app/features/sendouq/actions/q.server.ts
Kalle 2b5b1b1948
Some checks are pending
E2E Tests / e2e (push) Waiting to run
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run
New match page (#3032)
2026-05-04 18:15:10 +03:00

156 lines
4.4 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 ChatSystemMessage from "~/features/chat/ChatSystemMessage.server";
import * as Seasons from "~/features/mmr/core/Seasons";
import * as SQGroupRepository from "~/features/sendouq/SQGroupRepository.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
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";
import { SendouQError, setGroupChatMetadata } from "../q-utils.server";
export const action: ActionFunction = async ({ request }) => {
const user = requireUser();
const data = await parseRequestPayload({
request,
schema: frontPageSchema,
});
try {
switch (data._action) {
case "JOIN_QUEUE": {
await validateCanJoinQ(user);
const { chatCodeToRevalidate } = await SQGroupRepository.createGroup({
status: data.direct === "true" ? "ACTIVE" : "PREPARING",
userId: user.id,
});
if (chatCodeToRevalidate) {
ChatSystemMessage.send({
room: chatCodeToRevalidate,
revalidateOnly: true,
});
}
await refreshSendouQInstance();
return redirect(
data.direct === "true"
? SENDOUQ_LOOKING_PAGE
: SENDOUQ_PREPARING_PAGE,
);
}
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",
);
const { chatCodeToRevalidate } = await SQGroupRepository.addMember(
groupInvitedTo.id,
{
userId: user.id,
role: "MANAGER",
},
);
if (chatCodeToRevalidate) {
ChatSystemMessage.send({
room: chatCodeToRevalidate,
revalidateOnly: true,
});
}
await refreshSendouQInstance();
const joinedGroup = SendouQ.findOwnGroup(user.id);
if (joinedGroup?.chatCode) {
setGroupChatMetadata({
chatCode: joinedGroup.chatCode,
members: joinedGroup.members,
});
}
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);
}
}
} catch (error) {
// some errors are expected to happen, for example two requests racing to
// create/join a group. return null so loaders re-run and the user sees
// the fresh state instead of an error page
if (error instanceof SendouQError) {
return null;
}
throw error;
}
};
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");
}