core/resolvers/reader.py
2023-11-30 14:04:55 +03:00

255 lines
8.4 KiB
Python

import time
from sqlalchemy.orm import joinedload, aliased
from sqlalchemy.sql.expression import desc, asc, select, func, case, and_, nulls_last
from services.auth import login_required
from services.db import local_session
from services.schema import query
from orm.author import AuthorFollower, Author
from orm.topic import TopicFollower, Topic
from orm.community import CommunityAuthor as CommunityFollower, Community
from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout, ShoutAuthor, ShoutTopic, ShoutVisibility
from services.search import SearchService
from services.viewed import ViewedStorage
def add_stat_columns(q):
aliased_reaction = aliased(Reaction)
q = q.outerjoin(aliased_reaction).add_columns(
func.sum(aliased_reaction.id).label("reacted_stat"),
func.sum(case((aliased_reaction.kind == ReactionKind.COMMENT.value, 1), else_=0)).label("commented_stat"),
func.sum(
case(
(aliased_reaction.kind == ReactionKind.AGREE.value, 1),
(aliased_reaction.kind == ReactionKind.DISAGREE.value, -1),
(aliased_reaction.kind == ReactionKind.PROOF.value, 1),
(aliased_reaction.kind == ReactionKind.DISPROOF.value, -1),
(aliased_reaction.kind == ReactionKind.ACCEPT.value, 1),
(aliased_reaction.kind == ReactionKind.REJECT.value, -1),
(aliased_reaction.kind == ReactionKind.LIKE.value, 1),
(aliased_reaction.kind == ReactionKind.DISLIKE.value, -1),
else_=0,
)
).label("rating_stat"),
func.max(
case(
(aliased_reaction.kind != ReactionKind.COMMENT.value, 0),
else_=aliased_reaction.created_at,
)
).label("last_comment"),
)
return q
def apply_filters(q, filters, author_id=None):
# LoadShoutsFilters handling
if filters.get("reacted") and author_id:
q.join(Reaction, Reaction.created_by == author_id)
by_published = filters.get("published")
if by_published:
q = q.filter(Shout.visibility == ShoutVisibility.PUBLIC.value)
by_layouts = filters.get("layouts")
if by_layouts:
q = q.filter(Shout.layout.in_(by_layouts))
by_author = filters.get("author")
if by_author:
q = q.filter(Shout.authors.any(slug=by_author))
by_topic = filters.get("topic")
if by_topic:
q = q.filter(Shout.topics.any(slug=by_topic))
by_after = filters.get("after")
if by_after:
ts = int(by_after)
q = q.filter(Shout.created_at > ts)
return q
@query.field("get_shout")
async def get_shout(_, _info, slug=None, shout_id=None):
with local_session() as session:
q = select(Shout).options(
# joinedload(Shout.created_by),
joinedload(Shout.authors),
joinedload(Shout.topics),
)
q = add_stat_columns(q)
if slug is not None:
q = q.filter(Shout.slug == slug)
if shout_id is not None:
q = q.filter(Shout.id == shout_id)
q = q.filter(Shout.deleted_at.is_(None)).group_by(Shout.id)
try:
author_stats = session.execute(q).first()
if author_stats:
[
shout,
viewed_stat,
reacted_stat,
commented_stat,
rating_stat,
] = author_stats
shout.stat = {
"viewed": viewed_stat,
"reacted": reacted_stat,
"commented": commented_stat,
"rating": rating_stat,
}
for author_caption in session.query(ShoutAuthor).join(Shout).where(Shout.slug == slug):
for author in shout.authors:
if author.id == author_caption.author:
author.caption = author_caption.caption
return shout
except Exception as e:
import traceback
traceback.print_exc()
print(e)
return None
@query.field("load_shouts_by")
async def load_shouts_by(_, info, options):
"""
:param _:
:param info:GraphQLInfo
:param options: {
filters: {
layouts: ['audio', 'video', ..],
reacted: True,
published: True, // filter published-only
author: 'discours',
topic: 'culture',
after: 1234567 // unixtime
}
offset: 0
limit: 50
order_by: 'created_at' | 'commented' | 'reacted' | 'rating'
order_by_desc: true
}
:return: Shout[]
"""
q = (
select(Shout)
.options(
# joinedload(Shout.created_by, Author.id == Shout.created_by),
joinedload(Shout.authors),
joinedload(Shout.topics),
)
.where(Shout.deleted_at.is_(None))
)
q = add_stat_columns(q)
q = apply_filters(q, options.get("filters", {}))
order_by = options.get("order_by", Shout.published_at)
query_order_by = desc(order_by) if options.get("order_by_desc", True) else asc(order_by)
offset = options.get("offset", 0)
limit = options.get("limit", 10)
q = q.group_by(Shout.id).order_by(nulls_last(query_order_by)).limit(limit).offset(offset)
shouts = []
shouts_map = {}
with local_session() as session:
for [
shout,
viewed_stat,
reacted_stat,
commented_stat,
rating_stat,
] in session.execute(q).unique():
shouts.append(shout)
shout.stat = {
"viewed": viewed_stat,
"reacted": reacted_stat,
"commented": commented_stat,
"rating": rating_stat,
}
shouts_map[shout.id] = shout
return shouts
@login_required
@query.field("load_shouts_feed")
async def load_shouts_feed(_, info, options):
user_id = info.context["user_id"]
with local_session() as session:
author = session.query(Author).filter(Author.user == user_id).first()
if author:
author_followed_authors = select(AuthorFollower.author).where(AuthorFollower.follower == author.id)
author_followed_topics = select(TopicFollower.topic).where(TopicFollower.follower == author.id)
subquery = (
select(Shout.id)
.where(Shout.id == ShoutAuthor.shout)
.where(Shout.id == ShoutTopic.shout)
.where(
(ShoutAuthor.author.in_(author_followed_authors)) | (ShoutTopic.topic.in_(author_followed_topics))
)
)
q = (
select(Shout)
.options(
# joinedload(Shout.created_by, Author.id == Shout.created_by),
joinedload(Shout.authors),
joinedload(Shout.topics),
)
.where(
and_(
Shout.published_at.is_not(None),
Shout.deleted_at.is_(None),
Shout.id.in_(subquery),
)
)
)
q = add_stat_columns(q)
q = apply_filters(q, options.get("filters", {}), author.id)
order_by = options.get("order_by", Shout.published_at)
query_order_by = desc(order_by) if options.get("order_by_desc", True) else asc(order_by)
offset = options.get("offset", 0)
limit = options.get("limit", 10)
q = q.group_by(Shout.id).order_by(nulls_last(query_order_by)).limit(limit).offset(offset)
shouts = []
for [
shout,
reacted_stat,
commented_stat,
rating_stat,
_last_comment,
] in session.execute(q).unique():
shout.stat = {
"viewed": ViewedStorage.get_shout(shout.slug),
"reacted": reacted_stat,
"commented": commented_stat,
"rating": rating_stat,
}
shouts.append(shout)
return shouts
return []
@query.field("load_shouts_search")
async def load_shouts_search(_, _info, text, limit=50, offset=0):
if text and len(text) > 2:
return SearchService.search(text, limit, offset)
else:
return []