refactor: move into api/db packages

This commit is contained in:
Henry
2026-01-25 22:01:49 +00:00
parent 327089e10e
commit 4353b9620b
17 changed files with 32 additions and 27 deletions

View File

@@ -48,14 +48,12 @@
"@trpc/server": "catalog:", "@trpc/server": "catalog:",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"geist": "^1.3.1", "geist": "^1.3.1",
"ioredis": "^5.9.2",
"jose": "^6.1.2", "jose": "^6.1.2",
"next": "15.5.9", "next": "15.5.9",
"next-runtime-env": "^1.7.2", "next-runtime-env": "^1.7.2",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"nextjs-cors": "^2.2.0", "nextjs-cors": "^2.2.0",
"posthog-js": "^1.254.0", "posthog-js": "^1.254.0",
"rate-limiter-flexible": "^9.0.1",
"react": "catalog:react18", "react": "catalog:react18",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-contenteditable": "^3.3.7", "react-contenteditable": "^3.3.7",

View File

@@ -2,7 +2,7 @@ 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"; import { withRateLimit } from "@kan/api/utils/rateLimit";
export const config = { api: { bodyParser: false } }; export const config = { api: { bodyParser: false } };

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { withRateLimit } from "~/utils/rateLimit"; import { withRateLimit } from "@kan/api/utils/rateLimit";
export default withRateLimit( export default withRateLimit(
{ points: 100, duration: 60 }, { points: 100, duration: 60 },

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { withRateLimit } from "~/utils/rateLimit"; import { withRateLimit } from "@kan/api/utils/rateLimit";
export default withRateLimit( export default withRateLimit(
{ points: 100, duration: 60 }, { points: 100, duration: 60 },

View File

@@ -3,7 +3,7 @@ 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"; import { withRateLimit } from "@kan/api/utils/rateLimit";
export default withRateLimit( export default withRateLimit(
{ points: 100, duration: 60 }, { points: 100, duration: 60 },

View File

@@ -6,7 +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"; import { withRateLimit } from "@kan/api/utils/rateLimit";
const workspaceSlugSchema = z const workspaceSlugSchema = z
.string() .string()

View File

@@ -3,7 +3,7 @@ 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"; import { withRateLimit } from "@kan/api/utils/rateLimit";
export default withRateLimit( export default withRateLimit(
{ points: 100, duration: 60 }, { points: 100, duration: 60 },

View File

@@ -4,7 +4,7 @@ import { createNextApiHandler } from "@trpc/server/adapters/next";
import { appRouter } from "@kan/api/root"; import { appRouter } from "@kan/api/root";
import { createTRPCContext } from "@kan/api/trpc"; import { createTRPCContext } from "@kan/api/trpc";
import { env } from "~/env"; import { env } from "~/env";
import { withRateLimit } from "~/utils/rateLimit"; import { withRateLimit } from "@kan/api/utils/rateLimit";
const nextApiHandler = createNextApiHandler({ const nextApiHandler = createNextApiHandler({
router: appRouter, router: appRouter,

View File

@@ -4,7 +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"; import { withRateLimit } from "@kan/api/utils/rateLimit";
const requestSchema = z.object({ const requestSchema = z.object({
token: z.string().min(1), token: z.string().min(1),

View File

@@ -6,7 +6,7 @@ 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"; import { withRateLimit } from "@kan/api/utils/rateLimit";
const allowedContentTypes = ["image/jpeg", "image/png"]; const allowedContentTypes = ["image/jpeg", "image/png"];

View File

@@ -6,7 +6,7 @@ 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"; import { withRateLimit } from "@kan/api/utils/rateLimit";
export default withRateLimit( export default withRateLimit(
{ points: 100, duration: 60 }, { points: 100, duration: 60 },

View File

@@ -1,7 +1,7 @@
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"; import { withRateLimit } from "@kan/api/utils/rateLimit";
export default withRateLimit( export default withRateLimit(
{ points: 100, duration: 60 }, { points: 100, duration: 60 },

View File

@@ -23,6 +23,10 @@
"./openapi": { "./openapi": {
"types": "./dist/openapi.d.ts", "types": "./dist/openapi.d.ts",
"default": "./src/openapi.ts" "default": "./src/openapi.ts"
},
"./utils/rateLimit": {
"types": "./dist/utils/rateLimit.d.ts",
"default": "./src/utils/rateLimit.ts"
} }
}, },
"license": "GPL-3.0", "license": "GPL-3.0",
@@ -43,6 +47,7 @@
"@kan/shared": "workspace:^", "@kan/shared": "workspace:^",
"@kan/stripe": "workspace:^", "@kan/stripe": "workspace:^",
"@trpc/server": "catalog:", "@trpc/server": "catalog:",
"rate-limiter-flexible": "^9.0.1",
"superjson": "2.2.1", "superjson": "2.2.1",
"trpc-to-openapi": "^2.3.2", "trpc-to-openapi": "^2.3.2",
"zod": "catalog:" "zod": "catalog:"

View File

@@ -4,7 +4,7 @@ import {
RateLimiterMemory, RateLimiterMemory,
} from "rate-limiter-flexible"; } from "rate-limiter-flexible";
import { getRedisClient } from "./redis"; import { getRedisClient } from "@kan/db/redis";
export interface RateLimitOptions { export interface RateLimitOptions {
points?: number; points?: number;
@@ -13,7 +13,6 @@ export interface RateLimitOptions {
errorMessage?: string; errorMessage?: string;
} }
const defaultIdentifier = (req: NextApiRequest): string => { const defaultIdentifier = (req: NextApiRequest): string => {
// Try to identify the IP address of the request // Try to identify the IP address of the request
const forwardedFor = req.headers["x-forwarded-for"]; const forwardedFor = req.headers["x-forwarded-for"];
@@ -32,7 +31,6 @@ const defaultIdentifier = (req: NextApiRequest): string => {
return ip; return ip;
}; };
const DEFAULT_OPTIONS = { const DEFAULT_OPTIONS = {
points: 100, points: 100,
duration: 60, duration: 60,
@@ -40,7 +38,6 @@ const DEFAULT_OPTIONS = {
identifier: defaultIdentifier, identifier: defaultIdentifier,
} as const; } as const;
function createRateLimiter(options: RateLimitOptions = {}) { function createRateLimiter(options: RateLimitOptions = {}) {
const redis = getRedisClient(); const redis = getRedisClient();
const points = options.points ?? DEFAULT_OPTIONS.points; 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 // Use Redis if available, otherwise fall back to in-memory storage
if (redis) { if (redis) {
console.log("Using Redis for rate limiting");
return new RateLimiterRedis({ return new RateLimiterRedis({
storeClient: redis, storeClient: redis,
points, points,
@@ -55,6 +53,7 @@ function createRateLimiter(options: RateLimitOptions = {}) {
}); });
} }
console.log("Using in-memory for rate limiting");
return new RateLimiterMemory({ return new RateLimiterMemory({
points, points,
duration, duration,
@@ -97,3 +96,4 @@ export function withRateLimit(
} }
}; };
} }

View File

@@ -23,6 +23,10 @@
"./repository/*": { "./repository/*": {
"types": "./dist/repository/*.d.ts", "types": "./dist/repository/*.d.ts",
"default": "./src/repository/*.ts" "default": "./src/repository/*.ts"
},
"./redis": {
"types": "./dist/redis.d.ts",
"default": "./src/redis.ts"
} }
}, },
"license": "GPL-3.0", "license": "GPL-3.0",
@@ -43,6 +47,7 @@
"@kan/shared": "workspace:^", "@kan/shared": "workspace:^",
"drizzle-orm": "^0.42.0", "drizzle-orm": "^0.42.0",
"drizzle-zod": "^0.5.1", "drizzle-zod": "^0.5.1",
"ioredis": "^5.9.2",
"pg": "^8.11.3", "pg": "^8.11.3",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"zod": "catalog:" "zod": "catalog:"

View File

@@ -1,7 +1,5 @@
import Redis from "ioredis"; import Redis from "ioredis";
import { env } from "~/env";
let redisClient: Redis | null = null; let redisClient: Redis | null = null;
export function getRedisClient(): Redis | null { export function getRedisClient(): Redis | null {
@@ -9,7 +7,7 @@ export function getRedisClient(): Redis | null {
return redisClient; return redisClient;
} }
const redisUrl = env.REDIS_URL; const redisUrl = process.env.REDIS_URL;
if (!redisUrl) { if (!redisUrl) {
return null; return null;
@@ -31,4 +29,3 @@ export async function closeRedisClient(): Promise<void> {
} }
} }

12
pnpm-lock.yaml generated
View File

@@ -166,9 +166,6 @@ importers:
geist: geist:
specifier: ^1.3.1 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)) 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: jose:
specifier: ^6.1.2 specifier: ^6.1.2
version: 6.1.2 version: 6.1.2
@@ -187,9 +184,6 @@ importers:
posthog-js: posthog-js:
specifier: ^1.254.0 specifier: ^1.254.0
version: 1.261.0 version: 1.261.0
rate-limiter-flexible:
specifier: ^9.0.1
version: 9.0.1
react: react:
specifier: catalog:react18 specifier: catalog:react18
version: 18.3.1 version: 18.3.1
@@ -320,6 +314,9 @@ importers:
'@trpc/server': '@trpc/server':
specifier: 'catalog:' specifier: 'catalog:'
version: 11.5.0(typescript@5.9.2) version: 11.5.0(typescript@5.9.2)
rate-limiter-flexible:
specifier: ^9.0.1
version: 9.0.1
superjson: superjson:
specifier: 2.2.1 specifier: 2.2.1
version: 2.2.1 version: 2.2.1
@@ -403,6 +400,9 @@ importers:
drizzle-zod: drizzle-zod:
specifier: ^0.5.1 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) 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: pg:
specifier: ^8.11.3 specifier: ^8.11.3
version: 8.16.3 version: 8.16.3