From 4353b9620bb2b47461d1b988856b67c8f775b220 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 25 Jan 2026 22:01:49 +0000 Subject: [PATCH] refactor: move into api/db packages --- apps/web/package.json | 2 -- apps/web/src/pages/api/auth/[...all].ts | 2 +- apps/web/src/pages/api/download/attatchment.ts | 2 +- apps/web/src/pages/api/oss-friends.ts | 2 +- .../src/pages/api/stripe/create_billing_session.ts | 2 +- .../src/pages/api/stripe/create_checkout_session.ts | 2 +- apps/web/src/pages/api/trello/authenticate.ts | 2 +- apps/web/src/pages/api/trpc/[trpc].ts | 2 +- apps/web/src/pages/api/unsubscribe.ts | 2 +- apps/web/src/pages/api/upload/image.ts | 2 +- apps/web/src/pages/api/v1/[...trpc].ts | 2 +- apps/web/src/pages/api/v1/openapi.json.ts | 2 +- packages/api/package.json | 5 +++++ {apps/web => packages/api}/src/utils/rateLimit.ts | 8 ++++---- packages/db/package.json | 5 +++++ {apps/web/src/utils => packages/db/src}/redis.ts | 5 +---- pnpm-lock.yaml | 12 ++++++------ 17 files changed, 32 insertions(+), 27 deletions(-) rename {apps/web => packages/api}/src/utils/rateLimit.ts (94%) rename {apps/web/src/utils => packages/db/src}/redis.ts (88%) diff --git a/apps/web/package.json b/apps/web/package.json index 444c0572..7990ec6a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -48,14 +48,12 @@ "@trpc/server": "catalog:", "date-fns": "^4.1.0", "geist": "^1.3.1", - "ioredis": "^5.9.2", "jose": "^6.1.2", "next": "15.5.9", "next-runtime-env": "^1.7.2", "next-themes": "^0.4.6", "nextjs-cors": "^2.2.0", "posthog-js": "^1.254.0", - "rate-limiter-flexible": "^9.0.1", "react": "catalog:react18", "react-beautiful-dnd": "^13.1.1", "react-contenteditable": "^3.3.7", diff --git a/apps/web/src/pages/api/auth/[...all].ts b/apps/web/src/pages/api/auth/[...all].ts index edde523c..7b4ecb52 100644 --- a/apps/web/src/pages/api/auth/[...all].ts +++ b/apps/web/src/pages/api/auth/[...all].ts @@ -2,7 +2,7 @@ import { toNodeHandler } from "better-auth/node"; import { initAuth } from "@kan/auth/server"; import { createDrizzleClient } from "@kan/db/client"; -import { withRateLimit } from "~/utils/rateLimit"; +import { withRateLimit } from "@kan/api/utils/rateLimit"; export const config = { api: { bodyParser: false } }; diff --git a/apps/web/src/pages/api/download/attatchment.ts b/apps/web/src/pages/api/download/attatchment.ts index ed9a4df8..b697fb2d 100644 --- a/apps/web/src/pages/api/download/attatchment.ts +++ b/apps/web/src/pages/api/download/attatchment.ts @@ -1,6 +1,6 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { withRateLimit } from "~/utils/rateLimit"; +import { withRateLimit } from "@kan/api/utils/rateLimit"; export default withRateLimit( { points: 100, duration: 60 }, diff --git a/apps/web/src/pages/api/oss-friends.ts b/apps/web/src/pages/api/oss-friends.ts index 57a99390..5a61f545 100644 --- a/apps/web/src/pages/api/oss-friends.ts +++ b/apps/web/src/pages/api/oss-friends.ts @@ -1,6 +1,6 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { withRateLimit } from "~/utils/rateLimit"; +import { withRateLimit } from "@kan/api/utils/rateLimit"; export default withRateLimit( { points: 100, duration: 60 }, diff --git a/apps/web/src/pages/api/stripe/create_billing_session.ts b/apps/web/src/pages/api/stripe/create_billing_session.ts index 4c449ed6..e6363506 100644 --- a/apps/web/src/pages/api/stripe/create_billing_session.ts +++ b/apps/web/src/pages/api/stripe/create_billing_session.ts @@ -3,7 +3,7 @@ import { env } from "next-runtime-env"; import { createNextApiContext } from "@kan/api/trpc"; import { createStripeClient } from "@kan/stripe"; -import { withRateLimit } from "~/utils/rateLimit"; +import { withRateLimit } from "@kan/api/utils/rateLimit"; export default withRateLimit( { points: 100, duration: 60 }, diff --git a/apps/web/src/pages/api/stripe/create_checkout_session.ts b/apps/web/src/pages/api/stripe/create_checkout_session.ts index a5fd69c3..78cac3ee 100644 --- a/apps/web/src/pages/api/stripe/create_checkout_session.ts +++ b/apps/web/src/pages/api/stripe/create_checkout_session.ts @@ -6,7 +6,7 @@ import { createNextApiContext } from "@kan/api/trpc"; import * as subscriptionRepo from "@kan/db/repository/subscription.repo"; import * as workspaceRepo from "@kan/db/repository/workspace.repo"; import { createStripeClient } from "@kan/stripe"; -import { withRateLimit } from "~/utils/rateLimit"; +import { withRateLimit } from "@kan/api/utils/rateLimit"; const workspaceSlugSchema = z .string() diff --git a/apps/web/src/pages/api/trello/authenticate.ts b/apps/web/src/pages/api/trello/authenticate.ts index 7155c017..048efdbf 100644 --- a/apps/web/src/pages/api/trello/authenticate.ts +++ b/apps/web/src/pages/api/trello/authenticate.ts @@ -3,7 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { createNextApiContext } from "@kan/api/trpc"; import { integrations } from "@kan/db/schema"; import { addYears } from "date-fns"; -import { withRateLimit } from "~/utils/rateLimit"; +import { withRateLimit } from "@kan/api/utils/rateLimit"; export default withRateLimit( { points: 100, duration: 60 }, diff --git a/apps/web/src/pages/api/trpc/[trpc].ts b/apps/web/src/pages/api/trpc/[trpc].ts index 532f6ac1..de4b9b59 100644 --- a/apps/web/src/pages/api/trpc/[trpc].ts +++ b/apps/web/src/pages/api/trpc/[trpc].ts @@ -4,7 +4,7 @@ import { createNextApiHandler } from "@trpc/server/adapters/next"; import { appRouter } from "@kan/api/root"; import { createTRPCContext } from "@kan/api/trpc"; import { env } from "~/env"; -import { withRateLimit } from "~/utils/rateLimit"; +import { withRateLimit } from "@kan/api/utils/rateLimit"; const nextApiHandler = createNextApiHandler({ router: appRouter, diff --git a/apps/web/src/pages/api/unsubscribe.ts b/apps/web/src/pages/api/unsubscribe.ts index 44093ec2..a4a4430e 100644 --- a/apps/web/src/pages/api/unsubscribe.ts +++ b/apps/web/src/pages/api/unsubscribe.ts @@ -4,7 +4,7 @@ import { jwtVerify } from "jose"; import { z } from "zod"; import { env } from "~/env"; -import { withRateLimit } from "~/utils/rateLimit"; +import { withRateLimit } from "@kan/api/utils/rateLimit"; const requestSchema = z.object({ token: z.string().min(1), diff --git a/apps/web/src/pages/api/upload/image.ts b/apps/web/src/pages/api/upload/image.ts index f9c60b39..1ead915c 100644 --- a/apps/web/src/pages/api/upload/image.ts +++ b/apps/web/src/pages/api/upload/image.ts @@ -6,7 +6,7 @@ import { env as nextRuntimeEnv } from "next-runtime-env"; import { createNextApiContext } from "@kan/api/trpc"; import { env } from "~/env"; -import { withRateLimit } from "~/utils/rateLimit"; +import { withRateLimit } from "@kan/api/utils/rateLimit"; const allowedContentTypes = ["image/jpeg", "image/png"]; diff --git a/apps/web/src/pages/api/v1/[...trpc].ts b/apps/web/src/pages/api/v1/[...trpc].ts index 9a81abcc..3b4bc8d0 100644 --- a/apps/web/src/pages/api/v1/[...trpc].ts +++ b/apps/web/src/pages/api/v1/[...trpc].ts @@ -6,7 +6,7 @@ import { appRouter } from "@kan/api"; import { createRESTContext } from "@kan/api/trpc"; import { env } from "~/env"; -import { withRateLimit } from "~/utils/rateLimit"; +import { withRateLimit } from "@kan/api/utils/rateLimit"; export default withRateLimit( { points: 100, duration: 60 }, diff --git a/apps/web/src/pages/api/v1/openapi.json.ts b/apps/web/src/pages/api/v1/openapi.json.ts index 20dd4b5a..db5e4ea7 100644 --- a/apps/web/src/pages/api/v1/openapi.json.ts +++ b/apps/web/src/pages/api/v1/openapi.json.ts @@ -1,7 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { openApiDocument } from "@kan/api/openapi"; -import { withRateLimit } from "~/utils/rateLimit"; +import { withRateLimit } from "@kan/api/utils/rateLimit"; export default withRateLimit( { points: 100, duration: 60 }, diff --git a/packages/api/package.json b/packages/api/package.json index 7c6e3a0e..292fad98 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -23,6 +23,10 @@ "./openapi": { "types": "./dist/openapi.d.ts", "default": "./src/openapi.ts" + }, + "./utils/rateLimit": { + "types": "./dist/utils/rateLimit.d.ts", + "default": "./src/utils/rateLimit.ts" } }, "license": "GPL-3.0", @@ -43,6 +47,7 @@ "@kan/shared": "workspace:^", "@kan/stripe": "workspace:^", "@trpc/server": "catalog:", + "rate-limiter-flexible": "^9.0.1", "superjson": "2.2.1", "trpc-to-openapi": "^2.3.2", "zod": "catalog:" diff --git a/apps/web/src/utils/rateLimit.ts b/packages/api/src/utils/rateLimit.ts similarity index 94% rename from apps/web/src/utils/rateLimit.ts rename to packages/api/src/utils/rateLimit.ts index 8170b61d..7459f534 100644 --- a/apps/web/src/utils/rateLimit.ts +++ b/packages/api/src/utils/rateLimit.ts @@ -4,7 +4,7 @@ import { RateLimiterMemory, } from "rate-limiter-flexible"; -import { getRedisClient } from "./redis"; +import { getRedisClient } from "@kan/db/redis"; export interface RateLimitOptions { points?: number; @@ -13,7 +13,6 @@ export interface RateLimitOptions { errorMessage?: string; } - const defaultIdentifier = (req: NextApiRequest): string => { // Try to identify the IP address of the request const forwardedFor = req.headers["x-forwarded-for"]; @@ -32,7 +31,6 @@ const defaultIdentifier = (req: NextApiRequest): string => { return ip; }; - const DEFAULT_OPTIONS = { points: 100, duration: 60, @@ -40,7 +38,6 @@ const DEFAULT_OPTIONS = { identifier: defaultIdentifier, } as const; - function createRateLimiter(options: RateLimitOptions = {}) { const redis = getRedisClient(); const points = options.points ?? DEFAULT_OPTIONS.points; @@ -48,6 +45,7 @@ function createRateLimiter(options: RateLimitOptions = {}) { // Use Redis if available, otherwise fall back to in-memory storage if (redis) { + console.log("Using Redis for rate limiting"); return new RateLimiterRedis({ storeClient: redis, points, @@ -55,6 +53,7 @@ function createRateLimiter(options: RateLimitOptions = {}) { }); } + console.log("Using in-memory for rate limiting"); return new RateLimiterMemory({ points, duration, @@ -97,3 +96,4 @@ export function withRateLimit( } }; } + diff --git a/packages/db/package.json b/packages/db/package.json index 4bfa25c9..1363da56 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -23,6 +23,10 @@ "./repository/*": { "types": "./dist/repository/*.d.ts", "default": "./src/repository/*.ts" + }, + "./redis": { + "types": "./dist/redis.d.ts", + "default": "./src/redis.ts" } }, "license": "GPL-3.0", @@ -43,6 +47,7 @@ "@kan/shared": "workspace:^", "drizzle-orm": "^0.42.0", "drizzle-zod": "^0.5.1", + "ioredis": "^5.9.2", "pg": "^8.11.3", "uuid": "^11.1.0", "zod": "catalog:" diff --git a/apps/web/src/utils/redis.ts b/packages/db/src/redis.ts similarity index 88% rename from apps/web/src/utils/redis.ts rename to packages/db/src/redis.ts index fb4c405a..098f2caf 100644 --- a/apps/web/src/utils/redis.ts +++ b/packages/db/src/redis.ts @@ -1,7 +1,5 @@ import Redis from "ioredis"; -import { env } from "~/env"; - let redisClient: Redis | null = null; export function getRedisClient(): Redis | null { @@ -9,7 +7,7 @@ export function getRedisClient(): Redis | null { return redisClient; } - const redisUrl = env.REDIS_URL; + const redisUrl = process.env.REDIS_URL; if (!redisUrl) { return null; @@ -31,4 +29,3 @@ export async function closeRedisClient(): Promise { } } - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27d98693..fec9b156 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -166,9 +166,6 @@ importers: geist: specifier: ^1.3.1 version: 1.4.2(next@15.5.9(@babel/core@7.28.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - ioredis: - specifier: ^5.9.2 - version: 5.9.2 jose: specifier: ^6.1.2 version: 6.1.2 @@ -187,9 +184,6 @@ importers: posthog-js: specifier: ^1.254.0 version: 1.261.0 - rate-limiter-flexible: - specifier: ^9.0.1 - version: 9.0.1 react: specifier: catalog:react18 version: 18.3.1 @@ -320,6 +314,9 @@ importers: '@trpc/server': specifier: 'catalog:' version: 11.5.0(typescript@5.9.2) + rate-limiter-flexible: + specifier: ^9.0.1 + version: 9.0.1 superjson: specifier: 2.2.1 version: 2.2.1 @@ -403,6 +400,9 @@ importers: drizzle-zod: specifier: ^0.5.1 version: 0.5.1(drizzle-orm@0.42.0(@electric-sql/pglite@0.3.7)(@types/pg@8.15.5)(kysely@0.28.8)(pg@8.16.3))(zod@3.25.76) + ioredis: + specifier: ^5.9.2 + version: 5.9.2 pg: specifier: ^8.11.3 version: 8.16.3