Skip to content

Commit 7ce1191

Browse files
committed
coderabbit fixes
1 parent 9471fcc commit 7ce1191

1 file changed

Lines changed: 80 additions & 70 deletions

File tree

apps/webapp/app/routes/admin.back-office.orgs.$orgId.tsx

Lines changed: 80 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Form, useNavigation, useSearchParams } from "@remix-run/react";
22
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/server-runtime";
33
import { json } from "@remix-run/server-runtime";
4-
import { Prisma } from "@trigger.dev/database";
54
import { useEffect, useState } from "react";
65
import { redirect, typedjson, useTypedActionData, useTypedLoaderData } from "remix-typedjson";
76
import { z } from "zod";
@@ -17,12 +16,15 @@ import { prisma } from "~/db.server";
1716
import { env } from "~/env.server";
1817
import {
1918
RateLimitTokenBucketConfig,
20-
type RateLimiterConfig,
19+
RateLimiterConfig,
2120
} from "~/services/authorizationRateLimitMiddleware.server";
2221
import { logger } from "~/services/logger.server";
2322
import { type Duration } from "~/services/rateLimiter.server";
2423
import { requireUser } from "~/services/session.server";
2524

25+
const SAVED_QUERY_KEY = "saved";
26+
const SAVED_QUERY_VALUE = "1";
27+
2628
type EffectiveRateLimit = {
2729
source: "override" | "default";
2830
config: RateLimiterConfig;
@@ -41,12 +43,12 @@ function resolveEffectiveRateLimit(override: unknown): EffectiveRateLimit {
4143
if (override == null) {
4244
return { source: "default", config: systemDefaultRateLimit() };
4345
}
44-
const parsed = RateLimitTokenBucketConfig.safeParse(override);
46+
const parsed = RateLimiterConfig.safeParse(override);
4547
if (parsed.success) {
4648
return { source: "override", config: parsed.data };
4749
}
48-
// Override exists but isn't tokenBucket (fixedWindow/slidingWindow). We can't
49-
// edit it from this UI — show the default and let the admin know.
50+
// Column holds malformed JSON — fall back silently. Admin must investigate
51+
// at the DB level; this UI can't recover it.
5052
return { source: "default", config: systemDefaultRateLimit() };
5153
}
5254

@@ -110,32 +112,25 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
110112
}
111113

112114
const effective = resolveEffectiveRateLimit(org.apiRateLimiterConfig);
113-
const overrideIsIncompatible =
114-
org.apiRateLimiterConfig != null && effective.source === "default";
115115

116116
return typedjson({
117117
org,
118118
effective,
119-
overrideIsIncompatible,
120119
});
121120
}
122121

123122
const SetRateLimitSchema = z.object({
124123
intent: z.literal("set-rate-limit"),
125124
refillRate: z.coerce.number().int().min(1),
126-
interval: z.string().min(1),
125+
interval: z
126+
.string()
127+
.trim()
128+
.refine((v) => parseDurationToMs(v) > 0, {
129+
message: "Must be a duration like 10s, 1m, 500ms.",
130+
}),
127131
maxTokens: z.coerce.number().int().min(1),
128132
});
129133

130-
const ResetRateLimitSchema = z.object({
131-
intent: z.literal("reset-rate-limit"),
132-
});
133-
134-
const ActionSchema = z.discriminatedUnion("intent", [
135-
SetRateLimitSchema,
136-
ResetRateLimitSchema,
137-
]);
138-
139134
export async function action({ request, params }: ActionFunctionArgs) {
140135
const user = await requireUser(request);
141136
if (!user.admin) {
@@ -148,7 +143,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
148143
}
149144

150145
const formData = await request.formData();
151-
const submission = ActionSchema.safeParse(Object.fromEntries(formData));
146+
const submission = SetRateLimitSchema.safeParse(Object.fromEntries(formData));
152147
if (!submission.success) {
153148
return json(
154149
{ errors: submission.error.flatten().fieldErrors },
@@ -164,46 +159,39 @@ export async function action({ request, params }: ActionFunctionArgs) {
164159
throw new Response(null, { status: 404 });
165160
}
166161

167-
let next: RateLimiterConfig | null;
168-
if (submission.data.intent === "set-rate-limit") {
169-
const built = RateLimitTokenBucketConfig.safeParse({
170-
type: "tokenBucket",
171-
refillRate: submission.data.refillRate,
172-
interval: submission.data.interval,
173-
maxTokens: submission.data.maxTokens,
174-
});
175-
if (!built.success) {
176-
return json(
177-
{ errors: built.error.flatten().fieldErrors },
178-
{ status: 400 }
179-
);
180-
}
181-
next = built.data;
182-
} else {
183-
next = null;
162+
const built = RateLimitTokenBucketConfig.safeParse({
163+
type: "tokenBucket",
164+
refillRate: submission.data.refillRate,
165+
interval: submission.data.interval,
166+
maxTokens: submission.data.maxTokens,
167+
});
168+
if (!built.success) {
169+
return json(
170+
{ errors: built.error.flatten().fieldErrors },
171+
{ status: 400 }
172+
);
184173
}
174+
const next = built.data;
185175

186176
await prisma.organization.update({
187177
where: { id: orgId },
188-
data: {
189-
apiRateLimiterConfig: next === null ? Prisma.JsonNull : (next as any),
190-
},
178+
data: { apiRateLimiterConfig: next as any },
191179
});
192180

193181
logger.info("admin.backOffice.rateLimit", {
194182
adminUserId: user.id,
195183
orgId,
196-
intent: submission.data.intent,
197184
previous: existing.apiRateLimiterConfig,
198185
next,
199186
});
200187

201-
return redirect(`/admin/back-office/orgs/${orgId}?saved=1`);
188+
return redirect(
189+
`/admin/back-office/orgs/${orgId}?${SAVED_QUERY_KEY}=${SAVED_QUERY_VALUE}`
190+
);
202191
}
203192

204193
export default function BackOfficeOrgPage() {
205-
const { org, effective, overrideIsIncompatible } =
206-
useTypedLoaderData<typeof loader>();
194+
const { org, effective } = useTypedLoaderData<typeof loader>();
207195
const actionData = useTypedActionData<typeof action>();
208196
const navigation = useNavigation();
209197
const isSubmitting = navigation.state !== "idle";
@@ -232,7 +220,7 @@ export default function BackOfficeOrgPage() {
232220
);
233221

234222
const [searchParams, setSearchParams] = useSearchParams();
235-
const savedJustNow = searchParams.get("saved") === "1";
223+
const savedJustNow = searchParams.get(SAVED_QUERY_KEY) === SAVED_QUERY_VALUE;
236224

237225
// If a submit comes back with validation errors, re-open edit mode so the
238226
// admin can see and correct them without clicking Edit again.
@@ -252,7 +240,7 @@ export default function BackOfficeOrgPage() {
252240
const t = setTimeout(() => {
253241
setSearchParams(
254242
(prev) => {
255-
prev.delete("saved");
243+
prev.delete(SAVED_QUERY_KEY);
256244
return prev;
257245
},
258246
{ replace: true, preventScrollReset: true }
@@ -303,7 +291,7 @@ export default function BackOfficeOrgPage() {
303291
<Button
304292
variant="tertiary/small"
305293
onClick={() => setIsEditing(true)}
306-
disabled={isSubmitting}
294+
disabled={isSubmitting || effective.config.type !== "tokenBucket"}
307295
>
308296
Edit
309297
</Button>
@@ -323,34 +311,56 @@ export default function BackOfficeOrgPage() {
323311
{effective.source === "override"
324312
? "Custom override active."
325313
: "Using system default."}
326-
{overrideIsIncompatible && (
327-
<span className="ml-2 text-amber-500">
328-
(An override exists but is not a tokenBucket — not editable here.)
329-
</span>
330-
)}
331314
</Paragraph>
332315

333316
{!isEditing ? (
334-
<Property.Table>
335-
{currentDescription ? (
336-
<>
337-
<Property.Item>
338-
<Property.Label>Sustained rate</Property.Label>
339-
<Property.Value>{currentDescription.sustained}</Property.Value>
340-
</Property.Item>
341-
<Property.Item>
342-
<Property.Label>Burst allowance</Property.Label>
343-
<Property.Value>{currentDescription.burst}</Property.Value>
344-
</Property.Item>
345-
</>
346-
) : (
347-
<Property.Item>
348-
<Property.Value>
349-
No editable rate limit configured.
350-
</Property.Value>
351-
</Property.Item>
317+
<>
318+
<Property.Table>
319+
{effective.config.type === "tokenBucket" ? (
320+
currentDescription ? (
321+
<>
322+
<Property.Item>
323+
<Property.Label>Sustained rate</Property.Label>
324+
<Property.Value>{currentDescription.sustained}</Property.Value>
325+
</Property.Item>
326+
<Property.Item>
327+
<Property.Label>Burst allowance</Property.Label>
328+
<Property.Value>{currentDescription.burst}</Property.Value>
329+
</Property.Item>
330+
</>
331+
) : (
332+
<Property.Item>
333+
<Property.Value>
334+
Invalid interval on the stored config.
335+
</Property.Value>
336+
</Property.Item>
337+
)
338+
) : (
339+
<>
340+
<Property.Item>
341+
<Property.Label>Type</Property.Label>
342+
<Property.Value>{effective.config.type}</Property.Value>
343+
</Property.Item>
344+
<Property.Item>
345+
<Property.Label>Window</Property.Label>
346+
<Property.Value>{String(effective.config.window)}</Property.Value>
347+
</Property.Item>
348+
<Property.Item>
349+
<Property.Label>Tokens</Property.Label>
350+
<Property.Value>
351+
{effective.config.tokens.toLocaleString()}
352+
</Property.Value>
353+
</Property.Item>
354+
</>
355+
)}
356+
</Property.Table>
357+
{effective.config.type !== "tokenBucket" && (
358+
<Paragraph variant="small" className="text-amber-500">
359+
This override is a {effective.config.type} limit and can't be
360+
edited from this form. Change it in the database directly.
361+
</Paragraph>
352362
)}
353-
</Property.Table>
363+
</>
354364
) : (
355365
<Form method="post" className="flex flex-col gap-3 pt-2">
356366
<input type="hidden" name="intent" value="set-rate-limit" />

0 commit comments

Comments
 (0)