develpoer role
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 10s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 43s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m14s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m29s

This commit is contained in:
2026-08-30 12:36:28 -05:00
parent 6bb3ee4bc7
commit f6faf97cdf
19 changed files with 254 additions and 230 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
import { superTokensAdminFunctionMiddleware } from "@/utils/supertokens";
import { superTokensDeveloperFunctionMiddleware } from "@/utils/supertokens";
import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client";
import { z } from "zod";
@@ -17,7 +17,7 @@ const activitySearchParamsSchema = z.object({
export const searchActivities = createServerFn()
.validator(activitySearchParamsSchema)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<ActivityListResult>(async () => {
const result = await pbAdmin.searchActivities(data);
@@ -1,7 +1,6 @@
import { List, Text } from "@mantine/core";
import { Plural, Trans, useLingui } from "@lingui/react/macro";
import {
DatabaseIcon,
TreeStructureIcon,
TrophyIcon,
MedalIcon,
@@ -10,12 +9,14 @@ import {
} from "@phosphor-icons/react";
import ListLink from "@/components/list-link";
import ListButton from "@/components/list-button";
import { useIsDeveloper } from "@/features/core/hooks/use-is-developer";
import { getBadgeMigrationStatus, migrateBadgeProgress } from "@/features/badges/server";
import { useServerQuery } from "@/lib/tanstack-query/hooks";
import toast from "@/lib/sonner";
export const ToolsList = () => {
const { t } = useLingui();
const isDeveloper = useIsDeveloper();
const { data: migrationStatus, refetch: refetchMigrationStatus } = useServerQuery({
queryKey: ["admin", "badge-migration-status"],
@@ -51,11 +52,13 @@ export const ToolsList = () => {
Icon={TrophyIcon}
to="/admin/tournaments"
/>
<ListLink
label={t`Telemetry`}
Icon={PulseIcon}
to="/admin/telemetry/usage"
/>
{isDeveloper && (
<ListLink
label={t`Telemetry`}
Icon={PulseIcon}
to="/admin/telemetry/usage"
/>
)}
<ListLink
label={t`Award Badges`}
Icon={CrownIcon}
@@ -91,13 +94,6 @@ export const ToolsList = () => {
<Trans>Badge recalculation failed. Check the server logs.</Trans>
</Text>
)}
<ListButton
label={t`Open Pocketbase`}
Icon={DatabaseIcon}
onClick={() =>
window.location.replace(process.env.POCKETBASE_URL! + "/_/")
}
/>
<ListLink
label={t`Bracket Preview`}
Icon={TreeStructureIcon}
@@ -0,0 +1,7 @@
import { useAuth } from "@/contexts/auth-context";
import { isDeveloperRole } from "../utils/roles";
export const useIsDeveloper = (): boolean => {
const { roles } = useAuth();
return isDeveloperRole(roles);
};
+5 -1
View File
@@ -1,4 +1,8 @@
export const ADMIN_ROLE = "Admin";
export const DEVELOPER_ROLE = "Developer";
export const isDeveloperRole = (roles?: string[] | null): boolean =>
!!roles?.includes(DEVELOPER_ROLE);
export const isAdminRole = (roles?: string[] | null): boolean =>
!!roles?.includes(ADMIN_ROLE);
!!roles?.includes(ADMIN_ROLE) || isDeveloperRole(roles);
@@ -3,7 +3,7 @@ import { Box, Title, Stack, Switch, Button, Text, Group } from "@mantine/core";
import { Trans, useLingui } from "@lingui/react/macro";
import { useWebPush } from "@/hooks/use-web-push";
import { useAuth } from "@/contexts/auth-context";
import { useIsAdmin } from "@/features/core/hooks/use-is-admin";
import { useIsDeveloper } from "@/features/core/hooks/use-is-developer";
import { updateUserTelemetryAlerts } from "@/features/settings/server";
import toast from "@/lib/sonner";
@@ -40,7 +40,7 @@ const TelemetryAlertsSwitch = () => {
export function NotificationsSection() {
const { supported, configured, permission, subscribed, busy, enable, disable, sendTest } =
useWebPush();
const isAdmin = useIsAdmin();
const isDeveloper = useIsDeveloper();
const { t } = useLingui();
if (!supported || !configured) {
@@ -99,7 +99,7 @@ export function NotificationsSection() {
disabled={busy}
label={t`Enable notifications on this device`}
/>
{subscribed && isAdmin && <TelemetryAlertsSwitch />}
{subscribed && isDeveloper && <TelemetryAlertsSwitch />}
{subscribed && (
<Group>
<Button
+2 -2
View File
@@ -1,6 +1,6 @@
import { createServerFn } from "@tanstack/react-start";
import {
superTokensAdminFunctionMiddleware,
superTokensDeveloperFunctionMiddleware,
superTokensFunctionMiddleware,
} from "@/utils/supertokens";
@@ -145,7 +145,7 @@ export const updateUserAccentColor = createServerFn({ method: "POST" })
export const updateUserTelemetryAlerts = createServerFn({ method: "POST" })
.validator((data: boolean) => data === true)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ context, data }) => {
const { userAuthId, metadata } = context;
if (!userAuthId) return;
+4 -3
View File
@@ -1,4 +1,5 @@
import { superTokensFunctionMiddleware, superTokensAdminFunctionMiddleware } from "@/utils/supertokens";
import { isAdminRole } from "@/features/core/utils/roles";
import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client";
import { z } from "zod";
@@ -35,8 +36,8 @@ export const createTeam = createServerFn()
.handler(async ({ data, context }) =>
toServerResult(async () => {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
const isAdmin = isAdminRole(context.roles);
//if (!isAdmin && !data.players.includes(userId)) {
// throw new Error("You can only create teams that include yourself as a player");
//}
@@ -57,7 +58,7 @@ export const updateTeam = createServerFn()
.handler(async ({ data: { id, updates }, context }) =>
toServerResult(async () => {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
const isAdmin = isAdminRole(context.roles);
const team = await pbAdmin.getTeam(id);
if (!team) {
+10 -10
View File
@@ -1,6 +1,6 @@
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
import { superTokensAdminFunctionMiddleware } from "@/utils/supertokens";
import { superTokensDeveloperFunctionMiddleware } from "@/utils/supertokens";
import { pbAdmin } from "@/lib/pocketbase/client";
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
import {
@@ -37,7 +37,7 @@ const clientEventSearchSchema = z.object({
export const searchClientEvents = createServerFn()
.validator(clientEventSearchSchema)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<TelemetryListResult<ClientEvent>>(async () => {
const result = await pbAdmin.searchClientEvents(data);
@@ -58,7 +58,7 @@ const clientErrorSearchSchema = z.object({
export const searchClientErrors = createServerFn()
.validator(clientErrorSearchSchema)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<TelemetryListResult<ClientError>>(async () => {
const result = await pbAdmin.searchClientErrors(data);
@@ -77,7 +77,7 @@ const errorGroupsSchema = z.object({
// not on the next rollup tick.
export const getClientErrorGroups = createServerFn()
.validator(errorGroupsSchema)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<ClientErrorGroup[]>(async () => {
const { from, to, includeResolved = false, limit = 50 } = data;
@@ -141,7 +141,7 @@ export const getClientErrorGroups = createServerFn()
export const resolveClientErrorGroup = createServerFn({ method: "POST" })
.validator(z.object({ groupHash: z.string(), resolved: z.boolean().optional() }))
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<{ updated: number }>(async () => {
const updated = await pbAdmin.resolveErrorsByGroup(data.groupHash, data.resolved ?? true);
@@ -158,7 +158,7 @@ const rollupRangeSchema = z.object({
export const getRollupRange = createServerFn()
.validator(rollupRangeSchema)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<RollupRow[]>(async () => pbAdmin.getRollupRange(data))
);
@@ -167,7 +167,7 @@ export const listTelemetryAlerts = createServerFn()
.validator(
z.object({ page: z.number().optional(), perPage: z.number().max(200).optional() })
)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<TelemetryListResult<TelemetryAlert>>(async () =>
pbAdmin.listTelemetryAlerts(data.page, data.perPage)
@@ -197,7 +197,7 @@ export const listSessions = createServerFn()
perPage: z.number().max(100).optional(),
})
)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<TelemetryListResult<SessionSummary>>(async () => {
const { days = 7, page = 1, perPage = 30 } = data;
@@ -285,7 +285,7 @@ const MAX_TIMELINE_ENTRIES = 500;
export const getSessionTimeline = createServerFn()
.validator(z.object({ sessionId: z.string().min(8).max(64) }))
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<SessionTimeline>(async () => {
const events = await pbAdmin.searchClientEvents({
@@ -426,7 +426,7 @@ export const getSessionTimeline = createServerFn()
);
export const getTelemetryRuntimeStatus = createServerFn()
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async () =>
toServerResult<TelemetryRuntimeStatus>(async () => {
ensureTelemetryScheduler();
+3 -2
View File
@@ -1,4 +1,5 @@
import { superTokensAdminFunctionMiddleware, superTokensFunctionMiddleware } from "@/utils/supertokens";
import { isAdminRole } from "@/features/core/utils/roles";
import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client";
import { tournamentInputSchema, GroupStanding } from "@/features/tournaments/types";
@@ -46,7 +47,7 @@ export const getTournament = createServerFn()
.validator(z.string())
.middleware([superTokensFunctionMiddleware])
.handler(async ({ data: tournamentId, context }) => {
const isAdmin = context.roles.includes("Admin");
const isAdmin = isAdminRole(context.roles);
return toServerResult(() => pbAdmin.getTournament(tournamentId, isAdmin));
});
@@ -64,7 +65,7 @@ export const enrollTeam = createServerFn()
.handler(async ({ data: { tournamentId, teamId }, context }) =>
toServerResult(async () => {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
const isAdmin = isAdminRole(context.roles);
const team = await pbAdmin.getTeam(teamId);
if (!team) { throw new Error('Team not found'); }