docs: files & attachments architecture
This commit is contained in:
parent
300c6fa98e
commit
14cea83750
398
FILES-ARCHITECTURE.md
Normal file
398
FILES-ARCHITECTURE.md
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
# Архитектура файловой системы 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
|
||||||
|
- Очистка неиспользуемых файлов
|
||||||
|
- Пересчет квот проектов
|
||||||
|
- Генерация превью в фоне
|
||||||
|
- Сжатие старых превью
|
||||||
Loading…
Reference in New Issue
Block a user