diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b2a32b..0231fe5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,13 @@ - `docs` added - optimized and unified `load_shouts_*` resolvers with `LoadShoutsOptions` - `load_shouts_bookmarked` resolver fixed -- refactored with `resolvers/feed` +- resolvers updates: + - new resolvers group `feed` + - `load_shouts_authored_by` resolver added + - `load_shouts_with_topic` resolver added + - `load_shouts_followed` removed + - `load_shouts_random_topic` removed + - `get_topics_random` removed - model updates: - `ShoutsOrderBy` enum added - `Shout.main_topic` from `ShoutTopic.main` as `Topic` type output diff --git a/resolvers/__init__.py b/resolvers/__init__.py index 2d62f076..003a627c 100644 --- a/resolvers/__init__.py +++ b/resolvers/__init__.py @@ -12,6 +12,12 @@ from resolvers.author import ( # search_authors, ) from resolvers.community import get_communities_all, get_community from resolvers.editor import create_shout, delete_shout, update_shout +from resolvers.feed import ( + load_shouts_coauthored, + load_shouts_discussed, + load_shouts_feed, + load_shouts_followed_by, +) from resolvers.follower import follow, get_shout_followers, unfollow from resolvers.notifier import ( load_notifications, @@ -33,17 +39,9 @@ from resolvers.reader import ( get_shout, load_shouts_by, load_shouts_random_top, - load_shouts_random_topic, load_shouts_search, load_shouts_unrated, ) -from resolvers.feed import ( - load_shouts_coauthored, - load_shouts_discussed, - load_shouts_feed, - load_shouts_followed, - load_shouts_followed_by, -) from resolvers.topic import ( get_topic, get_topic_authors, @@ -81,15 +79,16 @@ __all__ = [ # reader "get_shout", "load_shouts_by", + "load_shouts_random_top", + # feed "load_shouts_feed", "load_shouts_search", - "load_shouts_followed", "load_shouts_followed_by", "load_shouts_unrated", "load_shouts_coauthored", "load_shouts_discussed", - "load_shouts_random_top", - "load_shouts_random_topic", + "load_shouts_with_topic", + "load_shouts_authored_by", # follower "follow", "unfollow", diff --git a/resolvers/bookmark.py b/resolvers/bookmark.py index c50c36f6..5d9ee692 100644 --- a/resolvers/bookmark.py +++ b/resolvers/bookmark.py @@ -1,4 +1,5 @@ from operator import and_ + from graphql import GraphQLError from sqlalchemy import delete, insert, select @@ -30,14 +31,18 @@ def load_shouts_bookmarked(_, info, options): if not author_id: raise GraphQLError("User not authenticated") - q = query_with_stat() if has_field(info, "stat") else select(Shout).filter(and_(Shout.published_at.is_not(None), Shout.deleted_at.is_(None))) + q = ( + query_with_stat() + if has_field(info, "stat") + else select(Shout).filter(and_(Shout.published_at.is_not(None), Shout.deleted_at.is_(None))) + ) q = q.join(AuthorBookmark) q = q.filter( - and_( - Shout.id == AuthorBookmark.shout, - AuthorBookmark.author == author_id, - ) + and_( + Shout.id == AuthorBookmark.shout, + AuthorBookmark.author == author_id, ) + ) q, limit, offset = apply_options(q, options, author_id) return get_shouts_with_links(info, q, limit, offset) diff --git a/resolvers/feed.py b/resolvers/feed.py index 0e43667a..3223cf77 100644 --- a/resolvers/feed.py +++ b/resolvers/feed.py @@ -6,7 +6,7 @@ from sqlalchemy.orm import joinedload from orm.author import Author, AuthorFollower from orm.reaction import Reaction from orm.shout import Shout, ShoutAuthor, ShoutReactionsFollower, ShoutTopic -from orm.topic import TopicFollower +from orm.topic import Topic, TopicFollower from resolvers.reader import apply_filters, apply_sorting, get_shouts_with_links, has_field, query_with_stat from services.auth import login_required from services.db import local_session @@ -70,29 +70,6 @@ def filter_followed(info, q): return q, reader_id -@query.field("load_shouts_feed") -@login_required -async def load_shouts_feed(_, info, options): - """ - Загрузка ленты публикаций для авторизованного пользователя. - - :param info: Информация о контексте GraphQL. - :param options: Опции фильтрации и сортировки. - :return: Список публикаций для ленты. - """ - author_id = info.context.get("author_id") - if not author_id: - return [] - q = ( - query_with_stat() - if has_field(info, "stat") - else select(Shout).filter(and_(Shout.published_at.is_not(None), Shout.deleted_at.is_(None))) - ) - - q, limit, offset = apply_options(q, options, author_id) - return get_shouts_with_links(info, q, limit, offset) - - @query.field("load_shouts_coauthored") @login_required async def load_shouts_coauthored(_, info, options): @@ -200,9 +177,9 @@ async def reacted_shouts_updates(info, follower_id: int, options) -> List[Shout] return shouts -@query.field("load_shouts_followed") +@query.field("load_shouts_feed") @login_required -async def load_shouts_followed(_, info, options) -> List[Shout]: +async def load_shouts_feed(_, info, options) -> List[Shout]: """ Загружает публикации, на которые подписан пользователь. @@ -243,3 +220,41 @@ async def load_shouts_followed_by(_, info, slug: str, options) -> List[Shout]: except Exception as error: logger.debug(error) return [] + + +@query.field("load_shouts_authored_by") +async def load_shouts_authored_by(_, info, slug: str, options) -> List[Shout]: + """ + Загружает публикации, написанные автором по slug. + """ + with local_session() as session: + author = session.query(Author).filter(Author.slug == slug).first() + if author: + try: + author_id: int = author.dict()["id"] + q = query_with_stat() if has_field(info, "stat") else select(Shout) + q = q.filter(and_(Shout.published_at.is_not(None), Shout.deleted_at.is_(None))) + q = q.filter(Shout.authors.any(id=author_id)) + q, limit, offset = apply_options(q, options, author_id) + shouts = get_shouts_with_links(info, q, limit, offset=offset) + return shouts + except Exception as error: + logger.debug(error) + return [] + + +@query.field("load_shouts_with_topic") +async def load_shouts_with_topic(_, info, slug: str, options) -> List[Shout]: + """ + Загружает публикации, связанные с темой по slug. + """ + with local_session() as session: + topic = session.query(Topic).filter(Topic.slug == slug).first() + if topic: + try: + topic_id: int = topic.dict()["id"] + shouts = await reacted_shouts_updates(info, topic_id, options) + return shouts + except Exception as error: + logger.debug(error) + return [] diff --git a/resolvers/reader.py b/resolvers/reader.py index 4224ed51..6db2c270 100644 --- a/resolvers/reader.py +++ b/resolvers/reader.py @@ -7,7 +7,6 @@ from orm.author import Author from orm.reaction import Reaction, ReactionKind from orm.shout import Shout, ShoutAuthor, ShoutTopic from orm.topic import Topic -from resolvers.topic import get_topics_random from services.db import local_session from services.schema import query from services.search import search_text @@ -457,31 +456,3 @@ async def load_shouts_random_top(_, info, options): q = q.order_by(func.random()) limit = options.get("limit", 10) return get_shouts_with_links(info, q, limit) - - -@query.field("load_shouts_random_topic") -async def load_shouts_random_topic(_, info, options): - """ - Загрузка случайной темы и связанных с ней публикаций. - - :param info: Информация о контексте GraphQL. - :param options: Опции фильтрации и сортировки. - :return: Тема и связанные публикации. - """ - [topic] = get_topics_random(None, None, 1) - if topic: - q = ( - query_with_stat() - if has_field(info, "stat") - else select(Shout).filter(and_(Shout.published_at.is_not(None), Shout.deleted_at.is_(None))) - ) - q = q.filter(Shout.topics.any(slug=topic.slug)) - - q = apply_filters(q, options) - q = apply_sorting(q, options) - limit = options.get("limit", 10) - offset = options.get("offset", 0) - shouts = get_shouts_with_links(info, q, limit, offset) - if shouts: - return {"topic": topic, "shouts": shouts} - return {"error": "failed to get random topic"} diff --git a/resolvers/topic.py b/resolvers/topic.py index 2d0f66bd..8a3b9036 100644 --- a/resolvers/topic.py +++ b/resolvers/topic.py @@ -1,4 +1,4 @@ -from sqlalchemy import distinct, func, select +from sqlalchemy import select from cache.cache import ( get_cached_topic_authors, @@ -7,7 +7,6 @@ from cache.cache import ( ) from cache.memorycache import cache_region from orm.author import Author -from orm.shout import ShoutTopic from orm.topic import Topic from resolvers.stat import get_with_stat from services.auth import login_required @@ -116,23 +115,6 @@ async def delete_topic(_, info, slug: str): return {"error": "access denied"} -# Запрос на получение случайных тем -@query.field("get_topics_random") -def get_topics_random(_, _info, amount=12): - q = select(Topic) - q = q.join(ShoutTopic) - q = q.group_by(Topic.id) - q = q.having(func.count(distinct(ShoutTopic.shout)) > 2) - q = q.order_by(func.random()).limit(amount) - - topics = [] - with local_session() as session: - for [topic] in session.execute(q): - topics.append(topic) - - return topics - - # Запрос на получение подписчиков темы @query.field("get_topic_followers") async def get_topic_followers(_, _info, slug: str): diff --git a/schema/query.graphql b/schema/query.graphql index dbc0ac6b..ced67d31 100644 --- a/schema/query.graphql +++ b/schema/query.graphql @@ -19,8 +19,6 @@ type Query { get_author_follows(slug: String, user: String, author_id: Int): CommonResult! get_author_follows_topics(slug: String, user: String, author_id: Int): [Topic] get_author_follows_authors(slug: String, user: String, author_id: Int): [Author] - load_shouts_followed(limit: Int, offset: Int): [Shout] - load_shouts_followed_by(slug: String, limit: Int, offset: Int): [Shout] # reaction load_reactions_by(by: ReactionBy!, limit: Int, offset: Int): [Reaction] @@ -33,14 +31,18 @@ type Query { load_shouts_by(options: LoadShoutsOptions): [Shout] load_shouts_search(text: String!, options: LoadShoutsOptions): [SearchResult] load_shouts_bookmarked(options: LoadShoutsOptions): [Shout] - load_shouts_random_topic(options: LoadShoutsOptions): CommonResult! # { topic shouts } - # feed + # public feeds + load_shouts_with_topic(slug: String, options: LoadShoutsOptions): [Shout] # topic feed + load_shouts_random_top(options: LoadShoutsOptions): [Shout] # random order, fixed filter, limit offset can be used + load_shouts_authored_by(slug: String, options: LoadShoutsOptions): [Shout] # author feed + load_shouts_followed_by(slug: String, options: LoadShoutsOptions): [Shout] # another author feed + + # my feeds load_shouts_feed(options: LoadShoutsOptions): [Shout] load_shouts_unrated(options: LoadShoutsOptions): [Shout] load_shouts_coauthored(options: LoadShoutsOptions): [Shout] load_shouts_discussed(options: LoadShoutsOptions): [Shout] - load_shouts_random_top(options: LoadShoutsOptions): [Shout] # random order, fixed filter, limit offset can be used # editor get_my_shout(shout_id: Int!): CommonResult! @@ -49,7 +51,6 @@ type Query { # topic get_topic(slug: String!): Topic get_topics_all: [Topic] - get_topics_random(amount: Int): [Topic] get_topics_by_author(slug: String, user: String, author_id: Int): [Topic] get_topics_by_community(slug: String, community_id: Int): [Topic]