fixes-api

This commit is contained in:
tonyrewin 2022-11-21 11:13:57 +03:00
parent 4c4f18147e
commit 5ad8328c03
17 changed files with 187 additions and 170 deletions

View File

@ -7,18 +7,18 @@ from resolvers.auth import (
auth_send_link, auth_send_link,
get_current_user, get_current_user,
) )
from resolvers.collab import remove_author, invite_author
from resolvers.migrate import markdown_body
# from resolvers.collab import invite_author, remove_author from resolvers.create.collab import remove_author, invite_author
from resolvers.editor import create_shout, delete_shout, update_shout from resolvers.create.migrate import markdown_body
from resolvers.profile import ( from resolvers.create.editor import create_shout, delete_shout, update_shout
from resolvers.zine.profile import (
load_authors_by, load_authors_by,
rate_user, rate_user,
update_profile update_profile
) )
from resolvers.reactions import ( from resolvers.zine.reactions import (
create_reaction, create_reaction,
delete_reaction, delete_reaction,
update_reaction, update_reaction,
@ -26,7 +26,7 @@ from resolvers.reactions import (
reactions_follow, reactions_follow,
load_reactions_by load_reactions_by
) )
from resolvers.topics import ( from resolvers.zine.topics import (
topic_follow, topic_follow,
topic_unfollow, topic_unfollow,
topics_by_author, topics_by_author,
@ -35,9 +35,13 @@ from resolvers.topics import (
get_topic get_topic
) )
from resolvers.zine import ( from resolvers.zine.following import (
follow, follow,
unfollow, unfollow
)
from resolvers.zine.load import (
load_shout,
load_shouts_by load_shouts_by
) )
@ -56,9 +60,10 @@ from resolvers.inbox.messages import (
) )
from resolvers.inbox.load import ( from resolvers.inbox.load import (
load_chats, load_chats,
load_messages_by load_messages_by,
load_recipients
) )
from resolvers.inbox.search import search_users from resolvers.inbox.search import search_recipients
__all__ = [ __all__ = [
# auth # auth
@ -69,32 +74,34 @@ __all__ = [
"auth_send_link", "auth_send_link",
"sign_out", "sign_out",
"get_current_user", "get_current_user",
# authors # zine.profile
"load_authors_by", "load_authors_by",
"rate_user", "rate_user",
"update_profile", "update_profile",
"get_authors_all", "get_authors_all",
# zine # zine.load
"load_shout",
"load_shouts_by", "load_shouts_by",
# zine.following
"follow", "follow",
"unfollow", "unfollow",
# editor # create.editor
"create_shout", "create_shout",
"update_shout", "update_shout",
"delete_shout", "delete_shout",
# migrate # create.migrate
"markdown_body", "markdown_body",
# collab # create.collab
"invite_author", "invite_author",
"remove_author", "remove_author",
# topics # zine.topics
"topics_all", "topics_all",
"topics_by_community", "topics_by_community",
"topics_by_author", "topics_by_author",
"topic_follow", "topic_follow",
"topic_unfollow", "topic_unfollow",
"get_topic", "get_topic",
# reactions # zine.reactions
"reactions_follow", "reactions_follow",
"reactions_unfollow", "reactions_unfollow",
"create_reaction", "create_reaction",
@ -113,5 +120,6 @@ __all__ = [
"update_message", "update_message",
"message_generator", "message_generator",
"mark_as_read", "mark_as_read",
"search_users" "load_recipients",
"search_recipients"
] ]

View File

@ -17,7 +17,7 @@ from base.exceptions import (BaseHttpException, InvalidPassword, InvalidToken,
from base.orm import local_session from base.orm import local_session
from base.resolvers import mutation, query from base.resolvers import mutation, query
from orm import Role, User from orm import Role, User
from resolvers.profile import user_subscriptions from resolvers.zine.profile import user_subscriptions
@mutation.field("refreshSession") @mutation.field("refreshSession")

View File

@ -7,7 +7,7 @@ from orm.rbac import Resource
from orm.shout import Shout, ShoutAuthor, ShoutTopic from orm.shout import Shout, ShoutAuthor, ShoutTopic
from orm.topic import TopicFollower from orm.topic import TopicFollower
from orm.user import User from orm.user import User
from resolvers.reactions import reactions_follow, reactions_unfollow from resolvers.zine.reactions import reactions_follow, reactions_unfollow
from services.zine.gittask import GitTask from services.zine.gittask import GitTask

View File

@ -4,8 +4,7 @@ from datetime import datetime
from auth.authenticate import login_required from auth.authenticate import login_required
from base.redis import redis from base.redis import redis
from base.resolvers import mutation, query from base.resolvers import mutation
from services.auth.users import UserStorage
async def add_user_to_chat(user_slug: str, chat_id: str, chat=None): async def add_user_to_chat(user_slug: str, chat_id: str, chat=None):
@ -122,10 +121,3 @@ async def delete_chat(_, info, chat_id: str):
return { return {
"error": "chat not exist" "error": "chat not exist"
} }
@query.field("chatUsersAll")
@login_required
async def get_chat_users_all(_, info):
chat_users = await UserStorage.get_all_chat_users()
return chat_users

View File

@ -3,27 +3,11 @@ from datetime import datetime, timedelta
from auth.authenticate import login_required from auth.authenticate import login_required
from base.redis import redis from base.redis import redis
from base.orm import local_session
from base.resolvers import query from base.resolvers import query
from orm.user import User
from resolvers.zine.profile import followed_authors
async def get_unread_counter(chat_id: str, user_slug: str): from .unread import get_unread_counter
try:
unread = await redis.execute("LLEN", f"chats/{chat_id}/unread/{user_slug}")
if unread:
return unread
except Exception:
return 0
async def get_total_unread_counter(user_slug: str):
chats = await redis.execute("GET", f"chats_by_user/{user_slug}")
unread = 0
if chats:
chats = json.loads(chats)
for chat_id in chats:
n = await get_unread_counter(chat_id, user_slug)
unread += n
return unread
async def load_messages(chatId: str, limit: int, offset: int): async def load_messages(chatId: str, limit: int, offset: int):
@ -100,3 +84,20 @@ async def load_messages_by(_, info, by, limit: int = 50, offset: int = 0):
"messages": messages, "messages": messages,
"error": None "error": None
} }
@query.field("loadRecipients")
async def load_recipients(_, info, limit=50, offset=0):
chat_users = []
user = info.context["request"].user
try:
chat_users += await followed_authors(user.slug)
limit = limit - len(chat_users)
except Exception:
pass
with local_session() as session:
chat_users += session.query(User).where(User.emailConfirmed).limit(limit).offset(offset)
return {
"members": chat_users,
"error": None
}

View File

@ -7,9 +7,9 @@ from base.orm import local_session
from orm.user import AuthorFollower from orm.user import AuthorFollower
@query.field("searchUsers") @query.field("searchRecipients")
@login_required @login_required
async def search_users(_, info, query: str, limit: int = 50, offset: int = 0): async def search_recipients(_, info, query: str, limit: int = 50, offset: int = 0):
result = [] result = []
# TODO: maybe redis scan? # TODO: maybe redis scan?
user = info.context["request"].user user = info.context["request"].user
@ -38,6 +38,6 @@ async def search_users(_, info, query: str, limit: int = 50, offset: int = 0):
result += session.query(AuthorFollower.follower).where(AuthorFollower.author.startswith(query))\ result += session.query(AuthorFollower.follower).where(AuthorFollower.author.startswith(query))\
.offset(offset + len(result)).limit(offset + len(result) + limit) .offset(offset + len(result)).limit(offset + len(result) + limit)
return { return {
"slugs": list(result), "members": list(result),
"error": None "error": None
} }

22
resolvers/inbox/unread.py Normal file
View File

@ -0,0 +1,22 @@
from base.redis import redis
import json
async def get_unread_counter(chat_id: str, user_slug: str):
try:
unread = await redis.execute("LLEN", f"chats/{chat_id}/unread/{user_slug}")
if unread:
return unread
except Exception:
return 0
async def get_total_unread_counter(user_slug: str):
chats = await redis.execute("GET", f"chats_by_user/{user_slug}")
unread = 0
if chats:
chats = json.loads(chats)
for chat_id in chats:
n = await get_unread_counter(chat_id, user_slug)
unread += n
return unread

View File

@ -0,0 +1,47 @@
from auth.authenticate import login_required
from base.resolvers import mutation
# from resolvers.community import community_follow, community_unfollow
from resolvers.zine.profile import author_follow, author_unfollow
from resolvers.zine.reactions import reactions_follow, reactions_unfollow
from resolvers.zine.topics import topic_follow, topic_unfollow
@mutation.field("follow")
@login_required
async def follow(_, info, what, slug):
user = info.context["request"].user
try:
if what == "AUTHOR":
author_follow(user, slug)
elif what == "TOPIC":
topic_follow(user, slug)
elif what == "COMMUNITY":
# community_follow(user, slug)
pass
elif what == "REACTIONS":
reactions_follow(user, slug)
except Exception as e:
return {"error": str(e)}
return {}
@mutation.field("unfollow")
@login_required
async def unfollow(_, info, what, slug):
user = info.context["request"].user
try:
if what == "AUTHOR":
author_unfollow(user, slug)
elif what == "TOPIC":
topic_unfollow(user, slug)
elif what == "COMMUNITY":
# community_unfollow(user, slug)
pass
elif what == "REACTIONS":
reactions_unfollow(user, slug)
except Exception as e:
return {"error": str(e)}
return {}

View File

@ -1,21 +1,65 @@
#!/usr/bin/env python3.10
from datetime import datetime, timedelta from datetime import datetime, timedelta
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from sqlalchemy.sql.expression import desc, asc, select, case from sqlalchemy.sql.expression import desc, asc, select, case
from auth.authenticate import login_required
from base.orm import local_session from base.orm import local_session
from base.resolvers import mutation, query from base.resolvers import query
from orm.shout import Shout from orm.shout import Shout
from orm.reaction import Reaction, ReactionKind 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
from resolvers.topics import topic_follow, topic_unfollow
from services.zine.shoutauthor import ShoutAuthorStorage from services.zine.shoutauthor import ShoutAuthorStorage
from services.stat.reacted import ReactedStorage from services.stat.reacted import ReactedStorage
def apply_filters(filters, q, user=None):
if filters.get("reacted") and user:
q.join(Reaction, Reaction.createdBy == user.slug)
if filters.get("visibility"):
q = q.filter(Shout.visibility == filters.get("visibility"))
if filters.get("layout"):
q = q.filter(Shout.layout == filters.get("layout"))
if filters.get("author"):
q = q.filter(Shout.authors.any(slug=filters.get("author")))
if filters.get("topic"):
q = q.filter(Shout.topics.any(slug=filters.get("topic")))
if filters.get("title"):
q = q.filter(Shout.title.ilike(f'%{filters.get("title")}%'))
if filters.get("body"):
q = q.filter(Shout.body.ilike(f'%{filters.get("body")}%s'))
if filters.get("days"):
before = datetime.now() - timedelta(days=int(filters.get("days")) or 30)
q = q.filter(Shout.createdAt > before)
return q
def extract_order(o, q):
if o:
q = q.add_columns(sa.func.count(Reaction.id).label(o))
if o == 'comments':
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 == "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(o))
return o
else:
return 'createdAt'
@query.field("loadShout") @query.field("loadShout")
async def load_shout(_, info, slug): async def load_shout(_, info, slug):
with local_session() as session: with local_session() as session:
@ -48,7 +92,7 @@ async def load_shouts_by(_, info, options):
offset: 0 offset: 0
limit: 50 limit: 50
order_by: 'createdAt' order_by: 'createdAt'
order_by_desc: tr order_by_desc: true
} }
:return: Shout[] :return: Shout[]
@ -61,53 +105,9 @@ async def load_shouts_by(_, info, options):
).where( ).where(
Shout.deletedAt.is_(None) Shout.deletedAt.is_(None)
) )
if options.get("filters"):
if options.get("filters").get("reacted"):
user = info.context["request"].user user = info.context["request"].user
q.join(Reaction, Reaction.createdBy == user.slug) q = apply_filters(options.get("filters"), q, user)
if options.get("filters").get("visibility"): order_by = extract_order(options.get("order_by"))
q = q.filter(Shout.visibility == options.get("filters").get("visibility"))
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)
o = options.get("order_by")
if o:
q = q.add_columns(sa.func.count(Reaction.id).label(o))
if o == 'comments':
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 == "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(o))
order_by = o
else:
order_by = 'createdAt'
query_order_by = desc(order_by) if options.get("order_by_desc") else asc(order_by) query_order_by = desc(order_by) if options.get("order_by_desc") else asc(order_by)
offset = options.get("offset", 0) offset = options.get("offset", 0)
limit = options.get("limit", 10) limit = options.get("limit", 10)
@ -121,44 +121,3 @@ async def load_shouts_by(_, info, options):
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug) a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
return shouts return shouts
@mutation.field("follow")
@login_required
async def follow(_, info, what, slug):
user = info.context["request"].user
try:
if what == "AUTHOR":
author_follow(user, slug)
elif what == "TOPIC":
topic_follow(user, slug)
elif what == "COMMUNITY":
# community_follow(user, slug)
pass
elif what == "REACTIONS":
reactions_follow(user, slug)
except Exception as e:
return {"error": str(e)}
return {}
@mutation.field("unfollow")
@login_required
async def unfollow(_, info, what, slug):
user = info.context["request"].user
try:
if what == "AUTHOR":
author_unfollow(user, slug)
elif what == "TOPIC":
topic_unfollow(user, slug)
elif what == "COMMUNITY":
# community_unfollow(user, slug)
pass
elif what == "REACTIONS":
reactions_unfollow(user, slug)
except Exception as e:
return {"error": str(e)}
return {}

View File

@ -14,7 +14,7 @@ from services.stat.topicstat import TopicStat
from services.zine.shoutauthor import ShoutAuthor from services.zine.shoutauthor import ShoutAuthor
# from .community import followed_communities # from .community import followed_communities
from .inbox.load import get_total_unread_counter from resolvers.inbox.unread import get_total_unread_counter
from .topics import get_topic_stat from .topics import get_topic_stat

View File

@ -9,7 +9,7 @@ from orm.topic import Topic, TopicFollower
from services.zine.topics import TopicStorage from services.zine.topics import TopicStorage
from services.stat.reacted import ReactedStorage from services.stat.reacted import ReactedStorage
from services.stat.topicstat import TopicStat from services.stat.topicstat import TopicStat
from services.stat.viewed import ViewedStorage # from services.stat.viewed import ViewedStorage
async def get_topic_stat(slug): async def get_topic_stat(slug):
@ -17,7 +17,7 @@ async def get_topic_stat(slug):
"shouts": len(TopicStat.shouts_by_topic.get(slug, {}).keys()), "shouts": len(TopicStat.shouts_by_topic.get(slug, {}).keys()),
"authors": len(TopicStat.authors_by_topic.get(slug, {}).keys()), "authors": len(TopicStat.authors_by_topic.get(slug, {}).keys()),
"followers": len(TopicStat.followers_by_topic.get(slug, {}).keys()), "followers": len(TopicStat.followers_by_topic.get(slug, {}).keys()),
"viewed": await ViewedStorage.get_topic(slug), # "viewed": await ViewedStorage.get_topic(slug),
"reacted": len(await ReactedStorage.get_topic(slug)), "reacted": len(await ReactedStorage.get_topic(slug)),
"commented": len(await ReactedStorage.get_topic_comments(slug)), "commented": len(await ReactedStorage.get_topic_comments(slug)),
"rating": await ReactedStorage.get_topic_rating(slug) "rating": await ReactedStorage.get_topic_rating(slug)

View File

@ -24,9 +24,11 @@ type AuthResult {
} }
type ChatMember { type ChatMember {
id: Int!
slug: String! slug: String!
name: String! name: String!
userpic: String userpic: String
lastSeen: DateTime
invitedAt: DateTime invitedAt: DateTime
invitedBy: String # user slug invitedBy: String # user slug
# TODO: add more # TODO: add more
@ -261,8 +263,8 @@ type Query {
# inbox # inbox
loadChats( limit: Int, offset: Int): Result! # your chats loadChats( limit: Int, offset: Int): Result! # your chats
loadMessagesBy(by: MessagesBy!, limit: Int, offset: Int): Result! loadMessagesBy(by: MessagesBy!, limit: Int, offset: Int): Result!
searchUsers(query: String!, limit: Int, offset: Int): Result! loadRecipients(limit: Int, offset: Int): Result!
chatUsersAll: [ChatUser]! searchRecipients(query: String!, limit: Int, offset: Int): Result!
# auth # auth
isEmailUsed(email: String!): Boolean! isEmailUsed(email: String!): Boolean!
@ -369,14 +371,6 @@ type User {
oid: String oid: String
} }
type ChatUser {
id: Int!
slug: String!
name: String!
userpic: String
lastSeen: DateTime
}
type Collab { type Collab {
authors: [String]! authors: [String]!
invites: [String] invites: [String]
@ -482,7 +476,7 @@ type TopicStat {
shouts: Int! shouts: Int!
followers: Int! followers: Int!
authors: Int! authors: Int!
viewed: Int # viewed: Int
reacted: Int! reacted: Int!
commented: Int commented: Int
rating: Int rating: Int

View File

@ -1,6 +1,5 @@
import asyncio import asyncio
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from base.orm import local_session
from orm.user import User from orm.user import User
@ -33,11 +32,6 @@ class UserStorage:
aaa.sort(key=lambda user: user.createdAt) aaa.sort(key=lambda user: user.createdAt)
return aaa return aaa
@staticmethod
async def get_all_chat_users():
with local_session() as session:
return session.query(User).where(User.emailConfirmed).all()
@staticmethod @staticmethod
async def get_top_users(): async def get_top_users():
self = UserStorage self = UserStorage

View File

@ -2,7 +2,7 @@ import asyncio
import json import json
from base.redis import redis from base.redis import redis
from orm.shout import Shout from orm.shout import Shout
from resolvers.zine import load_shouts_by from resolvers.zine.load import load_shouts_by
class SearchService: class SearchService: