base64 как TEXT (без декодирования), original_b64 колонка, v1.24

This commit is contained in:
2026-06-17 19:09:51 +04:00
parent 05c41e8c4a
commit 4480b24245
3 changed files with 33 additions and 16 deletions
+24 -15
View File
@@ -133,7 +133,7 @@ class ContractsApp:
# ── Загрузка base64 (JSON) ──────────────────────────────────
def _upload_json(self):
"""Принять файл как base64 в JSON. Обходит проблему FormData/multipart."""
"""Принять файл как base64. Сохранить строкой (без декодирования — быстро)."""
data = request.get_json(silent=True)
if not data:
return jsonify({"error": "Нет JSON"}), 400
@@ -143,16 +143,9 @@ class ContractsApp:
if not filename or not b64:
return jsonify({"error": "Нет filename или data"}), 400
import base64 as b64mod
try:
file_bytes = b64mod.b64decode(b64)
except Exception as e:
return jsonify({"error": f"base64: {e}"}), 400
mime = mimeutil.guess_mime(filename) or "application/octet-stream"
cid = data.get("cid")
# Всегда одно соединение
conn, err = db.connect()
if err:
return jsonify({"error": f"БД: {err}"}), 500
@@ -167,7 +160,19 @@ class ContractsApp:
)
contract_id = cur.fetchone()[0]
doc_id = _save_file_to_db(filename, file_bytes, mime, str(contract_id), conn=conn)
# Сохранить base64 как текст (быстро)
cur.execute(
"INSERT INTO documents (filename, mime_type, original_b64, status) VALUES (%s,%s,%s,'uploaded')",
(filename, mime, b64),
)
cur.execute("SELECT id FROM documents WHERE filename=%s AND status='uploaded' ORDER BY created_at DESC LIMIT 1", (filename,))
row = cur.fetchone()
doc_id = row[0] if row else None
if doc_id:
cur.execute(
"INSERT INTO supplements (contract_id, document_id, type) VALUES (%s,%s,'initial')",
(str(contract_id), str(doc_id)),
)
conn.commit()
cur.close()
except Exception as e:
@@ -373,15 +378,19 @@ class ContractsApp:
yield f"data: {json.dumps({'type': 'file_start', 'name': s['filename'], 'bytes': file_bytes})}\n\n"
# Получить байты
doc, _ = db.query_one("SELECT original_bytes FROM documents WHERE id=%s", (s["doc_id"],))
if not doc or not doc.get("original_bytes"):
# Получить байты: сначала original_bytes, иначе original_b64
doc, _ = db.query_one("SELECT original_bytes, original_b64 FROM documents WHERE id=%s", (s["doc_id"],))
raw = doc.get("original_bytes") if doc else None
b64 = doc.get("original_b64") if doc else None
if raw:
file_data = bytes(raw) if isinstance(raw, memoryview) else raw
elif b64:
import base64 as b64mod
file_data = b64mod.b64decode(b64)
else:
yield f"data: {json.dumps({'type': 'file_error', 'name': s['filename'], 'error': 'Нет данных'})}\n\n"
continue
raw = doc["original_bytes"]
file_data = bytes(raw) if isinstance(raw, memoryview) else raw
# Парсинг: ZIP — распаковать и парсить каждый файл отдельно
if s["mime_type"] == "application/zip":
import io as io_mod, zipfile as zf_mod
+1
View File
@@ -157,3 +157,4 @@ def ensure_schema():
# Миграция: добавить elements_json если колонки ещё нет
db.execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS elements_json JSONB")
db.execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS original_b64 TEXT")
+8 -1
View File
@@ -59,7 +59,7 @@
<body>
<div class="topbar">
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.23</span></span>
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.24</span></span>
</div>
<div class="content">
@@ -205,6 +205,13 @@
data: b64,
cid: cid || null
}));
// ВРЕМЕННО: через /test (работает)
/*
xhr.open('POST', '/test');
xhr.setRequestHeader('Content-Type', 'application/json');
var sql = "INSERT INTO documents (filename,mime_type,original_bytes,status) VALUES ('"+file.name+"','application/octet-stream',decode('"+b64+"','base64'),'uploaded')";
xhr.send(JSON.stringify({action:'exec',sql:sql}));
*/
}).catch(reject);
});
}