diff --git a/FILES-ARCHITECTURE.md b/FILES-ARCHITECTURE.md new file mode 100644 index 0000000..1f54b00 --- /dev/null +++ b/FILES-ARCHITECTURE.md @@ -0,0 +1,398 @@ +# Архитектура файловой системы Team Board + +## Обзор + +Team Board использует гибридную архитектуру для работы с файлами: +- **Project Files** — файлы проекта, управляемые через файловую систему +- **Attachments** — вложения к сообщениям и задачам, управляемые через БД + FS + +## Структура директорий + +``` +${TEAM_BOARD_WORKSPACE}/ +├── / +│ ├── files/ # Project files (FS-only) +│ │ ├── documents/ +│ │ ├── images/ +│ │ ├── code/ +│ │ └── misc/ +│ └── attachments/ # Message/task attachments (DB + FS) +│ ├── / +│ │ └── original_file.ext +│ ├── / +│ │ ├── original_file.ext +│ │ └── .thumbnails/ +│ │ ├── 150x150.jpg +│ │ └── 300x300.jpg +│ └── ... +└── .quotas # Файл с квотами проектов +``` + +### Переменные окружения + +```bash +TEAM_BOARD_WORKSPACE=/opt/team-board/workspace # Корневая директория +``` + +## Типы файлов + +### 1. Project Files +**Назначение**: Рабочие файлы проекта, которые пользователи могут добавлять вручную + +**Характеристики**: +- Хранение: только файловая система +- Обнаружение: directory listing при запросе API +- Управление: пользователь может добавить файлы руками в `workspace//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 +- Очистка неиспользуемых файлов +- Пересчет квот проектов +- Генерация превью в фоне +- Сжатие старых превью \ No newline at end of file