feat(friends): restore moderated friend links - #118
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughSummary by CodeRabbit
Walkthrough新增完整友链功能,覆盖数据库存储、公开查询与申请、后台审核管理、旧链接导入,以及前后台路由和导航入口。 Changes友链功能
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Visitor
participant FriendsPage
participant PublicAPI
participant Database
Visitor->>FriendsPage: 打开 /friends
FriendsPage->>PublicAPI: GET /api/friends
PublicAPI->>Database: getApprovedFriendLinks
Database-->>PublicAPI: 返回已审核友链
PublicAPI-->>FriendsPage: 返回友链列表
Visitor->>FriendsPage: 提交申请
FriendsPage->>PublicAPI: POST /api/friends/apply
PublicAPI->>Database: createFriendLink
Database-->>PublicAPI: 创建待审核记录
PublicAPI-->>FriendsPage: 返回申请结果
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (6)
server/src/storage/db/turso.ts (1)
186-203: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
ensureCoreTables()未包含friend_links,与 D1 适配器的初始化行为不一致。
D1Adapter.ensureSchema()已把ensureFriendLinksTable()纳入统一初始化,而这里ensureCoreTables()(Line 107-162)没有加入,friend_links只能靠首个友链请求惰性创建。功能上不受影响(每个友链方法都会自行 ensure),但建议补上以保持多后端初始化行为一致。As per path instructions:「存储适配器层……3. 多后端(D1/Turso/PostgreSQL)行为一致性」。
♻️ 建议改动
await this.ensureCommentsTable(); + await this.ensureFriendLinksTable();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/storage/db/turso.ts` around lines 186 - 203, Update ensureCoreTables() to invoke ensureFriendLinksTable() during the unified core-table initialization, matching D1Adapter.ensureSchema() while preserving the existing lazy ensures in individual friend-link methods.Source: Path instructions
server/src/storage/db/d1.ts (1)
234-251: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value建议复用
ensureSchema()的缓存,避免每次友链读写都多发 2 条 DDL。
ensureFriendLinksTable()已被纳入ensureSchema()(Line 37)并由schemaReady缓存,但 Line 1049/1059/1068… 每个方法又单独调用它,导致每个友链请求在 D1 上额外产生CREATE TABLE IF NOT EXISTS+CREATE INDEX IF NOT EXISTS两次往返。虽然与文件内ensureCommentsTable()既有风格一致,但读路径(/api/friends公开接口)会被明显放大。可考虑在方法内改为await this.ensureSchema()。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/storage/db/d1.ts` around lines 234 - 251, Update ensureFriendLinksTable to reuse the cached ensureSchema flow by calling await this.ensureSchema() instead of issuing its own CREATE TABLE and CREATE INDEX statements. Preserve the existing friend_links schema initialization through ensureSchema and avoid adding separate DDL round trips for friend-link reads and writes.server/src/index.ts (1)
866-889: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value内存限流在 Workers 上只对单个 isolate 生效。
friendLinkAttempts随 isolate 创建/回收,多实例间不共享,实际配额远高于 3 次/小时,且实例重启即清零。淘汰逻辑本身正确(Map.keys()按插入顺序,近似 FIFO),对小站点足够。若后续需要可靠配额,建议迁移到 KV 或 Durable Object 计数器。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/index.ts` around lines 866 - 889, Replace the isolate-local friendLinkAttempts rate limiter with shared persistence suitable for Workers, such as a KV or Durable Object counter, so the 3-per-hour quota is enforced across instances and survives isolate recycling. Preserve the existing window and cleanup semantics from pruneFriendLinkAttempts and isFriendLinkRateLimited, removing the in-memory Map dependency.server/src/storage/db/postgres.ts (1)
1015-1032: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win公开读路径上每次都跑一遍完整
ensureCoreTables(),代价偏高。
ensureCoreTables()会顺序执行约 20 条CREATE TABLE IF NOT EXISTS/ALTER TABLE ... ADD COLUMN IF NOT EXISTS(Line 36-166),且没有像D1Adapter.schemaReady那样的一次性缓存。getApprovedFriendLinks()服务的是公开的/api/friends,每个请求都会带上这笔固定开销。建议给 PG 适配器也加一个schemaReady: Promise<void> | null缓存,读路径复用即可。♻️ 建议的缓存写法
+ private schemaReady: Promise<void> | null = null; + + private async ensureSchemaOnce(): Promise<void> { + if (!this.schemaReady) this.schemaReady = this.ensureCoreTables(); + await this.schemaReady; + } + async getApprovedFriendLinks(): Promise<FriendLink[]> { - await this.ensureCoreTables(); + await this.ensureSchemaOnce();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/storage/db/postgres.ts` around lines 1015 - 1032, 为 Postgres 适配器增加与 D1Adapter.schemaReady 等效的 Promise<void> | null 一次性缓存,并让 ensureCoreTables() 复用该缓存,避免重复执行完整建表与迁移检查;确保 getApprovedFriendLinks() 和 getAllFriendLinks() 等读路径继续通过 ensureCoreTables() 获取初始化保障,但不会在每次请求中重复运行 schema 初始化。server/src/db/schema.ts (1)
111-114: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDrizzle schema 声明的
url具名唯一索引在任何实际 DDL 中都不存在。 迁移脚本与三个适配器的建表语句统一使用列级url TEXT NOT NULL UNIQUE(产生匿名唯一索引),而两份 schema 声明的是具名uniqueIndex。唯一性保证在运行时等价,但drizzle-kitdiff 会认为索引缺失并生成多余迁移。建议统一为其中一种写法。
server/src/db/schema.ts#L111-L114:将uniqueIndex("friend_links_url_idx")改为在列上使用.unique(),或在server/src/migrations/0010_friend_links.sql与d1.ts/turso.ts的ensureFriendLinksTable()中补CREATE UNIQUE INDEX IF NOT EXISTS friend_links_url_idx ON friend_links(url)。server/src/db/schema-pg.ts#L108-L111:同样把uniqueIndex("pg_friend_links_url_idx")改为列级.unique(),或在postgres.ts的ensureCoreTables()中补建同名唯一索引。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/db/schema.ts` around lines 111 - 114, 统一 friend_links 的唯一约束声明与实际建表方式,避免 Drizzle 反复生成索引迁移:在 server/src/db/schema.ts 的 friend_links 定义中将 urlIdx 改为列级 unique 配置,并在 server/src/db/schema-pg.ts 的对应定义中将 pg_friend_links_url_idx 同样改为列级 unique;不要同时新增具名唯一索引 DDL,保留 statusIdx 及现有唯一性行为。client/src/pages/friends.tsx (1)
37-43: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value移除
friends.tsx中多余的document.title赋值。
<SeoHead title="友链" />已在 effect 中设置document.title = "友链 | Monolith",父组件 own effect 先于子组件执行,可能导致标题先被父组件赋值、随后又被覆盖或产生不必要的副作用。保留SeoHead的 title 管理即可。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@client/src/pages/friends.tsx` around lines 37 - 43, Remove the redundant document.title assignment from the useEffect in friends.tsx, leaving title management to SeoHead. Keep the fetchFriends loading, success, error, and completion behavior unchanged.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@client/src/lib/api.ts`:
- Around line 582-588: Update fetchAdminFriends to reuse the existing readError
helper when the response is unsuccessful, replacing the hardcoded “获取友链失败” error
while preserving the current successful res.json() behavior.
In `@client/src/pages/friends.tsx`:
- Around line 39-42: Update the friends page’s fetchFriends promise chain to
track and display a loading error instead of silently replacing failures with an
empty links list. Preserve setLinks([]) only as appropriate for failed state
handling, ensure the rendered UI distinguishes loading failure from “no
displayed links,” and keep setLoading(false) in the existing finally path.
In `@server/src/index.ts`:
- Around line 688-703: Update the catch block around createFriendLink in the
friend-link submission handler to return 409 only for a recognized
unique-constraint conflict. For all other errors, log the exception with the
existing request context and return a generic 500 response instead of the
URL-conflict message.
---
Nitpick comments:
In `@client/src/pages/friends.tsx`:
- Around line 37-43: Remove the redundant document.title assignment from the
useEffect in friends.tsx, leaving title management to SeoHead. Keep the
fetchFriends loading, success, error, and completion behavior unchanged.
In `@server/src/db/schema.ts`:
- Around line 111-114: 统一 friend_links 的唯一约束声明与实际建表方式,避免 Drizzle 反复生成索引迁移:在
server/src/db/schema.ts 的 friend_links 定义中将 urlIdx 改为列级 unique 配置,并在
server/src/db/schema-pg.ts 的对应定义中将 pg_friend_links_url_idx 同样改为列级
unique;不要同时新增具名唯一索引 DDL,保留 statusIdx 及现有唯一性行为。
In `@server/src/index.ts`:
- Around line 866-889: Replace the isolate-local friendLinkAttempts rate limiter
with shared persistence suitable for Workers, such as a KV or Durable Object
counter, so the 3-per-hour quota is enforced across instances and survives
isolate recycling. Preserve the existing window and cleanup semantics from
pruneFriendLinkAttempts and isFriendLinkRateLimited, removing the in-memory Map
dependency.
In `@server/src/storage/db/d1.ts`:
- Around line 234-251: Update ensureFriendLinksTable to reuse the cached
ensureSchema flow by calling await this.ensureSchema() instead of issuing its
own CREATE TABLE and CREATE INDEX statements. Preserve the existing friend_links
schema initialization through ensureSchema and avoid adding separate DDL round
trips for friend-link reads and writes.
In `@server/src/storage/db/postgres.ts`:
- Around line 1015-1032: 为 Postgres 适配器增加与 D1Adapter.schemaReady 等效的
Promise<void> | null 一次性缓存,并让 ensureCoreTables() 复用该缓存,避免重复执行完整建表与迁移检查;确保
getApprovedFriendLinks() 和 getAllFriendLinks() 等读路径继续通过 ensureCoreTables()
获取初始化保障,但不会在每次请求中重复运行 schema 初始化。
In `@server/src/storage/db/turso.ts`:
- Around line 186-203: Update ensureCoreTables() to invoke
ensureFriendLinksTable() during the unified core-table initialization, matching
D1Adapter.ensureSchema() while preserving the existing lazy ensures in
individual friend-link methods.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0a8cfb57-6e8e-4ad1-a539-bf1ffabd3ba3
📒 Files selected for processing (14)
client/src/app.tsxclient/src/components/admin-layout.tsxclient/src/components/navbar.tsxclient/src/lib/api.tsclient/src/pages/admin/friends.tsxclient/src/pages/friends.tsxserver/src/db/schema-pg.tsserver/src/db/schema.tsserver/src/index.tsserver/src/migrations/0010_friend_links.sqlserver/src/storage/db/d1.tsserver/src/storage/db/postgres.tsserver/src/storage/db/turso.tsserver/src/storage/interfaces.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
client/src/components/**
⚙️ CodeRabbit configuration file
client/src/components/**: 这是 React 前端组件目录。审查时请关注: 1. 是否同时兼容暗色和亮色主题(检查 CSS 变量和 data-theme) 2. 响应式布局是否完整(移动端/平板/桌面端) 3. 无障碍访问(aria 标签、键盘导航) 4. 组件是否保持单一职责
Files:
client/src/components/navbar.tsxclient/src/components/admin-layout.tsx
client/src/pages/**
⚙️ CodeRabbit configuration file
client/src/pages/**: 页面级组件。审查时请关注: 1. 数据加载和错误处理是否完善 2. SEO 相关(页面标题、meta 标签) 3. 导航和路由是否正确
Files:
client/src/pages/friends.tsxclient/src/pages/admin/friends.tsx
server/src/storage/**
⚙️ CodeRabbit configuration file
server/src/storage/**: 存储适配器层(数据库 + 对象存储)。审查时请关注: 1. 接口实现是否完整(IDatabase / IObjectStorage) 2. SQL 注入防护(参数化查询) 3. 多后端(D1/Turso/PostgreSQL)行为一致性 4. 错误处理和边界情况
Files:
server/src/storage/db/d1.tsserver/src/storage/db/turso.tsserver/src/storage/db/postgres.tsserver/src/storage/interfaces.ts
server/src/index.ts
⚙️ CodeRabbit configuration file
server/src/index.ts: Hono Workers API 路由总入口。审查时请关注: 1. JWT 认证中间件是否正确保护管理接口 2. CORS 配置是否安全 3. 请求参数验证
Files:
server/src/index.ts
🔇 Additional comments (22)
server/src/storage/interfaces.ts (2)
152-184: LGTM!
260-269: LGTM!server/src/migrations/0010_friend_links.sql (1)
1-17: LGTM!server/src/storage/db/d1.ts (4)
8-12: LGTM!Also applies to: 37-37, 72-72
1030-1065: LGTM!
1067-1118: LGTM!
1120-1145: LGTM!server/src/storage/db/postgres.ts (2)
118-135: LGTM!Also applies to: 223-225
1034-1112: LGTM!server/src/storage/db/turso.ts (1)
968-1085: LGTM!server/src/index.ts (5)
12-12: LGTM!Also applies to: 136-159, 161-198
646-663: LGTM!
1427-1498: LGTM!Also applies to: 1500-1505
844-847: 📐 Maintainability & Code Quality无需修改:
friendLinkAttempts已只声明一次。
1422-1425: 🔒 Security & Privacy无需修改:
/api/admin/friends*已在/api/admin/*JWT 前缀下受保护。client/src/lib/api.ts (2)
68-106: LGTM!Also applies to: 122-135
590-642: LGTM!client/src/pages/friends.tsx (1)
45-181: LGTM!client/src/app.tsx (1)
17-25: LGTM!Also applies to: 171-171, 221-221
client/src/components/navbar.tsx (1)
15-15: LGTM!client/src/components/admin-layout.tsx (1)
8-8: LGTM!Also applies to: 33-33
client/src/pages/admin/friends.tsx (1)
85-345: LGTM!
Summary
/friendspage and admin/admin/friendsmoderation UI/api/friendsendpoints and D1/Turso/PostgreSQL adapter support0010_friend_links.sqlContext
The current production
maindeployment no longer exposes/api/friendsbecause the friend-links commit lived ondevand was never merged intomain. This PR applies only the standalone friend-links commit onto the latestmain, preserving the merged RSS and sitemap fixes and recent dependency updates.Validation
npm run checknpm run lintnpm run buildmaincompleted without conflictsDeployment follow-up
After merge, apply the D1 migration, deploy Worker and Pages, then verify
/api/friendsreturns HTTP 200.