Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
| `POST` | `/api/cases` | `201` | 创建案件和老人画像 |
| `GET` | `/api/cases/{case_id}` | `200` | 查询案件、老人画像和线索 |
| `PATCH` | `/api/cases/{case_id}/status` | `200` | 人工更新案件状态 |
| `GET` | `/api/cases/{case_id}/clues` | `200` | 获取角色裁剪、可分页的线索时间轴 |
| `POST` | `/api/cases/{case_id}/clues` | `201` | 提交待审核线索 |
| `POST` | `/api/cases/{case_id}/places` | `201` | 家属或指挥提交常去/关键地点,始终待人工审核 |
| `GET` | `/api/cases/{case_id}/resource-configuration` | `200` | 获取当前案件可用的地点类型和图片限制 |
Expand Down Expand Up @@ -115,6 +116,8 @@ duplicate

AI 或其他自动化能力未来只能生成草稿或待审核输入,不得绕过人工审核直接创建 `confirmed` 线索。

线索时间轴使用 `page`(默认 `1`)、`page_size`(默认 `25`,最大 `100`)、`status`、`sort`(`created_at` 或 `occurred_at`)和 `order`(`asc` 或 `desc`)查询参数。所有参数均为白名单;响应中的 `total` 只统计当前认证用户可见的线索。指挥可见全量,家属可见已确认或本人提交的线索,志愿者仅可见已确认线索;无案件成员关系一律返回 `404`。

## 6. 请求示例

创建案件:
Expand Down
54 changes: 54 additions & 0 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,45 @@ paths:
/api/cases/{case_id}/clues:
parameters:
- $ref: "#/components/parameters/CaseId"
get:
tags: [Cases]
operationId: listCaseClues
summary: 获取角色裁剪的线索时间轴
description: |
commander 可以查看全部线索;family 只能查看已确认线索和本人提交的线索;
volunteer 只能查看已确认线索。分页总数只计算当前角色可见的线索。无案件成员关系返回 404。
security:
- bearerAuth: []
x-account-types: [member]
x-case-roles: [family, commander, volunteer]
x-data-classification: case-restricted
parameters:
- name: page
in: query
schema: { type: integer, minimum: 1, default: 1 }
- name: page_size
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
- name: status
in: query
schema: { $ref: "#/components/schemas/ClueStatus" }
- name: sort
in: query
schema: { type: string, enum: [created_at, occurred_at], default: created_at }
- name: order
in: query
schema: { type: string, enum: [asc, desc], default: desc }
responses:
"200":
description: 角色裁剪后的稳定排序线索页
content:
application/json:
schema:
$ref: "#/components/schemas/ClueTimelinePage"
"400": { $ref: "#/components/responses/ValidationError" }
"401": { $ref: "#/components/responses/Unauthorized" }
"404": { $ref: "#/components/responses/NotFound" }
"500": { $ref: "#/components/responses/InternalError" }
post:
tags: [Cases]
operationId: createClue
Expand Down Expand Up @@ -1483,6 +1522,21 @@ components:
type: boolean
description: 当前认证用户是否为该线索的提交者。

ClueTimelinePage:
type: object
additionalProperties: false
required: [items, page, page_size, total]
properties:
items:
type: array
items: { $ref: "#/components/schemas/Clue" }
page: { type: integer, minimum: 1 }
page_size: { type: integer, minimum: 1, maximum: 100 }
total:
type: integer
minimum: 0
description: Only clues visible to the authenticated user are counted.

CreateCasePlaceRequest:
type: object
additionalProperties: false
Expand Down
18 changes: 18 additions & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,16 @@ pub struct CreateClueRequest {
pub location_text: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ClueTimelineQuery {
pub page: Option<u64>,
pub page_size: Option<u64>,
pub status: Option<String>,
pub sort: Option<String>,
pub order: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CreateCasePlaceRequest {
Expand Down Expand Up @@ -494,6 +504,14 @@ pub struct ClueResponse {
pub is_own_submission: bool,
}

#[derive(Debug, Serialize)]
pub struct ClueTimelineResponse {
pub items: Vec<ClueResponse>,
pub page: u64,
pub page_size: u64,
pub total: u64,
}

#[derive(Debug, Serialize)]
pub struct CasePlaceResponse {
pub id: String,
Expand Down
11 changes: 11 additions & 0 deletions src/routes/cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub fn configure(config: &mut web::ServiceConfig) {
.route("", web::post().to(create_case))
.route("/{case_id}", web::get().to(get_case))
.route("/{case_id}/status", web::patch().to(update_case_status))
.route("/{case_id}/clues", web::get().to(list_clues))
.route("/{case_id}/clues", web::post().to(create_clue))
.route("/{case_id}/places", web::post().to(create_place))
.route(
Expand Down Expand Up @@ -202,6 +203,16 @@ async fn create_clue(
Ok(HttpResponse::Created().json(clue))
}

async fn list_clues(
auth: AuthenticatedUser,
state: web::Data<AppState>,
case_id: web::Path<String>,
query: web::Query<crate::models::ClueTimelineQuery>,
) -> Result<HttpResponse, ApiError> {
let clues = case_service::list_clues(&state.db, &auth, &case_id, query.into_inner()).await?;
Ok(HttpResponse::Ok().json(clues))
}

async fn add_case_member(
auth: AuthenticatedUser,
state: web::Data<AppState>,
Expand Down
30 changes: 29 additions & 1 deletion src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,41 @@ mod clues;
mod health;
mod intake_sessions;

use actix_web::web;
use actix_web::{HttpResponse, web};

use serde_json::json;

use crate::auth::ApiSessionAuthentication;

pub fn configure(config: &mut web::ServiceConfig) {
let json_config = web::JsonConfig::default().error_handler(|error, _request| {
actix_web::error::InternalError::from_response(
error,
HttpResponse::BadRequest().json(json!({
"error": {
"code": "validation_error",
"message": "request body is invalid"
}
})),
)
.into()
});
let query_config = web::QueryConfig::default().error_handler(|error, _request| {
actix_web::error::InternalError::from_response(
error,
HttpResponse::BadRequest().json(json!({
"error": {
"code": "validation_error",
"message": "query parameters are invalid"
}
})),
)
.into()
});
config.service(
web::scope("/api")
.app_data(json_config)
.app_data(query_config)
.wrap(ApiSessionAuthentication)
.service(health::get_health)
.configure(auth::configure)
Expand Down
Loading
Loading