User middleware (#2687)

This commit is contained in:
Kalle 2026-01-02 18:34:45 +02:00 committed by GitHub
parent 91e26948b2
commit 4fca18ac8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
117 changed files with 532 additions and 277 deletions

View File

@ -289,6 +289,8 @@ function wipeDB() {
"UserFriendCode",
"NotificationUser",
"Notification",
"BanLog",
"ModNote",
"User",
"PlusSuggestion",
"PlusVote",

View File

@ -21,7 +21,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
request,
schema: adminActionSchema,
});
const user = await requireUser(request);
const user = await requireUser();
let message: string;
switch (data._action) {

View File

@ -8,7 +8,7 @@ import { DANGEROUS_CAN_ACCESS_DEV_CONTROLS } from "../core/dev-controls";
export const loader = async ({ request }: LoaderFunctionArgs) => {
if (!DANGEROUS_CAN_ACCESS_DEV_CONTROLS) {
const user = await requireUser(request);
const user = await requireUser();
requireRole(user, "STAFF");
}

View File

@ -3,6 +3,7 @@ import { z } from "zod";
import { seed } from "~/db/seed";
import { DANGEROUS_CAN_ACCESS_DEV_CONTROLS } from "~/features/admin/core/dev-controls";
import { SEED_VARIATIONS } from "~/features/api-private/constants";
import { refreshBannedCache } from "~/features/ban/core/banned.server";
import { refreshSendouQInstance } from "~/features/sendouq/core/SendouQ.server";
import { parseRequestPayload } from "~/utils/remix.server";
@ -26,6 +27,7 @@ export const action: ActionFunction = async ({ request }) => {
await seed(variation);
await refreshBannedCache();
await refreshSendouQInstance();
return Response.json(null);

View File

@ -16,7 +16,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
request,
schema: apiActionSchema,
});
const user = await requireUser(request);
const user = await requireUser();
const hasApiAccess = await checkUserHasApiAccess(user);
if (!hasApiAccess) {

View File

@ -1,10 +1,9 @@
import type { LoaderFunctionArgs } from "react-router";
import { requireUser } from "~/features/auth/core/user.server";
import * as ApiRepository from "../ApiRepository.server";
import { checkUserHasApiAccess } from "../core/perms";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
export const loader = async () => {
const user = await requireUser();
const hasApiAccess = await checkUserHasApiAccess(user);

View File

@ -20,7 +20,7 @@ import { NEW_ART_EXISTING_SEARCH_PARAM_KEY } from "../art-constants";
import { editArtSchema, newArtSchema } from "../art-schemas.server";
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const user = await requireUser();
requireRole(user, "ARTIST");
const searchParams = new URL(request.url).searchParams;

View File

@ -4,7 +4,7 @@ import * as ArtRepository from "../ArtRepository.server";
import { NEW_ART_EXISTING_SEARCH_PARAM_KEY } from "../art-constants";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const artIdRaw = new URL(request.url).searchParams.get(
NEW_ART_EXISTING_SEARCH_PARAM_KEY,

View File

@ -7,7 +7,7 @@ import { associationsPage } from "~/utils/urls";
import * as AssociationRepository from "../AssociationRepository.server";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: createNewAssociationSchema,

View File

@ -13,7 +13,7 @@ import { assertUnreachable } from "~/utils/types";
import * as AssociationRepository from "../AssociationRepository.server";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: associationsPageActionSchema,

View File

@ -1,5 +1,5 @@
import type { LoaderFunctionArgs } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import type { SerializeFrom } from "~/utils/remix";
import { parseSafeSearchParams } from "~/utils/remix.server";
import { inviteCodeObject } from "~/utils/zod";
@ -8,7 +8,7 @@ import * as AssociationRepository from "../AssociationRepository.server";
export type AssociationsLoaderData = SerializeFrom<typeof loader>;
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUserId(request);
const user = await requireUser();
const associations = (
await AssociationRepository.findByMemberUserId(user.id, {

View File

@ -21,7 +21,7 @@ import {
SESSION_KEY,
} from "./authenticator.server";
import { authSessionStorage } from "./session.server";
import { getUserId, requireUser } from "./user.server";
import { getUser, requireUser } from "./user.server";
export const callbackLoader: LoaderFunction = async ({ request }) => {
const url = new URL(request.url);
@ -76,7 +76,7 @@ export const logInAction: ActionFunction = async ({ request }) => {
export const impersonateAction: ActionFunction = async ({ request }) => {
if (!DANGEROUS_CAN_ACCESS_DEV_CONTROLS) {
const user = await requireUser(request);
const user = await requireUser();
requireRole(user, "ADMIN");
}
@ -163,7 +163,7 @@ export const logInViaLinkLoader: LoaderFunction = async ({ request }) => {
request,
schema: logInViaLinkActionSchema,
});
const user = await getUserId(request);
const user = await getUser();
if (user) {
throw redirect("/");

View File

@ -0,0 +1,47 @@
import { AsyncLocalStorage } from "node:async_hooks";
import { redirect } from "react-router";
import { userIsBanned } from "~/features/ban/core/banned.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { SUSPENDED_PAGE } from "~/utils/urls";
import { IMPERSONATED_SESSION_KEY, SESSION_KEY } from "./authenticator.server";
import { authSessionStorage } from "./session.server";
export type AuthenticatedUser = NonNullable<
Awaited<ReturnType<typeof UserRepository.findLeanById>>
>;
interface UserContext {
getUserLazy: () => Promise<AuthenticatedUser | undefined>;
}
export const userAsyncLocalStorage = new AsyncLocalStorage<UserContext>();
export function getUserContext(): UserContext {
const context = userAsyncLocalStorage.getStore();
if (!context) {
throw new Error("getUserContext called outside of user middleware context");
}
return context;
}
export async function getUserFromRequest(
request: Request,
): Promise<AuthenticatedUser | undefined> {
const session = await authSessionStorage.getSession(
request.headers.get("Cookie"),
);
const userId =
session.get(IMPERSONATED_SESSION_KEY) ?? session.get(SESSION_KEY);
if (!userId) return undefined;
if (userIsBanned(userId)) {
const url = new URL(request.url);
if (url.pathname !== SUSPENDED_PAGE) {
throw redirect(SUSPENDED_PAGE);
}
}
return UserRepository.findLeanById(userId);
}

View File

@ -0,0 +1,34 @@
import {
type AuthenticatedUser,
getUserFromRequest,
userAsyncLocalStorage,
} from "./user-context.server";
type MiddlewareArgs = {
request: Request;
context: unknown;
};
type MiddlewareFn = (
args: MiddlewareArgs,
next: () => Promise<Response>,
) => Promise<Response>;
function createLazyUserGetter(
request: Request,
): () => Promise<AuthenticatedUser | undefined> {
let fetchPromise: Promise<AuthenticatedUser | undefined> | undefined;
return () => {
fetchPromise ??= getUserFromRequest(request);
return fetchPromise;
};
}
export const userMiddleware: MiddlewareFn = async ({ request }, next) => {
const context = {
getUserLazy: createLazyUserGetter(request),
};
return userAsyncLocalStorage.run(context, () => next());
};

View File

@ -1,51 +1,16 @@
import { redirect } from "react-router";
import type { Tables } from "~/db/tables";
import { userIsBanned } from "~/features/ban/core/banned.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { SUSPENDED_PAGE } from "~/utils/urls";
import { IMPERSONATED_SESSION_KEY, SESSION_KEY } from "./authenticator.server";
import { IMPERSONATED_SESSION_KEY } from "./authenticator.server";
import { authSessionStorage } from "./session.server";
import { type AuthenticatedUser, getUserContext } from "./user-context.server";
export type AuthenticatedUser = NonNullable<
Awaited<ReturnType<typeof getUser>>
>;
export type { AuthenticatedUser };
export async function getUserId(
request: Request,
redirectIfBanned = true,
): Promise<Pick<Tables["User"], "id"> | undefined> {
const session = await authSessionStorage.getSession(
request.headers.get("Cookie"),
);
const userId =
session.get(IMPERSONATED_SESSION_KEY) ?? session.get(SESSION_KEY);
if (!userId) return;
if (userIsBanned(userId) && redirectIfBanned) throw redirect(SUSPENDED_PAGE);
return { id: userId };
export async function getUser(): Promise<AuthenticatedUser | undefined> {
const context = getUserContext();
return context.getUserLazy();
}
export async function getUser(request: Request, redirectIfBanned = true) {
const userId = (await getUserId(request, redirectIfBanned))?.id;
if (!userId) return;
return UserRepository.findLeanById(userId);
}
export async function requireUserId(request: Request) {
const user = await getUserId(request);
if (!user) throw new Response(null, { status: 401 });
return user;
}
export async function requireUser(request: Request) {
const user = await getUser(request);
export async function requireUser(): Promise<AuthenticatedUser> {
const user = await getUser();
if (!user) throw new Response(null, { status: 401 });

View File

@ -21,7 +21,7 @@ export const action: ActionFunction = async ({ request, params }) => {
schema: editBadgeActionSchema,
});
const badgeId = z.preprocess(actualNumber, z.number()).parse(params.id);
const user = await requireUser(request);
const user = await requireUser();
const badge = notFoundIfFalsy(await BadgeRepository.findById(badgeId));

View File

@ -3,15 +3,22 @@ import { databaseTimestampToDate } from "~/utils/dates";
let bannedUsers = await AdminRepository.allBannedUsers();
export function checkBanStatus(
banned: number | null | undefined,
now: Date = new Date(),
): boolean {
if (!banned) return false;
if (banned === 1) return true;
const banExpiresAt = databaseTimestampToDate(banned);
return banExpiresAt > now;
}
export function userIsBanned(userId: number) {
const banStatus = bannedUsers.get(userId);
if (!banStatus?.banned) return false;
if (banStatus.banned === 1) return true;
const banExpiresAt = databaseTimestampToDate(banStatus.banned);
return banExpiresAt > new Date();
return checkBanStatus(banStatus?.banned);
}
export async function refreshBannedCache() {

View File

@ -0,0 +1,59 @@
import { describe, expect, it } from "vitest";
import { checkBanStatus } from "./banned.server";
describe("checkBanStatus", () => {
it("returns false when banned is null", () => {
expect(checkBanStatus(null)).toBe(false);
});
it("returns false when banned is undefined", () => {
expect(checkBanStatus(undefined)).toBe(false);
});
it("returns false when banned is 0", () => {
expect(checkBanStatus(0)).toBe(false);
});
it("returns true when banned is 1 (permanent ban)", () => {
expect(checkBanStatus(1)).toBe(true);
});
it("returns true when ban expires in the future", () => {
const now = new Date("2025-01-01T12:00:00Z");
const futureTimestamp = Math.floor(
new Date("2025-01-01T13:00:00Z").getTime() / 1000,
);
expect(checkBanStatus(futureTimestamp, now)).toBe(true);
});
it("returns false when ban has expired", () => {
const now = new Date("2025-01-01T12:00:00Z");
const pastTimestamp = Math.floor(
new Date("2025-01-01T11:00:00Z").getTime() / 1000,
);
expect(checkBanStatus(pastTimestamp, now)).toBe(false);
});
it("returns false when ban expires exactly at current time", () => {
const now = new Date("2025-01-01T12:00:00Z");
const exactTimestamp = Math.floor(now.getTime() / 1000);
expect(checkBanStatus(exactTimestamp, now)).toBe(false);
});
it("returns true when ban expires 1 second in the future", () => {
const now = new Date("2025-01-01T12:00:00Z");
const oneSecondLater = Math.floor(now.getTime() / 1000) + 1;
expect(checkBanStatus(oneSecondLater, now)).toBe(true);
});
it("returns false when ban expired 1 second ago", () => {
const now = new Date("2025-01-01T12:00:00Z");
const oneSecondEarlier = Math.floor(now.getTime() / 1000) - 1;
expect(checkBanStatus(oneSecondEarlier, now)).toBe(false);
});
});

View File

@ -15,7 +15,7 @@ import { buildFiltersSearchParams } from "../builds-schemas.server";
import { filterBuilds } from "../core/filter.server";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await getUser(request);
const user = await getUser();
const t = await i18next.getFixedT(request, ["weapons", "common"], {
lng: "en",
});

View File

@ -1,6 +1,6 @@
import type { ActionFunction } from "react-router";
import { redirect } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import * as CalendarRepository from "~/features/calendar/CalendarRepository.server";
import {
errorToastIfFalsy,
@ -14,7 +14,7 @@ import { reportWinnersActionSchema } from "../calendar-schemas";
import { canReportCalendarEventWinners } from "../calendar-utils";
export const action: ActionFunction = async (args) => {
const user = await requireUserId(args.request);
const user = await requireUser();
const params = parseParams({
params: args.params,
schema: idObject,

View File

@ -1,7 +1,7 @@
import type { ActionFunction } from "react-router";
import { redirect } from "react-router";
import { z } from "zod";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import * as CalendarRepository from "~/features/calendar/CalendarRepository.server";
import * as ShowcaseTournaments from "~/features/front-page/core/ShowcaseTournaments.server";
import {
@ -14,8 +14,8 @@ import { CALENDAR_PAGE } from "~/utils/urls";
import { actualNumber, id } from "~/utils/zod";
import { canDeleteCalendarEvent } from "../calendar-utils";
export const action: ActionFunction = async ({ params, request }) => {
const user = await requireUserId(request);
export const action: ActionFunction = async ({ params }) => {
const user = await requireUser();
const parsedParams = z
.object({ id: z.preprocess(actualNumber, id) })
.parse(params);

View File

@ -31,7 +31,7 @@ import { canEditCalendarEvent, regClosesAtDate } from "../calendar-utils";
import { findValidOrganizations } from "../loaders/calendar.new.server";
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const user = await requireUser();
const { avatarFileName, formData } = await uploadImageIfSubmitted({
request,

View File

@ -1,5 +1,5 @@
import { type ActionFunctionArgs, redirect } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import { calendarFiltersSearchParamsSchema } from "~/features/calendar/calendar-schemas";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import {
@ -10,7 +10,7 @@ import { calendarPage } from "~/utils/urls";
import { dayMonthYear } from "~/utils/zod";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUserId(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: calendarFiltersSearchParamsSchema,

View File

@ -1,5 +1,5 @@
import type { LoaderFunctionArgs } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import * as CalendarRepository from "~/features/calendar/CalendarRepository.server";
import {
notFoundIfFalsy,
@ -14,7 +14,7 @@ export const loader = async (args: LoaderFunctionArgs) => {
params: args.params,
schema: idObject,
});
const user = await requireUserId(args.request);
const user = await requireUser();
const event = notFoundIfFalsy(await CalendarRepository.findById(params.id));
unauthorizedIfFalsy(

View File

@ -11,7 +11,7 @@ import { tournamentBracketsPage } from "~/utils/urls";
import { canEditCalendarEvent } from "../calendar-utils";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
requireRole(user, "CALENDAR_EVENT_ADDER");
const url = new URL(request.url);

View File

@ -16,7 +16,7 @@ import * as CalendarEvent from "../core/CalendarEvent";
export type CalendarLoaderData = SerializeFrom<typeof loader>;
export const loader = async (args: LoaderFunctionArgs) => {
const user = await getUser(args.request);
const user = await getUser();
const parsed = parseSafeSearchParams({
request: args.request,
schema: dayMonthYear,

View File

@ -1,7 +1,6 @@
import cachified from "@epic-web/cachified";
import type { LoaderFunctionArgs } from "react-router";
import type { Tables } from "~/db/tables";
import { getUserId } from "~/features/auth/core/user.server";
import { getUser } from "~/features/auth/core/user.server";
import * as Changelog from "~/features/front-page/core/Changelog.server";
import { cachedFullUserLeaderboard } from "~/features/leaderboards/core/leaderboards.server";
import * as LeaderboardRepository from "~/features/leaderboards/LeaderboardRepository.server";
@ -10,8 +9,8 @@ import { cache, IN_MILLISECONDS, ttl } from "~/utils/cache.server";
import { discordAvatarUrl, teamPage, userPage } from "~/utils/urls";
import * as ShowcaseTournaments from "../core/ShowcaseTournaments.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await getUserId(request);
export const loader = async () => {
const user = await getUser();
const [tournaments, changelog, leaderboards] = await Promise.all([
ShowcaseTournaments.frontPageTournamentsByUserId(user?.id ?? null),

View File

@ -12,7 +12,7 @@ import * as ImageRepository from "../ImageRepository.server";
import { validateImageSchema } from "../upload-schemas.server";
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const user = await requireUser();
requireRole(user, "STAFF");
const data = await parseRequestPayload({

View File

@ -22,7 +22,7 @@ import { MAX_UNVALIDATED_IMG_COUNT } from "../upload-constants";
import { requestToImgType } from "../upload-utils";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const validatedType = requestToImgType(request);
errorToastIfFalsy(validatedType, "Invalid image type");

View File

@ -1,10 +1,9 @@
import type { LoaderFunctionArgs } from "react-router";
import { requireUser } from "~/features/auth/core/user.server";
import { requireRole } from "~/modules/permissions/guards.server";
import * as ImageRepository from "../ImageRepository.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
export const loader = async () => {
const user = await requireUser();
requireRole(user, "STAFF");
return {

View File

@ -7,7 +7,7 @@ import * as ImageRepository from "../ImageRepository.server";
import { requestToImgType } from "../upload-utils";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const validatedType = requestToImgType(request);
if (!validatedType) {

View File

@ -28,7 +28,7 @@ import {
} from "../queries/XPLeaderboard.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await getUser(request);
const user = await getUser();
const unvalidatedType = new URL(request.url).searchParams.get(
TYPE_SEARCH_PARAM_KEY,
);

View File

@ -11,7 +11,7 @@ import * as LFGRepository from "../LFGRepository.server";
import { LFG, TEAM_POST_TYPES, TIMEZONES } from "../lfg-constants";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema,

View File

@ -6,7 +6,7 @@ import { _action, id } from "~/utils/zod";
import * as LFGRepository from "../LFGRepository.server";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema,

View File

@ -9,7 +9,7 @@ import { id } from "~/utils/zod";
import * as LFGRepository from "../LFGRepository.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const userProfileData = await UserRepository.findProfileByIdentifier(
String(user.id),

View File

@ -1,4 +1,3 @@
import type { LoaderFunctionArgs } from "react-router";
import { getUser } from "~/features/auth/core/user.server";
import * as Seasons from "~/features/mmr/core/Seasons";
import type { TieredSkill } from "~/features/mmr/tiered.server";
@ -6,8 +5,8 @@ import { userSkills } from "~/features/mmr/tiered.server";
import type { Unpacked } from "~/utils/types";
import * as LFGRepository from "../LFGRepository.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await getUser(request);
export const loader = async () => {
const user = await getUser();
const posts = await LFGRepository.posts(user);
return {

View File

@ -1,9 +1,8 @@
import type { LoaderFunctionArgs } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import * as NotificationRepository from "../NotificationRepository.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUserId(request);
export const loader = async () => {
const user = await requireUser();
return {
notifications: await NotificationRepository.findByUserId(user.id),

View File

@ -1,11 +1,11 @@
import type { ActionFunctionArgs } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import { parseRequestPayload } from "~/utils/remix.server";
import * as NotificationRepository from "../NotificationRepository.server";
import { markAsSeenActionSchema } from "../notifications-schemas";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUserId(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: markAsSeenActionSchema,

View File

@ -5,7 +5,7 @@ import * as NotificationRepository from "../NotificationRepository.server";
import { subscribeSchema } from "../notifications-schemas";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: subscribeSchema,

View File

@ -20,7 +20,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
request,
schema: followUpCommentActionSchema,
});
const user = await requireUser(request);
const user = await requireUser();
const votingMonthYear = rangeToMonthYear(
badRequestIfFalsy(nextNonCompletedVoting(new Date())),

View File

@ -23,7 +23,7 @@ import {
} from "../plus-suggestions-utils";
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,

View File

@ -21,7 +21,7 @@ export const action: ActionFunction = async ({ request }) => {
request,
schema: suggestionActionSchema,
});
const user = await requireUser(request);
const user = await requireUser();
const votingMonthYear = rangeToMonthYear(
badRequestIfFalsy(nextNonCompletedVoting(new Date())),

View File

@ -14,7 +14,7 @@ import { PLUS_UPVOTE } from "../plus-voting-constants";
import { votingActionSchema } from "../plus-voting-schemas";
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: votingActionSchema,

View File

@ -1,4 +1,3 @@
import type { LoaderFunctionArgs } from "react-router";
import type { UserWithPlusTier } from "~/db/tables";
import { getUser } from "~/features/auth/core/user.server";
import { lastCompletedVoting } from "~/features/plus-voting/core";
@ -7,8 +6,8 @@ import { isSupporter } from "~/modules/permissions/utils";
import invariant from "~/utils/invariant";
import { roundToNDecimalPlaces } from "~/utils/number";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await getUser(request);
export const loader = async () => {
const user = await getUser();
const results = await PlusVotingRepository.resultsByMonthYear(
lastCompletedVoting(new Date()),
);

View File

@ -33,8 +33,8 @@ export type PlusVotingLoaderData =
};
};
export const loader: LoaderFunction = async ({ request }) => {
const user = await getUser(request);
export const loader: LoaderFunction = async () => {
const user = await getUser();
const now = new Date();
const nextVotingRange = nextNonCompletedVoting(now);

View File

@ -21,7 +21,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
const { id } = parseParams({ params, schema: idObject });
const post = notFoundIfFalsy(await ScrimPostRepository.findById(id));
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: cancelScrimSchema,

View File

@ -28,7 +28,7 @@ import {
import { serializeLutiDiv } from "../scrims-utils";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: scrimsNewActionSchema,

View File

@ -22,7 +22,7 @@ import { generateTimeOptions } from "../scrims-utils";
import { usersListForPost } from "./scrims.new.server";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,

View File

@ -9,8 +9,8 @@ import {
import * as Scrim from "../core/Scrim";
import * as ScrimPostRepository from "../ScrimPostRepository.server";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
export const loader = async ({ params }: LoaderFunctionArgs) => {
const user = await requireUser();
const post = notFoundIfFalsy(
await ScrimPostRepository.findById(Number(params.id)),

View File

@ -1,13 +1,12 @@
import type { LoaderFunctionArgs } from "react-router";
import * as AssociationRepository from "~/features/associations/AssociationRepository.server";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import type { SerializeFrom } from "~/utils/remix";
import * as TeamRepository from "../../team/TeamRepository.server";
export type ScrimsNewLoaderData = SerializeFrom<typeof loader>;
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUserId(request);
export const loader = async () => {
const user = await requireUser();
return {
teams: await TeamRepository.teamsByMemberUserId(user.id),

View File

@ -10,7 +10,7 @@ import { scrimsFiltersSearchParamsObject } from "../scrims-schemas";
import { dividePosts } from "../scrims-utils";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await getUser(request);
const user = await getUser();
const now = new Date();
const associations = user

View File

@ -50,7 +50,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
params,
schema: qMatchPageParamsSchema,
}).id;
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: matchSchema,

View File

@ -8,8 +8,8 @@ import * as SQMatchRepository from "~/features/sendouq-match/SQMatchRepository.s
import { notFoundIfFalsy, parseParams } from "~/utils/remix.server";
import { qMatchPageParamsSchema } from "../q-match-schemas";
export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const user = await getUser(request);
export const loader = async ({ params }: LoaderFunctionArgs) => {
const user = await getUser();
const matchId = parseParams({
params,
schema: qMatchPageParamsSchema,

View File

@ -1,12 +1,11 @@
import type { ActionFunctionArgs } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import * as QSettingsRepository from "~/features/sendouq-settings/QSettingsRepository.server";
import { parseRequestPayload } from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";
import { settingsActionSchema } from "../q-settings-schemas.server";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUserId(request);
export const action = async ({ request }: { request: Request }) => {
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: settingsActionSchema,

View File

@ -1,9 +1,8 @@
import type { LoaderFunctionArgs } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import * as QSettingsRepository from "~/features/sendouq-settings/QSettingsRepository.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUserId(request);
export const loader = async () => {
const user = await requireUser();
return {
settings: await QSettingsRepository.settingsByUserId(user.id),

View File

@ -23,7 +23,7 @@ import { SendouQError } from "../q-utils.server";
// if there is a validation error the user saw stale data
// and when we return null we just force a refresh
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: lookingSchema,

View File

@ -13,7 +13,7 @@ import { preparingSchema } from "../q-schemas.server";
export type SendouQPreparingAction = typeof action;
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: preparingSchema,

View File

@ -21,7 +21,7 @@ import { frontPageSchema } from "../q-schemas.server";
import { userCanJoinQueueAt } from "../q-utils";
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: frontPageSchema,

View File

@ -8,7 +8,7 @@ import * as PrivateUserNoteRepository from "../PrivateUserNoteRepository.server"
import { sqRedirectIfNeeded } from "../q-utils.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const isPreview =
new URL(request.url).searchParams.get("preview") === "true" &&

View File

@ -1,10 +1,9 @@
import type { LoaderFunctionArgs } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import { SendouQ } from "../core/SendouQ.server";
import { sqRedirectIfNeeded } from "../q-utils.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUserId(request);
export const loader = async () => {
const user = await requireUser();
const ownGroup = SendouQ.findOwnGroup(user.id);

View File

@ -1,5 +1,5 @@
import type { LoaderFunctionArgs } from "react-router";
import { getUserId } from "~/features/auth/core/user.server";
import { getUser } from "~/features/auth/core/user.server";
import * as Seasons from "~/features/mmr/core/Seasons";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { SendouQ } from "../core/SendouQ.server";
@ -7,7 +7,7 @@ import { JOIN_CODE_SEARCH_PARAM_KEY } from "../q-constants";
import { sqRedirectIfNeeded } from "../q-utils.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await getUserId(request);
const user = await getUser();
const code = new URL(request.url).searchParams.get(
JOIN_CODE_SEARCH_PARAM_KEY,

View File

@ -1,12 +1,11 @@
import type { LoaderFunctionArgs } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import * as SQGroupRepository from "~/features/sendouq/SQGroupRepository.server";
import type { SerializeFrom } from "~/utils/remix";
export type TrustersLoaderData = SerializeFrom<typeof loader>;
export const loader = async ({ request }: LoaderFunctionArgs) => {
const { id: userId } = await requireUserId(request);
export const loader = async () => {
const { id: userId } = await requireUser();
return {
trusters: await SQGroupRepository.usersThatTrusted(userId),

View File

@ -7,7 +7,7 @@ import { assertUnreachable } from "~/utils/types";
import { settingsEditSchema } from "../settings-schemas";
export const action = async ({ request }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: settingsEditSchema,

View File

@ -1,9 +1,8 @@
import type { LoaderFunctionArgs } from "react-router";
import { getUserId } from "~/features/auth/core/user.server";
import { getUser } from "~/features/auth/core/user.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await getUserId(request);
export const loader = async () => {
const user = await getUser();
return {
noScreen: user

View File

@ -13,7 +13,7 @@ import { editTeamSchema, teamParamsSchema } from "../team-schemas.server";
import { isTeamManager, isTeamOwner } from "../team-utils";
export const action: ActionFunction = async ({ request, params }) => {
const user = await requireUser(request);
const user = await requireUser();
const { customUrl } = teamParamsSchema.parse(params);
const team = notFoundIfFalsy(await TeamRepository.findByCustomUrl(customUrl));

View File

@ -1,5 +1,5 @@
import type { ActionFunction } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import {
errorToastIfFalsy,
notFoundIfFalsy,
@ -14,7 +14,7 @@ import {
import { isTeamMember, isTeamOwner, resolveNewOwner } from "../team-utils";
export const action: ActionFunction = async ({ request, params }) => {
const user = await requireUserId(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: teamProfilePageActionSchema,

View File

@ -8,7 +8,7 @@ import { TEAM } from "../team-constants";
import { teamParamsSchema } from "../team-schemas.server";
export const action: ActionFunction = async ({ request, params }) => {
const user = await requireUser(request);
const user = await requireUser();
const { customUrl } = teamParamsSchema.parse(params);
const team = notFoundIfFalsy(

View File

@ -12,7 +12,7 @@ import { manageRosterSchema, teamParamsSchema } from "../team-schemas.server";
import { isTeamManager } from "../team-utils";
export const action: ActionFunction = async ({ request, params }) => {
const user = await requireUser(request);
const user = await requireUser();
const { customUrl } = teamParamsSchema.parse(params);
const team = notFoundIfFalsy(await TeamRepository.findByCustomUrl(customUrl));

View File

@ -8,7 +8,7 @@ import { TEAM } from "../team-constants";
import { createTeamSchema } from "../team-schemas.server";
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: createTeamSchema,

View File

@ -7,8 +7,8 @@ import * as TeamRepository from "../TeamRepository.server";
import { teamParamsSchema } from "../team-schemas.server";
import { isTeamManager } from "../team-utils";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
export const loader = async ({ params }: LoaderFunctionArgs) => {
const user = await requireUser();
const { customUrl } = teamParamsSchema.parse(params);
const team = notFoundIfFalsy(await TeamRepository.findByCustomUrl(customUrl));

View File

@ -10,7 +10,7 @@ import { teamParamsSchema } from "../team-schemas.server";
import { isTeamFull, isTeamMember } from "../team-utils";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const { customUrl } = teamParamsSchema.parse(params);
const team = notFoundIfFalsy(

View File

@ -7,8 +7,8 @@ import * as TeamRepository from "../TeamRepository.server";
import { teamParamsSchema } from "../team-schemas.server";
import { isTeamManager } from "../team-utils";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
export const loader = async ({ params }: LoaderFunctionArgs) => {
const user = await requireUser();
const { customUrl } = teamParamsSchema.parse(params);
const team = notFoundIfFalsy(

View File

@ -1,11 +1,10 @@
import type { LoaderFunctionArgs } from "react-router";
import * as R from "remeda";
import type { UserWithPlusTier } from "~/db/tables";
import { getUserId } from "~/features/auth/core/user.server";
import { getUser } from "~/features/auth/core/user.server";
import * as TeamRepository from "../TeamRepository.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await getUserId(request);
export const loader = async () => {
const user = await getUser();
const unsortedTeams = await TeamRepository.findAllUndisbanded();

View File

@ -11,8 +11,8 @@ import {
import { idObject } from "~/utils/zod";
import * as XRankPlacementRepository from "../XRankPlacementRepository.server";
export const action = async ({ request, params }: ActionFunctionArgs) => {
const user = await requireUser(request);
export const action = async ({ params }: ActionFunctionArgs) => {
const user = await requireUser();
const { id } = parseParams({
params,
schema: idObject,

View File

@ -1,5 +1,5 @@
import type { ActionFunctionArgs } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import * as BadgeRepository from "~/features/badges/BadgeRepository.server";
import * as CalendarRepository from "~/features/calendar/CalendarRepository.server";
import * as Seasons from "~/features/mmr/core/Seasons";
@ -41,7 +41,7 @@ import { tournamentBracketsPage } from "~/utils/urls";
import { idObject } from "~/utils/zod";
export const action = async ({ request, params }: ActionFunctionArgs) => {
const user = await requireUserId(request);
const user = await requireUser();
const { id: tournamentId } = parseParams({
params,
schema: idObject,

View File

@ -34,7 +34,7 @@ import {
} from "../tournament-bracket-utils";
export const action: ActionFunction = async ({ params, request }) => {
const user = await requireUser(request);
const user = await requireUser();
const { id: tournamentId } = parseParams({
params,
schema: idObject,

View File

@ -50,7 +50,7 @@ import {
} from "../tournament-bracket-utils";
export const action: ActionFunction = async ({ params, request }) => {
const user = await requireUser(request);
const user = await requireUser();
const { mid: matchId, id: tournamentId } = parseParams({
params,
schema: matchPageParamsSchema,

View File

@ -1,5 +1,5 @@
import { type LoaderFunctionArgs, redirect } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import * as CalendarRepository from "~/features/calendar/CalendarRepository.server";
import * as Seasons from "~/features/mmr/core/Seasons";
import {
@ -21,8 +21,8 @@ import { idObject } from "~/utils/zod";
export type FinalizeTournamentLoaderData = SerializeFrom<typeof loader>;
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUserId(request);
export const loader = async ({ params }: LoaderFunctionArgs) => {
const user = await requireUser();
const { id: tournamentId } = parseParams({
params,
schema: idObject,

View File

@ -6,8 +6,8 @@ import { idObject } from "~/utils/zod";
import type { Unwrapped } from "../../../utils/types";
import { tournamentFromDB } from "../core/Tournament.server";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await getUser(request);
export const loader = async ({ params }: LoaderFunctionArgs) => {
const user = await getUser();
const { id: tournamentId } = parseParams({
params,
schema: idObject,

View File

@ -12,7 +12,7 @@ import { organizationEditSchema } from "../tournament-organization-schemas";
import { organizationFromParams } from "../tournament-organization-utils.server";
export const action = async ({ request, params }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: organizationEditSchema,

View File

@ -19,7 +19,7 @@ import { orgPageActionSchema } from "../tournament-organization-schemas";
import { organizationFromParams } from "../tournament-organization-utils.server";
export const action = async ({ request, params }: ActionFunctionArgs) => {
const user = await requireUser(request);
const user = await requireUser();
const organization = await organizationFromParams(params);
const data = await parseRequestPayload({
request,

View File

@ -8,7 +8,7 @@ import { TOURNAMENT_ORGANIZATION } from "../tournament-organization-constants";
import { newOrganizationSchema } from "../tournament-organization-schemas";
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const user = await requireUser();
requireRole(user, "TOURNAMENT_ADDER");
const data = await parseRequestPayload({

View File

@ -4,8 +4,8 @@ import * as BadgeRepository from "~/features/badges/BadgeRepository.server";
import { requirePermission } from "~/modules/permissions/guards.server";
import { organizationFromParams } from "../tournament-organization-utils.server";
export async function loader({ params, request }: LoaderFunctionArgs) {
const user = await requireUser(request);
export async function loader({ params }: LoaderFunctionArgs) {
const user = await requireUser();
const organization = await organizationFromParams(params);
requirePermission(organization, "EDIT", user);

View File

@ -12,7 +12,7 @@ import { organizationFromParams } from "../tournament-organization-utils.server"
export type OrganizationPageLoaderData = SerializeFrom<typeof loader>;
export async function loader({ params, request }: LoaderFunctionArgs) {
const user = await getUser(request);
const user = await getUser();
const {
month = new Date().getMonth(),
year = new Date().getFullYear(),

View File

@ -15,7 +15,7 @@ import * as TournamentSubRepository from "../TournamentSubRepository.server";
import { subSchema } from "../tournament-subs-schemas.server";
export const action: ActionFunction = async ({ params, request }) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: subSchema,

View File

@ -14,7 +14,7 @@ import { deleteSub } from "../queries/deleteSub.server";
import { deleteSubSchema } from "../tournament-subs-schemas.server";
export const action: ActionFunction = async ({ request, params }) => {
const user = await requireUser(request);
const user = await requireUser();
const { id: tournamentId } = parseParams({
params,
schema: idObject,

View File

@ -7,8 +7,8 @@ import { tournamentSubsPage } from "~/utils/urls";
import { idObject } from "~/utils/zod";
import * as TournamentSubRepository from "../TournamentSubRepository.server";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
export const loader = async ({ params }: LoaderFunctionArgs) => {
const user = await requireUser();
const { id: tournamentId } = parseParams({
params,
schema: idObject,

View File

@ -6,8 +6,8 @@ import { tournamentRegisterPage } from "~/utils/urls";
import { idObject } from "~/utils/zod";
import * as TournamentSubRepository from "../TournamentSubRepository.server";
export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const user = await getUser(request);
export const loader = async ({ params }: LoaderFunctionArgs) => {
const user = await getUser();
const { id: tournamentId } = parseParams({
params,
schema: idObject,

View File

@ -34,7 +34,7 @@ import {
} from "../tournament-utils.server";
export const action: ActionFunction = async ({ request, params }) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: adminActionSchema,

View File

@ -1,6 +1,6 @@
import type { ActionFunction } from "react-router";
import { redirect } from "react-router";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import * as ShowcaseTournaments from "~/features/front-page/core/ShowcaseTournaments.server";
import {
clearTournamentDataCache,
@ -31,7 +31,7 @@ export const action: ActionFunction = async ({ request, params }) => {
params,
schema: idObject,
});
const user = await requireUserId(request);
const user = await requireUser();
const url = new URL(request.url);
const inviteCode = url.searchParams.get("code");
const data = await parseRequestPayload({ request, schema: joinSchema });

View File

@ -38,7 +38,7 @@ import {
} from "../tournament-utils.server";
export const action: ActionFunction = async ({ request, params }) => {
const user = await requireUser(request);
const user = await requireUser();
const { avatarFileName, formData } = await uploadImageIfSubmitted({
request,
fileNamePrefix: "pickup-logo",

View File

@ -20,7 +20,7 @@ export const action: ActionFunction = async ({ request, params }) => {
request,
schema: seedsActionSchema,
});
const user = await requireUser(request);
const user = await requireUser();
const { id: tournamentId } = parseParams({
params,
schema: idObject,

View File

@ -7,8 +7,8 @@ import { parseParams } from "~/utils/remix.server";
import { idObject } from "~/utils/zod";
import { findOwnTournamentTeam } from "../queries/findOwnTournamentTeam.server";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await getUser(request);
export const loader = async ({ params }: LoaderFunctionArgs) => {
const user = await getUser();
if (!user) return null;
const { id: tournamentId } = parseParams({

View File

@ -6,8 +6,8 @@ import { parseParams } from "~/utils/remix.server";
import { tournamentBracketsPage } from "~/utils/urls";
import { idObject } from "~/utils/zod";
export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
export const loader = async ({ params }: LoaderFunctionArgs) => {
const user = await requireUser();
const { id: tournamentId } = parseParams({
params,
schema: idObject,

View File

@ -10,8 +10,8 @@ import { streamsByTournamentId } from "../core/streams.server";
export type TournamentLoaderData = SerializeFrom<typeof loader>;
export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const user = await getUser(request);
export const loader = async ({ params }: LoaderFunctionArgs) => {
const user = await getUser();
const { id: tournamentId } = parseParams({
params,
schema: idObject,

View File

@ -1,5 +1,5 @@
import type { LoaderFunctionArgs } from "react-router";
import { getUserId } from "~/features/auth/core/user.server";
import { getUser } from "~/features/auth/core/user.server";
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
import type { SerializeFrom } from "~/utils/remix";
import { parseSearchParams } from "~/utils/remix.server";
@ -8,7 +8,7 @@ import { tournamentSearchSearchParamsSchema } from "../tournament-schemas.server
export type TournamentSearchLoaderData = SerializeFrom<typeof loader>;
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await getUserId(request);
const user = await getUser();
if (!user) {
return [];
}

View File

@ -12,7 +12,7 @@ import {
import { assertUnreachable } from "~/utils/types";
export const action = async ({ request, params }: ActionFunctionArgs) => {
const loggedInUser = await requireUser(request);
const loggedInUser = await requireUser();
requireRole(loggedInUser, "STAFF");

View File

@ -1,7 +1,7 @@
import type { ActionFunction } from "react-router";
import * as ArtRepository from "~/features/art/ArtRepository.server";
import { userArtPageActionSchema } from "~/features/art/art-schemas.server";
import { requireUserId } from "~/features/auth/core/user.server";
import { requireUser } from "~/features/auth/core/user.server";
import { logger } from "~/utils/logger";
import {
errorToastIfFalsy,
@ -11,7 +11,7 @@ import {
import { assertUnreachable } from "~/utils/types";
export const action: ActionFunction = async ({ request }) => {
const user = await requireUserId(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: userArtPageActionSchema,

View File

@ -32,7 +32,7 @@ import {
} from "~/utils/zod";
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: newBuildActionSchema,

View File

@ -18,7 +18,7 @@ import {
} from "~/utils/zod";
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const user = await requireUser();
const data = await parseRequestPayload({
request,
schema: buildsActionSchema,

Some files were not shown because too many files have changed in this diff Show More