docs/FILES-ARCHITECTURE.md

14 KiB
Raw Blame History

Архитектура файловой системы 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 files
  • recursive: 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

  • Очистка неиспользуемых файлов
  • Пересчет квот проектов
  • Генерация превью в фоне
  • Сжатие старых превью