from datetime import datetime, timedelta, timezone from typing import List from sqlalchemy import and_, distinct, func, literal, select from sqlalchemy.orm import aliased, joinedload from auth.authenticate import login_required from auth.credentials import AuthCredentials from base.orm import local_session from base.resolvers import mutation, query from orm.reaction import Reaction, ReactionKind from orm.shout import ShoutAuthor, ShoutTopic from orm.topic import Topic, TopicFollower from orm.user import AuthorFollower, Role, User, UserRating, UserRole 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')) # 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 # dufok mod (^*^') : @query.field("userFollowedTopics") async def get_followed_topics(_, info, slug) -> List[Topic]: user_id_query = select(User.id).where(User.slug == slug) with local_session() as session: user_id = session.execute(user_id_query).scalar() if user_id is None: raise ValueError("User not found") return await followed_topics(user_id) async def followed_topics(user_id): return followed_by_user(user_id) # dufok mod (^*^') : @query.field("userFollowedAuthors") async def get_followed_authors(_, _info, slug) -> List[User]: # 1. First, we need to get the user_id for the given slug user_id_query = select(User.id).where(User.slug == slug) with local_session() as session: user_id = session.execute(user_id_query).scalar() if user_id is None: raise ValueError("User not found") return await followed_authors(user_id) # 2. Now, we can use the user_id to get the followed authors async def followed_authors(user_id): q = select(User) q = add_author_stat_columns(q) q = q.join(AuthorFollower, AuthorFollower.author == User.id).where( AuthorFollower.follower == user_id ) # 3. Pass the query to the get_authors_from_query function and return the results return get_authors_from_query(q) @query.field("userFollowers") async def user_followers(_, _info, slug) -> List[User]: q = select(User) q = add_author_stat_columns(q) aliased_user = aliased(User) q = ( q.join(AuthorFollower, AuthorFollower.follower == User.id) .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): with local_session() as session: user = session.query(User).where(User.slug == slug).first() roles = ( session.query(Role) .options(joinedload(Role.permissions)) .join(UserRole) .where(UserRole.user == user.id) .all() ) return roles @mutation.field("updateProfile") @login_required async def update_profile(_, info, profile): auth = info.context["request"].auth user_id = auth.user_id with local_session() as session: user = session.query(User).filter(User.id == user_id).one() if not user: return {"error": "canoot find user"} user.update(profile) session.commit() return {"error": None, "author": user} @mutation.field("rateUser") @login_required async def rate_user(_, info, rated_userslug, value): auth: AuthCredentials = info.context["request"].auth with local_session() as session: rating = ( session.query(UserRating) .filter(and_(UserRating.rater == auth.user_id, UserRating.user == rated_userslug)) .first() ) if rating: rating.value = value session.commit() return {} try: UserRating.create(rater=auth.user_id, user=rated_userslug, value=value) except Exception as err: return {"error": err} return {} # for mutation.field("follow") def author_follow(user_id, slug): try: with local_session() as session: author = session.query(User).where(User.slug == slug).one() af = AuthorFollower.create(follower=user_id, author=author.id) session.add(af) session.commit() return True except: return False # for mutation.field("unfollow") def author_unfollow(user_id, slug): with local_session() as session: flw = ( session.query(AuthorFollower) .join(User, User.id == AuthorFollower.author) .filter(and_(AuthorFollower.follower == user_id, User.slug == slug)) .first() ) if flw: session.delete(flw) session.commit() return True return False @query.field("authorsAll") async def get_authors_all(_, _info): 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): q = select(User).where(User.slug == slug) q = add_author_stat_columns(q) [author] = get_authors_from_query(q) with local_session() as session: comments_count = ( session.query(Reaction) .where(and_(Reaction.createdBy == author.id, Reaction.kind == ReactionKind.COMMENT)) .count() ) author.stat["commented"] = comments_count return author @query.field("loadAuthorsBy") async def load_authors_by(_, info, by, limit, offset): 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) @query.field("loadMySubscriptions") @login_required async def load_my_subscriptions(_, info): auth = info.context["request"].auth user_id = auth.user_id authors_query = ( select(User) .join(AuthorFollower, AuthorFollower.author == User.id) .where(AuthorFollower.follower == user_id) ) topics_query = select(Topic).join(TopicFollower).where(TopicFollower.follower == user_id) topics = [] authors = [] with local_session() as session: for [author] in session.execute(authors_query): authors.append(author) for [topic] in session.execute(topics_query): topics.append(topic) return {"topics": topics, "authors": authors}