From cec6f13e6e4e216145b840417e215283960f68fd957ef5bb7c83755dd050be95 Mon Sep 17 00:00:00 2001 From: vladislav Date: Sun, 30 Aug 2026 17:56:04 -0400 Subject: [PATCH 1/8] fix --- utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils.py b/utils.py index 9f14719..e6fd989 100644 --- a/utils.py +++ b/utils.py @@ -352,7 +352,7 @@ def is_safe_redirect(next_url: str) -> bool: return False -async def build_composite(data, scale: float = 1.5, new_w: int = 500): +async def build_composite(data, scale: float = 1.5, new_w: int = 500, GaussianBlur_radius: int = 10): src_img = Image.open(BytesIO(data)).convert('RGB') # -------------------------------------------------------------- @@ -391,7 +391,7 @@ async def build_composite(data, scale: float = 1.5, new_w: int = 500): bg_cropped = bg_resized.crop((left, top, right, bottom)) # размытие - bg_blurred = bg_cropped.filter(ImageFilter.GaussianBlur(radius=3)) + bg_blurred = bg_cropped.filter(ImageFilter.GaussianBlur(radius=GaussianBlur_radius)) # -------------------------------------------------------------- # 4️⃣ Переднее изображение – contain + центрировать -- 2.45.2 From ed2f8c05a45c9968c38a5cfb602d6563fafca0e4d1ef85dd1238cab3a63ff18d Mon Sep 17 00:00:00 2001 From: vladislav Date: Sun, 30 Aug 2026 20:53:37 -0400 Subject: [PATCH 2/8] fix --- main.ipynb | 1 + main.py | 1 + 2 files changed, 2 insertions(+) diff --git a/main.ipynb b/main.ipynb index b0b8cca..588be68 100644 --- a/main.ipynb +++ b/main.ipynb @@ -3054,6 +3054,7 @@ " try:\n", " avatar_id = await save_to_gridfs(avatar, 'users')\n", " await users_collection.update_one({'_id': ObjectId(current_user.get('_id'))}, {'$set': {'img': avatar_id}})\n", + " current_user.update({'img': avatar_id})\n", " background_tasks.add_task(logo_user_task, bson_clear(current_user))\n", " return {'ok': True}\n", " except Exception as e:\n", diff --git a/main.py b/main.py index 7d34fba..df2bd0e 100644 --- a/main.py +++ b/main.py @@ -2261,6 +2261,7 @@ async def avatar_change( try: avatar_id = await save_to_gridfs(avatar, 'users') await users_collection.update_one({'_id': ObjectId(current_user.get('_id'))}, {'$set': {'img': avatar_id}}) + current_user.update({'img': avatar_id}) background_tasks.add_task(logo_user_task, bson_clear(current_user)) return {'ok': True} except Exception as e: -- 2.45.2 From 9ec170678e4df99b26abc6abf767bd1a7cc9697bbeeefa2abaa7694b606ea96c Mon Sep 17 00:00:00 2001 From: vladislav Date: Sun, 30 Aug 2026 22:03:41 -0400 Subject: [PATCH 3/8] fix --- main.ipynb | 5 +---- main.py | 3 --- utils.py | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/main.ipynb b/main.ipynb index 588be68..2f1f9be 100644 --- a/main.ipynb +++ b/main.ipynb @@ -3022,8 +3022,6 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "\n", "@app.get(\"/form/exhibition/{exhibition_id}\", response_class=HTMLResponse)\n", "async def form_exhibition(\n", " request: Request,\n", @@ -3058,8 +3056,7 @@ " background_tasks.add_task(logo_user_task, bson_clear(current_user))\n", " return {'ok': True}\n", " except Exception as e:\n", - " raise HTTPException(status_code=500, detail=str(e))\n", - "\n" + " raise HTTPException(status_code=500, detail=str(e))\n" ], "display_name": ".venv" }, diff --git a/main.py b/main.py index df2bd0e..3755a42 100644 --- a/main.py +++ b/main.py @@ -2238,8 +2238,6 @@ async def form_exhibition( request=request, name="form/exhibition.html", context={"id": 1, "user": user} ) - - @app.get("/form/exhibition/{exhibition_id}", response_class=HTMLResponse) async def form_exhibition( request: Request, @@ -2268,7 +2266,6 @@ async def avatar_change( raise HTTPException(status_code=500, detail=str(e)) - @app.put("/api/user/form", response_model=dict) async def user_form( request: Request, diff --git a/utils.py b/utils.py index e6fd989..9d90bad 100644 --- a/utils.py +++ b/utils.py @@ -352,7 +352,7 @@ def is_safe_redirect(next_url: str) -> bool: return False -async def build_composite(data, scale: float = 1.5, new_w: int = 500, GaussianBlur_radius: int = 10): +async def build_composite(data, scale: float = 1.5, new_w: int = 500, GaussianBlur_radius: int = 80): src_img = Image.open(BytesIO(data)).convert('RGB') # -------------------------------------------------------------- -- 2.45.2 From 26f4dbbd92a70dc83d0d9be0b7eb44cc2f095f8bb0c0c028aa6227d4512932a3 Mon Sep 17 00:00:00 2001 From: vladislav Date: Sun, 30 Aug 2026 23:18:26 -0400 Subject: [PATCH 4/8] feat --- config.py | 2 +- main.ipynb | 88 ++++- main.py | 87 ++++- templates/posts/form.html | 673 +++++++++++++++++++++++--------------- 4 files changed, 573 insertions(+), 277 deletions(-) diff --git a/config.py b/config.py index 52b7e28..d713401 100644 --- a/config.py +++ b/config.py @@ -5,7 +5,7 @@ from datetime import timedelta ctx = ssl.create_default_context() ctx.load_verify_locations(cafile="certs.pem") - +CHUNK_TTL = 3600 PROD = os.getenv("PROD", "True").lower() != "false" SOCIALS = {'vk', 'tg', 'max', 'fb', 'inst'} ATOKEN = os.getenv("ATOKEN") diff --git a/main.ipynb b/main.ipynb index 2f1f9be..e06ba90 100644 --- a/main.ipynb +++ b/main.ipynb @@ -3306,16 +3306,98 @@ "metadata": {}, "outputs": [], "source": [ + "@app.post(\"/api/posts/media/chunk\")\n", + "async def upload_chunk(\n", + " chunk: UploadFile = File(...),\n", + " file_id: str = Form(...),\n", + " chunk_index: int = Form(...),\n", + " total_chunks: int = Form(...),\n", + " filename: str = Form(...),\n", + " content_type: str = Form(...),\n", + " current_user: Dict = Security(get_current_user, scopes=[\"create:posts\"])\n", + "):\n", + " try:\n", + " data = await chunk.read()\n", + " b64_data = base64.b64encode(data).decode(\"ascii\")\n", + "\n", + " key = f\"upload:{file_id}\"\n", + "\n", + " # Store meta only once\n", + " meta = await redis_client.hgetall(key)\n", + " if not meta:\n", + " await redis_client.hset(key, mapping={\n", + " \"filename\": filename,\n", + " \"content_type\": content_type,\n", + " \"total_chunks\": str(total_chunks),\n", + " \"user_id\": str(current_user.get(\"id\", \"\")),\n", + " })\n", + " await redis_client.expire(key, CHUNK_TTL)\n", + "\n", + " # Store the chunk\n", + " await redis_client.hset(key, f\"chunk:{chunk_index}\", b64_data)\n", + " await redis_client.expire(key, CHUNK_TTL)\n", + "\n", + " return {\"ok\": True, \"received\": chunk_index}\n", + " except Exception as e:\n", + " raise HTTPException(status_code=500, detail=str(e))\n", + "\n", + "\n", + "@app.post(\"/api/posts/media/finalize\")\n", + "async def finalize_upload(\n", + " payload: dict,\n", + " current_user: Dict = Security(get_current_user, scopes=[\"create:posts\"])\n", + "):\n", + " file_id = payload.get(\"file_id\")\n", + " if not file_id:\n", + " raise HTTPException(status_code=400, detail=\"file_id required\")\n", + "\n", + " key = f\"upload:{file_id}\"\n", + " meta = await redis_client.hgetall(key)\n", + "\n", + " if not meta:\n", + " raise HTTPException(status_code=400, detail=\"Unknown or expired file_id\")\n", + "\n", + " total_chunks = int(meta[\"total_chunks\"])\n", + " filename = meta[\"filename\"]\n", + " content_type = meta[\"content_type\"]\n", + "\n", + " # Collect all chunks\n", + " chunks = []\n", + " for i in range(total_chunks):\n", + " b64 = await redis_client.hget(key, f\"chunk:{i}\")\n", + " if b64 is None:\n", + " raise HTTPException(status_code=400, detail=f\"Missing chunk {i}\")\n", + " chunks.append(base64.b64decode(b64))\n", + "\n", + " full_content = b\"\".join(chunks)\n", + "\n", + " temp_file = StarletteUploadFile(\n", + " file=BytesIO(full_content),\n", + " filename=filename,\n", + " headers={\"content-type\": content_type}\n", + " )\n", + "\n", + " try:\n", + " saved_name = await save_to_gridfs(temp_file, \"posts\")\n", + " return {\"post_medias\": [saved_name]}\n", + " finally:\n", + " # Cleanup\n", + " await redis_client.delete(key)\n", + "\n", + "\n", + "# Optional fallback (non-chunked)\n", "@app.post(\"/api/posts/media\", response_model=dict)\n", "async def post_media(\n", " medias: Optional[List[UploadFile]] = File(None),\n", " current_user: Dict = Security(get_current_user, scopes=[\"create:posts\"])\n", "):\n", " try:\n", - " post_medias = await asyncio.gather(*[save_to_gridfs(media, 'posts') for media in medias])\n", - " return {'post_medias': post_medias}\n", + " post_medias = await asyncio.gather(\n", + " *[save_to_gridfs(media, \"posts\") for media in medias or []]\n", + " )\n", + " return {\"post_medias\": post_medias}\n", " except Exception as e:\n", - " raise HTTPException(status_code=500, detail=str(e))\n" + " raise HTTPException(status_code=500, detail=str(e))" ], "display_name": ".venv" }, diff --git a/main.py b/main.py index 3755a42..efdfb6b 100644 --- a/main.py +++ b/main.py @@ -2464,18 +2464,99 @@ async def put_painting( return doc_out +@app.post("/api/posts/media/chunk") +async def upload_chunk( + chunk: UploadFile = File(...), + file_id: str = Form(...), + chunk_index: int = Form(...), + total_chunks: int = Form(...), + filename: str = Form(...), + content_type: str = Form(...), + current_user: Dict = Security(get_current_user, scopes=["create:posts"]) +): + try: + data = await chunk.read() + b64_data = base64.b64encode(data).decode("ascii") + + key = f"upload:{file_id}" + + # Store meta only once + meta = await redis_client.hgetall(key) + if not meta: + await redis_client.hset(key, mapping={ + "filename": filename, + "content_type": content_type, + "total_chunks": str(total_chunks), + "user_id": str(current_user.get("id", "")), + }) + await redis_client.expire(key, CHUNK_TTL) + + # Store the chunk + await redis_client.hset(key, f"chunk:{chunk_index}", b64_data) + await redis_client.expire(key, CHUNK_TTL) + + return {"ok": True, "received": chunk_index} + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@app.post("/api/posts/media/finalize") +async def finalize_upload( + payload: dict, + current_user: Dict = Security(get_current_user, scopes=["create:posts"]) +): + file_id = payload.get("file_id") + if not file_id: + raise HTTPException(status_code=400, detail="file_id required") + + key = f"upload:{file_id}" + meta = await redis_client.hgetall(key) + + if not meta: + raise HTTPException(status_code=400, detail="Unknown or expired file_id") + + total_chunks = int(meta["total_chunks"]) + filename = meta["filename"] + content_type = meta["content_type"] + + # Collect all chunks + chunks = [] + for i in range(total_chunks): + b64 = await redis_client.hget(key, f"chunk:{i}") + if b64 is None: + raise HTTPException(status_code=400, detail=f"Missing chunk {i}") + chunks.append(base64.b64decode(b64)) + + full_content = b"".join(chunks) + + temp_file = StarletteUploadFile( + file=BytesIO(full_content), + filename=filename, + headers={"content-type": content_type} + ) + + try: + saved_name = await save_to_gridfs(temp_file, "posts") + return {"post_medias": [saved_name]} + finally: + # Cleanup + await redis_client.delete(key) + + +# Optional fallback (non-chunked) @app.post("/api/posts/media", response_model=dict) async def post_media( medias: Optional[List[UploadFile]] = File(None), current_user: Dict = Security(get_current_user, scopes=["create:posts"]) ): try: - post_medias = await asyncio.gather(*[save_to_gridfs(media, 'posts') for media in medias]) - return {'post_medias': post_medias} + post_medias = await asyncio.gather( + *[save_to_gridfs(media, "posts") for media in medias or []] + ) + return {"post_medias": post_medias} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) - @app.post("/api/news", response_model=dict) async def post_news( background_tasks: BackgroundTasks, diff --git a/templates/posts/form.html b/templates/posts/form.html index d9eab2a..7b05591 100644 --- a/templates/posts/form.html +++ b/templates/posts/form.html @@ -120,11 +120,357 @@ +{{ header.section(user) }} +
+
- + -
- - -
-
- -
- - {{ footer.section() }} {% endblock %} @@ -522,6 +599,62 @@ {% block head %} {{ header.style() }}