Skip to content

feat(clues): add scoped timeline API - #123

Merged
cg8-5712 merged 3 commits into
mainfrom
issue-13-14-15-clue-api
Jul 25, 2026
Merged

cg8-5712 merged 3 commits into
mainfrom
issue-13-14-15-clue-api

Conversation

@cg8-5712

@cg8-5712 cg8-5712 commented Jul 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • 新功能
    • 新增案件线索时间轴查询接口 GET /api/cases/{case_id}/clues,支持分页(page/page_size)、状态筛选、排序(sort/ order)。
    • 结果按当前认证用户的角色与“是否本人提交”进行可见性裁剪,分页返回 items/page/page_size/total。
    • 无案件成员的情况下返回 404。
  • 错误修复
    • 非法查询参数触发 400,并返回结构化 validation_error(如分页/枚举白名单不通过)。
  • 文档
    • 更新 OpenAPI/接口文档契约,补充 ClueTimelinePage 等响应结构。
  • 测试
    • 新增并增强接口契约、鉴权、参数校验与角色可见性/分页行为的自动化测试。

Copilot AI review requested due to automatic review settings July 25, 2026 16:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

新增 GET /api/cases/{case_id}/clues 线索时间轴接口,支持分页、状态过滤和排序,并根据案件角色及提交者身份裁剪可见线索;同时统一查询校验错误响应并补充集成测试与 OpenAPI 契约测试。

Changes

线索时间轴查询

Layer / File(s) Summary
时间轴契约与数据模型
docs/API.md, docs/openapi.yaml, src/models.rs
新增查询参数、分页响应模型、OpenAPI 操作及角色可见性和错误响应定义。
路由入口与校验错误
src/routes/cases.rs, src/routes/mod.rs, tests/api/authentication_guards.rs
注册 GET 路由,调用线索查询服务,并统一返回查询或请求体校验错误。
查询服务与可见性裁剪
src/services/case_service.rs
实现参数验证、状态过滤、排序、分页、归属批量加载及按角色裁剪线索响应,并复用详情查询中的可见性逻辑。
接口与审计行为验证
tests/api/case_clues_get.rs, tests/api/openapi_intake_contract.rs, tests/api/case_clues_post.rs, tests/api/clue_review_patch.rs, tests/api/mod.rs
验证时间轴查询、鉴权、OpenAPI 契约,以及线索提交和评审状态转换的审计记录。

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ListClues
  participant CaseService
  participant Database
  Client->>ListClues: 请求案件线索时间轴
  ListClues->>CaseService: 传递认证信息和查询参数
  CaseService->>Database: 加载案件角色与线索
  CaseService->>Database: 批量加载线索归属
  CaseService-->>ListClues: 返回分页线索响应
  ListClues-->>Client: 返回 200 JSON
Loading

Possibly related PRs

  • AG-Angui/Angui#108:调整案件角色与线索可见性判定代码路径,与本次时间轴接口的角色裁剪逻辑直接相关。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.83% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题简洁且准确概括了本次新增受角色裁剪的线索时间轴 API。
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-13-14-15-clue-api

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

@github-actions

Copy link
Copy Markdown

Preview ready

The preview for this build is available at https://pr-123-f4060549e85d.angui.cg8.site.

Temporary demo accounts (all use the password edffd3d88ed12dd4e4e381adf14d1bfb795d0307e816e14a):

Role Email
Family family@demo.invalid
Commander commander@demo.invalid
Volunteer volunteer@demo.invalid
Learner learner@demo.invalid
Administrator admin@demo.invalid

These accounts and all preview data belong only to this deployment. The next preview deployment resets the SQLite database and invalidates these credentials.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (5)
tests/api/case_clues_get.rs (1)

71-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

排序行为未被断言。

请求带了 sort=created_at&order=asc,但只校验了条数与分页字段,没有校验返回顺序;sort=occurred_at(含二级 created_at/id 兜底)这条分支完全没有覆盖。建议断言首末项顺序,并补一个 sort=occurred_at&order=desc 的用例;顺带对成功响应断言 StatusCode::OK,避免错误响应体被静默反序列化成 Value 后仅在字段断言处才失败。

如需,我可以补齐排序断言与 occurred_at 用例。

As per path instructions, "Check that changed behavior has meaningful positive, negative, and boundary-case coverage."

🤖 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/api/case_clues_get.rs` around lines 71 - 85, 增强 case clues 分页测试:在现有
sort=created_at&order=asc 请求中断言响应状态为 StatusCode::OK,并校验首末返回项的排序顺序;新增
sort=occurred_at&order=desc 的成功响应用例,覆盖其 occurred_at 及 created_at/id
兜底排序,并同样断言状态和结果顺序。

Source: Path instructions

src/services/case_service.rs (1)

290-309: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

分页在内存中完成,会全量加载案件线索。

clue_query.all(db) 未加 LIMIT/OFFSET,每次请求都会拉取该案件的全部线索及其归属记录,再在内存里裁剪可见性并分页。由于 total 需要反映可见条数,当前实现语义正确,但线索量增长后内存与 IO 开销会线性上升。可考虑将可见性条件下推到 SQL(例如 status = 'confirmed' OR submitted_by_user_id = :user,Commander 不加条件),用 paginate / count 取代内存切片。

如需,我可以给出下推可见性条件后的查询实现草案。

🤖 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/services/case_service.rs` around lines 290 - 309,
将案件线索的可见性筛选和分页下推到数据库查询:在 clue_query 中复用 status、submitted_by_user_id 及 Commander
角色对应的条件,使用查询层的 count 和 paginate/limit/offset 获取 total 与当前页,避免 clue_query.all(db)
加载全部线索及归属记录。保留现有响应映射和可见性语义,确保 total 仍仅统计可见线索。
tests/api/openapi_intake_contract.rs (1)

146-152: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

契约断言依赖 YAML 中 GET 必须写在 POST 之前。

纯文本切片让测试与文档书写顺序耦合,单纯调整 openapi.yaml 里操作顺序就会红。若愿意引入 serde_yaml(或按缩进定位 get: 块而不要求它在 post: 之前),断言会稳健得多。当前写法与本文件既有风格一致,属可选改进。

🤖 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/api/openapi_intake_contract.rs` around lines 146 - 152, 改进
openapi_intake_contract.rs 中围绕 OPENAPI 的 GET/POST 提取逻辑,避免通过 split_once 要求 get:
必须出现在 post: 之前。可改用 serde_yaml 解析操作,或按缩进独立定位 get: 块,使断言仅验证对应路径的 GET
契约而不依赖文档中的操作顺序。
tests/api/case_clues_post.rs (1)

32-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

body["id"].as_str() 传 Option 会让缺失 id 退化为 IS NULL 查询。

显式解包更能定位失败原因。

♻️ 建议改动
+    let clue_id = body["id"].as_str().expect("clue id should be text");
     let audit = angui::entities::audit_events::Entity::find()
-        .filter(angui::entities::audit_events::Column::EntityId.eq(body["id"].as_str()))
+        .filter(angui::entities::audit_events::Column::EntityId.eq(clue_id))
         .one(&context.database)
🤖 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/api/case_clues_post.rs` around lines 32 - 37, 在审计查询中先对
body["id"].as_str() 显式解包并在缺失时立即失败,再将实际字符串传给
audit_events::Column::EntityId.eq;不要把 Option 直接传入查询,保留现有的审计记录查找与断言流程。
tests/api/clue_review_patch.rs (1)

81-139: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

审计断言再精确一点
当前只检查 metadata_json 是否包含状态字符串,无法区分每条审计的 from / to;建议解析成 JSON 后,逐条断言两次状态变更的组合。

🤖 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/api/clue_review_patch.rs` around lines 81 - 139, Update the audit
assertions in
patch_clue_review_rejects_volunteers_and_records_each_review_transition to parse
each audit’s metadata_json as JSON and assert the exact from/to transition for
both records, rather than only checking that each status string appears.
Preserve the existing audit count and verify the two expected transition
combinations individually.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/services/case_service.rs`:
- Around line 290-309: 将案件线索的可见性筛选和分页下推到数据库查询:在 clue_query 中复用
status、submitted_by_user_id 及 Commander 角色对应的条件,使用查询层的 count 和
paginate/limit/offset 获取 total 与当前页,避免 clue_query.all(db)
加载全部线索及归属记录。保留现有响应映射和可见性语义,确保 total 仍仅统计可见线索。

In `@tests/api/case_clues_get.rs`:
- Around line 71-85: 增强 case clues 分页测试:在现有 sort=created_at&order=asc 请求中断言响应状态为
StatusCode::OK,并校验首末返回项的排序顺序;新增 sort=occurred_at&order=desc 的成功响应用例,覆盖其
occurred_at 及 created_at/id 兜底排序,并同样断言状态和结果顺序。

In `@tests/api/case_clues_post.rs`:
- Around line 32-37: 在审计查询中先对 body["id"].as_str() 显式解包并在缺失时立即失败,再将实际字符串传给
audit_events::Column::EntityId.eq;不要把 Option 直接传入查询,保留现有的审计记录查找与断言流程。

In `@tests/api/clue_review_patch.rs`:
- Around line 81-139: Update the audit assertions in
patch_clue_review_rejects_volunteers_and_records_each_review_transition to parse
each audit’s metadata_json as JSON and assert the exact from/to transition for
both records, rather than only checking that each status string appears.
Preserve the existing audit count and verify the two expected transition
combinations individually.

In `@tests/api/openapi_intake_contract.rs`:
- Around line 146-152: 改进 openapi_intake_contract.rs 中围绕 OPENAPI 的 GET/POST
提取逻辑,避免通过 split_once 要求 get: 必须出现在 post: 之前。可改用 serde_yaml 解析操作,或按缩进独立定位 get:
块,使断言仅验证对应路径的 GET 契约而不依赖文档中的操作顺序。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fea64306-8fd1-4c9d-a51f-f933adc0871f

📥 Commits

Reviewing files that changed from the base of the PR and between 1260fae and f406054.

📒 Files selected for processing (12)
  • docs/API.md
  • docs/openapi.yaml
  • src/models.rs
  • src/routes/cases.rs
  • src/routes/mod.rs
  • src/services/case_service.rs
  • tests/api/authentication_guards.rs
  • tests/api/case_clues_get.rs
  • tests/api/case_clues_post.rs
  • tests/api/clue_review_patch.rs
  • tests/api/mod.rs
  • tests/api/openapi_intake_contract.rs

@cg8-5712
cg8-5712 force-pushed the issue-13-14-15-clue-api branch from f406054 to cd87fe0 Compare July 25, 2026 16:43

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
tests/api/case_clues_post.rs (1)

32-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

将审计事件断言限定为创建操作并解析结构化元数据。

当前查询只按 entity_id 取任意记录,且 contains 可能匹配错误事件或错误字段,导致审计状态/隐私回归仍然通过。请筛选预期的创建审计操作,并反序列化 metadata_json 后断言状态字段及不存在内容字段。

As per path instructions,tests/** 要求变更行为具有有意义的覆盖。

🤖 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/api/case_clues_post.rs` around lines 32 - 48, Update the audit lookup
in the case clue submission test to filter by both entity ID and the expected
create operation, rather than selecting any audit event. Deserialize
audit.metadata_json into its structured representation, then assert the status
field is pending_review and the content field is absent instead of using string
contains checks.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/api/case_clues_post.rs`:
- Around line 32-48: Update the audit lookup in the case clue submission test to
filter by both entity ID and the expected create operation, rather than
selecting any audit event. Deserialize audit.metadata_json into its structured
representation, then assert the status field is pending_review and the content
field is absent instead of using string contains checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ff54d67-a61b-4f2e-862e-65539c4477d9

📥 Commits

Reviewing files that changed from the base of the PR and between f406054 and cd87fe0.

📒 Files selected for processing (12)
  • docs/API.md
  • docs/openapi.yaml
  • src/models.rs
  • src/routes/cases.rs
  • src/routes/mod.rs
  • src/services/case_service.rs
  • tests/api/authentication_guards.rs
  • tests/api/case_clues_get.rs
  • tests/api/case_clues_post.rs
  • tests/api/clue_review_patch.rs
  • tests/api/mod.rs
  • tests/api/openapi_intake_contract.rs
🚧 Files skipped from review as they are similar to previous changes (10)
  • tests/api/authentication_guards.rs
  • tests/api/openapi_intake_contract.rs
  • tests/api/mod.rs
  • docs/openapi.yaml
  • src/routes/cases.rs
  • tests/api/clue_review_patch.rs
  • tests/api/case_clues_get.rs
  • docs/API.md
  • src/routes/mod.rs
  • src/services/case_service.rs

@github-actions

Copy link
Copy Markdown

Preview ready

The preview for this build is available at https://pr-123-cd87fe0aee68.angui.cg8.site.

Temporary demo accounts (all use the password 31d7b3940cd3ef66c20bfa8f5851f080dbe705a9a66197f3):

Role Email
Family family@demo.invalid
Commander commander@demo.invalid
Volunteer volunteer@demo.invalid
Learner learner@demo.invalid
Administrator admin@demo.invalid

These accounts and all preview data belong only to this deployment. The next preview deployment resets the SQLite database and invalidates these credentials.

@cg8-5712
cg8-5712 merged commit c84b1f6 into main Jul 25, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants