879 lines
22 KiB
Markdown
879 lines
22 KiB
Markdown
# Team Board — REST API спецификация
|
||
|
||
## Общие сведения
|
||
|
||
**Базовый URL**: `/api/v1/`
|
||
**Аутентификация**: Bearer токен в заголовке `Authorization` или query parameter `?token=`
|
||
**Формат данных**: JSON
|
||
**Middleware**: все API endpoints требуют авторизации (кроме `/api/v1/auth/login`)
|
||
|
||
### Типы токенов:
|
||
- **JWT** — для web пользователей (срок жизни 72 часа)
|
||
- **Bearer токены** — для агентов (формат `tb-xxxxx`, постоянные)
|
||
|
||
### CORS:
|
||
- `https://team.uix.su`
|
||
- `https://dev.team.uix.su`
|
||
- `http://localhost:3100`
|
||
|
||
---
|
||
|
||
## Auth — Аутентификация
|
||
|
||
### `POST /api/v1/auth/login`
|
||
Вход в систему для пользователей
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"login": "string", // slug или name пользователя
|
||
"password": "string"
|
||
}
|
||
```
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
{
|
||
"token": "jwt_token_here",
|
||
"member_id": "uuid",
|
||
"slug": "string",
|
||
"role": "owner|admin|member"
|
||
}
|
||
```
|
||
|
||
**Ошибки:** `401` - неверный логин/пароль
|
||
|
||
---
|
||
|
||
## Members — Участники (люди + агенты)
|
||
|
||
### `GET /api/v1/members`
|
||
Список всех участников
|
||
|
||
**Query параметры:**
|
||
- `include_inactive=boolean` — включить неактивных (по умолчанию false)
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
[
|
||
{
|
||
"id": "uuid",
|
||
"name": "string",
|
||
"slug": "string",
|
||
"type": "human|agent|bridge",
|
||
"role": "owner|admin|member",
|
||
"status": "online|offline|busy",
|
||
"is_active": true,
|
||
"avatar_url": "string|null",
|
||
"last_seen_at": "iso_timestamp",
|
||
"agent_config": {
|
||
"capabilities": ["string"],
|
||
"labels": ["string"],
|
||
"chat_listen": "all|mentions",
|
||
"task_listen": "all|mentions",
|
||
"prompt": "string|null",
|
||
"model": "string|null"
|
||
}
|
||
}
|
||
]
|
||
```
|
||
|
||
### `GET /api/v1/members/{member_id}`
|
||
Получить участника по ID
|
||
|
||
**Ответ (200):** объект Member (как в списке)
|
||
**Ошибки:** `404` - участник не найден
|
||
|
||
### `POST /api/v1/members`
|
||
Создать нового участника (человека или агента)
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"name": "string",
|
||
"slug": "string",
|
||
"type": "human|agent", // по умолчанию "agent"
|
||
"role": "owner|admin|member", // по умолчанию "member"
|
||
"agent_config": { // только для type="agent"
|
||
"capabilities": ["string"],
|
||
"labels": ["string"],
|
||
"chat_listen": "all|mentions",
|
||
"task_listen": "all|mentions",
|
||
"prompt": "string|null",
|
||
"model": "string|null"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Ответ (201):**
|
||
```json
|
||
{
|
||
"id": "uuid",
|
||
"name": "string",
|
||
"slug": "string",
|
||
"type": "human|agent",
|
||
"role": "owner|admin|member",
|
||
"token": "tb-xxxxx", // только при создании агента, показывается один раз
|
||
"agent_config": {...} // если был передан
|
||
}
|
||
```
|
||
|
||
**Ошибки:** `409` - slug уже занят
|
||
|
||
### `PATCH /api/v1/members/{member_id}`
|
||
Обновить участника
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"name": "string|null",
|
||
"role": "string|null",
|
||
"status": "string|null",
|
||
"avatar_url": "string|null",
|
||
"agent_config": { // только для агентов
|
||
"capabilities": ["string"],
|
||
"labels": ["string"],
|
||
"chat_listen": "all|mentions",
|
||
"task_listen": "all|mentions",
|
||
"prompt": "string|null",
|
||
"model": "string|null"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Ответ (200):** обновлённый объект Member
|
||
|
||
**Примечания:**
|
||
- Изменение agent_config отправляет WebSocket событие `config.updated` агенту
|
||
- При изменении конфигурации агент получает новые настройки в реальном времени
|
||
|
||
### `POST /api/v1/members/{member_id}/regenerate-token`
|
||
Сгенерировать новый токен для агента
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
{
|
||
"token": "tb-xxxxx"
|
||
}
|
||
```
|
||
|
||
**Ошибки:** `400` - только для агентов
|
||
|
||
### `POST /api/v1/members/{member_id}/revoke-token`
|
||
Отозвать токен агента
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
{
|
||
"ok": true
|
||
}
|
||
```
|
||
|
||
**Ошибки:** `400` - только для агентов
|
||
|
||
### `PATCH /api/v1/members/me/status`
|
||
Обновить свой статус (используется агентами)
|
||
|
||
**Query параметры:**
|
||
- `status=string` — новый статус
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
{
|
||
"status": "online|offline|busy"
|
||
}
|
||
```
|
||
|
||
**Примечания:** отправляет WebSocket событие `agent.status` всем подключённым клиентам
|
||
|
||
### `DELETE /api/v1/members/{member_id}`
|
||
Удалить участника (soft delete — is_active=false)
|
||
|
||
**Авторизация:** только владельцы
|
||
**Ответ (200):** `{"ok": true}`
|
||
**Ошибки:** `403` - недостаточно прав
|
||
|
||
---
|
||
|
||
## Projects — Проекты
|
||
|
||
### `GET /api/v1/projects`
|
||
Список активных проектов
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
[
|
||
{
|
||
"id": "uuid",
|
||
"name": "string",
|
||
"slug": "string",
|
||
"description": "string|null",
|
||
"repo_urls": ["string"],
|
||
"status": "active|archived",
|
||
"task_counter": 42,
|
||
"chat_id": "uuid|null",
|
||
"auto_assign": true
|
||
}
|
||
]
|
||
```
|
||
|
||
### `GET /api/v1/projects/{project_id}`
|
||
Получить проект по ID
|
||
|
||
**Ответ (200):** объект Project (как в списке)
|
||
**Ошибки:** `404` - проект не найден
|
||
|
||
### `POST /api/v1/projects`
|
||
Создать новый проект
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"name": "string",
|
||
"slug": "string",
|
||
"description": "string|null",
|
||
"repo_urls": ["string"]
|
||
}
|
||
```
|
||
|
||
**Ответ (201):** объект Project
|
||
**Ошибки:** `409` - slug уже занят
|
||
|
||
**Примечания:**
|
||
- Автоматически создаётся основной чат проекта
|
||
- Отправляет WebSocket событие `project.created`
|
||
|
||
### `PATCH /api/v1/projects/{project_id}`
|
||
Обновить проект
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"name": "string|null",
|
||
"slug": "string|null",
|
||
"description": "string|null",
|
||
"repo_urls": ["string"]|null,
|
||
"status": "active|archived|null",
|
||
"auto_assign": "boolean|null"
|
||
}
|
||
```
|
||
|
||
**Ответ (200):** обновлённый объект Project
|
||
**Ошибки:** `409` - slug уже занят
|
||
|
||
### `DELETE /api/v1/projects/{project_id}`
|
||
Удалить проект
|
||
|
||
**Авторизация:** только владельцы
|
||
**Ответ (200):** `{"ok": true}`
|
||
**Ошибки:** `403` - недостаточно прав
|
||
|
||
### `GET /api/v1/projects/{project_id}/members`
|
||
Участники проекта
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
[
|
||
{
|
||
"id": "uuid",
|
||
"name": "string",
|
||
"slug": "string",
|
||
"type": "human|agent|bridge",
|
||
"role": "owner|admin|member"
|
||
}
|
||
]
|
||
```
|
||
|
||
### `POST /api/v1/projects/{project_id}/members`
|
||
Добавить участника в проект
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"member_id": "uuid"
|
||
}
|
||
```
|
||
|
||
**Ответ (200):** `{"ok": true}`
|
||
**Ошибки:** `404` - участник не найден, `409` - участник уже в проекте
|
||
|
||
### `DELETE /api/v1/projects/{project_id}/members/{member_id}`
|
||
Исключить участника из проекта
|
||
|
||
**Ответ (200):** `{"ok": true}`
|
||
**Ошибки:** `400` - нельзя исключить владельца, `404` - участник не в проекте
|
||
|
||
---
|
||
|
||
## Tasks — Задачи
|
||
|
||
### `GET /api/v1/tasks`
|
||
Список задач с фильтрацией
|
||
|
||
**Query параметры:**
|
||
- `project_id=uuid` — фильтр по проекту
|
||
- `status=string` — фильтр по статусу (backlog|todo|in_progress|review|done)
|
||
- `assignee_id=uuid` — фильтр по исполнителю
|
||
- `label=string` — фильтр по лейблу
|
||
- `q=string` — поиск по номеру (XX-123) или заголовку
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
[
|
||
{
|
||
"id": "uuid",
|
||
"project": {
|
||
"id": "uuid",
|
||
"slug": "string",
|
||
"name": "string"
|
||
},
|
||
"number": 123,
|
||
"key": "XX-123",
|
||
"title": "string",
|
||
"description": "string|null",
|
||
"type": "task|bug|epic",
|
||
"status": "backlog|todo|in_progress|review|done",
|
||
"priority": "low|medium|high|urgent",
|
||
"labels": ["string"],
|
||
"assignee": {
|
||
"id": "uuid",
|
||
"slug": "string",
|
||
"name": "string"
|
||
} | null,
|
||
"reviewer": {...} | null,
|
||
"parent": {...} | null,
|
||
"subtasks": [...],
|
||
"steps": [
|
||
{
|
||
"id": "uuid",
|
||
"title": "string",
|
||
"done": true,
|
||
"position": 0
|
||
}
|
||
],
|
||
"watcher_ids": ["uuid"],
|
||
"depends_on": ["uuid"],
|
||
"position": 0,
|
||
"created_at": "iso_timestamp",
|
||
"updated_at": "iso_timestamp"
|
||
}
|
||
]
|
||
```
|
||
|
||
### `GET /api/v1/tasks/{task_id}`
|
||
Получить задачу по ID
|
||
|
||
**Ответ (200):** объект Task (как в списке)
|
||
**Ошибки:** `404` - задача не найдена
|
||
|
||
### `POST /api/v1/tasks`
|
||
Создать новую задачу
|
||
|
||
**Query параметры:**
|
||
- `project_id=uuid` — ID проекта (обязательно)
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"title": "string",
|
||
"description": "string|null",
|
||
"type": "task|bug|epic", // по умолчанию "task"
|
||
"status": "backlog|todo|in_progress|review|done", // по умолчанию "backlog"
|
||
"priority": "low|medium|high|urgent", // по умолчанию "medium"
|
||
"labels": ["string"],
|
||
"parent_id": "uuid|null",
|
||
"assignee_id": "uuid|null",
|
||
"reviewer_id": "uuid|null",
|
||
"depends_on": ["uuid"]
|
||
}
|
||
```
|
||
|
||
**Ответ (201):** объект Task
|
||
|
||
**Примечания:**
|
||
- Автоматически назначается номер задачи (инкрементальный счётчик проекта)
|
||
- При auto_assign=true проекта может автоматически назначаться на агента по лейблам
|
||
- Отправляет WebSocket событие `task.created`
|
||
- Создаёт системное сообщение в чате проекта и комментариях задачи
|
||
|
||
### `PATCH /api/v1/tasks/{task_id}`
|
||
Обновить задачу
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"title": "string|null",
|
||
"description": "string|null",
|
||
"type": "string|null",
|
||
"status": "string|null",
|
||
"priority": "string|null",
|
||
"labels": ["string"]|null,
|
||
"assignee_id": "string|null",
|
||
"reviewer_id": "string|null",
|
||
"position": "number|null"
|
||
}
|
||
```
|
||
|
||
**Ответ (200):** обновлённый объект Task
|
||
|
||
**Примечания:**
|
||
- Каждое изменение записывается в audit log (TaskAction)
|
||
- Отправляет WebSocket событие `task.updated`
|
||
- Создаёт системные сообщения для значимых изменений (статус, назначение)
|
||
|
||
### `DELETE /api/v1/tasks/{task_id}`
|
||
Удалить задачу
|
||
|
||
**Ответ (200):** `{"ok": true}`
|
||
**Примечания:** отправляет WebSocket событие `task.deleted`
|
||
|
||
### `POST /api/v1/tasks/{task_id}/take`
|
||
Взять задачу в работу (для агентов)
|
||
|
||
**Условия:**
|
||
- Задача не назначена (assignee_id=null)
|
||
- Статус backlog или todo
|
||
|
||
**Действие:**
|
||
- assignee_id = текущий пользователь
|
||
- status = in_progress
|
||
- Добавление в watcher_ids
|
||
|
||
**Ответ (200):** обновлённый объект Task
|
||
**Ошибки:** `409` - задача уже назначена или неподходящий статус
|
||
|
||
### `POST /api/v1/tasks/{task_id}/reject`
|
||
Отклонить назначенную задачу
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"reason": "string"
|
||
}
|
||
```
|
||
|
||
**Действие:**
|
||
- assignee_id = null
|
||
- status = backlog
|
||
- Создание системного сообщения с причиной
|
||
|
||
**Ответ (200):** `{"ok": true, "reason": "string"}`
|
||
|
||
### `POST /api/v1/tasks/{task_id}/assign`
|
||
Назначить задачу на участника
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"assignee_id": "uuid"
|
||
}
|
||
```
|
||
|
||
**Ответ (200):** обновлённый объект Task
|
||
**Ошибки:** `404` - участник не найден
|
||
|
||
### `POST /api/v1/tasks/{task_id}/watch`
|
||
Подписаться на уведомления по задаче
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"watcher_ids": ["uuid"]
|
||
}
|
||
```
|
||
|
||
### `DELETE /api/v1/tasks/{task_id}/watch`
|
||
Отписаться от уведомлений по задаче
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"watcher_ids": ["uuid"]
|
||
}
|
||
```
|
||
|
||
### `POST /api/v1/tasks/{task_id}/links`
|
||
Создать связь между задачами (зависимость)
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"target_id": "uuid",
|
||
"link_type": "blocks|depends_on|relates_to"
|
||
}
|
||
```
|
||
|
||
**Ответ (201):**
|
||
```json
|
||
{
|
||
"id": "uuid",
|
||
"source_id": "uuid",
|
||
"target_id": "uuid",
|
||
"link_type": "string",
|
||
"target_key": "XX-123",
|
||
"target_title": "string",
|
||
"source_key": "XX-124",
|
||
"source_title": "string"
|
||
}
|
||
```
|
||
|
||
**Ошибки:** `400` - нельзя связать задачу с самой собой, `409` - связь уже существует
|
||
|
||
---
|
||
|
||
## Steps — Этапы задач (чеклист)
|
||
|
||
### `GET /api/v1/tasks/{task_id}/steps`
|
||
Список этапов задачи
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
[
|
||
{
|
||
"id": "uuid",
|
||
"title": "string",
|
||
"done": true,
|
||
"position": 0
|
||
}
|
||
]
|
||
```
|
||
|
||
### `POST /api/v1/tasks/{task_id}/steps`
|
||
Создать новый этап
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"title": "string"
|
||
}
|
||
```
|
||
|
||
**Ответ (201):** объект Step
|
||
**Ошибки:** `404` - задача не найдена
|
||
|
||
### `PATCH /api/v1/tasks/{task_id}/steps/{step_id}`
|
||
Обновить этап
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"title": "string|null",
|
||
"done": "boolean|null"
|
||
}
|
||
```
|
||
|
||
**Ответ (200):** обновлённый объект Step
|
||
|
||
### `DELETE /api/v1/tasks/{task_id}/steps/{step_id}`
|
||
Удалить этап
|
||
|
||
**Ответ (200):** `{"ok": true}`
|
||
|
||
---
|
||
|
||
## Messages — Сообщения (чаты + комментарии к задачам)
|
||
|
||
### `GET /api/v1/messages`
|
||
Список сообщений
|
||
|
||
**Query параметры:**
|
||
- `chat_id=uuid` — сообщения из чата
|
||
- `task_id=uuid` — комментарии к задаче
|
||
- `parent_id=uuid` — ответы в треде
|
||
- `limit=number` — лимит (по умолчанию 50, макс 200)
|
||
- `offset=number` — смещение (по умолчанию 0)
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
[
|
||
{
|
||
"id": "uuid",
|
||
"chat_id": "uuid|null",
|
||
"task_id": "uuid|null",
|
||
"parent_id": "uuid|null",
|
||
"author_type": "human|agent|system",
|
||
"author_id": "uuid|null",
|
||
"author": {
|
||
"id": "uuid",
|
||
"slug": "string",
|
||
"name": "string"
|
||
} | null,
|
||
"actor": {...} | null, // для системных сообщений
|
||
"content": "string",
|
||
"thinking": "string|null",
|
||
"mentions": [
|
||
{
|
||
"id": "uuid",
|
||
"slug": "string",
|
||
"name": "string"
|
||
}
|
||
],
|
||
"voice_url": "string|null",
|
||
"attachments": [
|
||
{
|
||
"id": "uuid",
|
||
"filename": "string",
|
||
"mime_type": "string",
|
||
"size": 1024
|
||
}
|
||
],
|
||
"created_at": "iso_timestamp"
|
||
}
|
||
]
|
||
```
|
||
|
||
**Примечания:** возвращает последние N сообщений в хронологическом порядке
|
||
|
||
### `POST /api/v1/messages`
|
||
Отправить сообщение
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"chat_id": "uuid|null", // либо chat_id, либо task_id обязательно
|
||
"task_id": "uuid|null",
|
||
"parent_id": "uuid|null", // для ответов в треде
|
||
"content": "string",
|
||
"thinking": "string|null", // внутренние размышления (для агентов)
|
||
"mentions": ["member_id"], // упоминания по ID
|
||
"voice_url": "string|null", // ссылка на голосовое сообщение
|
||
"attachments": [ // прикреплённые файлы из /upload
|
||
{
|
||
"file_id": "uuid",
|
||
"filename": "string",
|
||
"mime_type": "string",
|
||
"size": 1024,
|
||
"storage_name": "string"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Ответ (201):** объект Message
|
||
**Ошибки:** `400` - не указан chat_id или task_id, `401` - не авторизован
|
||
|
||
**Примечания:**
|
||
- author_id берётся из токена авторизации
|
||
- Отправляет WebSocket событие `message.new`
|
||
- Для project чатов событие фильтруется по подпискам участников
|
||
|
||
### `GET /api/v1/messages/{message_id}/replies`
|
||
Ответы в треде
|
||
|
||
**Ответ (200):** массив объектов Message (отсортированы по created_at)
|
||
|
||
---
|
||
|
||
## Attachments — Файловые вложения
|
||
|
||
### `POST /api/v1/upload`
|
||
Загрузить файл
|
||
|
||
**Content-Type:** `multipart/form-data`
|
||
**Поля:**
|
||
- `file` — файловое поле
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
{
|
||
"file_id": "uuid",
|
||
"filename": "string",
|
||
"mime_type": "string",
|
||
"size": 1024,
|
||
"storage_name": "string"
|
||
}
|
||
```
|
||
|
||
**Ошибки:** `413` - файл слишком большой (лимит 50MB)
|
||
|
||
**Примечания:**
|
||
- Файл сохраняется в `/data/uploads/`
|
||
- Возвращаемые данные используются в attachments при создании сообщений
|
||
- Файл пока не привязан к сообщению
|
||
|
||
### `GET /api/v1/attachments/{attachment_id}/download`
|
||
Скачать файл
|
||
|
||
**Query параметры:**
|
||
- `token=string` — токен авторизации (опционально, для img src)
|
||
|
||
**Ответ (200):** файл с правильными заголовками
|
||
**Ошибки:** `404` - файл не найден
|
||
|
||
---
|
||
|
||
## Project Files — Файлы проектов
|
||
|
||
### `GET /api/v1/projects/{project_id}/files`
|
||
Список файлов проекта
|
||
|
||
**Query параметры:**
|
||
- `search=string` — поиск по имени файла
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
[
|
||
{
|
||
"id": "uuid",
|
||
"filename": "string",
|
||
"description": "string|null",
|
||
"mime_type": "string",
|
||
"size": 1024,
|
||
"uploaded_by": {
|
||
"id": "uuid",
|
||
"slug": "string",
|
||
"name": "string"
|
||
},
|
||
"created_at": "iso_timestamp",
|
||
"updated_at": "iso_timestamp"
|
||
}
|
||
]
|
||
```
|
||
|
||
### `POST /api/v1/projects/{project_id}/files`
|
||
Загрузить файл в проект
|
||
|
||
**Content-Type:** `multipart/form-data`
|
||
**Поля:**
|
||
- `file` — файловое поле
|
||
- `description` — описание файла (опционально)
|
||
|
||
**Ответ (201):** объект ProjectFile
|
||
**Ошибки:** `413` - файл слишком большой, `400` - некорректное имя файла
|
||
|
||
**Примечания:**
|
||
- Файлы сохраняются в `/data/projects/{project_slug}/`
|
||
- При загрузке файла с существующим именем — перезаписывается
|
||
|
||
### `GET /api/v1/projects/{project_id}/files/{file_id}`
|
||
Информация о файле
|
||
|
||
**Ответ (200):** объект ProjectFile
|
||
|
||
### `GET /api/v1/projects/{project_id}/files/{file_id}/download`
|
||
Скачать файл проекта
|
||
|
||
**Ответ (200):** файл с правильными заголовками
|
||
**Ошибки:** `404` - файл не найден
|
||
|
||
### `PATCH /api/v1/projects/{project_id}/files/{file_id}`
|
||
Обновить описание файла
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"description": "string|null"
|
||
}
|
||
```
|
||
|
||
**Ответ (200):** обновлённый объект ProjectFile
|
||
|
||
### `DELETE /api/v1/projects/{project_id}/files/{file_id}`
|
||
Удалить файл проекта
|
||
|
||
**Ответ (200):** `{"ok": true}`
|
||
|
||
---
|
||
|
||
## Labels — Глобальные лейблы
|
||
|
||
### `GET /api/v1/labels`
|
||
Список всех лейблов
|
||
|
||
**Ответ (200):**
|
||
```json
|
||
[
|
||
{
|
||
"id": "uuid",
|
||
"name": "string",
|
||
"color": "#6366f1"
|
||
}
|
||
]
|
||
```
|
||
|
||
### `POST /api/v1/labels`
|
||
Создать новый лейбл
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"name": "string",
|
||
"color": "#6366f1" // по умолчанию
|
||
}
|
||
```
|
||
|
||
**Ответ (201):** объект Label
|
||
|
||
### `PATCH /api/v1/labels/{label_id}`
|
||
Обновить лейбл
|
||
|
||
**Тело запроса:**
|
||
```json
|
||
{
|
||
"name": "string|null",
|
||
"color": "string|null"
|
||
}
|
||
```
|
||
|
||
**Ответ (200):** обновлённый объект Label
|
||
|
||
### `DELETE /api/v1/labels/{label_id}`
|
||
Удалить лейбл
|
||
|
||
**Ответ (200):** `{"ok": true}`
|
||
|
||
### `POST /api/v1/tasks/{task_id}/labels/{label_id}`
|
||
Добавить лейбл к задаче
|
||
|
||
**Ответ (200):** `{"ok": true}`
|
||
|
||
### `DELETE /api/v1/tasks/{task_id}/labels/{label_id}`
|
||
Убрать лейбл с задачи
|
||
|
||
**Ответ (200):** `{"ok": true}`
|
||
|
||
---
|
||
|
||
## Utility Endpoints
|
||
|
||
### `GET /health`
|
||
Health check
|
||
|
||
**Авторизация:** не требуется
|
||
**Ответ (200):**
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"version": "0.2.0"
|
||
}
|
||
```
|
||
|
||
### `GET /docs`
|
||
Swagger UI документация
|
||
|
||
**Авторизация:** не требуется
|
||
**Доступность:** только в dev режиме
|
||
|
||
---
|
||
|
||
## Коды ошибок
|
||
|
||
- `400` — Bad Request (некорректные данные)
|
||
- `401` — Unauthorized (не авторизован или неверный токен)
|
||
- `403` — Forbidden (недостаточно прав)
|
||
- `404` — Not Found (ресурс не найден)
|
||
- `409` — Conflict (конфликт данных, например дублирование slug)
|
||
- `413` — Payload Too Large (файл слишком большой)
|
||
- `500` — Internal Server Error (внутренняя ошибка сервера)
|
||
|
||
## Middleware и логирование
|
||
|
||
**Логирование запросов:** все HTTP запросы логируются с телом (для POST/PUT/PATCH), временем выполнения и кодом ответа
|
||
|
||
**Извлечение участника:** middleware автоматически извлекает Member из JWT/agent token и сохраняет в `request.state.member`
|
||
|
||
**CORS:** автоматически обрабатывается для разрешённых origins
|
||
|
||
Этот документ описывает API на основе исходного кода в `/root/projects/team-board/tracker/src/tracker/api/` на дату создания спецификации. |