14 KiB
Архитектура файловой системы 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 # Файл с квотами проектов
Переменные окружения
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
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
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
Ответ:
{
"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) — путь размещения, по умолчанию "/"
Ответ:
{
"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 задачи
Ответ:
{
"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 filesrecursive: bool(optional) — рекурсивный обход
Лимиты и квоты
Лимиты на файл
- Максимальный размер: 50MB (52,428,800 байт)
- Поддерживаемые типы: все MIME-types, проверка по расширению
Квоты на проект
- Общий размер по умолчанию: 1GB
- Максимальное количество файлов: 1000
- Настройка: через admin панель или ENV переменные
Проверка квот
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)
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
Примеры использования
Пользователь добавляет файл вручную
# Пользователь копирует файл
cp /home/user/document.pdf ${TEAM_BOARD_WORKSPACE}/my-project/files/docs/
# API автоматически увидит файл при следующем запросе
curl -X GET "https://teamboard.com/api/v1/projects/123/files"
Агент загружает вложение
# MCP tool call
result = await upload_file(
project_id="123",
file_path="/tmp/screenshot.png",
file_type="attachment",
message_id="456"
)
Получение превью
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
- Очистка неиспользуемых файлов
- Пересчет квот проектов
- Генерация превью в фоне
- Сжатие старых превью