open-prompt / src /lib /api-utils.ts
GitHub Action
Automated sync to Hugging Face
bcce530
import { NextRequest, NextResponse } from "next/server"
import { getAuthUser, type AuthUser } from "./auth"
import { checkRateLimit, getClientIdentifier, getRateLimitHeaders } from "./rate-limit"
/**
* Reusable API route wrapper that enforces authentication.
* Usage:
* export const POST = withAuth(async (req, user) => { ... })
*/
export function withAuth(
handler: (request: NextRequest, user: AuthUser) => Promise<NextResponse | Response>
) {
return async (request: NextRequest) => {
const user = await getAuthUser()
if (!user) {
return NextResponse.json(
{ error: "Authentication required" },
{ status: 401 }
)
}
return handler(request, user)
}
}
/**
* Rate limit configuration presets for different route types.
*/
export const RATE_LIMIT_PRESETS = {
/** Standard write ops: 30 req/hour guest, 100 req/hour user */
write: { guest: { max: 30, window: 3600 }, user: { max: 100, window: 3600 } },
/** Expensive ops (AI search, run): 10 req/hour guest, 50 req/hour user */
expensive: { guest: { max: 10, window: 3600 }, user: { max: 50, window: 3600 } },
/** Read-heavy: 120 req/hour guest, 300 req/hour user */
read: { guest: { max: 120, window: 3600 }, user: { max: 300, window: 3600 } },
} as const
type RateLimitPreset = keyof typeof RATE_LIMIT_PRESETS
/**
* Reusable API route wrapper that enforces rate limiting.
* Usage:
* export const POST = withRateLimit(async (req) => { ... }, "write")
*/
export function withRateLimit(
handler: (request: NextRequest) => Promise<NextResponse | Response>,
preset: RateLimitPreset = "write"
) {
return async (request: NextRequest) => {
const user = await getAuthUser()
const identifier = getClientIdentifier(request, user?.id ?? undefined)
const isAuth = !!user
const rateLimit = await checkRateLimit(
`${preset}:${identifier}`,
isAuth
)
if (!rateLimit.success) {
const headers = getRateLimitHeaders(rateLimit)
return NextResponse.json(
{ error: rateLimit.error },
{
status: 429,
headers: Object.fromEntries(headers.entries()),
}
)
}
const response = await handler(request)
// Append rate limit headers to successful responses
const rlHeaders = getRateLimitHeaders(rateLimit)
rlHeaders.forEach((value, key) => {
response.headers.set(key, value)
})
return response
}
}
/**
* Combined: auth + rate limit wrapper.
* Usage:
* export const POST = withAuthAndRateLimit(async (req, user) => { ... }, "write")
*/
export function withAuthAndRateLimit(
handler: (request: NextRequest, user: AuthUser) => Promise<NextResponse | Response>,
preset: RateLimitPreset = "write"
) {
return async (request: NextRequest) => {
// 1. Auth check
const user = await getAuthUser()
if (!user) {
return NextResponse.json(
{ error: "Authentication required" },
{ status: 401 }
)
}
// 2. Rate limit check
const identifier = getClientIdentifier(request, user.id)
const rateLimit = await checkRateLimit(`${preset}:${identifier}`, true)
if (!rateLimit.success) {
const headers = getRateLimitHeaders(rateLimit)
return NextResponse.json(
{ error: rateLimit.error },
{
status: 429,
headers: Object.fromEntries(headers.entries()),
}
)
}
const response = await handler(request, user)
// Append rate limit headers
const rlHeaders = getRateLimitHeaders(rateLimit)
rlHeaders.forEach((value, key) => {
response.headers.set(key, value)
})
return response
}
}
/**
* Sanitize user-provided text: strip HTML tags and limit length.
*/
export function sanitizeText(text: string, maxLength: number = 10000): string {
return text
.replace(/<[^>]*>/g, "")
.trim()
.slice(0, maxLength)
}
/**
* Clamp a number to a range.
*/
export function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max)
}