This commit is contained in:
2022-11-30 23:20:08 +03:00
35 changed files with 617 additions and 782 deletions

View File

@@ -15,7 +15,8 @@ from resolvers.create.editor import create_shout, delete_shout, update_shout
from resolvers.zine.profile import (
load_authors_by,
rate_user,
update_profile
update_profile,
get_authors_all
)
from resolvers.zine.reactions import (

View File

@@ -1,13 +1,13 @@
from datetime import datetime, timezone
from sqlalchemy import and_
from auth.authenticate import login_required
from base.orm import local_session
from base.resolvers import mutation
from orm.rbac import Resource
from orm.shout import Shout, ShoutAuthor, ShoutTopic
from orm.collab import Collab
from services.inbox import MessagesStorage
from orm.topic import TopicFollower
from orm.topic import TopicFollower, Topic
from orm.user import User
from resolvers.zine.reactions import reactions_follow, reactions_unfollow
from services.zine.gittask import GitTask
@@ -49,7 +49,7 @@ async def create_shout(_, info, inp):
session.add(new_collab)
# NOTE: shout made by one first author
sa = ShoutAuthor.create(shout=new_shout.slug, user=user.slug)
sa = ShoutAuthor.create(shout=new_shout.id, user=user.id)
session.add(sa)
reactions_follow(user, new_shout.slug, True)
@@ -58,11 +58,16 @@ async def create_shout(_, info, inp):
topic_slugs.append(inp["mainTopic"])
for slug in topic_slugs:
st = ShoutTopic.create(shout=new_shout.slug, topic=slug)
topic = session.query(Topic).where(Topic.slug == slug).one()
st = ShoutTopic.create(shout=new_shout.id, topic=topic.id)
session.add(st)
tf = session.query(TopicFollower).where(follower=user.slug, topic=slug)
tf = session.query(TopicFollower).where(
and_(TopicFollower.follower == user.id, TopicFollower.topic == topic.id)
)
if not tf:
tf = TopicFollower.create(follower=user.slug, topic=slug, auto=True)
tf = TopicFollower.create(follower=user.id, topic=topic.id, auto=True)
session.add(tf)
new_shout.topic_slugs = topic_slugs
@@ -70,7 +75,7 @@ async def create_shout(_, info, inp):
session.commit()
GitTask(inp, user.username, user.email, "new shout %s" % (new_shout.slug))
GitTask(inp, user.username, user.email, "new shout %s" % new_shout.slug)
return {"shout": new_shout}
@@ -92,7 +97,7 @@ async def update_shout(_, info, inp):
if user_id not in authors:
scopes = auth.scopes
print(scopes)
if Resource.shout_id not in scopes:
if Resource.shout not in scopes:
return {"error": "access denied"}
else:
shout.update(inp)
@@ -100,7 +105,7 @@ async def update_shout(_, info, inp):
session.add(shout)
if inp.get("topics"):
# remove old links
links = session.query(ShoutTopic).where(ShoutTopic.shout == slug).all()
links = session.query(ShoutTopic).where(ShoutTopic.shout == shout.id).all()
for topiclink in links:
session.delete(topiclink)
# add new topic links

View File

@@ -4,7 +4,7 @@ from auth.authenticate import login_required
from base.redis import redis
from base.resolvers import query
from base.orm import local_session
from orm.user import AuthorFollower
from orm.user import AuthorFollower, User
@query.field("searchRecipients")
@@ -30,13 +30,19 @@ async def search_recipients(_, info, query: str, limit: int = 50, offset: int =
with local_session() as session:
# followings
result += session.query(AuthorFollower.author).where(AuthorFollower.follower.startswith(query))\
.offset(offset + len(result)).limit(more_amount)
result += session.query(AuthorFollower.author).join(
User, User.id == AuthorFollower.follower
).where(
User.slug.startswith(query)
).offset(offset + len(result)).limit(more_amount)
more_amount = limit
# followers
result += session.query(AuthorFollower.follower).where(AuthorFollower.author.startswith(query))\
.offset(offset + len(result)).limit(offset + len(result) + limit)
result += session.query(AuthorFollower.follower).join(
User, User.id == AuthorFollower.author
).where(
User.slug.startswith(query)
).offset(offset + len(result)).limit(offset + len(result) + limit)
return {
"members": list(result),
"error": None

31
resolvers/zine/_common.py Normal file
View File

@@ -0,0 +1,31 @@
from sqlalchemy import func, case
from sqlalchemy.orm import aliased
from orm.reaction import Reaction, ReactionKind
def add_common_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.body.is_not(None), 1),
else_=0
)
).label('commented_stat'),
func.sum(case(
(aliased_reaction.kind == ReactionKind.AGREE, 1),
(aliased_reaction.kind == ReactionKind.DISAGREE, -1),
(aliased_reaction.kind == ReactionKind.PROOF, 1),
(aliased_reaction.kind == ReactionKind.DISPROOF, -1),
(aliased_reaction.kind == ReactionKind.ACCEPT, 1),
(aliased_reaction.kind == ReactionKind.REJECT, -1),
(aliased_reaction.kind == ReactionKind.LIKE, 1),
(aliased_reaction.kind == ReactionKind.DISLIKE, -1),
else_=0)
).label('rating_stat'))
return q

View File

@@ -1,51 +1,31 @@
from datetime import datetime, timedelta, timezone
import sqlalchemy as sa
from sqlalchemy.orm import joinedload, aliased
from sqlalchemy.sql.expression import desc, asc, select, case
from sqlalchemy.sql.expression import desc, asc, select, func
from base.orm import local_session
from base.resolvers import query
from orm import ViewedEntry
from orm.shout import Shout
from orm.reaction import Reaction, ReactionKind
from services.zine.shoutauthor import ShoutAuthorStorage
from services.stat.viewed import ViewedStorage
from orm.shout import Shout, ShoutAuthor
from orm.reaction import Reaction
from resolvers.zine._common import add_common_stat_columns
def calc_reactions(q):
aliased_reaction = aliased(Reaction)
return q.join(aliased_reaction).add_columns(
sa.func.sum(case(
(aliased_reaction.kind == ReactionKind.AGREE, 1),
(aliased_reaction.kind == ReactionKind.DISAGREE, -1),
(aliased_reaction.kind == ReactionKind.PROOF, 1),
(aliased_reaction.kind == ReactionKind.DISPROOF, -1),
(aliased_reaction.kind == ReactionKind.ACCEPT, 1),
(aliased_reaction.kind == ReactionKind.REJECT, -1),
(aliased_reaction.kind == ReactionKind.LIKE, 1),
(aliased_reaction.kind == ReactionKind.DISLIKE, -1),
else_=0)
).label('rating'),
sa.func.sum(
case(
(aliased_reaction.body.is_not(None), 1),
else_=0
)
).label('commented'),
sa.func.sum(
aliased_reaction.id
).label('reacted')
)
def add_stat_columns(q):
q = q.outerjoin(ViewedEntry).add_columns(func.sum(ViewedEntry.amount).label('viewed_stat'))
return add_common_stat_columns(q)
def apply_filters(q, filters, user=None):
filters = {} if filters is None else filters
if filters.get("reacted") and user:
q.join(Reaction, Reaction.createdBy == user.slug)
q.join(Reaction, Reaction.createdBy == user.id)
v = filters.get("visibility")
if v == "public":
q = q.filter(Shout.visibility == filters.get("visibility"))
if v == "community":
q = q.filter(Shout.visibility.in_(["public", "community"]))
if filters.get("layout"):
q = q.filter(Shout.layout == filters.get("layout"))
if filters.get("author"):
@@ -59,6 +39,7 @@ def apply_filters(q, filters, user=None):
if filters.get("days"):
before = datetime.now(tz=timezone.utc) - timedelta(days=int(filters.get("days")) or 30)
q = q.filter(Shout.createdAt > before)
return q
@@ -69,24 +50,27 @@ async def load_shout(_, info, slug):
joinedload(Shout.authors),
joinedload(Shout.topics),
)
q = calc_reactions(q)
q = add_stat_columns(q)
q = q.filter(
Shout.slug == slug
).filter(
Shout.deletedAt.is_(None)
).group_by(Shout.id)
[shout, rating, commented, reacted] = session.execute(q).unique().one()
for a in shout.authors:
a.caption = await ShoutAuthorStorage.get_author_caption(shout.slug, a.slug)
viewed = await ViewedStorage.get_shout(shout.slug)
[shout, viewed_stat, reacted_stat, commented_stat, rating_stat] = session.execute(q).unique().one()
shout.stat = {
"rating": rating,
"viewed": viewed,
"commented": commented,
"reacted": reacted
"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.user:
author.caption = author_caption.caption
return shout
@@ -105,7 +89,7 @@ async def load_shouts_by(_, info, options):
}
offset: 0
limit: 50
order_by: 'createdAt'
order_by: 'createdAt' | 'commented' | 'reacted' | 'rating'
order_by_desc: true
}
@@ -118,47 +102,36 @@ async def load_shouts_by(_, info, options):
).where(
Shout.deletedAt.is_(None)
)
q = add_stat_columns(q)
user = info.context["request"].user
q = apply_filters(q, options.get("filters"), user)
q = calc_reactions(q)
q = apply_filters(q, options.get("filters", {}), user)
o = options.get("order_by")
if o:
if o == 'comments':
q = q.add_columns(sa.func.count(Reaction.id).label(o))
q = q.join(Reaction, Shout.slug == Reaction.shout)
q = q.filter(Reaction.body.is_not(None))
elif o == 'reacted':
q = q.join(
Reaction
).add_columns(
sa.func.max(Reaction.createdAt).label(o)
)
elif o == 'views':
q = q.join(ViewedEntry)
q = q.add_columns(sa.func.sum(ViewedEntry.amount).label(o))
order_by = o
else:
order_by = Shout.createdAt
order_by = options.get("order_by", Shout.createdAt)
if order_by == 'reacted':
aliased_reaction = aliased(Reaction)
q.outerjoin(aliased_reaction).add_columns(func.max(aliased_reaction.createdAt).label('reacted'))
order_by_desc = True if options.get('order_by_desc') is None else options.get('order_by_desc')
order_by_desc = options.get('order_by_desc', True)
query_order_by = desc(order_by) if order_by_desc else asc(order_by)
offset = options.get("offset", 0)
limit = options.get("limit", 10)
q = q.group_by(Shout.id).order_by(query_order_by).limit(limit).offset(offset)
shouts = []
with local_session() as session:
for [shout, rating, commented, reacted] in session.execute(q).unique():
shout.stat = {
"rating": rating,
"viewed": await ViewedStorage.get_shout(shout.slug),
"commented": commented,
"reacted": reacted
}
# NOTE: no need authors captions in arrays
# for author in shout.authors:
# author.caption = await ShoutAuthorStorage.get_author_caption(shout.slug, author.slug)
shouts = []
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
}
return shouts

View File

@@ -1,25 +1,82 @@
from typing import List
from datetime import datetime, timedelta, timezone
from sqlalchemy import and_, func
from sqlalchemy.orm import selectinload
from sqlalchemy import and_, func, distinct, select, literal
from sqlalchemy.orm import aliased, joinedload
from auth.authenticate import login_required
from base.orm import local_session
from base.resolvers import mutation, query
from orm.reaction import Reaction
from orm.shout import ShoutAuthor
from orm.topic import Topic, TopicFollower
from orm.shout import ShoutAuthor, ShoutTopic
from orm.topic import Topic
from orm.user import AuthorFollower, Role, User, UserRating, UserRole
from services.stat.topicstat import TopicStat
# from .community import followed_communities
from resolvers.inbox.unread import get_total_unread_counter
from .topics import get_topic_stat
from resolvers.zine.topics import followed_by_user
def add_author_stat_columns(q):
author_followers = aliased(AuthorFollower)
author_following = aliased(AuthorFollower)
shout_author_aliased = aliased(ShoutAuthor)
user_rating_aliased = aliased(UserRating)
q = q.outerjoin(shout_author_aliased).add_columns(
func.count(distinct(shout_author_aliased.shout)).label('shouts_stat')
)
q = q.outerjoin(author_followers, author_followers.author == User.id).add_columns(
func.count(distinct(author_followers.follower)).label('followers_stat')
)
q = q.outerjoin(author_following, author_following.follower == User.id).add_columns(
func.count(distinct(author_following.author)).label('followings_stat')
)
q = q.add_columns(literal(0).label('rating_stat'))
# FIXME
# q = q.outerjoin(user_rating_aliased, user_rating_aliased.user == User.id).add_columns(
# # TODO: check
# func.sum(user_rating_aliased.value).label('rating_stat')
# )
q = q.add_columns(literal(0).label('commented_stat'))
# FIXME
# q = q.outerjoin(Reaction, and_(Reaction.createdBy == User.id, Reaction.body.is_not(None))).add_columns(
# func.count(distinct(Reaction.id)).label('commented_stat')
# )
q = q.group_by(User.id)
return q
def add_stat(author, stat_columns):
[shouts_stat, followers_stat, followings_stat, rating_stat, commented_stat] = stat_columns
author.stat = {
"shouts": shouts_stat,
"followers": followers_stat,
"followings": followings_stat,
"rating": rating_stat,
"commented": commented_stat
}
return author
def get_authors_from_query(q):
authors = []
with local_session() as session:
for [author, *stat_columns] in session.execute(q):
author = add_stat(author, stat_columns)
authors.append(author)
return authors
async def user_subscriptions(slug: str):
return {
"unread": await get_total_unread_counter(slug), # unread inbox messages counter
"unread": await get_total_unread_counter(slug), # unread inbox messages counter
"topics": [t.slug for t in await followed_topics(slug)], # followed topics slugs
"authors": [a.slug for a in await followed_authors(slug)], # followed authors slugs
"reactions": await followed_reactions(slug)
@@ -27,23 +84,6 @@ async def user_subscriptions(slug: str):
}
async def get_author_stat(slug):
with local_session() as session:
return {
"shouts": session.query(ShoutAuthor).where(ShoutAuthor.user == slug).count(),
"followers": session.query(AuthorFollower).where(AuthorFollower.author == slug).count(),
"followings": session.query(AuthorFollower).where(AuthorFollower.follower == slug).count(),
"rating": session.query(func.sum(UserRating.value)).where(UserRating.user == slug).first(),
"commented": session.query(
Reaction.id
).where(
Reaction.createdBy == slug
).filter(
Reaction.body.is_not(None)
).count()
}
# @query.field("userFollowedDiscussions")
@login_required
async def followed_discussions(_, info, slug) -> List[Topic]:
@@ -56,7 +96,7 @@ async def followed_reactions(slug):
return session.query(
Reaction.shout
).where(
Reaction.createdBy == slug
Reaction.createdBy == user.id
).filter(
Reaction.createdAt > user.lastSeen
).all()
@@ -69,17 +109,7 @@ async def get_followed_topics(_, info, slug) -> List[Topic]:
async def followed_topics(slug):
topics = []
with local_session() as session:
topics = (
session.query(Topic)
.join(TopicFollower)
.where(TopicFollower.follower == slug)
.all()
)
for topic in topics:
topic.stat = await get_topic_stat(topic.slug)
return topics
return followed_by_user(slug)
@query.field("userFollowedAuthors")
@@ -88,29 +118,26 @@ async def get_followed_authors(_, _info, slug) -> List[User]:
async def followed_authors(slug) -> List[User]:
authors = []
with local_session() as session:
authors = (
session.query(User)
.join(AuthorFollower, User.slug == AuthorFollower.author)
.where(AuthorFollower.follower == slug)
.all()
)
for author in authors:
author.stat = await get_author_stat(author.slug)
return authors
q = select(User)
q = add_author_stat_columns(q)
q = q.join(AuthorFollower).join(User, User.id == AuthorFollower.follower).where(User.slug == slug)
return get_authors_from_query(q)
@query.field("userFollowers")
async def user_followers(_, _info, slug) -> List[User]:
with local_session() as session:
users = (
session.query(User)
.join(AuthorFollower, User.slug == AuthorFollower.follower)
.where(AuthorFollower.author == slug)
.all()
)
return users
q = select(User)
q = add_author_stat_columns(q)
aliased_user = aliased(User)
q = q.join(AuthorFollower).join(
aliased_user, aliased_user.id == AuthorFollower.author
).where(
aliased_user.slug == slug
)
return get_authors_from_query(q)
async def get_user_roles(slug):
@@ -118,11 +145,12 @@ async def get_user_roles(slug):
user = session.query(User).where(User.slug == slug).first()
roles = (
session.query(Role)
.options(selectinload(Role.permissions))
.join(UserRole)
.where(UserRole.user_id == user.id)
.all()
.options(joinedload(Role.permissions))
.join(UserRole)
.where(UserRole.user == user.id)
.all()
)
return roles
@@ -147,8 +175,8 @@ async def rate_user(_, info, rated_userslug, value):
with local_session() as session:
rating = (
session.query(UserRating)
.filter(and_(UserRating.rater == user.slug, UserRating.user == rated_userslug))
.first()
.filter(and_(UserRating.rater == user.slug, UserRating.user == rated_userslug))
.first()
)
if rating:
rating.value = value
@@ -164,7 +192,8 @@ async def rate_user(_, info, rated_userslug, value):
# for mutation.field("follow")
def author_follow(user, slug):
with local_session() as session:
af = AuthorFollower.create(follower=user.slug, author=slug)
author = session.query(User).where(User.slug == slug).one()
af = AuthorFollower.create(follower=user.id, author=author.id)
session.add(af)
session.commit()
@@ -173,13 +202,13 @@ def author_follow(user, slug):
def author_unfollow(user, slug):
with local_session() as session:
flw = (
session.query(AuthorFollower)
.filter(
session.query(
AuthorFollower
).join(User, User.id == AuthorFollower.author).filter(
and_(
AuthorFollower.follower == user.slug, AuthorFollower.author == slug
AuthorFollower.follower == user.id, User.slug == slug
)
)
.first()
).first()
)
if not flw:
raise Exception("[resolvers.profile] follower not exist, cant unfollow")
@@ -190,49 +219,41 @@ def author_unfollow(user, slug):
@query.field("authorsAll")
async def get_authors_all(_, _info):
with local_session() as session:
authors = session.query(User).join(ShoutAuthor).all()
for author in authors:
author.stat = await get_author_stat(author.slug)
return authors
q = select(User)
q = add_author_stat_columns(q)
q = q.join(ShoutAuthor, User.id == ShoutAuthor.user)
return get_authors_from_query(q)
@query.field("getAuthor")
async def get_author(_, _info, slug):
with local_session() as session:
author = session.query(User).where(User.slug == slug).first()
author.stat = await get_author_stat(author.slug)
return author
q = select(User).where(User.slug == slug)
q = add_author_stat_columns(q)
authors = get_authors_from_query(q)
return authors[0]
@query.field("loadAuthorsBy")
async def load_authors_by(_, info, by, limit, offset):
authors = []
with local_session() as session:
aq = session.query(User)
if by.get("slug"):
aq = aq.filter(User.slug.ilike(f"%{by['slug']}%"))
elif by.get("name"):
aq = aq.filter(User.name.ilike(f"%{by['name']}%"))
elif by.get("topic"):
aaa = list(map(lambda a: a.slug, TopicStat.authors_by_topic.get(by["topic"])))
aq = aq.filter(User.name._in(aaa))
if by.get("lastSeen"): # in days
days_before = datetime.now(tz=timezone.utc) - timedelta(days=by["lastSeen"])
aq = aq.filter(User.lastSeen > days_before)
elif by.get("createdAt"): # in days
days_before = datetime.now(tz=timezone.utc) - timedelta(days=by["createdAt"])
aq = aq.filter(User.createdAt > days_before)
aq = aq.group_by(
User.id
).order_by(
by.get("order") or "createdAt"
).limit(limit).offset(offset)
print(aq)
authors = list(map(lambda r: r.User, session.execute(aq)))
if by.get("stat"):
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")))
return authors
q = select(User)
q = add_author_stat_columns(q)
if by.get("slug"):
q = q.filter(User.slug.ilike(f"%{by['slug']}%"))
elif by.get("name"):
q = q.filter(User.name.ilike(f"%{by['name']}%"))
elif by.get("topic"):
q = q.join(ShoutAuthor).join(ShoutTopic).join(Topic).where(Topic.slug == by["topic"])
if by.get("lastSeen"): # in days
days_before = datetime.now(tz=timezone.utc) - timedelta(days=by["lastSeen"])
q = q.filter(User.lastSeen > days_before)
elif by.get("createdAt"): # in days
days_before = datetime.now(tz=timezone.utc) - timedelta(days=by["createdAt"])
q = q.filter(User.createdAt > days_before)
q = q.order_by(
by.get("order", User.createdAt)
).limit(limit).offset(offset)
return get_authors_from_query(q)

View File

@@ -1,29 +1,34 @@
from datetime import datetime, timedelta, timezone
from sqlalchemy import and_, asc, desc, select, text, func
from sqlalchemy.orm import aliased
from auth.authenticate import login_required
from base.orm import local_session
from base.resolvers import mutation, query
from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout, ShoutReactionsFollower
from orm.user import User
# from services.stat.reacted import ReactedStorage
from resolvers.zine.load import calc_reactions
from resolvers.zine._common import add_common_stat_columns
def add_reaction_stat_columns(q):
return add_common_stat_columns(q)
def reactions_follow(user: User, slug: str, auto=False):
with local_session() as session:
shout = session.query(Shout).where(Shout.slug == slug).one()
following = (
session.query(ShoutReactionsFollower).where(and_(
ShoutReactionsFollower.follower == user.slug,
ShoutReactionsFollower.shout == slug
ShoutReactionsFollower.follower == user.id,
ShoutReactionsFollower.shout == shout.id,
)).first()
)
if not following:
following = ShoutReactionsFollower.create(
follower=user.slug,
shout=slug,
follower=user.id,
shout=shout.id,
auto=auto
)
session.add(following)
@@ -32,12 +37,15 @@ def reactions_follow(user: User, slug: str, auto=False):
def reactions_unfollow(user, slug):
with local_session() as session:
shout = session.query(Shout).where(Shout.slug == slug).one()
following = (
session.query(ShoutReactionsFollower).where(and_(
ShoutReactionsFollower.follower == user.slug,
ShoutReactionsFollower.shout == slug
ShoutReactionsFollower.follower == user.id,
ShoutReactionsFollower.shout == shout.id
)).first()
)
if following:
session.delete(following)
session.commit()
@@ -134,7 +142,6 @@ async def create_reaction(_, info, inp):
elif check_to_publish(session, user, reaction):
set_published(session, reaction.shout, reaction.createdBy)
# ReactedStorage.react(reaction)
try:
reactions_follow(user, inp["shout"], True)
except Exception as e:
@@ -157,9 +164,9 @@ async def update_reaction(_, info, inp):
with local_session() as session:
user = session.query(User).where(User.id == user_id).first()
q = select(Reaction).filter(Reaction.id == inp.id)
q = calc_reactions(q)
q = add_reaction_stat_columns(q)
[reaction, rating, commented, reacted] = session.execute(q).unique().one()
[reaction, reacted_stat, commented_stat, rating_stat] = session.execute(q).unique().one()
if not reaction:
return {"error": "invalid reaction id"}
@@ -175,9 +182,9 @@ async def update_reaction(_, info, inp):
reaction.range = inp.get("range")
session.commit()
reaction.stat = {
"commented": commented,
"reacted": reacted,
"rating": rating
"commented": commented_stat,
"reacted": reacted_stat,
"rating": rating_stat
}
return {"reaction": reaction}
@@ -199,6 +206,7 @@ async def delete_reaction(_, info, rid):
session.commit()
return {}
@query.field("loadReactionsBy")
async def load_reactions_by(_, _info, by, limit=50, offset=0):
"""
@@ -222,28 +230,33 @@ async def load_reactions_by(_, _info, by, limit=50, offset=0):
q = select(
Reaction, CreatedByUser, ReactedShout
).join(
CreatedByUser, Reaction.createdBy == CreatedByUser.slug
CreatedByUser, Reaction.createdBy == CreatedByUser.id
).join(
ReactedShout, Reaction.shout == ReactedShout.slug
ReactedShout, Reaction.shout == ReactedShout.id
)
if by.get("shout"):
q = q.filter(Reaction.shout == by["shout"])
aliased_shout = aliased(Shout)
q = q.join(aliased_shout).filter(aliased_shout.slug == by["shout"])
elif by.get("shouts"):
q = q.filter(Reaction.shout.in_(by["shouts"]))
aliased_shout = aliased(Shout)
q = q.join(aliased_shout).filter(aliased_shout.shout.in_(by["shouts"]))
if by.get("createdBy"):
q = q.filter(Reaction.createdBy == by.get("createdBy"))
aliased_user = aliased(User)
q = q.join(aliased_user).filter(aliased_user.slug == by.get("createdBy"))
if by.get("topic"):
# TODO: check
q = q.filter(Shout.topics.contains(by["topic"]))
if by.get("comment"):
q = q.filter(func.length(Reaction.body) > 0)
if by.get('search', 0) > 2:
if len(by.get('search', '')) > 2:
q = q.filter(Reaction.body.ilike(f'%{by["body"]}%'))
if by.get("days"):
after = datetime.now(tz=timezone.utc) - timedelta(days=int(by["days"]) or 30)
q = q.filter(Reaction.createdAt > after)
order_way = asc if by.get("sort", "").startswith("-") else desc
# replace "-" -> "" ?
order_field = by.get("sort") or Reaction.createdAt
q = q.group_by(
@@ -252,23 +265,24 @@ async def load_reactions_by(_, _info, by, limit=50, offset=0):
order_way(order_field)
)
q = calc_reactions(q)
q = add_reaction_stat_columns(q)
q = q.where(Reaction.deletedAt.is_(None))
q = q.limit(limit).offset(offset)
reactions = []
with local_session() as session:
for [reaction, user, shout, rating, commented, reacted] in session.execute(q):
for [reaction, user, shout, reacted_stat, commented_stat, rating_stat] in session.execute(q):
reaction.createdBy = user
reaction.shout = shout
reaction.stat = {
"rating": rating,
"commented": commented,
"reacted": reacted
"rating": rating_stat,
"commented": commented_stat,
"reacted": reacted_stat
}
reactions.append(reaction)
# ?
if by.get("stat"):
reactions.sort(lambda r: r.stat.get(by["stat"]) or r.createdAt)

View File

@@ -1,56 +1,91 @@
from sqlalchemy import and_
from sqlalchemy import and_, select, distinct, func
from auth.authenticate import login_required
from base.orm import local_session
from base.resolvers import mutation, query
from orm.shout import ShoutTopic, ShoutAuthor
from orm.topic import Topic, TopicFollower
from services.zine.topics import TopicStorage
from services.stat.topicstat import TopicStat
from orm import Shout, User
# from services.stat.viewed import ViewedStorage
def add_topic_stat_columns(q):
q = q.outerjoin(ShoutTopic, Topic.id == ShoutTopic.topic).add_columns(
func.count(distinct(ShoutTopic.shout)).label('shouts_stat')
).outerjoin(ShoutAuthor, ShoutTopic.shout == ShoutAuthor.shout).add_columns(
func.count(distinct(ShoutAuthor.user)).label('authors_stat')
).outerjoin(TopicFollower,
and_(
TopicFollower.topic == Topic.id,
TopicFollower.follower == ShoutAuthor.id
)).add_columns(
func.count(distinct(TopicFollower.follower)).label('followers_stat')
)
q = q.group_by(Topic.id)
return q
async def get_topic_stat(slug):
return {
"shouts": len(TopicStat.shouts_by_topic.get(slug, {}).keys()),
"authors": len(TopicStat.authors_by_topic.get(slug, {}).keys()),
"followers": len(TopicStat.followers_by_topic.get(slug, {}).keys())
def add_stat(topic, stat_columns):
[shouts_stat, authors_stat, followers_stat] = stat_columns
topic.stat = {
"shouts": shouts_stat,
"authors": authors_stat,
"followers": followers_stat
}
return topic
def get_topics_from_query(q):
topics = []
with local_session() as session:
for [topic, *stat_columns] in session.execute(q):
topic = add_stat(topic, stat_columns)
topics.append(topic)
return topics
def followed_by_user(user_slug):
q = select(Topic)
q = add_topic_stat_columns(q)
q = q.join(User).where(User.slug == user_slug)
return get_topics_from_query(q)
@query.field("topicsAll")
async def topics_all(_, _info):
topics = await TopicStorage.get_topics_all()
for topic in topics:
topic.stat = await get_topic_stat(topic.slug)
return topics
q = select(Topic)
q = add_topic_stat_columns(q)
return get_topics_from_query(q)
@query.field("topicsByCommunity")
async def topics_by_community(_, info, community):
topics = await TopicStorage.get_topics_by_community(community)
for topic in topics:
topic.stat = await get_topic_stat(topic.slug)
return topics
q = select(Topic).where(Topic.community == community)
q = add_topic_stat_columns(q)
return get_topics_from_query(q)
@query.field("topicsByAuthor")
async def topics_by_author(_, _info, author):
shouts = TopicStorage.get_topics_by_author(author)
author_topics = set()
for s in shouts:
for tpc in s.topics:
tpc = await TopicStorage.topics[tpc.slug]
tpc.stat = await get_topic_stat(tpc.slug)
author_topics.add(tpc)
return list(author_topics)
q = select(Topic)
q = add_topic_stat_columns(q)
q = q.join(User).where(User.slug == author)
return get_topics_from_query(q)
@query.field("getTopic")
async def get_topic(_, _info, slug):
t = TopicStorage.topics[slug]
t.stat = await get_topic_stat(slug)
return t
q = select(Topic).where(Topic.slug == slug)
q = add_topic_stat_columns(q)
topics = get_topics_from_query(q)
return topics[0]
@mutation.field("createTopic")
@@ -61,7 +96,7 @@ async def create_topic(_, _info, inp):
new_topic = Topic.create(**inp)
session.add(new_topic)
session.commit()
await TopicStorage.update_topic(new_topic)
return {"topic": new_topic}
@@ -76,25 +111,26 @@ async def update_topic(_, _info, inp):
else:
topic.update(**inp)
session.commit()
await TopicStorage.update_topic(topic.slug)
return {"topic": topic}
async def topic_follow(user, slug):
with local_session() as session:
following = TopicFollower.create(topic=slug, follower=user.slug)
topic = session.query(Topic).where(Topic.slug == slug).one()
following = TopicFollower.create(topic=topic.id, follower=user.id)
session.add(following)
session.commit()
await TopicStorage.update_topic(slug)
async def topic_unfollow(user, slug):
with local_session() as session:
sub = (
session.query(TopicFollower).filter(
session.query(TopicFollower).join(Topic).filter(
and_(
TopicFollower.follower == user.slug,
TopicFollower.topic == slug
TopicFollower.follower == user.id,
Topic.slug == slug
)
).first()
)
@@ -103,9 +139,13 @@ async def topic_unfollow(user, slug):
else:
session.delete(sub)
session.commit()
await TopicStorage.update_topic(slug)
@query.field("topicsRandom")
async def topics_random(_, info, amount=12):
return TopicStorage.get_random_topics(amount)
q = select(Topic)
q = add_topic_stat_columns(q)
q = q.join(Shout, ShoutTopic.shout == Shout.id).group_by(Topic.id).having(func.count(Shout.id) > 2)
q = q.order_by(func.random()).limit(amount)
return get_topics_from_query(q)