diff --git a/resolvers/profile.py b/resolvers/profile.py index 4c9ba04a..cacf64a5 100644 --- a/resolvers/profile.py +++ b/resolvers/profile.py @@ -12,6 +12,7 @@ from orm.user import AuthorFollower, Role, User, UserRating, UserRole from services.auth.users import UserStorage from services.stat.reacted import ReactedStorage from services.stat.topicstat import TopicStat +from services.zine.authors import AuthorsStorage from services.zine.shoutauthor import ShoutAuthorStorage # from .community import followed_communities @@ -174,12 +175,9 @@ def author_unfollow(user, slug): @query.field("authorsAll") async def get_authors_all(_, _info): - users = await UserStorage.get_all_users() - authors = [] - for author in users: - if ShoutAuthorStorage.shouts_by_author.get(author.slug): - author.stat = await get_author_stat(author.slug) - authors.append(author) + authors = await AuthorsStorage.get_all_authors() + for author in authors: + author.stat = await get_author_stat(author.slug) return authors @@ -212,5 +210,5 @@ async def load_authors_by(_, info, by, limit, offset): for a in authors: a.stat = await get_author_stat(a.slug) authors = list(set(authors)) - authors = sorted(authors, key=lambda a: a["stat"].get(by.get("stat"))) + # authors = sorted(authors, key=lambda a: a["stat"].get(by.get("stat"))) return authors diff --git a/resolvers/reactions.py b/resolvers/reactions.py index 2ca7ebd2..e410b2f1 100644 --- a/resolvers/reactions.py +++ b/resolvers/reactions.py @@ -217,13 +217,10 @@ async def load_reactions_by(_, info, by, limit=50, offset=0): :return: Reaction[] """ - q = select(Reaction).options( - selectinload(Reaction.shout), + q = select(Reaction).join( + Shout ).where( Reaction.deletedAt.is_(None) - ).join( - Shout, - Shout.slug == Reaction.shout ) if by.get("slug"): q = q.filter(Shout.slug == by["slug"]) @@ -243,8 +240,9 @@ async def load_reactions_by(_, info, by, limit=50, offset=0): if by.get("days"): before = datetime.now() - timedelta(days=int(by["days"]) or 30) q = q.filter(Reaction.createdAt > before) - q = q.group_by(Shout.id).order_by( - desc(by.get("order") or "createdAt") + + q = q.group_by(Reaction.id).order_by( + desc(by.get("order") or Reaction.createdAt) ).limit(limit).offset(offset) rrr = [] diff --git a/resolvers/zine.py b/resolvers/zine.py index 87a976ef..9c7efab8 100644 --- a/resolvers/zine.py +++ b/resolvers/zine.py @@ -6,8 +6,8 @@ from timeit import default_timer as timer from auth.authenticate import login_required from base.orm import local_session from base.resolvers import mutation, query -from orm.shout import Shout -from orm.reaction import Reaction, ReactionsWeights, ReactionKind +from orm.shout import Shout, ShoutAuthor +from orm.reaction import Reaction, ReactionKind # from resolvers.community import community_follow, community_unfollow from resolvers.profile import author_follow, author_unfollow from resolvers.reactions import reactions_follow, reactions_unfollow @@ -16,22 +16,41 @@ from services.zine.shoutauthor import ShoutAuthorStorage from services.stat.reacted import ReactedStorage -@query.field("loadShoutsBy") -async def load_shouts_by(_, info, filter_by, limit, offset, order_by="createdAt", order_by_desc=True): +@query.field("loadShout") +async def load_shout(_, info, slug): + with local_session() as session: + shout = session.query(Shout).options( + # TODO add cation + selectinload(Shout.authors), + selectinload(Shout.topics), + ).filter( + Shout.slug == slug + ).filter( + Shout.deletedAt.is_(None) + ).one() + + return shout + + +@query.field("loadShouts") +async def load_shouts_by(_, info, options): """ - :param filterBy: { - layout: 'audio', - visibility: "public", - author: 'discours', - topic: 'culture', - title: 'something', - body: 'something else', - days: 30 + :param options: { + filters: { + layout: 'audio', + visibility: "public", + author: 'discours', + topic: 'culture', + title: 'something', + body: 'something else', + days: 30 + } + offset: 0 + limit: 50 + order_by: 'createdAt' + order_by_desc: tr + } - :param order_by: 'rating' | 'comments' | 'reacted' | 'views' | 'createdAt - :param order_by_desc: order be desc/ask (desc by default) - :param limit: int amount of shouts - :param offset: int offset in this order :return: Shout[] """ @@ -43,68 +62,70 @@ async def load_shouts_by(_, info, filter_by, limit, offset, order_by="createdAt" Shout.deletedAt.is_(None) ) - if filter_by.get("slug"): - q = q.filter(Shout.slug == filter_by["slug"]) - else: - if filter_by.get("reacted"): + if options.get("filters"): + if options.get("filters").get("reacted"): user = info.context["request"].user q.join(Reaction, Reaction.createdBy == user.slug) - if filter_by.get("visibility"): + if options.get("filters").get("visibility"): q = q.filter(or_( - Shout.visibility.ilike(f"%{filter_by.get('visibility')}%"), + Shout.visibility.ilike(f"%{options.get('filters').get('visibility')}%"), Shout.visibility.ilike(f"%{'public'}%"), )) - if filter_by.get("layout"): - q = q.filter(Shout.layout == filter_by["layout"]) - if filter_by.get("author"): - q = q.filter(Shout.authors.any(slug=filter_by["author"])) - if filter_by.get("topic"): - q = q.filter(Shout.topics.any(slug=filter_by["topic"])) - if filter_by.get("title"): - q = q.filter(Shout.title.ilike(f'%{filter_by["title"]}%')) - if filter_by.get("body"): - q = q.filter(Shout.body.ilike(f'%{filter_by["body"]}%')) - if filter_by.get("days"): - before = datetime.now() - timedelta(days=int(filter_by["days"]) or 30) + if options.get("filters").get("layout"): + q = q.filter(Shout.layout == options.get("filters").get("layout")) + if options.get("filters").get("author"): + q = q.filter(Shout.authors.any(slug=options.get("filters").get("author"))) + if options.get("filters").get("topic"): + q = q.filter(Shout.topics.any(slug=options.get("filters").get("topic"))) + if options.get("filters").get("title"): + q = q.filter(Shout.title.ilike(f'%{options.get("filters").get("title")}%')) + if options.get("filters").get("body"): + q = q.filter(Shout.body.ilike(f'%{options.get("filters").get("body")}%s')) + if options.get("filters").get("days"): + before = datetime.now() - timedelta(days=int(options.get("filter").get("days")) or 30) q = q.filter(Shout.createdAt > before) - if order_by == 'comments': - q = q.join(Reaction).add_columns(sa.func.count(Reaction.id).label(order_by)) - if order_by == 'reacted': - # TODO ? - q = q.join(Reaction).add_columns(sa.func.count(Reaction.id).label(order_by)) - if order_by == "rating": - q = q.join(Reaction).add_columns(sa.func.sum(case( - (Reaction.kind == ReactionKind.AGREE, 1), - (Reaction.kind == ReactionKind.DISAGREE, -1), - (Reaction.kind == ReactionKind.PROOF, 1), - (Reaction.kind == ReactionKind.DISPROOF, -1), - (Reaction.kind == ReactionKind.ACCEPT, 1), - (Reaction.kind == ReactionKind.REJECT, -1), - (Reaction.kind == ReactionKind.LIKE, 1), - (Reaction.kind == ReactionKind.DISLIKE, -1), - else_=0 - )).label(order_by)) - # if order_by == 'views': - # TODO dump ackee data to db periodically - query_order_by = desc(order_by) if order_by_desc else asc(order_by) + if options.get("order_by") == 'comments': + q = q.join(Reaction, Shout.slug == Reaction.shout and Reaction.body.is_not(None)).add_columns( + sa.func.count(Reaction.id).label(options.get("order_by"))) + if options.get("order_by") == 'reacted': + q = q.join(Reaction).add_columns(sa.func.max(Reaction.createdAt).label(options.get("order_by"))) + if options.get("order_by") == "rating": + q = q.join(Reaction).add_columns(sa.func.sum(case( + (Reaction.kind == ReactionKind.AGREE, 1), + (Reaction.kind == ReactionKind.DISAGREE, -1), + (Reaction.kind == ReactionKind.PROOF, 1), + (Reaction.kind == ReactionKind.DISPROOF, -1), + (Reaction.kind == ReactionKind.ACCEPT, 1), + (Reaction.kind == ReactionKind.REJECT, -1), + (Reaction.kind == ReactionKind.LIKE, 1), + (Reaction.kind == ReactionKind.DISLIKE, -1), + else_=0 + )).label(options.get("order_by"))) + # if order_by == 'views': + # TODO dump ackee data to db periodically - q = q.group_by(Shout.id).order_by(query_order_by).limit(limit).offset(offset) + order_by = options.get("order_by") if options.get("order_by") else 'createdAt' - with local_session() as session: - # post query stats and author's captions - # start = timer() - shouts = list(map(lambda r: r.Shout, session.execute(q))) - for s in shouts: - s.stat = await ReactedStorage.get_shout_stat(s.slug) - for a in s.authors: - a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug) + query_order_by = desc(order_by) if options.get('order_by_desc') else asc(order_by) - # end = timer() - # print(end - start) - # print(q) + q = q.group_by(Shout.id).order_by(query_order_by).limit(options.get("limit")).offset( + options.get("offset") if options.get("offset") else 0) - return shouts + with local_session() as session: + # post query stats and author's captions + # start = timer() + shouts = list(map(lambda r: r.Shout, session.execute(q))) + for s in shouts: + s.stat = await ReactedStorage.get_shout_stat(s.slug) + for a in s.authors: + a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug) + + # end = timer() + # print(end - start) + # print(q) + + return shouts @mutation.field("follow") diff --git a/schema.graphql b/schema.graphql index 4ab7da35..214547bf 100644 --- a/schema.graphql +++ b/schema.graphql @@ -212,17 +212,36 @@ input AuthorsBy { } input ShoutsFilterBy { - slug: String - title: String - body: String - topic: String - topics: [String] - author: String - authors: [String] - layout: String - visibility: String - days: Int - stat: String + slug: String + title: String + body: String + topic: String + topics: [String] + author: String + authors: [String] + layout: String + visibility: String + days: Int + stat: String +} + +input LoadShoutsFilters { + title: String + body: String + topic: String + author: String + layout: String + visibility: String + days: Int + reacted: Boolean +} + +input LoadShoutsOptions { + filters: LoadShoutsFilters + limit: Int! + offset: Int + order_by: String + order_by_desc: Boolean } input ReactionBy { @@ -251,13 +270,14 @@ type Query { # zine loadAuthorsBy(by: AuthorsBy, limit: Int, offset: Int): [Author]! - loadShoutsBy(filter_by: ShoutsFilterBy!, limit: Int!, offset: Int!, order_by: String, order_by_desc: Boolean): [Shout]! + loadShout(slug: String!): Shout + loadShouts(options: LoadShoutsOptions): [Shout]! loadReactionsBy(by: ReactionBy!, limit: Int, offset: Int): [Reaction]! userFollowers(slug: String!): [Author]! userFollowedAuthors(slug: String!): [Author]! userFollowedTopics(slug: String!): [Topic]! authorsAll: [Author]! - getAuthor(slug: String!): User! + getAuthor(slug: String!): User # collab getCollabs: [Collab]! @@ -266,7 +286,7 @@ type Query { markdownBody(body: String!): String! # topics - getTopic(slug: String!): Topic! + getTopic(slug: String!): Topic topicsAll: [Topic]! topicsRandom(amount: Int): [Topic]! topicsByCommunity(community: String!): [Topic]! diff --git a/services/zine/authors.py b/services/zine/authors.py new file mode 100644 index 00000000..b91e788e --- /dev/null +++ b/services/zine/authors.py @@ -0,0 +1,12 @@ +from base.orm import local_session +from orm.user import User +from orm.shout import ShoutAuthor + + +class AuthorsStorage: + @staticmethod + async def get_all_authors(): + with local_session() as session: + query = session.query(User).join(ShoutAuthor) + result = query.all() + return result diff --git a/services/zine/shoutauthor.py b/services/zine/shoutauthor.py index b56e3ed0..ca224959 100644 --- a/services/zine/shoutauthor.py +++ b/services/zine/shoutauthor.py @@ -6,7 +6,6 @@ from orm.shout import ShoutAuthor, Shout class ShoutAuthorStorage: authors_by_shout = {} - shouts_by_author = {} lock = asyncio.Lock() period = 30 * 60 # sec @@ -17,10 +16,7 @@ class ShoutAuthorStorage: for sa in sas: self.authors_by_shout[sa.shout] = self.authors_by_shout.get(sa.shout, []) self.authors_by_shout[sa.shout].append([sa.user, sa.caption]) - self.shouts_by_author[sa.user] = self.shouts_by_author.get(sa.user, []) - self.shouts_by_author[sa.user].append(sa.shout) print("[zine.authors] %d shouts indexed by authors" % len(self.authors_by_shout)) - print("[zine.authors] %d authors indexed by shouts" % len(self.shouts_by_author)) @staticmethod async def get_authors(shout):