fmt
All checks were successful
Deploy on push / deploy (push) Successful in 3m45s

This commit is contained in:
Untone 2024-02-24 21:45:38 +03:00
parent 12137eccda
commit eaaace4d28
6 changed files with 263 additions and 215 deletions

View File

@ -7,9 +7,9 @@ from resolvers.author import (
get_author_id,
get_authors_all,
load_authors_by,
rate_author,
update_author,
)
from resolvers.rating import rate_author
from resolvers.community import get_communities_all, get_community
from resolvers.editor import create_shout, delete_shout, update_shout
from resolvers.follower import (

View File

@ -1,14 +1,19 @@
import json
import time
from sqlalchemy import and_, desc, select, or_, distinct, func
from sqlalchemy import desc, select, or_, distinct, func
from sqlalchemy.orm import aliased
from orm.author import Author, AuthorFollower, AuthorRating
from orm.author import Author, AuthorFollower
from orm.shout import ShoutAuthor, ShoutTopic
from orm.topic import Topic
from resolvers.follower import query_follows
from resolvers.stat import get_authors_with_stat, execute_with_ministat, author_follows_authors, author_follows_topics
from resolvers.stat import (
get_authors_with_stat,
execute_with_ministat,
author_follows_authors,
author_follows_topics,
)
from services.auth import login_required
from services.db import local_session
from services.rediscache import redis
@ -47,7 +52,7 @@ def get_author(_, _info, slug='', author_id=None):
if author_id:
q = select(Author).where(Author.id == author_id)
[author, ] = get_authors_with_stat(q, ratings=True)
[author] = get_authors_with_stat(q, ratings=True)
except Exception as exc:
logger.error(exc)
return author
@ -67,7 +72,7 @@ async def get_author_by_user_id(user_id: str, ratings=False):
logger.info(f'getting author id for {user_id}')
q = select(Author).filter(Author.user == user_id)
[author, ] = get_authors_with_stat(q, ratings)
[author] = get_authors_with_stat(q, ratings)
except Exception as exc:
logger.error(exc)
return author
@ -115,7 +120,11 @@ def load_authors_by(_, _info, by, limit, offset):
def get_author_follows(_, _info, slug='', user=None, author_id=None):
with local_session() as session:
if user or slug:
author_id_result = session.query(Author.id).filter(or_(Author.user == user, Author.slug == slug)).first()
author_id_result = (
session.query(Author.id)
.filter(or_(Author.user == user, Author.slug == slug))
.first()
)
author_id = author_id_result[0] if author_id_result else None
if author_id:
follows = query_follows(author_id)
@ -128,7 +137,11 @@ def get_author_follows(_, _info, slug='', user=None, author_id=None):
def get_author_follows_topics(_, _info, slug='', user=None, author_id=None):
with local_session() as session:
if user or slug:
author_id_result = session.query(Author.id).filter(or_(Author.user == user, Author.slug == slug)).first()
author_id_result = (
session.query(Author.id)
.filter(or_(Author.user == user, Author.slug == slug))
.first()
)
author_id = author_id_result[0] if author_id_result else None
if author_id:
follows = author_follows_authors(author_id)
@ -141,7 +154,11 @@ def get_author_follows_topics(_, _info, slug='', user=None, author_id=None):
def get_author_follows_authors(_, _info, slug='', user=None, author_id=None):
with local_session() as session:
if user or slug:
author_id_result = session.query(Author.id).filter(or_(Author.user == user, Author.slug == slug)).first()
author_id_result = (
session.query(Author.id)
.filter(or_(Author.user == user, Author.slug == slug))
.first()
)
author_id = author_id_result[0] if author_id_result else None
if author_id:
follows = author_follows_topics(author_id)
@ -150,42 +167,6 @@ def get_author_follows_authors(_, _info, slug='', user=None, author_id=None):
raise ValueError('Author not found')
@mutation.field('rate_author')
@login_required
def rate_author(_, info, rated_slug, value):
user_id = info.context['user_id']
with local_session() as session:
rated_author = session.query(Author).filter(Author.slug == rated_slug).first()
rater = session.query(Author).filter(Author.slug == user_id).first()
if rater and rated_author:
rating: AuthorRating = (
session.query(AuthorRating)
.filter(
and_(
AuthorRating.rater == rater.id,
AuthorRating.author == rated_author.id,
)
)
.first()
)
if rating:
rating.plus = value > 0
session.add(rating)
session.commit()
return {}
else:
try:
rating = AuthorRating(
rater=rater.id, author=rated_author.id, plus=value > 0
)
session.add(rating)
session.commit()
except Exception as err:
return {'error': err}
return {}
def create_author(user_id: str, slug: str, name: str = ''):
with local_session() as session:
new_author = Author(user=user_id, slug=slug, name=name)

137
resolvers/rating.py Normal file
View File

@ -0,0 +1,137 @@
from sqlalchemy import and_
from sqlalchemy.orm import aliased
from orm.author import AuthorRating, Author
from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout
from services.auth import login_required
from services.db import local_session
from services.schema import mutation
@mutation.field('rate_author')
@login_required
def rate_author(_, info, rated_slug, value):
user_id = info.context['user_id']
with local_session() as session:
rated_author = session.query(Author).filter(Author.slug == rated_slug).first()
rater = session.query(Author).filter(Author.slug == user_id).first()
if rater and rated_author:
rating: AuthorRating = (
session.query(AuthorRating)
.filter(
and_(
AuthorRating.rater == rater.id,
AuthorRating.author == rated_author.id,
)
)
.first()
)
if rating:
rating.plus = value > 0
session.add(rating)
session.commit()
return {}
else:
try:
rating = AuthorRating(
rater=rater.id, author=rated_author.id, plus=value > 0
)
session.add(rating)
session.commit()
except Exception as err:
return {'error': err}
return {}
def count_author_comments_rating(session, author_id) -> int:
replied_alias = aliased(Reaction)
replies_likes = (
session.query(replied_alias)
.join(Reaction, replied_alias.id == Reaction.reply_to)
.where(
and_(
replied_alias.created_by == author_id,
replied_alias.kind == ReactionKind.COMMENT.value,
)
)
.filter(replied_alias.kind == ReactionKind.LIKE.value)
.count()
) or 0
replies_dislikes = (
session.query(replied_alias)
.join(Reaction, replied_alias.id == Reaction.reply_to)
.where(
and_(
replied_alias.created_by == author_id,
replied_alias.kind == ReactionKind.COMMENT.value,
)
)
.filter(replied_alias.kind == ReactionKind.DISLIKE.value)
.count()
) or 0
return replies_likes - replies_dislikes
def count_author_shouts_rating(session, author_id) -> int:
shouts_likes = (
session.query(Reaction, Shout)
.join(Shout, Shout.id == Reaction.shout)
.filter(
and_(
Shout.authors.any(id=author_id),
Reaction.kind == ReactionKind.LIKE.value,
)
)
.count()
or 0
)
shouts_dislikes = (
session.query(Reaction, Shout)
.join(Shout, Shout.id == Reaction.shout)
.filter(
and_(
Shout.authors.any(id=author_id),
Reaction.kind == ReactionKind.DISLIKE.value,
)
)
.count()
or 0
)
return shouts_likes - shouts_dislikes
def load_author_ratings(author: Author):
with local_session() as session:
comments_count = (
session.query(Reaction)
.filter(
and_(
Reaction.created_by == author.id,
Reaction.kind == ReactionKind.COMMENT.value,
Reaction.deleted_at.is_(None),
)
)
.count()
)
likes_count = (
session.query(AuthorRating)
.filter(and_(AuthorRating.author == author.id, AuthorRating.plus.is_(True)))
.count()
)
dislikes_count = (
session.query(AuthorRating)
.filter(
and_(AuthorRating.author == author.id, AuthorRating.plus.is_not(True))
)
.count()
)
author.stat['rating'] = likes_count - dislikes_count
author.stat['rating_shouts'] = count_author_shouts_rating(session, author.id)
author.stat['rating_comments'] = count_author_comments_rating(
session, author.id
)
author.stat['commented'] = comments_count
return author

View File

@ -57,7 +57,7 @@ def check_to_feature(session, approver_id, reaction):
"""set shout to public if publicated approvers amount > 4"""
if not reaction.reply_to and is_positive(reaction.kind):
if is_featured_author(session, approver_id):
approvers = [approver_id, ]
approvers = [approver_id]
# now count how many approvers are voted already
reacted_readers = (
session.query(Reaction).where(Reaction.shout == reaction.shout).all()
@ -148,36 +148,10 @@ async def _create_reaction(session, shout, author, reaction):
return rdict
@mutation.field('create_reaction')
@login_required
async def create_reaction(_, info, reaction):
user_id = info.context['user_id']
shout_id = reaction.get('shout')
if not shout_id:
return {'error': 'Shout ID is required to create a reaction.'}
try:
with local_session() as session:
shout = session.query(Shout).filter(Shout.id == shout_id).first()
author = session.query(Author).filter(Author.user == user_id).first()
if shout and author:
reaction['created_by'] = author.id
def check_rating(reaction: dict, shout_id: int, session, author: Author):
kind = reaction.get('kind')
shout_id = shout.id
if not kind and isinstance(reaction.get('body'), str):
kind = ReactionKind.COMMENT.value
if not kind:
return {'error': 'cannot create reaction without a kind'}
if kind in RATING_REACTIONS:
opposite_kind = (
ReactionKind.DISLIKE.value
if is_positive(kind)
else ReactionKind.LIKE.value
ReactionKind.DISLIKE.value if is_positive(kind) else ReactionKind.LIKE.value
)
q = select(Reaction).filter(
@ -205,6 +179,38 @@ async def create_reaction(_, info, reaction):
return {'error': 'Remove opposite vote first'}
elif filter(lambda r: r.created_by == author.id, rating_reactions):
return {'error': "You can't rate your own thing"}
return
@mutation.field('create_reaction')
@login_required
async def create_reaction(_, info, reaction):
user_id = info.context['user_id']
shout_id = reaction.get('shout')
if not shout_id:
return {'error': 'Shout ID is required to create a reaction.'}
try:
with local_session() as session:
shout = session.query(Shout).filter(Shout.id == shout_id).first()
author = session.query(Author).filter(Author.user == user_id).first()
if shout and author:
reaction['created_by'] = author.id
kind = reaction.get('kind')
shout_id = shout.id
if not kind and isinstance(reaction.get('body'), str):
kind = ReactionKind.COMMENT.value
if not kind:
return {'error': 'cannot create reaction without a kind'}
if kind in RATING_REACTIONS:
result = check_rating(reaction, shout_id, session, author)
if result:
return result
rdict = await _create_reaction(session, shout, author, reaction)
return {'reaction': rdict}

View File

@ -1,11 +1,11 @@
from sqlalchemy import func, distinct, select, join, and_
from sqlalchemy import func, distinct, select, join
from sqlalchemy.orm import aliased
from orm.reaction import Reaction, ReactionKind
from orm.topic import TopicFollower, Topic
from resolvers.rating import load_author_ratings
from services.db import local_session
from orm.author import AuthorFollower, Author, AuthorRating
from orm.shout import ShoutTopic, ShoutAuthor, Shout
from orm.author import AuthorFollower, Author
from orm.shout import ShoutTopic, ShoutAuthor
from services.logger import root_logger as logger
@ -16,11 +16,22 @@ def add_topic_stat_columns(q):
q = (
q.outerjoin(aliased_shout_topic, aliased_shout_topic.topic == Topic.id)
.add_columns(func.count(distinct(aliased_shout_topic.shout)).label("shouts_stat"))
.outerjoin(aliased_shout_author, aliased_shout_topic.shout == aliased_shout_author.shout)
.add_columns(func.count(distinct(aliased_shout_author.author)).label("authors_stat"))
.add_columns(
func.count(distinct(aliased_shout_topic.shout)).label('shouts_stat')
)
.outerjoin(
aliased_shout_author,
aliased_shout_topic.shout == aliased_shout_author.shout,
)
.add_columns(
func.count(distinct(aliased_shout_author.author)).label('authors_stat')
)
.outerjoin(aliased_topic_follower)
.add_columns(func.count(distinct(aliased_topic_follower.follower)).label("followers_stat"))
.add_columns(
func.count(distinct(aliased_topic_follower.follower)).label(
'followers_stat'
)
)
)
q = q.group_by(Topic.id)
@ -35,11 +46,21 @@ def add_author_stat_columns(q):
q = (
q.outerjoin(aliased_shout_author, aliased_shout_author.author == Author.id)
.add_columns(func.count(distinct(aliased_shout_author.shout)).label("shouts_stat"))
.add_columns(
func.count(distinct(aliased_shout_author.shout)).label('shouts_stat')
)
.outerjoin(aliased_author_authors, aliased_author_authors.follower == Author.id)
.add_columns(func.count(distinct(aliased_shout_author.author)).label("authors_stat"))
.outerjoin(aliased_author_followers, aliased_author_followers.author == Author.id)
.add_columns(func.count(distinct(aliased_author_followers.follower)).label("followers_stat"))
.add_columns(
func.count(distinct(aliased_shout_author.author)).label('authors_stat')
)
.outerjoin(
aliased_author_followers, aliased_author_followers.author == Author.id
)
.add_columns(
func.count(distinct(aliased_author_followers.follower)).label(
'followers_stat'
)
)
)
q = q.group_by(Author.id)
@ -47,104 +68,6 @@ def add_author_stat_columns(q):
return q
def count_author_comments_rating(session, author_id) -> int:
replied_alias = aliased(Reaction)
replies_likes = (
session.query(replied_alias)
.join(Reaction, replied_alias.id == Reaction.reply_to)
.where(
and_(
replied_alias.created_by == author_id,
replied_alias.kind == ReactionKind.COMMENT.value,
)
)
.filter(replied_alias.kind == ReactionKind.LIKE.value)
.count()
) or 0
replies_dislikes = (
session.query(replied_alias)
.join(Reaction, replied_alias.id == Reaction.reply_to)
.where(
and_(
replied_alias.created_by == author_id,
replied_alias.kind == ReactionKind.COMMENT.value,
)
)
.filter(replied_alias.kind == ReactionKind.DISLIKE.value)
.count()
) or 0
return replies_likes - replies_dislikes
def count_author_shouts_rating(session, author_id) -> int:
shouts_likes = (
session.query(Reaction, Shout)
.join(Shout, Shout.id == Reaction.shout)
.filter(
and_(
Shout.authors.any(id=author_id),
Reaction.kind == ReactionKind.LIKE.value,
)
)
.count()
or 0
)
shouts_dislikes = (
session.query(Reaction, Shout)
.join(Shout, Shout.id == Reaction.shout)
.filter(
and_(
Shout.authors.any(id=author_id),
Reaction.kind == ReactionKind.DISLIKE.value,
)
)
.count()
or 0
)
return shouts_likes - shouts_dislikes
def load_author_ratings(author: Author):
with local_session() as session:
comments_count = (
session.query(Reaction)
.filter(
and_(
Reaction.created_by == author.id,
Reaction.kind == ReactionKind.COMMENT.value,
Reaction.deleted_at.is_(None),
)
)
.count()
)
likes_count = (
session.query(AuthorRating)
.filter(
and_(AuthorRating.author == author.id, AuthorRating.plus.is_(True))
)
.count()
)
dislikes_count = (
session.query(AuthorRating)
.filter(
and_(
AuthorRating.author == author.id, AuthorRating.plus.is_not(True)
)
)
.count()
)
author.stat['rating'] = likes_count - dislikes_count
author.stat['rating_shouts'] = count_author_shouts_rating(
session, author.id
)
author.stat['rating_comments'] = count_author_comments_rating(
session, author.id
)
author.stat['commented'] = comments_count
return author
def execute_with_ministat(q):
records = []
with local_session() as session:
@ -176,11 +99,11 @@ def get_topics_with_stat(q):
def author_follows_authors(author_id: int):
af = aliased(AuthorFollower, name="af")
af = aliased(AuthorFollower, name='af')
q = (
select(Author).select_from(
join(Author, af, Author.id == int(af.author))
).where(af.follower == author_id)
select(Author)
.select_from(join(Author, af, Author.id == int(af.author)))
.where(af.follower == author_id)
)
q = add_author_stat_columns(q)
return execute_with_ministat(q)
@ -188,9 +111,9 @@ def author_follows_authors(author_id: int):
def author_follows_topics(author_id: int):
q = (
select(Topic).select_from(
join(Topic, TopicFollower, Topic.id == TopicFollower.topic)
).where(TopicFollower.follower == author_id)
select(Topic)
.select_from(join(Topic, TopicFollower, Topic.id == TopicFollower.topic))
.where(TopicFollower.follower == author_id)
)
q = add_topic_stat_columns(q)
@ -207,5 +130,5 @@ def query_follows(author_id: int):
'communities': [{'id': 1, 'name': 'Дискурс', 'slug': 'discours'}],
}
except Exception as e:
logger.exception(f"An error occurred while executing query_follows: {e}")
raise Exception("An error occurred while executing query_follows") from e
logger.exception(f'An error occurred while executing query_follows: {e}')
raise Exception('An error occurred while executing query_follows') from e

View File

@ -28,16 +28,15 @@ async def update_author_cache(author: Author, ttl=25 * 60 * 60):
@event.listens_for(Shout, 'after_update')
def after_shouts_update(mapper, connection, shout: Shout):
# Создаем подзапрос для проверки наличия авторов в списке shout.authors
subquery = (
select(1)
.where(or_(
subquery = select(1).where(
or_(
Author.id == shout.created_by,
and_(
Shout.id == shout.id,
ShoutAuthor.shout == Shout.id,
ShoutAuthor.author == Author.id
ShoutAuthor.author == Author.id,
),
)
))
)
# Основной запрос с использованием объединения и подзапроса exists
@ -45,10 +44,7 @@ def after_shouts_update(mapper, connection, shout: Shout):
select(Author)
.join(ShoutAuthor, Author.id == ShoutAuthor.author)
.where(ShoutAuthor.shout == shout.id)
.union(
select(Author)
.where(exists(subquery))
)
.union(select(Author).where(exists(subquery)))
)
authors = get_authors_with_stat(authors_query, ratings=True)
for author in authors:
@ -57,10 +53,7 @@ def after_shouts_update(mapper, connection, shout: Shout):
@event.listens_for(Reaction, 'after_insert')
def after_reaction_insert(mapper, connection, reaction: Reaction):
author_subquery = (
select(Author)
.where(Author.id == reaction.created_by)
)
author_subquery = select(Author).where(Author.id == reaction.created_by)
replied_author_subquery = (
select(Author)
.join(Reaction, Author.id == Reaction.created_by)
@ -112,7 +105,9 @@ def after_author_follower_delete(mapper, connection, target: AuthorFollower):
)
async def update_follows_for_user(connection, user_id, entity_type, entity: dict, is_insert):
async def update_follows_for_user(
connection, user_id, entity_type, entity: dict, is_insert
):
redis_key = f'user:{user_id}:follows'
follows_str = await redis.get(redis_key)
if follows_str:
@ -123,13 +118,17 @@ async def update_follows_for_user(connection, user_id, entity_type, entity: dict
follows[f'{entity_type}s'].append(entity)
else:
# Remove the entity from follows
follows[f'{entity_type}s'] = [e for e in follows[f'{entity_type}s'] if e['id'] != entity['id']]
follows[f'{entity_type}s'] = [
e for e in follows[f'{entity_type}s'] if e['id'] != entity['id']
]
await redis.execute('SET', redis_key, json.dumps(follows))
async def handle_author_follower_change(connection, author_id: int, follower_id: int, is_insert: bool):
async def handle_author_follower_change(
connection, author_id: int, follower_id: int, is_insert: bool
):
author_query = select(Author).filter(Author.id == author_id)
[author, ] = get_authors_with_stat(author_query, ratings=True)
[author] = get_authors_with_stat(author_query, ratings=True)
follower_query = select(Author).filter(Author.id == follower_id)
follower = get_authors_with_stat(follower_query, ratings=True)
if follower and author:
@ -151,7 +150,9 @@ async def handle_author_follower_change(connection, author_id: int, follower_id:
)
async def handle_topic_follower_change(connection, topic_id: int, follower_id: int, is_insert: bool):
async def handle_topic_follower_change(
connection, topic_id: int, follower_id: int, is_insert: bool
):
q = select(Topic).filter(Topic.id == topic_id)
topics = get_topics_with_stat(q)
topic = topics[0]