From 002235b6f63d71e304ca2f62022106210175257f346d9522e76d913599528d5f Mon Sep 17 00:00:00 2001 From: vladislav Date: Mon, 7 Sep 2026 12:34:35 -0400 Subject: [PATCH 1/7] fix --- main.ipynb | 3 ++- main.py | 3 ++- vk.py | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/main.ipynb b/main.ipynb index ad328d4..04dd2ca 100644 --- a/main.ipynb +++ b/main.ipynb @@ -1535,7 +1535,8 @@ " ]\n", "\n", " cursor = competition_request_col.aggregate(pipeline)\n", - " return await cursor.to_list(length=None)" + " data = await cursor.to_list(length=None)\n", + " return await posts_collection.find({\"_id\": {\"$in\":[ ObjectId(doc['_id']) if type(doc['_id']) is str else doc['_id'] for doc in data ]}}).to_list()" ], "display_name": ".venv" }, diff --git a/main.py b/main.py index cd32f21..ea1556a 100644 --- a/main.py +++ b/main.py @@ -1186,7 +1186,8 @@ async def get_unique_posts_by_user( ] cursor = competition_request_col.aggregate(pipeline) - return await cursor.to_list(length=None) + data = await cursor.to_list(length=None) + return await posts_collection.find({"_id": {"$in":[ ObjectId(doc['_id']) if type(doc['_id']) is str else doc['_id'] for doc in data ]}}).to_list() async def get_unique_posts_с(competition_request_col: AsyncIOMotorCollection, competition_id: str, finish: bool = False) -> List[Any]: match_criteria = { diff --git a/vk.py b/vk.py index 079e182..fadb241 100644 --- a/vk.py +++ b/vk.py @@ -188,6 +188,7 @@ async def send_vk(data:dict, request: Request): return {"ok": True} + # TODO: function call like send_tg from tg.py (without router) @router.post("/edit") async def edit_vk(data:dict, request: Request): @@ -257,6 +258,7 @@ async def edit_vk(data:dict, request: Request): return {"ok": True} + # TODO: function call like send_tg from tg.py (without router) @router.post("/delete") async def wall_delete(data: dict, request: Request): -- 2.45.2 From 66539d373aea9c6748a364e4d03642f734204e99e5bcf2168434bc08b0f0e559 Mon Sep 17 00:00:00 2001 From: vladislav Date: Tue, 8 Sep 2026 04:41:20 -0400 Subject: [PATCH 2/7] feat --- tasks.ipynb | 13 +++++++++++++ tasks.py | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/tasks.ipynb b/tasks.ipynb index a47bca3..d7e45fe 100644 --- a/tasks.ipynb +++ b/tasks.ipynb @@ -69,6 +69,19 @@ "outputs": [], "source": [ "async def notify_in_profile(item: NotifyItem):\n", + " user = await users_collection.find_one({'_id': ObjectId(str(item.user_id))},{'email':1})\n", + " login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/profile')}'\n", + " if PROD:\n", + " email_task.notify.delay(user['email'], {\n", + " 'text': 'в личном кабинете',\n", + " 'list': [[]],\n", + " 'primary': {\n", + " 'href': login_link,\n", + " 'text': 'Войти и посмотреть'\n", + " },\n", + " 'title': 'новое уведомление',\n", + " 'login_link': login_link,\n", + " }, 'Уведомление', 'main.html')\n", " return await notify_collection.update_one(\n", " {\n", " \"user_id\": item.user_id,\n", diff --git a/tasks.py b/tasks.py index 533bdbe..996119a 100644 --- a/tasks.py +++ b/tasks.py @@ -12,6 +12,19 @@ import asyncio async def notify_in_profile(item: NotifyItem): + user = await users_collection.find_one({'_id': ObjectId(str(item.user_id))},{'email':1}) + login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/profile')}' + if PROD: + email_task.notify.delay(user['email'], { + 'text': 'в личном кабинете', + 'list': [[]], + 'primary': { + 'href': login_link, + 'text': 'Войти и посмотреть' + }, + 'title': 'новое уведомление', + 'login_link': login_link, + }, 'Уведомление', 'main.html') return await notify_collection.update_one( { "user_id": item.user_id, -- 2.45.2 From 082bd1e4a7ee2bbbc9ce13ade8dd6b670d6f01d06f719070ae4a20015b3fae1c Mon Sep 17 00:00:00 2001 From: vladislav Date: Tue, 8 Sep 2026 05:21:00 -0400 Subject: [PATCH 3/7] fix --- tasks.ipynb | 4 ++-- tasks.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tasks.ipynb b/tasks.ipynb index d7e45fe..7ba26d4 100644 --- a/tasks.ipynb +++ b/tasks.ipynb @@ -73,13 +73,13 @@ " login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/profile')}'\n", " if PROD:\n", " email_task.notify.delay(user['email'], {\n", - " 'text': 'в личном кабинете',\n", + " 'text': item.text,\n", " 'list': [[]],\n", " 'primary': {\n", " 'href': login_link,\n", " 'text': 'Войти и посмотреть'\n", " },\n", - " 'title': 'новое уведомление',\n", + " 'title': item.title,\n", " 'login_link': login_link,\n", " }, 'Уведомление', 'main.html')\n", " return await notify_collection.update_one(\n", diff --git a/tasks.py b/tasks.py index 996119a..bbdeddd 100644 --- a/tasks.py +++ b/tasks.py @@ -16,13 +16,13 @@ async def notify_in_profile(item: NotifyItem): login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/profile')}' if PROD: email_task.notify.delay(user['email'], { - 'text': 'в личном кабинете', + 'text': item.text, 'list': [[]], 'primary': { 'href': login_link, 'text': 'Войти и посмотреть' }, - 'title': 'новое уведомление', + 'title': item.title, 'login_link': login_link, }, 'Уведомление', 'main.html') return await notify_collection.update_one( -- 2.45.2 From 67e58c2dda17caa2b283b5f8164c17eaa2bb1c310c37d6539e8639fe9ad8f420 Mon Sep 17 00:00:00 2001 From: vladislav Date: Tue, 8 Sep 2026 07:49:52 -0400 Subject: [PATCH 4/7] fix --- tasks.ipynb | 32 +++++++++++++++++--------------- tasks.py | 30 ++++++++++++++++-------------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/tasks.ipynb b/tasks.ipynb index 7ba26d4..560ae4d 100644 --- a/tasks.ipynb +++ b/tasks.ipynb @@ -69,20 +69,7 @@ "outputs": [], "source": [ "async def notify_in_profile(item: NotifyItem):\n", - " user = await users_collection.find_one({'_id': ObjectId(str(item.user_id))},{'email':1})\n", - " login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/profile')}'\n", - " if PROD:\n", - " email_task.notify.delay(user['email'], {\n", - " 'text': item.text,\n", - " 'list': [[]],\n", - " 'primary': {\n", - " 'href': login_link,\n", - " 'text': 'Войти и посмотреть'\n", - " },\n", - " 'title': item.title,\n", - " 'login_link': login_link,\n", - " }, 'Уведомление', 'main.html')\n", - " return await notify_collection.update_one(\n", + " doc = await notify_collection.update_one(\n", " {\n", " \"user_id\": item.user_id,\n", " \"type\": item.type_item,\n", @@ -102,7 +89,22 @@ " }\n", " },\n", " upsert=True,\n", - " )\n" + " )\n", + " if doc.upserted_id:\n", + " user = await users_collection.find_one({'_id': ObjectId(str(item.user_id))},{'email':1})\n", + " login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/profile')}'\n", + " if PROD:\n", + " email_task.notify.delay(user['email'], {\n", + " 'text': item.text,\n", + " 'list': [[]],\n", + " 'primary': {\n", + " 'href': login_link,\n", + " 'text': 'Войти и посмотреть'\n", + " },\n", + " 'title': item.title,\n", + " 'login_link': login_link,\n", + " }, 'Уведомление', 'main.html')\n", + " return doc\n" ], "display_name": ".venv" }, diff --git a/tasks.py b/tasks.py index bbdeddd..252a739 100644 --- a/tasks.py +++ b/tasks.py @@ -12,20 +12,7 @@ import asyncio async def notify_in_profile(item: NotifyItem): - user = await users_collection.find_one({'_id': ObjectId(str(item.user_id))},{'email':1}) - login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/profile')}' - if PROD: - email_task.notify.delay(user['email'], { - 'text': item.text, - 'list': [[]], - 'primary': { - 'href': login_link, - 'text': 'Войти и посмотреть' - }, - 'title': item.title, - 'login_link': login_link, - }, 'Уведомление', 'main.html') - return await notify_collection.update_one( + doc = await notify_collection.update_one( { "user_id": item.user_id, "type": item.type_item, @@ -46,6 +33,21 @@ async def notify_in_profile(item: NotifyItem): }, upsert=True, ) + if doc.upserted_id: + user = await users_collection.find_one({'_id': ObjectId(str(item.user_id))},{'email':1}) + login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/profile')}' + if PROD: + email_task.notify.delay(user['email'], { + 'text': item.text, + 'list': [[]], + 'primary': { + 'href': login_link, + 'text': 'Войти и посмотреть' + }, + 'title': item.title, + 'login_link': login_link, + }, 'Уведомление', 'main.html') + return doc async def email_new_post(new_post: Dict): -- 2.45.2 From 01164bc9acd86f7c6c189fc1422fc280cb45f63594df1ae9e66135e7244a42b2 Mon Sep 17 00:00:00 2001 From: vladislav Date: Tue, 8 Sep 2026 07:56:27 -0400 Subject: [PATCH 5/7] fix --- tasks.ipynb | 51 ++++++++++++++++++++++++++++++++++++--------------- tasks.py | 51 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 30 deletions(-) diff --git a/tasks.ipynb b/tasks.ipynb index 560ae4d..f04d25c 100644 --- a/tasks.ipynb +++ b/tasks.ipynb @@ -69,7 +69,7 @@ "outputs": [], "source": [ "async def notify_in_profile(item: NotifyItem):\n", - " doc = await notify_collection.update_one(\n", + " result = await notify_collection.update_one(\n", " {\n", " \"user_id\": item.user_id,\n", " \"type\": item.type_item,\n", @@ -90,21 +90,42 @@ " },\n", " upsert=True,\n", " )\n", - " if doc.upserted_id:\n", - " user = await users_collection.find_one({'_id': ObjectId(str(item.user_id))},{'email':1})\n", - " login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/profile')}'\n", - " if PROD:\n", - " email_task.notify.delay(user['email'], {\n", - " 'text': item.text,\n", - " 'list': [[]],\n", - " 'primary': {\n", - " 'href': login_link,\n", - " 'text': 'Войти и посмотреть'\n", + "\n", + " if result.upserted_id is None:\n", + " return result # Existing notification was updated; no email\n", + "\n", + " user = await users_collection.find_one(\n", + " {\"_id\": ObjectId(str(item.user_id))},\n", + " {\"email\": 1},\n", + " )\n", + "\n", + " if not user:\n", + " return result\n", + "\n", + " token = await token_create(\n", + " str(user[\"_id\"]),\n", + " url=\"/profile\",\n", + " )\n", + " login_link = f\"{FULL_DOMAIN}/login/{token}\"\n", + "\n", + " if PROD:\n", + " email_task.notify.delay(\n", + " user[\"email\"],\n", + " {\n", + " \"text\": item.text,\n", + " \"list\": [[]],\n", + " \"primary\": {\n", + " \"href\": login_link,\n", + " \"text\": \"Войти и посмотреть\",\n", " },\n", - " 'title': item.title,\n", - " 'login_link': login_link,\n", - " }, 'Уведомление', 'main.html')\n", - " return doc\n" + " \"title\": item.title,\n", + " \"login_link\": login_link,\n", + " },\n", + " \"Уведомление\",\n", + " \"main.html\",\n", + " )\n", + "\n", + " return result\n" ], "display_name": ".venv" }, diff --git a/tasks.py b/tasks.py index 252a739..594acca 100644 --- a/tasks.py +++ b/tasks.py @@ -12,7 +12,7 @@ import asyncio async def notify_in_profile(item: NotifyItem): - doc = await notify_collection.update_one( + result = await notify_collection.update_one( { "user_id": item.user_id, "type": item.type_item, @@ -33,21 +33,42 @@ async def notify_in_profile(item: NotifyItem): }, upsert=True, ) - if doc.upserted_id: - user = await users_collection.find_one({'_id': ObjectId(str(item.user_id))},{'email':1}) - login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/profile')}' - if PROD: - email_task.notify.delay(user['email'], { - 'text': item.text, - 'list': [[]], - 'primary': { - 'href': login_link, - 'text': 'Войти и посмотреть' + + if result.upserted_id is None: + return result # Existing notification was updated; no email + + user = await users_collection.find_one( + {"_id": ObjectId(str(item.user_id))}, + {"email": 1}, + ) + + if not user: + return result + + token = await token_create( + str(user["_id"]), + url="/profile", + ) + login_link = f"{FULL_DOMAIN}/login/{token}" + + if PROD: + email_task.notify.delay( + user["email"], + { + "text": item.text, + "list": [[]], + "primary": { + "href": login_link, + "text": "Войти и посмотреть", }, - 'title': item.title, - 'login_link': login_link, - }, 'Уведомление', 'main.html') - return doc + "title": item.title, + "login_link": login_link, + }, + "Уведомление", + "main.html", + ) + + return result async def email_new_post(new_post: Dict): -- 2.45.2 From f5607d666d87e1da107ba5e29f776678cc843c724d76498e084ffe803371717d Mon Sep 17 00:00:00 2001 From: vladislav Date: Wed, 9 Sep 2026 04:10:51 -0400 Subject: [PATCH 6/7] feat --- main.ipynb | 216 +++++++++++++++++++++-------------------------------- main.py | 188 +++++++++++++++------------------------------- 2 files changed, 144 insertions(+), 260 deletions(-) diff --git a/main.ipynb b/main.ipynb index 04dd2ca..c13b577 100644 --- a/main.ipynb +++ b/main.ipynb @@ -173,6 +173,33 @@ ], "display_name": ".venv" }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7845a8a", + "metadata": {}, + "outputs": [], + "source": [ + "def get_delivery_aggregate(_filter: dict = {}, _status: str = None):\n", + " cursor = delivery_collection.aggregate([\n", + " {\"$match\": {\n", + " \"prod\": PROD, **_filter\n", + " }},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " {\"$group\": {\n", + " \"_id\": \"$post_id\",\n", + " \"doc\": {\"$first\": \"$$ROOT\"}\n", + " }},\n", + " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " ])\n", + " if _status:\n", + " return (doc async for doc in cursor if doc.get('status') == _status)\n", + " else:\n", + " return cursor\n" + ], + "display_name": ".venv" + }, { "cell_type": "code", "execution_count": null, @@ -573,7 +600,6 @@ "\n", " prod_filter = {\"prod\": PROD}\n", "\n", - " # 1) If there's an exhibition that is going to be finished soon -> move deliveries to \"Возврат художнику\"\n", " exhibition_finished = await exhibition_collection.find_one_and_update(\n", " {**prod_filter, \"status\": 1, \"date_finish\": {\"$lt\": limit_dt}},\n", " {\"$set\": {\"status\": 2}},\n", @@ -581,20 +607,9 @@ " )\n", " if exhibition_finished:\n", " exhibition_id_str = str(exhibition_finished[\"_id\"])\n", - " cursor = delivery_collection.aggregate([\n", - " {\"$match\": {\n", - " \"exhibition_id\": exhibition_id_str, **prod_filter\n", - " }},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " {\"$group\": {\n", - " \"_id\": \"$post_id\",\n", - " \"doc\": {\"$first\": \"$$ROOT\"}\n", - " }},\n", - " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " ])\n", "\n", - " data = [doc async for doc in cursor if doc.get('status') == 'На выставке']\n", + " data = [doc async for doc in get_delivery_aggregate({\"exhibition_id\": exhibition_id_str}, 'На выставке')]\n", + "\n", " for doc in data:\n", " post_id = doc.get(\"post_id\")\n", " exhibition_id = doc.get(\"exhibition_id\")\n", @@ -619,27 +634,25 @@ " return {}\n", " tasks = []\n", " async for exhibition_to_start in exhibition_collection.find(\n", - " {**prod_filter}\n", + " {\"status\": {'$lte':1}, **prod_filter}\n", " ):\n", " exhibition_id_str = str(exhibition_to_start[\"_id\"])\n", " exhibition_title = exhibition_to_start.get(\"title\")\n", "\n", " notified_user_id = None\n", - " notified_post_user_id = None\n", + " notified_post_user_ids = {}\n", "\n", - " async for doc in delivery_collection.find(\n", - " {\"exhibition_id\": exhibition_id_str, \"status\": \"Получено\", **prod_filter},\n", - " {\"_id\": 0},\n", - " ):\n", + " data = [doc async for doc in get_delivery_aggregate({\"exhibition_id\": exhibition_id_str}, 'Получено')]\n", + " \n", + " for doc in data:\n", " post_id = doc.get(\"post_id\")\n", " exhibition_id = doc.get(\"exhibition_id\")\n", "\n", " post = await posts_collection.find_one({\"_id\": ObjectId(post_id)})\n", "\n", - " # Use user from post; keep them even if multiple deliveries exist\n", - " if post and post.get(\"user\") and not notified_post_user_id:\n", - " notified_post_user_id = str(post[\"user\"].get(\"_id\"))\n", - "\n", + " if post and post.get(\"user\"):\n", + " notified_post_user_ids.update({str(post[\"user\"].get(\"_id\")):1})\n", + " \n", " new_doc = {\n", " **doc,\n", " \"exhibition\": exhibition_to_start,\n", @@ -659,12 +672,10 @@ " {\"$set\": {\"status\": \"На выставке\"}}\n", " ))\n", "\n", - " # NOTE: your original code used `post` from inside the loop (could be last item / even undefined).\n", - " # This version notifies once using the first found post user.\n", - " if notified_post_user_id:\n", + " for user_id in notified_post_user_ids.keys():\n", " await notify_in_profile(\n", " NotifyItem(\n", - " user_id=notified_post_user_id,\n", + " user_id=user_id,\n", " type_item=\"exhibtion\",\n", " title=f\"Выставка {exhibition_title}\",\n", " link=\"/profile/exhibition\",\n", @@ -673,7 +684,7 @@ " )\n", " if tasks:\n", " asyncio.gather(*tasks)\n", - " # 2) Otherwise find an exhibition that should start soon -> move deliveries to \"На выставке\"\n", + "\n", " exhibition_to_start = await exhibition_collection.find_one_and_update(\n", " {**prod_filter, \"status\": 0, \"datetime_start\": {\"$lt\": limit_dt}},\n", " {\"$set\": {\"status\": 1}},\n", @@ -683,7 +694,6 @@ " if not exhibition_to_start:\n", " return {}\n", "\n", - " # If vk/tg arrays are empty -> send social invitation\n", " if exhibition_to_start.get(\"vk\") == [] or exhibition_to_start.get(\"tg\") == []:\n", " await service_social_exhibition(bson_clear(exhibition_to_start))\n", " return {}\n" @@ -1643,22 +1653,8 @@ "):\n", " exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)})\n", " if exhibition:\n", - " match_ = {\n", - " \"exhibition_id\": exhibition_id,\n", - " 'prod': PROD,\n", - " }\n", - "\n", - " cursor = delivery_collection.aggregate([\n", - " {\"$match\": match_},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " {\"$group\": {\n", - " \"_id\": \"$post_id\",\n", - " \"doc\": {\"$first\": \"$$ROOT\"}\n", - " }},\n", - " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " ])\n", - " posts = [doc async for doc in cursor if doc.get('status') == 'На выставке']\n", + " posts = [doc async for doc in get_delivery_aggregate({\"exhibition_id\": exhibition_id}, 'На выставке')]\n", + " \n", " return templates.TemplateResponse(\n", " request=request, name=\"exhibition/index.html\", context={\"id\": 1, \"user\": user, \"exhibition\": exhibition, \"posts\": posts, 'now': datetime.now()}\n", " )\n", @@ -4343,25 +4339,15 @@ " {\"sender._id\": user.get('_id')},\n", " {\"user._id\": user.get('_id')},\n", " {\"receiver._id\": user.get('_id')}\n", - " ],\n", - " 'prod': PROD,\n", + " ]\n", " }\n", " if data.get('competition'):\n", " match_.update({'competition_id': data.get('competition').get('_id')})\n", " if data.get('exhibition_id'):\n", " match_.update({'exhibition_id': data.get('exhibition_id')})\n", - " cursor = delivery_collection.aggregate([\n", - " {\"$match\": match_},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " {\"$group\": {\n", - " \"_id\": \"$post_id\",\n", - " \"doc\": {\"$first\": \"$$ROOT\"}\n", - " }},\n", - " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " ])\n", + " \n", + " posts = [doc async for doc in get_delivery_aggregate(match_)]\n", "\n", - " posts = [doc async for doc in cursor]\n", " return templates.TemplateResponse(\n", " request=request, name=\"profile/invitation.html\", context={ \"user\": user, \"doc\": data, \"data\": posts}\n", " )" @@ -4403,7 +4389,7 @@ " user: Dict = Security(get_current_user)\n", "):\n", " await social_collection.update_one({'_id': ObjectId(id), 'user_id': user.get('_id')},{'$set': {key:check}})\n", - " return {'ok': True}" + " return {'ok': True}\n" ], "display_name": ".venv" }, @@ -4415,22 +4401,8 @@ "outputs": [], "source": [ "async def get_delivery_post(post_id):\n", - " cursor = delivery_collection.aggregate([\n", - " {\"$match\": {\n", - " 'post_id': post_id,\n", - " 'prod': PROD,\n", - " }},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " {\"$group\": {\n", - " \"_id\": \"$post_id\",\n", - " \"doc\": {\"$first\": \"$$ROOT\"}\n", - " }},\n", - " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " ])\n", - "\n", - " data = [doc async for doc in cursor]\n", - " return data[0] if data else False" + " data = [doc async for doc in get_delivery_aggregate({'post_id': post_id})]\n", + " return data[0] if data else False\n" ], "display_name": ".venv" }, @@ -4716,6 +4688,17 @@ ], "display_name": ".venv" }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d762f57", + "metadata": {}, + "outputs": [], + "source": [ + "# TODO BEGIN" + ], + "display_name": ".venv" + }, { "cell_type": "code", "execution_count": null, @@ -4725,26 +4708,16 @@ "source": [ "# @app.get('/profile/delivery/{type_d}/{item_id}')\n", "# async def profile_delivery_by_old(request: Request, type_d:str, item_id:str, user: Dict = Security(get_current_user)):\n", - "# cursor = delivery_collection.aggregate([\n", - "# {\"$match\": {\n", + "# _match = {\n", "# \"$or\": [\n", "# {\"sender._id\": user.get('_id')},\n", "# {\"user._id\": user.get('_id')},\n", "# {\"receiver._id\": user.get('_id')}\n", "# ],\n", "# f'{type_d}_id': ObjectId(item_id),\n", - "# 'prod': PROD,\n", - "# }},\n", - "# {\"$sort\": {\"_id\": -1}},\n", - "# {\"$group\": {\n", - "# \"_id\": \"$post_id\",\n", - "# \"doc\": {\"$first\": \"$$ROOT\"}\n", - "# }},\n", - "# {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", - "# {\"$sort\": {\"_id\": -1}},\n", - "# ])\n", + "# }\n", "\n", - "# data = [doc async for doc in cursor]\n", + "# data = [doc async for doc in get_delivery_aggregate(_match)]\n", "# data.sort(key=lambda doc: doc[\"_id\"].generation_time, reverse=True)\n", "\n", "# return templates.TemplateResponse(\n", @@ -4767,25 +4740,15 @@ "source": [ "@app.get('/profile/delivery/competition/{competition_id}')\n", "async def profile_delivery_by_competition(request: Request, competition_id:str, user: Dict = Security(get_current_user)):\n", - " cursor = delivery_collection.aggregate([\n", - " {\"$match\": {\n", + " _match = {\n", " \"$or\": [\n", " {\"sender._id\": user.get('_id')},\n", " {\"user._id\": user.get('_id')},\n", " {\"receiver._id\": user.get('_id')}\n", - " ],\n", - " 'prod': PROD,\n", - " }},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " {\"$group\": {\n", - " \"_id\": \"$post_id\",\n", - " \"doc\": {\"$first\": \"$$ROOT\"}\n", - " }},\n", - " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " ])\n", + " ]\n", + " }\n", "\n", - " data = [doc async for doc in cursor]\n", + " data = [doc async for doc in get_delivery_aggregate(_match)]\n", " data.sort(key=lambda doc: doc[\"_id\"].generation_time, reverse=True)\n", "\n", " return templates.TemplateResponse(\n", @@ -4808,26 +4771,16 @@ "source": [ "@app.get('/profile/delivery/exhibition/{exhibition_id}')\n", "async def profile_delivery_by_exhibition(request: Request, exhibition_id:str, user: Dict = Security(get_current_user)):\n", - " cursor = delivery_collection.aggregate([\n", - " {\"$match\": {\n", + " _match = {\n", " \"$or\": [\n", " {\"sender._id\": user.get('_id')},\n", " {\"user._id\": user.get('_id')},\n", " {\"receiver._id\": user.get('_id')}\n", " ],\n", " 'exhibition_id': exhibition_id,\n", - " 'prod': PROD,\n", - " }},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " {\"$group\": {\n", - " \"_id\": \"$post_id\",\n", - " \"doc\": {\"$first\": \"$$ROOT\"}\n", - " }},\n", - " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " ])\n", + " }\n", "\n", - " data = [doc async for doc in cursor]\n", + " data = [doc async for doc in get_delivery_aggregate(_match)]\n", " data.sort(key=lambda doc: doc[\"_id\"].generation_time, reverse=True)\n", "\n", " return templates.TemplateResponse(\n", @@ -4841,6 +4794,17 @@ ], "display_name": ".venv" }, + { + "cell_type": "code", + "execution_count": null, + "id": "e26f23d4", + "metadata": {}, + "outputs": [], + "source": [ + "# TODO END" + ], + "display_name": ".venv" + }, { "cell_type": "code", "execution_count": null, @@ -4877,26 +4841,16 @@ " Returns:\n", " HTMLResponse с шаблоном profile/delivery.html\n", " \"\"\"\n", - " # Получаем все доставки пользователя (отправитель ИЛИ получатель)\n", - " cursor = delivery_collection.aggregate([\n", - " {\"$match\": {\n", + " _match = {\n", " \"$or\": [\n", " {\"sender._id\": user.get('_id')},\n", " {\"user._id\": user.get('_id')},\n", " {\"receiver._id\": user.get('_id')}\n", - " ],\n", - " 'prod': PROD,\n", - " }},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " {\"$group\": {\n", - " \"_id\": \"$post_id\",\n", - " \"doc\": {\"$first\": \"$$ROOT\"}\n", - " }},\n", - " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", - " {\"$sort\": {\"_id\": -1}},\n", - " ])\n", + " ]\n", + " }\n", + " \n", + " data = [doc async for doc in get_delivery_aggregate(_match)]\n", "\n", - " data = [doc async for doc in cursor]\n", " exhibitions = await exhibition_collection.find({'_id': {'$in': [ObjectId(i.get('exhibition_id')) for i in data if i.get('exhibition_id')]}}).to_list()\n", " competitions = await competitions_col.find({'_id': {'$in': [i.get('competition_id') for i in data if i.get('competition_id')]}}).to_list()\n", " return templates.TemplateResponse(\n", diff --git a/main.py b/main.py index ea1556a..ad3aa46 100644 --- a/main.py +++ b/main.py @@ -81,6 +81,25 @@ def transliterate(text: str) -> str: return ''.join(RUS_TO_LAT.get(ch, '' if ch not in string.ascii_letters else ch) for ch in text) +def get_delivery_aggregate(_filter: dict = {}, _status: str = None): + cursor = delivery_collection.aggregate([ + {"$match": { + "prod": PROD, **_filter + }}, + {"$sort": {"_id": -1}}, + {"$group": { + "_id": "$post_id", + "doc": {"$first": "$$ROOT"} + }}, + {"$replaceRoot": {"newRoot": "$doc"}}, + {"$sort": {"_id": -1}}, + ]) + if _status: + return (doc async for doc in cursor if doc.get('status') == _status) + else: + return cursor + + async def get_latest_news() -> List[dict]: cursor = news_collection.find({'publish_on_platform': True}).sort("_id", -1).limit(30) docs: List[dict] = [] @@ -353,7 +372,6 @@ async def cron_every_five_min(token: str = Security(get_token)): prod_filter = {"prod": PROD} - # 1) If there's an exhibition that is going to be finished soon -> move deliveries to "Возврат художнику" exhibition_finished = await exhibition_collection.find_one_and_update( {**prod_filter, "status": 1, "date_finish": {"$lt": limit_dt}}, {"$set": {"status": 2}}, @@ -361,20 +379,9 @@ async def cron_every_five_min(token: str = Security(get_token)): ) if exhibition_finished: exhibition_id_str = str(exhibition_finished["_id"]) - cursor = delivery_collection.aggregate([ - {"$match": { - "exhibition_id": exhibition_id_str, **prod_filter - }}, - {"$sort": {"_id": -1}}, - {"$group": { - "_id": "$post_id", - "doc": {"$first": "$$ROOT"} - }}, - {"$replaceRoot": {"newRoot": "$doc"}}, - {"$sort": {"_id": -1}}, - ]) - data = [doc async for doc in cursor if doc.get('status') == 'На выставке'] + data = [doc async for doc in get_delivery_aggregate({"exhibition_id": exhibition_id_str}, 'На выставке')] + for doc in data: post_id = doc.get("post_id") exhibition_id = doc.get("exhibition_id") @@ -399,27 +406,25 @@ async def cron_every_five_min(token: str = Security(get_token)): return {} tasks = [] async for exhibition_to_start in exhibition_collection.find( - {**prod_filter} + {"status": {'$lte':1}, **prod_filter} ): exhibition_id_str = str(exhibition_to_start["_id"]) exhibition_title = exhibition_to_start.get("title") notified_user_id = None - notified_post_user_id = None + notified_post_user_ids = {} - async for doc in delivery_collection.find( - {"exhibition_id": exhibition_id_str, "status": "Получено", **prod_filter}, - {"_id": 0}, - ): + data = [doc async for doc in get_delivery_aggregate({"exhibition_id": exhibition_id_str}, 'Получено')] + + for doc in data: post_id = doc.get("post_id") exhibition_id = doc.get("exhibition_id") post = await posts_collection.find_one({"_id": ObjectId(post_id)}) - # Use user from post; keep them even if multiple deliveries exist - if post and post.get("user") and not notified_post_user_id: - notified_post_user_id = str(post["user"].get("_id")) - + if post and post.get("user"): + notified_post_user_ids.update({str(post["user"].get("_id")):1}) + new_doc = { **doc, "exhibition": exhibition_to_start, @@ -439,12 +444,10 @@ async def cron_every_five_min(token: str = Security(get_token)): {"$set": {"status": "На выставке"}} )) - # NOTE: your original code used `post` from inside the loop (could be last item / even undefined). - # This version notifies once using the first found post user. - if notified_post_user_id: + for user_id in notified_post_user_ids.keys(): await notify_in_profile( NotifyItem( - user_id=notified_post_user_id, + user_id=user_id, type_item="exhibtion", title=f"Выставка {exhibition_title}", link="/profile/exhibition", @@ -453,7 +456,7 @@ async def cron_every_five_min(token: str = Security(get_token)): ) if tasks: asyncio.gather(*tasks) - # 2) Otherwise find an exhibition that should start soon -> move deliveries to "На выставке" + exhibition_to_start = await exhibition_collection.find_one_and_update( {**prod_filter, "status": 0, "datetime_start": {"$lt": limit_dt}}, {"$set": {"status": 1}}, @@ -463,7 +466,6 @@ async def cron_every_five_min(token: str = Security(get_token)): if not exhibition_to_start: return {} - # If vk/tg arrays are empty -> send social invitation if exhibition_to_start.get("vk") == [] or exhibition_to_start.get("tg") == []: await service_social_exhibition(bson_clear(exhibition_to_start)) return {} @@ -1222,22 +1224,8 @@ async def exhibition_members( ): exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)}) if exhibition: - match_ = { - "exhibition_id": exhibition_id, - 'prod': PROD, - } - - cursor = delivery_collection.aggregate([ - {"$match": match_}, - {"$sort": {"_id": -1}}, - {"$group": { - "_id": "$post_id", - "doc": {"$first": "$$ROOT"} - }}, - {"$replaceRoot": {"newRoot": "$doc"}}, - {"$sort": {"_id": -1}}, - ]) - posts = [doc async for doc in cursor if doc.get('status') == 'На выставке'] + posts = [doc async for doc in get_delivery_aggregate({"exhibition_id": exhibition_id}, 'На выставке')] + return templates.TemplateResponse( request=request, name="exhibition/index.html", context={"id": 1, "user": user, "exhibition": exhibition, "posts": posts, 'now': datetime.now()} ) @@ -3340,25 +3328,15 @@ async def profile_invitation( {"sender._id": user.get('_id')}, {"user._id": user.get('_id')}, {"receiver._id": user.get('_id')} - ], - 'prod': PROD, + ] } if data.get('competition'): match_.update({'competition_id': data.get('competition').get('_id')}) if data.get('exhibition_id'): match_.update({'exhibition_id': data.get('exhibition_id')}) - cursor = delivery_collection.aggregate([ - {"$match": match_}, - {"$sort": {"_id": -1}}, - {"$group": { - "_id": "$post_id", - "doc": {"$first": "$$ROOT"} - }}, - {"$replaceRoot": {"newRoot": "$doc"}}, - {"$sort": {"_id": -1}}, - ]) + + posts = [doc async for doc in get_delivery_aggregate(match_)] - posts = [doc async for doc in cursor] return templates.TemplateResponse( request=request, name="profile/invitation.html", context={ "user": user, "doc": data, "data": posts} ) @@ -3384,24 +3362,12 @@ async def social_setting( await social_collection.update_one({'_id': ObjectId(id), 'user_id': user.get('_id')},{'$set': {key:check}}) return {'ok': True} -async def get_delivery_post(post_id): - cursor = delivery_collection.aggregate([ - {"$match": { - 'post_id': post_id, - 'prod': PROD, - }}, - {"$sort": {"_id": -1}}, - {"$group": { - "_id": "$post_id", - "doc": {"$first": "$$ROOT"} - }}, - {"$replaceRoot": {"newRoot": "$doc"}}, - {"$sort": {"_id": -1}}, - ]) - data = [doc async for doc in cursor] +async def get_delivery_post(post_id): + data = [doc async for doc in get_delivery_aggregate({'post_id': post_id})] return data[0] if data else False + @app.post('/api/v1/views') async def get_views_count(request: Request): url = (await request.json())['url'] @@ -3609,28 +3575,20 @@ async def profile_delivery_history_post(request: Request, post_id:str, user: Dic ) +# TODO BEGIN + # @app.get('/profile/delivery/{type_d}/{item_id}') # async def profile_delivery_by_old(request: Request, type_d:str, item_id:str, user: Dict = Security(get_current_user)): -# cursor = delivery_collection.aggregate([ -# {"$match": { +# _match = { # "$or": [ # {"sender._id": user.get('_id')}, # {"user._id": user.get('_id')}, # {"receiver._id": user.get('_id')} # ], # f'{type_d}_id': ObjectId(item_id), -# 'prod': PROD, -# }}, -# {"$sort": {"_id": -1}}, -# {"$group": { -# "_id": "$post_id", -# "doc": {"$first": "$$ROOT"} -# }}, -# {"$replaceRoot": {"newRoot": "$doc"}}, -# {"$sort": {"_id": -1}}, -# ]) +# } -# data = [doc async for doc in cursor] +# data = [doc async for doc in get_delivery_aggregate(_match)] # data.sort(key=lambda doc: doc["_id"].generation_time, reverse=True) # return templates.TemplateResponse( @@ -3645,25 +3603,15 @@ async def profile_delivery_history_post(request: Request, post_id:str, user: Dic @app.get('/profile/delivery/competition/{competition_id}') async def profile_delivery_by_competition(request: Request, competition_id:str, user: Dict = Security(get_current_user)): - cursor = delivery_collection.aggregate([ - {"$match": { + _match = { "$or": [ {"sender._id": user.get('_id')}, {"user._id": user.get('_id')}, {"receiver._id": user.get('_id')} - ], - 'prod': PROD, - }}, - {"$sort": {"_id": -1}}, - {"$group": { - "_id": "$post_id", - "doc": {"$first": "$$ROOT"} - }}, - {"$replaceRoot": {"newRoot": "$doc"}}, - {"$sort": {"_id": -1}}, - ]) + ] + } - data = [doc async for doc in cursor] + data = [doc async for doc in get_delivery_aggregate(_match)] data.sort(key=lambda doc: doc["_id"].generation_time, reverse=True) return templates.TemplateResponse( @@ -3678,26 +3626,16 @@ async def profile_delivery_by_competition(request: Request, competition_id:str, @app.get('/profile/delivery/exhibition/{exhibition_id}') async def profile_delivery_by_exhibition(request: Request, exhibition_id:str, user: Dict = Security(get_current_user)): - cursor = delivery_collection.aggregate([ - {"$match": { + _match = { "$or": [ {"sender._id": user.get('_id')}, {"user._id": user.get('_id')}, {"receiver._id": user.get('_id')} ], 'exhibition_id': exhibition_id, - 'prod': PROD, - }}, - {"$sort": {"_id": -1}}, - {"$group": { - "_id": "$post_id", - "doc": {"$first": "$$ROOT"} - }}, - {"$replaceRoot": {"newRoot": "$doc"}}, - {"$sort": {"_id": -1}}, - ]) + } - data = [doc async for doc in cursor] + data = [doc async for doc in get_delivery_aggregate(_match)] data.sort(key=lambda doc: doc["_id"].generation_time, reverse=True) return templates.TemplateResponse( @@ -3709,6 +3647,8 @@ async def profile_delivery_by_exhibition(request: Request, exhibition_id:str, us } ) +# TODO END + @app.get( "/profile/delivery", response_class=HTMLResponse, @@ -3738,26 +3678,16 @@ async def profile_delivery( Returns: HTMLResponse с шаблоном profile/delivery.html """ - # Получаем все доставки пользователя (отправитель ИЛИ получатель) - cursor = delivery_collection.aggregate([ - {"$match": { + _match = { "$or": [ {"sender._id": user.get('_id')}, {"user._id": user.get('_id')}, {"receiver._id": user.get('_id')} - ], - 'prod': PROD, - }}, - {"$sort": {"_id": -1}}, - {"$group": { - "_id": "$post_id", - "doc": {"$first": "$$ROOT"} - }}, - {"$replaceRoot": {"newRoot": "$doc"}}, - {"$sort": {"_id": -1}}, - ]) + ] + } + + data = [doc async for doc in get_delivery_aggregate(_match)] - data = [doc async for doc in cursor] exhibitions = await exhibition_collection.find({'_id': {'$in': [ObjectId(i.get('exhibition_id')) for i in data if i.get('exhibition_id')]}}).to_list() competitions = await competitions_col.find({'_id': {'$in': [i.get('competition_id') for i in data if i.get('competition_id')]}}).to_list() return templates.TemplateResponse( -- 2.45.2 From faaf317f1493b7e76f54ba1c2c34a81b722a256b0fd1acfdddeab46552f4ad0b Mon Sep 17 00:00:00 2001 From: vladislav Date: Wed, 9 Sep 2026 10:49:12 -0400 Subject: [PATCH 7/7] hore --- build.ipynb | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/build.ipynb b/build.ipynb index 7b48c77..9405dda 100644 --- a/build.ipynb +++ b/build.ipynb @@ -756,6 +756,35 @@ "### fastapi prod service" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "721bee4c", + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "ssh kultmost 'tee /app/production/start-uvicorn.sh >/dev/null' <<'EOF'\n", + "#!/usr/bin/bash\n", + "set -euo pipefail\n", + "\n", + "workers=\"$(nproc)\"\n", + "\n", + "exec /app/production/.venv/bin/uvicorn main:app \\\n", + " --host 127.0.0.1 \\\n", + " --port 30081 \\\n", + " --workers \"$workers\"\n", + "EOF\n", + "\n", + "ssh kultmost 'chmod +x /app/production/start-uvicorn.sh'\n", + "ssh kultmost 'ls -ln /app/production/start-uvicorn.sh'" + ], + "display_name": ".venv" + }, { "cell_type": "code", "execution_count": null, @@ -775,16 +804,16 @@ "\n", "[Service]\n", "WorkingDirectory=/app/production\n", - "ExecStart=/app/production/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30081 --workers 3\n", + "ExecStart=/app/production/start-uvicorn.sh\n", "Restart=always\n", "EnvironmentFile=/app/production/env\n", "CPUWeight=60\n", "\n", "[Install]\n", - "WantedBy=default.target\n", + "WantedBy=multi-user.target\n", "EOF\n", "\n", - "ssh kultmost 'systemctl daemon-reload && systemctl enable --now production.service && systemctl status --no-pager -l production.service'\n" + "ssh kultmost 'systemctl daemon-reload && systemctl enable --now production.service && systemctl status --no-pager -l production.service'" ], "display_name": ".venv" }, -- 2.45.2