feat: wrap withRateLimit on remaining routes

This commit is contained in:
Henry
2026-01-25 21:10:02 +00:00
parent c9dd45de39
commit c94d08b35c
11 changed files with 86 additions and 65 deletions

View File

@@ -2,9 +2,17 @@ import { toNodeHandler } from "better-auth/node";
import { initAuth } from "@kan/auth/server"; import { initAuth } from "@kan/auth/server";
import { createDrizzleClient } from "@kan/db/client"; import { createDrizzleClient } from "@kan/db/client";
import { withRateLimit } from "~/utils/rateLimit";
export const config = { api: { bodyParser: false } }; export const config = { api: { bodyParser: false } };
export const auth = initAuth(createDrizzleClient()); export const auth = initAuth(createDrizzleClient());
export default toNodeHandler(auth.handler); const authHandler = toNodeHandler(auth.handler);
export default withRateLimit(
{ points: 100, duration: 60 },
async (req, res) => {
return await authHandler(req, res);
},
);

View File

@@ -1,9 +1,10 @@
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler( import { withRateLimit } from "~/utils/rateLimit";
req: NextApiRequest,
res: NextApiResponse, export default withRateLimit(
) { { points: 100, duration: 60 },
async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== "GET") { if (req.method !== "GET") {
return res.status(405).json({ message: "Method not allowed" }); return res.status(405).json({ message: "Method not allowed" });
} }
@@ -44,4 +45,5 @@ export default async function handler(
console.error("Error downloading attachment:", error); console.error("Error downloading attachment:", error);
return res.status(500).json({ message: "Failed to download attachment" }); return res.status(500).json({ message: "Failed to download attachment" });
} }
} },
);

View File

@@ -1,9 +1,10 @@
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler( import { withRateLimit } from "~/utils/rateLimit";
req: NextApiRequest,
res: NextApiResponse, export default withRateLimit(
) { { points: 100, duration: 60 },
async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== "GET") { if (req.method !== "GET") {
return res.status(405).json({ message: "Method not allowed" }); return res.status(405).json({ message: "Method not allowed" });
} }
@@ -20,4 +21,5 @@ export default async function handler(
console.error("Error fetching OSS friends:", error); console.error("Error fetching OSS friends:", error);
return res.status(500).json({ message: "Failed to fetch OSS friends" }); return res.status(500).json({ message: "Failed to fetch OSS friends" });
} }
} },
);

View File

@@ -3,11 +3,11 @@ import { env } from "next-runtime-env";
import { createNextApiContext } from "@kan/api/trpc"; import { createNextApiContext } from "@kan/api/trpc";
import { createStripeClient } from "@kan/stripe"; import { createStripeClient } from "@kan/stripe";
import { withRateLimit } from "~/utils/rateLimit";
export default async function handler( export default withRateLimit(
req: NextApiRequest, { points: 100, duration: 60 },
res: NextApiResponse, async (req: NextApiRequest, res: NextApiResponse) => {
) {
const stripe = createStripeClient(); const stripe = createStripeClient();
if (req.method !== "POST") { if (req.method !== "POST") {
@@ -31,4 +31,5 @@ export default async function handler(
console.error("Error:", error); console.error("Error:", error);
return res.status(500).json({ error: "Error creating portal session" }); return res.status(500).json({ error: "Error creating portal session" });
} }
} },
);

View File

@@ -6,6 +6,7 @@ import { createNextApiContext } from "@kan/api/trpc";
import * as subscriptionRepo from "@kan/db/repository/subscription.repo"; import * as subscriptionRepo from "@kan/db/repository/subscription.repo";
import * as workspaceRepo from "@kan/db/repository/workspace.repo"; import * as workspaceRepo from "@kan/db/repository/workspace.repo";
import { createStripeClient } from "@kan/stripe"; import { createStripeClient } from "@kan/stripe";
import { withRateLimit } from "~/utils/rateLimit";
const workspaceSlugSchema = z const workspaceSlugSchema = z
.string() .string()
@@ -21,10 +22,9 @@ interface CheckoutSessionRequest {
stripeCustomerId: string; stripeCustomerId: string;
} }
export default async function handler( export default withRateLimit(
req: NextApiRequest, { points: 100, duration: 60 },
res: NextApiResponse, async (req: NextApiRequest, res: NextApiResponse) => {
) {
const stripe = createStripeClient(); const stripe = createStripeClient();
if (req.method !== "POST") { if (req.method !== "POST") {
@@ -115,4 +115,5 @@ export default async function handler(
console.error("Error:", error); console.error("Error:", error);
return res.status(500).json({ error: "Error creating checkout session" }); return res.status(500).json({ error: "Error creating checkout session" });
} }
} },
);

View File

@@ -4,6 +4,7 @@ import type { Readable } from "node:stream";
import { createNextApiContext } from "@kan/api/trpc"; import { createNextApiContext } from "@kan/api/trpc";
import * as workspaceRepo from "@kan/db/repository/workspace.repo"; import * as workspaceRepo from "@kan/db/repository/workspace.repo";
import { createStripeClient } from "@kan/stripe"; import { createStripeClient } from "@kan/stripe";
import { withRateLimit } from "~/utils/rateLimit";
async function buffer(readable: Readable) { async function buffer(readable: Readable) {
const chunks = []; const chunks = [];
@@ -13,10 +14,9 @@ async function buffer(readable: Readable) {
return Buffer.concat(chunks); return Buffer.concat(chunks);
} }
export default async function handler( export default withRateLimit(
req: NextApiRequest, { points: 100, duration: 60 },
res: NextApiResponse, async (req: NextApiRequest, res: NextApiResponse) => {
) {
const stripe = createStripeClient(); const stripe = createStripeClient();
if (req.method !== "POST") { if (req.method !== "POST") {
@@ -64,7 +64,8 @@ export default async function handler(
console.error("Webhook error:", err); console.error("Webhook error:", err);
return res.status(400).json({ message: "Webhook handler failed" }); return res.status(400).json({ message: "Webhook handler failed" });
} }
} },
);
export const config = { export const config = {
api: { api: {

View File

@@ -3,11 +3,11 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { createNextApiContext } from "@kan/api/trpc"; import { createNextApiContext } from "@kan/api/trpc";
import { integrations } from "@kan/db/schema"; import { integrations } from "@kan/db/schema";
import { addYears } from "date-fns"; import { addYears } from "date-fns";
import { withRateLimit } from "~/utils/rateLimit";
export default async function handler( export default withRateLimit(
req: NextApiRequest, { points: 100, duration: 60 },
res: NextApiResponse, async (req: NextApiRequest, res: NextApiResponse) => {
) {
if (req.method !== "POST") { if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" }); return res.status(405).json({ message: "Method not allowed" });
} }
@@ -48,4 +48,5 @@ export default async function handler(
console.error("Trello authentication error:", err); console.error("Trello authentication error:", err);
return res.status(400).json({ message: "Trello authentication failed" }); return res.status(400).json({ message: "Trello authentication failed" });
} }
} },
);

View File

@@ -4,6 +4,7 @@ import { jwtVerify } from "jose";
import { z } from "zod"; import { z } from "zod";
import { env } from "~/env"; import { env } from "~/env";
import { withRateLimit } from "~/utils/rateLimit";
const requestSchema = z.object({ const requestSchema = z.object({
token: z.string().min(1), token: z.string().min(1),
@@ -19,10 +20,9 @@ type ResponseData =
const textEncoder = new TextEncoder(); const textEncoder = new TextEncoder();
export default async function handler( export default withRateLimit(
req: NextApiRequest, { points: 100, duration: 60 },
res: NextApiResponse<ResponseData>, async (req: NextApiRequest, res: NextApiResponse<ResponseData>) => {
) {
if (process.env.NEXT_PUBLIC_KAN_ENV !== "cloud") { if (process.env.NEXT_PUBLIC_KAN_ENV !== "cloud") {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
@@ -101,4 +101,5 @@ export default async function handler(
} }
return res.status(200).json({ success: true }); return res.status(200).json({ success: true });
} },
);

View File

@@ -6,13 +6,13 @@ import { env as nextRuntimeEnv } from "next-runtime-env";
import { createNextApiContext } from "@kan/api/trpc"; import { createNextApiContext } from "@kan/api/trpc";
import { env } from "~/env"; import { env } from "~/env";
import { withRateLimit } from "~/utils/rateLimit";
const allowedContentTypes = ["image/jpeg", "image/png"]; const allowedContentTypes = ["image/jpeg", "image/png"];
export default async function handler( export default withRateLimit(
req: NextApiRequest, { points: 100, duration: 60 },
res: NextApiResponse, async (req: NextApiRequest, res: NextApiResponse) => {
) {
if (req.method !== "POST") { if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" }); return res.status(405).json({ error: "Method not allowed" });
} }
@@ -71,4 +71,5 @@ export default async function handler(
} catch (error) { } catch (error) {
return res.status(500).json({ error: (error as Error).message }); return res.status(500).json({ error: (error as Error).message });
} }
} },
);

View File

@@ -6,25 +6,26 @@ import { appRouter } from "@kan/api";
import { createRESTContext } from "@kan/api/trpc"; import { createRESTContext } from "@kan/api/trpc";
import { env } from "~/env"; import { env } from "~/env";
import { withRateLimit } from "~/utils/rateLimit";
export default async function handler( export default withRateLimit(
req: NextApiRequest, { points: 100, duration: 60 },
res: NextApiResponse, async (req: NextApiRequest, res: NextApiResponse) => {
) { await cors(req, res);
await cors(req, res);
const openApiHandler = createOpenApiNextHandler({ const openApiHandler = createOpenApiNextHandler({
router: appRouter, router: appRouter,
createContext: createRESTContext, createContext: createRESTContext,
onError: onError:
env.NODE_ENV === "development" env.NODE_ENV === "development"
? ({ path, error }) => { ? ({ path, error }) => {
console.error( console.error(
`❌ REST failed on ${path ?? "<no-path>"}: ${error.message}`, `❌ REST failed on ${path ?? "<no-path>"}: ${error.message}`,
); );
} }
: undefined, : undefined,
}); });
return await openApiHandler(req, res); return await openApiHandler(req, res);
} },
);

View File

@@ -1,9 +1,11 @@
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { openApiDocument } from "@kan/api/openapi"; import { openApiDocument } from "@kan/api/openapi";
import { withRateLimit } from "~/utils/rateLimit";
const handler = (req: NextApiRequest, res: NextApiResponse) => { export default withRateLimit(
res.status(200).send(openApiDocument); { points: 100, duration: 60 },
}; (req: NextApiRequest, res: NextApiResponse) => {
res.status(200).send(openApiDocument);
export default handler; },
);