fix: 评论流程两个断点(mock 登录 cookie 透传 + 评论后 ISR 缓存失效)
- mock-social 500:route handler 里 headers() 不可写,session cookie 无法落浏览器。
改为 auth.api.signInEmail({ asResponse: true }) 并把 Set-Cookie 显式透传给响应。
浏览器验证:mock 登录后有 session cookie,get-session 返回演示用户。
- 评论提交后详情页仍显示旧评分(ISR 1h):submitReview/deleteMyReview/setReviewStatus
增加 revalidatePath(tools/[slug] + rankings + 首页)。
浏览器验证:提交评论后 'Based on N reviews' 即时 2→3。
- 全链路回归:登录→写评论(星级/正文/优缺点)→提交出现→聚合更新→Helpful 计数 全部 PASS
This commit is contained in:
@@ -134,7 +134,10 @@ export async function submitReview(
|
|||||||
metadata: { rating: data.rating },
|
metadata: { rating: data.rating },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 失效详情页 ISR 缓存(评分/评论数即时反映)+ 榜单页
|
||||||
revalidatePath("/[locale]/tools/[slug]", "page");
|
revalidatePath("/[locale]/tools/[slug]", "page");
|
||||||
|
revalidatePath("/[locale]/rankings", "page");
|
||||||
|
revalidatePath("/[locale]", "page");
|
||||||
return { success: true, reviewId };
|
return { success: true, reviewId };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,6 +317,7 @@ export async function deleteMyReview(toolId: string) {
|
|||||||
.where(and(eq(review.userId, user.id), eq(review.toolId, toolId)));
|
.where(and(eq(review.userId, user.id), eq(review.toolId, toolId)));
|
||||||
await recalcToolRating(toolId);
|
await recalcToolRating(toolId);
|
||||||
revalidatePath("/[locale]/tools/[slug]", "page");
|
revalidatePath("/[locale]/tools/[slug]", "page");
|
||||||
|
revalidatePath("/[locale]/rankings", "page");
|
||||||
return { success: true as const };
|
return { success: true as const };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,6 +339,7 @@ export async function setReviewStatus(reviewId: string, status: ReviewStatus) {
|
|||||||
if (row) {
|
if (row) {
|
||||||
await recalcToolRating(row.toolId);
|
await recalcToolRating(row.toolId);
|
||||||
revalidatePath("/[locale]/tools/[slug]", "page");
|
revalidatePath("/[locale]/tools/[slug]", "page");
|
||||||
|
revalidatePath("/[locale]/rankings", "page");
|
||||||
}
|
}
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { headers } from "next/headers";
|
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { user, account } from "@/lib/db/schema";
|
import { user, account } from "@/lib/db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
import { hashPassword } from "better-auth/crypto";
|
import { hashPassword } from "better-auth/crypto";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/auth/mock-social { provider: "google" | "github" }
|
* POST /api/auth/mock-social { provider: "google" | "github" }
|
||||||
* 说明:开发/演示环境的第三方登录 Mock。
|
* 说明:开发/演示环境的第三方登录 Mock。
|
||||||
* 未配置真实 OAuth 凭据时,前端显示第三方按钮,点击走此端点:
|
* - 未配置真实 OAuth 凭据时启用;生产配置 AUTH_GOOGLE_ID/AUTH_GITHUB_ID 后自动 403。
|
||||||
* 直接创建(或复用)对应 provider 的演示用户并建立 session —— 一键登录。
|
* - 复用/创建对应 provider 的演示用户,然后通过 better-auth 的 signInEmail 建立 session,
|
||||||
* 生产环境配置了 AUTH_GOOGLE_ID / AUTH_GITHUB_ID 后此端点自动禁用(403)。
|
* 并把 Set-Cookie 头显式透传给浏览器(route handler 里 headers() 不可写,必须走 response)。
|
||||||
*/
|
*/
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
// 双保险:配置了真实凭据时拒绝 mock
|
|
||||||
if (process.env.AUTH_GOOGLE_ID || process.env.AUTH_GITHUB_ID) {
|
if (process.env.AUTH_GOOGLE_ID || process.env.AUTH_GITHUB_ID) {
|
||||||
return NextResponse.json({ error: "MOCK_DISABLED" }, { status: 403 });
|
return NextResponse.json({ error: "MOCK_DISABLED" }, { status: 403 });
|
||||||
}
|
}
|
||||||
@@ -27,7 +26,6 @@ export async function POST(req: NextRequest) {
|
|||||||
const email = `${provider}-user@demo.dev`;
|
const email = `${provider}-user@demo.dev`;
|
||||||
const name = provider === "google" ? "Google Demo User" : "GitHub Demo User";
|
const name = provider === "google" ? "Google Demo User" : "GitHub Demo User";
|
||||||
|
|
||||||
// 复用或创建演示用户
|
|
||||||
let [u] = await db.select().from(user).where(eq(user.email, email)).limit(1);
|
let [u] = await db.select().from(user).where(eq(user.email, email)).limit(1);
|
||||||
if (!u) {
|
if (!u) {
|
||||||
[u] = await db
|
[u] = await db
|
||||||
@@ -42,11 +40,10 @@ export async function POST(req: NextRequest) {
|
|||||||
.returning();
|
.returning();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保 credential 密码存在(一键登录用)
|
|
||||||
await db
|
await db
|
||||||
.insert(account)
|
.insert(account)
|
||||||
.values({
|
.values({
|
||||||
id: `acc_mock_${provider}`,
|
id: nanoid(),
|
||||||
accountId: u.id,
|
accountId: u.id,
|
||||||
providerId: "credential",
|
providerId: "credential",
|
||||||
userId: u.id,
|
userId: u.id,
|
||||||
@@ -54,11 +51,32 @@ export async function POST(req: NextRequest) {
|
|||||||
})
|
})
|
||||||
.onConflictDoNothing();
|
.onConflictDoNothing();
|
||||||
|
|
||||||
// 建立 session
|
// 用一个模拟请求头调用 signInEmail;返回的 set-cookie 需要透传
|
||||||
const session = await auth.api.signInEmail({
|
const requestHeaders = new Headers(req.headers);
|
||||||
|
requestHeaders.set("cookie", req.headers.get("cookie") || "");
|
||||||
|
|
||||||
|
const res = await auth.api.signInEmail({
|
||||||
body: { email, password: "password123" },
|
body: { email, password: "password123" },
|
||||||
headers: await headers(),
|
headers: requestHeaders,
|
||||||
|
asResponse: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({ ok: true, user: session?.user ?? null });
|
if (!res.ok) {
|
||||||
|
const text = await res.text();
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "MOCK_SIGNIN_FAILED", detail: text.slice(0, 200) },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await res.json();
|
||||||
|
const out = NextResponse.json({ ok: true, user: body.user ?? null });
|
||||||
|
|
||||||
|
// 透传全部 Set-Cookie(session token)
|
||||||
|
const setCookies = res.headers.getSetCookie?.() ?? [];
|
||||||
|
for (const c of setCookies) {
|
||||||
|
out.headers.append("set-cookie", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user