From 38e54f0ab2ebc8e083c1b0a825056a63f69f5696 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 20 Sep 2025 20:21:49 +0000 Subject: [PATCH] nice version --- app/api/routes/content.py | 2 +- app/api/routes/upload_tus.py | 23 +++++++++++++++++++---- app/core/background/convert_v3_service.py | 8 ++++---- app/core/crypto/aes_siv_stream.py | 6 ++---- app/core/ipfs_client.py | 13 ++++++++++++- 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/app/api/routes/content.py b/app/api/routes/content.py index d758e02..5562cc5 100644 --- a/app/api/routes/content.py +++ b/app/api/routes/content.py @@ -28,7 +28,7 @@ async def s_api_v1_content_list(request): select(StoredContent) .where( StoredContent.type.like(store + '%'), - StoredContent.disabled == False + StoredContent.disabled.is_(None) ) .order_by(StoredContent.created.desc()) .offset(offset) diff --git a/app/api/routes/upload_tus.py b/app/api/routes/upload_tus.py index d7d4af6..9fcbcbb 100644 --- a/app/api/routes/upload_tus.py +++ b/app/api/routes/upload_tus.py @@ -27,14 +27,29 @@ async def s_api_v1_upload_tus_hook(request): tusd HTTP hook endpoint. We mainly handle post-finish to: encrypt -> IPFS add+pin -> record DB. """ try: - payload: Dict[str, Any] = request.json or {} + payload: Dict[str, Any] = request.json except Exception: - payload = {} - event = payload.get("Type") or payload.get("type") or payload.get("Event") or payload.get("event") + payload = None + if payload is None: + raw_body = request.body or b'' + try: + payload = json.loads(raw_body) if raw_body else {} + except Exception: + payload = {} + event = (payload.get("Type") or payload.get("type") or + payload.get("Event") or payload.get("event") or + payload.get("Hook") or payload.get("hook") or + payload.get("HookName") or payload.get("hook_name") or + request.headers.get("Hook-Name") or request.headers.get("hook-name")) upload = payload.get("Upload") or payload.get("upload") or {} if not event: - return response.json({"ok": False, "error": "NO_EVENT"}, status=400) + hook_name = (payload.get("HookName") or payload.get("hook") or + payload.get("hook_name") or request.headers.get("Hook-Name")) + raw = request.body or b'' + preview = raw[:512] + make_log("tus-hook", f"Missing event type in hook payload; ignoring (hook={hook_name}, keys={list(payload.keys())}, raw={preview!r})", level="warning") + return response.json({"ok": True, "skipped": True}) if event not in ("post-finish", "postfinish"): # accept but ignore other events diff --git a/app/core/background/convert_v3_service.py b/app/core/background/convert_v3_service.py index 09cd1fb..0ac26e6 100644 --- a/app/core/background/convert_v3_service.py +++ b/app/core/background/convert_v3_service.py @@ -3,7 +3,7 @@ import os import json import shutil from datetime import datetime -from typing import List, Tuple +from typing import List, Tuple, Optional from sqlalchemy import select @@ -57,7 +57,7 @@ async def _save_derivative(file_path: str, filename: str) -> Tuple[str, int]: return file_hash, size -async def _run_media_converter(input_host_path: str, input_ext: str, quality: str, trim_value: str | None, is_audio: bool) -> Tuple[str, dict]: +async def _run_media_converter(input_host_path: str, input_ext: str, quality: str, trim_value: Optional[str], is_audio: bool) -> Tuple[str, dict]: rid = __import__('uuid').uuid4().hex[:8] output_dir_container = f"/tmp/conv_{rid}" output_dir_host = f"/tmp/conv_{rid}" @@ -215,7 +215,7 @@ async def _pick_pending(limit: int) -> List[Tuple[EncryptedContent, str]]: if required.issubset(kinds_ready): continue # Always decrypt from IPFS using local or remote key - storage_path: str | None = None + storage_path: Optional[str] = None ck = (await session.execute(select(ContentKey).where(ContentKey.content_id == ec.id))).scalars().first() if ck: storage_path = await stage_plain_from_ipfs(ec, ck.key_ciphertext_b64) @@ -285,7 +285,7 @@ async def main_fn(memory): await worker_loop() -async def stage_plain_from_ipfs(ec: EncryptedContent, dek_wrapped: str) -> str | None: +async def stage_plain_from_ipfs(ec: EncryptedContent, dek_wrapped: str) -> Optional[str]: """Download encrypted ENCF stream from IPFS and decrypt on the fly into a temp file.""" import tempfile try: diff --git a/app/core/crypto/aes_siv_stream.py b/app/core/crypto/aes_siv_stream.py index 1e08d9e..4de3bd7 100644 --- a/app/core/crypto/aes_siv_stream.py +++ b/app/core/crypto/aes_siv_stream.py @@ -4,7 +4,6 @@ import os import struct from typing import BinaryIO, Iterator, AsyncIterator -from Crypto.Cipher import SIV from Crypto.Cipher import AES @@ -62,7 +61,7 @@ def encrypt_file_to_encf(src: BinaryIO, key: bytes, chunk_bytes: int, salt: byte block = src.read(chunk_bytes) if not block: break - siv = SIV.new(key=key, ciphermod=AES) # new object per message + siv = AES.new(key, AES.MODE_SIV) # new object per message siv.update(_ad(salt, idx)) ciph, tag = siv.encrypt_and_digest(block) yield struct.pack(">I", len(block)) @@ -76,7 +75,6 @@ async def decrypt_encf_to_file(byte_iter: AsyncIterator[bytes], key: bytes, out_ Parse ENCF v1 stream from async byte iterator and write plaintext to out_path. """ import aiofiles - from Crypto.Cipher import SIV as _SIV from Crypto.Cipher import AES as _AES buf = bytearray() @@ -129,7 +127,7 @@ async def decrypt_encf_to_file(byte_iter: AsyncIterator[bytes], key: bytes, out_ c = bytes(buf[:p_len]) t = bytes(buf[p_len:p_len+TAG_LEN]) del buf[:p_len+TAG_LEN] - siv = _SIV.new(key=key, ciphermod=_AES) + siv = _AES.new(key, _AES.MODE_SIV) siv.update(_ad(salt, idx)) p = siv.decrypt_and_verify(c, t) await out.write(p) diff --git a/app/core/ipfs_client.py b/app/core/ipfs_client.py index 11cd3ad..7610af5 100644 --- a/app/core/ipfs_client.py +++ b/app/core/ipfs_client.py @@ -26,8 +26,19 @@ async def add_streamed_file(stream_iter: Iterable[bytes], filename: str = "file. } q = {**default_params, **params} + class _StreamAdapter: + def __init__(self, iterable): + self._iter = iter(iterable) + + def read(self, size=-1): + try: + return next(self._iter) + except StopIteration: + return b'' + + stream = _StreamAdapter(stream_iter) async with httpx.AsyncClient(timeout=None) as client: - files = {"file": (filename, stream_iter, "application/octet-stream")} + files = {"file": (filename, stream, "application/octet-stream")} r = await client.post(f"{IPFS_API_URL}/api/v0/add", params=q, files=files) r.raise_for_status() # /add may emit NDJSON lines; most often single JSON