5330 lines
207 KiB
Plaintext
5330 lines
207 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "4e3a145b",
|
||
"metadata": {},
|
||
"source": [
|
||
"# FastAPI"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "44794e2f",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# ignore\n",
|
||
"import os\n",
|
||
"\n",
|
||
"def load_env(file_path):\n",
|
||
" \"\"\"Загружает переменные окружения из файла .env с поддержкой булевых значений.\"\"\"\n",
|
||
" with open(file_path) as f:\n",
|
||
" for line in f:\n",
|
||
" line = line.strip()\n",
|
||
" if not line or line.startswith('#'):\n",
|
||
" continue\n",
|
||
" \n",
|
||
" try:\n",
|
||
" key, value = line.split('=', 1)\n",
|
||
" key = key.strip()\n",
|
||
" value = value.strip('\"\\' ')\n",
|
||
" # Преобразование строковых значений в булевые\n",
|
||
" if value.lower() in ['true', '1', 'yes']:\n",
|
||
" value = True\n",
|
||
" elif value.lower() in ['false', '0', 'no']:\n",
|
||
" value = False\n",
|
||
" \n",
|
||
" # Проверка, что ключ не пустой\n",
|
||
" if key:\n",
|
||
" os.environ[key] = str(value) # Храним как строку\n",
|
||
" except ValueError:\n",
|
||
" print(f\"Ошибка: неверный формат в строке '{line}'\")\n",
|
||
"\n",
|
||
"try:\n",
|
||
" load_env('env')\n",
|
||
"except:\n",
|
||
" load_env('env_example')"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "dd08738b",
|
||
"metadata": {},
|
||
"source": [
|
||
"## App"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "49f00c66",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from fastapi import FastAPI, APIRouter, UploadFile, File, Depends, HTTPException, Body, Security, Form, Request, Response, BackgroundTasks, Query\n",
|
||
"from motor.motor_asyncio import AsyncIOMotorCollection\n",
|
||
"import jwt\n",
|
||
"from datetime import datetime, timedelta, timezone, date\n",
|
||
"import io\n",
|
||
"import asyncio\n",
|
||
"from fastapi.middleware.cors import CORSMiddleware\n",
|
||
"from fastapi.responses import HTMLResponse, StreamingResponse, RedirectResponse\n",
|
||
"from starlette.datastructures import UploadFile as StarletteUploadFile\n",
|
||
"from fastapi.staticfiles import StaticFiles\n",
|
||
"import mimetypes\n",
|
||
"from urllib.parse import quote\n",
|
||
"from starlette.middleware.gzip import GZipMiddleware\n",
|
||
"import uuid\n",
|
||
"import bcrypt\n",
|
||
"from typing import List, Any, Dict, Optional, Union\n",
|
||
"from urllib.parse import urlencode\n",
|
||
"from bson import ObjectId\n",
|
||
"from PIL import Image\n",
|
||
"from io import BytesIO\n",
|
||
"from dateutil.parser import isoparse\n",
|
||
"import httpx\n",
|
||
"import string\n",
|
||
"from db import *\n",
|
||
"import vk as vk\n",
|
||
"import tg as tg\n",
|
||
"from delivery_router import router as delivery_router\n",
|
||
"from auth import get_user, get_current_user, get_bearer_token, get_access_token, get_token\n",
|
||
"from roles import *\n",
|
||
"from utils import *\n",
|
||
"from tasks import *\n",
|
||
"from models import * \n",
|
||
"from config import *\n",
|
||
"import logging\n",
|
||
"import json\n",
|
||
"from templates import templates\n",
|
||
"import email_task as email_task\n",
|
||
"from pydantic import EmailStr\n",
|
||
"import secrets\n",
|
||
"from pymongo import ReturnDocument\n",
|
||
"\n",
|
||
"logging.basicConfig(level=logging.INFO)\n",
|
||
"logger = logging.getLogger(__name__)"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "28509a4b",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Конфигурация"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "0142add9",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"app = FastAPI(title=\"KULTMOST\")\n",
|
||
"\n",
|
||
"app.mount(\"/static\", StaticFiles(directory=\"static\"), name=\"static\")\n",
|
||
"app.add_middleware(GZipMiddleware, minimum_size=500)\n",
|
||
"app.add_middleware(\n",
|
||
" CORSMiddleware,\n",
|
||
" allow_origins=['*'],\n",
|
||
" allow_credentials=True,\n",
|
||
" allow_methods=[\"*\"],\n",
|
||
" allow_headers=[\"*\"],\n",
|
||
")\n",
|
||
"app.include_router(delivery_router)\n",
|
||
"app.include_router(vk.router, prefix='/api/v1/social', tags=[\"Auth\"])"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f299b21b",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Хелперы"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "77d90b86",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"RUS_TO_LAT = {\n",
|
||
" 'а':'a','б':'b','в':'v','г':'g','д':'d','е':'e','ё':'yo','ж':'zh','з':'z',\n",
|
||
" 'и':'i','й':'y','к':'k','л':'l','м':'m','н':'n','о':'o','п':'p','р':'r',\n",
|
||
" 'с':'s','т':'t','у':'u','ф':'f','х':'kh','ц':'ts','ч':'ch','ш':'sh','щ':'shch',\n",
|
||
" 'ъ':'\"','ы':'y','ь':\"'\",'э':'e','ю':'yu','я':'ya',\n",
|
||
" ' ':'-',\n",
|
||
" 'А':'A','Б':'B','В':'V','Г':'G','Д':'D','Е':'E','Ё':'Yo','Ж':'Zh','З':'Z',\n",
|
||
" 'И':'I','Й':'Y','К':'K','Л':'L','М':'M','Н':'N','О':'O','П':'P','Р':'R',\n",
|
||
" 'С':'S','Т':'T','У':'U','Ф':'F','Х':'Kh','Ц':'Ts','Ч':'Ch','Ш':'Sh','Щ':'Shch',\n",
|
||
" 'Ъ':'\"','Ы':'Y','Ь':\"'\",'Э':'E','Ю':'Yu','Я':'Ya'\n",
|
||
"}\n",
|
||
"\n",
|
||
"def transliterate(text: str) -> str:\n",
|
||
" return ''.join(RUS_TO_LAT.get(ch, '' if ch not in string.ascii_letters else ch) for ch in text)\n"
|
||
],
|
||
"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,
|
||
"id": "77d90b86",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def get_latest_news() -> List[dict]:\n",
|
||
" cursor = news_collection.find({'publish_on_platform': True}).sort(\"_id\", -1).limit(30)\n",
|
||
" docs: List[dict] = []\n",
|
||
" async for doc in cursor:\n",
|
||
" published = doc.get(\"published_at\")\n",
|
||
" if published is not None and hasattr(published, \"isoformat\"):\n",
|
||
" doc[\"published_at\"] = published.isoformat()\n",
|
||
" docs.append(doc)\n",
|
||
" return docs\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "1f960469",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def logo_user_task(user: dict):\n",
|
||
" img_id = user.get('img') if user else None\n",
|
||
" new_id = None\n",
|
||
"\n",
|
||
" if img_id:\n",
|
||
" # download original image bytes\n",
|
||
" file_data = await gridfs_map['users'].open_download_stream(ObjectId(img_id))\n",
|
||
" original_image_data = await file_data.read()\n",
|
||
"\n",
|
||
" # store original in GridFS (keep a copy)\n",
|
||
" original_img_id = await gridfs_map['users'].upload_from_stream(f'original_{img_id}', BytesIO(original_image_data))\n",
|
||
"\n",
|
||
" # open image with PIL\n",
|
||
" img = Image.open(BytesIO(original_image_data))\n",
|
||
" w, h = img.size\n",
|
||
"\n",
|
||
" # resize if needed\n",
|
||
" if w > 500:\n",
|
||
" factor = 500 / w\n",
|
||
" new_size = (500, int(h * factor))\n",
|
||
" img = img.resize(new_size, resample=Image.LANCZOS)\n",
|
||
"\n",
|
||
" # ensure image mode is compatible with JPEG\n",
|
||
" if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):\n",
|
||
" # composite onto white background to remove alpha\n",
|
||
" background = Image.new('RGB', img.size, (255, 255, 255))\n",
|
||
" alpha = img.split()[-1] if img.mode != 'P' else img.convert('RGBA').split()[-1]\n",
|
||
" background.paste(img.convert('RGBA'), mask=alpha)\n",
|
||
" img = background\n",
|
||
" else:\n",
|
||
" img = img.convert('RGB')\n",
|
||
"\n",
|
||
" # save to buffer as JPEG\n",
|
||
" buf = BytesIO()\n",
|
||
" # img.save(buf, format='JPEG', quality=85)\n",
|
||
" img.save(buf, format=\"WEBP\", quality=85, method=6)\n",
|
||
" buf.seek(0)\n",
|
||
"\n",
|
||
" # upload processed avatar to GridFS\n",
|
||
" new_id = await gridfs_map['users'].upload_from_stream('500_avatar.jpg', buf)\n",
|
||
"\n",
|
||
" # update user doc with new image ids (store as strings to match your original code)\n",
|
||
" await users_collection.update_one(\n",
|
||
" {'_id': ObjectId(user.get('_id'))},\n",
|
||
" {'$set': {'img': str(new_id), 'original_img': str(original_img_id)}}\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "7a666b69",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def poster_competition(competition):\n",
|
||
" img_id = competition.get('poster_orig_id')\n",
|
||
" try:\n",
|
||
" stream = await gridfs_map['competition'].open_download_stream(ObjectId(img_id))\n",
|
||
" data = await stream.read()\n",
|
||
"\n",
|
||
" buf = await build_composite(data, 0.75, 800)\n",
|
||
"\n",
|
||
" new_id = await gridfs_map['competition'].upload_from_stream(f\"500_{stream.filename}\", buf)\n",
|
||
"\n",
|
||
" except Exception:\n",
|
||
" new_id = img_id\n",
|
||
" print(f'Ошибка обработки id {img_id} в выставке {competition[\"_id\"]}')\n",
|
||
"\n",
|
||
" if new_id:\n",
|
||
" await competitions_col.update_one(\n",
|
||
" {'_id': ObjectId(competition.get('_id'))},\n",
|
||
" {'$set': {\n",
|
||
" 'poster_file_id': new_id\n",
|
||
" }}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "185ed4e5",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def poster_exhibition(exhibition):\n",
|
||
" img_id = exhibition.get('poster_orig_id')\n",
|
||
" try:\n",
|
||
" stream = await gridfs_map['exhibition'].open_download_stream(ObjectId(img_id))\n",
|
||
" data = await stream.read()\n",
|
||
"\n",
|
||
" buf = await build_composite(data, 0.73, 800)\n",
|
||
"\n",
|
||
" new_id = await gridfs_map['exhibition'].upload_from_stream(f\"500_{stream.filename}\", buf)\n",
|
||
"\n",
|
||
" except Exception:\n",
|
||
" new_id = img_id\n",
|
||
" print(f'Ошибка обработки id {img_id} в выставке {exhibition[\"_id\"]}')\n",
|
||
"\n",
|
||
" if new_id:\n",
|
||
" await exhibition_collection.update_one(\n",
|
||
" {'_id': ObjectId(exhibition.get('_id'))},\n",
|
||
" {'$set': {\n",
|
||
" 'poster_file_id': new_id\n",
|
||
" }}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "9d00fe86",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def replace_new_medias(post, links_media):\n",
|
||
" img_ids = post.get('medias_original', [])\n",
|
||
" if not isinstance(img_ids, list):\n",
|
||
" img_ids = [img_ids]\n",
|
||
"\n",
|
||
" new_ids = []\n",
|
||
"\n",
|
||
" for img_id in img_ids:\n",
|
||
" if img_id in links_media:\n",
|
||
" new_ids.append(links_media['img_id'])\n",
|
||
" continue\n",
|
||
" try:\n",
|
||
" stream = await gridfs_map['posts'].open_download_stream(ObjectId(img_id))\n",
|
||
" data = await stream.read()\n",
|
||
"\n",
|
||
" buf = await build_composite(data)\n",
|
||
"\n",
|
||
" new_id = await gridfs_map['posts'].upload_from_stream(f\"500_{stream.filename}\", buf)\n",
|
||
" new_ids.append(str(new_id))\n",
|
||
"\n",
|
||
" except Exception:\n",
|
||
" new_ids.append(str(img_id))\n",
|
||
" print(f'Ошибка обработки id {img_id} в посте {post[\"_id\"]}')\n",
|
||
"\n",
|
||
" if new_ids:\n",
|
||
" await posts_collection.update_one(\n",
|
||
" {'_id': ObjectId(post.get('_id'))},\n",
|
||
" {'$set': {\n",
|
||
" 'medias': new_ids\n",
|
||
" }}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "80790203",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def replace_post_medias(post):\n",
|
||
" img_ids = post.get('medias_original', [])\n",
|
||
" if not isinstance(img_ids, list):\n",
|
||
" img_ids = [img_ids]\n",
|
||
"\n",
|
||
" new_ids = []\n",
|
||
"\n",
|
||
" for img_id in img_ids:\n",
|
||
" try:\n",
|
||
" stream = await gridfs_map['posts'].open_download_stream(ObjectId(img_id))\n",
|
||
" data = await stream.read()\n",
|
||
"\n",
|
||
" buf = await build_composite(data)\n",
|
||
"\n",
|
||
" new_id = await gridfs_map['posts'].upload_from_stream(f\"500_{stream.filename}\", buf)\n",
|
||
" new_ids.append(str(new_id))\n",
|
||
"\n",
|
||
" except Exception:\n",
|
||
" new_ids.append(str(img_id))\n",
|
||
" print(f'Ошибка обработки id {img_id} в посте {post[\"_id\"]}')\n",
|
||
"\n",
|
||
" if new_ids:\n",
|
||
" await posts_collection.update_one(\n",
|
||
" {'_id': ObjectId(post.get('_id'))},\n",
|
||
" {'$set': {\n",
|
||
" 'medias': new_ids\n",
|
||
" }}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "d512558a",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def replace_news_medias(news):\n",
|
||
" img_ids = news.get('orig_media', [])\n",
|
||
" if not isinstance(img_ids, list):\n",
|
||
" img_ids = [img_ids]\n",
|
||
"\n",
|
||
" new_ids = []\n",
|
||
"\n",
|
||
" for img_id in img_ids:\n",
|
||
" try:\n",
|
||
" stream = await gridfs_map['news'].open_download_stream(ObjectId(img_id))\n",
|
||
" data = await stream.read()\n",
|
||
"\n",
|
||
" buf = await build_composite(data)\n",
|
||
"\n",
|
||
" new_id = await gridfs_map['news'].upload_from_stream(f\"500_{stream.filename}\", buf)\n",
|
||
" new_ids.append(str(new_id))\n",
|
||
"\n",
|
||
" except Exception:\n",
|
||
" new_ids.append(str(img_id))\n",
|
||
" print(f'Ошибка обработки id {img_id} в посте {news[\"_id\"]}')\n",
|
||
"\n",
|
||
" if new_ids:\n",
|
||
" await news_collection.update_one(\n",
|
||
" {'_id': ObjectId(news.get('_id'))},\n",
|
||
" {'$set': {\n",
|
||
" 'media': new_ids\n",
|
||
" }}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "02d14e84",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def save_to_gridfs(upload: UploadFile, bucket_name: str):\n",
|
||
" if upload is None:\n",
|
||
" return None\n",
|
||
"\n",
|
||
" grid = gridfs_map.get(bucket_name)\n",
|
||
" if grid is None:\n",
|
||
" return None\n",
|
||
"\n",
|
||
" filename = upload.filename or str(uuid.uuid4())\n",
|
||
"\n",
|
||
" contents = await upload.read()\n",
|
||
" stream = io.BytesIO(contents)\n",
|
||
"\n",
|
||
" try:\n",
|
||
" file_id = await grid.upload_from_stream(filename, stream)\n",
|
||
" return str(file_id)\n",
|
||
" finally:\n",
|
||
" await upload.close()\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "3e085813",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def posts_temp(user,request):\n",
|
||
" data=[]\n",
|
||
" async for i in posts_collection.find({'user._id': user.get('_id')}).sort('_id',-1).limit(1000):\n",
|
||
" data.append(i)\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/posts.html\", context={ \"user\": user, 'cards': data}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "edb712bd",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/cron/08\", response_model=dict)\n",
|
||
"async def cron_08(background_tasks: BackgroundTasks):\n",
|
||
" async for doc in posts_collection.find({\n",
|
||
" '_id': {'$gte': ObjectId.from_datetime(datetime.now() - timedelta(days=1))},\n",
|
||
" '$or':[{'vk':[]},{'tg':[]},{'fb':[]},{'inst':[]},{'max':[]}]\n",
|
||
" }):\n",
|
||
" background_tasks.add_task(publish_social_post, bson_clear(doc))\n",
|
||
" return {'ok': True}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "abe0b1c9",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/cron/day\")\n",
|
||
"async def cron_every_day(token: str = Security(get_token)):\n",
|
||
" res = await competitions_col.update_many(\n",
|
||
" {\n",
|
||
" \"date_finish\": {\"$lt\": datetime.now(timezone.utc) + timedelta(hours=3)},\n",
|
||
" \"status\": {\"$lt\": 2},\n",
|
||
" },\n",
|
||
" {\"$set\": {\"status\": 2}},\n",
|
||
" )\n",
|
||
" return {}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "d49bb0b6",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# await notify_collection.delete_many({'title':'Доставка картины'})"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "f9a0e519",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# await notify_collection.update_many({'title':'Доставка картины'},{'$set':{'type':'delivery'}})"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "43cfd9a2",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# async for existing in delivery_collection.find({\n",
|
||
"# 'exhibition_id':\n",
|
||
"# '6a55f0e90154435ba37a7c25'\n",
|
||
"# ,\n",
|
||
"# 'status':\n",
|
||
"# 'Ожидает получения'\n",
|
||
"# }):\n",
|
||
"# print(str(existing['receiver'].get('_id')))\n",
|
||
"# notify = await notify_in_profile(NotifyItem(\n",
|
||
"# user_id=str(existing['receiver'].get('_id')),\n",
|
||
"# type_item='delivery',\n",
|
||
"# title='Доставка картины',\n",
|
||
"# link=f'/profile/delivery',\n",
|
||
"# text=f'{str(existing['sender']['username'])} прислал вам картину — {existing['title']}'\n",
|
||
"# ))\n",
|
||
"# print(notify)\n",
|
||
"# await delivery_collection.update_one({'_id': existing.get('_id')},{'$set':{'notify': notify.upserted_id}})"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "95af5bfa",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/cron/5\")\n",
|
||
"async def cron_every_five_min(token: str = Security(get_token)):\n",
|
||
" now = datetime.now(timezone.utc)\n",
|
||
" limit_dt = now + timedelta(hours=3)\n",
|
||
"\n",
|
||
" prod_filter = {\"prod\": PROD}\n",
|
||
"\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",
|
||
" return_document=ReturnDocument.AFTER,\n",
|
||
" )\n",
|
||
" if exhibition_finished:\n",
|
||
" exhibition_id_str = str(exhibition_finished[\"_id\"])\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",
|
||
" new_doc = {\n",
|
||
" **doc,\n",
|
||
" \"delivery_type\": \"\",\n",
|
||
" \"track\": \"\",\n",
|
||
" \"status\": \"Возврат художнику\",\n",
|
||
" \"receiver\": doc.get(\"user\"),\n",
|
||
" \"sender\": doc.get(\"exhibition\", {}).get(\"user\"),\n",
|
||
" }\n",
|
||
" new_doc.pop('_id')\n",
|
||
" new_doc.pop('exhibition')\n",
|
||
"\n",
|
||
" await delivery_collection.update_one(\n",
|
||
" {**prod_filter, \"status\": \"Возврат художнику\", \"post_id\": post_id, \"exhibition_id\": exhibition_id},\n",
|
||
" {\"$set\": new_doc},\n",
|
||
" upsert=True,\n",
|
||
" )\n",
|
||
"\n",
|
||
" return {}\n",
|
||
" tasks = []\n",
|
||
" async for exhibition_to_start in exhibition_collection.find(\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_ids = {}\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",
|
||
" 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",
|
||
" \"status\": \"На выставке\",\n",
|
||
" \"user\": post.get(\"user\") if post else None,\n",
|
||
" \"post\": post,\n",
|
||
" **prod_filter\n",
|
||
" }\n",
|
||
"\n",
|
||
" tasks.append(delivery_collection.update_one(\n",
|
||
" {**prod_filter, \"status\": \"На выставке\", \"post_id\": post_id, \"exhibition_id\": exhibition_id},\n",
|
||
" {\"$set\": new_doc},\n",
|
||
" upsert=True,\n",
|
||
" ))\n",
|
||
" tasks.append(posts_collection.update_one(\n",
|
||
" {\"_id\": ObjectId(post_id)},\n",
|
||
" {\"$set\": {\"status\": \"На выставке\"}}\n",
|
||
" ))\n",
|
||
"\n",
|
||
" for user_id in notified_post_user_ids.keys():\n",
|
||
" await notify_in_profile(\n",
|
||
" NotifyItem(\n",
|
||
" user_id=user_id,\n",
|
||
" type_item=\"exhibtion\",\n",
|
||
" title=f\"Выставка {exhibition_title}\",\n",
|
||
" link=\"/profile/exhibition\",\n",
|
||
" text=\"Ваши картины на выставке\",\n",
|
||
" )\n",
|
||
" )\n",
|
||
" if tasks:\n",
|
||
" asyncio.gather(*tasks)\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",
|
||
" return_document=ReturnDocument.AFTER,\n",
|
||
" )\n",
|
||
"\n",
|
||
" if not exhibition_to_start:\n",
|
||
" return {}\n",
|
||
"\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"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "517caafe",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# ignore\n",
|
||
"# exhibition = await exhibition_collection.find_one_and_update(\n",
|
||
"# {\"prod\": True, \"datetime_start\": {\"$lt\": datetime.now(timezone.utc) + timedelta(hours=3)}},\n",
|
||
"# {\"$set\": {\"status\": 1}}, return_document=False)\n",
|
||
"# await service_social_exhibition(exhibition)\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "3e085813",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Эндпоинты"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "8faed7c5",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/register\", response_model=dict)\n",
|
||
"async def register(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" username: Optional[str] = Form(None),\n",
|
||
" phone: Optional[str] = Form(None),\n",
|
||
" email: Optional[str] = Form(None),\n",
|
||
" password: str = Form(...),\n",
|
||
" client_id: str = Form(...),\n",
|
||
" roles: List[str] = Form(...),\n",
|
||
" birthday: Optional[date] = Form(None),\n",
|
||
" grade: Optional[str] = Form(None),\n",
|
||
" Female_Name_Lastame: Optional[str] = Form(None),\n",
|
||
" living_place: Optional[str] = Form(None),\n",
|
||
" offer_accepted: Optional[bool] = Form(False),\n",
|
||
" img: Optional[UploadFile] = File(None),\n",
|
||
" request: Request = None,\n",
|
||
" response: Response = None\n",
|
||
"):\n",
|
||
" user = UserRegister(\n",
|
||
" username=username, phone=phone, email=email, password=password,\n",
|
||
" client_id=client_id, roles=roles, birthday=birthday, grade=grade,\n",
|
||
" Female_Name_Lastame=Female_Name_Lastame, living_place=living_place,\n",
|
||
" offer_accepted=offer_accepted\n",
|
||
" )\n",
|
||
" # if normalize_phone(user.phone) and await users_collection.find_one({\"phone\": normalize_phone(user.phone)}):\n",
|
||
" # raise HTTPException(400, \"Телефон уже зарегестрирован\")\n",
|
||
" if user.email and await users_collection.find_one({\"email\": user.email}):\n",
|
||
" raise HTTPException(400, \"Email уже зарегестрирован\")\n",
|
||
"\n",
|
||
" hashed_password = get_password_hash(user.password)\n",
|
||
" allowed_roles = [r for r in user.roles if r not in BLOCK_ROLES]\n",
|
||
" if any(r in REQUIRES_OFFER for r in user.roles):\n",
|
||
" if not user.offer_accepted:\n",
|
||
" raise HTTPException(status_code=400, detail=\"Оферта должна быть принята\")\n",
|
||
" if not user.offer_accepted_at:\n",
|
||
" user.offer_accepted_at = datetime.utcnow()\n",
|
||
" if img:\n",
|
||
" img_id = await save_to_gridfs(img, 'users')\n",
|
||
" else:\n",
|
||
" img_id = '/static/painter.svg'\n",
|
||
"\n",
|
||
" doc = {\n",
|
||
" \"username\": user.username,\n",
|
||
" \"phone\": normalize_phone(user.phone) if user.phone else None,\n",
|
||
" \"email\": user.email,\n",
|
||
" \"img\": img_id,\n",
|
||
" \"hashed_password\": hashed_password,\n",
|
||
" \"roles\": allowed_roles,\n",
|
||
" \"birthday\": user.birthday.isoformat() if user.birthday else None,\n",
|
||
" \"grade\": user.grade,\n",
|
||
" \"female_name_lastname\": user.female_name_lastname,\n",
|
||
" \"living_place\": user.living_place,\n",
|
||
" \"client_id\": user.client_id,\n",
|
||
" \"extra_scopes\": user.extra_scopes,\n",
|
||
" \"offer_accepted\": bool(user.offer_accepted),\n",
|
||
" \"offer_accepted_at\": user.offer_accepted_at.isoformat() if user.offer_accepted_at else None,\n",
|
||
" }\n",
|
||
"\n",
|
||
" result = await users_collection.insert_one(doc)\n",
|
||
" user_id_str = str(result.inserted_id)\n",
|
||
" \n",
|
||
" doc_out = {\n",
|
||
" \"_id\": user_id_str,\n",
|
||
" \"id\": user_id_str,\n",
|
||
" **{k: v for k, v in doc.items()}\n",
|
||
" }\n",
|
||
" \n",
|
||
" background_tasks.add_task(email_new_user, bson_clear(doc_out))\n",
|
||
" background_tasks.add_task(logo_user_task, bson_clear(doc_out))\n",
|
||
" scopes = get_all_user_scopes(allowed_roles, [])\n",
|
||
"\n",
|
||
" client_id = user.client_id\n",
|
||
" client_ip = request.headers.get('X-Real-IP') or request.client.host\n",
|
||
"\n",
|
||
" access_exp_delta = timedelta(days=ACCESS_TOKEN_EXPIRE_MINUTES)\n",
|
||
" refresh_exp_delta = timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)\n",
|
||
"\n",
|
||
" access_token = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"scopes\": scopes, \"client_id\": client_id, \"ip\": client_ip},\n",
|
||
" access_exp_delta\n",
|
||
" )\n",
|
||
" refresh_token = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"type\": \"refresh\", \"client_id\": client_id, \"ip\": client_ip},\n",
|
||
" refresh_exp_delta\n",
|
||
" )\n",
|
||
"\n",
|
||
" await redis_client.setex(f\"access:{access_token}\", int(access_exp_delta.total_seconds()), user_id_str)\n",
|
||
" await redis_client.setex(f\"refresh:{hash_token(refresh_token)}\", int(refresh_exp_delta.total_seconds()), user_id_str)\n",
|
||
"\n",
|
||
" session_key = f\"user_sessions:{user_id_str}\"\n",
|
||
" await redis_client.sadd(session_key, f\"access:{access_token}\")\n",
|
||
" await redis_client.sadd(session_key, f\"refresh:{hash_token(refresh_token)}\")\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"access_token\",\n",
|
||
" value=access_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(access_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"refresh_token\",\n",
|
||
" value=refresh_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(refresh_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
"\n",
|
||
" return {\n",
|
||
" \"ok\": True,\n",
|
||
" \"access_token\": access_token,\n",
|
||
" \"refresh_token\": refresh_token,\n",
|
||
" \"user_id\": user_id_str,\n",
|
||
" \"token_type\": \"bearer\"\n",
|
||
" }\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "8faed7c5",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/admin/login\")\n",
|
||
"async def login_admin(\n",
|
||
" request: Request,\n",
|
||
" response: Response,\n",
|
||
" login_data: AdminLoginRequest = Body(...),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"admin\"])\n",
|
||
"):\n",
|
||
" user = await users_collection.find_one({\"_id\": ObjectId(login_data.user_id)})\n",
|
||
" user_id_str = str(user[\"_id\"])\n",
|
||
" scopes = get_all_user_scopes(user.get(\"roles\", []), user.get(\"extra_scopes\", []))\n",
|
||
"\n",
|
||
" client_ip = request.headers.get('X-Real-IP') or request.headers.get('X-Real-IP') or request.client.host\n",
|
||
"\n",
|
||
" access_exp_delta = timedelta(days=ACCESS_TOKEN_EXPIRE_MINUTES)\n",
|
||
" refresh_exp_delta = timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)\n",
|
||
"\n",
|
||
" access_token = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"scopes\": scopes, \"client_id\": 'admin', \"ip\": client_ip},\n",
|
||
" access_exp_delta\n",
|
||
" )\n",
|
||
" refresh_token = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"type\": \"refresh\", \"client_id\": 'admin', \"ip\": client_ip},\n",
|
||
" refresh_exp_delta\n",
|
||
" )\n",
|
||
"\n",
|
||
" await redis_client.setex(f\"access:{access_token}\", int(access_exp_delta.total_seconds()), user_id_str)\n",
|
||
" await redis_client.setex(f\"refresh:{hash_token(refresh_token)}\", int(refresh_exp_delta.total_seconds()), user_id_str)\n",
|
||
"\n",
|
||
" session_key = f\"user_sessions:{user_id_str}\"\n",
|
||
" await redis_client.sadd(session_key, f\"access:{access_token}\")\n",
|
||
" await redis_client.sadd(session_key, f\"refresh:{hash_token(refresh_token)}\")\n",
|
||
"\n",
|
||
"\n",
|
||
" admin_access_token = request.cookies.get(\"access_token\")\n",
|
||
" admin_refresh_token = request.cookies.get(\"refresh_token\")\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"admin_access_token\",\n",
|
||
" value=admin_access_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(access_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"admin_refresh_token\",\n",
|
||
" value=admin_refresh_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(refresh_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"access_token\",\n",
|
||
" value=access_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(access_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"refresh_token\",\n",
|
||
" value=refresh_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(refresh_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
"\n",
|
||
" return {\n",
|
||
" \"access_token\": access_token,\n",
|
||
" \"refresh_token\": refresh_token,\n",
|
||
" \"user_id\": user_id_str,\n",
|
||
" \"token_type\": \"bearer\"\n",
|
||
" }\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "092e6b9a",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/admin/back\")\n",
|
||
"async def login_admin_back(\n",
|
||
" request: Request,\n",
|
||
" response: Response,\n",
|
||
"):\n",
|
||
" access_exp_delta = timedelta(days=ACCESS_TOKEN_EXPIRE_MINUTES)\n",
|
||
" refresh_exp_delta = timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)\n",
|
||
" admin_access_token = request.cookies.get(\"admin_access_token\")\n",
|
||
" admin_refresh_token = request.cookies.get(\"refreadmin_refresh_tokensh_token\")\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"access_token\",\n",
|
||
" value=admin_access_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(access_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"refresh_token\",\n",
|
||
" value=admin_refresh_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(refresh_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" return {'ok': True}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "b7ddd254",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/login/{token}\")\n",
|
||
"async def login_token(token: str, request: Request, response: Response):\n",
|
||
" token_hash = hash_token(token)\n",
|
||
"\n",
|
||
" log = await users_token_collection.find_one(\n",
|
||
" {'token_hash': token_hash}\n",
|
||
" )\n",
|
||
" if not log:\n",
|
||
" return RedirectResponse(\"/login?\" + urlencode({\"next\": '/profile'}))\n",
|
||
" next_url = log.get('url', '/')\n",
|
||
" created = log.get(\"created_at\")\n",
|
||
" if not created:\n",
|
||
" try:\n",
|
||
" created = isoparse(ObjectId(log.get(\"_id\")).generation_time.replace(tzinfo=timezone.utc))\n",
|
||
" except Exception:\n",
|
||
" return RedirectResponse(\"/login?\" + urlencode({\"next\": next_url}))\n",
|
||
" \n",
|
||
" created = created.replace(tzinfo=timezone.utc)\n",
|
||
" now = datetime.now(timezone.utc)\n",
|
||
" if created < now - timedelta(days=TOKEN_TTL_DAYS):\n",
|
||
" await users_token_collection.delete_one({'token_hash': token_hash})\n",
|
||
" return RedirectResponse(\"/login?\" + urlencode({\"next\": log.get('url', next_url)}))\n",
|
||
"\n",
|
||
" user_id = log.get(\"user_id\")\n",
|
||
" if not user_id:\n",
|
||
" return RedirectResponse(\"/login?\" + urlencode({\"next\": log.get('url', next_url)}))\n",
|
||
"\n",
|
||
" user = await users_collection.find_one({\"_id\": ObjectId(user_id)})\n",
|
||
" if not user:\n",
|
||
" return RedirectResponse(\"/login?\" + urlencode({\"next\": log.get('url', next_url)}))\n",
|
||
"\n",
|
||
" user_id_str = str(user[\"_id\"])\n",
|
||
" scopes = get_all_user_scopes(user.get(\"roles\", []), user.get(\"extra_scopes\", []))\n",
|
||
"\n",
|
||
" client_ip = request.headers.get('X-Real-IP') or request.client.host\n",
|
||
"\n",
|
||
" access_exp_delta = timedelta(days=ACCESS_TOKEN_EXPIRE_MINUTES)\n",
|
||
" refresh_exp_delta = timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)\n",
|
||
"\n",
|
||
" access_token = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"scopes\": scopes, \"client_id\": 'token', \"ip\": client_ip},\n",
|
||
" access_exp_delta\n",
|
||
" )\n",
|
||
" refresh_token = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"type\": \"refresh\", \"client_id\": 'token', \"ip\": client_ip},\n",
|
||
" refresh_exp_delta\n",
|
||
" )\n",
|
||
"\n",
|
||
" await redis_client.setex(f\"access:{access_token}\", int(access_exp_delta.total_seconds()), user_id_str)\n",
|
||
" await redis_client.setex(f\"refresh:{hash_token(refresh_token)}\", int(refresh_exp_delta.total_seconds()), user_id_str)\n",
|
||
"\n",
|
||
" session_key = f\"user_sessions:{user_id_str}\"\n",
|
||
" await redis_client.sadd(session_key, f\"access:{access_token}\")\n",
|
||
" await redis_client.sadd(session_key, f\"refresh:{hash_token(refresh_token)}\")\n",
|
||
"\n",
|
||
"\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"access_token\",\n",
|
||
" value=access_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(access_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"refresh_token\",\n",
|
||
" value=refresh_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(refresh_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"access_token\",\n",
|
||
" value=access_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" domain='.kultmost.ru',\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(access_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"refresh_token\",\n",
|
||
" value=refresh_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" domain='.kultmost.ru',\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(refresh_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
"\n",
|
||
" # Validate and sanitize redirect\n",
|
||
" \n",
|
||
" if not is_safe_redirect(next_url):\n",
|
||
" next_url = '/'\n",
|
||
"\n",
|
||
" return RedirectResponse(next_url,headers=response.headers)\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "096074ab",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import hashlib\n",
|
||
"\n",
|
||
"def sha256(text: str) -> str:\n",
|
||
" \"\"\"Server-side SHA-256 (hex)\"\"\"\n",
|
||
" return hashlib.sha256(text.encode(\"utf-8\")).hexdigest()\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.post(\"/api/login\")\n",
|
||
"async def login(\n",
|
||
" request: Request,\n",
|
||
" response: Response,\n",
|
||
" login_data: LoginRequest = Body(...),\n",
|
||
"):\n",
|
||
" identifier = safe_strip(login_data.identifier)\n",
|
||
" await check_bruteforce(identifier)\n",
|
||
"\n",
|
||
" or_conditions = [\n",
|
||
" {\"email\": identifier},\n",
|
||
" {\"email\": identifier.lower()},\n",
|
||
" ]\n",
|
||
"\n",
|
||
" try:\n",
|
||
" normalized_phone = normalize_phone(identifier)\n",
|
||
" if normalized_phone:\n",
|
||
" or_conditions.append({\"phone\": normalized_phone})\n",
|
||
" except Exception:\n",
|
||
" pass\n",
|
||
"\n",
|
||
" users = [doc async for doc in users_collection.find({\"$or\": or_conditions})]\n",
|
||
"\n",
|
||
" if not users:\n",
|
||
" await increment_failed_attempt(identifier)\n",
|
||
" raise HTTPException(status_code=401, detail=\"Incorrect credentials\")\n",
|
||
"\n",
|
||
" matched_user = None\n",
|
||
" new_hash = None\n",
|
||
" is_hash_login = login_data.client_id == \"web_hash\"\n",
|
||
"\n",
|
||
" for user in users:\n",
|
||
" stored = user.get(\"hashed_password\")\n",
|
||
" if isinstance(stored, bytes):\n",
|
||
" stored = stored.decode()\n",
|
||
"\n",
|
||
" if not stored:\n",
|
||
" continue\n",
|
||
"\n",
|
||
" # ---------- 1. Client sent SHA-256 hash ----------\n",
|
||
" if is_hash_login:\n",
|
||
" # Stored value is expected to be Argon2(SHA256(password))\n",
|
||
" if stored.startswith(\"$argon2\"):\n",
|
||
" if verify_password(login_data.password, stored):\n",
|
||
" matched_user = user\n",
|
||
" break\n",
|
||
"\n",
|
||
" # ---------- 2. Client sent plaintext ----------\n",
|
||
" else:\n",
|
||
" # Modern: Argon2(SHA256(plain))\n",
|
||
" if stored.startswith(\"$argon2\"):\n",
|
||
" # Try verifying as if stored is Argon2(SHA256(plain))\n",
|
||
" hashed_input = sha256(login_data.password)\n",
|
||
" if verify_password(hashed_input, stored):\n",
|
||
" matched_user = user\n",
|
||
" break\n",
|
||
"\n",
|
||
" # Also try classic Argon2(plain) for very old accounts\n",
|
||
" if verify_password(login_data.password, stored):\n",
|
||
" matched_user = user\n",
|
||
" # Force migration to Argon2(SHA256)\n",
|
||
" new_hash = get_password_hash(sha256(login_data.password))\n",
|
||
" break\n",
|
||
"\n",
|
||
" # Legacy bcrypt(plain)\n",
|
||
" elif stored.startswith((\"$2y$\", \"$2b$\", \"$2a$\")):\n",
|
||
" stored_bcrypt = stored.replace(\"$2y$\", \"$2b$\", 1)\n",
|
||
" try:\n",
|
||
" if bcrypt.checkpw(\n",
|
||
" login_data.password.encode(\"utf-8\"),\n",
|
||
" stored_bcrypt.encode(\"utf-8\")\n",
|
||
" ):\n",
|
||
" matched_user = user\n",
|
||
" # Migrate to Argon2(SHA256(plain))\n",
|
||
" new_hash = get_password_hash(sha256(login_data.password))\n",
|
||
" break\n",
|
||
" except Exception:\n",
|
||
" continue\n",
|
||
"\n",
|
||
" if not matched_user:\n",
|
||
" await increment_failed_attempt(identifier)\n",
|
||
" raise HTTPException(status_code=401, detail=\"Incorrect credentials\")\n",
|
||
"\n",
|
||
" # Apply migration if needed\n",
|
||
" if new_hash:\n",
|
||
" await users_collection.update_one(\n",
|
||
" {\"_id\": matched_user[\"_id\"]},\n",
|
||
" {\"$set\": {\"hashed_password\": new_hash}}\n",
|
||
" )\n",
|
||
"\n",
|
||
" await reset_failed_attempts(identifier)\n",
|
||
"\n",
|
||
" user_id_str = str(user[\"_id\"])\n",
|
||
" scopes = get_all_user_scopes(user.get(\"roles\", []), user.get(\"extra_scopes\", []))\n",
|
||
"\n",
|
||
" client_ip = request.headers.get('X-Real-IP') or request.client.host\n",
|
||
"\n",
|
||
" access_exp_delta = timedelta(days=ACCESS_TOKEN_EXPIRE_MINUTES)\n",
|
||
" refresh_exp_delta = timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)\n",
|
||
"\n",
|
||
" access_token = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"scopes\": scopes, \"client_id\": login_data.client_id, \"ip\": client_ip},\n",
|
||
" access_exp_delta\n",
|
||
" )\n",
|
||
" refresh_token = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"type\": \"refresh\", \"client_id\": login_data.client_id, \"ip\": client_ip},\n",
|
||
" refresh_exp_delta\n",
|
||
" )\n",
|
||
"\n",
|
||
" await redis_client.setex(f\"access:{access_token}\", int(access_exp_delta.total_seconds()), user_id_str)\n",
|
||
" await redis_client.setex(f\"refresh:{hash_token(refresh_token)}\", int(refresh_exp_delta.total_seconds()), user_id_str)\n",
|
||
"\n",
|
||
" session_key = f\"user_sessions:{user_id_str}\"\n",
|
||
" await redis_client.sadd(session_key, f\"access:{access_token}\")\n",
|
||
" await redis_client.sadd(session_key, f\"refresh:{hash_token(refresh_token)}\")\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"access_token\",\n",
|
||
" value=access_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(access_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"refresh_token\",\n",
|
||
" value=refresh_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(refresh_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
"\n",
|
||
" return {\n",
|
||
" \"access_token\": access_token,\n",
|
||
" \"refresh_token\": refresh_token,\n",
|
||
" \"user_id\": user_id_str,\n",
|
||
" \"token_type\": \"bearer\"\n",
|
||
" }\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "67f34ef5",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/old/login\")\n",
|
||
"async def old_login(\n",
|
||
" request: Request,\n",
|
||
" response: Response,\n",
|
||
"):\n",
|
||
" token_old = request.cookies.get('token') or request.cookies.get('v2token')\n",
|
||
" access = await get_access_token(request, response)\n",
|
||
" if token_old and not access:\n",
|
||
" try:\n",
|
||
" headers = {'Authorization': f\"Bearer {token_old}\"}\n",
|
||
" async with httpx.AsyncClient(http2=True, verify=ctx) as client:\n",
|
||
" res = await client.get('https://avtopost.kultmost.ru/api/v1/auth/me', headers=headers)\n",
|
||
" email = res.json()['data']['email']\n",
|
||
" print(email)\n",
|
||
" user = await users_collection.find_one({'email': email})\n",
|
||
" except:\n",
|
||
" return {'ok': False}\n",
|
||
"\n",
|
||
" user_id_str = str(user[\"_id\"])\n",
|
||
" scopes = get_all_user_scopes(user.get(\"roles\", []), user.get(\"extra_scopes\", []))\n",
|
||
"\n",
|
||
" client_ip = request.headers.get('X-Real-IP') or request.client.host\n",
|
||
"\n",
|
||
" access_exp_delta = timedelta(days=ACCESS_TOKEN_EXPIRE_MINUTES)\n",
|
||
" refresh_exp_delta = timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)\n",
|
||
"\n",
|
||
" access_token = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"scopes\": scopes, \"client_id\": 'old_token', \"ip\": client_ip},\n",
|
||
" access_exp_delta\n",
|
||
" )\n",
|
||
" refresh_token = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"type\": \"refresh\", \"client_id\": 'old_token', \"ip\": client_ip},\n",
|
||
" refresh_exp_delta\n",
|
||
" )\n",
|
||
"\n",
|
||
" await redis_client.setex(f\"access:{access_token}\", int(access_exp_delta.total_seconds()), user_id_str)\n",
|
||
" await redis_client.setex(f\"refresh:{hash_token(refresh_token)}\", int(refresh_exp_delta.total_seconds()), user_id_str)\n",
|
||
"\n",
|
||
" session_key = f\"user_sessions:{user_id_str}\"\n",
|
||
" await redis_client.sadd(session_key, f\"access:{access_token}\")\n",
|
||
" await redis_client.sadd(session_key, f\"refresh:{hash_token(refresh_token)}\")\n",
|
||
"\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"access_token\",\n",
|
||
" value=access_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(access_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"refresh_token\",\n",
|
||
" value=refresh_token,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"Strict\",\n",
|
||
" max_age=int(refresh_exp_delta.total_seconds())\n",
|
||
" )\n",
|
||
" return {'ok': True}"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "ff1da72c",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/refresh\", response_model=TokenResponse)\n",
|
||
"async def refresh_token(\n",
|
||
" request: Request,\n",
|
||
" response: Response,\n",
|
||
" req: RefreshRequest\n",
|
||
"):\n",
|
||
" refresh_token = (\n",
|
||
" request.cookies.get(\"refresh_token\") or\n",
|
||
" get_bearer_token(request) or\n",
|
||
" (req.refresh_token if req else None)\n",
|
||
" )\n",
|
||
"\n",
|
||
" if not refresh_token:\n",
|
||
" raise HTTPException(401, \"Refresh token required\")\n",
|
||
"\n",
|
||
" client_id = None\n",
|
||
"\n",
|
||
" if req:\n",
|
||
" client_id = req.client_id\n",
|
||
"\n",
|
||
" refresh_hash = hash_token(refresh_token)\n",
|
||
" user_id_str = await redis_client.get(f\"refresh:{refresh_hash}\")\n",
|
||
"\n",
|
||
" if not user_id_str:\n",
|
||
" raise HTTPException(status_code=401, detail=\"Invalid or expired refresh token\")\n",
|
||
"\n",
|
||
" try:\n",
|
||
" payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM])\n",
|
||
" if payload.get(\"type\") != \"refresh\":\n",
|
||
" raise HTTPException(status_code=401, detail=\"Not a refresh token\")\n",
|
||
" if payload.get(\"sub\") != user_id_str:\n",
|
||
" raise HTTPException(status_code=401, detail=\"Token mismatch\")\n",
|
||
" except jwt.InvalidTokenError as e:\n",
|
||
" raise HTTPException(status_code=401, detail=f\"Invalid refresh token: {str(e)}\")\n",
|
||
"\n",
|
||
" await redis_client.delete(f\"refresh:{refresh_hash}\")\n",
|
||
" user = await users_collection.find_one({\"_id\": ObjectId(user_id_str)})\n",
|
||
"\n",
|
||
" scopes = get_all_user_scopes(user.get(\"roles\", []), user.get(\"extra_scopes\", []))\n",
|
||
"\n",
|
||
" client_ip = request.headers.get('X-Real-IP') or request.client.host\n",
|
||
"\n",
|
||
" access_exp = timedelta(days=ACCESS_TOKEN_EXPIRE_MINUTES)\n",
|
||
" refresh_exp = timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)\n",
|
||
"\n",
|
||
" new_access = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"scopes\": scopes, \"client_id\": client_id, \"ip\": client_ip},\n",
|
||
" access_exp\n",
|
||
" )\n",
|
||
" new_refresh = create_jwt_token(\n",
|
||
" {\"sub\": user_id_str, \"type\": \"refresh\", \"client_id\": client_id, \"ip\": client_ip},\n",
|
||
" refresh_exp\n",
|
||
" )\n",
|
||
"\n",
|
||
" new_refresh_hash = hash_token(new_refresh)\n",
|
||
" await redis_client.setex(f\"access:{new_access}\", int(access_exp.total_seconds()), user_id_str)\n",
|
||
" await redis_client.setex(f\"refresh:{new_refresh_hash}\", int(refresh_exp.total_seconds()), user_id_str)\n",
|
||
"\n",
|
||
" session_key = f\"user_sessions:{user_id_str}\"\n",
|
||
" await redis_client.sadd(session_key, f\"access:{new_access}\")\n",
|
||
" await redis_client.sadd(session_key, f\"refresh:{new_refresh_hash}\")\n",
|
||
"\n",
|
||
"\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"access_token\",\n",
|
||
" value=new_access,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"strict\",\n",
|
||
" max_age=int(access_exp.total_seconds())\n",
|
||
" )\n",
|
||
"\n",
|
||
" response.set_cookie(\n",
|
||
" key=\"refresh_token\",\n",
|
||
" value=new_refresh,\n",
|
||
" httponly=True,\n",
|
||
" secure=PROD,\n",
|
||
" samesite=\"strict\",\n",
|
||
" max_age=int(refresh_exp.total_seconds())\n",
|
||
" )\n",
|
||
" \n",
|
||
"\n",
|
||
"\n",
|
||
" return {\n",
|
||
" \"access_token\": new_access,\n",
|
||
" \"token_type\": \"bearer\",\n",
|
||
" \"refresh_token\": new_refresh\n",
|
||
" }"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "83e1b0f8",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/logout\")\n",
|
||
"async def logout(\n",
|
||
" request: Request,\n",
|
||
" response: Response,\n",
|
||
" current_user: Dict = Depends(get_current_user)\n",
|
||
"):\n",
|
||
" user_id_str = current_user[\"id\"]\n",
|
||
"\n",
|
||
" session_key = f\"user_sessions:{user_id_str}\"\n",
|
||
" tokens = await redis_client.smembers(session_key)\n",
|
||
" for t in tokens:\n",
|
||
" await redis_client.delete(t)\n",
|
||
" await redis_client.delete(session_key)\n",
|
||
"\n",
|
||
" for cookie_name in request.cookies.keys():\n",
|
||
" if cookie_name in ['zoom']:\n",
|
||
" continue\n",
|
||
"\n",
|
||
" for dom in [None,'.kultmost.ru','www.kultmost.ru','avtopost.kultmost.ru','avtopost.kultmost.ru','kultmost.ru']:\n",
|
||
" response.delete_cookie(\n",
|
||
" key=cookie_name,\n",
|
||
" domain=dom,\n",
|
||
" path=\"/\", \n",
|
||
" secure=False, \n",
|
||
" httponly=False,\n",
|
||
" )\n",
|
||
"\n",
|
||
" return {\"message\": \"Logged out from all sessions\"}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "5390f2b5",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/api/me\", response_model=dict)\n",
|
||
"async def get_me(response: Response, current_user: Dict = Security(get_current_user, scopes=[\"read:self\"])):\n",
|
||
" user = display_user(current_user)\n",
|
||
" return bson_clear(user)\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "452fab96",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/healthz\")\n",
|
||
"async def healthz():\n",
|
||
" try:\n",
|
||
" await mongo_client.admin.command('ping')\n",
|
||
" except Exception as e:\n",
|
||
" logging.error(f\"MongoDB is unhealthy: {str(e)}\")\n",
|
||
" raise HTTPException(status_code=500, detail=f\"MongoDB is unhealthy: {str(e)}\")\n",
|
||
"\n",
|
||
" try:\n",
|
||
" await redis_client.ping()\n",
|
||
" except Exception as e:\n",
|
||
" logging.error(f\"Redis is unhealthy: {str(e)}\")\n",
|
||
" raise HTTPException(status_code=500, detail=f\"Redis is unhealthy: {str(e)}\")\n",
|
||
"\n",
|
||
" return {}"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "452fab96",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/registration\", response_class=HTMLResponse)\n",
|
||
"async def index(\n",
|
||
" request: Request,\n",
|
||
" next: str | None = None,\n",
|
||
"):\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"registration.html\", context={'next': next}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "34f4c829",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def get_unique_users(competition_request_col: AsyncIOMotorCollection, competition_id: str, finish: bool = False) -> List[Dict[str, Any]]:\n",
|
||
" # Determine match criteria based on finish parameter\n",
|
||
" status_match = {\"$match\": {\"posts.status\": \"Проходит\"}} if finish else {\"$match\": {}}\n",
|
||
"\n",
|
||
" pipeline = [\n",
|
||
" {\"$match\": {\"competition_id\": competition_id}},\n",
|
||
" {\"$unwind\": \"$user\"},\n",
|
||
" {\"$project\": {\n",
|
||
" \"user\": 1,\n",
|
||
" \"posts\": 1,\n",
|
||
" \"status\": 1\n",
|
||
" }},\n",
|
||
" {\"$unwind\": \"$posts\"},\n",
|
||
" status_match, # Use the conditional match here\n",
|
||
" {\"$group\": {\n",
|
||
" \"_id\": \"$user._id\",\n",
|
||
" \"user\": {\"$first\": \"$user\"},\n",
|
||
" \"unique_posts\": {\"$addToSet\": \"$posts._id\"},\n",
|
||
" \"status\": {\"$first\": \"$status\"}\n",
|
||
" }},\n",
|
||
" {\"$project\": {\n",
|
||
" \"user\": 1,\n",
|
||
" \"post_count\": {\"$size\": \"$unique_posts\"},\n",
|
||
" \"status\": { \"$cond\": {\"if\": {\"$ne\": [\"$status\", None]}, \"then\": \"$status\", \"else\": False } }\n",
|
||
" }}\n",
|
||
" ]\n",
|
||
" \n",
|
||
" cursor = competition_request_col.aggregate(pipeline)\n",
|
||
" results = []\n",
|
||
" async for doc in cursor:\n",
|
||
" results.append(doc)\n",
|
||
" \n",
|
||
" return results\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "df78c145",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from typing import Any, Dict, List\n",
|
||
"from bson import ObjectId\n",
|
||
"from motor.motor_asyncio import AsyncIOMotorCollection\n",
|
||
"\n",
|
||
"\n",
|
||
"async def get_unique_posts_by_user(\n",
|
||
" competition_request_col: AsyncIOMotorCollection,\n",
|
||
" competition_id: str,\n",
|
||
" user_id: str,\n",
|
||
") -> list[dict[str, Any]]:\n",
|
||
" pipeline = [\n",
|
||
" {\"$match\": {\"competition_id\": competition_id, \"user._id\": user_id}},\n",
|
||
" {\"$unwind\": \"$posts\"},\n",
|
||
" {\n",
|
||
" \"$addFields\": {\n",
|
||
" \"status_priority\": {\n",
|
||
" \"$switch\": {\n",
|
||
" \"branches\": [\n",
|
||
" {\"case\": {\"$eq\": [\"$posts.status\", \"Проходит\"]}, \"then\": 1},\n",
|
||
" {\"case\": {\"$eq\": [\"$posts.status\", \"На рассмотрении\"]}, \"then\": 2},\n",
|
||
" ],\n",
|
||
" \"default\": 3,\n",
|
||
" }\n",
|
||
" }\n",
|
||
" }\n",
|
||
" },\n",
|
||
" {\"$sort\": {\"status_priority\": 1, \"posts._id\": 1}}, # tie-breaker if needed\n",
|
||
" {\"$group\": {\"_id\": \"$posts._id\", \"post\": {\"$first\": \"$posts\"}}},\n",
|
||
" {\"$replaceRoot\": {\"newRoot\": \"$post\"}},\n",
|
||
" ]\n",
|
||
"\n",
|
||
" cursor = competition_request_col.aggregate(pipeline)\n",
|
||
" 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"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "8494b389",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# ignore\n",
|
||
"# from typing import Any, Dict, List, Optional\n",
|
||
"# from motor.motor_asyncio import AsyncIOMotorCollection\n",
|
||
"\n",
|
||
"# async def get_unique_posts_by_user(\n",
|
||
"# competition_request_col: AsyncIOMotorCollection,\n",
|
||
"# competition_id: str,\n",
|
||
"# user_id: str,\n",
|
||
"# ) -> List[Dict[str, Any]]:\n",
|
||
"# docs = await competition_request_col.find({\n",
|
||
"# \"competition_id\": competition_id,\n",
|
||
"# \"user._id\": user_id,\n",
|
||
"# }).to_list(length=None)\n",
|
||
"\n",
|
||
"# all_posts: List[Dict[str, Any]] = []\n",
|
||
"# for d in docs:\n",
|
||
"# for p in (d.get(\"posts\") or []):\n",
|
||
"# all_posts.append(p)\n",
|
||
"\n",
|
||
"# def status_priority(status: Optional[str]) -> int:\n",
|
||
"# if status == \"Проходит\":\n",
|
||
"# return 1\n",
|
||
"# if status == \"На рассмотрении\":\n",
|
||
"# return 2\n",
|
||
"# return 3\n",
|
||
"\n",
|
||
"# all_posts.sort(\n",
|
||
"# key=lambda p: (\n",
|
||
"# status_priority(p.get(\"status\")),\n",
|
||
"# str(p.get(\"_id\")),\n",
|
||
"# )\n",
|
||
"# )\n",
|
||
"\n",
|
||
"# # вместо \"$first\" делаем \"$last\": перезаписываем всегда\n",
|
||
"# unique_by_post_id: Dict[Any, Dict[str, Any]] = {}\n",
|
||
"# for p in all_posts:\n",
|
||
"# pid = p.get(\"_id\")\n",
|
||
"# if pid is not None:\n",
|
||
"# unique_by_post_id[pid] = p\n",
|
||
"\n",
|
||
"# posts_ids = list(unique_by_post_id.keys())\n",
|
||
"\n",
|
||
"# cursor = posts_collection.find({\"_id\": {\"$in\": posts_ids}})\n",
|
||
"# return await cursor.to_list(length=None)\n",
|
||
"\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "365dc738",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def get_unique_posts_с(competition_request_col: AsyncIOMotorCollection, competition_id: str, finish: bool = False) -> List[Any]:\n",
|
||
" match_criteria = {\n",
|
||
" \"competition_id\": competition_id,\n",
|
||
" }\n",
|
||
" if finish:\n",
|
||
" match_criteria['posts.status'] = 'Проходит'\n",
|
||
"\n",
|
||
" pipeline = [\n",
|
||
" {\"$match\": match_criteria},\n",
|
||
" {\"$unwind\": \"$posts\"},\n",
|
||
" {\"$match\": {\"posts.status\": \"Проходит\"} if finish else {}},\n",
|
||
" {\n",
|
||
" \"$group\": {\n",
|
||
" \"_id\": \"$posts._id\", # Group by unique post ID\n",
|
||
" \"post\": {\"$first\": \"$posts\"} # Retrieve the first occurrence\n",
|
||
" }\n",
|
||
" },\n",
|
||
" {\"$project\": {\"_id\": 0, \"post\": 1}} # Include the post details\n",
|
||
" ]\n",
|
||
"\n",
|
||
" cursor = competition_request_col.aggregate(pipeline)\n",
|
||
" unique_posts = await cursor.to_list(length=None) # Fetch all unique posts\n",
|
||
" return [unique_post[\"post\"] for unique_post in unique_posts] if unique_posts else []\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "6471770b",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/exhibition/{exhibition_id}\", response_class=HTMLResponse)\n",
|
||
"async def exhibition_members(\n",
|
||
" request: Request,\n",
|
||
" exhibition_id: str,\n",
|
||
" user: Dict = Security(get_user)\n",
|
||
"):\n",
|
||
" exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)})\n",
|
||
" if exhibition:\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",
|
||
" else:\n",
|
||
" raise HTTPException(status_code=307, headers={\"Location\": '/competitions'}, detail=\"Конкурс не найден\")"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "60ae3fa1",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/competition/{competition_id}\", response_class=HTMLResponse)\n",
|
||
"async def competition_members(\n",
|
||
" request: Request,\n",
|
||
" competition_id: str,\n",
|
||
" user: Dict = Security(get_user)\n",
|
||
"):\n",
|
||
" competition = await competitions_col.find_one({'_id': ObjectId(competition_id)})\n",
|
||
" if competition:\n",
|
||
" members = await get_unique_users(competition_request_col, competition_id, competition['status']==2)\n",
|
||
" posts = await get_unique_posts_с(competition_request_col, competition_id, competition['status']==2)\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"competition/index.html\", context={\"id\": 1, \"user\": user, \"competition\": competition, \"members\": members, \"posts\": posts}\n",
|
||
" )\n",
|
||
" else:\n",
|
||
" raise HTTPException(status_code=307, headers={\"Location\": '/competitions'}, detail=\"Конкурс не найден\")"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "fddb4c0b",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/competition/{competition_id}/{member_id}\", response_class=HTMLResponse)\n",
|
||
"async def competition_member(\n",
|
||
" request: Request,\n",
|
||
" competition_id: str,\n",
|
||
" member_id: str,\n",
|
||
" user: Dict = Security(get_user)\n",
|
||
"):\n",
|
||
" competition = await competitions_col.find_one({'_id': ObjectId(competition_id)})\n",
|
||
" if competition:\n",
|
||
" member = await users_collection.find_one({'_id':ObjectId(member_id)})\n",
|
||
" req = await competition_request_col.find_one({'competition_id': competition_id,'user._id': member_id})\n",
|
||
" member['status'] = req.get('status', False)\n",
|
||
" if member:\n",
|
||
" posts = await get_unique_posts_by_user(competition_request_col, competition_id, member_id)\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"competition/member/index.html\", context={\"id\": 1, \"user\": user, \"competition\": competition, 'member':member, 'posts': posts}\n",
|
||
" )\n",
|
||
" else:\n",
|
||
" raise HTTPException(status_code=307, headers={\"Location\": f'/competition/{competition._id}'}, detail=\"Участник не найден\")\n",
|
||
" else:\n",
|
||
" raise HTTPException(status_code=307, headers={\"Location\": '/competitions'}, detail=\"Конкурс не найден\")"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "cf18ebc8",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def _serialize(doc):\n",
|
||
" doc = doc.copy()\n",
|
||
" if \"_id\" in doc:\n",
|
||
" doc[\"_id\"] = str(doc[\"_id\"])\n",
|
||
" if \"user\" in doc:\n",
|
||
" doc[\"user\"] = {'img': doc[\"user\"].get('img'), 'username': doc[\"user\"].get('username')}\n",
|
||
" # if \"_id\" in doc[\"user\"]:\n",
|
||
" # doc[\"user\"][\"_id\"] = str(doc[\"user\"][\"_id\"])\n",
|
||
" # doc[\"user\"][\"notify\"] = []\n",
|
||
" return doc\n",
|
||
"\n",
|
||
"@app.get(\"/api/competitions\", response_model=List)\n",
|
||
"async def get_competition():\n",
|
||
" competitions = []\n",
|
||
" current_time = datetime.now(timezone.utc) + timedelta(hours=3)\n",
|
||
"\n",
|
||
" async for doc in competitions_col.find({\n",
|
||
" 'datetime_start': {'$lt': current_time}\n",
|
||
" },{'fb':0,'inst':0,'max':0,'tg':0,'vk':0}).sort('_id',-1).limit(32):\n",
|
||
" competitions.append(bson_clear(doc))\n",
|
||
" return competitions"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "769746f1",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/exhibitions\", response_class=HTMLResponse)\n",
|
||
"async def exhibitions_all(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_user)\n",
|
||
"):\n",
|
||
" current_time = datetime.now(timezone.utc) + timedelta(hours=3)\n",
|
||
" find_list = [\n",
|
||
" {'datetime_start': {'$lt': current_time}},\n",
|
||
" {'user._id': user.get('id','') if user else ''}\n",
|
||
" ]\n",
|
||
" if user and 'admin' in user.get('roles',[]):\n",
|
||
" find_list.append({'datetime_start': {'$gte': current_time}})\n",
|
||
" \n",
|
||
" exhibitions = await exhibition_collection.find({\n",
|
||
" '$or': find_list,\n",
|
||
" 'prod': PROD\n",
|
||
" }).sort({'_id':-1}).limit(1000).to_list()\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"exhibitions.html\", context={\"id\": 1, \"user\": user, \"exhibitions\": exhibitions, 'now': datetime.now()}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "5713a63a",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/competitions\", response_class=HTMLResponse)\n",
|
||
"async def competition(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_user)\n",
|
||
"):\n",
|
||
" competitions = []\n",
|
||
" current_time = datetime.now(timezone.utc) + timedelta(hours=3)\n",
|
||
" find_list = [\n",
|
||
" {'datetime_start': {'$lt': current_time}},\n",
|
||
" {'user._id': user.get('id','') if user else ''}\n",
|
||
" ]\n",
|
||
" if user and 'admin' in user.get('roles',[]):\n",
|
||
" find_list.append({'datetime_start': {'$gte': current_time}})\n",
|
||
" \n",
|
||
" async for doc in competitions_col.find({\n",
|
||
" '$or': find_list\n",
|
||
" } if PROD else {}).sort({'_id':-1}).limit(1000):\n",
|
||
" competitions.append(doc)\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"competitions.html\", context={\"id\": 1, \"user\": user, \"competitions\": competitions}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "853822a3",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"router = APIRouter()\n",
|
||
"\n",
|
||
"\n",
|
||
"@router.post(\"/api/restore\")\n",
|
||
"async def restore_api(\n",
|
||
" request: Request,\n",
|
||
" restore_data: RestoreRequest = Body(...),\n",
|
||
"):\n",
|
||
" # 1. Валидация и очистка входных данных\n",
|
||
" identifier = restore_data.identifier.strip()\n",
|
||
" \n",
|
||
" if not identifier or not is_valid_email(identifier):\n",
|
||
" raise HTTPException(status_code=400, detail=\"Некорректный формат email\")\n",
|
||
"\n",
|
||
" # 2. Проверка анти-спама / ограничения частоты запросов\n",
|
||
" client_ip = request.client.host\n",
|
||
" rate_limit_key = f\"rate_limit:restore:{identifier}:{client_ip}\"\n",
|
||
" \n",
|
||
" # Проверяем, запрашивал ли пользователь сброс недавно (например, за последние 60 секунд)\n",
|
||
" existing_request = await redis_client.get(rate_limit_key)\n",
|
||
" \n",
|
||
" if existing_request:\n",
|
||
" # Опционально: получаем TTL, чтобы сообщить пользователю, когда можно повторить попытку\n",
|
||
" ttl = await redis_client.ttl(rate_limit_key)\n",
|
||
" raise HTTPException(\n",
|
||
" status_code=429, \n",
|
||
" detail=f\"Слишком много запросов. Попробуйте через {ttl} секунд.\"\n",
|
||
" )\n",
|
||
"\n",
|
||
" # Устанавливаем флаг ограничения частоты (истекает через 60 секунд)\n",
|
||
" await redis_client.set(rate_limit_key, \"1\", ex=60)\n",
|
||
"\n",
|
||
" # 3. Поиск пользователя\n",
|
||
" user = await users_collection.find_one({'$or': [\n",
|
||
" {'email': identifier}, \n",
|
||
" {'email': identifier.lower()}\n",
|
||
" ]})\n",
|
||
" \n",
|
||
" if not user:\n",
|
||
" return {'ok': True}\n",
|
||
" \n",
|
||
" # 4. Генерация токена\n",
|
||
" token = secrets.token_urlsafe(32)\n",
|
||
" expires_at = datetime.now(timezone.utc) + timedelta(hours=1)\n",
|
||
" \n",
|
||
" token_data = {\n",
|
||
" \"user_id\": str(user[\"_id\"]),\n",
|
||
" \"email\": user[\"email\"],\n",
|
||
" \"next\": restore_data.next,\n",
|
||
" \"expires_at\": expires_at.isoformat()\n",
|
||
" }\n",
|
||
" \n",
|
||
" # 5. Сохранение токена сброса в Redis\n",
|
||
" reset_token_key = f\"reset_token:{token}\"\n",
|
||
" await redis_client.set(reset_token_key, json.dumps(token_data), ex=3600)\n",
|
||
"\n",
|
||
" # 6. Генерация ссылки для сброса\n",
|
||
" base_url = str(request.base_url).rstrip('/')\n",
|
||
" reset_link = f\"{base_url}/restore/{token}\"\n",
|
||
"\n",
|
||
" # 7. Асинхронная отправка email\n",
|
||
" try:\n",
|
||
" email_task.notify.delay(\n",
|
||
" to_email=user['email'],\n",
|
||
" data={\n",
|
||
" 'link': reset_link, \n",
|
||
" 'email': user['email']\n",
|
||
" },\n",
|
||
" subject='Восстановление пароля',\n",
|
||
" template='restore.html'\n",
|
||
" )\n",
|
||
" except Exception as e:\n",
|
||
" # Логирование ошибки\n",
|
||
" print(f\"Не удалось отправить письмо: {e}\")\n",
|
||
" # В зависимости от политики, вы можете захотеть удалить токен, если письмо не отправлено\n",
|
||
" await redis_client.delete(reset_token_key)\n",
|
||
" raise HTTPException(status_code=500, detail=\"Ошибка отправки письма\")\n",
|
||
"\n",
|
||
" return {'ok': True}\n",
|
||
"\n",
|
||
"\n",
|
||
"@router.get(\"/restore/{token}\", response_class=HTMLResponse)\n",
|
||
"async def restore_page(request: Request, token: str):\n",
|
||
" token_key = f\"reset_token:{token}\"\n",
|
||
" token_data = await redis_client.get(token_key)\n",
|
||
" \n",
|
||
" if not token_data:\n",
|
||
" return HTMLResponse(\n",
|
||
" \"<h2 style='text-align:center;margin-top:100px;'>Ссылка недействительна или истекла</h2>\",\n",
|
||
" status_code=400\n",
|
||
" )\n",
|
||
" \n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"restore_token.html\", context={\"token\": token}\n",
|
||
" )\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.post(\"/api/reset-hash-password\")\n",
|
||
"async def reset_hash_password_user(\n",
|
||
" request: Request,\n",
|
||
" response: Response,\n",
|
||
" data: ResetPasswordRequest = Body(...),\n",
|
||
" current_user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" user_id_str = current_user[\"id\"]\n",
|
||
"\n",
|
||
" session_key = f\"user_sessions:{user_id_str}\"\n",
|
||
" tokens = await redis_client.smembers(session_key)\n",
|
||
" for t in tokens:\n",
|
||
" await redis_client.delete(t)\n",
|
||
" await redis_client.delete(session_key)\n",
|
||
"\n",
|
||
" for cookie_name in request.cookies.keys():\n",
|
||
" if cookie_name in ['zoom']:\n",
|
||
" continue\n",
|
||
"\n",
|
||
" for dom in [None,'.kultmost.ru','www.kultmost.ru','avtopost.kultmost.ru','avtopost.kultmost.ru','kultmost.ru']:\n",
|
||
" response.delete_cookie(\n",
|
||
" key=cookie_name,\n",
|
||
" domain=dom,\n",
|
||
" path=\"/\", \n",
|
||
" secure=False, \n",
|
||
" httponly=False,\n",
|
||
" )\n",
|
||
" hashed_password = get_password_hash(data.new_password)\n",
|
||
" \n",
|
||
" await users_collection.update_one(\n",
|
||
" {\"_id\": ObjectId(user_id_str)},\n",
|
||
" {\"$set\": {\"hashed_password\": hashed_password}}\n",
|
||
" )\n",
|
||
" \n",
|
||
" return {\"ok\": True, \"message\": \"Пароль успешно изменён\", \"next\": '/profile'}\n",
|
||
"\n",
|
||
"\n",
|
||
"@router.post(\"/api/reset-password\")\n",
|
||
"async def reset_password(\n",
|
||
" data: ResetPasswordRequest = Body(...),\n",
|
||
"):\n",
|
||
" token_key = f\"reset_token:{data.token}\"\n",
|
||
" token_data_str = await redis_client.get(token_key)\n",
|
||
" \n",
|
||
" if not token_data_str:\n",
|
||
" raise HTTPException(status_code=400, detail=\"Токен недействителен или истёк\")\n",
|
||
" \n",
|
||
" token_data = json.loads(token_data_str)\n",
|
||
"\n",
|
||
" hashed_password = get_password_hash(data.new_password)\n",
|
||
" \n",
|
||
" await users_collection.update_one(\n",
|
||
" {\"_id\": ObjectId(token_data[\"user_id\"])},\n",
|
||
" {\"$set\": {\"hashed_password\": hashed_password}}\n",
|
||
" )\n",
|
||
" \n",
|
||
" await redis_client.delete(token_key)\n",
|
||
" \n",
|
||
" return {\"ok\": True, \"message\": \"Пароль успешно изменён\", \"next\": token_data.get('next', '/profile')}\n",
|
||
"\n",
|
||
"\n",
|
||
"@router.get(\"/restore\", response_class=HTMLResponse)\n",
|
||
"async def restore_init(\n",
|
||
" request: Request,\n",
|
||
" next: str | None = None,\n",
|
||
"):\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"restore.html\", context={'next': next}\n",
|
||
" )\n",
|
||
"\n",
|
||
"\n",
|
||
"app.include_router(router, tags=['Восстановление пароля'])"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "eafaa621",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/login\", response_class=HTMLResponse)\n",
|
||
"async def login_(\n",
|
||
" request: Request,\n",
|
||
" next_url: str | None = None,\n",
|
||
"):\n",
|
||
" if not next_url:\n",
|
||
" next_url = request.headers.get(\"Referer\")\n",
|
||
" context={\"next\": next_url}\n",
|
||
" print(next_url)\n",
|
||
" if next_url and 'profile/competition' in next_url:\n",
|
||
" context.update({'notify':{'text':'Участвовать в конкурсе могут только пользователи сервиса! Пожалуйста, войдите или зарегистрируйтесь.', 'status':'info','duration':8000}})\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request,\n",
|
||
" name=\"login.html\",\n",
|
||
" context=context,\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "b61deb40",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/switch-profile\")\n",
|
||
"async def switch_profile(request: Request,\n",
|
||
" response: Response,user: Dict = Security(get_current_user)):\n",
|
||
" await users_collection.update_one({'_id':ObjectId(user.get('_id'))},{'$set':{'profile':'' if user.get('profile',False) else 'partner'}})\n",
|
||
" return {'ok': True}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "b22ace38",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/post/description\")\n",
|
||
"async def post_edit_description(\n",
|
||
" data: PostEditDescription = Body(...),\n",
|
||
" current_user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" await posts_collection.update_one(\n",
|
||
" {'_id':ObjectId(data.post_id), 'user._id': current_user.get('_id')},\n",
|
||
" {'$set': {'description': data.description}}\n",
|
||
" )\n",
|
||
" return {'ok': True}"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "dbd15e8b",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.delete(\"/api/partner/exhibition/delete\")\n",
|
||
"async def partner_exhibition_delete(\n",
|
||
" data: PartnerExhibitionDelete = Body(...),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
|
||
"):\n",
|
||
" exhibition = await exhibition_collection.find_one_and_delete(\n",
|
||
" {'_id':ObjectId(data.exhibition_id), 'user._id': current_user.get('_id')},\n",
|
||
" )\n",
|
||
" for i in exhibition.get('tg',[]):\n",
|
||
" tg.delete_from_tg.delay(i.get('result',[{}])[0].get('message_id'), i.get('doc'))\n",
|
||
" for i in exhibition.get('vk',[]):\n",
|
||
" vk.delete_wall.delay(i.get('response',{}).get('post_id'), i.get('doc'))\n",
|
||
" return {'ok': True}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "5c514008",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.delete(\"/api/partner/competition/delete\")\n",
|
||
"async def partner_competition_delete(\n",
|
||
" data: Partnercompetitionstop = Body(...),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
|
||
"):\n",
|
||
" competition = await competitions_col.delete_one(\n",
|
||
" {'_id':ObjectId(data.competition_id), 'user._id': current_user.get('_id')},\n",
|
||
" )\n",
|
||
" for i in competition.get('tg',[]):\n",
|
||
" tg.delete_from_tg.delay(i.get('result',[{}])[0].get('message_id'), i.get('doc'))\n",
|
||
" for i in competition.get('vk',[]):\n",
|
||
" vk.delete_wall.delay(i.get('response',{}).get('post_id'), i.get('doc'))\n",
|
||
" return {'ok': True}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "0a6e98ee",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/partner/competition/finish\")\n",
|
||
"async def partner_competition_finish(\n",
|
||
" data: Partnercompetitionstop = Body(...),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
|
||
"):\n",
|
||
" await competitions_col.update_one(\n",
|
||
" {'_id':ObjectId(data.competition_id), 'user._id': current_user.get('_id')},\n",
|
||
" {'$set': {'status': 2}},\n",
|
||
" )\n",
|
||
" return {'ok': True}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "e5d0e790",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/partner/competition/stop\")\n",
|
||
"async def partner_competition_stop(\n",
|
||
" data: Partnercompetitionstop = Body(...),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
|
||
"):\n",
|
||
" await competitions_col.update_one(\n",
|
||
" {'_id':ObjectId(data.competition_id), 'user._id': current_user.get('_id')},\n",
|
||
" {'$set': {'status': 1}},\n",
|
||
" )\n",
|
||
" return {'ok': True}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "9397d524",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/partner/competition/select\")\n",
|
||
"async def partner_competition_request_artist(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" data: CompetitionArtistRequest = Body(...),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
|
||
"):\n",
|
||
" print(data)\n",
|
||
" competition = await competitions_col.find_one({'_id':ObjectId(data.competition_id), 'user._id': current_user.get('_id')})\n",
|
||
" if competition:\n",
|
||
" await competition_request_col.update_many(\n",
|
||
" {'user._id': data.user_id, 'competition_id': data.competition_id},\n",
|
||
" {'$set': {'status': True,\"posts.$[].status\": 'Проходит' }},\n",
|
||
" )\n",
|
||
" background_tasks.add_task(accept_competition_req, competition, data.user_id)\n",
|
||
" social_vk = await social_collection.find_one({'user_id':current_user.get('_id'), 'group': {\"$exists\": False}, 'type':'vk'})\n",
|
||
" user = display_user(current_user)\n",
|
||
" user['social_vk'] = social_vk\n",
|
||
" await invitations_collection.update_one({\n",
|
||
" \"user_id\": data.user_id,\n",
|
||
" \"item\": competition.get('title'),\n",
|
||
" \"title\": \"Вы прошли отбор\",\n",
|
||
" \"type_\": competition.get(\"type_\"),\n",
|
||
" \"user\": user,\n",
|
||
" \"prod\": PROD,\n",
|
||
" \"type\": \"Конкурс\"\n",
|
||
" },{'$set':{\n",
|
||
" \"user_id\": data.user_id,\n",
|
||
" \"item\": competition.get('title'),\n",
|
||
" \"title\": \"Вы прошли отбор\",\n",
|
||
" \"type_\": competition.get(\"type_\"),\n",
|
||
" \"user\": user,\n",
|
||
" \"prod\": PROD,\n",
|
||
" \"type\": \"Конкурс\"\n",
|
||
" }},upsert=True)\n",
|
||
" return {'ok': True}\n",
|
||
" else:\n",
|
||
" return {'ok': False}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "032576ee",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from fastapi import HTTPException, BackgroundTasks, Body, Security\n",
|
||
"from bson import ObjectId\n",
|
||
"from typing import Dict\n",
|
||
"\n",
|
||
"@app.post(\n",
|
||
" \"/api/partner/competition/request\",\n",
|
||
" response_model=dict,\n",
|
||
" summary=\"Обновление статуса заявки участника в конкурсе (для партнёра)\",\n",
|
||
" description=\"\"\"\n",
|
||
" Позволяет владельцу конкурса обновить статус конкретного поста в заявке.\n",
|
||
" \n",
|
||
" - Проверяет права владельца конкурса.\n",
|
||
" - Обновляет статус поста в массиве `posts`.\n",
|
||
" - Если после обновления все посты в заявке имеют статус \"Проходит\" или \"На рассмотрении\" — \n",
|
||
" заявка помечается `notify: True` и запускается фоновая обработка.\n",
|
||
" \"\"\",\n",
|
||
" status_code=200,\n",
|
||
" tags=[\"Partner\", \"Competition\", \"Requests\"],\n",
|
||
")\n",
|
||
"async def partner_competition_request(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" data: PartnercompetitionRequest = Body(...),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
|
||
"):\n",
|
||
" \"\"\"\n",
|
||
" Обработчик обновления статуса поста в заявке на конкурс.\n",
|
||
"\n",
|
||
" Flow:\n",
|
||
" 1. Проверка владения конкурсом.\n",
|
||
" 2. Массовое обновление статуса поста в массиве заявок.\n",
|
||
" 3. Проверка полностью одобренных заявок → установка `notify` + фоновая задача.\n",
|
||
" \"\"\"\n",
|
||
" # Проверка прав владельца конкурса\n",
|
||
" competition = await competitions_col.find_one({\n",
|
||
" '_id': ObjectId(data.competition_id),\n",
|
||
" 'user._id': current_user.get('_id')\n",
|
||
" })\n",
|
||
"\n",
|
||
" if not competition:\n",
|
||
" raise HTTPException(\n",
|
||
" status_code=403,\n",
|
||
" detail=\"Конкурс не найден или у вас нет прав на его редактирование\"\n",
|
||
" )\n",
|
||
"\n",
|
||
" # Обновляем статус конкретного поста\n",
|
||
" await competition_request_col.update_many(\n",
|
||
" {\n",
|
||
" 'posts._id': ObjectId(data.post_id),\n",
|
||
" 'competition_id': data.competition_id\n",
|
||
" },\n",
|
||
" {'$set': {'posts.$[p].status': data.status}},\n",
|
||
" array_filters=[{'p._id': ObjectId(data.post_id)}]\n",
|
||
" )\n",
|
||
"\n",
|
||
" posts_list = []\n",
|
||
" user = None\n",
|
||
"\n",
|
||
" async for req in competition_request_col.find({\n",
|
||
" 'posts._id': ObjectId(data.post_id),\n",
|
||
" 'competition_id': data.competition_id,\n",
|
||
" }):\n",
|
||
" if not user:\n",
|
||
" user = req.get('user')\n",
|
||
" \n",
|
||
" posts_list.extend(req.get('posts', []))\n",
|
||
"\n",
|
||
" unique_posts = {str(p.get('_id')): p for p in posts_list}.values()\n",
|
||
" \n",
|
||
" background_tasks.add_task(update_competition_req, competition, user, unique_posts)\n",
|
||
"\n",
|
||
"\n",
|
||
" return {'ok': True}"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "25fec0c7",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def _update_post_description(post):\n",
|
||
" if post.get(\"description\"):\n",
|
||
" await posts_collection.update_one(\n",
|
||
" {\"_id\": ObjectId(post[\"postId\"])},\n",
|
||
" {\"$set\": {\"description\": post[\"description\"]}}\n",
|
||
" )\n",
|
||
"\n",
|
||
"\n",
|
||
"async def background_request_competition(posts: List[dict]):\n",
|
||
" coros = [_update_post_description(p) for p in posts]\n",
|
||
" if coros:\n",
|
||
" await asyncio.gather(*coros)"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "b44b4b2e",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/send_competition_request/{competition_id}\")\n",
|
||
"async def send_competition_request(\n",
|
||
" request: Request,\n",
|
||
" competition_id: str,\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" user: Dict = Security(get_current_user),\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" data = await request.json()\n",
|
||
" except Exception:\n",
|
||
" raise HTTPException(status_code=400, detail=\"Invalid JSON\")\n",
|
||
"\n",
|
||
" posts_input = data.get(\"posts\") or []\n",
|
||
" if not posts_input:\n",
|
||
" raise HTTPException(status_code=400, detail=\"Поле 'posts' обязательно\")\n",
|
||
"\n",
|
||
" # Build mapping: postId -> nomination (if provided)\n",
|
||
" nomination_map: dict[str, str | None] = {}\n",
|
||
" post_ids: list[str] = []\n",
|
||
"\n",
|
||
" for p in posts_input:\n",
|
||
" post_id = p.get(\"postId\")\n",
|
||
" if post_id:\n",
|
||
" post_ids.append(post_id)\n",
|
||
" nomination_map[post_id] = p.get(\"nomination\") # can be str or None\n",
|
||
"\n",
|
||
" unique_post_ids = list(dict.fromkeys(post_ids))\n",
|
||
"\n",
|
||
" if not unique_post_ids:\n",
|
||
" raise HTTPException(status_code=400, detail=\"Нет валидных postId\")\n",
|
||
"\n",
|
||
" # Validate ObjectIds\n",
|
||
" try:\n",
|
||
" obj_ids = [ObjectId(pid) for pid in unique_post_ids]\n",
|
||
" except Exception:\n",
|
||
" raise HTTPException(status_code=400, detail=\"Некорректный формат postId\")\n",
|
||
"\n",
|
||
" # Check already used posts\n",
|
||
" used_post_ids: set[str] = set()\n",
|
||
" async for req in competition_request_col.find({\n",
|
||
" \"user._id\": user[\"_id\"],\n",
|
||
" \"competition_id\": competition_id\n",
|
||
" }):\n",
|
||
" for post in req.get(\"posts\", []):\n",
|
||
" if post_id := post.get(\"_id\"):\n",
|
||
" used_post_ids.add(str(post_id))\n",
|
||
"\n",
|
||
" new_obj_ids = [oid for oid in obj_ids if str(oid) not in used_post_ids]\n",
|
||
" excluded_count = len(obj_ids) - len(new_obj_ids)\n",
|
||
"\n",
|
||
" if not new_obj_ids:\n",
|
||
" return {\"ok\": True, \"message\": \"Все посты уже были отправлены ранее\"}\n",
|
||
"\n",
|
||
" # Fetch full posts\n",
|
||
" cursor = posts_collection.find({\"_id\": {\"$in\": new_obj_ids}})\n",
|
||
" fetched_posts = [doc async for doc in cursor]\n",
|
||
"\n",
|
||
" # Enrich posts with nomination from input\n",
|
||
" for post in fetched_posts:\n",
|
||
" post_id_str = str(post.get(\"_id\"))\n",
|
||
" if nomination := nomination_map.get(post_id_str):\n",
|
||
" post[\"nomination\"] = nomination\n",
|
||
"\n",
|
||
" doc = {\n",
|
||
" \"user\": display_user(user),\n",
|
||
" \"competition_id\": competition_id,\n",
|
||
" \"posts\": fetched_posts,\n",
|
||
" \"created_at\": datetime.utcnow(),\n",
|
||
" }\n",
|
||
"\n",
|
||
" result = await competition_request_col.insert_one(doc)\n",
|
||
"\n",
|
||
" # Background tasks\n",
|
||
" background_tasks.add_task(background_request_competition, posts_input) # pass original input\n",
|
||
" background_tasks.add_task(new_competiton_req, bson_clear(doc))\n",
|
||
"\n",
|
||
" response = {\n",
|
||
" \"ok\": True,\n",
|
||
" \"inserted_id\": str(result.inserted_id)\n",
|
||
" }\n",
|
||
"\n",
|
||
" if excluded_count > 0:\n",
|
||
" response[\"excluded\"] = excluded_count\n",
|
||
" response[\"message\"] = f\"Исключено {excluded_count} уже использованных постов\"\n",
|
||
"\n",
|
||
" return response"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "11de4903",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/news\", response_class=HTMLResponse)\n",
|
||
"async def profile_news(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" news = [doc async for doc in news_collection.find({'user._id': user.get('_id')}).sort('_id',-1).limit(1000)]\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/news.html\", context={ \"user\": user, 'news': news}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "7cda79dd",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/form\", response_class=HTMLResponse)\n",
|
||
"async def profile_form(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/form.html\", context={ \"user\": user}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "2827fcab",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def competitions(user,request):\n",
|
||
" data=[]\n",
|
||
" async for i in competitions_col.find({'user._id': user.get('_id')}).sort('_id',-1).limit(1000):\n",
|
||
" data.append(i)\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/partner/competitions.html\", context={ \"user\": user, 'cards': data}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "220f51ed",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/partner/competitions\", response_class=HTMLResponse)\n",
|
||
"async def profile_partner_competitions(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
|
||
"):\n",
|
||
" await users_collection.update_one({'_id':ObjectId(user.get('_id'))},{'$set':{'profile':'partner'}})\n",
|
||
" user['profile'] = 'partner'\n",
|
||
" return await competitions(user,request)"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "afabbc56",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def get_unique_posts(competition_request_col: AsyncIOMotorCollection, competition_id: str) -> List[Dict[str, Any]]:\n",
|
||
" pipeline = [\n",
|
||
" {\"$match\": {\"competition_id\": competition_id}},\n",
|
||
" {\"$project\": {\"posts\": 1, \"_id\": 0}},\n",
|
||
" {\"$unwind\": \"$posts\"},\n",
|
||
" {\"$group\": {\n",
|
||
" \"_id\": \"$posts._id\", # group by post _id for uniqueness\n",
|
||
" \"post\": {\"$first\": \"$posts\"}\n",
|
||
" }},\n",
|
||
" {\"$addFields\": {\n",
|
||
" \"status_order\": {\n",
|
||
" \"$switch\": {\n",
|
||
" \"branches\": [\n",
|
||
" {\"case\": {\"$eq\": [\"$post.status\", 'Проходит']}, \"then\": 2},\n",
|
||
" {\"case\": {\"$eq\": [\"$post.status\", 'На рассмотрении']}, \"then\": 3}\n",
|
||
" ],\n",
|
||
" \"default\": 1\n",
|
||
" }\n",
|
||
" }\n",
|
||
" }},\n",
|
||
" {\"$sort\": {\"status_order\": 1}},\n",
|
||
" {\"$project\": {\"post\": 1, \"_id\": 0}} \n",
|
||
" ]\n",
|
||
" \n",
|
||
" cursor = competition_request_col.aggregate(pipeline)\n",
|
||
" results = []\n",
|
||
" \n",
|
||
" async for doc in cursor:\n",
|
||
" results.append(doc['post']) # Adjust based on the added $project stage\n",
|
||
" return results\n",
|
||
"\n",
|
||
"\n",
|
||
"async def add_competition_id_to_posts(posts, competition_id):\n",
|
||
" oid = ObjectId(competition_id)\n",
|
||
" for post in posts:\n",
|
||
" post['competition'] = oid\n",
|
||
" return posts\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "6ce1690c",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def get_unique_users_p(competition_request_col: AsyncIOMotorCollection, competition_id: str) -> List[Dict[str, Any]]:\n",
|
||
" pipeline = [\n",
|
||
" {\"$match\": {\"competition_id\": competition_id}},\n",
|
||
" {\"$unwind\": \"$user\"},\n",
|
||
" {\"$project\": {\n",
|
||
" \"user\": 1,\n",
|
||
" \"posts\": 1,\n",
|
||
" \"status\": 1\n",
|
||
" }},\n",
|
||
" {\"$unwind\": \"$posts\"},\n",
|
||
" {\"$group\": {\n",
|
||
" \"_id\": \"$user._id\",\n",
|
||
" \"user\": {\"$first\": \"$user\"},\n",
|
||
" \"unique_posts\": {\"$addToSet\": \"$posts._id\"},\n",
|
||
" \"status\": {\"$first\": \"$status\"}\n",
|
||
" }},\n",
|
||
" {\"$project\": {\n",
|
||
" \"user\": 1,\n",
|
||
" \"post_count\": {\"$size\": \"$unique_posts\"},\n",
|
||
" \"status\": { \"$cond\": { \"if\": {\"$ne\": [\"$status\", None]}, \"then\": \"$status\", \"else\": False}}\n",
|
||
" }},\n",
|
||
" {\"$sort\": {\"status\": 1, \"_id\": 1}}\n",
|
||
" ]\n",
|
||
" \n",
|
||
" cursor = competition_request_col.aggregate(pipeline)\n",
|
||
" results = []\n",
|
||
" async for doc in cursor:\n",
|
||
" results.append(doc)\n",
|
||
" \n",
|
||
" return results\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "5e4a0d74",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/partner/competition/{competition_id}\", response_class=HTMLResponse)\n",
|
||
"async def profile_partner_competition(\n",
|
||
" request: Request,\n",
|
||
" competition_id: str,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" competition = await competitions_col.find_one({'_id': ObjectId(competition_id), 'user._id': user.get('_id')})\n",
|
||
" if not competition:\n",
|
||
" raise HTTPException(status_code=403, detail=\"Not enough permissions\")\n",
|
||
" \n",
|
||
" members = await get_unique_users_p(competition_request_col, competition_id)\n",
|
||
"\n",
|
||
" posts = await get_unique_posts(competition_request_col, competition_id)\n",
|
||
" posts = await add_competition_id_to_posts(posts, competition_id) \n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/partner/competition.html\", context={ \"user\": user, \"competition\": competition, 'posts': posts, \"members\": members}\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "ba9996c8",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/partner/competition/{competition_id}/{member_id}\", response_class=HTMLResponse)\n",
|
||
"async def profile_partner_competition_member(\n",
|
||
" request: Request,\n",
|
||
" competition_id: str,\n",
|
||
" member_id: str,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" competition = await competitions_col.find_one({'_id': ObjectId(competition_id), 'user._id': user.get('_id')})\n",
|
||
" if not competition:\n",
|
||
" raise HTTPException(status_code=403, detail=\"Not enough permissions\")\n",
|
||
" \n",
|
||
" social_vk = await social_collection.find_one({'user_id':member_id,'type':'vk','group':{\"$exists\": False}})\n",
|
||
" \n",
|
||
" member = await users_collection.find_one({'_id':ObjectId(member_id)})\n",
|
||
" req = await competition_request_col.find_one({'competition_id': competition_id,'user._id': member_id})\n",
|
||
" member['status'] = req.get('status', False)\n",
|
||
" posts = await get_unique_posts_by_user(competition_request_col, competition_id, member_id)\n",
|
||
" posts = await add_competition_id_to_posts(posts, competition_id) \n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/partner/competition_member.html\", context={ \"user\": user, \"social_vk\":social_vk, \"competition\": competition, 'posts': posts, \"member\": member}\n",
|
||
" )\n",
|
||
" "
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "d5500761",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/competition\", response_class=HTMLResponse)\n",
|
||
"async def profile_competitions(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" competitions = []\n",
|
||
" current_time = datetime.now(timezone.utc) + timedelta(hours=3)\n",
|
||
"\n",
|
||
" async for i in competitions_col.find({\n",
|
||
" '$or': [\n",
|
||
" {'datetime_start': {'$lt': current_time}},\n",
|
||
" {'user._id': user.get('_id')}\n",
|
||
" ]\n",
|
||
" } if PROD else {}).sort('_id', -1).limit(1000):\n",
|
||
" competitions.append(i)\n",
|
||
" \n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/competition.html\", context={ \"user\": user, 'competitions': competitions}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "c01c6aa7",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/competition/{competition_id}\", response_class=HTMLResponse)\n",
|
||
"async def profile_competition(\n",
|
||
" request: Request,\n",
|
||
" competition_id: str,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" # required_keys = [\n",
|
||
" # \"biography\",\n",
|
||
" # \"messengers\",\n",
|
||
" # \"style\",\n",
|
||
" # \"material_manufacturer\",\n",
|
||
" # \"genres\",\n",
|
||
" # \"education\", \"material_manufacturer\",\n",
|
||
" # \"art_types\", \"material_use\",\n",
|
||
" # \"unions\", \"master_classes\",\n",
|
||
" # \"style\", \"workshop_tour\",\n",
|
||
" # \"specialization\",\n",
|
||
" # ]\n",
|
||
"\n",
|
||
" # if all(user.get(key) for key in required_keys):\n",
|
||
" competition = await competitions_col.find_one({'_id':ObjectId(competition_id)})\n",
|
||
" posts=[]\n",
|
||
" async for i in posts_collection.find({'user._id': user.get('_id')}).sort('_id',-1).limit(1000):\n",
|
||
" posts.append(i)\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/competition/index.html\", context={\n",
|
||
" \"user\": user, 'competition': competition, 'posts': posts,\n",
|
||
" 'notify':{'text':'Выберите картины на конкурс','status':'info','duration':3000}\n",
|
||
" }\n",
|
||
" )\n",
|
||
" # else:\n",
|
||
" # return templates.TemplateResponse(\n",
|
||
" # request=request, name=\"profile/form.html\", context={ \"user\": user, 'notify':{'text':'В начале заполните анкету под звездочками','status':'error','duration':8000}}\n",
|
||
" # )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "3eb9e736",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile\", response_class=HTMLResponse)\n",
|
||
"async def profile(\n",
|
||
" request: Request,\n",
|
||
" response: Response,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"): \n",
|
||
" if user.get('profile',False):\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/index.html\", context={ \"user\": user, 'notify': {'text': 'Кабинет партнера', 'status': 'info', 'duration': 1500}}\n",
|
||
" )\n",
|
||
" else:\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/index.html\", context={ \"user\": user}\n",
|
||
" )\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.get(\"/profile/partner\", response_class=HTMLResponse)\n",
|
||
"async def profile(\n",
|
||
" request: Request,\n",
|
||
" response: Response,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"): \n",
|
||
" if user.get('profile',False):\n",
|
||
" return await competitions(user,request)\n",
|
||
" else:\n",
|
||
" return await posts_temp(user,request)\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.get(\"/profile/posts\", response_class=HTMLResponse)\n",
|
||
"async def profile_posts(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" return await posts_temp(user,request)\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.get(\"/profile/password\", response_class=HTMLResponse)\n",
|
||
"async def profile_password(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/password.html\", context={\"user\": user}\n",
|
||
" )\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.get(\"/profile/exhibition\", response_class=HTMLResponse)\n",
|
||
"async def profile_exhibition(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" # data = exhibition_collection.\n",
|
||
" data = await exhibition_collection.find({}).sort('_id', -1).to_list()\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/exhibition.html\", context={\"user\": user, \"data\": data, \"now\": datetime.now()}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "95b2823a",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/\")\n",
|
||
"async def index(request: Request, user: Dict = Security(get_user)):\n",
|
||
" news = await get_latest_news()\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"index.html\", context={\"user\": user, \"news\": news}\n",
|
||
" )\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.get(\"/about\")\n",
|
||
"async def about(request: Request, user: Dict = Security(get_user)):\n",
|
||
" news = await get_latest_news()\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"about.html\", context={\"user\": user}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "14ebeb80",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/news\")\n",
|
||
"async def news(request: Request, user: Dict = Security(get_user)):\n",
|
||
" news = [doc async for doc in news_collection.find({}).limit(1000)]\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"news.html\", context={\"user\": user, \"news\": news}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "6478802f",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/pwa\")\n",
|
||
"async def pwa(request: Request, user: Dict = Security(get_user)):\n",
|
||
" news = await get_latest_news()\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"index.html\", context={\"user\": user, \"news\": news, 'pwa':True}\n",
|
||
" )\n",
|
||
"\n",
|
||
"@app.get(\"/pwa2\")\n",
|
||
"async def pwa(request: Request, user: Dict = Security(get_user)):\n",
|
||
" news = await get_latest_news()\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"index.html\", context={\"user\": user, \"news\": news, 'pwa':True}\n",
|
||
" )\n",
|
||
"\n",
|
||
"@app.get(\"/offline.html\")\n",
|
||
"async def offline_html(request: Request, user: Dict = Security(get_user)):\n",
|
||
" news = await get_latest_news()\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"index.html\", context={\"user\": user, \"news\": news, 'pwa':True}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "642439cc",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/partner/galerist\", response_class=HTMLResponse)\n",
|
||
"async def partner_galerist(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_user)\n",
|
||
"):\n",
|
||
" partner = [doc async for doc in users_collection.find({'roles': 'partner'},{'username': 1, 'description': 1})]\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"partner/galerist.html\", context={\"id\": 1, \"user\": user, \"partner\": partner}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "0e2432c8",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/partner\", response_class=HTMLResponse)\n",
|
||
"async def partner(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_user)\n",
|
||
"):\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"partner/index.html\", context={\"id\": 1, \"user\": user}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "d1880762",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/artists\", response_class=HTMLResponse)\n",
|
||
"async def artists(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user, scopes=[\"users:read\"])\n",
|
||
"):\n",
|
||
" users = [doc async for doc in users_collection.find({'roles':'user'}).limit(1000)]\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"users/index.html\", context={\"id\": 1, \"user\":user, \"users\": users}\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "83105c5e",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/my-paints\", response_class=HTMLResponse)\n",
|
||
"async def My_paints(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user, scopes=[\"read:self\"])\n",
|
||
"):\n",
|
||
" user_id = user.get('_id')\n",
|
||
" if isinstance(user_id, int) or (is_str:=len(user_id) < 5):\n",
|
||
" doc = await users_collection.find_one({\"id\": int(user_id) if is_str else user_id}, {\"_id\": 1})\n",
|
||
" if not doc:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Пользователь не найден\")\n",
|
||
" return RedirectResponse(url=f\"/artists/{doc.get('_id')}\", status_code=301)\n",
|
||
"\n",
|
||
" doc = users_collection.find_one({\"_id\": user_id})\n",
|
||
" if not doc:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Пользователь не найден\")\n",
|
||
"\n",
|
||
" total_key = f\"total_w_c_artists:{user_id}\"\n",
|
||
" today_key = f\"today_w_c_artists:{user_id}\"\n",
|
||
" total_watch_count = await redis_client.incr(total_key)\n",
|
||
" today_date = datetime.now().strftime(\"%Y-%m-%d\")\n",
|
||
" daily_key = f\"{today_key}:{today_date}\"\n",
|
||
" today_watch_count = await redis_client.incr(daily_key)\n",
|
||
"\n",
|
||
" find = await users_collection.find_one({'_id':ObjectId(user_id)})\n",
|
||
" cards = [i async for i in posts_collection.find({'user._id': user_id}).sort('_id',-1).limit(1000)]\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"users/user.html\", context={\"id\": 1, \"user\":user, \"find\": find, \"cards\": cards,\"total_watch_count\": total_watch_count,\"today_watch_count\": today_watch_count,}\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "ff099b10",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/artists/{user_id}\", response_class=HTMLResponse)\n",
|
||
"async def artists_id(\n",
|
||
" request: Request,\n",
|
||
" user_id: Union[int, str],\n",
|
||
" user: Dict = Security(get_current_user, scopes=[\"users:read\"])\n",
|
||
"):\n",
|
||
" if isinstance(user_id, int) or (is_str:=len(user_id) < 5):\n",
|
||
" doc = await users_collection.find_one({\"id\": int(user_id) if is_str else user_id}, {\"_id\": 1})\n",
|
||
" if not doc:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Пользователь не найден\")\n",
|
||
" return RedirectResponse(url=f\"/artists/{doc.get('_id')}\", status_code=301)\n",
|
||
"\n",
|
||
" doc = users_collection.find_one({\"_id\": user_id})\n",
|
||
" if not doc:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Пользователь не найден\")\n",
|
||
"\n",
|
||
" total_key = f\"total_w_c_artists:{user_id}\"\n",
|
||
" today_key = f\"today_w_c_artists:{user_id}\"\n",
|
||
" total_watch_count = await redis_client.incr(total_key)\n",
|
||
" today_date = datetime.now().strftime(\"%Y-%m-%d\")\n",
|
||
" daily_key = f\"{today_key}:{today_date}\"\n",
|
||
" today_watch_count = await redis_client.incr(daily_key)\n",
|
||
"\n",
|
||
" find = await users_collection.find_one({'_id':ObjectId(user_id)})\n",
|
||
" cards = [i async for i in posts_collection.find({'user._id': user_id}).sort('_id',-1).limit(1000)]\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"users/user.html\", context={\"id\": 1, \"user\":user, \"find\": find, \"cards\": cards,\"total_watch_count\": total_watch_count,\"today_watch_count\": today_watch_count,}\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "e3ad237d",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/form/competition\", response_class=HTMLResponse)\n",
|
||
"async def form_(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
|
||
"): \n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"form/competition.html\", context={\"id\": 1, \"user\": user}\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "fae437a1",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/form/competition/{competition_id}\", response_class=HTMLResponse)\n",
|
||
"async def form_(\n",
|
||
" request: Request,\n",
|
||
" competition_id: str,\n",
|
||
" user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
|
||
"): \n",
|
||
" competition = await competitions_col.find_one({'_id': ObjectId(competition_id)})\n",
|
||
" data = json.dumps(competition, cls=MongoJSONEncoder, ensure_ascii=False)\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"form/competition_edit.html\", context={\"competition\": str(data), \"competition_id\": competition_id, \"user\": user}\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "9d1375e3",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get('/news/{news_id}')\n",
|
||
"async def article_page(\n",
|
||
" news_id: Union[int, str],\n",
|
||
" requset: Request,\n",
|
||
" user: Dict = Security(get_user)\n",
|
||
"):\n",
|
||
" if isinstance(news_id, int) or (is_str:=len(news_id) < 5):\n",
|
||
" doc = await news_collection.find_one({\"id\": int(news_id) if is_str else news_id}, {\"_id\": 1})\n",
|
||
" if not doc:\n",
|
||
" raise HTTPException(status_code=404, detail=\"новость не найдена\")\n",
|
||
" return RedirectResponse(url=f\"/news/{doc.get('_id')}\", status_code=301)\n",
|
||
" news = await news_collection.find_one({'_id':ObjectId(news_id)})\n",
|
||
" if not news:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Новость не найдена\")\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=requset, name='news/index.html', context={\"user\":user, \"news\": news}\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "a330f47f",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/form/post\", response_class=HTMLResponse)\n",
|
||
"async def form_post(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" partner = [doc async for doc in users_collection.find({'roles': 'partner'},{'username': 1})]\n",
|
||
" service_social = [doc async for doc in social_collection.find({'service': True})]\n",
|
||
" user_social = [doc async for doc in social_collection.find({'active': True,'service': {'$exists': False},'user_id': str(user.get('_id'))})]\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"posts/form.html\", context={\"id\": 1, \"user\": user, 'user_social': user_social, 'partner': partner, 'service_social': service_social}\n",
|
||
" )\n",
|
||
"\n",
|
||
"@app.get(\"/form/post/{post_id}\", response_class=HTMLResponse)\n",
|
||
"async def form_post_edit(\n",
|
||
" request: Request,\n",
|
||
" post_id:str,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" post = await posts_collection.find_one({'_id':ObjectId(post_id)})\n",
|
||
" data = json.dumps(post, cls=MongoJSONEncoder, ensure_ascii=False)\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"form/post_edit.html\", context={\"post\": data, \"user\": user}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "136946a5",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/form/news\", response_class=HTMLResponse)\n",
|
||
"async def form_news(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"form/news.html\", context={\"id\": 1, \"user\": user}\n",
|
||
" )\n",
|
||
"\n",
|
||
"@app.get(\"/form/news/{news_id}\", response_class=HTMLResponse)\n",
|
||
"async def form_news_edit(\n",
|
||
" request: Request,\n",
|
||
" news_id:str,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" news = await news_collection.find_one({'_id':ObjectId(news_id)})\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"form/news_edit.html\", context={\"news\": news, \"user\": user}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "f6c5b785",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/form/exhibition\", response_class=HTMLResponse)\n",
|
||
"async def form_exhibition(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"form/exhibition.html\", context={\"id\": 1, \"user\": user}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "d6c3a62d",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/form/exhibition/{exhibition_id}\", response_class=HTMLResponse)\n",
|
||
"async def form_exhibition(\n",
|
||
" request: Request,\n",
|
||
" exhibition_id: str,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)},{'posts':0,'vk':0,'tg':0,'fb':0,'inst':0,'max':0})\n",
|
||
" data = json.dumps(exhibition, cls=MongoJSONEncoder, ensure_ascii=False)\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"form/exhibition_edit.html\", context={\"id\": 1, \"user\": user, \"exhibition\": str(data)}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "5f2da076",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/avatar\", response_model=dict)\n",
|
||
"async def avatar_change(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" avatar: Optional[UploadFile] = File(...),\n",
|
||
" current_user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" 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",
|
||
" raise HTTPException(status_code=500, detail=str(e))\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "f952a708",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.put(\"/api/user/form\", response_model=dict)\n",
|
||
"async def user_form(\n",
|
||
" request: Request,\n",
|
||
" current_user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" doc = await request.json()\n",
|
||
" if not isinstance(doc, dict):\n",
|
||
" raise HTTPException(status_code=400, detail=\"Invalid JSON body\")\n",
|
||
"\n",
|
||
" # Remove any protected fields from incoming update\n",
|
||
" update_doc = {k: v for k, v in doc.items() if k not in PROTECTED_FIELDS_USER}\n",
|
||
"\n",
|
||
" if not update_doc:\n",
|
||
" return {\"ok\": True, \"updated\": False, \"reason\": \"no allowed fields\"}\n",
|
||
"\n",
|
||
" result = users_collection.update_one(\n",
|
||
" {\"_id\": ObjectId(current_user[\"id\"])},\n",
|
||
" {\"$set\": update_doc}\n",
|
||
" )\n",
|
||
"\n",
|
||
" return {\"ok\": True, \"updated\": True}\n",
|
||
" except HTTPException:\n",
|
||
" raise\n",
|
||
" except Exception as e:\n",
|
||
" raise HTTPException(status_code=500, detail=str(e))\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "6971b35c",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get('/paintings/{paint_id}')\n",
|
||
"async def paintings(\n",
|
||
" request: Request,\n",
|
||
" paint_id: Union[int, str],\n",
|
||
" user: Dict = Security(get_user)\n",
|
||
"):\n",
|
||
" if isinstance(paint_id, int) or (is_str:=len(paint_id) < 5):\n",
|
||
" doc = await posts_collection.find_one({\"id\": int(paint_id) if is_str else paint_id}, {\"_id\": 1})\n",
|
||
" if not doc:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Картина не найдена\")\n",
|
||
" return RedirectResponse(url=f\"/paintings/{doc.get('_id')}\", status_code=301)\n",
|
||
"\n",
|
||
" paint = await posts_collection.find_one({'_id': ObjectId(paint_id)})\n",
|
||
" if not paint:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Картина не найдена\")\n",
|
||
" total_key = f\"total_watch_count_paint:{paint_id}\"\n",
|
||
" today_key = f\"today_watch_count_paint:{paint_id}\"\n",
|
||
" total_watch_count = await redis_client.incr(total_key)\n",
|
||
" today_date = datetime.now().strftime(\"%Y-%m-%d\")\n",
|
||
" daily_key = f\"{today_key}:{today_date}\"\n",
|
||
" today_watch_count = await redis_client.incr(daily_key)\n",
|
||
" \n",
|
||
" description = f\"{paint['author']}\\n\"\n",
|
||
" description += f\"Размер: {paint['size']}\\n\"\n",
|
||
" description += f\"Материал: {paint['material']}\\n\"\n",
|
||
" description += f\"Стоимость: {paint['price']}\\n\"\n",
|
||
" description += f\"{paint['description']}\"\n",
|
||
"\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"paintings/paint.html\", context={\"paint\": paint, \"user\": user, \"description\": description,'today_watch_count':today_watch_count,'total_watch_count':total_watch_count}\n",
|
||
" )\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.get('/post/{paint_id}')\n",
|
||
"async def old_post(\n",
|
||
" request: Request,\n",
|
||
" paint_id: int,\n",
|
||
" user: Dict = Security(get_user)\n",
|
||
"):\n",
|
||
" if isinstance(paint_id, int):\n",
|
||
" doc = await posts_collection.find_one({\"id\": paint_id}, {\"_id\": 1})\n",
|
||
" if not doc:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Картина не найдена\")\n",
|
||
" return RedirectResponse(url=f\"/paintings/{doc.get('_id')}\", status_code=301)"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "aabb40cb",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.delete(\"/api/post/{post_id}\", response_model=dict)\n",
|
||
"async def post_delete(\n",
|
||
" post_id: Optional[str],\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:posts\"])\n",
|
||
"):\n",
|
||
" await posts_collection.delete_one({'_id': ObjectId(post_id), 'user._id': current_user.get('_id')})\n",
|
||
" return {'ok': True} \n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "d38cafed",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/post\", response_model=dict)\n",
|
||
"async def post_painting(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" author: str = Form(None),\n",
|
||
" grade: str = Form(None),\n",
|
||
" city: str = Form(None),\n",
|
||
" title: str = Form(...),\n",
|
||
" size: str = Form(None),\n",
|
||
" material: str = Form(None),\n",
|
||
" genre: str = Form(None),\n",
|
||
" price: str = Form(None),\n",
|
||
" description: str = Form(None),\n",
|
||
" post_medias: Optional[List[str]] = Form(...),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:posts\"])\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" doc = {\n",
|
||
" \"title\": safe_strip(title),\n",
|
||
" \"url\": transliterate(f\"{safe_strip(title)} {safe_strip(author, current_user['username'])} {safe_strip(city)}\"),\n",
|
||
" \"author\": safe_strip(author, current_user['username']),\n",
|
||
" \"grade\": safe_strip(grade),\n",
|
||
" \"city\": safe_strip(city) or current_user.get('living_place'),\n",
|
||
" \"size\": safe_strip(size),\n",
|
||
" \"status\": \"В каталоге\",\n",
|
||
" \"material\": safe_strip(material),\n",
|
||
" \"genre\": safe_strip(genre),\n",
|
||
" \"price\": safe_strip(price),\n",
|
||
" \"description\": safe_strip(description),\n",
|
||
" \"medias\": post_medias or [],\n",
|
||
" \"medias_original\": post_medias or [],\n",
|
||
" \"user\": display_user(current_user),\n",
|
||
" **{i:[] for i in SOCIALS},\n",
|
||
" }\n",
|
||
" \n",
|
||
" res = await posts_collection.insert_one(doc)\n",
|
||
" doc_out = {\n",
|
||
" \"id\": str(res.inserted_id),\n",
|
||
" \"_id\": str(res.inserted_id),\n",
|
||
" **{k: v for k, v in doc.items()}\n",
|
||
" }\n",
|
||
" doc_out = bson_clear(doc_out)\n",
|
||
" background_tasks.add_task(replace_post_medias, doc_out)\n",
|
||
" if PROD:\n",
|
||
" background_tasks.add_task(email_new_post, doc_out)\n",
|
||
" background_tasks.add_task(publish_social_post, doc_out)\n",
|
||
" if 'user' in current_user.get('roles',[]) and safe_strip(grade) != current_user.get('grade'):\n",
|
||
" await users_collection.update_one({'_id':ObjectId(current_user.get('_id'))},{\"$set\": {'grade': safe_strip(grade)}})\n",
|
||
" return doc_out\n",
|
||
" except Exception as e:\n",
|
||
" print(e)\n",
|
||
" raise HTTPException(status_code=500, detail=str(e))\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "8feec4b3",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.put(\"/api/post/{post_id}/sale\", response_model=dict)\n",
|
||
"async def sale_painting(\n",
|
||
" post_id: str,\n",
|
||
" current_user: Dict = Security(get_current_user)\n",
|
||
" ):\n",
|
||
" fin = [{'_id': ObjectId(post_id), 'user._id': current_user.get('_id')}]\n",
|
||
" if 'partner' in current_user.get('roles', []):\n",
|
||
" fin.append({'_id': ObjectId(post_id)})\n",
|
||
" doc = await posts_collection.update_one({'$or': fin}, {'$set': {\"status\": \"Продано\"}})\n",
|
||
" if doc.modified_count: \n",
|
||
" existing = await delivery_collection.find_one(\n",
|
||
" {\"post_id\": post_id, \"status\": \"На выставке\"},\n",
|
||
" {\"_id\": 0}\n",
|
||
" )\n",
|
||
" if existing:\n",
|
||
" update_data = {\n",
|
||
" \"status\": \"Продано\",\n",
|
||
" \"post\": {\"status\": \"Продано\"},\n",
|
||
" }\n",
|
||
" notify = await notify_in_profile(NotifyItem(\n",
|
||
" user_id=str(existing[\"user\"][\"_id\"]),\n",
|
||
" type_item=\"delivery\",\n",
|
||
" title=\"Картина продана\",\n",
|
||
" link=\"/profile/delivery\",\n",
|
||
" text=f\"{str(existing.get('post',{}).get('title'))}\"\n",
|
||
" ))\n",
|
||
"\n",
|
||
" new_doc = {**existing, **update_data, \"notify\": notify.upserted_id}\n",
|
||
"\n",
|
||
" result = await delivery_collection.insert_one(new_doc)\n",
|
||
" else:\n",
|
||
" doc = await posts_collection.update_one({'$or': fin}, {'$set': {\"status\": \"В каталоге\"}})\n",
|
||
" await delivery_collection.delete_one(\n",
|
||
" {\"post_id\": post_id, \"status\": \"Продано\"}\n",
|
||
" )\n",
|
||
"\n",
|
||
" return {'ok': True}"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "44ea690c",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.put(\"/api/post/{post_id}\", response_model=dict)\n",
|
||
"async def put_painting(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" post_id: str,\n",
|
||
" author: str = Form(None),\n",
|
||
" grade: str = Form(None),\n",
|
||
" city: str = Form(None),\n",
|
||
" title: str = Form(...),\n",
|
||
" size: str = Form(None),\n",
|
||
" material: str = Form(None),\n",
|
||
" genre: str = Form(None),\n",
|
||
" price: str = Form(None),\n",
|
||
" description: str = Form(None),\n",
|
||
" post_medias: Optional[List[str]] = Form(...),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:posts\"])\n",
|
||
"):\n",
|
||
" update_doc = {}\n",
|
||
"\n",
|
||
" if title is not None:\n",
|
||
" update_doc[\"title\"] = safe_strip(title)\n",
|
||
" update_doc[\"url\"] = transliterate(f\"{safe_strip(title)} {safe_strip(author)} {safe_strip(city)}\")\n",
|
||
" if author is not None:\n",
|
||
" update_doc[\"author\"] = safe_strip(author)\n",
|
||
" if grade is not None:\n",
|
||
" update_doc[\"grade\"] = safe_strip(grade)\n",
|
||
" if city is not None:\n",
|
||
" update_doc[\"city\"] = safe_strip(city)\n",
|
||
" if size is not None:\n",
|
||
" update_doc[\"size\"] = safe_strip(size)\n",
|
||
" if material is not None:\n",
|
||
" update_doc[\"material\"] = safe_strip(material)\n",
|
||
" if genre is not None:\n",
|
||
" update_doc[\"genre\"] = safe_strip(genre)\n",
|
||
" if price is not None:\n",
|
||
" update_doc[\"price\"] = safe_strip(price)\n",
|
||
" if description is not None:\n",
|
||
" update_doc[\"description\"] = safe_strip(description)\n",
|
||
" if post_medias:\n",
|
||
" update_doc[\"medias_original\"] = post_medias\n",
|
||
"\n",
|
||
" res = await posts_collection.find_one_and_update(\n",
|
||
" {'_id': ObjectId(post_id)}, \n",
|
||
" {'$set': update_doc},\n",
|
||
" return_document=False\n",
|
||
" )\n",
|
||
"\n",
|
||
" links_media = list(zip(res.get('medias_original',[]), res.get('medias',[])))\n",
|
||
"\n",
|
||
" if res is None:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Картина не найдена\")\n",
|
||
"\n",
|
||
" doc_out = {\n",
|
||
" \"_id\":post_id,\n",
|
||
" \"id\":post_id,\n",
|
||
" **{k: v for k, v in update_doc.items()}\n",
|
||
" }\n",
|
||
" doc_out = bson_clear(doc_out)\n",
|
||
" \n",
|
||
" background_tasks.add_task(update_social_post, doc_out)\n",
|
||
" background_tasks.add_task(replace_new_medias, bson_clear(doc_out), links_media)\n",
|
||
" return doc_out\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "3f18f6a6",
|
||
"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(\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))"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "9c463130",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/news\", response_model=dict)\n",
|
||
"async def post_news(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" title: str = Form(...),\n",
|
||
" description: str = Form(None),\n",
|
||
" short_description: str = Form(None),\n",
|
||
" publish_on_platform: bool = Form(False),\n",
|
||
" post_medias: Optional[List[str]] = Form(...),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:news\"])\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" doc = {\n",
|
||
" \"title\": safe_strip(title),\n",
|
||
" \"short_description\": safe_strip(short_description), \n",
|
||
" \"publish_on_platform\": publish_on_platform,\n",
|
||
" \"description\": safe_strip(description),\n",
|
||
" \"media\": post_medias,\n",
|
||
" \"media_original\": post_medias,\n",
|
||
" \"user\": display_user(current_user),\n",
|
||
" **{i:[] for i in SOCIALS},\n",
|
||
" }\n",
|
||
"\n",
|
||
" res = await news_collection.insert_one(doc)\n",
|
||
" doc_out = {\n",
|
||
" \"id\": str(res.inserted_id),\n",
|
||
" **{k: v for k, v in doc.items()}\n",
|
||
" }\n",
|
||
" doc_out = bson_clear(doc_out)\n",
|
||
" # background_tasks.add_task(email_new_post, doc_out)\n",
|
||
" background_tasks.add_task(replace_news_medias, doc_out)\n",
|
||
" return doc_out\n",
|
||
" except Exception as e:\n",
|
||
" raise HTTPException(status_code=500, detail=str(e))\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "de77638d",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.put(\"/api/news/{news_id}\", response_model=dict)\n",
|
||
"async def put_news(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" news_id: str,\n",
|
||
" title: str = Form(...),\n",
|
||
" description: str = Form(None),\n",
|
||
" short_description: str = Form(None),\n",
|
||
" publish_on_platform: bool = Form(False),\n",
|
||
" post_medias: Optional[List[str]] = Form(...),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:news\"])\n",
|
||
"):\n",
|
||
" update_doc = {}\n",
|
||
"\n",
|
||
" if title is not None:\n",
|
||
" update_doc[\"title\"] = safe_strip(title)\n",
|
||
" if description is not None:\n",
|
||
" update_doc[\"description\"] = safe_strip(description)\n",
|
||
" if short_description is not None:\n",
|
||
" update_doc[\"short_description\"] = safe_strip(short_description)\n",
|
||
" if publish_on_platform is not None:\n",
|
||
" update_doc[\"publish_on_platform\"] = publish_on_platform\n",
|
||
" if post_medias:\n",
|
||
" update_doc[\"media\"] = post_medias\n",
|
||
" update_doc[\"media_original\"] = post_medias\n",
|
||
"\n",
|
||
" res = await news_collection.update_one(\n",
|
||
" {'_id': ObjectId(news_id)}, \n",
|
||
" {'$set': update_doc}\n",
|
||
" )\n",
|
||
"\n",
|
||
" if res.modified_count == 0:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Картина не найдена\")\n",
|
||
"\n",
|
||
" doc_out = {\n",
|
||
" \"id\":news_id,\n",
|
||
" **{k: v for k, v in update_doc.items()}\n",
|
||
" }\n",
|
||
" doc_out = bson_clear(doc_out)\n",
|
||
" return doc_out\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "abe852d2",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/news/media\", response_model=dict)\n",
|
||
"async def news_media(\n",
|
||
" medias: Optional[List[UploadFile]] = File(None),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:news\"])\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" media = await asyncio.gather(*[save_to_gridfs(media, 'news') for media in medias])\n",
|
||
" return {'media': media}\n",
|
||
" except Exception as e:\n",
|
||
" raise HTTPException(status_code=500, detail=str(e))\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "d0a9730e",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/read/notify\", response_model=Dict)\n",
|
||
"async def read_notify(\n",
|
||
" user: Dict = Security(get_user)\n",
|
||
"):\n",
|
||
" await notify_collection.update_many({'user_id':user.get('_id')},{'$set':{'read':False}})\n",
|
||
" return {}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "0479dfcd",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/competition\", response_model=Dict, tags=[\"Competition\", \"Partner\"])\n",
|
||
"async def post_competition(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" payload: CompetitionCreate = Depends(build_payload),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:super_items\"]),\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" poster_id = await save_to_gridfs(payload.poster, \"competition\")\n",
|
||
" if payload.rules:\n",
|
||
" rules_id = await save_to_gridfs(payload.rules, \"competition\")\n",
|
||
" else:\n",
|
||
" rules_id = None\n",
|
||
"\n",
|
||
" await users_collection.update_one(\n",
|
||
" {\"_id\": ObjectId(current_user[\"_id\"])},\n",
|
||
" {\"$addToSet\": {\"send_address\": safe_strip(payload.address)}},\n",
|
||
" upsert=True,\n",
|
||
" )\n",
|
||
"\n",
|
||
" doc = {\n",
|
||
" \"url\": transliterate(safe_strip(payload.title)),\n",
|
||
" \"title\": safe_strip(payload.title),\n",
|
||
" \"topic\": safe_strip(payload.topic) if payload.topic else None,\n",
|
||
" \"type_\": payload.type_,\n",
|
||
" \"datetime_start\": payload.datetime_start,\n",
|
||
" \"date_finish\": payload.date_finish,\n",
|
||
" \"nominations\": payload.nominations,\n",
|
||
" \"address\": safe_strip(payload.address),\n",
|
||
" \"emailsNotify\": payload.emailsNotify,\n",
|
||
" \"serviceSocial\": payload.serviceSocial,\n",
|
||
" \"userSocial\": payload.userSocial,\n",
|
||
" \"short_desc\": safe_strip(payload.short_desc) if payload.short_desc else None,\n",
|
||
" \"poster_file_id\": poster_id,\n",
|
||
" \"poster_orig_id\": poster_id,\n",
|
||
" \"rules_file_id\": rules_id,\n",
|
||
" \"rules_link\": payload.rules_link,\n",
|
||
" \"status\": 0,\n",
|
||
" \"user\": display_user(current_user),\n",
|
||
" \"min_paintings\": payload.min_paintings,\n",
|
||
" \"max_paintings\": payload.max_paintings,\n",
|
||
" **{i:[] for i in SOCIALS},\n",
|
||
" }\n",
|
||
"\n",
|
||
" res = await competitions_col.insert_one(doc)\n",
|
||
"\n",
|
||
" background_tasks.add_task(\n",
|
||
" poster_competition, bson_clear({**doc, \"_id\": res.inserted_id})\n",
|
||
" )\n",
|
||
"\n",
|
||
" return {\"ok\": True, \"competition_id\": str(res.inserted_id)}\n",
|
||
" except Exception as exc:\n",
|
||
" logging.error(str(exc))\n",
|
||
" raise HTTPException(status_code=500, detail=str(exc))\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "cc6f3f8b",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/competition/publish\", response_model=Dict, tags=[\"Competition\", \"Partner\"])\n",
|
||
"async def publish_competition(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" payload: CompetitionPublishParams = Depends(build_publish_payload),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:super_items\"]),\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" logging.info(\n",
|
||
" {\n",
|
||
" \"current_user\": current_user.get(\"_id\"),\n",
|
||
" \"competition_id\": payload.competition_id,\n",
|
||
" \"emailsNotify\": payload.emailsNotify,\n",
|
||
" \"serviceSocial\": payload.serviceSocial,\n",
|
||
" \"userSocial\": payload.userSocial,\n",
|
||
" \"usersSocial\": payload.usersSocial,\n",
|
||
" }\n",
|
||
" )\n",
|
||
"\n",
|
||
" competition = await competitions_col.find_one(\n",
|
||
" {\n",
|
||
" \"user._id\": current_user.get(\"_id\"),\n",
|
||
" \"_id\": ObjectId(payload.competition_id),\n",
|
||
" }\n",
|
||
" )\n",
|
||
" if not competition:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Конкурс не найден\")\n",
|
||
"\n",
|
||
" competition = bson_clear(competition)\n",
|
||
"\n",
|
||
" if competition[\"datetime_start\"] > datetime.now():\n",
|
||
" await competitions_col.update_one(\n",
|
||
" {\"_id\": ObjectId(payload.competition_id)},\n",
|
||
" {\"$set\": {\"datetime_start\": datetime.now()}},\n",
|
||
" )\n",
|
||
"\n",
|
||
" if payload.emailsNotify:\n",
|
||
" background_tasks.add_task(invite_users_to_competitions, competition)\n",
|
||
"\n",
|
||
" if any([payload.serviceSocial, payload.userSocial, payload.usersSocial]):\n",
|
||
" background_tasks.add_task(\n",
|
||
" service_social_competition,\n",
|
||
" competition,\n",
|
||
" payload.serviceSocial,\n",
|
||
" payload.userSocial,\n",
|
||
" payload.usersSocial,\n",
|
||
" )\n",
|
||
"\n",
|
||
" return {\"ok\": True, \"competition_id\": payload.competition_id}\n",
|
||
" except Exception as exc:\n",
|
||
" logging.error(str(exc))\n",
|
||
" raise HTTPException(status_code=500, detail=str(exc))\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "80c70929",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.put(\"/api/competition/{competition_id}\", response_model=Dict, tags=[\"Competition\", \"Partner\"])\n",
|
||
"async def put_competition(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" competition_id: str,\n",
|
||
" payload: CompetitionUpdate = Depends(build_update_payload),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:super_items\"]),\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" update_doc = {\n",
|
||
" \"user\": display_user(current_user),\n",
|
||
" \"rules_link\": payload.rules_link,\n",
|
||
" \"type_\": payload.type_,\n",
|
||
" }\n",
|
||
" if payload.nominations is not None:\n",
|
||
" update_doc[\"nominations\"] = payload.nominations\n",
|
||
" if payload.title is not None:\n",
|
||
" update_doc[\"title\"] = safe_strip(payload.title)\n",
|
||
" if payload.topic is not None:\n",
|
||
" update_doc[\"topic\"] = safe_strip(payload.topic)\n",
|
||
" if payload.datetime_start is not None:\n",
|
||
" update_doc[\"datetime_start\"] = payload.datetime_start\n",
|
||
" if datetime.now(timezone.utc) + timedelta(hours=3) < payload.datetime_start:\n",
|
||
" update_doc[\"status\"] = -1\n",
|
||
" if payload.date_finish is not None:\n",
|
||
" update_doc[\"date_finish\"] = payload.date_finish\n",
|
||
" if datetime.now(timezone.utc) + timedelta(hours=3) > payload.datetime_start and datetime.now(timezone.utc) + timedelta(hours=3) < payload.date_finish:\n",
|
||
" update_doc[\"status\"] = 0\n",
|
||
" if payload.address is not None:\n",
|
||
" await users_collection.update_one(\n",
|
||
" { \"_id\": ObjectId(current_user[\"_id\"]) },\n",
|
||
" { \"$addToSet\": { \"send_address\": safe_strip(payload.address) } },\n",
|
||
" upsert=True\n",
|
||
" )\n",
|
||
" update_doc[\"address\"] = safe_strip(payload.address)\n",
|
||
" if payload.short_desc is not None:\n",
|
||
" update_doc[\"short_desc\"] = safe_strip(payload.short_desc)\n",
|
||
" if payload.emailsNotify is not None:\n",
|
||
" update_doc[\"emailsNotify\"] = payload.emailsNotify\n",
|
||
" if payload.serviceSocial is not None:\n",
|
||
" update_doc[\"serviceSocial\"] = payload.serviceSocial\n",
|
||
" if payload.userSocial is not None:\n",
|
||
" update_doc[\"userSocial\"] = payload.userSocial\n",
|
||
" if payload.min_paintings is not None:\n",
|
||
" update_doc[\"min_paintings\"] = int(payload.min_paintings)\n",
|
||
" if payload.max_paintings is not None:\n",
|
||
" update_doc[\"max_paintings\"] = int(payload.max_paintings)\n",
|
||
" if payload.poster:\n",
|
||
" poster_id = await save_to_gridfs(payload.poster, 'competition')\n",
|
||
" update_doc[\"poster_file_id\"] = poster_id\n",
|
||
" background_tasks.add_task(poster_competition, bson_clear({**update_doc, '_id': competition_id}))\n",
|
||
" if payload.rules:\n",
|
||
" rules_id = await save_to_gridfs(payload.rules, 'competition')\n",
|
||
" update_doc[\"rules_file_id\"] = rules_id\n",
|
||
"\n",
|
||
" res = await competitions_col.update_one(\n",
|
||
" {'_id': ObjectId(competition_id)}, \n",
|
||
" {'$set': update_doc}\n",
|
||
" )\n",
|
||
"\n",
|
||
" return {'ok': True}\n",
|
||
" except Exception as exc:\n",
|
||
" logging.error(str(exc))\n",
|
||
" raise HTTPException(status_code=500, detail=str(exc))\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "c04c5888",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/api/exhibitions\", response_model=List)\n",
|
||
"async def get_exhibitions():\n",
|
||
" exhibitions = []\n",
|
||
" current_time = datetime.now(timezone.utc) + timedelta(hours=3)\n",
|
||
"\n",
|
||
" async for doc in exhibition_collection.find({\n",
|
||
" '$or': [\n",
|
||
" {'datetime_start': {'$lt': current_time}},\n",
|
||
" ],\n",
|
||
" 'prod': True\n",
|
||
" },{'vk': 0,'max': 0,'tg': 0,'fb': 0,'inst': 0}).sort('_id',-1).limit(1000):\n",
|
||
" exhibitions.append(bson_clear(doc))\n",
|
||
" return exhibitions"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "55c76bf4",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/exhibition\", response_model=Dict)\n",
|
||
"async def post_exhibition(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" payload: ExhibitionCreate = Depends(build_exhibition_payload),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" poster_id = await save_to_gridfs(payload.poster, 'exhibition')\n",
|
||
" rules_id = await save_to_gridfs(payload.rules, 'exhibition') if payload.rules else None\n",
|
||
" tizer_id = await save_to_gridfs(payload.tizer, 'exhibition') if payload.tizer else None\n",
|
||
"\n",
|
||
" await users_collection.update_one(\n",
|
||
" {\"_id\": ObjectId(current_user[\"_id\"])},\n",
|
||
" {\"$addToSet\": {\n",
|
||
" \"send_address\": safe_strip(payload.address),\n",
|
||
" \"places\": safe_strip(payload.place)\n",
|
||
" }},\n",
|
||
" upsert=True\n",
|
||
" )\n",
|
||
" \n",
|
||
" doc = {\n",
|
||
" \"url\": transliterate(safe_strip(payload.title)),\n",
|
||
" \"title\": safe_strip(payload.title),\n",
|
||
" \"topic\": safe_strip(payload.topic) if payload.topic else None,\n",
|
||
" \"datetime_start\": payload.datetime_start,\n",
|
||
" \"date_finish\": payload.date_finish,\n",
|
||
" \"address\": safe_strip(payload.address) if payload.address else None,\n",
|
||
" \"place\": safe_strip(payload.place) if payload.place else None,\n",
|
||
" \"rules_file_id\": rules_id,\n",
|
||
" \"rules_link\": payload.rules_link,\n",
|
||
" \"tizer_file_id\": tizer_id,\n",
|
||
" \"tizer_link\": payload.tizer_link,\n",
|
||
" \n",
|
||
" \"poster_orig_id\": poster_id,\n",
|
||
" \"poster_file_id\": poster_id,\n",
|
||
" \"status\": 0,\n",
|
||
" \"prod\": PROD,\n",
|
||
" \"user\": display_user(current_user),\n",
|
||
" \"posts\": [],\n",
|
||
" **{i:[] for i in SOCIALS},\n",
|
||
" }\n",
|
||
" \n",
|
||
" res = await exhibition_collection.insert_one(doc)\n",
|
||
" exhibition_id = res.inserted_id\n",
|
||
" background_tasks.add_task(\n",
|
||
" poster_exhibition, \n",
|
||
" bson_clear({**doc, '_id': exhibition_id})\n",
|
||
" )\n",
|
||
" \n",
|
||
" return {'_id': str(exhibition_id)}\n",
|
||
" except Exception as e:\n",
|
||
" logging.error(str(e))\n",
|
||
" raise HTTPException(status_code=500, detail=str(e))"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "46e76a8c",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.put(\"/api/exhibition/{exhibition_id}\", response_model=Dict)\n",
|
||
"async def put_exhibition(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" exhibition_id: str,\n",
|
||
" payload: ExhibitionUpdate = Depends(build_update_exhibition_payload),\n",
|
||
" current_user: Dict = Security(get_current_user, scopes=[\"create:super_items\"])\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" update_doc = {\n",
|
||
" \"user\": display_user(current_user),\n",
|
||
" }\n",
|
||
"\n",
|
||
" if payload.title is not None:\n",
|
||
" update_doc[\"title\"] = safe_strip(payload.title)\n",
|
||
" if payload.topic is not None:\n",
|
||
" update_doc[\"topic\"] = safe_strip(payload.topic)\n",
|
||
" if payload.place is not None:\n",
|
||
" await users_collection.update_one(\n",
|
||
" {\"_id\": ObjectId(current_user[\"_id\"])},\n",
|
||
" {\"$addToSet\": {\"places\": safe_strip(payload.place)}},\n",
|
||
" upsert=True\n",
|
||
" )\n",
|
||
" update_doc[\"place\"] = safe_strip(payload.place)\n",
|
||
" if payload.datetime_start is not None:\n",
|
||
" update_doc[\"datetime_start\"] = payload.datetime_start\n",
|
||
" if payload.date_finish is not None:\n",
|
||
" update_doc[\"date_finish\"] = payload.date_finish\n",
|
||
" if payload.address is not None:\n",
|
||
" await users_collection.update_one(\n",
|
||
" {\"_id\": ObjectId(current_user[\"_id\"])},\n",
|
||
" {\"$addToSet\": {\"send_address\": safe_strip(payload.address)}},\n",
|
||
" upsert=True\n",
|
||
" )\n",
|
||
" update_doc[\"address\"] = safe_strip(payload.address)\n",
|
||
" if payload.short_desc is not None:\n",
|
||
" update_doc[\"short_desc\"] = safe_strip(payload.short_desc)\n",
|
||
" update_doc[\"rules_link\"] = payload.rules_link\n",
|
||
" update_doc[\"tizer_link\"] = payload.tizer_link\n",
|
||
"\n",
|
||
" # Обработка файлов\n",
|
||
" if payload.poster:\n",
|
||
" poster_id = await save_to_gridfs(payload.poster, 'exhibition')\n",
|
||
" update_doc[\"poster_file_id\"] = poster_id\n",
|
||
" update_doc[\"poster_orig_id\"] = poster_id\n",
|
||
" background_tasks.add_task(\n",
|
||
" poster_exhibition, \n",
|
||
" bson_clear({**update_doc, '_id': exhibition_id})\n",
|
||
" )\n",
|
||
" if payload.tizer:\n",
|
||
" tizer_id = await save_to_gridfs(payload.tizer, 'exhibition')\n",
|
||
" update_doc[\"tizer_file_id\"] = tizer_id\n",
|
||
" update_doc[\"tizer_link\"] = None\n",
|
||
" if payload.rules:\n",
|
||
" rules_id = await save_to_gridfs(payload.rules, 'exhibition')\n",
|
||
" update_doc[\"rules_file_id\"] = rules_id\n",
|
||
" update_doc[\"rules_link\"] = None\n",
|
||
"\n",
|
||
" # Если нужно добавить nominations / min_paintings и т.д. — расширь модель и этот блок\n",
|
||
"\n",
|
||
" res = await exhibition_collection.update_one(\n",
|
||
" {'_id': ObjectId(exhibition_id)}, \n",
|
||
" {'$set': update_doc}\n",
|
||
" )\n",
|
||
"\n",
|
||
" return {'_id': exhibition_id}\n",
|
||
" except Exception as e:\n",
|
||
" logging.error(str(e))\n",
|
||
" raise HTTPException(status_code=500, detail=str(e))"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "f1412592",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def stream_gridfs_file(file_id: str, bucket_name: str):\n",
|
||
" if not file_id:\n",
|
||
" return None\n",
|
||
"\n",
|
||
" grid = gridfs_map.get(bucket_name)\n",
|
||
" if grid is None:\n",
|
||
" return None\n",
|
||
"\n",
|
||
" try:\n",
|
||
" oid = ObjectId(file_id)\n",
|
||
" except Exception:\n",
|
||
" return None\n",
|
||
"\n",
|
||
" stream = None\n",
|
||
" try:\n",
|
||
" stream = await grid.open_download_stream(oid)\n",
|
||
"\n",
|
||
" filename = stream.filename or \"file\"\n",
|
||
" content_type = (\n",
|
||
" stream.content_type\n",
|
||
" or mimetypes.guess_type(filename)[0]\n",
|
||
" or \"application/octet-stream\"\n",
|
||
" )\n",
|
||
" file_length = getattr(stream, \"length\", None)\n",
|
||
"\n",
|
||
" async def file_iterator(chunk_size: int = 64 * 1024):\n",
|
||
" try:\n",
|
||
" while True:\n",
|
||
" chunk = await stream.read(chunk_size)\n",
|
||
" if not chunk:\n",
|
||
" break\n",
|
||
" yield chunk\n",
|
||
" finally:\n",
|
||
" if stream is not None:\n",
|
||
" close = getattr(stream, \"close\", None)\n",
|
||
" if asyncio.iscoroutinefunction(close) or asyncio.iscoroutine(close):\n",
|
||
" await close()\n",
|
||
" elif callable(close):\n",
|
||
" close()\n",
|
||
"\n",
|
||
" return filename, content_type, file_length, file_iterator\n",
|
||
"\n",
|
||
" except Exception:\n",
|
||
" if stream is not None:\n",
|
||
" close = getattr(stream, \"close\", None)\n",
|
||
" if asyncio.iscoroutinefunction(close) or asyncio.iscoroutine(close):\n",
|
||
" await close()\n",
|
||
" elif callable(close):\n",
|
||
" close()\n",
|
||
" return None\n",
|
||
"\n",
|
||
"\n",
|
||
"def content_disposition_header(filename: str, inline: bool = True) -> str:\n",
|
||
" dispo_type = \"inline\" if inline else \"attachment\"\n",
|
||
"\n",
|
||
" fallback = filename.encode(\"latin-1\", errors=\"ignore\").decode(\"latin-1\")\n",
|
||
" fallback = fallback.replace('\"', '\\\\\"').replace(\"\\\\\", \"\\\\\\\\\") or \"file\"\n",
|
||
"\n",
|
||
" try:\n",
|
||
" filename.encode(\"latin-1\")\n",
|
||
" return f'{dispo_type}; filename=\"{fallback}\"'\n",
|
||
" except UnicodeEncodeError:\n",
|
||
" filename_quoted = quote(filename, safe=\"\")\n",
|
||
" return f'{dispo_type}; filename=\"{fallback}\"; filename*=UTF-8\\'\\'{filename_quoted}'\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.get(\"/b/{bucket_name}/{file_id}\")\n",
|
||
"async def get_file(bucket_name: str, file_id: str):\n",
|
||
" res = await stream_gridfs_file(file_id, bucket_name)\n",
|
||
" if res is None:\n",
|
||
" raise HTTPException(status_code=404, detail=\"File not found\")\n",
|
||
" filename, content_type, file_length, iterator = res\n",
|
||
" headers = {\n",
|
||
" \"Content-Disposition\": content_disposition_header(filename, inline=True),\n",
|
||
" \"Cache-Control\": \"public, max-age=3600\",\n",
|
||
" \"ETag\": f'\"{bucket_name}:{file_id}\"',\n",
|
||
" \"Vary\": \"Accept-Encoding\",\n",
|
||
" }\n",
|
||
" if file_length is not None:\n",
|
||
" headers[\"Content-Length\"] = str(file_length)\n",
|
||
" return StreamingResponse(iterator(), media_type=content_type, headers=headers)\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.get(\"/a/{user_id}\")\n",
|
||
"async def get_avatar(user_id: str):\n",
|
||
" user = await users_collection.find_one({'_id': ObjectId(user_id)},{'img':1})\n",
|
||
" file_id = user['img']\n",
|
||
" res = await stream_gridfs_file(file_id, 'users')\n",
|
||
" if res is None:\n",
|
||
" raise HTTPException(status_code=404, detail=\"File not found\")\n",
|
||
" filename, content_type, file_length, iterator = res\n",
|
||
" headers = {\n",
|
||
" \"Content-Disposition\": content_disposition_header(filename, inline=True),\n",
|
||
" \"Cache-Control\": \"public, max-age=3600\",\n",
|
||
" \"ETag\": f'\"users:{file_id}\"',\n",
|
||
" \"Vary\": \"Accept-Encoding\",\n",
|
||
" }\n",
|
||
" if file_length is not None:\n",
|
||
" headers[\"Content-Length\"] = str(file_length)\n",
|
||
" return StreamingResponse(iterator(), media_type=content_type, headers=headers)\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "30acea7c",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/b/{bucket_name}/{file_id}/{new_w}/{quality}/{GaussianBlur_radius}\")\n",
|
||
"async def get_file_with_config(\n",
|
||
" bucket_name: str,\n",
|
||
" file_id: str,\n",
|
||
" new_w: int,\n",
|
||
" quality: int,\n",
|
||
" GaussianBlur_radius: int,\n",
|
||
"):\n",
|
||
" res = await stream_gridfs_file(file_id, bucket_name)\n",
|
||
" if res is None:\n",
|
||
" raise HTTPException(status_code=404, detail=\"File not found\")\n",
|
||
"\n",
|
||
" filename, content_type, file_length, iterator = res\n",
|
||
"\n",
|
||
" # Собираем все байты файла\n",
|
||
" data = b\"\".join([chunk async for chunk in iterator()])\n",
|
||
"\n",
|
||
" # Обрабатываем\n",
|
||
" processed = await build_composite(\n",
|
||
" data,\n",
|
||
" new_w=new_w,\n",
|
||
" quality=quality,\n",
|
||
" GaussianBlur_radius=GaussianBlur_radius,\n",
|
||
" )\n",
|
||
"\n",
|
||
" headers = {\n",
|
||
" \"Content-Disposition\": content_disposition_header(filename, inline=True),\n",
|
||
" \"Cache-Control\": \"public, max-age=3600\",\n",
|
||
" \"ETag\": f'\"{bucket_name}:{file_id}:{new_w}:{quality}:{GaussianBlur_radius}\"',\n",
|
||
" \"Vary\": \"Accept-Encoding\",\n",
|
||
" \"Content-Length\": str(len(processed.getvalue())),\n",
|
||
" }\n",
|
||
"\n",
|
||
" return StreamingResponse(\n",
|
||
" processed,\n",
|
||
" media_type=\"image/webp\",\n",
|
||
" headers=headers,\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "cdf3993c",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def unsubscribe_user(\n",
|
||
" email: str, \n",
|
||
" token: Optional[str] = None\n",
|
||
") -> bool:\n",
|
||
" try:\n",
|
||
" if token:\n",
|
||
" stored_email = await redis_client.get(f\"unsub:{token}\")\n",
|
||
" if not stored_email or stored_email != email:\n",
|
||
" return False\n",
|
||
" await redis_client.delete(f\"unsub:{token}\")\n",
|
||
"\n",
|
||
" result = await mongo_client['email']['tokens'].update_one(\n",
|
||
" {\"token\": token, \"email\": email, \"used\": False},\n",
|
||
" {\"$set\": {\"used\": True, \"used_at\": datetime.utcnow()}}\n",
|
||
" )\n",
|
||
" if result.modified_count == 0:\n",
|
||
" return False\n",
|
||
"\n",
|
||
" await users_collection.update_one(\n",
|
||
" {\"email\": email},\n",
|
||
" {\"$set\": {\n",
|
||
" \"email_subscribed\": False,\n",
|
||
" \"email_unsubscribed_at\": datetime.utcnow(),\n",
|
||
" \"email_unsubscribe_reason\": \"user_request\"\n",
|
||
" }}\n",
|
||
" )\n",
|
||
"\n",
|
||
" logging.info(f\"✅ Пользователь отписался: {email}\")\n",
|
||
" return True\n",
|
||
"\n",
|
||
" except Exception as e:\n",
|
||
" logging.error(f\"❌ Ошибка отписки {email}: {e}\")\n",
|
||
" return False\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.get(\"/api/v1/email/unsubscribe\", response_class=HTMLResponse)\n",
|
||
"async def unsubscribe(\n",
|
||
" request: Request,\n",
|
||
" email: EmailStr,\n",
|
||
" token: Optional[str] = None,\n",
|
||
"):\n",
|
||
" logging.info(f\"Отписка {token} {email}\")\n",
|
||
" await unsubscribe_user(email)\n",
|
||
" \n",
|
||
" return \"\"\"\n",
|
||
" <html>\n",
|
||
" <head><title>Отписка</title></head>\n",
|
||
" <body style=\"font-family:Arial;text-align:center;margin-top:50px;\">\n",
|
||
" <h2>✅ Вы успешно отписались</h2>\n",
|
||
" <p>Вы больше не будете получать письма от нас.</p>\n",
|
||
" <p><a href=\"/\">Вернуться на сайт</a></p>\n",
|
||
" </body>\n",
|
||
" </html>\n",
|
||
" \"\"\"\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "c7c3772b",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/admin/delete-user\")\n",
|
||
"async def delete_user(\n",
|
||
" payload: DeleteUserRequest,\n",
|
||
" user: Dict = Security(get_current_user, scopes=[\"admin\"]),\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" result = users_collection.delete_one({\"_id\": ObjectId(payload._id)})\n",
|
||
" except Exception:\n",
|
||
" raise HTTPException(status_code=400, detail=\"Неверный ID пользователя\")\n",
|
||
"\n",
|
||
" if result.deleted_count == 0:\n",
|
||
" raise HTTPException(status_code=404, detail=\"Пользователь не найден\")\n",
|
||
"\n",
|
||
" return {\"ok\": True}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "32612dc5",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/admin/users\", response_class=HTMLResponse)\n",
|
||
"async def profile_admin_statistics(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user, scopes=[\"admin\"])\n",
|
||
"):\n",
|
||
" users = [doc async for doc in users_collection.find().sort({'_id':-1})]\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/admin/users.html\", context={\"user\": user,\n",
|
||
" 'users': users\n",
|
||
" }\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "caf0fa14",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from collections import defaultdict\n",
|
||
"@app.get(\"/profile/admin/statistics\", response_class=HTMLResponse)\n",
|
||
"async def profile_admin_statistics(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user, scopes=[\"admin\"])\n",
|
||
"):\n",
|
||
" pipeline1 = [\n",
|
||
" {\"$match\": {\"roles\": \"user\"}}, \n",
|
||
" {\n",
|
||
" \n",
|
||
" \"$group\": {\n",
|
||
" \"_id\": \"$living_place\",\n",
|
||
" \"user_count\": {\"$sum\": 1},\n",
|
||
" \"user_ids\": {\"$push\": \"$_id\"}\n",
|
||
" }\n",
|
||
" },\n",
|
||
" {\"$sort\": {\"user_count\": -1}}\n",
|
||
" ]\n",
|
||
"\n",
|
||
" living_stats = await users_collection.aggregate(pipeline1).to_list(length=None)\n",
|
||
"\n",
|
||
"\n",
|
||
" for stat in living_stats:\n",
|
||
" if stat[\"_id\"]:\n",
|
||
" user_ids = stat[\"user_ids\"] # list of user _id values (ObjectId or str)\n",
|
||
" # If posts store user._id as string, convert ObjectId -> str\n",
|
||
" user_id_strings = [str(u) for u in user_ids]\n",
|
||
"\n",
|
||
" # post_count (use count_documents as you had)\n",
|
||
" stat[\"post_count\"] = await posts_collection.count_documents(\n",
|
||
" {\"user._id\": {\"$in\": user_id_strings}}\n",
|
||
" )\n",
|
||
"\n",
|
||
" # usernames\n",
|
||
" cursor = users_collection.find(\n",
|
||
" {\"_id\": {\"$in\": user_ids}},\n",
|
||
" {\"username\": 1, \"_id\": 1}\n",
|
||
" ).limit(1000)\n",
|
||
" stat[\"usernames\"] = await cursor.to_list(length=None)\n",
|
||
"\n",
|
||
" # fetch posts for these users (limit/fields optional)\n",
|
||
" posts_cursor = posts_collection.find(\n",
|
||
" {\"user._id\": {\"$in\": user_id_strings}},\n",
|
||
" {\"_id\": 1, \"title\": 1, \"created_at\": 1, \"user._id\": 1}\n",
|
||
" ).sort(\"created_at\", -1)\n",
|
||
" stat[\"posts\"] = await posts_cursor.to_list(length=None)\n",
|
||
"\n",
|
||
" pipeline2 = [\n",
|
||
" {\"$match\": {\"roles\": \"user\"}},\n",
|
||
" {\n",
|
||
" \"$project\": {\n",
|
||
" \"year_month\": {\n",
|
||
" \"$dateToString\": {\n",
|
||
" \"format\": \"%Y-%m\",\n",
|
||
" \"date\": {\"$toDate\": \"$_id\"}\n",
|
||
" }\n",
|
||
" }\n",
|
||
" }\n",
|
||
" },\n",
|
||
" {\n",
|
||
" \"$group\": {\n",
|
||
" \"_id\": \"$year_month\",\n",
|
||
" \"user_count\": {\"$sum\": 1},\n",
|
||
" \"user_ids\": {\"$push\": \"$_id\"}\n",
|
||
" }\n",
|
||
" },\n",
|
||
" {\"$sort\": {\"_id\": -1}}\n",
|
||
" ]\n",
|
||
"\n",
|
||
" reg_stats = await users_collection.aggregate(pipeline2).to_list(length=None)\n",
|
||
"\n",
|
||
" for stat in reg_stats:\n",
|
||
" user_ids = stat[\"user_ids\"] # list of user _id values (ObjectId or str)\n",
|
||
" user_id_strings = [str(u) for u in user_ids] # use this if posts.user._id is a str\n",
|
||
"\n",
|
||
" stat[\"post_count\"] = await posts_collection.count_documents(\n",
|
||
" {\"user._id\": {\"$in\": user_id_strings}}\n",
|
||
" )\n",
|
||
"\n",
|
||
" cursor = users_collection.find(\n",
|
||
" {\"_id\": {\"$in\": user_ids}},\n",
|
||
" {\"username\": 1, \"_id\": 0}\n",
|
||
" ).limit(1000)\n",
|
||
" stat[\"usernames\"] = await cursor.to_list(length=None)\n",
|
||
"\n",
|
||
" # fetch posts (flat list)\n",
|
||
" posts_cursor = posts_collection.find(\n",
|
||
" {\"user._id\": {\"$in\": user_id_strings}},\n",
|
||
" {\"_id\": 1, \"title\": 1, \"created_at\": 1, \"user._id\": 1}\n",
|
||
" ).sort(\"created_at\", -1)\n",
|
||
" stat[\"posts\"] = await posts_cursor.to_list(length=None)\n",
|
||
"\n",
|
||
" # OR: group posts per user\n",
|
||
" posts = stat[\"posts\"]\n",
|
||
" grouped = defaultdict(list)\n",
|
||
" for p in posts:\n",
|
||
" grouped[p[\"user\"][\"_id\"]].append(p)\n",
|
||
" stat[\"posts_by_user\"] = dict(grouped)\n",
|
||
"\n",
|
||
"\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/admin/statistics.html\", context={\"user\": user, \"living_stats\": living_stats,\n",
|
||
" \"reg_stats\": reg_stats,\n",
|
||
" \"all_users\": await users_collection.count_documents({ 'roles': \"user\" }),\n",
|
||
" \"all_city\": len(await users_collection.distinct(\"living_place\", filter={ 'roles': \"user\" }))\n",
|
||
" }\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "afe4a444",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/tg\")\n",
|
||
"async def tg_result_save(data:dict, token: str = Security(get_token)):\n",
|
||
" item = data.get('item')\n",
|
||
" result = data.get('result', {})\n",
|
||
" if item:\n",
|
||
" update_spec = {\"$push\": {\"tg\": result}}\n",
|
||
" await mongo_client[item.get('db')][item.get('col')].update_one({'_id': ObjectId(item.get('_id'))}, update_spec)"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "d78c34a3",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/social\", response_class=HTMLResponse)\n",
|
||
"async def profile_social(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" socials = [doc async for doc in social_collection.find({'user_id': str(user.get('_id'))})]\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/social.html\", context={\"user\": user, 'socials': socials}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "5ace34ae",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/invitation/{invitation_id}\", response_class=HTMLResponse)\n",
|
||
"async def profile_invitation(\n",
|
||
" request: Request,\n",
|
||
" invitation_id: str,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" data = await invitations_collection.find_one({'_id': ObjectId(invitation_id)})\n",
|
||
" match_ = {\n",
|
||
" \"$or\": [\n",
|
||
" {\"sender._id\": user.get('_id')},\n",
|
||
" {\"user._id\": user.get('_id')},\n",
|
||
" {\"receiver._id\": user.get('_id')}\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",
|
||
" \n",
|
||
" posts = [doc async for doc in get_delivery_aggregate(match_)]\n",
|
||
"\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/invitation.html\", context={ \"user\": user, \"doc\": data, \"data\": posts}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "2d516c27",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/invitation\", response_class=HTMLResponse)\n",
|
||
"async def profile_invitation(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" data = await invitations_collection.find({'prod': PROD,'user_id': user.get('_id')}).sort('_id', -1).to_list()\n",
|
||
" await notify_collection.update_many({'user_id':user.get('_id'),'type':'invitation'},{\"$set\":{'read':False}})\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/invitations.html\", context={ \"user\": user, \"data\": data, \"now\": datetime.now()}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "140fc75b",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post('/api/social/settings', response_model=dict)\n",
|
||
"async def social_setting(\n",
|
||
" id: Optional[str] = Body(None, embed=True), \n",
|
||
" key: Optional[str] = Body(None, embed=True),\n",
|
||
" check: Optional[bool] = Body(None, embed=True),\n",
|
||
" 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}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "6df17366",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"async def get_delivery_post(post_id):\n",
|
||
" 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"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "028d0310",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post('/api/v1/views')\n",
|
||
"async def get_views_count(request: Request):\n",
|
||
" url = (await request.json())['url']\n",
|
||
" if 'artist' in url:\n",
|
||
" user_id = url.split('/')[-1]\n",
|
||
" total_key = f\"total_w_c_artists:{user_id}\"\n",
|
||
" today_key = f\"today_w_c_artists:{user_id}\"\n",
|
||
" total_watch_count = await redis_client.incr(total_key)\n",
|
||
" today_date = datetime.now().strftime(\"%Y-%m-%d\")\n",
|
||
" daily_key = f\"{today_key}:{today_date}\"\n",
|
||
" today_watch_count = await redis_client.incr(daily_key)\n",
|
||
" return {\"totalViews\":total_watch_count,\"todayViews\":today_watch_count}\n",
|
||
" if 'post' in url:\n",
|
||
" paint_id = url.split('/')[-1]\n",
|
||
" total_key = f\"total_watch_count_paint:{paint_id}\"\n",
|
||
" today_key = f\"today_watch_count_paint:{paint_id}\"\n",
|
||
" total_watch_count = await redis_client.incr(total_key)\n",
|
||
" today_date = datetime.now().strftime(\"%Y-%m-%d\")\n",
|
||
" daily_key = f\"{today_key}:{today_date}\"\n",
|
||
" today_watch_count = await redis_client.incr(daily_key)\n",
|
||
" return {\"totalViews\":total_watch_count,\"todayViews\":today_watch_count}\n",
|
||
" if 'news' in url: \n",
|
||
" news_id = url.split('/')[-1]\n",
|
||
" total_key = f\"total_watch_count_news:{news_id}\"\n",
|
||
" today_key = f\"today_watch_count_news:{news_id}\"\n",
|
||
" total_watch_count = await redis_client.incr(total_key)\n",
|
||
" today_date = datetime.now().strftime(\"%Y-%m-%d\")\n",
|
||
" daily_key = f\"{today_key}:{today_date}\"\n",
|
||
" today_watch_count = await redis_client.incr(daily_key)\n",
|
||
" return {\"totalViews\":total_watch_count,\"todayViews\":today_watch_count}\n",
|
||
" return {\"totalViews\":0,\"todayViews\":0}"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "70e57454",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get('/api/v1/generic/post', response_model=dict)\n",
|
||
"async def get_posts(\n",
|
||
" per_page: Optional[int] = Query(4),\n",
|
||
" page: Optional[int] = Query(1),\n",
|
||
" user_id: Optional[str] = Query(None),\n",
|
||
" filter: Optional[str] = Query(None, description=\"JSON filter string or omitted\")\n",
|
||
"):\n",
|
||
" posts = [] \n",
|
||
" if filter:\n",
|
||
" try:\n",
|
||
" filt = json.loads(filter)\n",
|
||
" mql = convert_to_mql(filt)\n",
|
||
" def mql_to_mongo(m):\n",
|
||
" if \"and\" in m:\n",
|
||
" return {\"$and\": [mql_to_mongo(x) for x in m[\"and\"]]}\n",
|
||
" if \"or\" in m:\n",
|
||
" return {\"$or\": [mql_to_mongo(x) for x in m[\"or\"]]}\n",
|
||
" field, op, value = m[\"field\"], m[\"op\"], m[\"value\"]\n",
|
||
" if field == 'author_name':\n",
|
||
" field = 'author'\n",
|
||
" if field == 'user.gallery_name':\n",
|
||
" field = 'user.username'\n",
|
||
" if field == 'name':\n",
|
||
" field = 'title'\n",
|
||
" if op == \"like\":\n",
|
||
" return {field: {\"$regex\": safe_strip(value), \"$options\": \"i\"}}\n",
|
||
" return {field: value}\n",
|
||
" mongo_query = mql_to_mongo(mql)\n",
|
||
" for post in [doc async for doc in posts_collection.find(mongo_query).sort({\"_id\":-1}).limit(per_page * page + 8)][per_page * page - per_page:per_page * page]:\n",
|
||
" post['delivery'] = await get_delivery_post(str(post.get('_id')))\n",
|
||
" post['user']['avatar_url'] = f\"/a/{post['user'].get('_id')}\"\n",
|
||
" post['user']['name'] = post['user']['username']\n",
|
||
" photos = [{'url':f\"/b/posts/{i}\", 'type':'photo', 'sort_order':x+1} for x,i in enumerate(post['medias'])]\n",
|
||
" post['id'] = post.get('_id')\n",
|
||
" post['photos'] = photos\n",
|
||
" post['name'] = post['title']\n",
|
||
" post['author_name'] = post['author']\n",
|
||
" post['media'] = photos\n",
|
||
" posts.append(bson_clear(post))\n",
|
||
" post_count = await posts_collection.count_documents(mongo_query)\n",
|
||
" return {\n",
|
||
" 'current_page': page,\n",
|
||
" 'data':posts,\n",
|
||
" 'total': post_count,\n",
|
||
" 'to': 16,\n",
|
||
" 'last_page': post_count / per_page,\n",
|
||
" 'per_page': per_page\n",
|
||
" }\n",
|
||
" except json.JSONDecodeError:\n",
|
||
" pass\n",
|
||
" q = {}\n",
|
||
" \n",
|
||
" if user_id:\n",
|
||
" try:\n",
|
||
" if len(str(user_id)) < 6:\n",
|
||
" user_id = int(user_id)\n",
|
||
" user = await users_collection.find_one({'id':user_id})\n",
|
||
" q['user._id'] = str(user[\"_id\"])\n",
|
||
" else:\n",
|
||
" q['user._id'] = user_id\n",
|
||
" except:\n",
|
||
" q['user._id'] = user_id\n",
|
||
" for post in [doc async for doc in posts_collection.find(q).sort({\"_id\":-1}).limit(per_page * page + 8)][per_page * page - per_page:per_page * page]:\n",
|
||
" post['delivery'] = await get_delivery_post(str(post.get('_id')))\n",
|
||
" post['user']['avatar_url'] = f\"/a/{post['user'].get('_id')}\"\n",
|
||
" post['user']['name'] = post['user']['username']\n",
|
||
" post['author_name'] = post['author']\n",
|
||
" photos = [{'url':f\"/b/posts/{i}\", 'type':'photo', 'sort_order':x+1} for x,i in enumerate(post['medias'])]\n",
|
||
" post['id'] = post.get('_id')\n",
|
||
" post['photos'] = photos\n",
|
||
" post['media'] = photos\n",
|
||
" post['name'] = post['title']\n",
|
||
" posts.append(bson_clear(post))\n",
|
||
" post_count = await posts_collection.count_documents(q)\n",
|
||
" return {\n",
|
||
" 'current_page': page,\n",
|
||
" 'data':posts,\n",
|
||
" 'total': post_count,\n",
|
||
" 'to': 16,\n",
|
||
" 'last_page': post_count / per_page,\n",
|
||
" 'per_page': per_page\n",
|
||
" }"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "955d38bc",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get('/api/v2/statistics', response_model=dict)\n",
|
||
"async def statistics_v2(\n",
|
||
" user: Optional[str] = Query(None),\n",
|
||
" post: Optional[str] = Query(None),\n",
|
||
"):\n",
|
||
" if user:\n",
|
||
" return {\"statistics\":await users_collection.count_documents({ 'roles': \"user\" }),\"additional_stats\":len(await users_collection.distinct(\"living_place\", filter={ 'roles': \"user\" }))}\n",
|
||
" else:\n",
|
||
" return {\"statistics\":await posts_collection.count_documents({}),\"additional_stats\":await users_collection.count_documents({ 'roles': \"user\" })}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "eecd50fe",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get('/api/v2/user/{user_id}', response_model=dict)\n",
|
||
"async def get_user_by_id(\n",
|
||
" user_id: Optional[str]\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" user = await users_collection.find_one({'id': int(user_id)})\n",
|
||
" except:\n",
|
||
" user = await users_collection.find_one({'_id': ObjectId(user_id)})\n",
|
||
" timestamp = user.get('_id').generation_time\n",
|
||
" user['posts_count'] = await posts_collection.count_documents({'user._id':str(user.get('_id'))})\n",
|
||
" timestamp_str = timestamp.strftime(\"%Y-%m-%dT%H:%M:%S.%f\")[:-3] + \"Z\"\n",
|
||
" user['created_at'] = timestamp_str\n",
|
||
" user['avatar_url'] = f\"https://avtopost.kultmost.ru/a/{user.get('_id')}\"\n",
|
||
" user['name'] = user['username']\n",
|
||
" user.pop('hashed_password')\n",
|
||
" user.pop('phone')\n",
|
||
" user.pop('email')\n",
|
||
" return {'data':bson_clear(user)}"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "10385a18",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get('/api/v1/generic/post/{post_id}', response_model=dict)\n",
|
||
"async def get_post_by_id(\n",
|
||
" post_id: Optional[str]\n",
|
||
"):\n",
|
||
" try:\n",
|
||
" post = await posts_collection.find_one({'id': int(post_id)})\n",
|
||
" except:\n",
|
||
" post = await posts_collection.find_one({'_id': ObjectId(post_id)})\n",
|
||
" post['user']['avatar_url'] = f\"/a/{post['user'].get('_id')}\"\n",
|
||
" post['user']['name'] = post['user']['username']\n",
|
||
" photos = [{'url':f\"/b/posts/{i}\", 'type':'photo', 'sort_order':x+1} for x,i in enumerate(post['medias_original'])]\n",
|
||
" post['photos'] = photos\n",
|
||
" post['id'] = post.get('_id')\n",
|
||
" post['name'] = post['title']\n",
|
||
" post['media'] = photos\n",
|
||
" return bson_clear(post)"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "06e7d5f2",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Work"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "13c5c5ab",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/invitation/competition/{competition_id}\", response_class=HTMLResponse)\n",
|
||
"async def profile_invitation(\n",
|
||
" request: Request,\n",
|
||
" competition_id: str,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" competition = await competitions_col.find_one({'_id': ObjectId(competition_id)})\n",
|
||
" req = await competition_request_col.find_one({'competition_id': competition_id, 'user._id': user.get('_id')})\n",
|
||
" \n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/invitation/competition.html\", context={ \"user\": user, \"competition\": competition, \"req\": req}\n",
|
||
" )"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "cf6b2218",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post('/api/invitation/accept/{invitations_id}')\n",
|
||
"async def invitation_accept(invitations_id: str, request: Request):\n",
|
||
" payload = await request.json()\n",
|
||
" posts_ids: List[str] = payload.get(\"posts\", [])\n",
|
||
"\n",
|
||
" # optional: валидация\n",
|
||
" if not isinstance(posts_ids, list):\n",
|
||
" posts_ids = []\n",
|
||
"\n",
|
||
" await accept_invitations(invitations_id, posts_ids)\n",
|
||
" return {'ok': True}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "994a94e1",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get('/profile/delivery/{post_id}')\n",
|
||
"async def profile_delivery_history_post(request: Request, post_id:str, user: Dict = Security(get_current_user)):\n",
|
||
" data = await delivery_collection.find({'post_id': post_id, \"$or\": [\n",
|
||
" {\"sender._id\": user.get('_id')},\n",
|
||
" {\"user._id\": user.get('_id')},\n",
|
||
" {\"receiver._id\": user.get('_id')}\n",
|
||
" ]}).sort('_id', -1).to_list()\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request,\n",
|
||
" name=\"profile/delivery_history.html\",\n",
|
||
" context={\n",
|
||
" \"user\": user,\n",
|
||
" \"data\": data,\n",
|
||
" }\n",
|
||
" )\n"
|
||
],
|
||
"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,
|
||
"id": "6157e497",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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",
|
||
"# _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",
|
||
"# }\n",
|
||
"\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",
|
||
"# request=request,\n",
|
||
"# name=\"profile/delivery_old.html\",\n",
|
||
"# context={\n",
|
||
"# \"user\": user,\n",
|
||
"# \"data\": data,\n",
|
||
"# }\n",
|
||
"# )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "089ba597",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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",
|
||
" _match = {\n",
|
||
" \"$or\": [\n",
|
||
" {\"sender._id\": user.get('_id')},\n",
|
||
" {\"user._id\": user.get('_id')},\n",
|
||
" {\"receiver._id\": user.get('_id')}\n",
|
||
" ]\n",
|
||
" }\n",
|
||
"\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",
|
||
" request=request,\n",
|
||
" name=\"profile/delivery_old.html\",\n",
|
||
" context={\n",
|
||
" \"user\": user,\n",
|
||
" \"data\": data,\n",
|
||
" }\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "2148ef21",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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",
|
||
" _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",
|
||
" }\n",
|
||
"\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",
|
||
" request=request,\n",
|
||
" name=\"profile/delivery_old.html\",\n",
|
||
" context={\n",
|
||
" \"user\": user,\n",
|
||
" \"data\": data,\n",
|
||
" }\n",
|
||
" )"
|
||
],
|
||
"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,
|
||
"id": "96302958",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\n",
|
||
" \"/profile/delivery\",\n",
|
||
" response_class=HTMLResponse,\n",
|
||
" summary=\"Личный кабинет → Доставка Картин\",\n",
|
||
" description=\"\"\"\n",
|
||
" Отображает страницу с историей доставок пользователя.\n",
|
||
" \n",
|
||
" Показывает **все** доставки, где пользователь является либо отправителем (`sender`), \n",
|
||
" либо получателем (`receiver`).\n",
|
||
" \"\"\",\n",
|
||
" tags=[\"Profile\", \"Delivery\"],\n",
|
||
")\n",
|
||
"async def profile_delivery(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" \"\"\"\n",
|
||
" Рендерит HTML-страницу с доставками текущего пользователя.\n",
|
||
"\n",
|
||
" Запрашивает все документы из коллекции `delivery`, где пользователь \n",
|
||
" участвует как отправитель или получатель.\n",
|
||
"\n",
|
||
" Args:\n",
|
||
" request: FastAPI Request (для шаблонизатора)\n",
|
||
" user: Текущий авторизованный пользователь (из Security)\n",
|
||
"\n",
|
||
" Returns:\n",
|
||
" HTMLResponse с шаблоном profile/delivery.html\n",
|
||
" \"\"\"\n",
|
||
" _match = {\n",
|
||
" \"$or\": [\n",
|
||
" {\"sender._id\": user.get('_id')},\n",
|
||
" {\"user._id\": user.get('_id')},\n",
|
||
" {\"receiver._id\": user.get('_id')}\n",
|
||
" ]\n",
|
||
" }\n",
|
||
" \n",
|
||
" data = [doc async for doc in get_delivery_aggregate(_match)]\n",
|
||
"\n",
|
||
" exhibitions = await exhibition_collection.find({'_id': {'$in': [ObjectId(i.get('exhibition_id')) for i in data if i.get('exhibition_id')]}}).sort(\"_id\", -1).to_list()\n",
|
||
" competitions = await competitions_col.find({'_id': {'$in': [i.get('competition_id') for i in data if i.get('competition_id')]}}).sort(\"_id\", -1).to_list()\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request,\n",
|
||
" name=\"profile/delivery.html\",\n",
|
||
" context={\n",
|
||
" \"user\": user,\n",
|
||
" \"exhibitions\": exhibitions,\n",
|
||
" \"competitions\": competitions,\n",
|
||
" \"data\": data,\n",
|
||
" \"now\": datetime.now(),\n",
|
||
" \"page_title\": \"Доставка\",\n",
|
||
" \"active_section\": \"delivery\"\n",
|
||
" }\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "4f58b246",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/exhibition/send/invitations\")\n",
|
||
"async def api_exhibition_send_invitations(\n",
|
||
" background_tasks: BackgroundTasks,\n",
|
||
" exhibition_id: Optional[str] = Form(...),\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" background_tasks.add_task(exhibition_send_invitations, exhibition_id)\n",
|
||
" logging.info(f\"send exh inv {exhibition_id}\")\n",
|
||
" return {\"ok\": True}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "2b5d81a2",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.post(\"/api/exhibition/post/select\")\n",
|
||
"async def api_exhibition_post_select(\n",
|
||
" post_id: Optional[str] = Form(...),\n",
|
||
" exhibition_id: Optional[str] = Form(...),\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" await exhibition_collection.update_one(\n",
|
||
" {\n",
|
||
" \"_id\": ObjectId(exhibition_id),\n",
|
||
" \"user._id\": user.get(\"_id\")\n",
|
||
" },\n",
|
||
" [\n",
|
||
" {\n",
|
||
" \"$set\": {\n",
|
||
" \"posts\": {\n",
|
||
" \"$cond\": [\n",
|
||
" {\"$in\": [post_id, {\"$ifNull\": [\"$posts\", []]}]},\n",
|
||
" {\"$setDifference\": [\"$posts\", [post_id]]},\n",
|
||
" {\"$setUnion\": [\"$posts\", [post_id]]}\n",
|
||
" ]\n",
|
||
" }\n",
|
||
" }\n",
|
||
" }\n",
|
||
" ]\n",
|
||
" )\n",
|
||
" return {\"ok\": True}\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "5ca78d9c",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/partner/exhibition/{exhibition_id}/catalog\", response_class=HTMLResponse)\n",
|
||
"async def profile_partner_exhibitions_catalog(\n",
|
||
" request: Request,\n",
|
||
" exhibition_id: str,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)})\n",
|
||
" cursor = posts_collection.aggregate([\n",
|
||
" {\n",
|
||
" \"$match\": {\n",
|
||
" \"city\": {\"$nin\": [None, \"\"]}\n",
|
||
" }\n",
|
||
" },\n",
|
||
" { '$group': { '_id': \"$city\", 'count': { '$sum': 1 } } },\n",
|
||
" { '$sort': { '_id': 1 } }\n",
|
||
" ])\n",
|
||
"\n",
|
||
" living_places = [doc async for doc in cursor]\n",
|
||
" cursor = posts_collection.aggregate([\n",
|
||
" {\n",
|
||
" \"$match\": {\n",
|
||
" \"genre\": {\"$nin\": [None, \"\"]}\n",
|
||
" }\n",
|
||
" },\n",
|
||
" {\n",
|
||
" \"$project\": {\n",
|
||
" \"genres\": {\"$split\": [\"$genre\", \",\"]}\n",
|
||
" }\n",
|
||
" },\n",
|
||
" {\"$unwind\": \"$genres\"},\n",
|
||
" {\n",
|
||
" \"$match\": {\n",
|
||
" \"genres\": {\"$nin\": [\"\", None]}\n",
|
||
" }\n",
|
||
" },\n",
|
||
" {\n",
|
||
" \"$group\": {\n",
|
||
" \"_id\": \"$genres\",\n",
|
||
" \"count\": {\"$sum\": 1}\n",
|
||
" }\n",
|
||
" },\n",
|
||
" {\"$sort\": {\"_id\": 1}}\n",
|
||
" ])\n",
|
||
"\n",
|
||
" genres = [{'_id': doc[\"_id\"].strip(), 'count': doc[\"count\"]} async for doc in cursor]\n",
|
||
" users = await users_collection.find(\n",
|
||
" {\"roles\": {\"$in\": [\"partner\", \"galerist\"]}},\n",
|
||
" {\"username\": 1, \"_id\": 0}\n",
|
||
" ).to_list()\n",
|
||
"\n",
|
||
" usernames = [u[\"username\"] for u in users]\n",
|
||
" pipeline = [\n",
|
||
" {\"$match\": {\"user.roles\": {\"$in\": [\"partner\", \"galerist\"]}}},\n",
|
||
" {\"$group\": {\"_id\": \"$user.username\", \"count\": {\"$sum\": 1}}},\n",
|
||
" ]\n",
|
||
" cursor = posts_collection.aggregate(pipeline)\n",
|
||
" post_counts = await cursor.to_list(length=None)\n",
|
||
"\n",
|
||
" counts_by_username = {d[\"_id\"]: d[\"count\"] for d in post_counts}\n",
|
||
"\n",
|
||
" galerists = [{\"_id\": uname, \"count\": counts_by_username.get(uname, '')} for uname in usernames]\n",
|
||
" posts = [doc async for doc in posts_collection.find().sort('_id', -1).limit(32)]\n",
|
||
" d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list()\n",
|
||
" for post in posts:\n",
|
||
" for doc in d_posts:\n",
|
||
" if str(post.get('_id')) == doc.get('post_id') and post.get('status') not in ['Продано', 'Приглашение отклонено'] and doc.get('status') not in ['Приглашение отклонено'] and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):\n",
|
||
" post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')})\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/partner/exhibition catalog.html\", context={\"now\": datetime.now(), \"user\": user, \"galerists\": galerists, \"genres\": genres, \"living_places\": living_places, \"exhibition\": exhibition, \"posts\": posts}\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "b29511be",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@app.get(\"/profile/partner/exhibition/{exhibition_id}\", response_class=HTMLResponse)\n",
|
||
"async def profile_partner_exhibitions(\n",
|
||
" request: Request,\n",
|
||
" exhibition_id: str,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)})\n",
|
||
" obj_ids = [ObjectId(x) for x in exhibition.get('posts', [])]\n",
|
||
" posts = await posts_collection.find({\"_id\": {\"$in\": obj_ids}}).to_list() if obj_ids else []\n",
|
||
" d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list()\n",
|
||
" for post in posts:\n",
|
||
" for doc in d_posts:\n",
|
||
" if str(post.get('_id')) == doc.get('post_id') and post.get('status') not in ['Продано', 'Приглашение отклонено'] and doc.get('status') not in ['Приглашение отклонено'] and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):\n",
|
||
" post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')})\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/partner/exhibition.html\", context={\"now\": datetime.now(), \"user\": user, \"exhibition\": exhibition, \"posts\": posts}\n",
|
||
" )\n",
|
||
"\n",
|
||
"\n",
|
||
"@app.get(\"/profile/partner/exhibitions\", response_class=HTMLResponse)\n",
|
||
"async def profile_exhibitions(\n",
|
||
" request: Request,\n",
|
||
" user: Dict = Security(get_current_user)\n",
|
||
"):\n",
|
||
" data = await exhibition_collection.find({'user._id': user.get('_id')}).sort('_id', -1).to_list()\n",
|
||
" return templates.TemplateResponse(\n",
|
||
" request=request, name=\"profile/partner/exhibitions.html\", context={\"now\": datetime.now(), \"user\": user, \"data\": data}\n",
|
||
" )\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "8810213f",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Run"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "23ac9374",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import json\n",
|
||
"import os\n",
|
||
"\n",
|
||
"IGNORE = ['build.ipynb', 'data.ipynb']\n",
|
||
"\n",
|
||
"for _filename in [i for i in os.listdir() if 'ipynb' in i and i not in IGNORE]:\n",
|
||
" filename_without_extension = os.path.splitext(os.path.basename(_filename))[0]\n",
|
||
"\n",
|
||
" with open(f'{filename_without_extension}.ipynb', 'r') as f:\n",
|
||
" data = f.read()\n",
|
||
"\n",
|
||
" main = json.loads(data)\n",
|
||
" app = []\n",
|
||
" for cell in main['cells']:\n",
|
||
" if '## Run' in cell['source']:\n",
|
||
" break\n",
|
||
" if '# ignore\\n' in cell['source']:\n",
|
||
" continue\n",
|
||
" app.append(''.join(cell['source']))\n",
|
||
"\n",
|
||
" with open(f'{filename_without_extension}.py', 'w') as f:\n",
|
||
" f.write('\\n\\n'.join(app))\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "c7dc7c3e",
|
||
"metadata": {
|
||
"vscode": {
|
||
"languageId": "shellscript"
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"!uvicorn main:app --host 0.0.0.0 --port 8005 --reload"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "abf4c2df",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import uvicorn\n",
|
||
"uvicorn.run('main:app', host='0.0.0.0', port=8005, reload=True)\n"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "62a866f8",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Test"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "a6f67f24",
|
||
"metadata": {
|
||
"vscode": {
|
||
"languageId": "shellscript"
|
||
}
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"!pip freeze > req.txt"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "9d49d0bd",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import sys\n",
|
||
"sys.executable"
|
||
],
|
||
"display_name": ".venv"
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "7a9444d9",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import sys\n",
|
||
"sys.executable\n",
|
||
"!{sys.executable} -m pip install -r requirements-api.txt"
|
||
],
|
||
"display_name": ".venv"
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": ".venv",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.13.5"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|