# 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）
- 前端实现
