comment-delete-handling-patch
All checks were successful
Deploy on push / deploy (push) Successful in 1m15s

This commit is contained in:
Untone 2025-03-20 11:01:39 +03:00
parent 354bda0efa
commit ae48a18536
5 changed files with 48 additions and 34 deletions

View File

@ -1,3 +1,6 @@
#### [0.4.12] - 2025-02-12
- `delete_reaction` detects comments and uses `deleted_at` update
#### [0.4.11] - 2025-02-12
- `create_draft` resolver requires draft_id fixed
- `create_draft` resolver defaults body and title fields to empty string

6
requirements.dev.txt Normal file
View File

@ -0,0 +1,6 @@
fakeredis
pytest
pytest-asyncio
pytest-cov
mypy
ruff

View File

@ -4,11 +4,8 @@ authlib
passlib
google-analytics-data
dogpile-cache
opensearch-py
colorlog
psycopg2-binary
dogpile-cache
httpx
redis[hiredis]
sentry-sdk[starlette,sqlalchemy]
@ -17,10 +14,4 @@ gql
ariadne
granian
pydantic
fakeredis
pytest
pytest-asyncio
pytest-cov
mypy
ruff
pydantic

View File

@ -1,5 +1,5 @@
from operator import or_
import time
from operator import or_
from sqlalchemy.sql import and_
@ -56,9 +56,11 @@ async def load_drafts(_, info):
return {"error": "User ID and author ID are required"}
with local_session() as session:
drafts = session.query(Draft).filter(or_(
Draft.authors.any(Author.id == author_id),
Draft.created_by == author_id)).all()
drafts = (
session.query(Draft)
.filter(or_(Draft.authors.any(Author.id == author_id), Draft.created_by == author_id))
.all()
)
return {"drafts": drafts}
@ -99,7 +101,7 @@ async def create_draft(_, info, draft_input):
# Проверяем обязательные поля
if "body" not in draft_input or not draft_input["body"]:
draft_input["body"] = "" # Пустая строка вместо NULL
if "title" not in draft_input or not draft_input["title"]:
draft_input["title"] = "" # Пустая строка вместо NULL
@ -123,23 +125,29 @@ async def create_draft(_, info, draft_input):
@mutation.field("update_draft")
@login_required
async def update_draft(_, info, draft_input):
async def update_draft(_, info, draft_id: int, draft_input):
"""Обновляет черновик публикации.
Args:
draft_id: ID черновика для обновления
draft_input: Данные для обновления черновика
Returns:
dict: Обновленный черновик или сообщение об ошибке
"""
user_id = info.context.get("user_id")
author_dict = info.context.get("author", {})
author_id = author_dict.get("id")
draft_id = draft_input.get("id")
if not draft_id:
return {"error": "Draft ID is required"}
if not user_id or not author_id:
return {"error": "Author ID are required"}
with local_session() as session:
draft = session.query(Draft).filter(Draft.id == draft_id).first()
del draft_input["id"]
Draft.update(draft, {**draft_input})
if not draft:
return {"error": "Draft not found"}
Draft.update(draft, draft_input)
draft.updated_at = int(time.time())
session.commit()
return {"draft": draft}

View File

@ -133,21 +133,18 @@ def check_to_feature(session, approver_id, reaction) -> bool:
return False
def check_to_unfeature(session, rejecter_id, reaction) -> bool:
def check_to_unfeature(session, reaction) -> bool:
"""
Unfeature a shout if 20% of reactions are negative.
:param session: Database session.
:param rejecter_id: Rejecter author ID.
:param reaction: Reaction object.
:return: True if shout should be unfeatured, else False.
"""
if not reaction.reply_to and is_negative(reaction.kind):
total_reactions = (
session.query(Reaction)
.filter(
Reaction.shout == reaction.shout, Reaction.kind.in_(RATING_REACTIONS), Reaction.deleted_at.is_(None)
)
.filter(Reaction.shout == reaction.shout, Reaction.reply_to.is_(None), Reaction.kind.in_(RATING_REACTIONS))
.count()
)
@ -217,7 +214,7 @@ async def _create_reaction(session, shout_id: int, is_author: bool, author_id: i
# Handle rating
if r.kind in RATING_REACTIONS:
if check_to_unfeature(session, author_id, r):
if check_to_unfeature(session, r):
set_unfeatured(session, shout_id)
elif check_to_feature(session, author_id, r):
await set_featured(session, shout_id)
@ -354,7 +351,7 @@ async def update_reaction(_, info, reaction):
result = session.execute(reaction_query).unique().first()
if result:
r, author, shout, commented_stat, rating_stat = result
r, author, _shout, commented_stat, rating_stat = result
if not r or not author:
return {"error": "Invalid reaction ID or unauthorized"}
@ -406,15 +403,24 @@ async def delete_reaction(_, info, reaction_id: int):
if r.created_by != author_id and "editor" not in roles:
return {"error": "Access denied"}
logger.debug(f"{user_id} user removing his #{reaction_id} reaction")
reaction_dict = r.dict()
session.delete(r)
session.commit()
# Update author stat
if r.kind == ReactionKind.COMMENT.value:
r.deleted_at = int(time.time())
update_author_stat(author.id)
session.add(r)
session.commit()
elif r.kind == ReactionKind.PROPOSE.value:
r.deleted_at = int(time.time())
session.add(r)
session.commit()
# TODO: add more reaction types here
else:
logger.debug(f"{user_id} user removing his #{reaction_id} reaction")
session.delete(r)
session.commit()
if check_to_unfeature(session, r):
set_unfeatured(session, r.shout)
reaction_dict = r.dict()
await notify_reaction(reaction_dict, "delete")
return {"error": None, "reaction": reaction_dict}