Rewrite sync pymongo to async motor
This commit is contained in:
+126
-115
@@ -6,8 +6,8 @@ import asyncio
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from fastapi import FastAPI, Depends, Response, status
|
from fastapi import FastAPI, Depends, Response, status
|
||||||
|
|
||||||
from schemas.api_schemas import *
|
from schemas.api_schemas import BaseResponse, AddUserBody, AddCardBody, SelectChoice, ReactionCard, AddCommentBody
|
||||||
from schemas.base_schemas import *
|
from schemas.base_schemas import Card
|
||||||
from mongo_worker import MongoWorker
|
from mongo_worker import MongoWorker
|
||||||
from tools.base_moderation import moderate_text
|
from tools.base_moderation import moderate_text
|
||||||
|
|
||||||
@@ -15,177 +15,188 @@ from tools.base_moderation import moderate_text
|
|||||||
load_dotenv()
|
load_dotenv()
|
||||||
disable_docs = os.getenv("DISABLE_DOCS", "true").lower() == "true"
|
disable_docs = os.getenv("DISABLE_DOCS", "true").lower() == "true"
|
||||||
|
|
||||||
app: FastAPI = FastAPI(title="This OR That",
|
app: FastAPI = FastAPI(
|
||||||
summary="OpenAPI schema for \"This OR That\" project!",
|
title="This OR That",
|
||||||
version="0.1",
|
summary="OpenAPI schema for \"This OR That\" project!",
|
||||||
contact={"GitHub": "https://github.com/IgorVolochay/thisORthat"},
|
version="0.1",
|
||||||
docs_url=None if disable_docs else "/docs",
|
contact={"GitHub": "https://github.com/IgorVolochay/thisORthat"},
|
||||||
redoc_url=None if disable_docs else "/redoc",
|
docs_url=None if disable_docs else "/docs",
|
||||||
openapi_url=None if disable_docs else "/openapi.json")
|
redoc_url=None if disable_docs else "/redoc",
|
||||||
|
openapi_url=None if disable_docs else "/openapi.json",
|
||||||
|
)
|
||||||
mongo_worker = MongoWorker()
|
mongo_worker = MongoWorker()
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
async def startup_event():
|
||||||
|
await mongo_worker.create_indexes()
|
||||||
|
|
||||||
|
|
||||||
@app.get("/check_user", status_code=200)
|
@app.get("/check_user", status_code=200)
|
||||||
async def check_user(user_id: NonNegativeInt,
|
async def check_user(
|
||||||
mongo: MongoWorker = Depends(lambda: mongo_worker)) -> BaseResponse:
|
user_id: int,
|
||||||
result = mongo.check_user(user_id)
|
mongo: MongoWorker = Depends(lambda: mongo_worker),) -> BaseResponse:
|
||||||
|
result = await mongo.check_user(user_id)
|
||||||
return BaseResponse(result=result)
|
return BaseResponse(result=result)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/get_user", status_code=200)
|
@app.get("/get_user", status_code=200)
|
||||||
async def get_user(user_id: NonNegativeInt,
|
async def get_user(
|
||||||
response: Response,
|
user_id: int,
|
||||||
mongo: MongoWorker = Depends(lambda: mongo_worker)) -> BaseResponse:
|
response: Response,
|
||||||
if mongo.check_user(user_id):
|
mongo: MongoWorker = Depends(lambda: mongo_worker),) -> BaseResponse:
|
||||||
result = mongo.get_user(user_id)
|
if await mongo.check_user(user_id):
|
||||||
|
result = await mongo.get_user(user_id)
|
||||||
return BaseResponse(result=result)
|
return BaseResponse(result=result)
|
||||||
else:
|
response.status_code = status.HTTP_404_NOT_FOUND
|
||||||
response.status_code = status.HTTP_404_NOT_FOUND
|
return BaseResponse(result="User doesn't exist", error=True)
|
||||||
return BaseResponse(result="User doesn't exist", error=True)
|
|
||||||
|
|
||||||
@app.post("/add_user", status_code=201)
|
@app.post("/add_user", status_code=201)
|
||||||
async def add_user(new_user: AddUserBody,
|
async def add_user(
|
||||||
response: Response,
|
new_user: AddUserBody,
|
||||||
mongo: MongoWorker = Depends(lambda: mongo_worker)) -> BaseResponse:
|
response: Response,
|
||||||
if not mongo.check_user(new_user.user_id):
|
mongo: MongoWorker = Depends(lambda: mongo_worker),) -> BaseResponse:
|
||||||
result = mongo.add_user(new_user.user_id,
|
if not await mongo.check_user(new_user.user_id):
|
||||||
new_user.username,
|
result = await mongo.add_user(
|
||||||
new_user.first_name,
|
new_user.user_id,
|
||||||
new_user.last_name,
|
new_user.username,
|
||||||
new_user.photo_url)
|
new_user.first_name,
|
||||||
|
new_user.last_name,
|
||||||
|
new_user.photo_url,
|
||||||
|
)
|
||||||
return BaseResponse(result=result)
|
return BaseResponse(result=result)
|
||||||
else:
|
response.status_code = status.HTTP_409_CONFLICT
|
||||||
response.status_code = status.HTTP_409_CONFLICT
|
return BaseResponse(result="User already exist", error=True)
|
||||||
return BaseResponse(result="User already exist", error=True)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/get_card", status_code=200)
|
@app.get("/get_card", status_code=200)
|
||||||
async def get_card(card_id: NonNegativeInt,
|
async def get_card(
|
||||||
response: Response,
|
card_id: int,
|
||||||
mongo: MongoWorker = Depends(lambda: mongo_worker)) -> BaseResponse:
|
response: Response,
|
||||||
card = mongo.get_card(card_id)
|
mongo: MongoWorker = Depends(lambda: mongo_worker),) -> BaseResponse:
|
||||||
|
card = await mongo.get_card(card_id)
|
||||||
if card:
|
if card:
|
||||||
return BaseResponse(result=card)
|
return BaseResponse(result=card)
|
||||||
else:
|
response.status_code = status.HTTP_404_NOT_FOUND
|
||||||
response.status_code = status.HTTP_404_NOT_FOUND
|
return BaseResponse(result="There is no card with this card_id", error=True)
|
||||||
return BaseResponse(result="There is no card with this card_id", error=True)
|
|
||||||
|
|
||||||
@app.get("/get_random_cards", status_code=200)
|
@app.get("/get_random_cards", status_code=200)
|
||||||
async def get_random_cards(user_id: NonNegativeInt,
|
async def get_random_cards(
|
||||||
response: Response,
|
user_id: int,
|
||||||
mongo: MongoWorker = Depends(lambda: mongo_worker)) -> BaseResponse:
|
response: Response,
|
||||||
cards_visited = mongo.get_visited_cards(user_id)
|
mongo: MongoWorker = Depends(lambda: mongo_worker),) -> BaseResponse:
|
||||||
|
cards_visited = await mongo.get_visited_cards(user_id)
|
||||||
|
|
||||||
if cards_visited.error:
|
if cards_visited.error:
|
||||||
response.status_code = status.HTTP_401_UNAUTHORIZED
|
|
||||||
return cards_visited
|
|
||||||
elif not cards_visited.result.cards_visited:
|
|
||||||
random_cards = mongo.get_random_cards(10, True)
|
|
||||||
if random_cards:
|
|
||||||
return BaseResponse(result=random_cards)
|
|
||||||
else:
|
|
||||||
response.status_code = status.HTTP_404_NOT_FOUND
|
|
||||||
return BaseResponse(result="No active cards", error=True)
|
|
||||||
|
|
||||||
result: list[Card] = list()
|
|
||||||
trys = 3
|
|
||||||
while len(result) < 10 and trys != 0:
|
|
||||||
random_cards = mongo.get_random_cards(10, True)
|
|
||||||
if not random_cards:
|
|
||||||
response.status_code = status.HTTP_404_NOT_FOUND
|
|
||||||
return BaseResponse(result="No active cards", error=True)
|
|
||||||
filtered_cards, filtered_cards_id = mongo.filter_cards(random_cards, cards_visited.result.cards_visited)
|
|
||||||
trys -= 1
|
|
||||||
if not filtered_cards:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
result.extend(filtered_cards)
|
|
||||||
cards_visited.result.cards_visited.update(filtered_cards_id)
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
response.status_code = status.HTTP_404_NOT_FOUND
|
response.status_code = status.HTTP_404_NOT_FOUND
|
||||||
return BaseResponse(result="No active cards fo this user", error=True)
|
return cards_visited
|
||||||
else:
|
|
||||||
return BaseResponse(result=result)
|
# Передаём exclude_ids напрямую в запрос — один round-trip к БД вместо цикла
|
||||||
|
exclude_ids = cards_visited.result.cards_visited or None
|
||||||
|
random_cards = await mongo.get_random_cards(10, True, exclude_ids=exclude_ids)
|
||||||
|
|
||||||
|
if not random_cards:
|
||||||
|
response.status_code = status.HTTP_404_NOT_FOUND
|
||||||
|
return BaseResponse(result="No active cards for this user", error=True)
|
||||||
|
|
||||||
|
return BaseResponse(result=random_cards)
|
||||||
|
|
||||||
|
|
||||||
@app.post("/add_card", status_code=201)
|
@app.post("/add_card", status_code=201)
|
||||||
async def add_card(new_card: AddCardBody,
|
async def add_card(
|
||||||
response: Response,
|
new_card: AddCardBody,
|
||||||
mongo: MongoWorker = Depends(lambda: mongo_worker)) -> BaseResponse:
|
response: Response,
|
||||||
|
mongo: MongoWorker = Depends(lambda: mongo_worker),) -> BaseResponse:
|
||||||
if moderate_text(new_card.choice_A) and moderate_text(new_card.choice_B):
|
if moderate_text(new_card.choice_A) and moderate_text(new_card.choice_B):
|
||||||
card = mongo.add_card_by_api(new_card.choice_A,
|
card = await mongo.add_card_by_api(new_card.choice_A, new_card.choice_B, new_card.author_id)
|
||||||
new_card.choice_B,
|
|
||||||
new_card.author_id)
|
|
||||||
return BaseResponse(result=card)
|
return BaseResponse(result=card)
|
||||||
else:
|
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
return BaseResponse(result="Card has not passed base moderation", error=True)
|
||||||
return BaseResponse(result="Card has not passed base moderation", error=True)
|
|
||||||
|
|
||||||
|
|
||||||
@app.patch("/select_choice", status_code=200)
|
@app.patch("/select_choice", status_code=200)
|
||||||
async def select_choice(choice_data: SelectChoice,
|
async def select_choice(
|
||||||
response: Response,
|
choice_data: SelectChoice,
|
||||||
mongo: MongoWorker = Depends(lambda: mongo_worker)) -> BaseResponse:
|
response: Response,
|
||||||
check_visited = mongo.get_visited_cards(choice_data.user_id)
|
mongo: MongoWorker = Depends(lambda: mongo_worker),) -> BaseResponse:
|
||||||
|
check_visited = await mongo.get_visited_cards(choice_data.user_id)
|
||||||
if check_visited.error:
|
if check_visited.error:
|
||||||
response.status_code = status.HTTP_404_NOT_FOUND
|
response.status_code = status.HTTP_404_NOT_FOUND
|
||||||
return check_visited
|
return check_visited
|
||||||
elif not check_visited.error and choice_data.card_id in check_visited.result.cards_visited:
|
if choice_data.card_id in check_visited.result.cards_visited:
|
||||||
response.status_code = status.HTTP_403_FORBIDDEN
|
response.status_code = status.HTTP_403_FORBIDDEN
|
||||||
return BaseResponse(result="Card already visited!", error=True)
|
return BaseResponse(result="Card already visited!", error=True)
|
||||||
else:
|
|
||||||
select_choice_result = mongo.select_choice(choice_data.card_id, choice_data.choice)
|
select_choice_result = await mongo.select_choice(choice_data.card_id, choice_data.choice)
|
||||||
if select_choice_result.error:
|
if select_choice_result.error:
|
||||||
response.status_code = status.HTTP_404_NOT_FOUND
|
response.status_code = status.HTTP_404_NOT_FOUND
|
||||||
return select_choice_result
|
return select_choice_result
|
||||||
else:
|
|
||||||
update_visited_result = mongo.update_visited_cards(choice_data.user_id, choice_data.card_id)
|
await mongo.update_visited_cards(choice_data.user_id, choice_data.card_id)
|
||||||
return BaseResponse(result="Select choice complite!")
|
return BaseResponse(result="Select choice complete!")
|
||||||
|
|
||||||
|
|
||||||
@app.patch("/like_card", status_code=200)
|
@app.patch("/like_card", status_code=200)
|
||||||
async def like_card(like_data: ReactionCard,
|
async def like_card(
|
||||||
response: Response,
|
like_data: ReactionCard,
|
||||||
mongo: MongoWorker = Depends(lambda: mongo_worker)) -> BaseResponse:
|
response: Response,
|
||||||
result = mongo.like_card(like_data.card_id, like_data.user_id)
|
mongo: MongoWorker = Depends(lambda: mongo_worker),) -> BaseResponse:
|
||||||
|
result = await mongo.like_card(like_data.card_id, like_data.user_id)
|
||||||
if not result.error and result.result:
|
if not result.error and result.result:
|
||||||
return BaseResponse(result="Added like to card")
|
return BaseResponse(result="Added like to card")
|
||||||
else:
|
response.status_code = status.HTTP_404_NOT_FOUND
|
||||||
response.status_code = status.HTTP_404_NOT_FOUND
|
return result
|
||||||
return result
|
|
||||||
|
|
||||||
@app.patch("/dislike_card", status_code=200)
|
@app.patch("/dislike_card", status_code=200)
|
||||||
async def dislike_card(dislike_data: ReactionCard,
|
async def dislike_card(
|
||||||
response: Response,
|
dislike_data: ReactionCard,
|
||||||
mongo: MongoWorker = Depends(lambda: mongo_worker)) -> BaseResponse:
|
response: Response,
|
||||||
result = mongo.dislike_card(dislike_data.card_id, dislike_data.user_id)
|
mongo: MongoWorker = Depends(lambda: mongo_worker),) -> BaseResponse:
|
||||||
|
result = await mongo.dislike_card(dislike_data.card_id, dislike_data.user_id)
|
||||||
if not result.error and result.result:
|
if not result.error and result.result:
|
||||||
return BaseResponse(result="Added dislike to card")
|
return BaseResponse(result="Added dislike to card")
|
||||||
else:
|
response.status_code = status.HTTP_404_NOT_FOUND
|
||||||
response.status_code = status.HTTP_404_NOT_FOUND
|
return result
|
||||||
return result
|
|
||||||
|
|
||||||
@app.post("/comment", status_code=201)
|
@app.post("/comment", status_code=201)
|
||||||
async def comment(comment_info: AddCommentBody,
|
async def comment(
|
||||||
response: Response,
|
comment_info: AddCommentBody,
|
||||||
mongo: MongoWorker = Depends(lambda: mongo_worker)) -> BaseResponse:
|
response: Response,
|
||||||
|
mongo: MongoWorker = Depends(lambda: mongo_worker),) -> BaseResponse:
|
||||||
if not moderate_text(comment_info.comment_text):
|
if not moderate_text(comment_info.comment_text):
|
||||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||||
return BaseResponse(result="Comment has not passed base moderation", error=True)
|
return BaseResponse(result="Comment has not passed base moderation", error=True)
|
||||||
|
|
||||||
result = mongo.add_comment(comment_info.author_id, comment_info.card_id, comment_info.comment_text)
|
result = await mongo.add_comment(comment_info.author_id, comment_info.card_id, comment_info.comment_text)
|
||||||
if result.error and result.result in ["User doesn't exist", "Card doesn't exist"]:
|
if result.error and result.result in ["User doesn't exist", "Card doesn't exist"]:
|
||||||
response.status_code = status.HTTP_404_NOT_FOUND
|
response.status_code = status.HTTP_404_NOT_FOUND
|
||||||
return result
|
return result
|
||||||
elif result.error:
|
if result.error:
|
||||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||||
return result
|
return result
|
||||||
else:
|
return result
|
||||||
|
|
||||||
|
@app.get("/get_comments", status_code=200)
|
||||||
|
async def get_comments(
|
||||||
|
card_id: int,
|
||||||
|
response: Response,
|
||||||
|
mongo: MongoWorker = Depends(lambda: mongo_worker),) -> BaseResponse:
|
||||||
|
result = await mongo.get_comments(card_id)
|
||||||
|
if result.error:
|
||||||
|
response.status_code = status.HTTP_404_NOT_FOUND
|
||||||
return result
|
return result
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
config = uvicorn.Config("main:app", port=5000, log_level="debug")
|
config = uvicorn.Config("main:app", host="0.0.0.0", port=5000, log_level="info")
|
||||||
server = uvicorn.Server(config)
|
server = uvicorn.Server(config)
|
||||||
await server.serve()
|
await server.serve()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
+185
-159
@@ -1,22 +1,31 @@
|
|||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
import pymongo
|
import motor.motor_asyncio
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from pymongo import ReturnDocument
|
||||||
|
|
||||||
from schemas.base_schemas import *
|
from schemas.base_schemas import User, Visited, Card, Comment
|
||||||
from schemas.api_schemas import *
|
from schemas.api_schemas import BaseResponse
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MongoWorker:
|
class MongoWorker:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
self.client = pymongo.MongoClient(host = os.getenv('MONGO_HOST'),
|
self.client = motor.motor_asyncio.AsyncIOMotorClient(
|
||||||
port = int(os.getenv('MONGO_PORT')),
|
host=os.getenv('MONGO_HOST'),
|
||||||
username = os.getenv('MONGO_USER'),
|
port=int(os.getenv('MONGO_PORT', 27017)),
|
||||||
password = os.getenv('MONGO_PASS'))
|
username=os.getenv('MONGO_USER'),
|
||||||
|
password=os.getenv('MONGO_PASS'),
|
||||||
|
serverSelectionTimeoutMS=5000,
|
||||||
|
connectTimeoutMS=5000,
|
||||||
|
)
|
||||||
self.db = self.client["data"]
|
self.db = self.client["data"]
|
||||||
self.users_data = self.db["users"]
|
self.users_data = self.db["users"]
|
||||||
self.visited_data = self.db["visited"]
|
self.visited_data = self.db["visited"]
|
||||||
@@ -24,191 +33,208 @@ class MongoWorker:
|
|||||||
self.game_data = self.db["cards"]
|
self.game_data = self.db["cards"]
|
||||||
self.comments_data = self.db["comments"]
|
self.comments_data = self.db["comments"]
|
||||||
|
|
||||||
|
async def create_indexes(self) -> None:
|
||||||
def check_user(self, user_id: int) -> bool:
|
"""Создаёт индексы при старте приложения."""
|
||||||
if self.users_data.find_one({"user_id": user_id}):
|
await self.users_data.create_index("user_id", unique=True)
|
||||||
return True
|
await self.game_data.create_index("card_id", unique=True)
|
||||||
else:
|
await self.game_data.create_index("active_status")
|
||||||
return False
|
await self.visited_data.create_index("user_id", unique=True)
|
||||||
|
await self.comments_data.create_index("comment_id", unique=True)
|
||||||
def add_user(self, user_id: int, username: str, first_name: str, last_name: str, photo_url: str) -> User:
|
logger.info("MongoDB indexes created.")
|
||||||
new_user = User(user_id=user_id,
|
|
||||||
username=username,
|
|
||||||
first_name=first_name,
|
|
||||||
last_name=last_name,
|
|
||||||
photo_url=photo_url,
|
|
||||||
registration_date=datetime.now().isoformat())
|
|
||||||
try:
|
|
||||||
self.users_data.insert_one(new_user.model_dump())
|
|
||||||
return new_user
|
|
||||||
except Exception as exception:
|
|
||||||
return new_user
|
|
||||||
|
|
||||||
def get_user(self, user_id: int) -> User:
|
|
||||||
return User.model_validate(self.users_data.find_one({"user_id": user_id}))
|
|
||||||
|
|
||||||
|
|
||||||
def get_and_update_counter(self, counter_name: str) -> int:
|
async def check_user(self, user_id: int) -> bool:
|
||||||
counter = self.counters.find_one_and_update(
|
document = await self.users_data.find_one({"user_id": user_id}, {"_id": 1})
|
||||||
|
return document is not None
|
||||||
|
|
||||||
|
async def add_user(
|
||||||
|
self, user_id: int, username: str, first_name: str, last_name: str, photo_url: str) -> User:
|
||||||
|
new_user = User(
|
||||||
|
user_id=user_id,
|
||||||
|
username=username,
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name,
|
||||||
|
photo_url=photo_url,
|
||||||
|
registration_date=datetime.now().isoformat(),
|
||||||
|
)
|
||||||
|
await self.users_data.insert_one(new_user.model_dump())
|
||||||
|
return new_user
|
||||||
|
|
||||||
|
async def get_user(self, user_id: int) -> User:
|
||||||
|
document = await self.users_data.find_one({"user_id": user_id})
|
||||||
|
return User.model_validate(document)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_and_update_counter(self, counter_name: str) -> int:
|
||||||
|
"""Атомарно инкрементирует счётчик и возвращает новое значение."""
|
||||||
|
counter = await self.counters.find_one_and_update(
|
||||||
{"counter_name": counter_name},
|
{"counter_name": counter_name},
|
||||||
{"$inc": {"counter": 1}},
|
{"$inc": {"counter": 1}},
|
||||||
upsert=True,
|
upsert=True,
|
||||||
return_document=True)
|
return_document=ReturnDocument.AFTER,
|
||||||
|
)
|
||||||
return counter["counter"]
|
return counter["counter"]
|
||||||
|
|
||||||
|
|
||||||
def get_visited_cards(self, user_id: int) -> BaseResponse:
|
async def get_visited_cards(self, user_id: int) -> BaseResponse:
|
||||||
document = self.visited_data.find_one({"user_id": user_id})
|
document = await self.visited_data.find_one({"user_id": user_id})
|
||||||
if not document:
|
if not document:
|
||||||
check_user = self.check_user(user_id)
|
if await self.check_user(user_id):
|
||||||
if check_user:
|
return BaseResponse(result=Visited(user_id=user_id, cards_visited=set()))
|
||||||
return BaseResponse(result=Visited(user_id=user_id,
|
return BaseResponse(result="User doesn't exist", error=True)
|
||||||
cards_visited=set()))
|
return BaseResponse(result=Visited.model_validate(document))
|
||||||
else:
|
|
||||||
return BaseResponse(result="User doesn't exist", error=True)
|
async def update_visited_cards(self, user_id: int, visited_card_id: int) -> Visited:
|
||||||
else:
|
updated = await self.visited_data.find_one_and_update(
|
||||||
return BaseResponse(result=Visited.model_validate(document))
|
{"user_id": user_id},
|
||||||
|
{"$addToSet": {"cards_visited": visited_card_id}},
|
||||||
|
upsert=True,
|
||||||
|
return_document=ReturnDocument.AFTER,
|
||||||
|
)
|
||||||
|
return Visited.model_validate(updated)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_card(self, card_id: int) -> Optional[Card]:
|
||||||
|
document = await self.game_data.find_one({"card_id": card_id})
|
||||||
|
if document:
|
||||||
|
return Card.model_validate(document)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_random_cards(self, amount: int,active_status: bool,exclude_ids: Optional[set[int]] = None,) -> Optional[list[Card]]:
|
||||||
|
"""Возвращает случайные карточки, исключая уже просмотренные (одним запросом)."""
|
||||||
|
match_filter: dict = {"active_status": active_status}
|
||||||
|
if exclude_ids:
|
||||||
|
match_filter["card_id"] = {"$nin": list(exclude_ids)}
|
||||||
|
|
||||||
|
pipeline = [
|
||||||
|
{"$match": match_filter},
|
||||||
|
{"$sample": {"size": amount}},
|
||||||
|
]
|
||||||
|
raw_items = await self.game_data.aggregate(pipeline).to_list(length=amount)
|
||||||
|
|
||||||
|
if raw_items:
|
||||||
|
return [Card.model_validate(item) for item in raw_items]
|
||||||
|
return None
|
||||||
|
|
||||||
def filter_cards(self, random_cards: list[Card], cards_visited: set) -> tuple[list[Card], list[int]]:
|
def filter_cards(self, random_cards: list[Card], cards_visited: set) -> tuple[list[Card], list[int]]:
|
||||||
filtered_cards = [card for card in random_cards if card.card_id not in cards_visited]
|
filtered_cards = [card for card in random_cards if card.card_id not in cards_visited]
|
||||||
filtered_cards_id = [filtered_card.card_id for filtered_card in filtered_cards]
|
filtered_cards_id = [card.card_id for card in filtered_cards]
|
||||||
|
|
||||||
return filtered_cards, filtered_cards_id
|
return filtered_cards, filtered_cards_id
|
||||||
|
|
||||||
def update_visited_cards(self, user_id: int, visited_card_id: int) -> Visited:
|
async def add_card_by_api(self, choice_A: str, choice_B: str, author_id: int) -> Card:
|
||||||
update_visited = self.visited_data.find_one_and_update({"user_id": user_id},
|
new_card = Card(
|
||||||
{"$addToSet": {"cards_visited": visited_card_id}},
|
card_id=await self.get_and_update_counter(counter_name="card"),
|
||||||
upsert=True,
|
choice_A=choice_A,
|
||||||
return_document=True)
|
choice_B=choice_B,
|
||||||
return Visited.model_validate(update_visited)
|
author_id=author_id,
|
||||||
|
creation_date=datetime.now().isoformat(),
|
||||||
|
)
|
||||||
|
await self.game_data.insert_one(new_card.model_dump())
|
||||||
|
return new_card
|
||||||
|
|
||||||
|
async def add_card_by_base_model(self, new_card: Card) -> Optional[Card]:
|
||||||
def add_card_by_api(self, choice_A: str, choice_B: str, author_id: int) -> Card:
|
new_card.card_id = await self.get_and_update_counter(counter_name="card")
|
||||||
new_card = Card(card_id=self.get_and_update_counter(counter_name="card"),
|
|
||||||
choice_A=choice_A,
|
|
||||||
choice_B=choice_B,
|
|
||||||
author_id=author_id,
|
|
||||||
creation_date=datetime.now().isoformat())
|
|
||||||
try:
|
try:
|
||||||
self.game_data.insert_one(new_card.model_dump())
|
await self.game_data.insert_one(new_card.model_dump())
|
||||||
return new_card
|
|
||||||
except Exception as exception:
|
|
||||||
print(exception)
|
|
||||||
return new_card
|
return new_card
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error("Failed to insert card: %s", exc)
|
||||||
|
raise
|
||||||
|
|
||||||
def add_card_by_base_model(self, new_card: Card) -> Optional[Card]:
|
async def select_choice(self, card_id: int, choice: str) -> BaseResponse:
|
||||||
new_card.card_id = self.get_and_update_counter(counter_name="card")
|
|
||||||
try:
|
|
||||||
self.game_data.insert_one(new_card.model_dump())
|
|
||||||
return new_card
|
|
||||||
except Exception as exception:
|
|
||||||
print(exception)
|
|
||||||
return new_card
|
|
||||||
|
|
||||||
def get_card(self, card_id: int) -> Optional[Card]:
|
|
||||||
document = self.game_data.find_one({"card_id": card_id})
|
|
||||||
if document:
|
|
||||||
return Card.model_validate(document)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_random_cards(self, amount: int, active_status: bool) -> Optional[list[Card]]:
|
|
||||||
pipeline = [{"$match": {"active_status": active_status}},
|
|
||||||
{"$sample": {"size": amount}}]
|
|
||||||
raw_items = list(self.game_data.aggregate(pipeline))
|
|
||||||
|
|
||||||
if raw_items:
|
|
||||||
validated_items = [Card.model_validate(item) for item in raw_items]
|
|
||||||
return validated_items
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def select_choice(self, card_id: int, choice: str) -> BaseResponse:
|
|
||||||
if choice == "A":
|
if choice == "A":
|
||||||
count_choice = "count_choice_A"
|
count_field = "count_choice_A"
|
||||||
elif choice == "B":
|
elif choice == "B":
|
||||||
count_choice = "count_choice_B"
|
count_field = "count_choice_B"
|
||||||
else:
|
else:
|
||||||
return BaseResponse(result="Wrong choice", error=True)
|
return BaseResponse(result="Wrong choice", error=True)
|
||||||
|
|
||||||
result = self.game_data.find_one_and_update({"card_id": card_id},
|
result = await self.game_data.find_one_and_update(
|
||||||
{"$inc": {"count_total": 1, count_choice: 1}})
|
{"card_id": card_id},
|
||||||
|
{"$inc": {"count_total": 1, count_field: 1}},
|
||||||
|
)
|
||||||
if not result:
|
if not result:
|
||||||
return BaseResponse(result="Card doesn't exist", error=True)
|
return BaseResponse(result="Card doesn't exist", error=True)
|
||||||
else:
|
return BaseResponse(result=True, error=False)
|
||||||
return BaseResponse(result=result, error=False)
|
|
||||||
|
|
||||||
def check_user_reactions(self, user_id: int, card_id: int) -> BaseResponse:
|
|
||||||
user_info: User = self.get_user(user_id)
|
|
||||||
liked_card_ids: list = user_info.liked_card_ids
|
|
||||||
disliked_card_ids: list = user_info.disliked_card_ids
|
|
||||||
|
|
||||||
if card_id in liked_card_ids:
|
async def check_user_reactions(self, user_id: int, card_id: int) -> BaseResponse:
|
||||||
|
user_info: User = await self.get_user(user_id)
|
||||||
|
if card_id in user_info.liked_card_ids:
|
||||||
return BaseResponse(result="Card already liked", error=True)
|
return BaseResponse(result="Card already liked", error=True)
|
||||||
elif card_id in disliked_card_ids:
|
if card_id in user_info.disliked_card_ids:
|
||||||
return BaseResponse(result="Card already disliked", error=True)
|
return BaseResponse(result="Card already disliked", error=True)
|
||||||
else:
|
return BaseResponse(result="No reactions", error=False)
|
||||||
return BaseResponse(result="No reactions", error=False)
|
|
||||||
|
|
||||||
def like_card(self, card_id: int, user_id: int) -> BaseResponse:
|
async def like_card(self, card_id: int, user_id: int) -> BaseResponse:
|
||||||
if self.check_user(user_id):
|
if not await self.check_user(user_id):
|
||||||
user_reaction = self.check_user_reactions(user_id, card_id)
|
|
||||||
if user_reaction.error:
|
|
||||||
return user_reaction
|
|
||||||
update_card_info = self.game_data.find_one_and_update({"card_id": card_id},
|
|
||||||
{"$inc": {"count_likes": 1}})
|
|
||||||
if not update_card_info:
|
|
||||||
return BaseResponse(result="Card doesn't exist", error=True)
|
|
||||||
|
|
||||||
add_card_to_user = self.users_data.update_one({'user_id': user_id},
|
|
||||||
{'$push': {'liked_card_ids': card_id}})
|
|
||||||
if not add_card_to_user:
|
|
||||||
return BaseResponse(result="User doesn't exist", error=True)
|
|
||||||
else:
|
|
||||||
return BaseResponse(result=True, error=False)
|
|
||||||
else:
|
|
||||||
return BaseResponse(result="User doesn't exist", error=True)
|
return BaseResponse(result="User doesn't exist", error=True)
|
||||||
|
|
||||||
def dislike_card(self, card_id: int, user_id: int) -> BaseResponse:
|
user_reaction = await self.check_user_reactions(user_id, card_id)
|
||||||
if self.check_user(user_id):
|
if user_reaction.error:
|
||||||
user_reaction = self.check_user_reactions(user_id, card_id)
|
return user_reaction
|
||||||
if user_reaction.error:
|
|
||||||
return user_reaction
|
|
||||||
update_card_info = self.game_data.find_one_and_update({"card_id": card_id},
|
|
||||||
{"$inc": {"count_dislikes": 1}})
|
|
||||||
if not update_card_info:
|
|
||||||
return BaseResponse(result="Card doesn't exist", error=True)
|
|
||||||
|
|
||||||
add_card_to_user = self.users_data.update_one({'user_id': user_id},
|
updated_card = await self.game_data.find_one_and_update(
|
||||||
{'$push': {'disliked_card_ids': card_id}})
|
{"card_id": card_id},
|
||||||
if not add_card_to_user:
|
{"$inc": {"count_likes": 1}},
|
||||||
return BaseResponse(result="User doesn't exist", error=True)
|
)
|
||||||
else:
|
if not updated_card:
|
||||||
return BaseResponse(result=True, error=False)
|
return BaseResponse(result="Card doesn't exist", error=True)
|
||||||
else:
|
|
||||||
|
await self.users_data.update_one(
|
||||||
|
{"user_id": user_id},
|
||||||
|
{"$push": {"liked_card_ids": card_id}},
|
||||||
|
)
|
||||||
|
return BaseResponse(result=True, error=False)
|
||||||
|
|
||||||
|
async def dislike_card(self, card_id: int, user_id: int) -> BaseResponse:
|
||||||
|
if not await self.check_user(user_id):
|
||||||
return BaseResponse(result="User doesn't exist", error=True)
|
return BaseResponse(result="User doesn't exist", error=True)
|
||||||
|
|
||||||
def add_comment(self, user_id: int, card_id: int, comment_text: str) -> BaseResponse:
|
user_reaction = await self.check_user_reactions(user_id, card_id)
|
||||||
if self.check_user(user_id):
|
if user_reaction.error:
|
||||||
if self.get_card(card_id):
|
return user_reaction
|
||||||
new_comment = Comment(comment_id=self.get_and_update_counter(counter_name="comment"),
|
|
||||||
author_id=user_id,
|
updated_card = await self.game_data.find_one_and_update(
|
||||||
card_id=card_id,
|
{"card_id": card_id},
|
||||||
commet_text=comment_text,
|
{"$inc": {"count_dislikes": 1}},
|
||||||
creation_date=datetime.now().isoformat())
|
)
|
||||||
result = self.comments_data.insert_one(new_comment.model_dump())
|
if not updated_card:
|
||||||
if result:
|
return BaseResponse(result="Card doesn't exist", error=True)
|
||||||
update_user_comments = self.users_data.find_one_and_update({"user_id": user_id},
|
|
||||||
{"$addToSet": {"comments_ids": new_comment.comment_id}})
|
await self.users_data.update_one(
|
||||||
if update_user_comments:
|
{"user_id": user_id},
|
||||||
return BaseResponse(result=new_comment)
|
{"$push": {"disliked_card_ids": card_id}},
|
||||||
else:
|
)
|
||||||
return BaseResponse(result="Difficulty adding comment_id to user", error=True)
|
return BaseResponse(result=True, error=False)
|
||||||
else:
|
|
||||||
return BaseResponse(result="Add comment error", error=True)
|
|
||||||
else:
|
async def add_comment(self, user_id: int, card_id: int, comment_text: str) -> BaseResponse:
|
||||||
return BaseResponse(result="Card doesn't exist", error=True)
|
if not await self.check_user(user_id):
|
||||||
else:
|
|
||||||
return BaseResponse(result="User doesn't exist", error=True)
|
return BaseResponse(result="User doesn't exist", error=True)
|
||||||
|
if not await self.get_card(card_id):
|
||||||
|
return BaseResponse(result="Card doesn't exist", error=True)
|
||||||
|
|
||||||
|
new_comment = Comment(
|
||||||
|
comment_id=await self.get_and_update_counter(counter_name="comment"),
|
||||||
|
author_id=user_id,
|
||||||
|
card_id=card_id,
|
||||||
|
commet_text=comment_text,
|
||||||
|
creation_date=datetime.now().isoformat(),
|
||||||
|
)
|
||||||
|
await self.comments_data.insert_one(new_comment.model_dump())
|
||||||
|
|
||||||
|
updated_user = await self.users_data.find_one_and_update(
|
||||||
|
{"user_id": user_id},
|
||||||
|
{"$addToSet": {"comments_ids": new_comment.comment_id}},
|
||||||
|
return_document=ReturnDocument.AFTER,
|
||||||
|
)
|
||||||
|
if not updated_user:
|
||||||
|
return BaseResponse(result="Difficulty adding comment_id to user", error=True)
|
||||||
|
|
||||||
|
return BaseResponse(result=new_comment)
|
||||||
|
|
||||||
|
async def get_comments(self, card_id: int) -> BaseResponse:
|
||||||
|
comments = await self.comments_data.find({"card_id": card_id}).sort("creation_date", -1).to_list(length=None)
|
||||||
|
comments = [Comment.model_validate(comment) for comment in comments]
|
||||||
|
return BaseResponse(result=comments)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
fastapi==0.115.7
|
fastapi==0.115.7
|
||||||
pymongo==4.10.1
|
motor==3.7.0
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
uvicorn==0.34.0
|
uvicorn==0.34.0
|
||||||
Reference in New Issue
Block a user