From 8d7a7dfaf605c83e4f746ae481edded7eed6d7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Wed, 19 Aug 2026 14:47:16 +0400 Subject: [PATCH] =?UTF-8?q?v0.0.45:=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=B9=20API=20process=5Fnames=20=E2=80=94=20=D0=BE=D0=B1?= =?UTF-8?q?=D1=84=D1=83=D1=81=D0=BA=D0=B0=D1=86=D0=B8=D1=8F=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB=D0=BE=D0=B2=20=D0=BF=D0=BE=20=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=D0=BC=20(=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=BD=D0=BE?= =?UTF-8?q?=D0=BC=20TEST=5FINPUT=5FDIR)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- History/2026-08-19-process-names-api.md | 57 ++++++++++++++++++++++++ site/app.py | 2 +- site/routes/api_bp.py | 58 +++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 History/2026-08-19-process-names-api.md diff --git a/History/2026-08-19-process-names-api.md b/History/2026-08-19-process-names-api.md new file mode 100644 index 0000000..88c69e7 --- /dev/null +++ b/History/2026-08-19-process-names-api.md @@ -0,0 +1,57 @@ +# v0.0.45 — тестовый API process_names (обфускация по именам файлов) — 2026-08-19 + +**Дата:** 2026-08-19 +**Версия:** 0.0.44 → 0.0.45 + +--- + +## Суть + +Тестовый endpoint для curl: обфускация файлов по ИМЕНАМ из локальной папки +сервера (не таскать файлы каждый раз). + +## Использование + +```bash +# На сервере задана папка TEST_INPUT_DIR (env), в ней лежат входные файлы. +curl -X POST https://drhider.../api/process_names \ + -H "Content-Type: application/json" \ + -d '{"files": ["0144-03-2023_отчет об оценке.pdf", "test.pdf"]}' \ + -o result.zip +``` + +Ответ: ZIP с обфусцированными .md + mapping.csv. + +## Поведение (по требованию) + +- **Если `TEST_INPUT_DIR` не задан ИЛИ невалиден (не директория) — endpoint + возвращает 404** (как будто API нет). +- Имена файлов — только **basename** (защита от path traversal). +- Файл читается из `TEST_INPUT_DIR/`; если нет → 404. + +## Изменения + +### `site/routes/api_bp.py` +- `import os` добавлен. +- Глобальные `TEST_INPUT_DIR = os.environ.get("TEST_INPUT_DIR", "").strip()` + и `_TEST_API_ENABLED = bool(TEST_INPUT_DIR) and os.path.isdir(TEST_INPUT_DIR)`. +- Endpoint `POST /api/process_names`: + - 404 если `_TEST_API_ENABLED` false; + - читает JSON `{"files": [...]}`; + - basename + проверка файла в TEST_INPUT_DIR; + - `obfuscate_files(all_files, llm_client=LLMClient())`; + - возврат ZIP (`send_file`). + +### `site/app.py` +- `VERSION = "0.0.45"`. + +## Проверка +- Без `TEST_INPUT_DIR` → 404. +- С валидной папкой → 200, ZIP `['t.md', 'mapping.csv']`. +- Path traversal (`../../etc/passwd`) → 404 «файл не найден». +- Невалидная папка → 404. +- `py_compile` — OK. + +## Примечание +- Endpoint тестовый. На проде включать только при необходимости (задать TEST_INPUT_DIR). +- В тесте локально LLM недоступен (нет ключа) — «LLM NER failed» ожидаемо; на проде ключ есть. diff --git a/site/app.py b/site/app.py index dd0cf9e..775ea7a 100644 --- a/site/app.py +++ b/site/app.py @@ -20,7 +20,7 @@ if _sys_path_root not in sys.path: sys.path.insert(0, _sys_path_root) # Версия приложения (меняется при изменениях) -VERSION = "0.0.44" +VERSION = "0.0.45" def create_app(): diff --git a/site/routes/api_bp.py b/site/routes/api_bp.py index fa3198c..0e79148 100644 --- a/site/routes/api_bp.py +++ b/site/routes/api_bp.py @@ -7,9 +7,13 @@ Five endpoints: - POST /api/process/ — process all session files -> {status:"done"} (legacy) - GET /api/download/ — download ZIP (with timestamp name) - GET /api/csv/ — download CSV separately + +Тестовый (только при TEST_INPUT_DIR): +- POST /api/process_names — обфускация файлов по именам из TEST_INPUT_DIR """ import io +import os import json import queue import threading @@ -24,6 +28,10 @@ from session import (create_session, add_file, get_files, store_result, api_bp = Blueprint("api", __name__, url_prefix="/api") +# Тестовый API «по именам» доступен ТОЛЬКО если задана валидная папка TEST_INPUT_DIR. +TEST_INPUT_DIR = os.environ.get("TEST_INPUT_DIR", "").strip() +_TEST_API_ENABLED = bool(TEST_INPUT_DIR) and os.path.isdir(TEST_INPUT_DIR) + def _disconnect_exceptions(): """Исключения, означающие отключение клиента SSE.""" @@ -57,6 +65,56 @@ def upload(): return jsonify({"ok": True, "session": sid, "count": file_count(sid)}) +@api_bp.route("/process_names", methods=["POST"]) +def process_names(): + """Тестовый API: обфускация файлов ПО ИМЕНАМ из TEST_INPUT_DIR. + + Удобно для тестирования curl'ом (не таскать файлы заново). + + Доступен ТОЛЬКО если задан и валиден TEST_INPUT_DIR (env). + Иначе endpoint возвращает 404 — как будто API нет. + + Запрос: {"files": ["имя1.pdf", "имя2.pdf"]} (только имена, без путей) + Ответ: ZIP с обфусцированными .md + mapping.csv. + """ + if not _TEST_API_ENABLED: + return jsonify({"ok": False, "error": "Not found"}), 404 + + data = request.get_json(silent=True) or {} + names = data.get("files") or [] + if not isinstance(names, list) or not names: + return jsonify({"ok": False, "error": "files: список имён файлов"}), 400 + + all_files = [] + for name in names: + # Только basename — защита от path traversal + base = os.path.basename(str(name)) + if not base: + continue + path = os.path.join(TEST_INPUT_DIR, base) + if not os.path.isfile(path): + return jsonify({"ok": False, "error": f"файл не найден: {base}"}), 404 + with open(path, "rb") as fh: + all_files.append((base, fh.read(), "")) + + if not all_files: + return jsonify({"ok": False, "error": "нет файлов"}), 400 + + try: + llm = LLMClient() + zip_data, csv_str = obfuscate_files(all_files, llm_client=llm) + except Exception as e: + traceback.print_exc() + return jsonify({"ok": False, "error": str(e)}), 500 + + return send_file( + io.BytesIO(zip_data), + mimetype="application/zip", + as_attachment=True, + download_name="drhider_test.zip", + ) + + @api_bp.route("/process_stream/", methods=["GET"]) def process_stream(sid): """SSE: process all session files, streaming per-file progress.