docs/FILES-ARCHITECTURE.md

398 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Архитектура файловой системы Team Board
## Обзор
Team Board использует гибридную архитектуру для работы с файлами:
- **Project Files** — файлы проекта, управляемые через файловую систему
- **Attachments** — вложения к сообщениям и задачам, управляемые через БД + FS
## Структура директорий
```
${TEAM_BOARD_WORKSPACE}/
├── <project-slug>/
│ ├── files/ # Project files (FS-only)
│ │ ├── documents/
│ │ ├── images/
│ │ ├── code/
│ │ └── misc/
│ └── attachments/ # Message/task attachments (DB + FS)
│ ├── <uuid1>/
│ │ └── original_file.ext
│ ├── <uuid2>/
│ │ ├── original_file.ext
│ │ └── .thumbnails/
│ │ ├── 150x150.jpg
│ │ └── 300x300.jpg
│ └── ...
└── .quotas # Файл с квотами проектов
```
### Переменные окружения
```bash
TEAM_BOARD_WORKSPACE=/opt/team-board/workspace # Корневая директория
```
## Типы файлов
### 1. Project Files
**Назначение**: Рабочие файлы проекта, которые пользователи могут добавлять вручную
**Характеристики**:
- Хранение: только файловая система
- Обнаружение: directory listing при запросе API
- Управление: пользователь может добавить файлы руками в `workspace/<project-slug>/files/`
- Структура: свободная, пользователь организует как хочет
- Метаданные: извлекаются на лету (размер, дата модификации, MIME-type)
### 2. Attachments
**Назначение**: Вложения к сообщениям в чате и задачам
**Характеристики**:
- Хранение: PostgreSQL (метаданные) + файловая система (содержимое)
- Управление: только через API
- Именование: UUID-based, оригинальное имя в БД
- Привязка: к message_id или task_id
- Превью: генерация thumbnail'ов для изображений
## Модели базы данных
### Таблица attachments
```sql
CREATE TABLE attachments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
filename VARCHAR(255) NOT NULL, -- Оригинальное имя файла
content_type VARCHAR(100) NOT NULL, -- MIME-type
file_size BIGINT NOT NULL, -- Размер в байтах
file_hash SHA256 NOT NULL, -- SHA-256 хеш файла
storage_path VARCHAR(500) NOT NULL, -- Путь в FS (относительно workspace)
-- Привязки
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
message_id UUID REFERENCES messages(id) ON DELETE CASCADE,
task_id UUID REFERENCES tasks(id) ON DELETE CASCADE,
-- Превью
has_thumbnail BOOLEAN DEFAULT FALSE,
thumbnail_sizes JSONB, -- {"150x150": "path", "300x300": "path"}
-- Аудит
uploaded_by UUID NOT NULL REFERENCES users(id),
uploaded_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- Ограничения
CONSTRAINT attachment_belongs_to_something
CHECK (message_id IS NOT NULL OR task_id IS NOT NULL),
CONSTRAINT valid_file_size
CHECK (file_size > 0 AND file_size <= 52428800) -- 50MB limit
);
CREATE INDEX idx_attachments_project_id ON attachments(project_id);
CREATE INDEX idx_attachments_message_id ON attachments(message_id);
CREATE INDEX idx_attachments_task_id ON attachments(task_id);
CREATE INDEX idx_attachments_hash ON attachments(file_hash);
```
### Таблица project_quotas
```sql
CREATE TABLE project_quotas (
project_id UUID PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
-- Лимиты
max_total_size BIGINT DEFAULT 1073741824, -- 1GB по умолчанию
max_files_count INTEGER DEFAULT 1000,
-- Текущее использование
current_files_size BIGINT DEFAULT 0, -- Project files + attachments
current_attachments_size BIGINT DEFAULT 0, -- Только attachments
current_files_count INTEGER DEFAULT 0,
-- Обновления
last_scan_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```
## REST API Endpoints
### Project Files
#### `GET /api/v1/projects/{project_id}/files`
Список файлов проекта (directory scan)
**Параметры запроса**:
- `path` (optional) — подпапка, по умолчанию "/"
- `recursive` (optional) — рекурсивный обход, по умолчанию false
**Ответ**:
```json
{
"path": "/documents",
"files": [
{
"name": "spec.md",
"path": "/documents/spec.md",
"size": 15420,
"mime_type": "text/markdown",
"modified_at": "2024-01-15T10:30:00Z",
"is_directory": false
}
],
"quota": {
"used": 245760000,
"total": 1073741824,
"files_count": 156
}
}
```
#### `POST /api/v1/projects/{project_id}/files/upload`
Загрузка файла в проект
**Тело запроса**: multipart/form-data
- `file` — файл для загрузки
- `path` (optional) — путь размещения, по умолчанию "/"
**Ответ**:
```json
{
"success": true,
"file": {
"name": "document.pdf",
"path": "/documents/document.pdf",
"size": 1540000
}
}
```
#### `GET /api/v1/projects/{project_id}/files/download`
Скачивание файла проекта
**Параметры запроса**:
- `path` — путь к файлу
**Ответ**: binary data с соответствующими headers
#### `DELETE /api/v1/projects/{project_id}/files`
Удаление файла проекта
**Параметры запроса**:
- `path` — путь к файлу
### Attachments
#### `POST /api/v1/projects/{project_id}/attachments`
Загрузка вложения
**Тело запроса**: multipart/form-data
- `file` — файл для загрузки
- `message_id` (optional) — ID сообщения
- `task_id` (optional) — ID задачи
**Ответ**:
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"filename": "screenshot.png",
"content_type": "image/png",
"file_size": 245760,
"has_thumbnail": true,
"download_url": "/api/v1/attachments/550e8400-e29b-41d4-a716-446655440000/download",
"thumbnail_url": "/api/v1/attachments/550e8400-e29b-41d4-a716-446655440000/thumbnail/150x150"
}
```
#### `GET /api/v1/projects/{project_id}/attachments`
Список вложений проекта
**Параметры запроса**:
- `message_id` (optional) — фильтр по сообщению
- `task_id` (optional) — фильтр по задаче
- `limit` (optional, default=50) — количество записей
- `offset` (optional, default=0) — смещение
#### `GET /api/v1/attachments/{attachment_id}/download`
Скачивание вложения
#### `GET /api/v1/attachments/{attachment_id}/thumbnail/{size}`
Получение превью (sizes: 150x150, 300x300)
#### `DELETE /api/v1/attachments/{attachment_id}`
Удаление вложения
## MCP Tools для агентов
### upload_file
Загрузка файла в проект
**Параметры**:
- `project_id: str` — ID проекта
- `file_path: str` — локальный путь к файлу
- `target_path: str` — путь размещения в проекте (optional)
- `file_type: "project" | "attachment"` — тип файла
- `message_id: str` (optional, для attachment)
- `task_id: str` (optional, для attachment)
### download_file
Скачивание файла из проекта
**Параметры**:
- `project_id: str` — ID проекта
- `file_path: str` — путь к файлу в проекте (для project files)
- `attachment_id: str` — ID вложения (для attachments)
- `local_path: str` — куда сохранить локально
### list_files
Список файлов проекта
**Параметры**:
- `project_id: str` — ID проекта
- `file_type: "project" | "attachment" | "all"` — тип файлов
- `path: str` (optional) — подпапка для project files
- `recursive: bool` (optional) — рекурсивный обход
## Лимиты и квоты
### Лимиты на файл
- **Максимальный размер**: 50MB (52,428,800 байт)
- **Поддерживаемые типы**: все MIME-types, проверка по расширению
### Квоты на проект
- **Общий размер по умолчанию**: 1GB
- **Максимальное количество файлов**: 1000
- **Настройка**: через admin панель или ENV переменные
### Проверка квот
```python
def check_project_quota(project_id: str, additional_size: int) -> bool:
quota = get_project_quota(project_id)
current_usage = calculate_project_usage(project_id)
return (current_usage.total_size + additional_size) <= quota.max_total_size
```
## Превью изображений
### Поддерживаемые форматы
- PNG, JPEG, GIF, WebP
- SVG (конвертация в PNG)
### Размеры превью
- `150x150` — мелкая иконка
- `300x300` — средний размер для карточек
### Генерация
- **Lazy loading**: генерация при первом запросе
- **Кэширование**: сохранение в подпапке `.thumbnails/`
- **Библиотека**: Pillow (Python) или Sharp (Node.js)
```python
async def generate_thumbnail(attachment_id: str, size: str) -> str:
"""Генерирует превью для изображения"""
attachment = await get_attachment(attachment_id)
if not attachment.is_image():
raise ValueError("Not an image")
thumbnail_path = f"{attachment.storage_path}/.thumbnails/{size}.jpg"
if os.path.exists(thumbnail_path):
return thumbnail_path
# Генерируем превью
with Image.open(attachment.full_path) as img:
img.thumbnail((int(size.split('x')[0]), int(size.split('x')[1])))
os.makedirs(os.path.dirname(thumbnail_path), exist_ok=True)
img.save(thumbnail_path, "JPEG", quality=85)
# Обновляем БД
await update_attachment_thumbnail(attachment_id, size, thumbnail_path)
return thumbnail_path
```
## Примеры использования
### Пользователь добавляет файл вручную
```bash
# Пользователь копирует файл
cp /home/user/document.pdf ${TEAM_BOARD_WORKSPACE}/my-project/files/docs/
# API автоматически увидит файл при следующем запросе
curl -X GET "https://teamboard.com/api/v1/projects/123/files"
```
### Агент загружает вложение
```python
# MCP tool call
result = await upload_file(
project_id="123",
file_path="/tmp/screenshot.png",
file_type="attachment",
message_id="456"
)
```
### Получение превью
```bash
curl -X GET "https://teamboard.com/api/v1/attachments/550e8400-e29b-41d4-a716-446655440000/thumbnail/150x150" \
-H "Authorization: Bearer TOKEN" \
--output thumbnail.jpg
```
## Безопасность
### Валидация файлов
- Проверка MIME-type по содержимому файла (magic bytes)
- Запрет исполняемых файлов (.exe, .sh, .bat)
- Сканирование на вирусы (ClamAV integration)
### Изоляция
- Каждый проект изолирован в своей директории
- Запрет обхода пути (path traversal protection)
- Валидация имен файлов (запрет специальных символов)
### Доступ
- Аутентификация через JWT токены
- Авторизация на уровне проекта
- Rate limiting для upload операций
## Мониторинг
### Метрики
- Использование дискового пространства по проектам
- Скорость загрузки/скачивания файлов
- Количество генерированных превью
- Ошибки валидации файлов
### Логирование
- Все операции с файлами (upload, download, delete)
- Превышение квот
- Ошибки генерации превью
- Подозрительные файлы
---
## Техническая реализация
### FastAPI структура
```
app/
├── routers/
│ ├── project_files.py # /api/v1/projects/{id}/files/*
│ └── attachments.py # /api/v1/attachments/*
├── services/
│ ├── file_storage.py # Работа с FS
│ ├── thumbnail.py # Генерация превью
│ └── quota.py # Управление квотами
└── models/
├── attachment.py # SQLAlchemy модели
└── quota.py
```
### Background tasks
- Очистка неиспользуемых файлов
- Пересчет квот проектов
- Генерация превью в фоне
- Сжатие старых превью