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

+47 -36
View File
@@ -4,13 +4,19 @@ import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { clientIp, rateLimit } from "@/lib/rate-limit";
// The reaction set the UI offers. The action rejects anything outside this list
// so the website_article_reactions.reaction VARCHAR(50) only ever holds known
// values. Keep this in sync with REACTIONS in src/app/news/[slug]/page.tsx.
const ALLOWED_REACTIONS = new Set(["like", "love", "wow"]);
type ReactionOutcome = "updated" | "invalid" | "not_found" | "error";
type ReactionOutcome =
| "updated"
| "invalid"
| "not_found"
| "ratelimit"
| "error";
function reactionRedirect(slug: string, outcome: ReactionOutcome): never {
const path = slug ? `/news/${encodeURIComponent(slug)}` : "/news";
@@ -50,58 +56,63 @@ export async function toggleReaction(formData: FormData): Promise<void> {
redirect("/login");
}
const reaction = String(formData.get("reaction") ?? "")
.normalize("NFC")
.trim()
.toLowerCase();
if (!ALLOWED_REACTIONS.has(reaction)) {
outcome = "invalid";
await clientIp();
if (!(await rateLimit(`article-reaction:${userId}`, 30, 60_000)).ok) {
outcome = "ratelimit";
} else {
const articleIdRaw = String(formData.get("articleId") ?? "")
const reaction = String(formData.get("reaction") ?? "")
.normalize("NFC")
.trim();
if (!/^\d+$/.test(articleIdRaw)) {
.trim()
.toLowerCase();
if (!ALLOWED_REACTIONS.has(reaction)) {
outcome = "invalid";
} else {
const articleId = BigInt(articleIdRaw);
const article = await prisma.websiteArticles.findUnique({
where: { id: articleId },
select: { slug: true },
});
if (!article) {
outcome = "not_found";
const articleIdRaw = String(formData.get("articleId") ?? "")
.normalize("NFC")
.trim();
if (!/^\d+$/.test(articleIdRaw)) {
outcome = "invalid";
} else {
slug = article.slug;
const articleId = BigInt(articleIdRaw);
const existing = await prisma.websiteArticleReactions.findFirst({
where: { userId, articleId, reaction },
select: { id: true, active: true },
const article = await prisma.websiteArticles.findUnique({
where: { id: articleId },
select: { slug: true },
});
if (existing?.active) {
await prisma.websiteArticleReactions.update({
where: { id: existing.id },
data: { active: false },
});
if (!article) {
outcome = "not_found";
} else {
await prisma.websiteArticleReactions.updateMany({
where: { userId, articleId, active: true },
data: { active: false },
slug = article.slug;
const existing = await prisma.websiteArticleReactions.findFirst({
where: { userId, articleId, reaction },
select: { id: true, active: true },
});
if (existing) {
if (existing?.active) {
await prisma.websiteArticleReactions.update({
where: { id: existing.id },
data: { active: true },
data: { active: false },
});
} else {
await prisma.websiteArticleReactions.create({
data: { userId, articleId, reaction, active: true },
await prisma.websiteArticleReactions.updateMany({
where: { userId, articleId, active: true },
data: { active: false },
});
if (existing) {
await prisma.websiteArticleReactions.update({
where: { id: existing.id },
data: { active: true },
});
} else {
await prisma.websiteArticleReactions.create({
data: { userId, articleId, reaction, active: true },
});
}
}
outcome = "updated";
}
outcome = "updated";
}
}
}