Move app to apps/web-react in pnpm workspace layout

This commit is contained in:
Kalle
2026-08-16 09:20:33 +03:00
parent 7e365ccfcf
commit bbc8ea57af
2913 changed files with 227 additions and 250 deletions

View File

@@ -0,0 +1,33 @@
import { requireUser } from "~/features/auth/core/user.server";
import type { EntityWithPermissions, Role } from "~/modules/permissions/types";
import { hasPermission } from "./utils";
/**
* Checks if a user has the required global role.
*
* @throws {Response} - Throws a 403 Forbidden response if the user does not have the required role.
*/
export function requireRole(role: Role) {
const user = requireUser();
if (!user.roles.includes(role)) {
throw new Response("Forbidden", { status: 403 });
}
}
/**
* Checks if a user has the required permission to perform an action on a given entity.
*
* @throws {Response} - Throws a 403 Forbidden response if the user does not have the required permission.
*/
export function requirePermission<
T extends EntityWithPermissions,
K extends keyof T["permissions"],
>(obj: T, permission: K) {
const user = requireUser();
if (hasPermission(obj, permission, user)) {
return;
}
throw new Response("Forbidden", { status: 403 });
}

View File

@@ -0,0 +1,30 @@
import { useUser } from "~/features/auth/core/user";
import type { EntityWithPermissions, Role } from "~/modules/permissions/types";
import { hasPermission } from "./utils";
/**
* Determines whether a user has a specific global role.
*
* @returns A boolean indicating whether the user has the specified role. Always false if user is not logged in.
*/
export function useHasRole(role: Role) {
const user = useUser();
if (!user) return false;
return user.roles.includes(role);
}
/**
* Determines whether a user has a specific permission for a given entity.
*
* @returns A boolean indicating whether the user has the specified permission. Always false if user is not logged in.
*/
export function useHasPermission<
T extends EntityWithPermissions,
K extends keyof T["permissions"],
>(obj: T, permission: K) {
const user = useUser();
return hasPermission(obj, permission, user);
}

View File

@@ -0,0 +1,81 @@
import type { UserWithPlusTier } from "~/utils/kysely.server";
import { userDiscordIdIsAged } from "~/utils/users";
import type { Role } from "./types";
import {
isAdmin,
isDev,
isQa,
isScannerTester,
isStaff,
isSupporter,
} from "./utils";
export function userRoles(
user: Pick<
UserWithPlusTier,
| "id"
| "discordId"
| "plusTier"
| "isArtist"
| "isTournamentOrganizer"
| "isVideoAdder"
| "isApiAccesser"
| "patronTier"
>,
) {
const result: Array<Role> = [];
if (isAdmin(user)) {
result.push("ADMIN");
}
if (isStaff(user) || isAdmin(user)) {
result.push("STAFF");
}
if (isDev(user)) {
result.push("DEV");
}
if (isQa(user)) {
result.push("QA");
}
if (isScannerTester(user)) {
result.push("SCANNER_TESTER");
}
if (typeof user.patronTier === "number") {
result.push("MINOR_SUPPORT");
}
if (isSupporter(user)) {
result.push("SUPPORTER");
}
if (typeof user.plusTier === "number") {
result.push("PLUS_SERVER_MEMBER");
}
if (user.isArtist) {
result.push("ARTIST");
}
if (user.isVideoAdder) {
result.push("VIDEO_ADDER");
}
if (user.isTournamentOrganizer || isSupporter(user)) {
result.push("TOURNAMENT_ADDER");
}
if (userDiscordIdIsAged(user) || isSupporter(user)) {
result.push("CALENDAR_EVENT_ADDER");
}
if (user.isTournamentOrganizer || user.isApiAccesser || isSupporter(user)) {
result.push("API_ACCESSER");
}
return result;
}

View File

@@ -0,0 +1,21 @@
export type Permissions = Record<string, number[]>;
export type EntityWithPermissions = {
permissions: Permissions;
};
/** Represents a "global" role with permissions associated to it */
export type Role =
| "ADMIN"
| "STAFF"
| "PLUS_SERVER_MEMBER"
| "VIDEO_ADDER"
| "ARTIST"
| "CALENDAR_EVENT_ADDER"
| "TOURNAMENT_ADDER"
| "API_ACCESSER"
| "QA"
| "DEV"
| "SCANNER_TESTER"
| "SUPPORTER" // patrons of "Supporter" tier or higher
| "MINOR_SUPPORT"; // patrons of "Support" tier or higher

View File

@@ -0,0 +1,53 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
import { ADMIN_ID } from "~/features/admin/admin-constants";
const e2e = vi.hoisted(() => ({ isTestRun: false }));
vi.mock("~/utils/e2e", () => ({
get IS_E2E_TEST_RUN() {
return e2e.isTestRun;
},
}));
import { hasPermission } from "./utils";
const REGULAR_USER_ID = ADMIN_ID + 1;
const OTHER_USER_ID = ADMIN_ID + 2;
const entity = { permissions: { EDIT: [REGULAR_USER_ID] } };
describe("hasPermission", () => {
beforeEach(() => {
e2e.isTestRun = false;
vi.unstubAllEnvs();
});
test("returns false for a logged out user", () => {
expect(hasPermission(entity, "EDIT", null)).toBe(false);
});
test("returns true for a user holding the permission", () => {
expect(hasPermission(entity, "EDIT", { id: REGULAR_USER_ID })).toBe(true);
});
test("returns false for a user not holding the permission", () => {
expect(hasPermission(entity, "EDIT", { id: OTHER_USER_ID })).toBe(false);
});
test("admin does not bypass the permission outside production", () => {
expect(hasPermission(entity, "EDIT", { id: ADMIN_ID })).toBe(false);
});
test("admin bypasses the permission in production", () => {
vi.stubEnv("NODE_ENV", "production");
expect(hasPermission(entity, "EDIT", { id: ADMIN_ID })).toBe(true);
});
test("admin does not bypass the permission in an e2e test run", () => {
vi.stubEnv("NODE_ENV", "production");
e2e.isTestRun = true;
expect(hasPermission(entity, "EDIT", { id: ADMIN_ID })).toBe(false);
});
});

View File

@@ -0,0 +1,65 @@
import {
ADMIN_ID,
DEV_IDS,
QA_IDS,
SCANNER_TESTER_IDS,
STAFF_IDS,
} from "~/features/admin/admin-constants";
import { IS_E2E_TEST_RUN } from "~/utils/e2e";
import type { EntityWithPermissions } from "./types";
/**
* Determines whether a user has a specific permission for a given entity.
* Single source of truth shared by `requirePermission` and `useHasPermission`.
*
* @returns A boolean indicating whether the user has the specified permission. Always false if user is not logged in.
*/
export function hasPermission<
T extends EntityWithPermissions,
K extends keyof T["permissions"],
>(obj: T, permission: K, user?: { id: number } | null) {
if (!user) return false;
// admin can do anything in production but not in development or e2e tests for better testing
if (
process.env.NODE_ENV === "production" &&
!IS_E2E_TEST_RUN &&
isAdmin(user)
) {
return true;
}
return (obj.permissions as Record<K, number[]>)[permission].includes(user.id);
}
export function isAdmin(user?: { id: number }) {
return user?.id === ADMIN_ID;
}
export function isStaff(user?: { id: number }) {
if (!user) return false;
return STAFF_IDS.includes(user.id);
}
export function isDev(user?: { id: number }) {
if (!user) return false;
return DEV_IDS.includes(user.id);
}
export function isQa(user?: { id: number }) {
if (!user) return false;
return QA_IDS.includes(user.id);
}
export function isScannerTester(user?: { id: number }) {
if (!user) return false;
return SCANNER_TESTER_IDS.includes(user.id);
}
export function isSupporter(user?: { patronTier: number | null }) {
return typeof user?.patronTier === "number" && user.patronTier >= 2;
}