feat: jwt cache, redis health, help-ticket admin, and write rate limits
Deploy / release (push) Skipped
Deploy / deploy (push) Successful in 1m33s

Cut Auth.js DB load with cached jwtVersion checks, surface Redis in /api/health and deploy warnings, add admin help-center ticket reply UI, rate-limit API tickets/reactions/referral claims, and revoke PATs on sign-out-everywhere.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-21 21:58:48 +02:00
1 parent 3bb96eb6f3
commit 968ca15c27
23 files changed
+1344 -205

No files matched your search

+5
View File
@@ -7,6 +7,7 @@
import { apiError, apiJson, positiveBigInt } from "@/lib/api";
import { bearerUserId } from "@/lib/api-auth";
import { prisma } from "@/lib/prisma";
import { rateLimit } from "@/lib/rate-limit";
import { createOwnedTicketReply } from "@/lib/services/ticket-replies";
export const dynamic = "force-dynamic";
@@ -19,6 +20,10 @@ export async function POST(
const uid = await bearerUserId(req);
if (!uid) return apiError("Unauthorized", 401);
if (!(await rateLimit(`api-ticket-reply:${uid}`, 10, 60_000)).ok) {
return apiError("Too many requests", 429);
}
const { id } = await params;
const ticketId = positiveBigInt(id);
if (!ticketId) return apiError("Invalid ticket id", 422);
+5
View File
@@ -7,6 +7,7 @@
import { apiError, apiJson, positiveBigInt } from "@/lib/api";
import { bearerUserId } from "@/lib/api-auth";
import { prisma } from "@/lib/prisma";
import { rateLimit } from "@/lib/rate-limit";
export const dynamic = "force-dynamic";
@@ -40,6 +41,10 @@ export async function POST(req: Request) {
const uid = await bearerUserId(req);
if (!uid) return apiError("Unauthorized", 401);
if (!(await rateLimit(`api-ticket:${uid}`, 5, 60_000)).ok) {
return apiError("Too many requests", 429);
}
const body = (await req.json().catch(() => ({}))) as {
title?: unknown;
content?: unknown;