diff --git a/src/app/api/foundry/[...path]/route.ts b/src/app/api/foundry/[...path]/route.ts index 8318218..2e516af 100644 --- a/src/app/api/foundry/[...path]/route.ts +++ b/src/app/api/foundry/[...path]/route.ts @@ -1,11 +1,12 @@ +import { sessionCookie, sessionMaxAge, SESSION_EXPIRY_HEADER } from "@/lib/session-cookie"; import { NextRequest, NextResponse } from "next/server"; const allowed = - /^(profile|auth\/(login|register|verify-email|exchange|me|logout)|workspaces(?:\/[a-zA-Z0-9_-]+(?:\/(?:materials|search)(?:\/[a-zA-Z0-9_-]+)?)?)?)$/; + /^(profile|auth\/(login|register|verify-email|exchange|me|logout)|workspaces(?:\/[a-zA-Z0-9_-]+(?:\/(?:materials(?:\/[a-zA-Z0-9_-]+(?:\/canvas)?)?|search))?)?)$/; const bodyLimit = 1024 * 1024; // Route Handlers do not have the Pages Router bodyParser size limit. -async function readBody(req: NextRequest) { +async function readBody(req: NextRequest, limit = bodyLimit) { const reader = req.body?.getReader(); if (!reader) return {}; const chunks: Uint8Array[] = []; @@ -15,7 +16,7 @@ async function readBody(req: NextRequest) { const { done, value } = await reader.read(); if (done) break; size += value.byteLength; - if (size > bodyLimit) { + if (size > limit) { await reader.cancel(); throw new Error("body_too_large"); } @@ -49,7 +50,7 @@ async function handler( let requestBody: unknown; if (req.method !== "GET") { try { - requestBody = await readBody(req); + requestBody = await readBody(req, path.endsWith("/canvas") ? 5 * 1024 * 1024 : bodyLimit); } catch (error) { const tooLarge = error instanceof Error && error.message === "body_too_large"; return json({ error: tooLarge ? "body_too_large" : "invalid_json" }, tooLarge ? 413 : 400); @@ -83,18 +84,21 @@ async function handler( cache: "no-store", }, ); + const etag = upstream.headers.get("etag"); + if (etag) responseHeaders.set("ETag", etag); const headersReceived = performance.now(); const body = upstream.status === 204 ? null : await upstream.json(); responseHeaders.set( "Server-Timing", `upstream;dur=${(headersReceived - started).toFixed(1)}, total;dur=${(performance.now() - started).toFixed(1)}`, ); + const secure = process.env.NODE_ENV === "production" && req.headers.get("x-forwarded-proto") === "https"; if (body?.access_token) { - responseHeaders.set( - "Set-Cookie", - `foundry_session=${encodeURIComponent(body.access_token)}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${Math.min(body.expires_in || 28800, 28800)}${process.env.NODE_ENV === "production" && req.headers.get("x-forwarded-proto") === "https" ? "; Secure" : ""}`, - ); + responseHeaders.set("Set-Cookie", sessionCookie(body.access_token, sessionMaxAge(body.expires_in), secure)); delete body.access_token; + } else if (session && upstream.status !== 401) { + const renewed = upstream.headers.get(SESSION_EXPIRY_HEADER); + if (renewed !== null && /^\d+$/.test(renewed)) responseHeaders.set("Set-Cookie", sessionCookie(session, sessionMaxAge(renewed), secure)); } if (path === "auth/logout" && upstream.ok) responseHeaders.set( diff --git a/src/app/dashboard/dashboard.tsx b/src/app/dashboard/dashboard.tsx index 8c35e9f..abb5bb4 100644 --- a/src/app/dashboard/dashboard.tsx +++ b/src/app/dashboard/dashboard.tsx @@ -7,7 +7,7 @@ import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { Check, ChevronsUpDown, FilePlus2, Home, Network, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings } from "lucide-react"; import { IconButton } from "@/components/ui/icon-button"; import { api, errorText, type Profile } from "@/lib/api"; -import { boardHref, createBoard, renameBoard, DEFAULT_BOARD_TITLE, loadWorkspaceHome, type Board, type Workspace } from "@/lib/workspace"; +import { boardHref, createBoard, renameBoard, matchesRef, DEFAULT_BOARD_TITLE, loadWorkspaceHome, type Board, type Workspace } from "@/lib/workspace"; import { useActiveBoard } from "@/components/workspace/active-board"; import toolbar from "@/components/ui/toolbar.module.css"; import { EditableTitle } from "@/components/workspace/editable-title"; @@ -39,7 +39,9 @@ export default function DashboardSidebar() { const createLock = useRef(false); const listVersion = useRef(0); const accountPopup = useRef(null); - const workspace = params.workspace || searchParams.get("workspace") || selectedWorkspace; + const workspaceRef = params.workspace || searchParams.get("workspace") || selectedWorkspace; + const currentWorkspace = workspaces.find((item) => matchesRef(item, workspaceRef)); + const workspace = currentWorkspace?.id || (activeBoard?.workspace_ref === workspaceRef ? activeBoard.workspace_id : workspaceRef); useEffect(() => { if (demo) { @@ -101,7 +103,7 @@ export default function DashboardSidebar() { setBoards((list) => [board, ...list]); if (demo) localStorage.setItem("endoleap_demo_boards_" + workspace, JSON.stringify([board, ...readDemoBoards(workspace)])); setQuery(""); - router.push(boardHref(board.workspace_id, board.id, demo)); + router.push(boardHref(currentWorkspace?.slug || board.workspace_id, board.slug || board.id, demo)); } catch (e) { setError(errorText(e)); } finally { createLock.current = false; setCreating(false); } } @@ -132,16 +134,15 @@ export default function DashboardSidebar() { } catch (e) { setError(errorText(e)); } } - const currentTitle = activeBoard && activeBoard.id === params.board && activeBoard.workspace_id === workspace + const currentTitle = activeBoard && matchesRef(activeBoard, params.board) && activeBoard.workspace_id === workspace ? activeBoard.title - : boards.find((board) => board.id === params.board)?.title || (params.board ? "正在读取图谱…" : "工作空间"); + : boards.find((board) => matchesRef(board, params.board))?.title || (params.board ? "正在读取图谱…" : "工作空间"); const name = profile?.display_name || profile?.username || "账户"; - const currentWorkspace = workspaces.find((w) => w.id === workspace); const filtered = boards.filter((b) => b.title.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase())); const canCreate = status === "ready" && (!workspace || Boolean(currentWorkspace && currentWorkspace.role !== "viewer")); async function renameTitle(title: string) { - const id = params.board; + const id = activeBoard?.id || params.board; if (!id || !workspace) return; try { if (demo) { @@ -175,7 +176,7 @@ export default function DashboardSidebar() {