Skip to content

fix: return full self user list shape#1213

Open
Clouder0 wants to merge 4 commits into
ding113:devfrom
Clouder0:codex/fix-user-management-self-page
Open

fix: return full self user list shape#1213
Clouder0 wants to merge 4 commits into
ding113:devfrom
Clouder0:codex/fix-user-management-self-page

Conversation

@Clouder0
Copy link
Copy Markdown

@Clouder0 Clouder0 commented May 21, 2026

AI-authored draft PR

This draft PR was written by OpenAI Codex on behalf of Clouder0. It is intentionally marked as draft for maintainer review before it is made ready.

Summary

  • Return /api/v1/users:self in the dashboard-compatible UserDisplay list shape, including keys.
  • Keep the self endpoint scoped to the authenticated session user without loading the full admin user inventory.
  • Redact key secrets while preserving Date values for normal JSON serialization.
  • Add regression coverage for the REST endpoint, dashboard compatibility fallback, and targeted self-user action path.

Problem

In v0.8.2, a non-admin dashboard user can log in with a Web UI-enabled key, but opening the users page falls back from /api/v1/users to /api/v1/users:self. The self endpoint returned a user detail shape without keys, while the dashboard users page expects user.keys.length, causing the global "Something went wrong!" error page.

Solution

Add getCurrentUserDisplay(), a targeted action that loads only the current session user and builds the same UserDisplay shape used by the users list. listCurrentUser now calls that action instead of the detail action or the full list action, so the response includes the keys array expected by the dashboard without turning an admin self request into an all-users scan.

The response still goes through redactUserKeys, and that redaction now preserves Date instances so Hono/JSON serialization emits ISO strings instead of converting nested dates to {}.

Changes

  • src/actions/users.ts
    • Extract shared buildUserDisplays() logic from getUsers().
    • Add getCurrentUserDisplay() for the single-current-user display shape.
  • src/app/api/v1/resources/users/handlers.ts
    • Use getCurrentUserDisplay() in /api/v1/users:self.
    • Preserve Date values while recursively removing fullKey.
  • tests/api/v1/users/users.test.ts
    • Assert self responses include keys, redact fullKey, preserve date serialization, and do not call the full users action.
  • tests/unit/users-action-get-users-compat.test.ts
    • Assert getCurrentUserDisplay() only loads the current user id.
  • tests/unit/api/v1/api-client-actions.test.ts
    • Assert dashboard compatibility fallback consumes the self endpoint shape as a one-item list.

Review follow-up

This PR includes a follow-up commit for reviewer feedback:

  • Avoids getUsers() in the self endpoint, addressing the N-to-1 full inventory load concern.
  • Preserves Date objects in redactUserKeys, addressing date corruption during recursive redaction.
  • Keeps pageInfo.limit tied to the actual returned item count.

One automated review note said the previous raw-array action result would fail because it lacked ok. That is not how this code path behaves: callAction() wraps non-ActionResult returns as { ok: true, data }. The final implementation no longer relies on that path for /api/v1/users:self, but the original concern about result.ok was not valid for this bridge.

Verification

  • bunx biome check src/actions/users.ts src/app/api/v1/resources/users/handlers.ts tests/api/v1/users/users.test.ts tests/unit/users-action-get-users-compat.test.ts tests/unit/api/v1/api-client-actions.test.ts
  • bunx vitest run tests/api/v1/users/users.test.ts tests/unit/users-action-get-users-compat.test.ts tests/unit/api/v1/api-client-actions.test.ts
  • git diff --check HEAD~1 HEAD

Known local blockers outside this change

  • bun run lint currently fails on pre-existing formatting drift in tests/integration/usage-ledger.test.ts.
  • bun run typecheck and bun run build currently fail because this checkout has package.json dependencies not present in bun.lock/node_modules, including maplibre-gl and Langfuse/OpenTelemetry packages.
  • bun run test:v1 currently fails because openapi-typescript is declared in package.json but missing from bun.lock/node_modules.

Greptile Summary

This PR fixes a dashboard crash for non-admin users who land on the users page: the /api/v1/users:self endpoint previously returned a user-detail shape without a keys array, but the dashboard unconditionally reads user.keys.length, causing the error page. The fix introduces getCurrentUserDisplay() which returns the same UserDisplay list-item shape (including keys) that getUsers() produces, without triggering a full admin user-list scan.

  • buildUserDisplays() is extracted from getUsers() as a shared helper; getCurrentUserDisplay() calls it with a single-element user array, and listCurrentUser now invokes this action instead of getUserById.
  • redactUserKeys gains a Date instance guard so that date fields (e.g. createdAt, expiresAt) are not converted to {} during recursive redaction — Hono's JSON serializer then correctly emits ISO strings.
  • Tests cover the self-endpoint response shape, fullKey redaction, date serialization, and confirm neither getUserById nor getUsers is called from the self path.

Confidence Score: 5/5

Safe to merge — the fix is well-scoped and the new action correctly limits data access to the session user.

The three core changes (extracted buildUserDisplays, new getCurrentUserDisplay action, and the Date guard in redactUserKeys) are all logically correct. The self endpoint no longer leaks the full user inventory for admin callers, date fields round-trip cleanly through redaction, and the keys array the dashboard requires is now present. Test coverage is targeted and covers the regression paths described in the PR.

No files require special attention.

Important Files Changed

Filename Overview
src/actions/users.ts Extracts buildUserDisplays() from getUsers() and adds getCurrentUserDisplay() — logic is correct and scoped to the session user.
src/app/api/v1/resources/users/handlers.ts Switches listCurrentUser from getUserById to getCurrentUserDisplay, fixes Date handling in redactUserKeys, and adds a structural id-equality guard that is always false at runtime (benign).
tests/api/v1/users/users.test.ts Adds targeted regression coverage for keys presence, Date serialization, and fullKey redaction in the self endpoint.
tests/unit/users-action-get-users-compat.test.ts New test verifies that getCurrentUserDisplay loads only the session user via findUserById and not the full user list.
tests/unit/api/v1/api-client-actions.test.ts Updates the compatibility-client fallback test to use the new list-item shape with items/pageInfo and verifies keys is passed through.

Sequence Diagram

sequenceDiagram
    participant Dashboard
    participant APIClient
    participant Handler as listCurrentUser handler
    participant Action as getCurrentUserDisplay()
    participant DB

    Dashboard->>APIClient: getUsers()
    APIClient->>+Handler: GET /api/v1/users (403/non-admin)
    Handler-->>-APIClient: 403 Forbidden

    APIClient->>+Handler: GET /api/v1/users:self (fallback)
    Handler->>+Action: callAction(getCurrentUserDisplay)
    Action->>DB: getSession() → session.user.id
    Action->>DB: findUserById(session.user.id)
    DB-->>Action: User record
    Action->>DB: findKeyListBatch([userId])
    Action->>DB: findKeyUsageTodayBatch([userId])
    DB-->>Action: keys + usage
    Action->>DB: findKeysStatisticsBatchFromKeys(keysMap)
    DB-->>Action: statistics
    Action-->>-Handler: ActionResult with keys[]
    Handler->>Handler: redactUserKeys (Date instances preserved)
    Handler-->>-APIClient: "{ items: [UserDisplay], pageInfo }"
    APIClient-->>Dashboard: UserDisplay[] with keys array
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/app/api/v1/resources/users/handlers.ts:83-91
The identity check here is always false when `result.ok` is true. `getCurrentUserDisplay` resolves `session.user.id` from the same auth session that `callAction` injects via `runWithAuthSession`, then builds the display from `findUserById(session.user.id)` — so `result.data.id` is definitionally equal to `currentUserId`. Keeping the branch intact silently implies there is a code path where the action can return a different user's record, which could mislead future maintainers.

```suggestion
  const items = [redactUserKeys(result.data)];
```

Reviews (4): Last reviewed commit: "Merge branch 'dev' into codex/fix-user-m..." | Re-trigger Greptile

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

将用户显示构建抽象为 buildUserDisplays;getUsers 与 getCurrentUserDisplay 复用该构建器;listCurrentUser 改为通过 getCurrentUserDisplay 返回 items(含 keys),并在 id 不匹配时返回 404;redactUserKeys 增加对 Date 实例的原样保留;相关集成与单元测试已更新。

变更说明

用户展示与 self 端点统一构建

Layer / File(s) Summary
构建器:buildUserDisplays 与调用点
src/actions/users.ts
新增 buildUserDisplays,集中批量查询 keys/usage/statistics 并组装 UserDisplaygetUsersgetCurrentUserDisplay 改为复用该函数并统一错误处理。
API Handler:listCurrentUser 使用 getCurrentUserDisplay
src/app/api/v1/resources/users/handlers.ts
listCurrentUser 改为调用 actions.getCurrentUserDisplay,校验返回 id 与 session 的 currentUserId 匹配,不匹配返回 404(resource.not_found);items 通过 redactUserKeys 返回,pageInfo.limit 改为根据 items.length 计算。
脱敏工具:redactUserKeys 支持 Date
src/app/api/v1/resources/users/handlers.ts
redactUserKeys 新增对 Date 实例的特殊分支:若 value instanceof Date 则直接返回原值,避免递归处理 Date 对象。
集成测试:self endpoint 使用 getCurrentUserDisplay
tests/api/v1/users/users.test.ts
新增并使用 getCurrentUserDisplayMock,更新用户自查与管理员自查断言,验证用户自查返回 keys(仅 maskedKey)、不暴露 fullKey/sk-user-secret;管理员自查 keys 仅保留 name,并断言不暴露库存用户。
单元测试:回退路径与兼容性测试
tests/unit/api/v1/api-client-actions.test.ts, tests/unit/users-action-get-users-compat.test.ts
调整 users.getUsers() 回退测试以匹配包含 keys 的返回形状;新增 getCurrentUserDisplay 兼容性测试,验证仅加载当前 session 用户、keys 字段、以及 expiresAt/createdAt 被转换为 Date 实例,并校验批量调用次数。

代码审查工作量估计

🎯 4 (Complex) | ⏱️ ~45 分钟

可能相关的PR

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题清晰准确地总结了主要变更:将 /api/v1/users:self 端点返回从单用户详情形状改为完整的 UserDisplay 列表形状,直接对应 PR 的核心目标。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR描述详细说明了问题、解决方案、具体改动、验证步骤和已知限制,与changeset完全相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

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

@github-actions github-actions Bot added the bug Something isn't working label May 21, 2026
Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/api/v1/resources/users/handlers.ts (1)

81-99: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

getUsers 拉全量列表再筛选当前用户的实现方式存在性能与数据暴露风险

listCurrentUser 现在调用 actions.getUsers([]) 拉取整个用户列表,然后在内存中 find 当前用户。这带来两个问题:

  1. 性能/可扩展性:管理员会话命中该端点时(例如本 PR 测试中的 "admin self" 用例),会从数据库读取并序列化全量用户(包括所有 keys、tags、limits 等),仅为返回单个用户。用户数量增长后这是一次明显放大的 N→1 查询。
  2. 数据最小化:即使外层会用 find 过滤,action 层仍会把所有用户的明细(包括包含 fullKey 的 keys)加载到进程内存并序列化为 result.dataredactUserKeys 只在最终响应层裁剪 fullKey,对中间过程的内存暴露和潜在日志/错误路径泄露无防护。

更合适的做法是新增/复用一个按 id 精确查询且返回 UserDisplay(带 keys)形态的 action(例如让 getUserById 也返回 keys,或新增 getCurrentUserDisplay()),既能保留 dashboard 期望的形状,又避免全量扫描。

🤖 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 `@src/app/api/v1/resources/users/handlers.ts` around lines 81 - 99, The handler
currently calls callAction(c, actions.getUsers, ...) and then finds currentUser
in result.data which causes full-table reads and exposes sensitive key material
in memory; change the handler to call a new or existing targeted action that
fetches a single user by id and returns the UserDisplay shape (e.g. use or add
actions.getUserById or actions.getCurrentUserDisplay) so only the requested user
(with keys) is loaded and serialized, and then apply redactUserKeys(currentUser)
before returning; update the handler to remove call to actions.getUsers and rely
on the single-user action to address performance and data-minimization concerns.
🧹 Nitpick comments (2)
src/app/api/v1/resources/users/handlers.ts (1)

92-99: 💤 Low value

pageInfo.limit: 1 语义存在误导

该 endpoint 始终返回单个用户,limit 字段实际并不代表请求的分页大小,硬编码为 1 容易让消费方误认为是受限分页结果。建议改用更明确的语义(例如沿用 items.length,或与 listUserslimit 默认值保持一致),或在 OpenAPI 文档中注明该字段在 self 端点下的固定语义。

🤖 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 `@src/app/api/v1/resources/users/handlers.ts` around lines 92 - 99, 当前实现将
pageInfo.limit 硬编码为 1,会误导消费方以为这是分页请求的 limit;在返回 jsonResponse 时改为使用实际返回条数(例如
items.length from redactUserKeys(currentUser))或与 listUsers 的默认 limit
保持一致,以明确语义;在 handlers.ts 的该返回块(jsonResponse / pageInfo.limit)将 limit 设置为
items.length 或其它明确值,并在必要时补充注释说明此 endpoint 始终只返回单个用户。
tests/unit/api/v1/api-client-actions.test.ts (1)

136-156: 💤 Low value

建议补充对 keys 形态在客户端层的回退断言

当前断言用 toMatchObject 校验 keys 字段透传,但未验证客户端 users.getUsers() 在回退到 /api/v1/users:self 时对 pageInfohasMore: false / nextCursor: null)的处理是否符合预期(例如不会把单元素结果误标为「还有下一页」)。可考虑显式断言返回数组长度为 1,并/或对客户端内部对 pageInfo 的消化路径补一条用例。

🤖 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 `@tests/unit/api/v1/api-client-actions.test.ts` around lines 136 - 156, The
test needs an explicit assertion that users.getUsers correctly consumes pageInfo
when falling back to "/api/v1/users:self" so it doesn't incorrectly report more
pages; after awaiting users.getUsers(), assert the resolved array has length 1
(e.g., expect(result).toHaveLength(1)) and/or assert the single item matches the
expected object shape, ensuring pageInfo (hasMore/nextCursor) is not causing
extra items; reference the users.getUsers call, the getMock responses (first
ApiError, then the items + pageInfo), and the "/api/v1/users:self" fallback to
locate where to add these extra assertions.
🤖 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.

Outside diff comments:
In `@src/app/api/v1/resources/users/handlers.ts`:
- Around line 81-99: The handler currently calls callAction(c, actions.getUsers,
...) and then finds currentUser in result.data which causes full-table reads and
exposes sensitive key material in memory; change the handler to call a new or
existing targeted action that fetches a single user by id and returns the
UserDisplay shape (e.g. use or add actions.getUserById or
actions.getCurrentUserDisplay) so only the requested user (with keys) is loaded
and serialized, and then apply redactUserKeys(currentUser) before returning;
update the handler to remove call to actions.getUsers and rely on the
single-user action to address performance and data-minimization concerns.

---

Nitpick comments:
In `@src/app/api/v1/resources/users/handlers.ts`:
- Around line 92-99: 当前实现将 pageInfo.limit 硬编码为 1,会误导消费方以为这是分页请求的 limit;在返回
jsonResponse 时改为使用实际返回条数(例如 items.length from redactUserKeys(currentUser))或与
listUsers 的默认 limit 保持一致,以明确语义;在 handlers.ts 的该返回块(jsonResponse /
pageInfo.limit)将 limit 设置为 items.length 或其它明确值,并在必要时补充注释说明此 endpoint 始终只返回单个用户。

In `@tests/unit/api/v1/api-client-actions.test.ts`:
- Around line 136-156: The test needs an explicit assertion that users.getUsers
correctly consumes pageInfo when falling back to "/api/v1/users:self" so it
doesn't incorrectly report more pages; after awaiting users.getUsers(), assert
the resolved array has length 1 (e.g., expect(result).toHaveLength(1)) and/or
assert the single item matches the expected object shape, ensuring pageInfo
(hasMore/nextCursor) is not causing extra items; reference the users.getUsers
call, the getMock responses (first ApiError, then the items + pageInfo), and the
"/api/v1/users:self" fallback to locate where to add these extra assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1e08d1fc-eab8-44cf-9c45-7bd6ae871790

📥 Commits

Reviewing files that changed from the base of the PR and between e0d0978 and 196e658.

📒 Files selected for processing (3)
  • src/app/api/v1/resources/users/handlers.ts
  • tests/api/v1/users/users.test.ts
  • tests/unit/api/v1/api-client-actions.test.ts

Copy link
Copy Markdown
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

This pull request updates the listCurrentUser handler to use the getUsers action instead of getUserById, filtering the results in-memory to find the current user. Reviewers identified several critical issues: the current implementation incorrectly checks for an ok property on a raw array result, which will cause the request to fail; fetching the entire user list for admin users introduces significant performance and memory risks; and the call to redactUserKeys is both redundant and likely to corrupt Date fields within the user object.

Comment on lines 81 to 82
const result = await callAction(c, actions.getUsers, [] as never[], c.get("auth"));
if (!result.ok) return actionError(c, result);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

The getUsers action (from @/actions/users) returns a raw array of UserDisplay[], unlike most other actions in this repository which return an ActionResult object (e.g., { ok: true, data: ... }).

Because getUsers returns an array, the check if (!result.ok) will always evaluate to true (since an array does not have an ok property, making it undefined), causing the request to fail and return an error response even when the action succeeds. You should either update the getUsers action to return an ActionResult or handle the raw array return type correctly in this handler.

[currentUserId] as never[],
c.get("auth")
);
const result = await callAction(c, actions.getUsers, [] as never[], c.get("auth"));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

For admin users, calling getUsers without parameters triggers loadAllUsersForAdmin(), which fetches the entire user database along with all associated keys, usage statistics, and rate limit data. This data is then filtered in-memory using .find().

This approach will cause significant performance degradation and potential memory exhaustion (OOM) as the user base grows. Since you only need the UserDisplay shape for the current user, it is recommended to implement a targeted action (e.g., getUserDisplayById) or add an ID filter to the getUsers action to avoid loading the entire inventory.

}
return jsonResponse({
items: [redactUserKeys(result.data)],
items: [redactUserKeys(currentUser)],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The redactUserKeys function (defined at line 353) contains a bug where it incorrectly processes Date objects. Because typeof date === 'object' is true and Object.entries(date) returns an empty array, any Date field in the currentUser object (such as createdAt, expiresAt, or costResetAt) will be transformed into an empty object {} in the final JSON response.

Furthermore, the UserDisplay objects returned by getUsers already have their keys masked and do not contain the fullKey field that redactUserKeys is designed to strip. You should remove this call to avoid data loss and unnecessary processing.

Suggested change
items: [redactUserKeys(currentUser)],
items: [currentUser],

@Clouder0
Copy link
Copy Markdown
Author

Follow-up pushed in 8b421362 after reviewing the automated comments.

Valid feedback addressed:

  • /api/v1/users:self no longer calls getUsers() and no longer loads the full user inventory for admin sessions. It now uses getCurrentUserDisplay(), which fetches only the authenticated session user and builds the same UserDisplay shape.
  • redactUserKeys() now preserves Date instances before recursing, so nested date fields serialize normally instead of becoming {}.
  • Added action-level coverage that asserts only the current user id is loaded, plus endpoint coverage for keys, secret redaction, and date serialization.
  • Added an explicit client fallback assertion that the self endpoint is consumed as a one-item list.

One note about result.ok on raw array returns appears to be a false positive: callAction() wraps non-ActionResult action returns into { ok: true, data }. The current self endpoint no longer uses that raw-array path, but the bridge behavior is already covered by the existing implementation.

Fresh local verification after the follow-up:

  • bunx biome check src/actions/users.ts src/app/api/v1/resources/users/handlers.ts tests/api/v1/users/users.test.ts tests/unit/users-action-get-users-compat.test.ts tests/unit/api/v1/api-client-actions.test.ts
  • bunx vitest run tests/api/v1/users/users.test.ts tests/unit/users-action-get-users-compat.test.ts tests/unit/api/v1/api-client-actions.test.ts -> 3 files, 34 tests passed
  • git diff --check HEAD~1 HEAD

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
src/actions/users.ts (1)

745-853: ⚖️ Poor tradeoff

可选重构:getUsersBatch 可复用 buildUserDisplays

getUsersBatch 中构建 UserDisplay 的逻辑(lines 745-853)与新增的 buildUserDisplays 高度重复。后续可考虑让 getUsersBatch 也复用 buildUserDisplays,减少维护成本。

此为建议性重构,不影响本 PR 合入。

🤖 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 `@src/actions/users.ts` around lines 745 - 853, The user-display construction
in getUsersBatch duplicates the mapping logic used in buildUserDisplays;
refactor getUsersBatch to call the existing buildUserDisplays function (or
extract the shared mapping into a helper used by both) so keys/usage/statistics
handling (keysMap, usageMap, statisticsMap), maskKey usage, canExposeFullKey
logic, and key shaping are centralized; update getUsersBatch to supply the same
inputs buildUserDisplays expects and remove the inline mapping block that
creates UserDisplay objects to avoid duplicate code and maintenance gaps.
🤖 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.

Nitpick comments:
In `@src/actions/users.ts`:
- Around line 745-853: The user-display construction in getUsersBatch duplicates
the mapping logic used in buildUserDisplays; refactor getUsersBatch to call the
existing buildUserDisplays function (or extract the shared mapping into a helper
used by both) so keys/usage/statistics handling (keysMap, usageMap,
statisticsMap), maskKey usage, canExposeFullKey logic, and key shaping are
centralized; update getUsersBatch to supply the same inputs buildUserDisplays
expects and remove the inline mapping block that creates UserDisplay objects to
avoid duplicate code and maintenance gaps.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 762fff72-6f87-47b7-9b48-9da6293e10a2

📥 Commits

Reviewing files that changed from the base of the PR and between 196e658 and 8b42136.

📒 Files selected for processing (5)
  • src/actions/users.ts
  • src/app/api/v1/resources/users/handlers.ts
  • tests/api/v1/users/users.test.ts
  • tests/unit/api/v1/api-client-actions.test.ts
  • tests/unit/users-action-get-users-compat.test.ts

@Clouder0 Clouder0 marked this pull request as ready for review May 21, 2026 14:54
Copilot AI review requested due to automatic review settings May 21, 2026 14:54
@github-actions github-actions Bot added the size/M Medium PR (< 500 lines) label May 21, 2026
Copy link
Copy Markdown
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

No significant issues identified in this PR. The changes correctly address the self-user dashboard compatibility problem by introducing a targeted getCurrentUserDisplay() action that returns the full UserDisplay shape (including keys) without triggering a full user inventory scan. The redactUserKeys fix for Date preservation is clean, and the test coverage validates the endpoint shape, redaction, date serialization, and the targeted single-user query path.

PR Size: M

  • Lines changed: 434 (291 additions, 143 deletions)
  • Files changed: 5

Review Coverage

  • Logic and correctness - Clean. The extracted buildUserDisplays() correctly shares the display-shape construction between getUsers() and getCurrentUserDisplay(). The handler's defensive result.data.id \!== currentUserId check is appropriate.
  • Security (OWASP Top 10) - Clean. fullKey redaction is preserved; no access control bypass; self endpoint remains scoped to the authenticated session user.
  • Error handling - Clean. Errors in getCurrentUserDisplay are logged and wrapped in ActionResult. The buildUserDisplays per-user catch block logs and degrades gracefully (empty keys array).
  • Type safety - Clean. buildUserDisplays accepts the minimal UserActionSession shape; no any usage introduced.
  • Documentation accuracy - Clean. The JSDoc on getCurrentUserDisplay accurately describes the intent (non-admin shape without full inventory scan).
  • Test coverage - Adequate. Integration tests verify the self endpoint response shape, redaction, and date serialization. Unit tests verify the targeted single-user query path and that the dashboard client fallback consumes the correct response shape.
  • Code clarity - Good. Moving canUserManageKey outside the keys.map() callback is a valid simplification.

Automated review by Claude AI

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a dashboard compatibility regression by ensuring the read-tier /api/v1/users:self endpoint returns the same UserDisplay list-item shape as the main users list (notably including the keys array), without requiring an admin-only full user inventory load.

Changes:

  • Added a targeted getCurrentUserDisplay() action that loads only the authenticated session user and builds the standard UserDisplay shape (including keys).
  • Updated /api/v1/users:self to use getCurrentUserDisplay() and adjusted key redaction to preserve Date instances so API JSON date serialization works correctly.
  • Added/updated regression tests for the self endpoint shape, dashboard fallback behavior, and ensuring the self path does not call the full users list action.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/actions/users.ts Extracts shared display-shaping logic and adds getCurrentUserDisplay() to avoid admin inventory scans while returning dashboard-compatible shapes.
src/app/api/v1/resources/users/handlers.ts Switches /users:self to the new action and fixes recursive redaction to preserve Date objects for correct ISO serialization.
tests/api/v1/users/users.test.ts Adds coverage that /users:self includes keys, redacts fullKey, preserves date serialization, and avoids calling full-list/detail actions.
tests/unit/users-action-get-users-compat.test.ts Adds a unit test ensuring getCurrentUserDisplay() only loads the current user ID and uses batch key/usage/stat queries for that single user.
tests/unit/api/v1/api-client-actions.test.ts Updates the client fallback test to consume the self endpoint’s { items, pageInfo } response shape.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/actions/users.ts Outdated
Comment on lines +524 to +530
if (!displayUser) {
return {
ok: false,
error: tError("USER_NOT_FOUND"),
errorCode: ERROR_CODES.NOT_FOUND,
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Dead-code guard after buildUserDisplays

buildUserDisplays is guaranteed to return exactly one element when called with a single-element array: the per-user try/catch inside the function always produces a value (either the full shape or a minimal fallback with keys: []), so displayUser can never be undefined on this path. The only way buildUserDisplays returns an empty array is when users.length === 0, but we've just confirmed user is non-null before calling it. If buildUserDisplays throws, the outer try/catch already handles it. The guard is effectively unreachable and could mislead future readers into thinking there's a genuine null-result code path here.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/actions/users.ts
Line: 524-530

Comment:
**Dead-code guard after `buildUserDisplays`**

`buildUserDisplays` is guaranteed to return exactly one element when called with a single-element array: the per-user `try/catch` inside the function always produces a value (either the full shape or a minimal fallback with `keys: []`), so `displayUser` can never be `undefined` on this path. The only way `buildUserDisplays` returns an empty array is when `users.length === 0`, but we've just confirmed `user` is non-null before calling it. If `buildUserDisplays` throws, the outer `try/catch` already handles it. The guard is effectively unreachable and could mislead future readers into thinking there's a genuine null-result code path here.

How can I resolve this? If you propose a fix, please make it concise.

@Clouder0 Clouder0 marked this pull request as draft May 21, 2026 15:07
@Clouder0 Clouder0 marked this pull request as ready for review May 21, 2026 20:52
Copy link
Copy Markdown
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

No significant issues identified in this PR. The fix is well-scoped, correctly extracts shared display-building logic into buildUserDisplays(), adds a targeted getCurrentUserDisplay() action that avoids full inventory scans, and properly preserves Date serialization in redactUserKeys. Tests cover the critical happy path and regression scenarios.

PR Size: M

  • Lines changed: 428 (284 additions, 144 deletions)
  • Files changed: 5

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

Automated review by Claude AI

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

Labels

bug Something isn't working size/M Medium PR (< 500 lines)

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants