diff --git a/app/accountMenu.tsx b/app/accountMenu.tsx index b0c4dce2..5a73a14e 100644 --- a/app/accountMenu.tsx +++ b/app/accountMenu.tsx @@ -1,7 +1,7 @@ "use client"; import { authClient } from "@/lib/auth-client"; -import { usePathname } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useEffect } from "react"; export function AutoAnonymousLogin() { @@ -18,6 +18,7 @@ export function AutoAnonymousLogin() { export function AccountMenu() { const { data: session, isPending } = authClient.useSession(); const pathname = usePathname(); + const router = useRouter(); const signout = () => { if ( @@ -27,7 +28,7 @@ export function AccountMenu() { ) { authClient.signOut({ fetchOptions: { - onSuccess: () => window.location.reload(), + onSuccess: () => router.refresh(), }, }); } @@ -36,7 +37,7 @@ export function AccountMenu() { if (window.confirm("チャット履歴は削除され、アクセスできなくなります。")) { authClient.signOut({ fetchOptions: { - onSuccess: () => window.location.reload(), + onSuccess: () => router.refresh(), }, }); } @@ -102,7 +103,7 @@ export function AccountMenu() { onClick={() => authClient.signIn.social({ provider: "github", - callbackURL: pathname, + callbackURL: `/clear-cache?redirect=${encodeURIComponent(pathname)}`, }) } > @@ -114,7 +115,7 @@ export function AccountMenu() { onClick={() => authClient.signIn.social({ provider: "google", - callbackURL: pathname, + callbackURL: `/clear-cache?redirect=${encodeURIComponent(pathname)}`, }) } > diff --git a/app/actions/clearUserCache.ts b/app/actions/clearUserCache.ts new file mode 100644 index 00000000..41687c9c --- /dev/null +++ b/app/actions/clearUserCache.ts @@ -0,0 +1,29 @@ +"use server"; + +import { initContext, cacheKeyForPage } from "@/lib/chatHistory"; +import { updateTag } from "next/cache"; +import { getPagesList } from "@/lib/docs"; +import { isCloudflare } from "@/lib/detectCloudflare"; + +export async function clearUserCacheAction() { + const ctx = await initContext(); + if (!ctx.userId) return; + + const pagesList = await getPagesList(); + for (const lang of pagesList) { + for (const page of lang.pages) { + updateTag(cacheKeyForPage({ lang: lang.id, page: page.slug }, ctx.userId)); + } + } + + if (isCloudflare()) { + const cache = await caches.open("chatHistory"); + for (const lang of pagesList) { + for (const page of lang.pages) { + await cache.delete( + cacheKeyForPage({ lang: lang.id, page: page.slug }, ctx.userId) + ); + } + } + } +} diff --git a/app/clear-cache/page.tsx b/app/clear-cache/page.tsx new file mode 100644 index 00000000..f86a6ad4 --- /dev/null +++ b/app/clear-cache/page.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { clearUserCacheAction } from "@/actions/clearUserCache"; +import { useRouter, useSearchParams } from "next/navigation"; +import { Suspense, useEffect } from "react"; + +function ClearCacheContent() { + const router = useRouter(); + const searchParams = useSearchParams(); + + useEffect(() => { + const redirectParam = searchParams.get("redirect") ?? "/"; + // Only allow relative redirects to prevent open redirect attacks + const redirect = redirectParam.startsWith("/") ? redirectParam : "/"; + clearUserCacheAction() + .catch(() => {}) + .finally(() => { + router.replace(redirect); + }); + }, [router, searchParams]); + + return null; +} + +export default function ClearCachePage() { + return ( + + + + ); +} diff --git a/app/lib/chatHistory.ts b/app/lib/chatHistory.ts index 2158941f..e559d6e4 100644 --- a/app/lib/chatHistory.ts +++ b/app/lib/chatHistory.ts @@ -6,7 +6,7 @@ import { and, asc, eq, exists } from "drizzle-orm"; import { Auth } from "better-auth"; import { updateTag } from "next/cache"; import { isCloudflare } from "./detectCloudflare"; -import { getPagesList, LangId, PagePath, PageSlug, SectionId } from "./docs"; +import { LangId, PagePath, PageSlug, SectionId } from "./docs"; export interface CreateChatMessage { role: "user" | "ai" | "error"; @@ -279,22 +279,4 @@ export async function migrateChatUser(oldUserId: string, newUserId: string) { .update(chat) .set({ userId: newUserId }) .where(eq(chat.userId, oldUserId)); - const pagesList = await getPagesList(); - for (const lang of pagesList) { - for (const page of lang.pages) { - updateTag( - cacheKeyForPage({ lang: lang.id, page: page.slug }, newUserId) - ); - } - } - if (isCloudflare()) { - const cache = await caches.open("chatHistory"); - for (const lang of pagesList) { - for (const page of lang.pages) { - await cache.delete( - cacheKeyForPage({ lang: lang.id, page: page.slug }, newUserId) - ); - } - } - } }