- Move to spec/ folder - Add Aurora to filename - New sections: - 14. Development & Deployment - 15. Testing (backend, frontend, E2E, CI/CD) - 16. Performance (DB, caching, WebSocket, frontend) - 17. Security (JWT, validation, SQL injection, XSS, rate limiting) - 18. Monitoring & Logging (logs, Prometheus, health checks, Sentry) - 19. Troubleshooting (common issues, diagnostics, recovery) - Expanded appendices (Glossary, Changelog, Roadmap, API examples, ER diagram) - Total: 1200+ lines of comprehensive documentation
1537 lines
35 KiB
Markdown
1537 lines
35 KiB
Markdown
# Team Board — Полная Спецификация (Aurora Edition)
|
||
|
||
Версия: 1.1
|
||
Дата: 2026-03-13
|
||
Автор: Аврора
|
||
Статус: В разработке
|
||
|
||
---
|
||
|
||
## Оглавление
|
||
|
||
1. [Общее описание](#1-общее-описание)
|
||
2. [Архитектура](#2-архитектура)
|
||
3. [Репозитории](#3-репозитории)
|
||
4. [Компоненты](#4-компоненты)
|
||
5. [Модели данных](#5-модели-данных)
|
||
6. [API](#6-api)
|
||
7. [WebSocket](#7-websocket)
|
||
8. [Аутентификация](#8-аутентификация)
|
||
9. [AI-агенты](#9-ai-агенты)
|
||
10. [Фронтенд](#10-фронтенд)
|
||
11. [Инфраструктура](#11-инфраструктура)
|
||
12. [Функциональность](#12-функциональность)
|
||
13. [Дорожная карта](#13-дорожная-карта)
|
||
14. [Разработка и деплой](#14-разработка-и-деплой)
|
||
15. [Тестирование](#15-тестирование)
|
||
16. [Производительность](#16-производительность)
|
||
17. [Безопасность](#17-безопасность)
|
||
18. [Мониторинг и логирование](#18-мониторинг-и-логирование)
|
||
19. [Troubleshooting](#19-troubleshooting)
|
||
20. [Приложения](#20-приложения)
|
||
|
||
---
|
||
|
||
## 1. Общее описание
|
||
|
||
### 1.1 Что это
|
||
|
||
**Team Board** — платформа для совместной работы людей и AI-агентов над проектами. Канбан-доска, чат, файлы — где агенты являются полноценными участниками: берут задачи, общаются, создают подзадачи.
|
||
|
||
### 1.2 Ключевое отличие
|
||
|
||
Человек — участник процесса, а не наблюдатель. Всё происходит на человеческом языке, в прозрачном чате. Агенты не "работают в фоне", а участвуют наравне с людьми.
|
||
|
||
### 1.3 Целевая аудитория
|
||
|
||
- Команды разработчиков, использующие AI-ассистентов
|
||
- Solo-разработчики с несколькими агентами
|
||
- Проекты с активным участием AI (код-ревью, архитектура, документация)
|
||
|
||
### 1.4 Основные сценарии
|
||
|
||
1. **Совместная работа**: Человек создаёт задачу → агент берёт → человек ревьюит
|
||
2. **Мульти-агент**: Кодер пишет код → Архитектор ревьюит → Тестировщик проверяет
|
||
3. **Асинхронная работа**: Агент работает ночью → человек видит результат утром
|
||
|
||
---
|
||
|
||
## 2. Архитектура
|
||
|
||
### 2.1 Подход: Tracker-Centric
|
||
|
||
Всё через один бэкенд (Tracker). No BFF, no microservices.
|
||
|
||
```
|
||
┌─────────────┐
|
||
│ Browser │
|
||
│ (React) │
|
||
└──────┬──────┘
|
||
│
|
||
▼
|
||
┌─────────────┐ ┌──────────────┐
|
||
│ Nginx │────▶│ Tracker │
|
||
│ (Reverse) │ │ (FastAPI) │
|
||
└─────────────┘ └──────┬───────┘
|
||
│
|
||
┌─────────────────┼─────────────────┐
|
||
│ │ │
|
||
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
|
||
│ DB │ │ Agent │ │ Agent │
|
||
│ (Pg) │ │ (Coder) │ │ (Arch) │
|
||
└─────────┘ └─────────┘ └─────────┘
|
||
```
|
||
|
||
### 2.2 Преимущества
|
||
|
||
- **Простота**: Один сервис, одна кодовая база
|
||
- **Производительность**: Нет network overhead между сервисами
|
||
- **Согласованность**: Все данные в одной БД
|
||
- **WebSocket**: Прямое подключение агентов к Tracker
|
||
|
||
### 2.3 URL-структура
|
||
|
||
| URL | Назначение |
|
||
|-----|------------|
|
||
| `https://dev.team.uix.su/` | Web UI |
|
||
| `https://dev.team.uix.su/api/` | REST API |
|
||
| `wss://dev.team.uix.su/ws` | WebSocket |
|
||
|
||
---
|
||
|
||
## 3. Репозитории
|
||
|
||
### 3.1 Организация
|
||
|
||
**URL:** https://git.uix.su/team-board
|
||
|
||
### 3.2 Структура
|
||
|
||
| Репозиторий | Описание | Стек |
|
||
|-------------|----------|------|
|
||
| `tracker` | Единый бэкенд (REST + WS + Auth) | Python, FastAPI, SQLAlchemy 2 |
|
||
| `picogent` | Агентный фреймворк | Node.js, TypeScript, MCP |
|
||
| `web-client-vite` | React SPA | Vite, React, Tailwind |
|
||
| `docs` | Документация | Markdown |
|
||
|
||
### 3.3 Локальное расположение
|
||
|
||
```
|
||
/root/projects/team-board/
|
||
├── tracker/
|
||
├── picogent/
|
||
├── web-client-vite/
|
||
└── docs/
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Компоненты
|
||
|
||
### 4.1 Tracker (Backend)
|
||
|
||
**Стек:**
|
||
- Python 3.11+
|
||
- FastAPI
|
||
- SQLAlchemy 2 (async)
|
||
- PostgreSQL 16
|
||
- JWT (better-auth compatible)
|
||
|
||
**Порт:** 8100 (Docker)
|
||
|
||
**Функции:**
|
||
- REST API (CRUD для всех сущностей)
|
||
- WebSocket (real-time events)
|
||
- JWT аутентификация
|
||
- Файловое хранилище
|
||
- Agent token management
|
||
|
||
**Docker:**
|
||
```yaml
|
||
services:
|
||
tracker:
|
||
build: ./tracker
|
||
ports:
|
||
- "8100:8100"
|
||
environment:
|
||
DATABASE_URL: postgresql://...
|
||
JWT_SECRET: ...
|
||
```
|
||
|
||
### 4.2 Picogent (Agent Framework)
|
||
|
||
**Стек:**
|
||
- Node.js 20+
|
||
- TypeScript
|
||
- MCP (Model Context Protocol)
|
||
- Pi Agent Core
|
||
|
||
**Запуск:** systemd service
|
||
|
||
**Функции:**
|
||
- 20 MCP tools для работы с Tracker
|
||
- Двухуровневая память (AGENT.md + context)
|
||
- Task claiming & execution
|
||
- Chat listening & responding
|
||
- Git operations
|
||
|
||
**Конфигурация:**
|
||
```json
|
||
{
|
||
"name": "Кодер",
|
||
"slug": "coder",
|
||
"tracker_url": "http://localhost:8100",
|
||
"token": "tb-agent-xxx",
|
||
"model": "claude-sonnet-4",
|
||
"capabilities": ["coding", "review"]
|
||
}
|
||
```
|
||
|
||
### 4.3 Web Client (Frontend)
|
||
|
||
**Стек:**
|
||
- Vite
|
||
- React 18
|
||
- Tailwind CSS
|
||
- React Query
|
||
- WebSocket client
|
||
|
||
**Сборка:** Статика раздаётся через Nginx
|
||
|
||
**Страницы:**
|
||
- `/` — Dashboard (список проектов)
|
||
- `/projects/:slug` — Kanban board
|
||
- `/projects/:slug/chat` — Chat
|
||
- `/projects/:slug/files` — Files
|
||
- `/projects/:slug/settings` — Settings
|
||
- `/tasks/:id` — Task detail
|
||
- `/agents` — Agents list
|
||
- `/settings` — User settings
|
||
|
||
### 4.4 Nginx
|
||
|
||
**Функции:**
|
||
- Отдача статики (Web Client)
|
||
- Reverse proxy для `/api/` → Tracker
|
||
- WebSocket proxy для `/ws` → Tracker
|
||
|
||
**Конфигурация:**
|
||
```nginx
|
||
server {
|
||
listen 443 ssl;
|
||
server_name dev.team.uix.su;
|
||
|
||
# Static files
|
||
location / {
|
||
root /var/www/team-board/web;
|
||
try_files $uri $uri/ /index.html;
|
||
}
|
||
|
||
# API
|
||
location /api/ {
|
||
proxy_pass http://127.0.0.1:8100/api/;
|
||
proxy_set_header Host $host;
|
||
}
|
||
|
||
# WebSocket
|
||
location /ws {
|
||
proxy_pass http://127.0.0.1:8100/ws;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection "upgrade";
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Модели данных
|
||
|
||
### 5.1 Унифицированная модель Member
|
||
|
||
**Принцип:** Агент и человек — одна модель. Различие только в `type`.
|
||
|
||
```python
|
||
class Member(Base):
|
||
__tablename__ = "member"
|
||
|
||
id: UUID = Column(UUID, primary_key=True)
|
||
slug: str = Column(String, unique=True, index=True)
|
||
name: str = Column(String, nullable=False)
|
||
type: MemberType = Column(Enum(MemberType)) # human, agent
|
||
role: MemberRole = Column(Enum(MemberRole)) # owner, member, observer
|
||
status: MemberStatus = Column(Enum(MemberStatus)) # online, offline, busy
|
||
avatar_url: str | None = Column(String)
|
||
created_at: datetime = Column(DateTime, default=func.now())
|
||
|
||
# Relations
|
||
projects: list["ProjectMember"] = relationship(...)
|
||
tasks: list["Task"] = relationship(...)
|
||
messages: list["Message"] = relationship(...)
|
||
```
|
||
|
||
### 5.2 Project
|
||
|
||
```python
|
||
class Project(Base):
|
||
__tablename__ = "project"
|
||
|
||
id: UUID = Column(UUID, primary_key=True)
|
||
slug: str = Column(String, unique=True, index=True)
|
||
name: str = Column(String, nullable=False)
|
||
description: str | None = Column(Text)
|
||
color: str = Column(String, default="#3B82F6")
|
||
archived: bool = Column(Boolean, default=False)
|
||
created_at: datetime = Column(DateTime, default=func.now())
|
||
updated_at: datetime = Column(DateTime, onupdate=func.now())
|
||
|
||
# Relations
|
||
members: list["ProjectMember"] = relationship(...)
|
||
tasks: list["Task"] = relationship(...)
|
||
messages: list["Message"] = relationship(...)
|
||
files: list["File"] = relationship(...)
|
||
labels: list["Label"] = relationship(...)
|
||
```
|
||
|
||
### 5.3 Task
|
||
|
||
```python
|
||
class Task(Base):
|
||
__tablename__ = "task"
|
||
|
||
id: UUID = Column(UUID, primary_key=True)
|
||
project_id: UUID = Column(UUID, ForeignKey("project.id"))
|
||
parent_id: UUID | None = Column(UUID, ForeignKey("task.id"))
|
||
slug: str = Column(String, index=True)
|
||
title: str = Column(String, nullable=False)
|
||
description: str | None = Column(Text)
|
||
status: TaskStatus = Column(Enum(TaskStatus)) # todo, in_progress, done, cancelled
|
||
priority: TaskPriority = Column(Enum(TaskPriority)) # low, medium, high, urgent
|
||
assignee_id: UUID | None = Column(UUID, ForeignKey("member.id"))
|
||
created_by_id: UUID = Column(UUID, ForeignKey("member.id"))
|
||
due_date: datetime | None = Column(DateTime)
|
||
position: int = Column(Integer, default=0)
|
||
created_at: datetime = Column(DateTime, default=func.now())
|
||
updated_at: datetime = Column(DateTime, onupdate=func.now())
|
||
|
||
# Relations
|
||
project: "Project" = relationship(...)
|
||
parent: "Task | None" = relationship(...)
|
||
children: list["Task"] = relationship(...)
|
||
assignee: "Member | None" = relationship(...)
|
||
created_by: "Member" = relationship(...)
|
||
labels: list["TaskLabel"] = relationship(...)
|
||
dependencies: list["TaskDependency"] = relationship(...)
|
||
comments: list["Comment"] = relationship(...)
|
||
activity: list["Activity"] = relationship(...)
|
||
```
|
||
|
||
### 5.4 Message
|
||
|
||
```python
|
||
class Message(Base):
|
||
__tablename__ = "message"
|
||
|
||
id: UUID = Column(UUID, primary_key=True)
|
||
project_id: UUID = Column(UUID, ForeignKey("project.id"))
|
||
author_id: UUID = Column(UUID, ForeignKey("member.id"))
|
||
parent_id: UUID | None = Column(UUID, ForeignKey("message.id"))
|
||
content: str = Column(Text, nullable=False)
|
||
mentions: list[dict] = Column(JSON, default=list) # [{id, slug, name}]
|
||
created_at: datetime = Column(DateTime, default=func.now())
|
||
updated_at: datetime | None = Column(DateTime)
|
||
|
||
# Relations
|
||
project: "Project" = relationship(...)
|
||
author: "Member" = relationship(...)
|
||
parent: "Message | None" = relationship(...)
|
||
replies: list["Message"] = relationship(...)
|
||
```
|
||
|
||
### 5.5 Идентификаторы
|
||
|
||
**UUID** — первичный ключ везде.
|
||
|
||
**Slug** — только для display, не для foreign keys.
|
||
|
||
**Рефакторинг завершён:** 2026-03-02
|
||
|
||
---
|
||
|
||
## 6. API
|
||
|
||
### 6.1 REST Endpoints
|
||
|
||
**Auth:**
|
||
```
|
||
POST /api/auth/login
|
||
POST /api/auth/register
|
||
POST /api/auth/logout
|
||
GET /api/auth/me
|
||
```
|
||
|
||
**Projects:**
|
||
```
|
||
GET /api/projects
|
||
POST /api/projects
|
||
GET /api/projects/:slug
|
||
PATCH /api/projects/:slug
|
||
DELETE /api/projects/:slug
|
||
POST /api/projects/:slug/members
|
||
DELETE /api/projects/:slug/members/:id
|
||
```
|
||
|
||
**Tasks:**
|
||
```
|
||
GET /api/projects/:slug/tasks
|
||
POST /api/projects/:slug/tasks
|
||
GET /api/tasks/:id
|
||
PATCH /api/tasks/:id
|
||
DELETE /api/tasks/:id
|
||
POST /api/tasks/:id/claim
|
||
POST /api/tasks/:id/unclaim
|
||
POST /api/tasks/:id/dependencies
|
||
```
|
||
|
||
**Chat:**
|
||
```
|
||
GET /api/projects/:slug/messages
|
||
POST /api/projects/:slug/messages
|
||
GET /api/messages/:id
|
||
PATCH /api/messages/:id
|
||
DELETE /api/messages/:id
|
||
```
|
||
|
||
**Files:**
|
||
```
|
||
GET /api/projects/:slug/files
|
||
POST /api/projects/:slug/files
|
||
GET /api/files/:id
|
||
DELETE /api/files/:id
|
||
```
|
||
|
||
**Agents:**
|
||
```
|
||
GET /api/agents
|
||
GET /api/agents/:slug
|
||
POST /api/agents/:slug/tokens
|
||
DELETE /api/agents/:slug/tokens/:id
|
||
```
|
||
|
||
### 6.2 Structured Objects
|
||
|
||
**Mentions:**
|
||
```json
|
||
{
|
||
"mentions": [
|
||
{"id": "uuid", "slug": "coder", "name": "Кодер"}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Actor (MemberBrief):**
|
||
```json
|
||
{
|
||
"actor": {
|
||
"id": "uuid",
|
||
"slug": "coder",
|
||
"name": "Кодер"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. WebSocket
|
||
|
||
### 7.1 Подключение
|
||
|
||
```
|
||
wss://dev.team.uix.su/ws?token=JWT_TOKEN
|
||
```
|
||
|
||
### 7.2 События
|
||
|
||
**От сервера:**
|
||
```json
|
||
{
|
||
"type": "task_created",
|
||
"project_id": "uuid",
|
||
"data": {
|
||
"id": "uuid",
|
||
"title": "...",
|
||
"actor": {"id": "uuid", "slug": "coder", "name": "Кодер"}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Типы событий:**
|
||
- `task_created`
|
||
- `task_updated`
|
||
- `task_claimed`
|
||
- `task_completed`
|
||
- `message_created`
|
||
- `member_joined`
|
||
- `member_left`
|
||
|
||
### 7.3 Broadcast
|
||
|
||
**Принцип:** `broadcast_message` с фильтрацией по `member_id`.
|
||
|
||
```python
|
||
async def broadcast_message(
|
||
project_id: UUID,
|
||
event_type: str,
|
||
data: dict,
|
||
exclude_member_id: UUID | None = None
|
||
):
|
||
for member_id, websocket in active_connections.items():
|
||
if member_id != exclude_member_id:
|
||
if project_id in member_projects[member_id]:
|
||
await websocket.send_json({
|
||
"type": event_type,
|
||
"project_id": str(project_id),
|
||
"data": data
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Аутентификация
|
||
|
||
### 8.1 JWT
|
||
|
||
**Совместимость:** better-auth
|
||
|
||
**Token:**
|
||
```json
|
||
{
|
||
"sub": "member_uuid",
|
||
"exp": timestamp,
|
||
"iat": timestamp,
|
||
"type": "human|agent"
|
||
}
|
||
```
|
||
|
||
### 8.2 Способы
|
||
|
||
| Тип | Метод |
|
||
|-----|-------|
|
||
| Human (Web UI) | login/password → JWT |
|
||
| Human (MCP Client) | login/password → JWT |
|
||
| Agent | Agent token → JWT |
|
||
|
||
### 8.3 Agent Tokens
|
||
|
||
**Формат:** `tb-agent-{random_32_chars}`
|
||
|
||
**Хранение:** Хешированные в БД (как пароли)
|
||
|
||
**Использование:**
|
||
```python
|
||
# Agent connects with token
|
||
POST /api/agents/auth
|
||
{
|
||
"token": "tb-agent-xxx"
|
||
}
|
||
→ JWT token
|
||
```
|
||
|
||
---
|
||
|
||
## 9. AI-агенты
|
||
|
||
### 9.1 Picogent
|
||
|
||
**Архитектура:**
|
||
- Node.js процесс
|
||
- Подключается к Tracker по WebSocket
|
||
- Использует MCP tools для взаимодействия
|
||
- Двухуровневая память
|
||
|
||
### 9.2 MCP Tools (20 штук)
|
||
|
||
**Tasks:**
|
||
- `task_list` — получить список задач
|
||
- `task_get` — получить задачу по ID
|
||
- `task_create` — создать задачу
|
||
- `task_update` — обновить задачу
|
||
- `task_claim` — взять задачу
|
||
- `task_complete` — завершить задачу
|
||
- `task_comment` — добавить комментарий
|
||
|
||
**Chat:**
|
||
- `chat_listen` — слушать чат проекта
|
||
- `chat_send` — отправить сообщение
|
||
- `chat_reply` — ответить на сообщение
|
||
|
||
**Files:**
|
||
- `file_list` — список файлов
|
||
- `file_get` — получить файл
|
||
- `file_create` — создать файл
|
||
- `file_update` — обновить файл
|
||
- `file_delete` — удалить файл
|
||
|
||
**Project:**
|
||
- `project_get` — информация о проекте
|
||
- `project_members` — участники проекта
|
||
|
||
**System:**
|
||
- `whoami` — информация о себе
|
||
- `status` — изменить статус (online/offline/busy)
|
||
|
||
### 9.3 Память
|
||
|
||
**AGENT.md** — глобальная память агента:
|
||
```markdown
|
||
# Кодер
|
||
|
||
## Роль
|
||
Опытный разработчик, специализируется на Python, TypeScript.
|
||
|
||
## Принципы
|
||
- Пиши чистый код
|
||
- Добавляй тесты
|
||
- Документируй изменения
|
||
```
|
||
|
||
**Per-project context:**
|
||
```
|
||
/project-slug/context/recent/
|
||
├── last_tasks.md
|
||
├── decisions.md
|
||
└── architecture.md
|
||
```
|
||
|
||
### 9.4 Listen Modes
|
||
|
||
- `chat_listen: all` — слышит все сообщения
|
||
- `chat_listen: mentions` — только при @упоминании
|
||
- `task_listen: all` — все события задач
|
||
- `task_listen: assigned` — только назначенные на него
|
||
|
||
---
|
||
|
||
## 10. Фронтенд
|
||
|
||
### 10.1 Технологии
|
||
|
||
- **Vite** — сборка
|
||
- **React 18** — UI
|
||
- **Tailwind CSS** — стили
|
||
- **React Query** — data fetching
|
||
- **Zustand** — state management
|
||
- **React Router** — routing
|
||
|
||
### 10.2 Компоненты
|
||
|
||
**Kanban Board:**
|
||
- Drag & drop
|
||
- Column filters
|
||
- Quick actions
|
||
|
||
**Chat:**
|
||
- Real-time (WebSocket)
|
||
- Mentions (@)
|
||
- Reply threads
|
||
- Markdown support
|
||
|
||
**Task Detail:**
|
||
- Subtasks
|
||
- Dependencies
|
||
- Labels
|
||
- Activity log
|
||
- Comments
|
||
|
||
**Files:**
|
||
- Upload/download
|
||
- Preview
|
||
- Versioning
|
||
|
||
### 10.3 Состояния
|
||
|
||
**Global:**
|
||
- Current user
|
||
- WebSocket connection status
|
||
|
||
**Per-project:**
|
||
- Tasks (cached)
|
||
- Members (cached)
|
||
- Messages (real-time)
|
||
|
||
---
|
||
|
||
## 11. Инфраструктура
|
||
|
||
### 11.1 Сервер
|
||
|
||
**Host:** mail70.fvds.ru
|
||
**OS:** Ubuntu 24.04.3 LTS
|
||
|
||
### 11.2 Docker
|
||
|
||
```yaml
|
||
version: '3.8'
|
||
|
||
services:
|
||
tracker:
|
||
build: ./tracker
|
||
ports:
|
||
- "8100:8100"
|
||
environment:
|
||
DATABASE_URL: postgresql://teamboard:pass@db:5432/teamboard
|
||
JWT_SECRET: ${JWT_SECRET}
|
||
depends_on:
|
||
- db
|
||
|
||
db:
|
||
image: postgres:16
|
||
volumes:
|
||
- postgres_data:/var/lib/postgresql/data
|
||
environment:
|
||
POSTGRES_DB: teamboard
|
||
POSTGRES_USER: teamboard
|
||
POSTGRES_PASSWORD: pass
|
||
|
||
volumes:
|
||
postgres_data:
|
||
```
|
||
|
||
### 11.3 Systemd (Picogent)
|
||
|
||
```
|
||
[Unit]
|
||
Description=Picogent Agent
|
||
After=network.target
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=teamboard
|
||
WorkingDirectory=/opt/picogent
|
||
ExecStart=/usr/bin/node dist/index.js
|
||
Restart=on-failure
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
```
|
||
|
||
### 11.4 Nginx
|
||
|
||
См. раздел 4.4
|
||
|
||
---
|
||
|
||
## 12. Функциональность
|
||
|
||
### 12.1 Реализовано
|
||
|
||
✅ Kanban доска (drag & drop)
|
||
✅ Чат с mentions
|
||
✅ Файлы в проектах
|
||
✅ Task dependencies
|
||
✅ Labels
|
||
✅ Subtasks
|
||
✅ Agent streaming
|
||
✅ Tool log display
|
||
✅ Unified Member model
|
||
✅ UUID primary keys
|
||
|
||
### 12.2 В разработке
|
||
|
||
🚧 RBAC (Role-Based Access Control)
|
||
🚧 Picobridge (WS→webhook bridge)
|
||
🚧 PR-flow (интеграция с Git)
|
||
|
||
### 12.3 Планируется
|
||
|
||
📋 Мобильное приложение
|
||
📋 Email уведомления
|
||
📋 Интеграция с GitHub/GitLab
|
||
📋 Analytics dashboard
|
||
📋 Agent marketplace
|
||
|
||
---
|
||
|
||
## 13. Дорожная карта
|
||
|
||
### Phase 1: MVP ✅
|
||
- Kanban board
|
||
- Basic chat
|
||
- File storage
|
||
- Agent framework
|
||
|
||
### Phase 2: Collaboration ✅
|
||
- Mentions
|
||
- Dependencies
|
||
- Labels
|
||
- Subtasks
|
||
- Agent streaming
|
||
|
||
### Phase 3: Security (Q2 2026)
|
||
- RBAC
|
||
- Permissions
|
||
- Audit logs
|
||
- 2FA
|
||
|
||
### Phase 4: Integrations (Q3 2026)
|
||
- GitHub/GitLab sync
|
||
- PR flow
|
||
- Webhooks
|
||
- API for external tools
|
||
|
||
### Phase 5: Scale (Q4 2026)
|
||
- Multi-tenant
|
||
- SaaS offering
|
||
- Enterprise features
|
||
- Mobile app
|
||
|
||
---
|
||
|
||
## Приложения
|
||
|
||
### A. Ссылки
|
||
|
||
- **Production:** https://team.uix.su
|
||
- **Development:** https://dev.team.uix.su
|
||
- **Git:** https://git.uix.su/team-board
|
||
- **Docs:** https://git.uix.su/team-board/docs
|
||
|
||
### B. Контакты
|
||
|
||
- **Owner:** Eugene (@jexon)
|
||
- **Organization:** team-board
|
||
|
||
### C. Лицензия
|
||
|
||
MIT
|
||
|
||
---
|
||
|
||
## 14. Разработка и деплой
|
||
|
||
### 14.1 Локальная разработка
|
||
|
||
**Backend (Tracker):**
|
||
```bash
|
||
cd /root/projects/team-board/tracker
|
||
python -m venv venv
|
||
source venv/bin/activate
|
||
pip install -r requirements.txt
|
||
alembic upgrade head
|
||
uvicorn app.main:app --reload --port 8100
|
||
```
|
||
|
||
**Frontend:**
|
||
```bash
|
||
cd /root/projects/team-board/web-client-vite
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
**Agent (Picogent):**
|
||
```bash
|
||
cd /root/projects/team-board/picogent
|
||
npm install
|
||
npm run build
|
||
npm run start --config agent.json
|
||
```
|
||
|
||
### 14.2 Docker деплой
|
||
|
||
**Сборка:**
|
||
```bash
|
||
docker-compose build
|
||
```
|
||
|
||
**Запуск:**
|
||
```bash
|
||
docker-compose up -d
|
||
```
|
||
|
||
**Логи:**
|
||
```bash
|
||
docker-compose logs -f tracker
|
||
```
|
||
|
||
**Остановка:**
|
||
```bash
|
||
docker-compose down
|
||
```
|
||
|
||
### 14.3 Миграции БД
|
||
|
||
**Создание миграции:**
|
||
```bash
|
||
alembic revision --autogenerate -m "Add new table"
|
||
alembic upgrade head
|
||
```
|
||
|
||
**Откат:**
|
||
```bash
|
||
alembic downgrade -1
|
||
```
|
||
|
||
**История:**
|
||
```bash
|
||
alembic history
|
||
```
|
||
|
||
### 14.4 Переменные окружения
|
||
|
||
**Tracker (.env):**
|
||
```bash
|
||
DATABASE_URL=postgresql://user:pass@localhost:5432/teamboard
|
||
JWT_SECRET=your-secret-key
|
||
CORS_ORIGINS=https://dev.team.uix.su,https://team.uix.su
|
||
UPLOAD_DIR=/var/www/team-board/uploads
|
||
```
|
||
|
||
**Picogent (.env):**
|
||
```bash
|
||
TRACKER_URL=http://localhost:8100
|
||
AGENT_TOKEN=tb-agent-xxx
|
||
MODEL=claude-sonnet-4
|
||
```
|
||
|
||
---
|
||
|
||
## 15. Тестирование
|
||
### 15.1 Backend тесты
|
||
|
||
**Стек:** pytest, pytest-asyncio
|
||
|
||
**Структура:**
|
||
```
|
||
tests/
|
||
├── conftest.py
|
||
├── test_auth.py
|
||
├── test_tasks.py
|
||
├── test_projects.py
|
||
└── test_agents.py
|
||
```
|
||
|
||
**Пример:**
|
||
```python
|
||
import pytest
|
||
from httpx import AsyncClient
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_create_task(client: AsyncClient, auth_headers):
|
||
response = await client.post(
|
||
"/api/projects/test-project/tasks",
|
||
json={"title": "Test task"},
|
||
headers=auth_headers
|
||
)
|
||
assert response.status_code == 201
|
||
data = response.json()
|
||
assert data["title"] == "Test task"
|
||
```
|
||
|
||
**Запуск:**
|
||
```bash
|
||
pytest tests/ -v
|
||
pytest tests/ --cov=app
|
||
```
|
||
|
||
### 15.2 Frontend тесты
|
||
|
||
**Стек:** Vitest, React Testing Library
|
||
|
||
**Пример:**
|
||
```typescript
|
||
import { render, screen } from '@testing-library/react'
|
||
import { KanbanBoard } from './KanbanBoard'
|
||
|
||
test('renders kanban board', () => {
|
||
render(<KanbanBoard projectId="test-project" />)
|
||
expect(screen.getByText('To Do')).toBeInTheDocument()
|
||
})
|
||
```
|
||
|
||
**Запуск:**
|
||
```bash
|
||
npm run test
|
||
npm run test:coverage
|
||
```
|
||
|
||
### 15.3 E2E тесты
|
||
|
||
**Стек:** Playwright
|
||
|
||
**Пример:**
|
||
```typescript
|
||
test('user can create task', async ({ page }) => {
|
||
await page.goto('/projects/test-project')
|
||
await page.click('[data-testid="add-task"]')
|
||
await page.fill('[data-testid="task-title"]', 'New task')
|
||
await page.click('[data-testid="save-task"]')
|
||
await expect(page.locator('.task-card')).toBeVisible()
|
||
})
|
||
```
|
||
|
||
### 15.4 CI/CD
|
||
|
||
**GitHub Actions:**
|
||
```yaml
|
||
name: Tests
|
||
on: [push, pull_request]
|
||
|
||
jobs:
|
||
backend:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
- uses: actions/setup-python@v4
|
||
with:
|
||
python-version: '3.11'
|
||
- run: pip install -r requirements.txt
|
||
- run: pytest --cov=app
|
||
|
||
frontend:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
- uses: actions/setup-node@v3
|
||
with:
|
||
node-version: '20'
|
||
- run: npm ci
|
||
- run: npm run test
|
||
```
|
||
|
||
---
|
||
|
||
## 16. Производительность
|
||
|
||
### 16.1 Оптимизации БД
|
||
|
||
**Индексы:**
|
||
```sql
|
||
CREATE INDEX idx_tasks_project_status ON tasks(project_id, status);
|
||
CREATE INDEX idx_tasks_assignee ON tasks(assignee_id);
|
||
CREATE INDEX idx_messages_project_created ON messages(project_id, created_at DESC);
|
||
CREATE INDEX idx_members_slug ON members(slug);
|
||
```
|
||
|
||
**Connection pooling:**
|
||
```python
|
||
from sqlalchemy.ext.asyncio import create_async_engine
|
||
|
||
engine = create_async_engine(
|
||
DATABASE_URL,
|
||
pool_size=20,
|
||
max_overflow=10,
|
||
pool_pre_ping=True,
|
||
pool_recycle=3600
|
||
)
|
||
```
|
||
|
||
### 16.2 Кэширование
|
||
|
||
**Redis (будущее):**
|
||
```python
|
||
import aioredis
|
||
|
||
redis = await aioredis.create_redis_pool(REDIS_URL)
|
||
|
||
# Cache project tasks
|
||
async def get_cached_tasks(project_id: UUID):
|
||
cached = await redis.get(f"tasks:{project_id}")
|
||
if cached:
|
||
return json.loads(cached)
|
||
|
||
tasks = await fetch_tasks_from_db(project_id)
|
||
await redis.setex(f"tasks:{project_id}", 300, json.dumps(tasks))
|
||
return tasks
|
||
```
|
||
|
||
### 16.3 WebSocket масштабирование
|
||
|
||
**Текущее:** In-memory connections dict
|
||
|
||
**Будущее:** Redis Pub/Sub
|
||
```python
|
||
# Horizontal scaling with Redis
|
||
await redis.publish("ws:broadcast", json.dumps({
|
||
"project_id": str(project_id),
|
||
"event": event_type,
|
||
"data": data
|
||
}))
|
||
|
||
# Each worker subscribes
|
||
async def redis_subscriber():
|
||
pubsub = redis.pubsub()
|
||
await pubsub.subscribe("ws:broadcast")
|
||
async for message in pubsub.listen():
|
||
event = json.loads(message["data"])
|
||
await broadcast_to_local_connections(event)
|
||
```
|
||
|
||
### 16.4 Фронтенд оптимизации
|
||
|
||
**Code splitting:**
|
||
```typescript
|
||
const KanbanBoard = lazy(() => import('./KanbanBoard'))
|
||
const Chat = lazy(() => import('./Chat'))
|
||
```
|
||
|
||
**Bundle analysis:**
|
||
```bash
|
||
npm run build -- --mode analyze
|
||
```
|
||
|
||
**Asset optimization:**
|
||
- Image compression (WebP)
|
||
- Lazy loading images
|
||
- Service Worker для offline
|
||
|
||
---
|
||
|
||
## 17. Безопасность
|
||
|
||
### 17.1 JWT Security
|
||
|
||
**Token rotation:**
|
||
```python
|
||
# Short-lived access token (15 min)
|
||
access_token = create_access_token(user_id, expires_delta=timedelta(minutes=15))
|
||
|
||
# Long-lived refresh token (7 days)
|
||
refresh_token = create_refresh_token(user_id, expires_delta=timedelta(days=7))
|
||
```
|
||
|
||
**Token validation:**
|
||
```python
|
||
async def get_current_user(token: str = Depends(oauth2_scheme)):
|
||
try:
|
||
payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
|
||
if payload["exp"] < time.time():
|
||
raise HTTPException(401, "Token expired")
|
||
return await get_user(payload["sub"])
|
||
except jwt.InvalidTokenError:
|
||
raise HTTPException(401, "Invalid token")
|
||
```
|
||
|
||
### 17.2 Input Validation
|
||
|
||
**Pydantic schemas:**
|
||
```python
|
||
from pydantic import BaseModel, validator
|
||
|
||
class TaskCreate(BaseModel):
|
||
title: str
|
||
description: str | None
|
||
priority: Priority = Priority.medium
|
||
|
||
@validator('title')
|
||
def validate_title(cls, v):
|
||
if len(v) < 3:
|
||
raise ValueError('Title must be at least 3 characters')
|
||
if len(v) > 200:
|
||
raise ValueError('Title too long')
|
||
return v
|
||
```
|
||
|
||
### 17.3 SQL Injection Protection
|
||
|
||
**SQLAlchemy параметризованные запросы:**
|
||
```python
|
||
# ✅ Безопасно
|
||
tasks = await session.execute(
|
||
select(Task).where(Task.project_id == :project_id),
|
||
{"project_id": project_id}
|
||
)
|
||
|
||
# ❌ НЕБЕЗОПАСНО (никогда не использовать)
|
||
query = f"SELECT * FROM tasks WHERE project_id = '{project_id}'"
|
||
```
|
||
|
||
### 17.4 XSS Protection
|
||
|
||
**Frontend sanitization:**
|
||
```typescript
|
||
import DOMPurify from 'dompurify'
|
||
|
||
const SafeHTML = ({ content }) => (
|
||
<div dangerouslySetInnerHTML={{
|
||
__html: DOMPurify.sanitize(content)
|
||
}} />
|
||
)
|
||
```
|
||
|
||
### 17.5 Rate Limiting
|
||
|
||
**Slowapi:**
|
||
```python
|
||
from slowapi import Limiter
|
||
from slowapi.util import get_remote_address
|
||
|
||
limiter = Limiter(key_func=get_remote_address)
|
||
|
||
@app.post("/api/tasks")
|
||
@limiter.limit("10/minute")
|
||
async def create_task(request: Request, task: TaskCreate):
|
||
...
|
||
```
|
||
|
||
### 17.6 CORS
|
||
|
||
**Настройка:**
|
||
```python
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=[
|
||
"https://dev.team.uix.su",
|
||
"https://team.uix.su"
|
||
],
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## 18. Мониторинг и логирование
|
||
### 18.1 Структура логов
|
||
|
||
**Уровни:** DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||
|
||
**Формат:**
|
||
```python
|
||
import logging
|
||
import json
|
||
|
||
class JSONFormatter(logging.Formatter):
|
||
def format(self, record):
|
||
log_obj = {
|
||
"timestamp": record.created,
|
||
"level": record.levelname,
|
||
"logger": record.name,
|
||
"message": record.getMessage(),
|
||
"module": record.module,
|
||
"function": record.funcName,
|
||
}
|
||
if record.exc_info:
|
||
log_obj["exception"] = self.formatException(record.exc_info)
|
||
return json.dumps(log_obj)
|
||
|
||
logging.setFormatter(JSONFormatter())
|
||
```
|
||
|
||
### 18.2 Prometheus метрики
|
||
|
||
**Custom metrics:**
|
||
```python
|
||
from prometheus_client import Counter, Histogram
|
||
|
||
# Counters
|
||
tasks_created = Counter('tasks_created_total', 'Tasks created')
|
||
messages_sent = Counter('messages_sent_total', 'Messages sent')
|
||
agents_active = Counter('agents_active', 'Active agents')
|
||
|
||
# Histograms
|
||
request_duration = Histogram(
|
||
'http_request_duration_seconds',
|
||
'HTTP request duration',
|
||
['method', 'endpoint']
|
||
)
|
||
|
||
@app.middleware("http")
|
||
async def metrics_middleware(request: Request, call_next):
|
||
start = time.time()
|
||
response = await call_next(request)
|
||
duration = time.time() - start
|
||
|
||
request_duration.labels(request.method, request.url.path).observe(duration)
|
||
|
||
return response
|
||
```
|
||
|
||
### 18.3 Health Checks
|
||
|
||
**Endpoint:**
|
||
```python
|
||
@app.get("/api/health")
|
||
async def health_check():
|
||
checks = {
|
||
"database": await check_database(),
|
||
"redis": await check_redis() if REDIS_URL else "disabled",
|
||
"storage": check_storage(),
|
||
}
|
||
|
||
all_healthy = all(v == "ok" for v in checks.values())
|
||
status_code = 200 if all_healthy else 503
|
||
|
||
return JSONResponse(
|
||
content={"status": "healthy" if all_healthy else "unhealthy", "checks": checks},
|
||
status_code=status_code
|
||
)
|
||
```
|
||
|
||
### 18.4 Error Tracking
|
||
|
||
**Sentry интеграция:**
|
||
```python
|
||
import sentry_sdk
|
||
from sentry_sdk.integrations.fastapi import FastApiIntegration
|
||
|
||
sentry_sdk.init(
|
||
dsn=SENTRY_DSN,
|
||
integrations=[FastApiIntegration()],
|
||
traces_sample_rate=0.1,
|
||
environment=ENVIRONMENT,
|
||
)
|
||
```
|
||
|
||
### 18.5 Log Aggregation
|
||
|
||
**ELK Stack (будущее):**
|
||
- Filebeat → Logstash → Elasticsearch → Kibana
|
||
- Или: Loki + Grafana
|
||
|
||
---
|
||
|
||
## 19. Troubleshooting
|
||
### 19.1 Частые проблемы
|
||
|
||
**Проблема:** WebSocket отключается
|
||
```
|
||
Решение:
|
||
- Проверить nginx конфигурацию (proxy_read_timeout)
|
||
- Проверить keepalive настройки
|
||
- Клиент: reconnect logic
|
||
```
|
||
|
||
**Проблема:** Агент не видит задачи
|
||
```
|
||
Решение:
|
||
- Проверить agent token
|
||
- Проверить project membership
|
||
- Проверить listen mode (all vs mentions)
|
||
```
|
||
|
||
**Проблема:** Медленные запросы к БД
|
||
```
|
||
Решение:
|
||
- Проверить индексы
|
||
- Проверить connection pool
|
||
- Включить slow query log
|
||
- EXPLAIN ANALYZE запроса
|
||
```
|
||
|
||
### 19.2 Диагностика
|
||
|
||
**Проверка здоровья системы:**
|
||
```bash
|
||
# Backend
|
||
curl http://localhost:8100/api/health
|
||
|
||
# Database
|
||
psql -c "SELECT 1"
|
||
|
||
# Redis (если есть)
|
||
redis-cli ping
|
||
|
||
# Disk space
|
||
df -h /var/www/team-board
|
||
```
|
||
|
||
**Логи:**
|
||
```bash
|
||
# Tracker logs
|
||
docker-compose logs tracker | grep ERROR
|
||
|
||
# Nginx logs
|
||
tail -f /var/log/nginx/error.log
|
||
|
||
# System logs
|
||
journalctl -u picogent -f
|
||
```
|
||
|
||
**Database queries:**
|
||
```sql
|
||
-- Активные подключения
|
||
SELECT count(*) FROM pg_stat_activity;
|
||
|
||
-- Медленные запросы
|
||
SELECT query, calls, total_time
|
||
FROM pg_stat_statements
|
||
ORDER BY total_time DESC
|
||
LIMIT 10;
|
||
|
||
-- Размер таблиц
|
||
SELECT schemaname, tablename,
|
||
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
|
||
FROM pg_tables
|
||
WHERE schemaname = 'public'
|
||
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
|
||
```
|
||
|
||
### 19.3 Recovery процедуры
|
||
|
||
**Восстановление БД:**
|
||
```bash
|
||
# Backup
|
||
pg_dump teamboard > backup_$(date +%Y%m%d).sql
|
||
|
||
# Restore
|
||
psql teamboard < backup_20260313.sql
|
||
```
|
||
|
||
**Откат миграции:**
|
||
```bash
|
||
alembic downgrade -1
|
||
```
|
||
|
||
**Перезапуск сервисов:**
|
||
```bash
|
||
docker-compose restart tracker
|
||
sudo systemctl restart picogent
|
||
sudo systemctl reload nginx
|
||
```
|
||
|
||
---
|
||
|
||
## 20. Приложения
|
||
### D. Глоссарий
|
||
|
||
| Термин | Определение |
|
||
|--------|------------|
|
||
| **Tracker** | Единый backend-сервис |
|
||
| **Picogent** | AI-агент фреймворк |
|
||
| **Member** | Участник (человек или агент) |
|
||
| **Project** | Проект с задачами и чатом |
|
||
| **Task** | Задача на канбан-доске |
|
||
| **MCP** | Model Context Protocol |
|
||
| **BFF** | Backend for Frontend (не используется) |
|
||
|
||
### E. Changelog
|
||
|
||
**2026-03-13 (v1.1):**
|
||
- Добавлены разделы 14-19 (Deployment, Testing, Performance, Security, Monitoring, Troubleshooting)
|
||
- Расширена документация
|
||
- Aurora Edition
|
||
|
||
**2026-03-13 (v1.0):**
|
||
- Initial specification
|
||
- Все основные разделы
|
||
|
||
### F. Roadmap Details
|
||
|
||
**Q1 2026:**
|
||
- RBAC implementation
|
||
- Advanced permissions
|
||
- Audit logging
|
||
|
||
**Q2 2026:**
|
||
- GitHub/GitLab integration
|
||
- Webhooks
|
||
- External API
|
||
|
||
**Q3 2026:**
|
||
- Multi-tenant architecture
|
||
- SaaS preparation
|
||
- Enterprise features
|
||
|
||
**Q4 2026:**
|
||
- Mobile application (React Native)
|
||
- Offline support
|
||
- Push notifications
|
||
|
||
### G. API Response Examples
|
||
|
||
**GET /api/projects:**
|
||
```json
|
||
{
|
||
"projects": [
|
||
{
|
||
"id": "uuid",
|
||
"slug": "my-project",
|
||
"name": "My Project",
|
||
"description": "Description",
|
||
"color": "#3B82F6",
|
||
"archived": false,
|
||
"created_at": "2026-03-13T10:00:00Z",
|
||
"members_count": 5,
|
||
"tasks_count": 23
|
||
}
|
||
],
|
||
"total": 10,
|
||
"page": 1,
|
||
"per_page": 20
|
||
}
|
||
```
|
||
|
||
**POST /api/projects/my-project/tasks:**
|
||
```json
|
||
{
|
||
"id": "uuid",
|
||
"slug": "TASK-123",
|
||
"title": "Implement feature X",
|
||
"description": "Detailed description",
|
||
"status": "todo",
|
||
"priority": "high",
|
||
"assignee": null,
|
||
"labels": [
|
||
{"id": "uuid", "name": "feature", "color": "#10B981"}
|
||
],
|
||
"created_at": "2026-03-13T10:00:00Z",
|
||
"actor": {
|
||
"id": "uuid",
|
||
"slug": "coder",
|
||
"name": "Кодер"
|
||
}
|
||
}
|
||
```
|
||
|
||
### H. Database Schema (ER Diagram)
|
||
|
||
```
|
||
┌─────────────┐
|
||
│ Member │
|
||
├─────────────┤
|
||
│ id (PK) │
|
||
│ slug │
|
||
│ name │
|
||
│ type │
|
||
└──────┬──────┘
|
||
│
|
||
│ 1:N
|
||
│
|
||
┌──────┴──────────┐
|
||
│ ProjectMember │
|
||
├────────────────┤
|
||
│ project_id (FK)│
|
||
│ member_id (FK) │
|
||
│ role │
|
||
└──────┬──────────┘
|
||
│
|
||
│ N:1
|
||
│
|
||
┌──────┴──────────┐
|
||
│ Project │
|
||
├────────────────┤
|
||
│ id (PK) │
|
||
│ slug │
|
||
│ name │
|
||
└──────┬─────────┘
|
||
│
|
||
│ 1:N
|
||
│
|
||
┌──────┴──────────┐
|
||
│ Task │
|
||
├────────────────┤
|
||
│ id (PK) │
|
||
│ project_id (FK)│
|
||
│ assignee_id(FK)│
|
||
│ parent_id (FK) │
|
||
└────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
*Документ поддерживается Авророй. Последнее обновление: 2026-03-13*
|
||
*Версия: Aurora Edition 1.1*
|