Skip to content

fix: 修复 my-usage 今日统计与只读 API 自助查询#532

Merged
ding113 merged 2 commits intodevfrom
fix/my-usage-today-stats-readonly-api
Jan 4, 2026
Merged

fix: 修复 my-usage 今日统计与只读 API 自助查询#532
ding113 merged 2 commits intodevfrom
fix/my-usage-today-stats-readonly-api

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Jan 4, 2026

Summary

This PR fixes bugs in the my-usage page's "today stats" feature and adds readonly API endpoints for self-service usage queries. Users were seeing all zeros in their "today usage" statistics despite logs showing correctly below.

Problem

  1. Today Stats Always Zero: The getMyTodayStats query used raw SQL ${timeRange.endTime} which caused Date injection issues, resulting in incorrect time range filtering
  2. Warmup Requests Counted: Statistics included warmup requests that should be excluded
  3. No Readonly API Access: Users with readonly keys couldn't query their usage programmatically - had to rely on Web UI
  4. Model Filter Inconsistency: Available models dropdown used originalModel but logs filtered by model, causing filter mismatches

Related Issues:

Solution

1. Fix Today Stats Query

Changed from raw SQL comparison to proper Drizzle ORM operator:
```typescript
// Before: Date injection issue
sql`${messageRequest.createdAt} < ${timeRange.endTime}`

// After: Proper typed comparison
lt(messageRequest.createdAt, timeRange.endTime)
```

2. Exclude Warmup Requests from Stats

Added unified EXCLUDE_WARMUP_CONDITION to all statistics queries:
```typescript
const EXCLUDE_WARMUP_CONDITION = sql`(blocked_by IS NULL OR blocked_by <> 'warmup')`;
```

3. New Readonly API Endpoints

Added 6 my-usage endpoints accessible via Bearer token (for scripts/CLI):

  • POST /api/actions/my-usage/getMyUsageMetadata
  • POST /api/actions/my-usage/getMyQuota
  • POST /api/actions/my-usage/getMyTodayStats
  • POST /api/actions/my-usage/getMyUsageLogs
  • POST /api/actions/my-usage/getMyAvailableModels
  • POST /api/actions/my-usage/getMyAvailableEndpoints

4. Bearer Token Authentication

Added Authorization: Bearer <token> support as alternative to Cookie authentication:

  • Registered bearerAuth security scheme in OpenAPI spec
  • action-adapter-openapi.ts now checks both Cookie and Authorization header
  • Added allowReadOnlyAccess option to bypass canLoginWebUi check for readonly endpoints

5. Fix Model Filter Consistency

Changed getDistinctModelsForKey to use message_request.model field (matching log filter behavior) instead of originalModel.

Changes

Core Changes

File Change
src/actions/my-usage.ts Fix time range query using lt(), add warmup exclusion
src/app/api/actions/[...route]/route.ts Add 6 my-usage readonly endpoints + bearerAuth scheme
src/lib/api/action-adapter-openapi.ts Bearer token auth + allowReadOnlyAccess option
src/repository/usage-logs.ts Fix model filter to use message_request.model

Supporting Changes

File Change
src/repository/system-config.ts Graceful degradation for missing DB columns
tests/api/action-adapter-openapi.unit.test.ts Unit tests for adapter logic
tests/api/auth.unit.test.ts Auth module unit tests
tests/api/my-usage-readonly.test.ts Readonly API integration tests
vitest.my-usage.config.ts Dedicated test config for my-usage module

Testing

Automated Tests

  • bun run lint - Code style checks
  • bun run typecheck - TypeScript validation
  • bun run build - Production build
  • bun run test - All existing tests pass
  • bun run test:coverage:my-usage - New my-usage specific tests

Manual Testing

  1. Navigate to /my-usage page
  2. Verify "Today Usage" card shows correct non-zero values
  3. Verify model filter dropdown matches available log entries
  4. Test API with Bearer token: curl -H "Authorization: Bearer <key>" -X POST .../api/actions/my-usage/getMyQuota

Breaking Changes

None. All changes are backwards compatible:

  • Bearer auth is additive (Cookie auth still works)
  • New API endpoints are additive
  • Query fixes don't change data schema

Description enhanced by Claude AI

@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

📝 Walkthrough

Walkthrough

该PR新增“我的用量”只读API端点、扩展认证以支持 Authorization: Bearer 与只读令牌,排除 warmup 日志的查询条件,增强 system_settings 容错与若干仓库查询,并补充大量单元/集成测试与专用 Vitest 配置(覆盖与并行清理协调)。

Changes

Cohort / File(s) 变更摘要
API 与 OpenAPI 适配器
src/app/api/actions/[...route]/route.ts, src/lib/api/action-adapter-openapi.ts
新增 6 个 "我的用量" 只读路由并在 OpenAPI 中注册;增加 bearerAuth 安全方案;为 action 路由引入 allowReadOnlyAccess 选项;支持从 Authorization header 提取 Bearer token 并将该选项传递给验证流程。
认证逻辑
src/lib/auth.ts
新增 parseBearerTokengetAuthToken(内部),getSession 优先使用 cookie,cookie 不存在时回退到 Authorization: Bearer。
用途统计与日志查询
src/actions/my-usage.ts, src/repository/usage-logs.ts
引入 EXCLUDE_WARMUP_CONDITION 并在今日统计与细分查询中排除 warmup;将 model 去空处理以仅返回非空模型值(getDistinctModelsForKey)。
系统配置容错
src/repository/system-config.ts
新增 isUndefinedColumnError 辅助函数,改用 select helper 与 onConflictDoNothing + 回退查询以提升对缺失列/表的容错,添加默认回退设置路径。
测试 与 Mocks
tests/*, tests/nextjs.mock.ts, tests/unit/*
添加/扩展多项测试:action-adapter 单元测试、auth 权限与 cookie 测试、my-usage 只读集成测试、API 注册完整性测试;扩展 next/headers mock 与 logger mock(新增 info/fatal 等方法);调整测试工具(UndiciRequest 替换 Request,test-utils 添加 Authorization header 支持)。
测试基础设施
vitest.my-usage.config.ts, tests/setup.ts, tests/cleanup-utils.ts, tests/test-utils.ts
新增专用 Vitest 配置(针对 my-usage 的 include、覆盖阈值与 coverage 输出);tests/setup 引入基于 Redis 的并行 worker 清理协调;cleanup-utils 用 Drizzle 替换原始 SQL 更新;test-utils 使用 UndiciRequest 并在有 authToken 时注入 Authorization 头。
包脚本
package.json
新增脚本 test:coverage:my-usage 调用 vitest run --config vitest.my-usage.config.ts --coverage 用于专用覆盖测试。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题清晰准确地概括了PR的核心变更:修复my-usage今日统计bug且添加只读API自助查询功能。
Description check ✅ Passed 描述详细相关,涵盖问题根源、解决方案、核心变更表格、测试方案和向后兼容性说明,与代码变更完全相符。
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/my-usage-today-stats-readonly-api

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求主要旨在提升用户在 my-usage 页面上的数据准确性和 API 访问的灵活性。通过修复今日使用统计的计算逻辑,用户现在可以获得正确的用量数据。同时,引入只读 API 自助查询端点和增强的认证机制,使得外部系统能够更便捷、安全地访问用户用量信息。此外,还对系统配置的数据库兼容性进行了改进,并增加了全面的测试覆盖,以确保功能的稳定性和安全性。

Highlights

  • 修复今日使用统计: 解决了 my-usage 页面中“今日使用”统计数据全部为 0 的问题,确保了时间范围查询的准确性,并统一排除了预热(warmup)请求的统计。
  • 新增只读 API 自助查询端点: 在 /api/actions 下新增了 my-usage 自助查询端点,允许只读 API Key 访问,从而避免了对 Web UI 权限的依赖,提升了 API 的灵活性。
  • Actions Adapter 认证方式增强: Actions Adapter 现在支持使用 Authorization: Bearer <token> 作为 Cookie 的补充认证方式,并在 OpenAPI 文档中声明了 bearerAuth,提供了更灵活的认证选项。
  • 可用模型筛选接口修复: 修复了可用模型筛选接口的返回逻辑,使其与日志筛选字段(按 message_request.model)保持一致,提高了数据一致性。
  • 测试覆盖率与清理机制优化: 新增了针对 my-usage 模块的专项测试配置和单元测试,包括 API 完整性、认证逻辑和只读访问测试,并优化了测试环境的并行清理协调机制。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 4, 2026

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@github-actions github-actions bot added size/XL Extra Large PR (> 1000 lines) bug Something isn't working area:core area:statistics labels Jan 4, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 质量很高,有效地修复了 my-usage 页面的今日统计功能,并通过引入 allowReadOnlyAccess 机制和 Authorization: Bearer 支持,安全地开放了只读 API,提升了系统的灵活性。代码层面,通过使用 Drizzle 的 lt 操作符替代原生 SQL 片段,修复了潜在的 SQL 注入风险,这是一个关键的安全改进。同时,对 getSystemSettings 的重构增强了对旧数据库架构的兼容性,使系统更具鲁棒性。此外,PR 包含了大量高质量的单元测试和集成测试,特别是为新功能增加了专门的测试覆盖率配置,这极大地保证了代码的正确性和未来的可维护性。整体而言,这是一次非常出色的代码贡献。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/repository/system-config.ts (1)

74-133: 建议抽取通用的递归错误检测逻辑以减少重复代码。

isUndefinedColumnErrorisTableMissingError(第12-72行)结构几乎完全相同,仅错误码和匹配字符串不同。可考虑抽取一个通用的递归错误检测工厂函数。

🔎 重构建议
type ErrorMatcher = (value: string) => boolean;

function createRecursiveErrorChecker(matcher: ErrorMatcher) {
  return function check(error: unknown, depth = 0): boolean {
    if (!error || depth > 5) return false;

    if (typeof error === "string") {
      return matcher(error.toLowerCase());
    }

    if (typeof error === "object") {
      const err = error as {
        code?: unknown;
        message?: unknown;
        cause?: unknown;
        errors?: unknown;
        originalError?: unknown;
      };

      if (typeof err.code === "string" && matcher(err.code.toLowerCase())) {
        return true;
      }
      if (typeof err.message === "string" && check(err.message, depth + 1)) {
        return true;
      }
      if ("cause" in err && err.cause && check(err.cause, depth + 1)) {
        return true;
      }
      if (Array.isArray(err.errors)) {
        return err.errors.some((item) => check(item, depth + 1));
      }
      if (err.originalError && check(err.originalError, depth + 1)) {
        return true;
      }

      try {
        const stringified = String(error);
        if (stringified && check(stringified, depth + 1)) return true;
      } catch { /* ignore */ }
    }

    return false;
  };
}

const isTableMissingError = createRecursiveErrorChecker((s) =>
  s.includes("42p01") ||
  (s.includes("system_settings") &&
    (s.includes("does not exist") || s.includes("doesn't exist") || s.includes("找不到")))
);

const isUndefinedColumnError = createRecursiveErrorChecker((s) =>
  s.includes("42703") ||
  (s.includes("column") &&
    (s.includes("does not exist") || s.includes("doesn't exist") || s.includes("不存在")))
);
src/actions/my-usage.ts (1)

23-24: Warmup 排除条件定义正确,但存在重复定义。

该条件与 src/repository/usage-logs.ts 第 9 行的 EXCLUDE_WARMUP_CONDITION 完全相同。考虑提取到共享模块避免重复。

🔎 可选:提取到共享常量

src/repository/usage-logs.ts 中导出该常量:

-const EXCLUDE_WARMUP_CONDITION = sql`(${messageRequest.blockedBy} IS NULL OR ${messageRequest.blockedBy} <> 'warmup')`;
+export const EXCLUDE_WARMUP_CONDITION = sql`(${messageRequest.blockedBy} IS NULL OR ${messageRequest.blockedBy} <> 'warmup')`;

然后在 src/actions/my-usage.ts 中导入:

-// Warmup 抢答请求只用于探测/预热:日志可见,但不计入任何聚合统计
-const EXCLUDE_WARMUP_CONDITION = sql`(${messageRequest.blockedBy} IS NULL OR ${messageRequest.blockedBy} <> 'warmup')`;
+import { EXCLUDE_WARMUP_CONDITION } from "@/repository/usage-logs";
tests/api/action-adapter-openapi.unit.test.ts (1)

173-196: 测试覆盖基本功能,可考虑增强 createActionRoutes 验证。

当前 createActionRoutes 测试仅验证返回数组长度,未验证生成的 route/handler 是否可用。

🔎 可选:增强 createActionRoutes 测试
 test("createActionRoutes:应批量生成 route/handler", () => {
   const routes = createActionRoutes(
     "demo",
     {
       a: async () => ({ ok: true, data: 1 }),
       b: async () => 2,
     },
     {
       b: { requiresAuth: false },
     }
   );

   expect(routes).toHaveLength(2);
+  for (const { route, handler } of routes) {
+    expect(route).toBeDefined();
+    expect(typeof handler).toBe("function");
+  }
 });
tests/api/my-usage-readonly.test.ts (1)

321-321: 精度参数过高,建议简化。

toBeCloseTo(0.02, 10) 中精度参数 10 表示比较到小数点后 10 位,对于 USD 金额过于严格。建议使用 2-4 位。

-    expect(data.costUsd).toBeCloseTo(0.02, 10);
+    expect(data.costUsd).toBeCloseTo(0.02, 4);
tests/api/auth.unit.test.ts (1)

29-39: Mock 与 Next.js 15+ 异步 cookies API 的兼容性

在 Next.js 15/16 中,cookies() 函数是异步的,返回 Promise<ReadonlyRequestCookies>。当前 mock 返回的是一个同步对象。虽然由于 await 在非 Promise 值上会直接返回该值,这个 mock 在当前测试中仍然可以工作,但建议显式返回 Promise 以保持与实际 API 的一致性,避免未来潜在的兼容性问题。

🔎 建议的改进
 vi.mock("next/headers", () => ({
-  cookies: () => ({
+  cookies: async () => ({
     get: (name: string) => {
       if (name !== "auth-token") return undefined;
       return currentCookieValue ? { value: currentCookieValue } : undefined;
     },
     set: cookieSet,
     delete: cookieDelete,
     has: (name: string) => name === "auth-token" && Boolean(currentCookieValue),
   }),
 }));
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

📥 Commits

Reviewing files that changed from the base of the PR and between b7d5bcd and 1e6abad.

📒 Files selected for processing (16)
  • package.json
  • src/actions/my-usage.ts
  • src/app/api/actions/[...route]/route.ts
  • src/lib/api/action-adapter-openapi.ts
  • src/repository/system-config.ts
  • src/repository/usage-logs.ts
  • tests/api/action-adapter-openapi.unit.test.ts
  • tests/api/api-actions-integrity.test.ts
  • tests/api/auth.unit.test.ts
  • tests/api/my-usage-readonly.test.ts
  • tests/cleanup-utils.ts
  • tests/setup.ts
  • tests/test-utils.ts
  • tests/unit/proxy/session-guard-warmup-intercept.test.ts
  • tests/unit/repository/warmup-stats-exclusion.test.ts
  • vitest.my-usage.config.ts
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use 2-space indentation in all code files

Files:

  • src/repository/usage-logs.ts
  • tests/api/api-actions-integrity.test.ts
  • src/repository/system-config.ts
  • src/actions/my-usage.ts
  • package.json
  • tests/api/action-adapter-openapi.unit.test.ts
  • tests/api/my-usage-readonly.test.ts
  • tests/unit/repository/warmup-stats-exclusion.test.ts
  • tests/setup.ts
  • tests/api/auth.unit.test.ts
  • tests/test-utils.ts
  • vitest.my-usage.config.ts
  • src/lib/api/action-adapter-openapi.ts
  • tests/cleanup-utils.ts
  • src/app/api/actions/[...route]/route.ts
  • tests/unit/proxy/session-guard-warmup-intercept.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use double quotes for strings instead of single quotes
Use trailing commas in multi-line structures
Enforce maximum line length of 100 characters
Use path alias @/* to reference files from ./src/* directory

**/*.{ts,tsx,js,jsx}: Use Biome for linting and formatting with 2-space indent, double quotes, trailing commas, and 100 character max line length
Use path alias @/* to reference files in ./src/* directory

Files:

  • src/repository/usage-logs.ts
  • tests/api/api-actions-integrity.test.ts
  • src/repository/system-config.ts
  • src/actions/my-usage.ts
  • tests/api/action-adapter-openapi.unit.test.ts
  • tests/api/my-usage-readonly.test.ts
  • tests/unit/repository/warmup-stats-exclusion.test.ts
  • tests/setup.ts
  • tests/api/auth.unit.test.ts
  • tests/test-utils.ts
  • vitest.my-usage.config.ts
  • src/lib/api/action-adapter-openapi.ts
  • tests/cleanup-utils.ts
  • src/app/api/actions/[...route]/route.ts
  • tests/unit/proxy/session-guard-warmup-intercept.test.ts
src/repository/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use Repository pattern in src/repository/ to wrap Drizzle queries

Files:

  • src/repository/usage-logs.ts
  • src/repository/system-config.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict mode for type safety
Use readonly or const assertions for immutable data structures

Files:

  • src/repository/usage-logs.ts
  • tests/api/api-actions-integrity.test.ts
  • src/repository/system-config.ts
  • src/actions/my-usage.ts
  • tests/api/action-adapter-openapi.unit.test.ts
  • tests/api/my-usage-readonly.test.ts
  • tests/unit/repository/warmup-stats-exclusion.test.ts
  • tests/setup.ts
  • tests/api/auth.unit.test.ts
  • tests/test-utils.ts
  • vitest.my-usage.config.ts
  • src/lib/api/action-adapter-openapi.ts
  • tests/cleanup-utils.ts
  • src/app/api/actions/[...route]/route.ts
  • tests/unit/proxy/session-guard-warmup-intercept.test.ts
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.ts: Hash API keys using SHA-256 before storing in database, never store plaintext keys
Mask API keys and sensitive data in application logs
Validate required environment variables at startup with clear error messages

Files:

  • src/repository/usage-logs.ts
  • src/repository/system-config.ts
  • src/actions/my-usage.ts
  • src/lib/api/action-adapter-openapi.ts
  • src/app/api/actions/[...route]/route.ts
src/**/*{message,response,log}*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Log request duration, token usage, and cost to message_request table for analytics

Files:

  • src/repository/usage-logs.ts
src/{repository,actions}/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Avoid N+1 queries by using eager loading and batch queries for statistics

Files:

  • src/repository/usage-logs.ts
  • src/repository/system-config.ts
  • src/actions/my-usage.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Vitest for unit testing with Node environment, coverage thresholds: 50% lines/functions, 40% branches

Files:

  • tests/api/api-actions-integrity.test.ts
  • tests/api/action-adapter-openapi.unit.test.ts
  • tests/api/my-usage-readonly.test.ts
  • tests/unit/repository/warmup-stats-exclusion.test.ts
  • tests/api/auth.unit.test.ts
  • tests/unit/proxy/session-guard-warmup-intercept.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

Ensure test database names contain 'test' keyword for safety validation

Files:

  • tests/api/api-actions-integrity.test.ts
  • tests/api/action-adapter-openapi.unit.test.ts
  • tests/api/my-usage-readonly.test.ts
  • tests/unit/repository/warmup-stats-exclusion.test.ts
  • tests/api/auth.unit.test.ts
  • tests/unit/proxy/session-guard-warmup-intercept.test.ts
src/actions/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/actions/**/*.ts: Validate all user inputs with Zod schemas before processing
Use Server Actions in next-safe-action with OpenAPI generation for admin API endpoints
Use Next.js API Routes and Server Actions for admin operations and REST endpoints

Files:

  • src/actions/my-usage.ts
**/*.{tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW

Files:

  • package.json
src/lib/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use connection pooling for database and Redis connections

Files:

  • src/lib/api/action-adapter-openapi.ts
src/app/api/actions/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Server Actions in src/app/api/actions/ must auto-generate OpenAPI 3.1.0 spec from Zod schemas and expose as REST endpoints

Files:

  • src/app/api/actions/[...route]/route.ts
src/app/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Implement Content-Security-Policy headers for XSS prevention

Files:

  • src/app/api/actions/[...route]/route.ts
src/app/api/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Implement health check endpoint returning database and Redis status

Files:

  • src/app/api/actions/[...route]/route.ts
🧠 Learnings (24)
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/app/api/actions/**/*.ts : Server Actions in `src/app/api/actions/` must auto-generate OpenAPI 3.1.0 spec from Zod schemas and expose as REST endpoints

Applied to files:

  • tests/api/api-actions-integrity.test.ts
  • tests/api/action-adapter-openapi.unit.test.ts
  • src/lib/api/action-adapter-openapi.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/actions/**/*.ts : Use Server Actions in `next-safe-action` with OpenAPI generation for admin API endpoints

Applied to files:

  • tests/api/api-actions-integrity.test.ts
  • tests/api/action-adapter-openapi.unit.test.ts
  • src/lib/api/action-adapter-openapi.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/drizzle/**/*.ts : Use soft delete pattern with `deletedAt` column instead of hard deletes

Applied to files:

  • src/actions/my-usage.ts
  • tests/cleanup-utils.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*{message,response,log}*.ts : Log request duration, token usage, and cost to message_request table for analytics

Applied to files:

  • src/actions/my-usage.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/{repository,actions}/**/*.ts : Avoid N+1 queries by using eager loading and batch queries for statistics

Applied to files:

  • src/actions/my-usage.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/drizzle/**/*.ts : Use Drizzle ORM with PostgreSQL for database operations

Applied to files:

  • src/actions/my-usage.ts
  • tests/cleanup-utils.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/drizzle/**/*.ts : Use Drizzle ORM with parameterized queries to prevent SQL injection

Applied to files:

  • src/actions/my-usage.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/drizzle/**/*.ts : Implement proper indexing strategy for common queries and foreign keys

Applied to files:

  • src/actions/my-usage.ts
  • tests/cleanup-utils.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/drizzle/**/*.ts : Use JSON columns in PostgreSQL for flexible data structures (modelRedirects, providerChain, etc.)

Applied to files:

  • src/actions/my-usage.ts
  • tests/cleanup-utils.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/repository/**/*.ts : Use Repository pattern in `src/repository/` to wrap Drizzle queries

Applied to files:

  • src/actions/my-usage.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest for unit testing with Node environment, coverage thresholds: 50% lines/functions, 40% branches

Applied to files:

  • package.json
  • tests/setup.ts
  • tests/api/auth.unit.test.ts
  • vitest.my-usage.config.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to tests/integration/**/*.test.{ts,tsx} : Integration tests requiring database must be placed in `tests/integration/` and use test database with 'test' in the name

Applied to files:

  • package.json
  • tests/api/my-usage-readonly.test.ts
  • tests/setup.ts
  • tests/api/auth.unit.test.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.test.ts : Ensure test database names contain 'test' keyword for safety validation

Applied to files:

  • package.json
  • tests/setup.ts
  • tests/api/auth.unit.test.ts
  • tests/cleanup-utils.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to tests/integration/**/*.test.ts : Place integration tests requiring database in `tests/integration/` (excluded by default)

Applied to files:

  • package.json
  • tests/setup.ts
  • tests/api/auth.unit.test.ts
  • vitest.my-usage.config.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/actions/**/*.ts : Use Next.js API Routes and Server Actions for admin operations and REST endpoints

Applied to files:

  • tests/api/action-adapter-openapi.unit.test.ts
  • tests/test-utils.ts
  • src/app/api/actions/[...route]/route.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.{ts,tsx} : Use readonly or const assertions for immutable data structures

Applied to files:

  • tests/api/my-usage-readonly.test.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*.ts : Validate required environment variables at startup with clear error messages

Applied to files:

  • tests/setup.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*.ts : Mask API keys and sensitive data in application logs

Applied to files:

  • tests/setup.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*{guard,auth}*.ts : Use constant-time comparison for API key validation to prevent timing attacks

Applied to files:

  • tests/api/auth.unit.test.ts
  • src/lib/api/action-adapter-openapi.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/app/v1/_lib/**/*.ts : Guard pipeline must execute in order: ProxyAuthenticator, SensitiveWordGuard, VersionGuard, ProxySessionGuard, ProxyRateLimitGuard, ProxyProviderResolver

Applied to files:

  • tests/api/auth.unit.test.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*.ts : Implement guard pipeline pattern for cross-cutting concerns in request processing (auth, rate limiting, session)

Applied to files:

  • tests/api/auth.unit.test.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*.ts : Use undici library for HTTP requests instead of node-fetch for better performance

Applied to files:

  • tests/test-utils.ts
📚 Learning: 2026-01-03T09:10:02.182Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 517
File: src/app/v1/_lib/proxy/auth-guard.ts:205-232
Timestamp: 2026-01-03T09:10:02.182Z
Learning: In `src/app/v1/_lib/proxy/auth-guard.ts`, the `extractApiKeyFromHeaders` function is intentionally a standalone simplified version for non-Guard flows. It deliberately does not check for multi-source conflicts (unlike `ProxyAuthenticator.validate`), and the code duplication is intentional to avoid coupling between Guard and non-Guard authentication flows.

Applied to files:

  • src/lib/api/action-adapter-openapi.ts
📚 Learning: 2026-01-03T09:08:49.020Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.020Z
Learning: Keep utility functions single-purpose and reuse helpers from `src/lib` and `src/actions`

Applied to files:

  • src/app/api/actions/[...route]/route.ts
🧬 Code graph analysis (9)
src/repository/usage-logs.ts (1)
src/drizzle/schema.ts (1)
  • messageRequest (264-345)
src/repository/system-config.ts (2)
src/drizzle/schema.ts (1)
  • systemSettings (439-473)
src/repository/_shared/transformers.ts (1)
  • toSystemSettings (140-158)
tests/api/action-adapter-openapi.unit.test.ts (1)
src/lib/api/action-adapter-openapi.ts (6)
  • createActionRoute (253-419)
  • createActionRoutes (451-460)
  • createParamSchema (466-467)
  • IdParamSchema (472-474)
  • PaginationSchema (479-482)
  • SortSchema (487-490)
tests/api/my-usage-readonly.test.ts (3)
src/drizzle/db.ts (1)
  • db (37-44)
src/drizzle/schema.ts (3)
  • users (33-84)
  • keys (87-126)
  • messageRequest (264-345)
tests/test-utils.ts (1)
  • callActionsRoute (36-77)
tests/setup.ts (2)
src/lib/rate-limit/service.ts (1)
  • redis (97-99)
tests/cleanup-utils.ts (1)
  • cleanupRecentTestData (86-88)
tests/api/auth.unit.test.ts (1)
src/lib/auth.ts (6)
  • validateKey (17-86)
  • getSession (116-128)
  • setAuthCookie (94-104)
  • getAuthCookie (106-109)
  • clearAuthCookie (111-114)
  • getLoginRedirectTarget (88-92)
src/lib/api/action-adapter-openapi.ts (1)
src/lib/auth.ts (1)
  • validateKey (17-86)
tests/cleanup-utils.ts (2)
src/drizzle/db.ts (1)
  • db (37-44)
src/drizzle/schema.ts (1)
  • users (33-84)
src/app/api/actions/[...route]/route.ts (1)
src/lib/api/action-adapter-openapi.ts (1)
  • createActionRoute (253-419)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Greptile Review
  • GitHub Check: pr-review
  • GitHub Check: check-codex-status
  • GitHub Check: pr-label
  • GitHub Check: pr-description
  • GitHub Check: 🌐 API Tests
  • GitHub Check: Docker Build Test
  • GitHub Check: dev-build-deploy
🔇 Additional comments (34)
src/repository/system-config.ts (2)

160-205: 降级读取逻辑设计合理。

selectSettingsRow 内部 helper 采用"先尝试全字段、失败后降级到最小字段集"的策略,能够优雅地兼容尚未完成迁移的旧版数据库。toSystemSettings 转换器会为缺失字段填充默认值,确保返回完整的 SystemSettings 对象。


214-224: onConflictDoNothing + 后续查询模式正确处理并发竞态。

使用 onConflictDoNothing 避免了多进程并发初始化时的唯一约束冲突,随后调用 selectSettingsRow 确保获取实际写入的记录。这种模式比 .returning() 更健壮,因为 returningonConflictDoNothing 场景下不会返回已存在的行。

tests/test-utils.ts (2)

53-57: LGTM:Bearer 令牌认证支持

正确实现了 Authorization 头的后备逻辑,与 PR 中新增的 bearerAuth 认证方式保持一致。使用 ?? 运算符避免覆盖已存在的 Authorization 头是正确的做法。


63-67: 代码正确使用了 undici.Request,与 Next.js 路由处理器完全兼容

undici^7 提供的 Request 实现遵循 Web API 标准,Hono 的 handle() 函数(通过 hono/vercel)完全支持任何符合 Web API 的 Request 对象。这种测试方式是标准的 in-process 测试模式,避免了启动完整开发服务器的开销,同时保留了 HTTP 语义验证。代码实现无需修改。

tests/unit/proxy/session-guard-warmup-intercept.test.ts (1)

40-49: LGTM:完善 logger mock 覆盖

扩展了 logger mock 以包含 infofatal 方法,与其他测试文件中的类似更新保持一致。这确保了测试代码可以调用所有 logger 方法而不会出错。

tests/unit/repository/warmup-stats-exclusion.test.ts (1)

180-189: LGTM:完善 logger mock 覆盖

扩展了 logger mock 以包含 debuginfowarnfatal 方法,确保测试中所有 logger 调用都有对应的 mock。与其他测试文件的更新保持一致。

tests/cleanup-utils.ts (3)

7-9: LGTM:导入优化

正确添加了 inArray 用于新的查询构建器语法,并将 keys 别名为 keysTable 以避免与 JavaScript 关键字冲突。


56-68: 优秀:迁移到 Drizzle 查询构建器

从原始 SQL 迁移到 Drizzle 查询构建器是正确的改进:

  • 使用共享的 now 时间戳确保 keys 和 users 的 deletedAtupdatedAt 一致
  • 正确实现了软删除模式(使用 deletedAt 列,符合 learnings)
  • 类型安全且更易维护
  • WHERE 条件逻辑正确:inArray 用于 ID 筛选,isNull 确保不重复删除

根据学习记录,基于 learnings...


74-74: LGTM:正确调整返回值处理

使用 .length 获取数组长度是正确的,因为 Drizzle 的 .returning() 返回的是记录数组而非包含 count 属性的对象。

tests/setup.ts (3)

13-16: LGTM:简化测试输出

为 dotenv 配置添加 quiet: true 可以抑制环境变量加载消息,使测试输出更清晰。


67-104: 良好的并行清理协调机制

Redis 计数器协调逻辑设计合理:

  • 使用数据库名称隔离不同测试套件的计数器
  • 2 秒 Redis 就绪超时确保不会无限等待
  • 15 分钟过期时间防止异常退出导致的计数器泄漏
  • 失败时仅警告而不影响测试运行

但需要注意该逻辑的复杂性,建议添加文档说明计数器的生命周期。


116-150: 清理协调逻辑正确但需注意潜在竞态

afterAll 中的计数器递减和清理逻辑设计合理:

  • 只有最后一个 worker(remaining <= 0)执行清理
  • 清理后删除计数器键防止重用
  • 多层级的错误处理确保测试不会因清理失败而中断

注意事项:
虽然 Redis 命令本身是原子的,但在极端情况下(如多个 worker 几乎同时完成),decrdel 之间可能存在微小时间窗口。不过由于 decr 返回值决定是否清理,且 Redis 命令是原子的,实际风险很低。

tests/api/api-actions-integrity.test.ts (2)

116-130: LGTM!新增的 my-usage 模块端点完整性测试符合规范。

测试结构与其他模块保持一致,覆盖了所有 6 个 my-usage 端点。


227-240: 缺少 my-usage 模块的标签映射。

moduleTagMapping 中未添加 /api/actions/my-usage/ 的标签映射。如果 my-usage 端点在 OpenAPI 中配置了 tags,这个测试会因标签不匹配而失败。

请确认 my-usage 端点是否配置了 OpenAPI tags,如有需要,请补充:

 const moduleTagMapping: Record<string, string> = {
   "/api/actions/users/": "用户管理",
   "/api/actions/keys/": "密钥管理",
   "/api/actions/providers/": "供应商管理",
   "/api/actions/model-prices/": "模型价格",
   "/api/actions/statistics/": "统计分析",
   "/api/actions/usage-logs/": "使用日志",
   "/api/actions/overview/": "概览",
   "/api/actions/sensitive-words/": "敏感词管理",
   "/api/actions/active-sessions/": "Session 管理",
   "/api/actions/notifications/": "通知管理",
   "/api/actions/webhook-targets/": "通知管理",
   "/api/actions/notification-bindings/": "通知管理",
+  "/api/actions/my-usage/": "我的用量",
 };
package.json (1)

22-22: LGTM!新增的 my-usage 专项覆盖率脚本符合规范。

命名和配置模式与现有的 test:coverage:quota 保持一致。

src/repository/usage-logs.ts (1)

268-281: LGTM!模型筛选口径统一修复正确。

COALESCE(original_model, model) 改为直接使用 messageRequest.model,与 findUsageLogsWithDetails 的筛选字段保持一致,避免筛选器下拉列表和实际筛选结果不匹配的问题。

src/actions/my-usage.ts (2)

305-313: LGTM!时间范围查询修复正确。

使用 gte()lt() 替代原始 SQL 模板,类型更安全且与 Drizzle ORM 风格一致。Warmup 排除条件正确应用于聚合查询。


325-333: LGTM!Breakdown 查询与聚合查询条件保持一致。

Warmup 排除条件正确应用于模型分组统计,确保"今日统计"与"模型明细"口径统一。

tests/api/action-adapter-openapi.unit.test.ts (2)

1-38: LGTM!测试辅助函数设计合理。

createMockContext 模拟了 Hono 上下文的核心接口,支持自定义请求体和 JSON 解析异常场景,覆盖了测试所需的关键路径。


40-171: LGTM!执行器逻辑测试覆盖全面。

测试用例涵盖了:

  • 非 ActionResult 返回值自动包装
  • 单字段/多字段参数推断
  • 显式 argsMapper 映射
  • 业务错误透传(ok=false)
  • Error 与非 Error 异常处理
  • 无效 JSON 降级处理
vitest.my-usage.config.ts (1)

11-58: LGTM!my-usage 专项覆盖率配置合理。

配置要点:

  • 针对安全敏感的只读 API 设置更高覆盖率阈值(80%)
  • 独立的覆盖率报告目录避免与全局覆盖率混淆
  • 测试文件精准包含,避免不相关模块影响阈值
tests/api/my-usage-readonly.test.ts (4)

20-30: LGTM!next/headers mock 实现正确。

通过 currentAuthToken 变量同步 adapter 和 action 的认证状态,避免测试中的假失败。


118-141: LGTM!afterAll 清理逻辑正确使用软删除。

符合项目规范,避免硬删除带来的外键约束问题。清理顺序(message → keys → users)也符合依赖关系。


212-379: LGTM!今日统计测试覆盖全面。

测试验证了:

  • Warmup 日志排除(统计口径)
  • 跨用户数据隔离
  • 模型分组统计
  • billingModelSource 配置一致性
  • 筛选器接口可用性

76-111: 无须修改 - 当前实现不存在外键约束风险。

message_request 表的 providerId 列在 Drizzle schema 中定义为 integer('provider_id').notNull(),未使用 .references() 方法。Drizzle 的 relations() API 仅定义 ORM 层级的关系(用于查询便利),不会创建数据库层级的外键约束。因此硬编码的 providerId: 0 不会触发任何外键约束错误。

tests/api/auth.unit.test.ts (3)

44-77: 测试辅助函数设计良好

createTestUsercreateTestKey 辅助函数结构清晰,包含了显式的错误处理(插入失败时抛出异常),这有助于快速定位测试数据准备阶段的问题。


79-189: 安全边界测试覆盖全面

测试套件覆盖了以下关键场景:

  • Admin Token 直接验证(无需 DB 查询)
  • 不存在的 key 返回 null
  • canLoginWebUi=falseallowReadOnlyAccess=false 时拒绝访问
  • allowReadOnlyAccess=true 时允许只读 key 访问
  • 软删除用户的 key 返回 null
  • getSession 在有/无 Cookie 时的行为

清理逻辑使用软删除模式,避免影响测试数据库的完整性约束。


191-229: Cookie 工具函数与跳转目标测试完整

测试验证了:

  • setAuthCookie / getAuthCookie / clearAuthCookie 的读写一致性
  • getLoginRedirectTarget 根据 rolecanLoginWebUi 正确返回跳转路径

覆盖了 getLoginRedirectTarget 的三个分支逻辑,测试结构清晰。

src/lib/api/action-adapter-openapi.ts (2)

69-78: allowReadOnlyAccess 选项文档清晰,安全警告到位

新增的 allowReadOnlyAccess 选项文档明确说明了其用途和安全边界:

  • 仅用于"只读且强制绑定当前会话"的端点
  • 明确警告不能用于可传入 userId/keyId 等可能导致越权的管理型接口

默认值为 false,遵循最小权限原则。


305-312: 认证流程扩展设计合理

认证逻辑的改进点:

  • Cookie 优先,Bearer Token 作为备选(适合脚本/CLI 调用场景)
  • allowReadOnlyAccess 选项正确传递给 validateKey
  • OpenAPI 安全定义同时声明了 cookieAuthbearerAuth
src/app/api/actions/[...route]/route.ts (4)

59-65: bearerAuth 安全方案注册正确

新增的 bearerAuth 安全方案使用 HTTP Bearer 认证,描述清晰说明了适用场景(脚本/CLI 调用)以及 token 与 Cookie 中 auth-token 值一致的特性。


619-646: my-usage 模块首个端点结构良好

getMyUsageMetadata 端点设计合理:

  • allowReadOnlyAccess: true 允许只读 Key 访问
  • 响应 schema 详细定义了返回字段及其描述
  • 标签分类为"概览"

作为该模块的模板,后续端点结构一致。


734-783: getMyUsageLogs 端点分页与过滤设计完整

日志查询端点支持:

  • 时间范围过滤(startDate/endDate
  • 模型、endpoint、状态码过滤
  • 分页参数(pageSize 默认 20,最大 100)

响应 schema 包含完整的日志字段定义,allowReadOnlyAccess: true 确保只读 Key 可访问自己的数据。


785-805: 筛选项端点设计简洁

getMyAvailableModelsgetMyAvailableEndpoints 两个端点:

  • 无需请求参数
  • 返回字符串数组
  • 用于日志筛选下拉框填充

设计简洁,符合只读数据获取的最佳实践。

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR implements critical bug fixes for the my-usage page statistics and adds readonly API endpoints. The implementation is solid with comprehensive test coverage.

PR Size: XL

  • Lines changed: 1437 (1333 additions + 104 deletions)
  • Files changed: 16

Note: This is a large PR. Consider these logical splits for future changes:

  1. Core Bug Fixes (my-usage.ts query fixes + warmup exclusion)
  2. API Infrastructure (Bearer auth + action-adapter changes)
  3. Readonly Endpoints (6 new my-usage API endpoints)
  4. Backward Compatibility (system-config degradation logic)
  5. Test Suite (all test files)

Issues Found

No significant issues identified. The code demonstrates good practices:

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate
  • Code clarity - Good

Highlights

Good Practices Observed:

  1. Proper SQL Migration: Replaced unsafe sql\${var}` with typed Drizzle operators (lt()`)
  2. Security-First Design: allowReadOnlyAccess properly documented with security warnings
  3. Bearer Auth Implementation: Correct regex pattern with case-insensitive matching
  4. Warmup Exclusion: Centralized EXCLUDE_WARMUP_CONDITION prevents duplication
  5. Graceful Degradation: system-config handles missing DB columns for backward compatibility
  6. Comprehensive Testing: Unit tests cover adapter logic, auth flows, and edge cases
  7. Error Handling: All async functions properly wrapped in try/catch with logging

Automated review by Claude AI

const authToken = getCookie(c, "auth-token");
const authToken =
getCookie(c, "auth-token") ?? getBearerTokenFromAuthHeader(c.req.header("authorization"));
if (!authToken) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] [LOGIC-BUG] bearerAuth 在 adapter 通过,但多数 action 仍依赖 Cookie 的 getSession(),导致 Bearer-only 调用失败

Why this is a problem: createActionRoute 现在允许从 Authorization: Bearer <token> 读取 token 并通过 validateKey() 放行,但多数 server action(包括本 PR 新增暴露的 my-usage)仍在 action 内部调用 getSession(),而 getSession() 只读取 auth-token Cookie。结果是:Bearer-only(不带 Cookie)的脚本/CLI 请求会在 adapter 通过后,在 action 内部变成 Unauthorized,最终返回 400。

Evidence:

  • src/lib/api/action-adapter-openapi.ts:305-316:
    • getCookie(c, "auth-token") ?? getBearerTokenFromAuthHeader(c.req.header("authorization"))
  • src/lib/auth.ts:122-127:
    • const keyString = await getAuthCookie();(没有从 Authorization header 取值)
  • src/actions/my-usage.ts:149-150:
    • const session = await getSession({ allowReadOnlyAccess: true }); if (!session) return { ok: false, error: "Unauthorized" };
  • OpenAPI 也声明 Bearer 适合脚本/CLI:src/app/api/actions/[...route]/route.ts:59-65

Suggested fix: 让 getSession()(或 getAuthCookie())在 Cookie 缺失时回退读取 Authorization: Bearer,并补充 Bearer-only 的 API 测试。

// src/lib/auth.ts
import { cookies, headers } from "next/headers";

function parseBearer(raw: string | null): string | undefined {
  const match = /^Bearer\s+(.+)$/i.exec(raw?.trim() ?? "");
  const token = match?.[1]?.trim();
  return token || undefined;
}

async function getAuthToken(): Promise<string | undefined> {
  const cookieStore = await cookies();
  const cookieToken = cookieStore.get("auth-token")?.value;
  if (cookieToken) return cookieToken;

  const headerStore = await headers();
  return parseBearer(headerStore.get("authorization"));
}

export async function getSession(options?: { allowReadOnlyAccess?: boolean }) {
  const token = await getAuthToken();
  if (!token) return null;
  return validateKey(token, options);
}
// tests/api/my-usage-readonly.test.ts
// 增加用例:仅设置 Authorization(不设置 Cookie)时,my-usage 端点应 200。

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR introduces a new /api/actions/my-usage/* read-only surface and expands the Actions adapter to accept Authorization: Bearer tokens. The current implementation is likely to break Bearer-only clients because most server actions still derive session state from the auth-token cookie.

PR Size: XL

  • Lines changed: 1437
  • Files changed: 16
  • Split suggestions: (1) auth/bearer session plumbing + tests, (2) my-usage stats/warmup aggregation fix, (3) test infra + coverage config.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 1 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 1 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

  • Bearer-only /api/actions/* requests can pass adapter auth but fail inside actions that call getSession() (cookie-only), contradicting the new bearerAuth scheme: src/lib/api/action-adapter-openapi.ts:306, src/lib/auth.ts:122, src/actions/my-usage.ts:149.

High Priority Issues (Should Fix)

Review Coverage

  • Logic and correctness
  • Security (OWASP Top 10)
  • Error handling
  • Type safety
  • Documentation accuracy
  • Test coverage
  • Code clarity

Automated review by Codex AI

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Identified PR #532 and applied size label size/XL (1437 lines changed across 16 files).
  • Posted an inline review comment for a [HIGH] [LOGIC-BUG]: bearerAuth is accepted in src/lib/api/action-adapter-openapi.ts:306, but most actions (incl. my-usage) still call getSession() which is cookie-only in src/lib/auth.ts:122, so Bearer-only clients will fail (src/actions/my-usage.ts:149).
  • Submitted the required PR review summary via gh pr review --comment.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 4, 2026

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/lib/auth.ts (2)

139-147: 认证 token 获取的回退机制实现正确,但存在轻微不一致

getAuthToken 正确实现了 Cookie 优先、Authorization 头部回退的认证流程,解决了之前评审中提到的"Bearer token 在 adapter 通过但在 action 中失败"的问题。

轻微不一致parseBearerToken 返回 string | undefined,而 src/lib/api/action-adapter-openapi.ts 中的 getBearerTokenFromAuthHeader 返回 string | null。建议统一返回类型以提高代码一致性。

🔎 可选:统一返回类型为 undefined
 function parseBearerToken(raw: string | null | undefined): string | undefined {
   const trimmed = raw?.trim();
   if (!trimmed) return undefined;

   const match = /^Bearer\s+(.+)$/i.exec(trimmed);
   const token = match?.[1]?.trim();
   return token || undefined;
 }

同时更新 action-adapter-openapi.ts 中的 getBearerTokenFromAuthHeader 也返回 undefined 而非 null


130-147: 可选:考虑抽取共享的 Bearer token 解析逻辑

parseBearerToken (此文件) 和 getBearerTokenFromAuthHeader (src/lib/api/action-adapter-openapi.ts) 实现了相同的功能。可以考虑将其抽取为共享工具函数,减少代码重复。

💡 建议的重构方案

src/lib/auth.ts 中导出 parseBearerToken

-function parseBearerToken(raw: string | null | undefined): string | undefined {
+export function parseBearerToken(raw: string | null | undefined): string | undefined {
   const trimmed = raw?.trim();
   if (!trimmed) return undefined;

   const match = /^Bearer\s+(.+)$/i.exec(trimmed);
   const token = match?.[1]?.trim();
   return token || undefined;
 }

然后在 src/lib/api/action-adapter-openapi.ts 中复用:

+import { parseBearerToken } from "@/lib/auth";

-function getBearerTokenFromAuthHeader(raw: string | undefined): string | null {
-  const trimmed = raw?.trim();
-  if (!trimmed) return null;
-
-  const match = /^Bearer\s+(.+)$/i.exec(trimmed);
-  const token = match?.[1]?.trim();
-  return token ? token : null;
-}
+function getBearerTokenFromAuthHeader(raw: string | undefined): string | undefined {
+  return parseBearerToken(raw);
+}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

📥 Commits

Reviewing files that changed from the base of the PR and between 1e6abad and 3776882.

📒 Files selected for processing (5)
  • src/lib/api/action-adapter-openapi.ts
  • src/lib/auth.ts
  • tests/api/auth.unit.test.ts
  • tests/api/my-usage-readonly.test.ts
  • tests/nextjs.mock.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/api/auth.unit.test.ts
  • tests/api/my-usage-readonly.test.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use 2-space indentation in all code files

Files:

  • tests/nextjs.mock.ts
  • src/lib/auth.ts
  • src/lib/api/action-adapter-openapi.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use double quotes for strings instead of single quotes
Use trailing commas in multi-line structures
Enforce maximum line length of 100 characters
Use path alias @/* to reference files from ./src/* directory

**/*.{ts,tsx,js,jsx}: Use Biome for linting and formatting with 2-space indent, double quotes, trailing commas, and 100 character max line length
Use path alias @/* to reference files in ./src/* directory

Files:

  • tests/nextjs.mock.ts
  • src/lib/auth.ts
  • src/lib/api/action-adapter-openapi.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict mode for type safety
Use readonly or const assertions for immutable data structures

Files:

  • tests/nextjs.mock.ts
  • src/lib/auth.ts
  • src/lib/api/action-adapter-openapi.ts
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.ts: Hash API keys using SHA-256 before storing in database, never store plaintext keys
Mask API keys and sensitive data in application logs
Validate required environment variables at startup with clear error messages

Files:

  • src/lib/auth.ts
  • src/lib/api/action-adapter-openapi.ts
src/**/*{guard,auth}*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use constant-time comparison for API key validation to prevent timing attacks

Files:

  • src/lib/auth.ts
src/**/*{auth,password,credential}*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Never store plaintext passwords; use bcrypt or similar hashing algorithms

Files:

  • src/lib/auth.ts
src/lib/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use connection pooling for database and Redis connections

Files:

  • src/lib/auth.ts
  • src/lib/api/action-adapter-openapi.ts
🧠 Learnings (7)
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.{tsx,json} : Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW

Applied to files:

  • tests/nextjs.mock.ts
📚 Learning: 2026-01-03T09:10:02.182Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 517
File: src/app/v1/_lib/proxy/auth-guard.ts:205-232
Timestamp: 2026-01-03T09:10:02.182Z
Learning: In `src/app/v1/_lib/proxy/auth-guard.ts`, the `extractApiKeyFromHeaders` function is intentionally a standalone simplified version for non-Guard flows. It deliberately does not check for multi-source conflicts (unlike `ProxyAuthenticator.validate`), and the code duplication is intentional to avoid coupling between Guard and non-Guard authentication flows.

Applied to files:

  • src/lib/auth.ts
  • src/lib/api/action-adapter-openapi.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*.ts : Implement guard pipeline pattern for cross-cutting concerns in request processing (auth, rate limiting, session)

Applied to files:

  • src/lib/auth.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*{guard,auth}*.ts : Use constant-time comparison for API key validation to prevent timing attacks

Applied to files:

  • src/lib/auth.ts
  • src/lib/api/action-adapter-openapi.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/actions/**/*.ts : Use Server Actions in `next-safe-action` with OpenAPI generation for admin API endpoints

Applied to files:

  • src/lib/api/action-adapter-openapi.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/actions/**/*.ts : Use Next.js API Routes and Server Actions for admin operations and REST endpoints

Applied to files:

  • src/lib/api/action-adapter-openapi.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/app/api/actions/**/*.ts : Server Actions in `src/app/api/actions/` must auto-generate OpenAPI 3.1.0 spec from Zod schemas and expose as REST endpoints

Applied to files:

  • src/lib/api/action-adapter-openapi.ts
🧬 Code graph analysis (1)
src/lib/api/action-adapter-openapi.ts (1)
src/lib/auth.ts (1)
  • validateKey (17-86)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 🌐 API Tests
  • GitHub Check: Docker Build Test
  • GitHub Check: dev-build-deploy
🔇 Additional comments (5)
tests/nextjs.mock.ts (1)

29-31: 测试基础设施补充合理

新增的 headers mock 与现有 cookies mock 结构保持一致,默认返回 null 适合作为测试基础,可在具体测试中按需覆盖以模拟 Authorization 头部场景。

src/lib/auth.ts (1)

130-137: Bearer token 解析逻辑正确

parseBearerToken 的正则表达式 /^Bearer\s+(.+)$/i 使用了单反斜杠 \s 正确匹配空白字符,提取逻辑健全。

src/lib/api/action-adapter-openapi.ts (3)

18-25: Bearer token 提取逻辑正确

getBearerTokenFromAuthHeader 的正则表达式 /^Bearer\s+(.+)$/i 使用了单反斜杠 \s 正确匹配空白字符(并非之前评审中提到的双反斜杠 \\s),实现逻辑正确。

:与 src/lib/auth.ts 中的 parseBearerToken 功能重复,已在该文件的评审中建议可选的代码整合。


69-78: 只读访问选项设计合理,文档清晰

allowReadOnlyAccess 选项的文档明确说明了使用场景和安全边界("白名单开关"、"只读且强制绑定当前会话"、"绝不能用于允许传入 userId/keyId 等可导致越权的管理型接口"),有助于防止误用导致的安全问题。


305-316: 双源 token 认证实现正确,已解决之前的评审问题

认证流程现在正确支持从 Cookie 或 Authorization 头部获取 token:

  • Line 305-306: getCookie(c, "auth-token") ?? getBearerTokenFromAuthHeader(...)
  • Line 312: validateKey(authToken, { allowReadOnlyAccess })

配合 src/lib/auth.tsgetSession() 的更新(通过 getAuthToken() 同样支持双源 token),之前评审中提到的"Bearer token 在 adapter 通过但在 action 内部失败"的问题已得到解决。

@ding113 ding113 merged commit af3fb3e into dev Jan 4, 2026
11 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 4, 2026
@github-actions github-actions bot mentioned this pull request Jan 6, 2026
4 tasks
@greptile-apps
Copy link

greptile-apps bot commented Jan 6, 2026

Greptile encountered an error while reviewing this PR. Please reach out to support@greptile.com for assistance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core area:statistics bug Something isn't working size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant