192 lines
4.1 KiB
Markdown
192 lines
4.1 KiB
Markdown
|
|
# Design: Schedule Items API
|
|||
|
|
|
|||
|
|
**Date:** 2026-02-27
|
|||
|
|
**Status:** Approved
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
实现日历事项(Schedule Items)的后端 CRUD API,支持用户创建、查询、更新、删除日历事项。
|
|||
|
|
|
|||
|
|
## Scope
|
|||
|
|
|
|||
|
|
- 仅后端 API,不涉及前端
|
|||
|
|
- 全量 CRUD
|
|||
|
|
- 查询按时间范围筛选
|
|||
|
|
- 暂不支持重复日程(recurrence_rule 留空)
|
|||
|
|
|
|||
|
|
## API Endpoints
|
|||
|
|
|
|||
|
|
### 1. 创建日历事项
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /api/v1/schedule-items
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Request:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"title": "string (1-255 chars, required)",
|
|||
|
|
"description": "string? (max 2000 chars)",
|
|||
|
|
"start_at": "string (ISO 8601 datetime, required)",
|
|||
|
|
"end_at": "string? (ISO 8601 datetime, must be after start_at)",
|
|||
|
|
"timezone": "string? (default: UTC)",
|
|||
|
|
"metadata": {
|
|||
|
|
"color": "#FF6B6B",
|
|||
|
|
"location": "会议室A",
|
|||
|
|
"notes": "记得带身份证",
|
|||
|
|
"attachments": [],
|
|||
|
|
"version": 1
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Response:** 201 Created
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"id": "uuid",
|
|||
|
|
"title": "string",
|
|||
|
|
"description": "string?",
|
|||
|
|
"start_at": "string",
|
|||
|
|
"end_at": "string?",
|
|||
|
|
"timezone": "string",
|
|||
|
|
"metadata": {...},
|
|||
|
|
"status": "active",
|
|||
|
|
"source_type": "manual",
|
|||
|
|
"created_at": "string",
|
|||
|
|
"updated_at": "string"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 查询日历事项列表
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GET /api/v1/schedule-items?start_at=2026-02-01&end_at=2026-02-28
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Query Parameters:**
|
|||
|
|
- `start_at`: ISO 8601 date/datetime(查询范围起始)
|
|||
|
|
- `end_at`: ISO 8601 date/datetime(查询范围结束)
|
|||
|
|
|
|||
|
|
**Response:** 200 OK
|
|||
|
|
```json
|
|||
|
|
[
|
|||
|
|
{
|
|||
|
|
"id": "uuid",
|
|||
|
|
"title": "string",
|
|||
|
|
"start_at": "string",
|
|||
|
|
"end_at": "string?",
|
|||
|
|
"timezone": "string",
|
|||
|
|
"status": "active"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 获取单个事项
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GET /api/v1/schedule-items/{id}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Response:** 200 OK(完整字段,同创建响应)
|
|||
|
|
|
|||
|
|
### 4. 更新事项
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
PATCH /api/v1/schedule-items/{id}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Request:** 支持 `title`/`description`/`start_at`/`end_at`/`timezone`/`metadata`/`status` 部分更新
|
|||
|
|
|
|||
|
|
**Response:** 200 OK
|
|||
|
|
|
|||
|
|
### 5. 删除事项
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
DELETE /api/v1/schedule-items/{id}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Response:** 204 No Content(软删除)
|
|||
|
|
|
|||
|
|
## Data Models
|
|||
|
|
|
|||
|
|
### Metadata 结构(Pydantic)
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from enum import Enum
|
|||
|
|
from pydantic import BaseModel
|
|||
|
|
from uuid import UUID
|
|||
|
|
|
|||
|
|
class AttachmentType(str, Enum):
|
|||
|
|
DOCUMENT = "document"
|
|||
|
|
REMINDER = "reminder"
|
|||
|
|
|
|||
|
|
class ScheduleItemMetadataAttachment(BaseModel):
|
|||
|
|
name: str
|
|||
|
|
type: AttachmentType
|
|||
|
|
visible_to: list[UUID] = []
|
|||
|
|
# document 类型
|
|||
|
|
url: str | None = None
|
|||
|
|
note: str | None = None
|
|||
|
|
# reminder 类型
|
|||
|
|
content: str | None = None
|
|||
|
|
|
|||
|
|
class ScheduleItemMetadata(BaseModel):
|
|||
|
|
color: str | None = None
|
|||
|
|
location: str | None = None
|
|||
|
|
notes: str | None = None
|
|||
|
|
attachments: list[ScheduleItemMetadataAttachment] = []
|
|||
|
|
version: int = 1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 数据库模型(已有)
|
|||
|
|
|
|||
|
|
参见 `backend/src/models/schedule_items.py`:
|
|||
|
|
- `id`: UUID
|
|||
|
|
- `owner_id`: UUID
|
|||
|
|
- `title`: String(255)
|
|||
|
|
- `description`: Text
|
|||
|
|
- `start_at`: DateTime(timezone=True)
|
|||
|
|
- `end_at`: DateTime(timezone=True)
|
|||
|
|
- `timezone`: String(50)
|
|||
|
|
- `extra_metadata`: JSONB (mapped as "metadata")
|
|||
|
|
- `recurrence_rule`: String(255)
|
|||
|
|
- `source_type`: Enum (MANUAL/IMPORTED/AGENT_GENERATED)
|
|||
|
|
- `status`: Enum (ACTIVE/COMPLETED/CANCELED/ARCHIVED)
|
|||
|
|
- `created_by`: UUID
|
|||
|
|
|
|||
|
|
## Architecture
|
|||
|
|
|
|||
|
|
遵循项目 `schemas / repository / service / router` 分层模式:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
backend/src/v1/schedule_items/
|
|||
|
|
├── __init__.py
|
|||
|
|
├── schemas.py # Pydantic 请求/响应模型
|
|||
|
|
├── repository.py # CRUD 操作(无 auth,无 commit)
|
|||
|
|
├── service.py # 业务逻辑 + 授权 + 事务边界
|
|||
|
|
├── router.py # FastAPI 路由定义
|
|||
|
|
└── dependencies.py # DI(如有)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Security
|
|||
|
|
|
|||
|
|
- 所有端点需要认证(JWT)
|
|||
|
|
- `owner_id` 从 JWT `sub` 提取,不从请求体读取
|
|||
|
|
- 用户只能操作自己的日历事项(`owner_id` 过滤)
|
|||
|
|
- RLS 已在数据库层启用(防御边界)
|
|||
|
|
|
|||
|
|
## Error Handling
|
|||
|
|
|
|||
|
|
使用 RFC 7807 `application/problem+json` 格式:
|
|||
|
|
- 400: 请求参数无效
|
|||
|
|
- 401: 未认证
|
|||
|
|
- 404: 事项不存在或无权限访问
|
|||
|
|
- 422: 验证失败
|
|||
|
|
|
|||
|
|
## Out of Scope
|
|||
|
|
|
|||
|
|
- 重复日程(recurrence_rule)
|
|||
|
|
- 日程订阅与协作(schedule_subscriptions)
|
|||
|
|
- 待办事项联动(todos/todo_sources)
|
|||
|
|
- 前端实现
|