# Архитектура файловой системы 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 - Очистка неиспользуемых файлов - Пересчет квот проектов - Генерация превью в фоне - Сжатие старых превью