load-by pattern, shoutscache removed
This commit is contained in:
parent
4b2f185986
commit
9942fc2558
|
@ -1,14 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
openssl req -newkey rsa:4096 \
|
|
||||||
-x509 \
|
|
||||||
-sha256 \
|
|
||||||
-days 3650 \
|
|
||||||
-nodes \
|
|
||||||
-out server.crt \
|
|
||||||
-keyout server.key \
|
|
||||||
-subj "/C=RU/ST=Moscow/L=Moscow/O=Discours/OU=Site/CN=newapi.discours.io"
|
|
||||||
|
|
||||||
openssl x509 -in server.crt -out server.pem -outform PEM
|
|
||||||
tar cvf server.tar server.crt server.key
|
|
||||||
dokku certs:add discoursio-api < server.tar
|
|
3
main.py
3
main.py
|
@ -14,7 +14,6 @@ from auth.oauth import oauth_login, oauth_authorize
|
||||||
from base.redis import redis
|
from base.redis import redis
|
||||||
from base.resolvers import resolvers
|
from base.resolvers import resolvers
|
||||||
from resolvers.auth import confirm_email_handler
|
from resolvers.auth import confirm_email_handler
|
||||||
from resolvers.zine import ShoutsCache
|
|
||||||
from services.main import storages_init
|
from services.main import storages_init
|
||||||
from services.stat.reacted import ReactedStorage
|
from services.stat.reacted import ReactedStorage
|
||||||
from services.stat.topicstat import TopicStat
|
from services.stat.topicstat import TopicStat
|
||||||
|
@ -36,8 +35,6 @@ async def start_up():
|
||||||
print(viewed_storage_task)
|
print(viewed_storage_task)
|
||||||
reacted_storage_task = asyncio.create_task(ReactedStorage.worker())
|
reacted_storage_task = asyncio.create_task(ReactedStorage.worker())
|
||||||
print(reacted_storage_task)
|
print(reacted_storage_task)
|
||||||
shouts_cache_task = asyncio.create_task(ShoutsCache.worker())
|
|
||||||
print(shouts_cache_task)
|
|
||||||
shout_author_task = asyncio.create_task(ShoutAuthorStorage.worker())
|
shout_author_task = asyncio.create_task(ShoutAuthorStorage.worker())
|
||||||
print(shout_author_task)
|
print(shout_author_task)
|
||||||
topic_stat_task = asyncio.create_task(TopicStat.worker())
|
topic_stat_task = asyncio.create_task(TopicStat.worker())
|
||||||
|
|
|
@ -64,4 +64,3 @@ class Shout(Base):
|
||||||
updatedAt = Column(DateTime, nullable=True, comment="Updated at")
|
updatedAt = Column(DateTime, nullable=True, comment="Updated at")
|
||||||
publishedAt = Column(DateTime, nullable=True)
|
publishedAt = Column(DateTime, nullable=True)
|
||||||
deletedAt = Column(DateTime, nullable=True)
|
deletedAt = Column(DateTime, nullable=True)
|
||||||
|
|
||||||
|
|
|
@ -8,32 +8,23 @@ from resolvers.auth import (
|
||||||
get_current_user,
|
get_current_user,
|
||||||
)
|
)
|
||||||
from resolvers.collab import remove_author, invite_author
|
from resolvers.collab import remove_author, invite_author
|
||||||
from resolvers.community import (
|
|
||||||
create_community,
|
|
||||||
delete_community,
|
|
||||||
get_community,
|
|
||||||
get_communities,
|
|
||||||
)
|
|
||||||
|
|
||||||
from resolvers.migrate import markdown_body
|
from resolvers.migrate import markdown_body
|
||||||
|
|
||||||
# from resolvers.collab import invite_author, remove_author
|
# from resolvers.collab import invite_author, remove_author
|
||||||
from resolvers.editor import create_shout, delete_shout, update_shout
|
from resolvers.editor import create_shout, delete_shout, update_shout
|
||||||
from resolvers.profile import (
|
from resolvers.profile import (
|
||||||
get_users_by_slugs,
|
load_authors_by,
|
||||||
get_user_reacted_shouts,
|
rate_user,
|
||||||
get_user_roles,
|
update_profile
|
||||||
get_top_authors,
|
|
||||||
get_author
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# from resolvers.feed import shouts_for_feed, my_candidates
|
|
||||||
from resolvers.reactions import (
|
from resolvers.reactions import (
|
||||||
create_reaction,
|
create_reaction,
|
||||||
delete_reaction,
|
delete_reaction,
|
||||||
update_reaction,
|
update_reaction,
|
||||||
reactions_unfollow,
|
reactions_unfollow,
|
||||||
reactions_follow,
|
reactions_follow,
|
||||||
|
load_reactions_by
|
||||||
)
|
)
|
||||||
from resolvers.topics import (
|
from resolvers.topics import (
|
||||||
topic_follow,
|
topic_follow,
|
||||||
|
@ -45,36 +36,31 @@ from resolvers.topics import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from resolvers.zine import (
|
from resolvers.zine import (
|
||||||
get_shout_by_slug,
|
|
||||||
follow,
|
follow,
|
||||||
unfollow,
|
unfollow,
|
||||||
increment_view,
|
load_shouts_by
|
||||||
top_month,
|
|
||||||
top_overall,
|
|
||||||
recent_published,
|
|
||||||
recent_all,
|
|
||||||
recent_commented,
|
|
||||||
recent_reacted,
|
|
||||||
shouts_by_authors,
|
|
||||||
shouts_by_topics,
|
|
||||||
shouts_by_layout_recent,
|
|
||||||
shouts_by_layout_top,
|
|
||||||
shouts_by_layout_topmonth,
|
|
||||||
shouts_by_communities,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from resolvers.inbox.chats import load_chats, \
|
from resolvers.inbox.chats import (
|
||||||
create_chat, delete_chat, update_chat, \
|
create_chat,
|
||||||
invite_to_chat, enter_chat
|
delete_chat,
|
||||||
from resolvers.inbox.messages import load_chat_messages, \
|
update_chat,
|
||||||
create_message, delete_message, update_message, \
|
invite_to_chat
|
||||||
message_generator, mark_as_read
|
)
|
||||||
from resolvers.inbox.search import search_users, \
|
from resolvers.inbox.messages import (
|
||||||
search_messages, search_chats
|
create_message,
|
||||||
|
delete_message,
|
||||||
|
update_message,
|
||||||
|
message_generator,
|
||||||
|
mark_as_read
|
||||||
|
)
|
||||||
|
from resolvers.inbox.load import (
|
||||||
|
load_chats,
|
||||||
|
load_messages_by
|
||||||
|
)
|
||||||
|
from resolvers.inbox.search import search_users
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"follow",
|
|
||||||
"unfollow",
|
|
||||||
# auth
|
# auth
|
||||||
"login",
|
"login",
|
||||||
"register_by_email",
|
"register_by_email",
|
||||||
|
@ -83,27 +69,15 @@ __all__ = [
|
||||||
"auth_send_link",
|
"auth_send_link",
|
||||||
"sign_out",
|
"sign_out",
|
||||||
"get_current_user",
|
"get_current_user",
|
||||||
# profile
|
# authors
|
||||||
"get_users_by_slugs",
|
"load_authors_by",
|
||||||
"get_user_roles",
|
"rate_user",
|
||||||
"get_top_authors",
|
"update_profile",
|
||||||
"get_author",
|
"get_authors_all",
|
||||||
# zine
|
# zine
|
||||||
"recent_published",
|
"load_shouts_by",
|
||||||
"recent_commented",
|
"follow",
|
||||||
"recent_reacted",
|
"unfollow",
|
||||||
"recent_all",
|
|
||||||
"shouts_by_topics",
|
|
||||||
"shouts_by_layout_recent",
|
|
||||||
"shouts_by_layout_topmonth",
|
|
||||||
"shouts_by_layout_top",
|
|
||||||
"shouts_by_authors",
|
|
||||||
"shouts_by_communities",
|
|
||||||
"get_user_reacted_shouts",
|
|
||||||
"top_month",
|
|
||||||
"top_overall",
|
|
||||||
"increment_view",
|
|
||||||
"get_shout_by_slug",
|
|
||||||
# editor
|
# editor
|
||||||
"create_shout",
|
"create_shout",
|
||||||
"update_shout",
|
"update_shout",
|
||||||
|
@ -120,31 +94,24 @@ __all__ = [
|
||||||
"topic_follow",
|
"topic_follow",
|
||||||
"topic_unfollow",
|
"topic_unfollow",
|
||||||
"get_topic",
|
"get_topic",
|
||||||
# communities
|
|
||||||
"get_community",
|
|
||||||
"get_communities",
|
|
||||||
"create_community",
|
|
||||||
"delete_community",
|
|
||||||
# reactions
|
# reactions
|
||||||
"reactions_follow",
|
"reactions_follow",
|
||||||
"reactions_unfollow",
|
"reactions_unfollow",
|
||||||
"create_reaction",
|
"create_reaction",
|
||||||
"update_reaction",
|
"update_reaction",
|
||||||
"delete_reaction",
|
"delete_reaction",
|
||||||
|
"load_reactions_by",
|
||||||
# inbox
|
# inbox
|
||||||
|
"load_chats",
|
||||||
|
"load_messages_by",
|
||||||
|
"invite_to_chat",
|
||||||
"create_chat",
|
"create_chat",
|
||||||
"delete_chat",
|
"delete_chat",
|
||||||
"update_chat",
|
"update_chat",
|
||||||
"load_chats",
|
|
||||||
"create_message",
|
"create_message",
|
||||||
"delete_message",
|
"delete_message",
|
||||||
"update_message",
|
"update_message",
|
||||||
"load_chat_messages",
|
|
||||||
"message_generator",
|
"message_generator",
|
||||||
"mark_as_read",
|
"mark_as_read",
|
||||||
"search_users",
|
"search_users"
|
||||||
"search_chats",
|
|
||||||
"search_messages",
|
|
||||||
"enter_chat",
|
|
||||||
"invite_to_chat"
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import and_
|
|
||||||
|
|
||||||
from auth.authenticate import login_required
|
|
||||||
from base.orm import local_session
|
|
||||||
from base.resolvers import mutation, query
|
|
||||||
from orm.collection import Collection, ShoutCollection
|
|
||||||
from orm.user import User
|
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("createCollection")
|
|
||||||
@login_required
|
|
||||||
async def create_collection(_, _info, inp):
|
|
||||||
# auth = info.context["request"].auth
|
|
||||||
# user_id = auth.user_id
|
|
||||||
collection = Collection.create(
|
|
||||||
slug=inp.get("slug", ""),
|
|
||||||
title=inp.get("title", ""),
|
|
||||||
desc=inp.get("desc", ""),
|
|
||||||
pic=inp.get("pic", ""),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"collection": collection}
|
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("updateCollection")
|
|
||||||
@login_required
|
|
||||||
async def update_collection(_, info, inp):
|
|
||||||
auth = info.context["request"].auth
|
|
||||||
user_id = auth.user_id
|
|
||||||
collection_slug = inp.get("slug", "")
|
|
||||||
with local_session() as session:
|
|
||||||
owner = session.query(User).filter(User.id == user_id) # note list here
|
|
||||||
collection = (
|
|
||||||
session.query(Collection).filter(Collection.slug == collection_slug).first()
|
|
||||||
)
|
|
||||||
editors = [e.slug for e in collection.editors]
|
|
||||||
if not collection:
|
|
||||||
return {"error": "invalid collection id"}
|
|
||||||
if collection.createdBy not in (owner + editors):
|
|
||||||
return {"error": "access denied"}
|
|
||||||
collection.title = inp.get("title", "")
|
|
||||||
collection.desc = inp.get("desc", "")
|
|
||||||
collection.pic = inp.get("pic", "")
|
|
||||||
collection.updatedAt = datetime.now()
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("deleteCollection")
|
|
||||||
@login_required
|
|
||||||
async def delete_collection(_, info, slug):
|
|
||||||
auth = info.context["request"].auth
|
|
||||||
user_id = auth.user_id
|
|
||||||
with local_session() as session:
|
|
||||||
collection = session.query(Collection).filter(Collection.slug == slug).first()
|
|
||||||
if not collection:
|
|
||||||
return {"error": "invalid collection slug"}
|
|
||||||
if collection.owner != user_id:
|
|
||||||
return {"error": "access denied"}
|
|
||||||
collection.deletedAt = datetime.now()
|
|
||||||
session.add(collection)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("getUserCollections")
|
|
||||||
async def get_user_collections(_, _info, userslug):
|
|
||||||
collections = []
|
|
||||||
with local_session() as session:
|
|
||||||
user = session.query(User).filter(User.slug == userslug).first()
|
|
||||||
if user:
|
|
||||||
# TODO: check rights here
|
|
||||||
collections = (
|
|
||||||
session.query(Collection)
|
|
||||||
.where(
|
|
||||||
and_(Collection.createdBy == userslug, Collection.publishedAt.is_not(None))
|
|
||||||
)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
for c in collections:
|
|
||||||
shouts = (
|
|
||||||
session.query(ShoutCollection)
|
|
||||||
.filter(ShoutCollection.collection == c.id)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
c.amount = len(shouts)
|
|
||||||
return collections
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("getMyColelctions")
|
|
||||||
@login_required
|
|
||||||
async def get_my_collections(_, info):
|
|
||||||
auth = info.context["request"].auth
|
|
||||||
user_id = auth.user_id
|
|
||||||
with local_session() as session:
|
|
||||||
collections = (
|
|
||||||
session.query(Collection).when(Collection.createdBy == user_id).all()
|
|
||||||
)
|
|
||||||
return collections
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: get shouts list by collection
|
|
|
@ -1,134 +0,0 @@
|
||||||
from datetime import datetime
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from sqlalchemy import and_
|
|
||||||
|
|
||||||
from auth.authenticate import login_required
|
|
||||||
from base.orm import local_session
|
|
||||||
from base.resolvers import mutation, query
|
|
||||||
from orm.community import Community, CommunityFollower
|
|
||||||
from orm.user import User
|
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("createCommunity")
|
|
||||||
@login_required
|
|
||||||
async def create_community(_, info, input):
|
|
||||||
auth = info.context["request"].auth
|
|
||||||
user_id = auth.user_id
|
|
||||||
with local_session() as session:
|
|
||||||
user = session.query(User).where(User.id == user_id).first()
|
|
||||||
community = Community.create(
|
|
||||||
slug=input.get("slug", ""),
|
|
||||||
title=input.get("title", ""),
|
|
||||||
desc=input.get("desc", ""),
|
|
||||||
pic=input.get("pic", ""),
|
|
||||||
createdBy=user.slug,
|
|
||||||
createdAt=datetime.now(),
|
|
||||||
)
|
|
||||||
session.add(community)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
return {"community": community}
|
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("updateCommunity")
|
|
||||||
@login_required
|
|
||||||
async def update_community(_, info, input):
|
|
||||||
auth = info.context["request"].auth
|
|
||||||
user_id = auth.user_id
|
|
||||||
community_slug = input.get("slug", "")
|
|
||||||
|
|
||||||
with local_session() as session:
|
|
||||||
owner = session.query(User).filter(User.id == user_id) # note list here
|
|
||||||
community = (
|
|
||||||
session.query(Community).filter(Community.slug == community_slug).first()
|
|
||||||
)
|
|
||||||
editors = [e.slug for e in community.editors]
|
|
||||||
if not community:
|
|
||||||
return {"error": "invalid community id"}
|
|
||||||
if community.createdBy not in (owner + editors):
|
|
||||||
return {"error": "access denied"}
|
|
||||||
community.title = input.get("title", "")
|
|
||||||
community.desc = input.get("desc", "")
|
|
||||||
community.pic = input.get("pic", "")
|
|
||||||
community.updatedAt = datetime.now()
|
|
||||||
session.add(community)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("deleteCommunity")
|
|
||||||
@login_required
|
|
||||||
async def delete_community(_, info, slug):
|
|
||||||
auth = info.context["request"].auth
|
|
||||||
user_id = auth.user_id
|
|
||||||
|
|
||||||
with local_session() as session:
|
|
||||||
community = session.query(Community).filter(Community.slug == slug).first()
|
|
||||||
if not community:
|
|
||||||
return {"error": "invalid community slug"}
|
|
||||||
if community.owner != user_id:
|
|
||||||
return {"error": "access denied"}
|
|
||||||
community.deletedAt = datetime.now()
|
|
||||||
session.add(community)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("getCommunity")
|
|
||||||
async def get_community(_, info, slug):
|
|
||||||
with local_session() as session:
|
|
||||||
community = session.query(Community).filter(Community.slug == slug).first()
|
|
||||||
if not community:
|
|
||||||
return {"error": "invalid community id"}
|
|
||||||
|
|
||||||
return community
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("getCommunities")
|
|
||||||
async def get_communities(_, info):
|
|
||||||
with local_session() as session:
|
|
||||||
communities = session.query(Community)
|
|
||||||
return communities
|
|
||||||
|
|
||||||
|
|
||||||
def community_follow(user, slug):
|
|
||||||
with local_session() as session:
|
|
||||||
cf = CommunityFollower.create(follower=user.slug, community=slug)
|
|
||||||
session.add(cf)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def community_unfollow(user, slug):
|
|
||||||
with local_session() as session:
|
|
||||||
following = (
|
|
||||||
session.query(CommunityFollower)
|
|
||||||
.filter(
|
|
||||||
and_(
|
|
||||||
CommunityFollower.follower == user.slug,
|
|
||||||
CommunityFollower.community == slug,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if not following:
|
|
||||||
raise Exception("[orm.community] following was not exist")
|
|
||||||
session.delete(following)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("userFollowedCommunities")
|
|
||||||
def get_followed_communities(_, _info, user_slug) -> List[Community]:
|
|
||||||
return followed_communities(user_slug)
|
|
||||||
|
|
||||||
|
|
||||||
def followed_communities(user_slug) -> List[Community]:
|
|
||||||
ccc = []
|
|
||||||
with local_session() as session:
|
|
||||||
ccc = (
|
|
||||||
session.query(Community.slug)
|
|
||||||
.join(CommunityFollower)
|
|
||||||
.where(CommunityFollower.follower == user_slug)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
return ccc
|
|
|
@ -73,9 +73,14 @@ async def update_shout(_, info, inp):
|
||||||
shout.update(inp)
|
shout.update(inp)
|
||||||
shout.updatedAt = datetime.now()
|
shout.updatedAt = datetime.now()
|
||||||
session.add(shout)
|
session.add(shout)
|
||||||
for topic in inp.get("topic_slugs", []):
|
if inp.get("topics"):
|
||||||
st = ShoutTopic.create(shout=slug, topic=topic)
|
# remove old links
|
||||||
session.add(st)
|
links = session.query(ShoutTopic).where(ShoutTopic.shout == slug).all()
|
||||||
|
for topiclink in links:
|
||||||
|
session.delete(topiclink)
|
||||||
|
# add new topic links
|
||||||
|
for topic in inp.get("topics", []):
|
||||||
|
ShoutTopic.create(shout=slug, topic=topic)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
GitTask(inp, user.username, user.email, "update shout %s" % (slug))
|
GitTask(inp, user.username, user.email, "update shout %s" % (slug))
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from sqlalchemy import and_, desc
|
|
||||||
|
|
||||||
from auth.authenticate import login_required
|
|
||||||
from base.orm import local_session
|
|
||||||
from base.resolvers import query
|
|
||||||
from orm.shout import Shout, ShoutAuthor, ShoutTopic
|
|
||||||
from orm.topic import TopicFollower
|
|
||||||
from orm.user import AuthorFollower
|
|
||||||
from services.zine.shoutscache import prepare_shouts
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("shoutsForFeed")
|
|
||||||
@login_required
|
|
||||||
async def get_user_feed(_, info, offset, limit) -> List[Shout]:
|
|
||||||
user = info.context["request"].user
|
|
||||||
shouts = []
|
|
||||||
with local_session() as session:
|
|
||||||
shouts = (
|
|
||||||
session.query(Shout)
|
|
||||||
.join(ShoutAuthor)
|
|
||||||
.join(AuthorFollower)
|
|
||||||
.where(AuthorFollower.follower == user.slug)
|
|
||||||
.order_by(desc(Shout.createdAt))
|
|
||||||
)
|
|
||||||
topic_rows = (
|
|
||||||
session.query(Shout)
|
|
||||||
.join(ShoutTopic)
|
|
||||||
.join(TopicFollower)
|
|
||||||
.where(TopicFollower.follower == user.slug)
|
|
||||||
.order_by(desc(Shout.createdAt))
|
|
||||||
)
|
|
||||||
shouts = shouts.union(topic_rows).limit(limit).offset(offset).all()
|
|
||||||
return shouts
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("recentCandidates")
|
|
||||||
@login_required
|
|
||||||
async def user_unpublished_shouts(_, info, offset, limit) -> List[Shout]:
|
|
||||||
user = info.context["request"].user
|
|
||||||
with local_session() as session:
|
|
||||||
shouts = prepare_shouts(
|
|
||||||
session.query(Shout)
|
|
||||||
.join(ShoutAuthor)
|
|
||||||
.where(and_(Shout.publishedAt.is_(None), ShoutAuthor.user == user.slug))
|
|
||||||
.order_by(desc(Shout.createdAt))
|
|
||||||
.group_by(Shout.id)
|
|
||||||
.limit(limit)
|
|
||||||
.offset(offset)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
return shouts
|
|
|
@ -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 resolvers.inbox.load import load_messages, load_user_chats
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
|
@ -20,40 +19,6 @@ async def add_user_to_chat(user_slug: str, chat_id: str, chat=None):
|
||||||
await redis.execute("SET", f"chats_by_user/{member}", json.dumps(chats_ids))
|
await redis.execute("SET", f"chats_by_user/{member}", json.dumps(chats_ids))
|
||||||
|
|
||||||
|
|
||||||
@query.field("loadChats")
|
|
||||||
@login_required
|
|
||||||
async def load_chats(_, info):
|
|
||||||
user = info.context["request"].user
|
|
||||||
return await load_user_chats(user.slug)
|
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("enterChat")
|
|
||||||
@login_required
|
|
||||||
async def enter_chat(_, info, chat_id: str):
|
|
||||||
''' enter to public chat with :chat_id '''
|
|
||||||
user = info.context["request"].user
|
|
||||||
chat = await redis.execute("GET", f"chats/{chat_id}")
|
|
||||||
if not chat:
|
|
||||||
return {
|
|
||||||
"error": "chat not exist"
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
chat = dict(json.loads(chat))
|
|
||||||
if chat['private']:
|
|
||||||
return {
|
|
||||||
"error": "cannot enter private chat"
|
|
||||||
}
|
|
||||||
if user.slug not in chat["users"]:
|
|
||||||
chat["users"].append(user.slug)
|
|
||||||
await add_user_to_chat(user.slug, chat_id, chat)
|
|
||||||
await redis.execute("SET" f"chats/{chat_id}", json.dumps(chat))
|
|
||||||
chat['messages'] = await load_messages(chat_id)
|
|
||||||
return {
|
|
||||||
"chat": chat,
|
|
||||||
"error": None
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("inviteChat")
|
@mutation.field("inviteChat")
|
||||||
async def invite_to_chat(_, info, invited: str, chat_id: str):
|
async def invite_to_chat(_, info, invited: str, chat_id: str):
|
||||||
''' invite user with :slug to chat with :chat_id '''
|
''' invite user with :slug to chat with :chat_id '''
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import json
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from base.redis import redis
|
from base.redis import redis
|
||||||
|
from base.resolvers import query
|
||||||
|
from auth.authenticate import login_required
|
||||||
|
|
||||||
|
|
||||||
async def get_unread_counter(chat_id: str, user_slug: str):
|
async def get_unread_counter(chat_id: str, user_slug: str):
|
||||||
|
@ -24,23 +26,6 @@ async def get_total_unread_counter(user_slug: str):
|
||||||
return unread
|
return unread
|
||||||
|
|
||||||
|
|
||||||
async def load_user_chats(slug, offset: int, amount: int):
|
|
||||||
""" load :amount chats of :slug user with :offset """
|
|
||||||
|
|
||||||
chats = await redis.execute("GET", f"chats_by_user/{slug}")
|
|
||||||
if chats:
|
|
||||||
chats = list(json.loads(chats))[offset:offset + amount]
|
|
||||||
if not chats:
|
|
||||||
chats = []
|
|
||||||
for c in chats:
|
|
||||||
c['messages'] = await load_messages(c['id'])
|
|
||||||
c['unread'] = await get_unread_counter(c['id'], slug)
|
|
||||||
return {
|
|
||||||
"chats": chats,
|
|
||||||
"error": None
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def load_messages(chatId: str, offset: int, amount: int):
|
async def load_messages(chatId: str, offset: int, amount: int):
|
||||||
''' load :amount messages for :chatId with :offset '''
|
''' load :amount messages for :chatId with :offset '''
|
||||||
messages = []
|
messages = []
|
||||||
|
@ -57,3 +42,61 @@ async def load_messages(chatId: str, offset: int, amount: int):
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
"error": None
|
"error": None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@query.field("loadChats")
|
||||||
|
@login_required
|
||||||
|
async def load_chats(_, info, offset: int, amount: int):
|
||||||
|
""" load :amount chats of current user with :offset """
|
||||||
|
user = info.context["request"].user
|
||||||
|
chats = await redis.execute("GET", f"chats_by_user/{user.slug}")
|
||||||
|
if chats:
|
||||||
|
chats = list(json.loads(chats))[offset:offset + amount]
|
||||||
|
if not chats:
|
||||||
|
chats = []
|
||||||
|
for c in chats:
|
||||||
|
c['messages'] = await load_messages(c['id'])
|
||||||
|
c['unread'] = await get_unread_counter(c['id'], user.slug)
|
||||||
|
return {
|
||||||
|
"chats": chats,
|
||||||
|
"error": None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@query.field("loadMessagesBy")
|
||||||
|
@login_required
|
||||||
|
async def load_messages_by(_, info, by, offset: int = 0, amount: int = 50):
|
||||||
|
''' load :amount messages of :chat_id with :offset '''
|
||||||
|
user = info.context["request"].user
|
||||||
|
my_chats = await redis.execute("GET", f"chats_by_user/{user.slug}")
|
||||||
|
chat_id = by.get('chat')
|
||||||
|
if chat_id:
|
||||||
|
chat = await redis.execute("GET", f"chats/{chat_id}")
|
||||||
|
if not chat:
|
||||||
|
return {
|
||||||
|
"error": "chat not exist"
|
||||||
|
}
|
||||||
|
messages = await load_messages(chat_id, offset, amount)
|
||||||
|
user_id = by.get('author')
|
||||||
|
if user_id:
|
||||||
|
chats = await redis.execute("GET", f"chats_by_user/{user_id}")
|
||||||
|
our_chats = list(set(chats) & set(my_chats))
|
||||||
|
for c in our_chats:
|
||||||
|
messages += await load_messages(c, offset, amount)
|
||||||
|
body_like = by.get('body')
|
||||||
|
if body_like:
|
||||||
|
for c in my_chats:
|
||||||
|
mmm = await load_messages(c, offset, amount)
|
||||||
|
for m in mmm:
|
||||||
|
if body_like in m["body"]:
|
||||||
|
messages.append(m)
|
||||||
|
days = by.get("days")
|
||||||
|
if days:
|
||||||
|
messages = filter(
|
||||||
|
lambda m: datetime.now() - int(m["createdAt"]) < timedelta(days=by.get("days")),
|
||||||
|
messages
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"messages": messages,
|
||||||
|
"error": None
|
||||||
|
}
|
||||||
|
|
|
@ -4,25 +4,8 @@ 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, subscription
|
from base.resolvers import mutation, subscription
|
||||||
from services.inbox import ChatFollowing, MessageResult, MessagesStorage
|
from services.inbox import ChatFollowing, MessageResult, MessagesStorage
|
||||||
from resolvers.inbox.load import load_messages
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("loadMessages")
|
|
||||||
@login_required
|
|
||||||
async def load_chat_messages(_, info, chat_id: str, offset: int = 0, amount: int = 50):
|
|
||||||
''' load [amount] chat's messages with [offset] '''
|
|
||||||
chat = await redis.execute("GET", f"chats/{chat_id}")
|
|
||||||
if not chat:
|
|
||||||
return {
|
|
||||||
"error": "chat not exist"
|
|
||||||
}
|
|
||||||
messages = await load_messages(chat_id, offset, amount)
|
|
||||||
return {
|
|
||||||
"messages": messages,
|
|
||||||
"error": None
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("createMessage")
|
@mutation.field("createMessage")
|
||||||
|
|
|
@ -41,39 +41,3 @@ async def search_users(_, info, query: str, offset: int = 0, amount: int = 50):
|
||||||
"slugs": list(result),
|
"slugs": list(result),
|
||||||
"error": None
|
"error": None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@query.field("searchChats")
|
|
||||||
@login_required
|
|
||||||
async def search_chats(_, info, query: str, offset: int = 0, amount: int = 50):
|
|
||||||
user = info.context["request"].user
|
|
||||||
my_chats = await redis.execute("GET", f"/chats_by_user/{user.slug}")
|
|
||||||
chats = []
|
|
||||||
for chat_id in my_chats:
|
|
||||||
chat = await redis.execute("GET", f"chats/{chat_id}")
|
|
||||||
if chat:
|
|
||||||
chat = dict(json.loads(chat))
|
|
||||||
chats.append(chat)
|
|
||||||
return {
|
|
||||||
"chats": chats,
|
|
||||||
"error": None
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("searchMessages")
|
|
||||||
@login_required
|
|
||||||
async def search_messages(_, info, query: str, offset: int = 0, amount: int = 50):
|
|
||||||
user = info.context["request"].user
|
|
||||||
my_chats = await redis.execute("GET", f"/chats_by_user/{user.slug}")
|
|
||||||
chats = []
|
|
||||||
if my_chats:
|
|
||||||
my_chats = list(json.loads(my_chats))
|
|
||||||
for chat_id in my_chats:
|
|
||||||
chat = await redis.execute("GET", f"chats/{chat_id}")
|
|
||||||
if chat:
|
|
||||||
chat = dict(json.loads(chat))
|
|
||||||
chats.append(chat)
|
|
||||||
return {
|
|
||||||
"chats": chats,
|
|
||||||
"error": None
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from sqlalchemy import and_, desc, func
|
from sqlalchemy import and_, func
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from auth.authenticate import login_required
|
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 mutation, query
|
||||||
from orm.reaction import Reaction
|
from orm.reaction import Reaction
|
||||||
from orm.shout import Shout
|
|
||||||
from orm.topic import Topic, TopicFollower
|
from orm.topic import Topic, TopicFollower
|
||||||
from orm.user import AuthorFollower, Role, User, UserRating, UserRole
|
from orm.user import AuthorFollower, Role, User, UserRating, UserRole
|
||||||
from services.auth.users import UserStorage
|
from services.auth.users import UserStorage
|
||||||
from services.stat.reacted import ReactedStorage
|
from services.stat.reacted import ReactedStorage
|
||||||
from services.zine.shoutscache import ShoutsCache
|
from services.stat.topicstat import TopicStat
|
||||||
|
from services.zine.shoutauthor import ShoutAuthorStorage
|
||||||
|
|
||||||
from .community import followed_communities
|
# from .community import followed_communities
|
||||||
from .inbox.load import get_total_unread_counter
|
from .inbox.load import get_total_unread_counter
|
||||||
from .topics import get_topic_stat
|
from .topics import get_topic_stat
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ async def user_subscriptions(slug: str):
|
||||||
"topics": [t.slug for t in await followed_topics(slug)], # followed topics slugs
|
"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
|
"authors": [a.slug for a in await followed_authors(slug)], # followed authors slugs
|
||||||
"reactions": await ReactedStorage.get_shouts_by_author(slug),
|
"reactions": await ReactedStorage.get_shouts_by_author(slug),
|
||||||
"communities": [c.slug for c in followed_communities(slug)], # communities
|
# "communities": [c.slug for c in followed_communities(slug)], # communities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,24 +46,6 @@ async def get_author_stat(slug):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@query.field("userReactedShouts")
|
|
||||||
async def get_user_reacted_shouts(_, slug: str, offset: int, limit: int) -> List[Shout]:
|
|
||||||
user = await UserStorage.get_user_by_slug(slug)
|
|
||||||
if not user:
|
|
||||||
return []
|
|
||||||
with local_session() as session:
|
|
||||||
shouts = (
|
|
||||||
session.query(Shout)
|
|
||||||
.join(Reaction)
|
|
||||||
.where(Reaction.createdBy == user.slug)
|
|
||||||
.order_by(desc(Reaction.createdAt))
|
|
||||||
.limit(limit)
|
|
||||||
.offset(offset)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
return shouts
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("userFollowedTopics")
|
@query.field("userFollowedTopics")
|
||||||
@login_required
|
@login_required
|
||||||
async def get_followed_topics(_, info, slug) -> List[Topic]:
|
async def get_followed_topics(_, info, slug) -> List[Topic]:
|
||||||
|
@ -115,20 +97,7 @@ async def user_followers(_, _info, slug) -> List[User]:
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
|
||||||
@query.field("getUsersBySlugs")
|
async def get_user_roles(slug):
|
||||||
async def get_users_by_slugs(_, _info, slugs):
|
|
||||||
with local_session() as session:
|
|
||||||
users = (
|
|
||||||
session.query(User)
|
|
||||||
.options(selectinload(User.ratings))
|
|
||||||
.filter(User.slug in slugs)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
return users
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("getUserRoles")
|
|
||||||
async def get_user_roles(_, _info, slug):
|
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
user = session.query(User).where(User.slug == slug).first()
|
user = session.query(User).where(User.slug == slug).first()
|
||||||
roles = (
|
roles = (
|
||||||
|
@ -206,7 +175,7 @@ def author_unfollow(user, slug):
|
||||||
@query.field("authorsAll")
|
@query.field("authorsAll")
|
||||||
async def get_authors_all(_, _info):
|
async def get_authors_all(_, _info):
|
||||||
users = await UserStorage.get_all_users()
|
users = await UserStorage.get_all_users()
|
||||||
authorslugs = await ShoutsCache.get_all_authors_slugs()
|
authorslugs = ShoutAuthorStorage.shouts_by_author.keys()
|
||||||
authors = []
|
authors = []
|
||||||
for author in users:
|
for author in users:
|
||||||
if author.slug in authorslugs:
|
if author.slug in authorslugs:
|
||||||
|
@ -215,13 +184,32 @@ async def get_authors_all(_, _info):
|
||||||
return authors
|
return authors
|
||||||
|
|
||||||
|
|
||||||
@query.field("topAuthors")
|
@query.field("loadAuthorsBy")
|
||||||
def get_top_authors(_, _info, offset, limit):
|
async def load_authors_by(_, info, by, amount, offset):
|
||||||
return list(UserStorage.get_top_users())[offset : offset + limit] # type: ignore
|
authors = []
|
||||||
|
with local_session() as session:
|
||||||
|
aq = session.query(User)
|
||||||
@query.field("getAuthor")
|
if by.get("slug"):
|
||||||
async def get_author(_, _info, slug):
|
aq = aq.filter(User.slug.ilike(f"%{by['slug']}%"))
|
||||||
a = await UserStorage.get_user_by_slug(slug)
|
elif by.get("name"):
|
||||||
a.stat = await get_author_stat(slug)
|
aq = aq.filter(User.name.ilike(f"%{by['name']}%"))
|
||||||
return a
|
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() - timedelta(days=by["lastSeen"])
|
||||||
|
aq = aq.filter(User.lastSeen > days_before)
|
||||||
|
elif by.get("createdAt"): # in days
|
||||||
|
days_before = datetime.now() - 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(amount).offset(offset)
|
||||||
|
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)).sort(lambda a: a["stat"].get(by.get("stat")))
|
||||||
|
return authors
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
|
from sqlalchemy import and_, desc, select
|
||||||
from sqlalchemy import and_, desc
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from auth.authenticate import login_required
|
from auth.authenticate import login_required
|
||||||
from base.orm import local_session
|
from base.orm import local_session
|
||||||
|
@ -8,14 +8,12 @@ from base.resolvers import mutation, query
|
||||||
from orm.reaction import Reaction, ReactionKind
|
from orm.reaction import Reaction, ReactionKind
|
||||||
from orm.shout import Shout, ShoutReactionsFollower
|
from orm.shout import Shout, ShoutReactionsFollower
|
||||||
from orm.user import User
|
from orm.user import User
|
||||||
from services.auth.users import UserStorage
|
|
||||||
from services.stat.reacted import ReactedStorage
|
from services.stat.reacted import ReactedStorage
|
||||||
from services.stat.viewed import ViewedStorage
|
|
||||||
|
|
||||||
|
|
||||||
async def get_reaction_stat(reaction_id):
|
async def get_reaction_stat(reaction_id):
|
||||||
return {
|
return {
|
||||||
"viewed": await ViewedStorage.get_reaction(reaction_id),
|
# "viewed": await ViewedStorage.get_reaction(reaction_id),
|
||||||
"reacted": len(await ReactedStorage.get_reaction(reaction_id)),
|
"reacted": len(await ReactedStorage.get_reaction(reaction_id)),
|
||||||
"rating": await ReactedStorage.get_reaction_rating(reaction_id),
|
"rating": await ReactedStorage.get_reaction_rating(reaction_id),
|
||||||
"commented": len(await ReactedStorage.get_reaction_comments(reaction_id)),
|
"commented": len(await ReactedStorage.get_reaction_comments(reaction_id)),
|
||||||
|
@ -202,57 +200,55 @@ async def delete_reaction(_, info, rid):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@query.field("reactionsForShouts")
|
@query.field("loadReactionsBy")
|
||||||
async def get_reactions_for_shouts(_, info, shouts, offset, limit):
|
async def load_reactions_by(_, info, by, amount=50, offset=0):
|
||||||
return await reactions_for_shouts(shouts, offset, limit)
|
"""
|
||||||
|
:param by: {
|
||||||
|
shout: 'some-slug'
|
||||||
|
author: 'discours',
|
||||||
|
topic: 'culture',
|
||||||
|
body: 'something else',
|
||||||
|
stat: 'rating' | 'comments' | 'reacted' | 'views',
|
||||||
|
days: 30
|
||||||
|
}
|
||||||
|
:param amount: int amount of shouts
|
||||||
|
:param offset: int offset in this order
|
||||||
|
:return: Reaction[]
|
||||||
|
"""
|
||||||
|
|
||||||
|
q = select(Reaction).options(
|
||||||
async def reactions_for_shouts(shouts, offset, limit):
|
selectinload(Reaction.shout),
|
||||||
reactions = []
|
).where(
|
||||||
with local_session() as session:
|
Reaction.deletedAt.is_(None)
|
||||||
for slug in shouts:
|
).join(
|
||||||
reactions += (
|
Shout,
|
||||||
session.query(Reaction)
|
Shout.slug == Reaction.shout
|
||||||
.filter(Reaction.shout == slug)
|
|
||||||
.where(Reaction.deletedAt.is_not(None))
|
|
||||||
.order_by(desc("createdAt"))
|
|
||||||
.offset(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.all()
|
|
||||||
)
|
)
|
||||||
for r in reactions:
|
if by.get("slug"):
|
||||||
r.stat = await get_reaction_stat(r.id)
|
q = q.filter(Shout.slug == by["slug"])
|
||||||
r.createdBy = await UserStorage.get_user(r.createdBy or "discours")
|
else:
|
||||||
return reactions
|
if by.get("reacted"):
|
||||||
reactions = []
|
user = info.context["request"].user
|
||||||
with local_session() as session:
|
q = q.filter(Reaction.createdBy == user.slug)
|
||||||
for slug in shouts:
|
if by.get("author"):
|
||||||
reactions += (
|
q = q.filter(Reaction.createdBy == by["author"])
|
||||||
session.query(Reaction)
|
if by.get("topic"):
|
||||||
.filter(Reaction.shout == slug)
|
q = q.filter(Shout.topics.contains(by["topic"]))
|
||||||
.where(Reaction.deletedAt.is_not(None))
|
if by.get("body"):
|
||||||
.order_by(desc("createdAt"))
|
q = q.filter(Reaction.body.ilike(f'%{by["body"]}%'))
|
||||||
.offset(offset)
|
if by.get("days"):
|
||||||
.limit(limit)
|
before = datetime.now() - timedelta(days=int(by["days"]) or 30)
|
||||||
.all()
|
q = q.filter(Reaction.createdAt > before)
|
||||||
)
|
q = q.group_by(Shout.id).order_by(
|
||||||
for r in reactions:
|
desc(by.get("order") or "createdAt")
|
||||||
r.stat = await get_reaction_stat(r.id)
|
).limit(amount).offset(offset)
|
||||||
r.createdBy = await UserStorage.get_user(r.createdBy or "discours")
|
|
||||||
return reactions
|
|
||||||
|
|
||||||
|
rrr = []
|
||||||
@query.field("reactionsByAuthor")
|
|
||||||
async def get_reactions_by_author(_, info, slug, limit=50, offset=0):
|
|
||||||
reactions = []
|
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
reactions = (
|
# post query stats and author's captions
|
||||||
session.query(Reaction)
|
for r in list(map(lambda r: r.Reaction, session.execute(q))):
|
||||||
.where(Reaction.createdBy == slug)
|
|
||||||
.limit(limit)
|
|
||||||
.offset(offset)
|
|
||||||
)
|
|
||||||
for r in reactions:
|
|
||||||
r.stat = await get_reaction_stat(r.id)
|
r.stat = await get_reaction_stat(r.id)
|
||||||
r.createdBy = await UserStorage.get_user(r.createdBy or "discours")
|
rrr.append(r)
|
||||||
return reactions
|
if by.get("stat"):
|
||||||
|
rrr.sort(lambda r: r.stat.get(by["stat"]) or r.createdAt)
|
||||||
|
return rrr
|
||||||
|
|
|
@ -6,11 +6,10 @@ 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 mutation, query
|
||||||
from orm.topic import Topic, TopicFollower
|
from orm.topic import Topic, TopicFollower
|
||||||
from services.zine.shoutscache import ShoutsCache
|
|
||||||
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):
|
||||||
|
@ -18,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)
|
||||||
|
@ -43,7 +42,7 @@ async def topics_by_community(_, info, community):
|
||||||
|
|
||||||
@query.field("topicsByAuthor")
|
@query.field("topicsByAuthor")
|
||||||
async def topics_by_author(_, _info, author):
|
async def topics_by_author(_, _info, author):
|
||||||
shouts = ShoutsCache.by_author.get(author, [])
|
shouts = TopicStorage.get_topics_by_author(author)
|
||||||
author_topics = set()
|
author_topics = set()
|
||||||
for s in shouts:
|
for s in shouts:
|
||||||
for tpc in s.topics:
|
for tpc in s.topics:
|
||||||
|
|
|
@ -1,248 +1,83 @@
|
||||||
from graphql.type import GraphQLResolveInfo
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
from sqlalchemy.sql.expression import and_, desc, select
|
from sqlalchemy.sql.expression import desc, select
|
||||||
|
|
||||||
from auth.authenticate import login_required
|
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 mutation, query
|
||||||
from orm.collection import ShoutCollection
|
from orm.shout import Shout
|
||||||
from orm.shout import Shout, ShoutTopic
|
from orm.reaction import Reaction
|
||||||
from orm.topic import Topic
|
# from resolvers.community import community_follow, community_unfollow
|
||||||
from resolvers.community import community_follow, community_unfollow
|
|
||||||
from resolvers.profile import author_follow, author_unfollow
|
from resolvers.profile import author_follow, author_unfollow
|
||||||
from resolvers.reactions import reactions_follow, reactions_unfollow
|
from resolvers.reactions import reactions_follow, reactions_unfollow
|
||||||
from resolvers.topics import topic_follow, topic_unfollow
|
from resolvers.topics import topic_follow, topic_unfollow
|
||||||
from services.search import SearchService
|
|
||||||
from services.stat.viewed import ViewedStorage
|
|
||||||
from services.zine.shoutauthor import ShoutAuthorStorage
|
from services.zine.shoutauthor import ShoutAuthorStorage
|
||||||
from services.zine.shoutscache import ShoutsCache, get_shout_stat
|
from services.stat.reacted import ReactedStorage
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("incrementView")
|
@query.field("loadShoutsBy")
|
||||||
async def increment_view(_, _info, shout):
|
async def load_shouts_by(_, info, by, amount=50, offset=0):
|
||||||
# TODO: use ackee to collect views
|
"""
|
||||||
async with ViewedStorage.lock:
|
:param by: {
|
||||||
return ViewedStorage.increment(shout)
|
layout: 'audio',
|
||||||
|
published: true,
|
||||||
|
author: 'discours',
|
||||||
|
topic: 'culture',
|
||||||
|
title: 'something',
|
||||||
|
body: 'something else',
|
||||||
|
stat: 'rating' | 'comments' | 'reacted' | 'views',
|
||||||
|
days: 30
|
||||||
|
}
|
||||||
|
:param amount: int amount of shouts
|
||||||
|
:param offset: int offset in this order
|
||||||
|
:return: Shout[]
|
||||||
|
"""
|
||||||
|
|
||||||
|
q = select(Shout, Reaction).options(
|
||||||
@query.field("topMonth")
|
selectinload(Shout.authors),
|
||||||
async def top_month(_, _info, offset, limit):
|
selectinload(Shout.topics),
|
||||||
async with ShoutsCache.lock:
|
selectinload(Shout.reactions)
|
||||||
return ShoutsCache.top_month[offset : offset + limit]
|
).where(
|
||||||
|
Shout.deletedAt.is_(None)
|
||||||
|
).join(
|
||||||
@query.field("topPublished")
|
Reaction, Reaction.shout == Shout.slug
|
||||||
async def top_published(_, _info, daysago, offset, limit):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
return ShoutsCache.get_top_published_before(daysago, offset, limit)
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("topCommented")
|
|
||||||
async def top_commented(_, _info, offset, limit):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
return ShoutsCache.top_commented[offset : offset + limit]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("topOverall")
|
|
||||||
async def top_overall(_, _info, offset, limit):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
return ShoutsCache.top_overall[offset : offset + limit]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("recentPublished")
|
|
||||||
async def recent_published(_, _info, offset, limit):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
return ShoutsCache.recent_published[offset : offset + limit]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("recentAll")
|
|
||||||
async def recent_all(_, _info, offset, limit):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
return ShoutsCache.recent_all[offset : offset + limit]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("recentReacted")
|
|
||||||
async def recent_reacted(_, _info, offset, limit):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
return ShoutsCache.recent_reacted[offset : offset + limit]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("recentCommented")
|
|
||||||
async def recent_commented(_, _info, offset, limit):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
return ShoutsCache.recent_commented[offset : offset + limit]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("getShoutBySlug")
|
|
||||||
async def get_shout_by_slug(_, info, slug):
|
|
||||||
all_fields = [
|
|
||||||
node.name.value for node in info.field_nodes[0].selection_set.selections
|
|
||||||
]
|
|
||||||
selected_fields = set(["authors", "topics"]).intersection(all_fields)
|
|
||||||
select_options = [selectinload(getattr(Shout, field)) for field in selected_fields]
|
|
||||||
with local_session() as session:
|
|
||||||
# s = text(open("src/queries/shout-by-slug.sql", "r").read() % slug)
|
|
||||||
shout = (
|
|
||||||
session.query(Shout)
|
|
||||||
.options(select_options)
|
|
||||||
.filter(Shout.slug == slug)
|
|
||||||
.first()
|
|
||||||
)
|
)
|
||||||
|
if by.get("slug"):
|
||||||
if not shout:
|
q = q.filter(Shout.slug == by["slug"])
|
||||||
print(f"shout with slug {slug} not exist")
|
|
||||||
return {"error": "shout not found"}
|
|
||||||
else:
|
else:
|
||||||
for a in shout.authors:
|
if by.get("reacted"):
|
||||||
a.caption = await ShoutAuthorStorage.get_author_caption(slug, a.slug)
|
user = info.context["request"].user
|
||||||
return shout
|
q = q.filter(Reaction.createdBy == user.slug)
|
||||||
|
if by.get("published"):
|
||||||
|
q = q.filter(Shout.publishedAt.is_not(None))
|
||||||
|
if by.get("layout"):
|
||||||
|
q = q.filter(Shout.layout == by["layout"])
|
||||||
|
if by.get("author"):
|
||||||
|
q = q.filter(Shout.authors.contains(by["author"]))
|
||||||
|
if by.get("topic"):
|
||||||
|
q = q.filter(Shout.topics.contains(by["topic"]))
|
||||||
|
if by.get("title"):
|
||||||
|
q = q.filter(Shout.title.ilike(f'%{by["title"]}%'))
|
||||||
|
if by.get("body"):
|
||||||
|
q = q.filter(Shout.body.ilike(f'%{by["body"]}%'))
|
||||||
|
if by.get("days"):
|
||||||
|
before = datetime.now() - timedelta(days=int(by["days"]) or 30)
|
||||||
|
q = q.filter(Shout.createdAt > before)
|
||||||
|
q = q.group_by(Shout.id).order_by(
|
||||||
|
desc(by.get("order") or "createdAt")
|
||||||
|
).limit(amount).offset(offset)
|
||||||
|
|
||||||
|
shouts = []
|
||||||
@query.field("searchQuery")
|
|
||||||
async def get_search_results(_, _info, searchtext, offset, limit):
|
|
||||||
shouts = SearchService.search(searchtext)
|
|
||||||
# TODO: sort and filter types for search service
|
|
||||||
for s in shouts:
|
|
||||||
shout = s.dict()
|
|
||||||
for a in shout['authors']:
|
|
||||||
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
|
|
||||||
s.stat.relevance = 1 # FIXME: expecting search engine rated relevance
|
|
||||||
return shouts[offset : offset + limit]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("shoutsByAuthors")
|
|
||||||
async def shouts_by_authors(_, _info, slugs, offset=0, limit=100):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
shouts = {}
|
|
||||||
for author in slugs:
|
|
||||||
shouts_by_author = list(ShoutsCache.by_author.get(author, {}).values())
|
|
||||||
for s in shouts_by_author:
|
|
||||||
for a in s.authors:
|
|
||||||
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
|
|
||||||
if bool(s.publishedAt):
|
|
||||||
shouts[s.slug] = s
|
|
||||||
shouts_prepared = list(shouts.values())
|
|
||||||
shouts_prepared.sort(key=lambda s: s.publishedAt, reverse=True)
|
|
||||||
return shouts_prepared[offset : offset + limit]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("recentLayoutShouts")
|
|
||||||
async def shouts_by_layout_recent(_param, _info: GraphQLResolveInfo, layout, amount=100, offset=0):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
shouts = {}
|
|
||||||
# for layout in ['image', 'audio', 'video', 'literature']:
|
|
||||||
shouts_by_layout = list(ShoutsCache.by_layout.get(layout, []))
|
|
||||||
for s in shouts_by_layout:
|
|
||||||
if s.visibility == 'public': # if bool(s.publishedAt):
|
|
||||||
shouts[s.slug] = s
|
|
||||||
for a in s.authors:
|
|
||||||
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
|
|
||||||
|
|
||||||
shouts_prepared = list(shouts.values())
|
|
||||||
shouts_prepared.sort(key=lambda s: s.createdAt, reverse=True)
|
|
||||||
return shouts_prepared[offset : offset + amount]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("topLayoutShouts")
|
|
||||||
async def shouts_by_layout_top(_param, _info: GraphQLResolveInfo, layout, amount=100, offset=0):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
shouts = {}
|
|
||||||
# for layout in ['image', 'audio', 'video', 'literature']:
|
|
||||||
shouts_by_layout = list(ShoutsCache.by_layout.get(layout, []))
|
|
||||||
for s in shouts_by_layout:
|
|
||||||
if s.visibility == 'public': # if bool(s.publishedAt):
|
|
||||||
shouts[s.slug] = s
|
|
||||||
for a in s.authors:
|
|
||||||
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
|
|
||||||
s.stat = await get_shout_stat(s.slug)
|
|
||||||
shouts_prepared = list(shouts.values())
|
|
||||||
shouts_prepared.sort(key=lambda s: s.stat["rating"], reverse=True)
|
|
||||||
return shouts_prepared[offset : offset + amount]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("topMonthLayoutShouts")
|
|
||||||
async def shouts_by_layout_topmonth(_param, _info: GraphQLResolveInfo, layout, amount=100, offset=0):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
shouts = {}
|
|
||||||
# for layout in ['image', 'audio', 'video', 'literature']:
|
|
||||||
shouts_by_layout = list(ShoutsCache.by_layout.get(layout, []))
|
|
||||||
month_ago = datetime.now() - timedelta(days=30)
|
|
||||||
for s in shouts_by_layout:
|
|
||||||
if s.visibility == 'public' and s.createdAt > month_ago:
|
|
||||||
shouts[s.slug] = s
|
|
||||||
for a in s.authors:
|
|
||||||
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
|
|
||||||
|
|
||||||
shouts_prepared = list(shouts.values())
|
|
||||||
shouts_prepared.sort(key=lambda s: s.stat["rating"], reverse=True)
|
|
||||||
return shouts_prepared[offset : offset + amount]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("shoutsByTopics")
|
|
||||||
async def shouts_by_topics(_, _info, slugs, offset=0, limit=100):
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
shouts = {}
|
|
||||||
for topic in slugs:
|
|
||||||
shouts_by_topic = list(ShoutsCache.by_topic.get(topic, {}).values())
|
|
||||||
for s in shouts_by_topic:
|
|
||||||
for a in s.authors:
|
|
||||||
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
|
|
||||||
if bool(s.publishedAt):
|
|
||||||
shouts[s.slug] = s
|
|
||||||
shouts_prepared = list(shouts.values())
|
|
||||||
shouts_prepared.sort(key=lambda s: s.publishedAt, reverse=True)
|
|
||||||
return shouts_prepared[offset : offset + limit]
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("shoutsByCollection")
|
|
||||||
async def shouts_by_collection(_, _info, collection, offset, limit):
|
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
shouts = (
|
# post query stats and author's captions
|
||||||
session.query(Shout)
|
for s in list(map(lambda r: r.Shout, session.execute(q))):
|
||||||
.join(ShoutCollection, ShoutCollection.collection == collection)
|
s.stat = await ReactedStorage.get_shout_stat(s.slug)
|
||||||
.where(and_(ShoutCollection.shout == Shout.slug, Shout.publishedAt.is_not(None)))
|
|
||||||
.order_by(desc("publishedAt"))
|
|
||||||
.limit(limit)
|
|
||||||
.offset(offset)
|
|
||||||
)
|
|
||||||
for s in shouts:
|
|
||||||
for a in s.authors:
|
|
||||||
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
|
|
||||||
return shouts
|
|
||||||
|
|
||||||
|
|
||||||
SINGLE_COMMUNITY = True
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("shoutsByCommunities")
|
|
||||||
async def shouts_by_communities(_, info, slugs, offset, limit):
|
|
||||||
if SINGLE_COMMUNITY:
|
|
||||||
return recent_published(_, info, offset, limit)
|
|
||||||
else:
|
|
||||||
with local_session() as session:
|
|
||||||
# TODO fix postgres high load
|
|
||||||
shouts = (
|
|
||||||
session.query(Shout)
|
|
||||||
.distinct()
|
|
||||||
.join(ShoutTopic)
|
|
||||||
.where(
|
|
||||||
and_(
|
|
||||||
Shout.publishedAt.is_not(None),
|
|
||||||
ShoutTopic.topic.in_(
|
|
||||||
select(Topic.slug).where(Topic.community.in_(slugs))
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.order_by(desc("publishedAt"))
|
|
||||||
.limit(limit)
|
|
||||||
.offset(offset)
|
|
||||||
)
|
|
||||||
|
|
||||||
for s in shouts:
|
|
||||||
for a in s.authors:
|
for a in s.authors:
|
||||||
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
|
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
|
||||||
|
shouts.append(s)
|
||||||
|
if by.get("stat"):
|
||||||
|
shouts.sort(lambda s: s.stat.get(by["stat"]) or s.createdAt)
|
||||||
return shouts
|
return shouts
|
||||||
|
|
||||||
|
|
||||||
|
@ -256,7 +91,8 @@ async def follow(_, info, what, slug):
|
||||||
elif what == "TOPIC":
|
elif what == "TOPIC":
|
||||||
topic_follow(user, slug)
|
topic_follow(user, slug)
|
||||||
elif what == "COMMUNITY":
|
elif what == "COMMUNITY":
|
||||||
community_follow(user, slug)
|
# community_follow(user, slug)
|
||||||
|
pass
|
||||||
elif what == "REACTIONS":
|
elif what == "REACTIONS":
|
||||||
reactions_follow(user, slug)
|
reactions_follow(user, slug)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -276,7 +112,8 @@ async def unfollow(_, info, what, slug):
|
||||||
elif what == "TOPIC":
|
elif what == "TOPIC":
|
||||||
topic_unfollow(user, slug)
|
topic_unfollow(user, slug)
|
||||||
elif what == "COMMUNITY":
|
elif what == "COMMUNITY":
|
||||||
community_unfollow(user, slug)
|
# community_unfollow(user, slug)
|
||||||
|
pass
|
||||||
elif what == "REACTIONS":
|
elif what == "REACTIONS":
|
||||||
reactions_unfollow(user, slug)
|
reactions_unfollow(user, slug)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
126
schema.graphql
126
schema.graphql
|
@ -110,18 +110,6 @@ input ProfileInput {
|
||||||
bio: String
|
bio: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input CommunityInput {
|
|
||||||
title: String!
|
|
||||||
desc: String
|
|
||||||
pic: String
|
|
||||||
}
|
|
||||||
|
|
||||||
input CollectionInput {
|
|
||||||
title: String!
|
|
||||||
desc: String
|
|
||||||
pic: String
|
|
||||||
}
|
|
||||||
|
|
||||||
input TopicInput {
|
input TopicInput {
|
||||||
slug: String!
|
slug: String!
|
||||||
community: String!
|
community: String!
|
||||||
|
@ -161,7 +149,7 @@ type Mutation {
|
||||||
updateChat(chat: ChatInput!): Result!
|
updateChat(chat: ChatInput!): Result!
|
||||||
deleteChat(chatId: String!): Result!
|
deleteChat(chatId: String!): Result!
|
||||||
inviteChat(chatId: String!, userslug: String!): Result!
|
inviteChat(chatId: String!, userslug: String!): Result!
|
||||||
enterChat(chatId: String!): Result!
|
|
||||||
createMessage(chatId: String!, body: String!, replyTo: String): Result!
|
createMessage(chatId: String!, body: String!, replyTo: String): Result!
|
||||||
updateMessage(chatId: String!, id: Int!, body: String!): Result!
|
updateMessage(chatId: String!, id: Int!, body: String!): Result!
|
||||||
deleteMessage(chatId: String!, id: Int!): Result!
|
deleteMessage(chatId: String!, id: Int!): Result!
|
||||||
|
@ -180,7 +168,7 @@ type Mutation {
|
||||||
|
|
||||||
# user profile
|
# user profile
|
||||||
rateUser(slug: String!, value: Int!): Result!
|
rateUser(slug: String!, value: Int!): Result!
|
||||||
# updateOnlineStatus: Result!
|
updateOnlineStatus: Result!
|
||||||
updateProfile(profile: ProfileInput!): Result!
|
updateProfile(profile: ProfileInput!): Result!
|
||||||
|
|
||||||
# topics
|
# topics
|
||||||
|
@ -189,22 +177,11 @@ type Mutation {
|
||||||
updateTopic(input: TopicInput!): Result!
|
updateTopic(input: TopicInput!): Result!
|
||||||
destroyTopic(slug: String!): Result!
|
destroyTopic(slug: String!): Result!
|
||||||
|
|
||||||
|
|
||||||
# reactions
|
# reactions
|
||||||
createReaction(reaction: ReactionInput!): Result!
|
createReaction(reaction: ReactionInput!): Result!
|
||||||
updateReaction(reaction: ReactionInput!): Result!
|
updateReaction(reaction: ReactionInput!): Result!
|
||||||
deleteReaction(id: Int!): Result!
|
deleteReaction(id: Int!): Result!
|
||||||
|
|
||||||
# community
|
|
||||||
createCommunity(community: CommunityInput!): Result!
|
|
||||||
updateCommunity(community: CommunityInput!): Result!
|
|
||||||
deleteCommunity(slug: String!): Result!
|
|
||||||
|
|
||||||
# collection
|
|
||||||
createCollection(collection: CollectionInput!): Result!
|
|
||||||
updateCollection(collection: CollectionInput!): Result!
|
|
||||||
deleteCollection(slug: String!): Result!
|
|
||||||
|
|
||||||
# collab
|
# collab
|
||||||
inviteAuthor(author: String!, shout: String!): Result!
|
inviteAuthor(author: String!, shout: String!): Result!
|
||||||
removeAuthor(author: String!, shout: String!): Result!
|
removeAuthor(author: String!, shout: String!): Result!
|
||||||
|
@ -212,65 +189,70 @@ type Mutation {
|
||||||
# following
|
# following
|
||||||
follow(what: FollowingEntity!, slug: String!): Result!
|
follow(what: FollowingEntity!, slug: String!): Result!
|
||||||
unfollow(what: FollowingEntity!, slug: String!): Result!
|
unfollow(what: FollowingEntity!, slug: String!): Result!
|
||||||
|
|
||||||
# seen
|
|
||||||
incrementView(shout: String!): Result!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input MessagesBy {
|
||||||
|
author: String
|
||||||
|
body: String
|
||||||
|
chat: String
|
||||||
|
days: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
input AuthorsBy {
|
||||||
|
lastSeen: DateTime
|
||||||
|
createdAt: DateTime
|
||||||
|
stat: String
|
||||||
|
slug: String
|
||||||
|
name: String
|
||||||
|
topic: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input ShoutsBy {
|
||||||
|
slug: String
|
||||||
|
title: String
|
||||||
|
body: String
|
||||||
|
topic: String
|
||||||
|
author: String
|
||||||
|
days: Int
|
||||||
|
layout: String
|
||||||
|
published: Boolean
|
||||||
|
visibility: String
|
||||||
|
stat: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input ReactionBy {
|
||||||
|
shout: String
|
||||||
|
body: String
|
||||||
|
topic: String
|
||||||
|
author: String
|
||||||
|
days: Int
|
||||||
|
stat: String
|
||||||
|
}
|
||||||
################################### Query
|
################################### Query
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
# inbox
|
# inbox
|
||||||
loadChats(offset: Int, amount: Int): Result!
|
loadChats(offset: Int, amount: Int): Result! # your chats
|
||||||
loadMessages(chatId: String!, offset: Int, amount: Int): Result!
|
loadMessagesBy(by: MessagesBy!, amount: Int, offset: Int): Result!
|
||||||
searchUsers(q: String!, offset: Int, amount: Int): Result!
|
searchUsers(query: String!, amount: Int, offset: Int): Result!
|
||||||
searchChats(q: String!, offset: Int, amount: Int): Result!
|
|
||||||
searchMessages(q: String!, offset: Int, amount: Int): Result!
|
|
||||||
|
|
||||||
# auth
|
# auth
|
||||||
isEmailUsed(email: String!): Boolean!
|
isEmailUsed(email: String!): Boolean!
|
||||||
signIn(email: String!, password: String, lang: String): AuthResult!
|
signIn(email: String!, password: String, lang: String): AuthResult!
|
||||||
signOut: AuthResult!
|
signOut: AuthResult!
|
||||||
|
|
||||||
# profile
|
# zine
|
||||||
getUsersBySlugs(slugs: [String]!): [Author]!
|
loadAuthorsBy(by: AuthorsBy, amount: Int, offset: Int): [Author]!
|
||||||
|
loadShoutsBy(by: ShoutsBy, amount: Int, offset: Int): [Shout]!
|
||||||
|
loadReactionsBy(by: ReactionBy!, amount: Int, limit: Int): [Reaction]!
|
||||||
userFollowers(slug: String!): [Author]!
|
userFollowers(slug: String!): [Author]!
|
||||||
userFollowedAuthors(slug: String!): [Author]!
|
userFollowedAuthors(slug: String!): [Author]!
|
||||||
userFollowedTopics(slug: String!): [Topic]!
|
userFollowedTopics(slug: String!): [Topic]!
|
||||||
userFollowedCommunities(slug: String!): [Community]!
|
|
||||||
userReactedShouts(slug: String!): [Shout]! # test
|
|
||||||
getUserRoles(slug: String!): [Role]!
|
|
||||||
authorsAll: [Author]!
|
authorsAll: [Author]!
|
||||||
getAuthor(slug: String!): User!
|
getAuthor(slug: String!): User!
|
||||||
|
|
||||||
# shouts
|
|
||||||
getShoutBySlug(slug: String!): Shout!
|
|
||||||
shoutsForFeed(offset: Int!, limit: Int!): [Shout]! # test
|
|
||||||
shoutsByLayout(layout: String, amount: Int!, offset: Int!): [Shout]!
|
|
||||||
shoutsByTopics(slugs: [String]!, offset: Int!, limit: Int!): [Shout]!
|
|
||||||
shoutsByAuthors(slugs: [String]!, offset: Int!, limit: Int!): [Shout]!
|
|
||||||
shoutsByCommunities(slugs: [String]!, offset: Int!, limit: Int!): [Shout]!
|
|
||||||
# topReacted(offset: Int!, limit: Int!): [Shout]!
|
|
||||||
topAuthors(offset: Int!, limit: Int!): [Author]! # by User.rating
|
|
||||||
topPublished(daysago: Int!, offset: Int!, limit: Int!): [Shout]!
|
|
||||||
topMonth(offset: Int!, limit: Int!): [Shout]! # TODO: implement topPublishedAfter(day, offset, limit)
|
|
||||||
topOverall(offset: Int!, limit: Int!): [Shout]!
|
|
||||||
topCommented(offset: Int!, limit: Int!): [Shout]!
|
|
||||||
recentPublished(offset: Int!, limit: Int!): [Shout]! # homepage
|
|
||||||
recentReacted(offset: Int!, limit: Int!): [Shout]! # TODO: use in design!
|
|
||||||
recentCommented(offset: Int!, limit: Int!): [Shout]!
|
|
||||||
recentAll(offset: Int!, limit: Int!): [Shout]!
|
|
||||||
recentCandidates(offset: Int!, limit: Int!): [Shout]!
|
|
||||||
|
|
||||||
# expo
|
|
||||||
topMonthLayoutShouts(layout: String!, amount: Int, offset: Int): [Shout]!
|
|
||||||
topLayoutShouts(layout: String!, amount: Int, offset: Int): [Shout]!
|
|
||||||
recentLayoutShouts(layout: String!, amount: Int, offset: Int): [Shout]!
|
|
||||||
|
|
||||||
# reactons
|
|
||||||
reactionsByAuthor(slug: String!, offset: Int!, limit: Int!): [Reaction]!
|
|
||||||
reactionsForShouts(shouts: [String]!, offset: Int!, limit: Int!): [Reaction]!
|
|
||||||
|
|
||||||
# collab
|
# collab
|
||||||
getCollabs: [Collab]!
|
getCollabs: [Collab]!
|
||||||
|
|
||||||
|
@ -283,18 +265,6 @@ type Query {
|
||||||
topicsRandom(amount: Int): [Topic]!
|
topicsRandom(amount: Int): [Topic]!
|
||||||
topicsByCommunity(community: String!): [Topic]!
|
topicsByCommunity(community: String!): [Topic]!
|
||||||
topicsByAuthor(author: String!): [Topic]!
|
topicsByAuthor(author: String!): [Topic]!
|
||||||
|
|
||||||
# collections
|
|
||||||
collectionsAll: [Collection]!
|
|
||||||
getUserCollections(author: String!): [Collection]!
|
|
||||||
shoutsByCollection(collection: String!, offset: Int!, limit: Int!): [Shout]!
|
|
||||||
|
|
||||||
# communities
|
|
||||||
getCommunity(slug: String): Community!
|
|
||||||
getCommunities: [Community]! # all
|
|
||||||
|
|
||||||
# search
|
|
||||||
searchQuery(q: String, offset: Int!, limit: Int!): [Shout]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
############################################ Subscription
|
############################################ Subscription
|
||||||
|
|
|
@ -32,6 +32,16 @@ class ReactedStorage:
|
||||||
lock = asyncio.Lock()
|
lock = asyncio.Lock()
|
||||||
modified_shouts = set([])
|
modified_shouts = set([])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_shout_stat(slug):
|
||||||
|
return {
|
||||||
|
# TODO: use ackee as datasource
|
||||||
|
"viewed": 0, # await ViewedStorage.get_shout(slug),
|
||||||
|
"reacted": len(await ReactedStorage.get_shout(slug)),
|
||||||
|
"commented": len(await ReactedStorage.get_comments(slug)),
|
||||||
|
"rating": await ReactedStorage.get_rating(slug),
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_shout(shout_slug):
|
async def get_shout(shout_slug):
|
||||||
self = ReactedStorage
|
self = ReactedStorage
|
||||||
|
|
|
@ -1,285 +0,0 @@
|
||||||
import asyncio
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
from sqlalchemy import and_, desc, func, select
|
|
||||||
from sqlalchemy.orm import selectinload
|
|
||||||
|
|
||||||
from base.orm import local_session
|
|
||||||
from orm.reaction import Reaction, ReactionKind
|
|
||||||
from orm.shout import Shout
|
|
||||||
from services.stat.reacted import ReactedStorage
|
|
||||||
|
|
||||||
|
|
||||||
async def get_shout_stat(slug):
|
|
||||||
return {
|
|
||||||
# TODO: use ackee as datasource
|
|
||||||
"viewed": 0, # await ViewedStorage.get_shout(slug),
|
|
||||||
"reacted": len(await ReactedStorage.get_shout(slug)),
|
|
||||||
"commented": len(await ReactedStorage.get_comments(slug)),
|
|
||||||
"rating": await ReactedStorage.get_rating(slug),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def prepare_shouts(session, stmt):
|
|
||||||
shouts = []
|
|
||||||
print(stmt)
|
|
||||||
for s in list(map(lambda r: r.Shout, session.execute(stmt))):
|
|
||||||
s.stat = await get_shout_stat(s.slug)
|
|
||||||
shouts.append(s)
|
|
||||||
return shouts
|
|
||||||
|
|
||||||
|
|
||||||
LAYOUTS = ['audio', 'video', 'image', 'literature']
|
|
||||||
|
|
||||||
|
|
||||||
class ShoutsCache:
|
|
||||||
# limit = 200
|
|
||||||
period = 60 * 60 # 1 hour
|
|
||||||
lock = asyncio.Lock()
|
|
||||||
|
|
||||||
recent_published = []
|
|
||||||
recent_all = []
|
|
||||||
recent_reacted = []
|
|
||||||
recent_commented = []
|
|
||||||
top_month = []
|
|
||||||
top_overall = []
|
|
||||||
top_commented = []
|
|
||||||
|
|
||||||
by_author = {}
|
|
||||||
by_topic = {}
|
|
||||||
by_layout = {}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def prepare_recent_published():
|
|
||||||
with local_session() as session:
|
|
||||||
shouts = await prepare_shouts(
|
|
||||||
session,
|
|
||||||
(
|
|
||||||
select(Shout)
|
|
||||||
.options(
|
|
||||||
selectinload(Shout.authors),
|
|
||||||
selectinload(Shout.topics)
|
|
||||||
)
|
|
||||||
.where(Shout.deletedAt.is_(None))
|
|
||||||
.filter(Shout.publishedAt.is_not(None))
|
|
||||||
.group_by(Shout.id)
|
|
||||||
.order_by(desc("publishedAt"))
|
|
||||||
# .limit(ShoutsCache.limit)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
for s in shouts:
|
|
||||||
for a in s.authors:
|
|
||||||
ShoutsCache.by_author[a.slug] = ShoutsCache.by_author.get(a.slug, {})
|
|
||||||
ShoutsCache.by_author[a.slug][s.slug] = s
|
|
||||||
for t in s.topics:
|
|
||||||
ShoutsCache.by_topic[t.slug] = ShoutsCache.by_topic.get(t.slug, {})
|
|
||||||
ShoutsCache.by_topic[t.slug][s.slug] = s
|
|
||||||
if s.layout in LAYOUTS:
|
|
||||||
ShoutsCache.by_layout[s.layout] = ShoutsCache.by_layout.get(s.layout, [])
|
|
||||||
ShoutsCache.by_layout[s.layout].append(s)
|
|
||||||
print("[zine.cache] indexed by %d topics " % len(ShoutsCache.by_topic.keys()))
|
|
||||||
print("[zine.cache] indexed by %d authors " % len(ShoutsCache.by_author.keys()))
|
|
||||||
print("[zine.cache] indexed by %d layouts " % len(ShoutsCache.by_layout.keys()))
|
|
||||||
ShoutsCache.recent_published = shouts
|
|
||||||
print("[zine.cache] %d recently published shouts " % len(shouts))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def prepare_recent_all():
|
|
||||||
with local_session() as session:
|
|
||||||
shouts = await prepare_shouts(
|
|
||||||
session,
|
|
||||||
(
|
|
||||||
select(Shout)
|
|
||||||
.options(
|
|
||||||
selectinload(Shout.authors),
|
|
||||||
selectinload(Shout.topics)
|
|
||||||
)
|
|
||||||
.where(Shout.deletedAt.is_(None))
|
|
||||||
.group_by(Shout.id)
|
|
||||||
.order_by(desc("createdAt"))
|
|
||||||
# .limit(ShoutsCache.limit)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
ShoutsCache.recent_all = shouts
|
|
||||||
print("[zine.cache] %d recently created shouts " % len(ShoutsCache.recent_all))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def prepare_recent_reacted():
|
|
||||||
with local_session() as session:
|
|
||||||
reactions = session.query(Reaction).order_by(Reaction.createdAt).all()
|
|
||||||
# .limit(ShoutsCache.limit)
|
|
||||||
reacted_slugs = set([])
|
|
||||||
for r in reactions:
|
|
||||||
reacted_slugs.add(r.shout)
|
|
||||||
shouts = await prepare_shouts(
|
|
||||||
session,
|
|
||||||
(
|
|
||||||
select(
|
|
||||||
Shout,
|
|
||||||
Reaction.createdAt.label('reactedAt')
|
|
||||||
)
|
|
||||||
.options(
|
|
||||||
selectinload(Shout.authors),
|
|
||||||
selectinload(Shout.topics),
|
|
||||||
selectinload(Shout.reactions),
|
|
||||||
)
|
|
||||||
.join(Reaction)
|
|
||||||
.where(and_(Shout.deletedAt.is_(None), Shout.slug.in_(reacted_slugs)))
|
|
||||||
.filter(Shout.publishedAt.is_not(None))
|
|
||||||
.group_by(Shout.id, "reactedAt")
|
|
||||||
.order_by(desc("reactedAt"))
|
|
||||||
# .limit(ShoutsCache.limit)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
ShoutsCache.recent_reacted = shouts
|
|
||||||
print("[zine.cache] %d recently reacted shouts " % len(shouts))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def prepare_recent_commented():
|
|
||||||
with local_session() as session:
|
|
||||||
reactions = session.query(Reaction).order_by(Reaction.createdAt).all()
|
|
||||||
# .limit(ShoutsCache.limit)
|
|
||||||
commented_slugs = set([])
|
|
||||||
for r in reactions:
|
|
||||||
if r.body and len(r.body) > 0:
|
|
||||||
commented_slugs.add(r.shout)
|
|
||||||
shouts = await prepare_shouts(
|
|
||||||
session,
|
|
||||||
(
|
|
||||||
select(
|
|
||||||
Shout,
|
|
||||||
Reaction.createdAt.label('reactedAt')
|
|
||||||
)
|
|
||||||
.options(
|
|
||||||
selectinload(Shout.authors),
|
|
||||||
selectinload(Shout.topics),
|
|
||||||
selectinload(Shout.reactions),
|
|
||||||
)
|
|
||||||
.join(Reaction)
|
|
||||||
.where(and_(Shout.deletedAt.is_(None), Shout.slug.in_(commented_slugs)))
|
|
||||||
.group_by(Shout.id, "reactedAt")
|
|
||||||
.order_by(desc("reactedAt"))
|
|
||||||
# .limit(ShoutsCache.limit)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
ShoutsCache.recent_commented = shouts
|
|
||||||
print("[zine.cache] %d recently commented shouts " % len(shouts))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def prepare_top_overall():
|
|
||||||
with local_session() as session:
|
|
||||||
shouts = await prepare_shouts(
|
|
||||||
session,
|
|
||||||
(
|
|
||||||
select(
|
|
||||||
Shout,
|
|
||||||
func.sum(Reaction.id).label('reacted')
|
|
||||||
)
|
|
||||||
.options(
|
|
||||||
selectinload(Shout.authors),
|
|
||||||
selectinload(Shout.topics),
|
|
||||||
selectinload(Shout.reactions),
|
|
||||||
)
|
|
||||||
.join(Reaction, Reaction.kind == ReactionKind.LIKE)
|
|
||||||
.where(Shout.deletedAt.is_(None))
|
|
||||||
.filter(Shout.publishedAt.is_not(None))
|
|
||||||
.group_by(Shout.id)
|
|
||||||
.order_by(desc("reacted"))
|
|
||||||
# .limit(ShoutsCache.limit)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
shouts.sort(key=lambda s: s.stat["rating"], reverse=True)
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
print("[zine.cache] %d top rated published " % len(shouts))
|
|
||||||
ShoutsCache.top_overall = shouts
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def prepare_top_month():
|
|
||||||
month_ago = datetime.now() - timedelta(days=30)
|
|
||||||
with local_session() as session:
|
|
||||||
shouts = await prepare_shouts(
|
|
||||||
session,
|
|
||||||
(
|
|
||||||
select(Shout)
|
|
||||||
.options(
|
|
||||||
selectinload(Shout.authors),
|
|
||||||
selectinload(Shout.topics),
|
|
||||||
selectinload(Shout.reactions),
|
|
||||||
)
|
|
||||||
.join(Reaction)
|
|
||||||
.where(Shout.deletedAt.is_(None))
|
|
||||||
.filter(Shout.publishedAt > month_ago)
|
|
||||||
.group_by(Shout.id)
|
|
||||||
# .limit(ShoutsCache.limit)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
shouts.sort(key=lambda s: s.stat["rating"], reverse=True)
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
ShoutsCache.top_month = shouts
|
|
||||||
print("[zine.cache] %d top month published " % len(ShoutsCache.top_month))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def prepare_top_commented():
|
|
||||||
month_ago = datetime.now() - timedelta(days=30)
|
|
||||||
with local_session() as session:
|
|
||||||
shouts = await prepare_shouts(
|
|
||||||
session,
|
|
||||||
(
|
|
||||||
select(
|
|
||||||
Shout,
|
|
||||||
func.sum(Reaction.id).label("commented")
|
|
||||||
)
|
|
||||||
.options(
|
|
||||||
selectinload(Shout.authors),
|
|
||||||
selectinload(Shout.topics),
|
|
||||||
selectinload(Shout.reactions)
|
|
||||||
)
|
|
||||||
.join(Reaction, func.length(Reaction.body) > 0)
|
|
||||||
.where(Shout.deletedAt.is_(None))
|
|
||||||
.filter(Shout.publishedAt > month_ago)
|
|
||||||
.group_by(Shout.id)
|
|
||||||
.order_by(desc("commented"))
|
|
||||||
# .limit(ShoutsCache.limit)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
shouts.sort(key=lambda s: s.stat["commented"], reverse=True)
|
|
||||||
async with ShoutsCache.lock:
|
|
||||||
ShoutsCache.top_commented = shouts
|
|
||||||
print("[zine.cache] %d last month top commented shouts " % len(ShoutsCache.top_commented))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def get_top_published_before(daysago, offset, limit):
|
|
||||||
shouts_by_rating = []
|
|
||||||
before = datetime.now() - timedelta(days=daysago)
|
|
||||||
for s in ShoutsCache.recent_published:
|
|
||||||
if s.publishedAt >= before:
|
|
||||||
shouts_by_rating.append(s)
|
|
||||||
shouts_by_rating.sort(lambda s: s.stat["rating"], reverse=True)
|
|
||||||
return shouts_by_rating
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def get_all_authors_slugs():
|
|
||||||
slugs = ShoutsCache.by_author.keys()
|
|
||||||
return slugs
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def worker():
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
await ShoutsCache.prepare_top_month()
|
|
||||||
await ShoutsCache.prepare_top_overall()
|
|
||||||
await ShoutsCache.prepare_top_commented()
|
|
||||||
|
|
||||||
await ShoutsCache.prepare_recent_published()
|
|
||||||
await ShoutsCache.prepare_recent_all()
|
|
||||||
await ShoutsCache.prepare_recent_reacted()
|
|
||||||
await ShoutsCache.prepare_recent_commented()
|
|
||||||
print("[zine.cache] periodical update")
|
|
||||||
except Exception as err:
|
|
||||||
print("[zine.cache] error: %s" % (err))
|
|
||||||
raise err
|
|
||||||
await asyncio.sleep(ShoutsCache.period)
|
|
Loading…
Reference in New Issue
Block a user