# Upload Platform Модульная платформа загрузки и транзита документов (версия `0.2.0`). Состоит из двух независимых автономных слоёв с чистыми API: 1. **Слой 1 (File Picker)**: Клиентский выбор файлов, папок и вложенных ZIP-архивов с дедупликацией, фильтрацией по расширениям, проверкой лимитов и визуализацией в виде дерева. 2. **Слой 2 (Transit Upload via VM Buffer)**: Пофайловый потоковый транзит файлов из браузера в оперативную память (RAM) сессии бэкенда через внешний WebDAV-буфер на ВМ. Решает проблему ограничения Ingress Kubernetes на входящий payload (64 КБ) за счёт исходящего pull из кластера. --- ## Архитектура слоёв и API ``` [ Браузер ] │ ├─► Слой 1: initFilePicker() ──► picker.getFiles() -> File[] │ └─► Слой 2: uploadViaVM(files, { vmUploadUrl, backendUploadUrl }) │ ├─ 1. PUT файл k ──► [ ВМ-буфер (RAM) ] │ ▲ └─ 2. POST /api/upload_refs│ │ │ ▼ │ [ Flask Backend ] ────┘ 3. Потоковый GET (pull) в RAM │ ├─► 4. DELETE файл k с ВМ └─► 5. session.add_file(sid, name, bytes) │ ▼ [ Слой 3: drhider / contractor ] ``` ### Слой 1: File Picker API - **Вход**: DOM-контейнер (`mount`), разрешённые расширения (`allowedExt`), лимиты (`limits`). - **Выход**: `picker.getFiles()` возвращает плоский массив объектов `{ path, name, size, file }`. - **Автономность**: Не зависит от транспорта и бэкенда. ### Слой 2: Transit Upload API - **Фронтенд**: функция `uploadViaVM(files, options)`: - `vmUploadUrl`: базовый URL буфера на ВМ (Zero Hardcode). - `backendUploadUrl`: эндпоинт приёма ссылок (`/api/upload_refs`). - `signal`: `AbortSignal` для отмены загрузки. - `onProgress`: колбэк прогресса загрузки текущего файла. - `onFileStatus`: колбэк статуса строки файла. - `onFileComplete`: колбэк завершения переноса файла в сессию бэкенда. - **Бэкенд**: Flask Blueprint `create_upload_refs_blueprint(cfg)`: - Принимает ссылку на один файл (пофайловый транзит). - Проверяет URL на доверенный префикс (`vmUploadPrefix`, защита от SSRF). - Санитизирует имя файла (`safe_name`, защита от path traversal). - Вытягивает байты потоком исходящим `GET` (ретраи, таймауты) прямо в RAM. - Немедленно удаляет файл из буфера на ВМ (`DELETE`). - Сохраняет файл в RAM сессии (`upload/backend/session`). - При необходимости вызывает `onFileReceived(sid, name, content)` для передачи в Слой 3. --- ## Структура репозитория ``` upload-platform/ ├── upload/ # Переиспользуемый встраиваемый модуль │ ├── frontend/ │ │ ├── index.js # Точка входа фронтенда (initFilePicker, uploadViaVM, putToVm) │ │ ├── table/ # UI и дерево файлов (Слой 1) │ │ ├── zip/ # Распаковка архивов fflate (Слой 1) │ │ └── upload/ # Транспорт Слоя 2: │ │ ├── put_to_vm.js # XHR PUT одного файла на буфер с прогрессом и abort │ │ └── upload_via_vm.js # Пофайловый транзит (PUT -> upload_refs -> repeat) │ └── backend/ # Серверная часть Слоя 2: │ ├── upload_refs/ │ │ ├── blueprint.py # Flask Blueprint (POST /api/upload_refs) │ │ ├── pull_file.py # Исходящий потоковый GET с ретраями (httpx) │ │ ├── safe_name.py # Защита от path traversal │ │ └── config.py # Дефолтные параметры │ └── session/ │ ├── state.py # In-memory хранилище сессий (_sessions, TTL, lock) │ ├── create_session.py # Создание сессии (UUID) │ ├── add_file.py # Добавление файла в RAM с проверкой лимита │ ├── get_files.py # Чтение файлов сессии │ ├── store_result.py # Хранение результирующего ZIP │ ├── store_csv.py # Хранение CSV │ ├── ttl.py # touch, pause_ttl, resume_ttl │ ├── cancel.py # Мягкая отмена сессии (threading.Event) │ └── cleanup.py # Удаление сессии ├── site/ # Тестовый стенд и demo-сервер │ ├── app.py # Flask demo + встроенный RAM mock WebDAV буфер │ ├── templates/index.html # Demo UI со сквозным сценарием выбора и загрузки │ └── static/style.css ├── dist/ # Готовые бандлы (собираются через build.mjs) │ ├── file-picker.esm.js │ └── file-picker.iife.js ├── tests/ # Автоматические тесты (100% offline) │ ├── test_safe_name.py # Тесты санитизации путей │ ├── test_session.py # Тесты RAM-хранилища сессий и лимитов │ ├── test_upload_refs.py # Тесты Blueprint, SSRF-защиты и pull │ ├── test_app_integration.py # Сквозной интеграционный тест site/app.py │ └── test_upload_layer2.test.mjs # Юнит-тесты фронтенда Слоя 2 (node:test) ├── build.mjs # esbuild сборщик ├── package.json # Версия 0.2.0, npm-скрипты └── requirements.txt # Python зависимости (Flask, httpx, pytest) ``` --- ## Быстрый старт и запуск ### 1. Установка зависимостей ```bash pip install -r requirements.txt npm install ``` ### 2. Сборка фронтенд-бандлов ```bash npm run build ``` ### 3. Запуск локального demo-сервера ```bash python site/app.py # Сервер доступен по адресу http://127.0.0.1:5000/ ``` В стенде встроен локальный **in-memory mock-буфер** (`/mock-buffer/`), поэтому demo полностью функционально локально без подключения к боевой ВМ. ### 4. Запуск всех тестов ```bash # Бэкенд тесты (pytest) pytest tests/ -v # Фронтенд тесты (Node.js test runner) npm test ``` --- ## Инструкция: интеграция в сторонний сервис за 2 минуты Чтобы встроить оба слоя в целевой сервис (`drhider`, `contractor` и др.): ### 1. Скопировать модуль Скопировать директорию `upload/` в корень целевого проекта: ```bash cp -r upload-platform/upload/ /path/to/service/upload/ ``` ### 2. Подключить бэкенд во Flask В файле создания Flask-приложения (например, `app.py`): ```python from upload.backend.upload_refs import create_upload_refs_blueprint from upload.backend.session import get_files upload_bp = create_upload_refs_blueprint({ "apiPrefix": "/api", "vmUploadPrefix": "https://contracts.kube5s.ru/drhider-upload/", # Префикс вашего буфера "maxFileBytes": 50 * 1024 * 1024, "maxSessionBytes": 500 * 1024 * 1024, }) app.register_blueprint(upload_bp) ``` ### 3. Подключить фронтенд В шаблоне страницы: ```html
``` ### 4. Получение файлов в Слое 3 Бизнес-логика получает файлы из оперативной памяти: ```python files = get_files(session_id) # files: [("document.pdf", b"...bytes..."), ...] ``` Никаких временных файлов на диске. Полная безопасность и изоляция.