Repository navigation
启用 MCP 并修复热门列表和访问统计 - #5
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fe6f723f76
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| wiki.get('/w/:slug', async (c, next) => { | ||
| const start = Date.now(); await next(); | ||
| if (c.res.status === 200 && c.req.query('for_edit') !== 'true') { | ||
| const result = await c.res.clone().json() as {slug?:string; id?:number; is_private?:boolean; deleted_at?:number}; | ||
| if (result.id && result.slug && !result.is_private && !result.deleted_at) trackPageView(c, result.slug, Date.now()-start); |
There was a problem hiding this comment.
Restore view tracking for direct SSR loads
When a visitor opens or refreshes /w/:slug, src/client/pages/index.ts consumes the injected ssrData and deliberately skips the /api/w/:slug request, so this middleware never runs; the corresponding trackPageView calls were also removed from src/index.ts, including the SSR cache-hit paths. Consequently only SPA/API navigations are counted, while direct landings and refreshes—typically a large share of real traffic—are absent from totals and trending.
Useful? React with 👍 / 👎.
| const result = await c.res.clone().json() as {slug?:string; id?:number; is_private?:boolean; deleted_at?:number}; | ||
| if (result.id && result.slug && !result.is_private && !result.deleted_at) trackPageView(c, result.slug, Date.now()-start); |
There was a problem hiding this comment.
Exclude reads through private redirect sources
When an authorized user reads a private page that redirects to a public page, the response contains the public target's id, slug, and is_private=0, so this condition records the target even though the request originated from a private document. The removed SSR logic explicitly checked sourceWasPrivate; this replacement should likewise inspect redirected_from or propagate the source's privacy state so private-page reads do not inflate public analytics.
Useful? React with 👍 / 👎.
| if (c.res.status === 200 && c.req.query('for_edit') !== 'true') { | ||
| const result = await c.res.clone().json() as {slug?:string; id?:number; is_private?:boolean; deleted_at?:number}; | ||
| if (result.id && result.slug && !result.is_private && !result.deleted_at) trackPageView(c, result.slug, Date.now()-start); |
There was a problem hiding this comment.
Exclude all editor metadata reads from page views
Checking only for_edit=true still counts several non-viewing editor requests as visits: the child-document existence check and conflict metadata refresh in src/client/edit/main.ts, plus the permissions modal's fetchCurrentPage, all call /api/w/:slug?redirect=no&nocache=true without for_edit. Each successful lookup now increments totals and trending, so routine editing and administration corrupt the newly introduced statistics; these callers need a non-tracking marker or the route needs a reliable read-purpose distinction.
Useful? React with 👍 / 👎.
| const accountId = c.env.CF_ACCOUNT_ID; | ||
| const apiToken = c.env.CF_API_TOKEN; | ||
| if (!accountId || !apiToken) return c.json({ trending: [] }); | ||
| if (!accountId || !apiToken || !c.env.ANALYTICS) return c.json({ trending: await localTrending(c.env.DB, Math.min(72, Math.max(1, Number(c.req.query('hours')) || 24)), Math.min(20, Math.max(1, Number(c.req.query('limit')) || 10))) }, 200, { 'Cache-Control': 'no-store' }); |
There was a problem hiding this comment.
Cache the polled D1 trending result
In environments without complete Analytics Engine configuration—including the checked production configuration—this branch executes the aggregate D1 query and returns no-store, so it never populates the cache checked immediately above. Since src/client/common.ts now polls this endpoint every 30 seconds in every visible tab, every active user causes two aggregate scans per minute; traffic therefore scales directly into D1 rows-read and latency even though a shared cache with a roughly 30-second TTL would preserve the requested refresh cadence.
Useful? React with 👍 / 👎.
| if(endpoint?.startsWith('page/')) { | ||
| const slug=c.req.param('slug') || '';return {slug,total:(await localPageViews(db,slug)).total,daily:await all(`SELECT date(a.hour,'unixepoch') AS date,SUM(a.events) AS views ${PUBLIC} AND p.slug=? AND a.hour>=? GROUP BY date ORDER BY date`,[slug,since])}; |
There was a problem hiding this comment.
Respect the requested period in per-page totals
For /api/admin/analytics/page/:slug?period=..., the fallback builds daily using the calculated since cutoff but obtains total from localPageViews, whose total comes from the lifetime wiki_page_view_totals table. This differs from the Analytics Engine implementation and the endpoint contract, where total is limited to the requested 7, 30, or 90 days, so older views make the headline total disagree with the accompanying daily series.
Useful? React with 👍 / 👎.
线上生产配置把 MCP 关闭,统计依赖未配置的 Analytics Engine/查询凭据,导致 MCP 403、热门为空及访问量恒为 0。
生产配置启用 MCP,认证与编辑权限继续执行。缺少完整 Analytics Engine 配置时,热门、文档访问量及后台统计改用 D1 小时汇总;公开文档 API 读取计数,覆盖单页导航和缓存命中,避免 SSR/API 双计数。私有、删除、编辑器读取和机器人不进入公开热门;移动文档保留累计数。明细约保留 90 天,累计访问单独保留。后备性能百分位是直方图上界估算,历史未记录访问无法补算。
热门列表每 30 秒刷新,更新时清理旧的滚动定时器/resize 监听。
验证:类型检查、4 项新服务测试(MCP 初始化/工具列表、D1 计数与访问隔离、后台权限及无外部凭据统计)、既有权限/Git/i18n 测试、完整构建与 42 个页面语言检查、Wrangler dry-run。