Skip to content

Commit 7f7b4cf

Browse files
committed
chore(docs-history): CR - 路径校验/错误状态/头像兜底/类型解耦
Copilot CR #279: - route: 新增 normalizeDocsPath 做路径校验,只允许 app/docs/ 下相对路径, 拒绝 ..、反斜杠、null 字节,消除 SSRF 风险 - route: 接受 'docs/...' 和 '/docs/...' 形式,统一补成仓库根相对 'app/docs/...' - route: 403 用 x-ratelimit-remaining 区分限流 vs 权限不足,401 单独处理 - route: author 为 null 时 avatarUrl 返回空串而不是拼 github.com/<name>.png 容易 404 - 类型 HistoryItem 抽到 app/types/docs-history.ts,解耦 client 组件与 route handler - DocHistoryPanel: path 变化先清空 items/error 避免 '错误 + 旧列表' 同时显示 - DocHistoryPanel: 空头像用 data URI 占位防 Image 报错
1 parent 890b3a7 commit 7f7b4cf

2 files changed

Lines changed: 29 additions & 3 deletions

File tree

app/components/DocHistoryPanel.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
import { useEffect, useState } from "react";
44
import Image from "next/image";
5-
import type { HistoryItem } from "@/app/api/docs/history/route";
5+
import type { HistoryItem } from "@/app/types/docs-history";
6+
7+
// author 缺失时用 1x1 透明占位图,避免 <Image> 收到空 src 报错
8+
const FALLBACK_AVATAR =
9+
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24'><rect width='24' height='24' fill='%23e5e7eb'/></svg>";
610

711
interface DocHistoryPanelProps {
812
path: string;
@@ -42,18 +46,26 @@ export function DocHistoryPanel({ path }: DocHistoryPanelProps) {
4246

4347
useEffect(() => {
4448
let cancelled = false;
49+
// path 变化触发重新 fetch 时先清空旧状态,避免"错误提示 + 旧列表"同时显示
50+
setItems(null);
51+
setError(null);
4552
fetch(`/api/docs/history?path=${encodeURIComponent(path)}`)
4653
.then((r) => r.json())
4754
.then((json) => {
4855
if (cancelled) return;
4956
if (json.success) {
5057
setItems(json.data);
58+
setError(null);
5159
} else {
60+
setItems(null);
5261
setError(json.error ?? "无法加载历史");
5362
}
5463
})
5564
.catch(() => {
56-
if (!cancelled) setError("无法加载历史");
65+
if (!cancelled) {
66+
setItems(null);
67+
setError("无法加载历史");
68+
}
5769
});
5870
return () => {
5971
cancelled = true;
@@ -103,7 +115,7 @@ export function DocHistoryPanel({ path }: DocHistoryPanelProps) {
103115
>
104116
{/* 头像 */}
105117
<Image
106-
src={item.avatarUrl}
118+
src={item.avatarUrl || FALLBACK_AVATAR}
107119
alt={item.authorLogin}
108120
width={24}
109121
height={24}

app/types/docs-history.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* 文档历史面板共享类型。
3+
* 抽出到独立模块避免 client 组件从 Route Handler(server 文件)直接 import 类型,
4+
* 防止未来 route 文件引入 node-only 依赖时 client bundle 踩边界问题。
5+
*/
6+
export interface HistoryItem {
7+
sha: string;
8+
authorName: string;
9+
authorLogin: string;
10+
avatarUrl: string;
11+
date: string;
12+
message: string;
13+
htmlUrl: string;
14+
}

0 commit comments

Comments
 (0)