Revert "Feature/lint"

This commit is contained in:
Kosta
2023-10-27 00:07:35 +03:00
committed by GitHub
parent 05136699ee
commit b142949805
70 changed files with 1465 additions and 1223 deletions

67
resolvers/__init__.py Normal file
View File

@@ -0,0 +1,67 @@
from resolvers.auth import (
login,
sign_out,
is_email_used,
register_by_email,
confirm_email,
auth_send_link,
get_current_user,
)
from resolvers.create.migrate import markdown_body
from resolvers.create.editor import create_shout, delete_shout, update_shout
from resolvers.zine.profile import (
load_authors_by,
rate_user,
update_profile,
get_authors_all
)
from resolvers.zine.reactions import (
create_reaction,
delete_reaction,
update_reaction,
reactions_unfollow,
reactions_follow,
load_reactions_by
)
from resolvers.zine.topics import (
topic_follow,
topic_unfollow,
topics_by_author,
topics_by_community,
topics_all,
get_topic
)
from resolvers.zine.following import (
follow,
unfollow
)
from resolvers.zine.load import (
load_shout,
load_shouts_by
)
from resolvers.inbox.chats import (
create_chat,
delete_chat,
update_chat
)
from resolvers.inbox.messages import (
create_message,
delete_message,
update_message,
mark_as_read
)
from resolvers.inbox.load import (
load_chats,
load_messages_by,
load_recipients
)
from resolvers.inbox.search import search_recipients
from resolvers.notifications import load_notifications

View File

@@ -1,29 +1,24 @@
# -*- coding: utf-8 -*-
from datetime import datetime, timezone
from urllib.parse import quote_plus
from graphql.type import GraphQLResolveInfo
from starlette.responses import RedirectResponse
from transliterate import translit
import re
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from auth.email import send_auth_email
from auth.identity import Identity, Password
from auth.jwtcodec import JWTCodec
from auth.tokenstorage import TokenStorage
from base.exceptions import (
BaseHttpException,
InvalidPassword,
InvalidToken,
ObjectNotExist,
Unauthorized,
)
from base.exceptions import (BaseHttpException, InvalidPassword, InvalidToken,
ObjectNotExist, Unauthorized)
from base.orm import local_session
from base.resolvers import mutation, query
from datetime import datetime, timezone
from graphql.type import GraphQLResolveInfo
from orm import Role, User
from settings import FRONTEND_URL, SESSION_TOKEN_HEADER
from starlette.responses import RedirectResponse
from transliterate import translit
from urllib.parse import quote_plus
import re
from settings import SESSION_TOKEN_HEADER, FRONTEND_URL
@mutation.field("getSession")
@@ -37,14 +32,17 @@ async def get_current_user(_, info):
user.lastSeen = datetime.now(tz=timezone.utc)
session.commit()
return {"token": token, "user": user}
return {
"token": token,
"user": user
}
@mutation.field("confirmEmail")
async def confirm_email(_, info, token):
"""confirm owning email address"""
try:
print("[resolvers.auth] confirm email by token")
print('[resolvers.auth] confirm email by token')
payload = JWTCodec.decode(token)
user_id = payload.user_id
await TokenStorage.get(f"{user_id}-{payload.username}-{token}")
@@ -55,7 +53,10 @@ async def confirm_email(_, info, token):
user.lastSeen = datetime.now(tz=timezone.utc)
session.add(user)
session.commit()
return {"token": session_token, "user": user}
return {
"token": session_token,
"user": user
}
except InvalidToken as e:
raise InvalidToken(e.message)
except Exception as e:
@@ -67,9 +68,9 @@ async def confirm_email_handler(request):
token = request.path_params["token"] # one time
request.session["token"] = token
res = await confirm_email(None, {}, token)
print("[resolvers.auth] confirm_email request: %r" % request)
print('[resolvers.auth] confirm_email request: %r' % request)
if "error" in res:
raise BaseHttpException(res["error"])
raise BaseHttpException(res['error'])
else:
response = RedirectResponse(url=FRONTEND_URL)
response.set_cookie("token", res["token"]) # session token
@@ -86,22 +87,22 @@ def create_user(user_dict):
def generate_unique_slug(src):
print("[resolvers.auth] generating slug from: " + src)
print('[resolvers.auth] generating slug from: ' + src)
slug = translit(src, "ru", reversed=True).replace(".", "-").lower()
slug = re.sub("[^0-9a-zA-Z]+", "-", slug)
slug = re.sub('[^0-9a-zA-Z]+', '-', slug)
if slug != src:
print("[resolvers.auth] translited name: " + slug)
print('[resolvers.auth] translited name: ' + slug)
c = 1
with local_session() as session:
user = session.query(User).where(User.slug == slug).first()
while user:
user = session.query(User).where(User.slug == slug).first()
slug = slug + "-" + str(c)
slug = slug + '-' + str(c)
c += 1
if not user:
unique_slug = slug
print("[resolvers.auth] " + unique_slug)
return quote_plus(unique_slug.replace("'", "")).replace("+", "-")
print('[resolvers.auth] ' + unique_slug)
return quote_plus(unique_slug.replace('\'', '')).replace('+', '-')
@mutation.field("registerUser")
@@ -116,12 +117,12 @@ async def register_by_email(_, _info, email: str, password: str = "", name: str
slug = generate_unique_slug(name)
user = session.query(User).where(User.slug == slug).first()
if user:
slug = generate_unique_slug(email.split("@")[0])
slug = generate_unique_slug(email.split('@')[0])
user_dict = {
"email": email,
"username": email, # will be used to store phone number or some messenger network id
"name": name,
"slug": slug,
"slug": slug
}
if password:
user_dict["password"] = Password.encode(password)
@@ -171,7 +172,10 @@ async def login(_, info, email: str, password: str = "", lang: str = "ru"):
user = Identity.password(orm_user, password)
session_token = await TokenStorage.create_session(user)
print(f"[auth] user {email} authorized")
return {"token": session_token, "user": user}
return {
"token": session_token,
"user": user
}
except InvalidPassword:
print(f"[auth] {email}: invalid password")
raise InvalidPassword("invalid password") # contains webserver status

View File

@@ -1,13 +1,15 @@
from datetime import datetime, timezone
from sqlalchemy import and_
from sqlalchemy.orm import joinedload
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.orm import local_session
from base.resolvers import mutation
from datetime import datetime, timezone
from orm.shout import Shout, ShoutAuthor, ShoutTopic
from orm.topic import Topic
from resolvers.zine.reactions import reactions_follow, reactions_unfollow
from sqlalchemy import and_
from sqlalchemy.orm import joinedload
@mutation.field("createShout")
@@ -16,23 +18,21 @@ async def create_shout(_, info, inp):
auth: AuthCredentials = info.context["request"].auth
with local_session() as session:
topics = session.query(Topic).filter(Topic.slug.in_(inp.get("topics", []))).all()
topics = session.query(Topic).filter(Topic.slug.in_(inp.get('topics', []))).all()
new_shout = Shout.create(
**{
"title": inp.get("title"),
"subtitle": inp.get("subtitle"),
"lead": inp.get("lead"),
"description": inp.get("description"),
"body": inp.get("body", ""),
"layout": inp.get("layout"),
"authors": inp.get("authors", []),
"slug": inp.get("slug"),
"mainTopic": inp.get("mainTopic"),
"visibility": "owner",
"createdBy": auth.user_id,
}
)
new_shout = Shout.create(**{
"title": inp.get("title"),
"subtitle": inp.get('subtitle'),
"lead": inp.get('lead'),
"description": inp.get('description'),
"body": inp.get("body", ''),
"layout": inp.get("layout"),
"authors": inp.get("authors", []),
"slug": inp.get("slug"),
"mainTopic": inp.get("mainTopic"),
"visibility": "owner",
"createdBy": auth.user_id
})
for topic in topics:
t = ShoutTopic.create(topic=topic.id, shout=new_shout.id)
@@ -64,15 +64,10 @@ async def update_shout(_, info, shout_id, shout_input=None, publish=False):
auth: AuthCredentials = info.context["request"].auth
with local_session() as session:
shout = (
session.query(Shout)
.options(
joinedload(Shout.authors),
joinedload(Shout.topics),
)
.filter(Shout.id == shout_id)
.first()
)
shout = session.query(Shout).options(
joinedload(Shout.authors),
joinedload(Shout.topics),
).filter(Shout.id == shout_id).first()
if not shout:
return {"error": "shout not found"}
@@ -99,36 +94,24 @@ async def update_shout(_, info, shout_id, shout_input=None, publish=False):
session.commit()
for new_topic_to_link in new_topics_to_link:
created_unlinked_topic = ShoutTopic.create(
shout=shout.id, topic=new_topic_to_link.id
)
created_unlinked_topic = ShoutTopic.create(shout=shout.id, topic=new_topic_to_link.id)
session.add(created_unlinked_topic)
existing_topics_input = [
topic_input for topic_input in topics_input if topic_input.get("id", 0) > 0
]
existing_topic_to_link_ids = [
existing_topic_input["id"]
for existing_topic_input in existing_topics_input
if existing_topic_input["id"] not in [topic.id for topic in shout.topics]
]
existing_topics_input = [topic_input for topic_input in topics_input if topic_input.get("id", 0) > 0]
existing_topic_to_link_ids = [existing_topic_input["id"] for existing_topic_input in existing_topics_input
if existing_topic_input["id"] not in [topic.id for topic in shout.topics]]
for existing_topic_to_link_id in existing_topic_to_link_ids:
created_unlinked_topic = ShoutTopic.create(
shout=shout.id, topic=existing_topic_to_link_id
)
created_unlinked_topic = ShoutTopic.create(shout=shout.id, topic=existing_topic_to_link_id)
session.add(created_unlinked_topic)
topic_to_unlink_ids = [
topic.id
for topic in shout.topics
if topic.id not in [topic_input["id"] for topic_input in existing_topics_input]
]
topic_to_unlink_ids = [topic.id for topic in shout.topics
if topic.id not in [topic_input["id"] for topic_input in existing_topics_input]]
shout_topics_to_remove = session.query(ShoutTopic).filter(
and_(
ShoutTopic.shout == shout.id,
ShoutTopic.topic.in_(topic_to_unlink_ids),
ShoutTopic.topic.in_(topic_to_unlink_ids)
)
)
@@ -137,13 +120,13 @@ async def update_shout(_, info, shout_id, shout_input=None, publish=False):
shout_input["mainTopic"] = shout_input["mainTopic"]["slug"]
if shout_input["mainTopic"] == "":
if shout_input["mainTopic"] == '':
del shout_input["mainTopic"]
shout.update(shout_input)
updated = True
if publish and shout.visibility == "owner":
if publish and shout.visibility == 'owner':
shout.visibility = "community"
shout.publishedAt = datetime.now(tz=timezone.utc)
updated = True

View File

@@ -1,10 +1,11 @@
# from base.resolvers import query
# from migration.extract import extract_md
# from resolvers.auth import login_required
#
#
# @login_required
# @query.field("markdownBody")
# def markdown_body(_, info, body: str):
# body = extract_md(body)
# return body
from base.resolvers import query
from resolvers.auth import login_required
from migration.extract import extract_md
@login_required
@query.field("markdownBody")
def markdown_body(_, info, body: str):
body = extract_md(body)
return body

View File

@@ -1,13 +1,13 @@
import json
import uuid
from datetime import datetime, timezone
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.redis import redis
from base.resolvers import mutation
from datetime import datetime, timezone
from validations.inbox import Chat
import json
import uuid
@mutation.field("updateChat")
@login_required
@@ -24,24 +24,27 @@ async def update_chat(_, info, chat_new: Chat):
chat_id = chat_new["id"]
chat = await redis.execute("GET", f"chats/{chat_id}")
if not chat:
return {"error": "chat not exist"}
return {
"error": "chat not exist"
}
chat = dict(json.loads(chat))
# TODO
if auth.user_id in chat["admins"]:
chat.update(
{
"title": chat_new.get("title", chat["title"]),
"description": chat_new.get("description", chat["description"]),
"updatedAt": int(datetime.now(tz=timezone.utc).timestamp()),
"admins": chat_new.get("admins", chat.get("admins") or []),
"users": chat_new.get("users", chat["users"]),
}
)
chat.update({
"title": chat_new.get("title", chat["title"]),
"description": chat_new.get("description", chat["description"]),
"updatedAt": int(datetime.now(tz=timezone.utc).timestamp()),
"admins": chat_new.get("admins", chat.get("admins") or []),
"users": chat_new.get("users", chat["users"])
})
await redis.execute("SET", f"chats/{chat.id}", json.dumps(chat))
await redis.execute("COMMIT")
return {"error": None, "chat": chat}
return {
"error": None,
"chat": chat
}
@mutation.field("createChat")
@@ -49,7 +52,7 @@ async def update_chat(_, info, chat_new: Chat):
async def create_chat(_, info, title="", members=[]):
auth: AuthCredentials = info.context["request"].auth
chat = {}
print("create_chat members: %r" % members)
print('create_chat members: %r' % members)
if auth.user_id not in members:
members.append(int(auth.user_id))
@@ -71,12 +74,15 @@ async def create_chat(_, info, title="", members=[]):
chat = await redis.execute("GET", f"chats/{c.decode('utf-8')}")
if chat:
chat = json.loads(chat)
if chat["title"] == "":
print("[inbox] createChat found old chat")
if chat['title'] == "":
print('[inbox] createChat found old chat')
print(chat)
break
if chat:
return {"chat": chat, "error": "existed"}
return {
"chat": chat,
"error": "existed"
}
chat_id = str(uuid.uuid4())
chat = {
@@ -86,7 +92,7 @@ async def create_chat(_, info, title="", members=[]):
"createdBy": auth.user_id,
"createdAt": int(datetime.now(tz=timezone.utc).timestamp()),
"updatedAt": int(datetime.now(tz=timezone.utc).timestamp()),
"admins": members if (len(members) == 2 and title == "") else [],
"admins": members if (len(members) == 2 and title == "") else []
}
for m in members:
@@ -94,7 +100,10 @@ async def create_chat(_, info, title="", members=[]):
await redis.execute("SET", f"chats/{chat_id}", json.dumps(chat))
await redis.execute("SET", f"chats/{chat_id}/next_message_id", str(0))
await redis.execute("COMMIT")
return {"error": None, "chat": chat}
return {
"error": None,
"chat": chat
}
@mutation.field("deleteChat")
@@ -105,9 +114,11 @@ async def delete_chat(_, info, chat_id: str):
chat = await redis.execute("GET", f"/chats/{chat_id}")
if chat:
chat = dict(json.loads(chat))
if auth.user_id in chat["admins"]:
if auth.user_id in chat['admins']:
await redis.execute("DEL", f"chats/{chat_id}")
await redis.execute("SREM", "chats_by_user/" + str(auth.user_id), chat_id)
await redis.execute("COMMIT")
else:
return {"error": "chat not exist"}
return {
"error": "chat not exist"
}

View File

@@ -1,26 +1,28 @@
from .unread import get_unread_counter
import json
# from datetime import datetime, timedelta, timezone
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.orm import local_session
from base.redis import redis
from base.orm import local_session
from base.resolvers import query
from orm.user import User
from resolvers.zine.profile import followed_authors
import json
# from datetime import datetime, timedelta, timezone
from .unread import get_unread_counter
async def load_messages(chat_id: str, limit: int = 5, offset: int = 0, ids=[]):
"""load :limit messages for :chat_id with :offset"""
''' load :limit messages for :chat_id with :offset '''
messages = []
message_ids = []
if ids:
message_ids += ids
try:
if limit:
mids = await redis.lrange(f"chats/{chat_id}/message_ids", offset, offset + limit)
mids = await redis.lrange(f"chats/{chat_id}/message_ids",
offset,
offset + limit
)
mids = [mid.decode("utf-8") for mid in mids]
message_ids += mids
except Exception as e:
@@ -28,10 +30,10 @@ async def load_messages(chat_id: str, limit: int = 5, offset: int = 0, ids=[]):
if message_ids:
message_keys = [f"chats/{chat_id}/messages/{mid}" for mid in message_ids]
messages = await redis.mget(*message_keys)
messages = [json.loads(msg.decode("utf-8")) for msg in messages]
messages = [json.loads(msg.decode('utf-8')) for msg in messages]
replies = []
for m in messages:
rt = m.get("replyTo")
rt = m.get('replyTo')
if rt:
rt = int(rt)
if rt not in message_ids:
@@ -44,14 +46,14 @@ async def load_messages(chat_id: str, limit: int = 5, offset: int = 0, ids=[]):
@query.field("loadChats")
@login_required
async def load_chats(_, info, limit: int = 50, offset: int = 0):
"""load :limit chats of current user with :offset"""
""" load :limit chats of current user with :offset """
auth: AuthCredentials = info.context["request"].auth
cids = await redis.execute("SMEMBERS", "chats_by_user/" + str(auth.user_id))
if cids:
cids = list(cids)[offset : offset + limit]
cids = list(cids)[offset:offset + limit]
if not cids:
print("[inbox.load] no chats were found")
print('[inbox.load] no chats were found')
cids = []
onliners = await redis.execute("SMEMBERS", "users-online")
if not onliners:
@@ -62,53 +64,62 @@ async def load_chats(_, info, limit: int = 50, offset: int = 0):
c = await redis.execute("GET", "chats/" + cid)
if c:
c = dict(json.loads(c))
c["messages"] = await load_messages(cid, 5, 0)
c["unread"] = await get_unread_counter(cid, auth.user_id)
c['messages'] = await load_messages(cid, 5, 0)
c['unread'] = await get_unread_counter(cid, auth.user_id)
with local_session() as session:
c["members"] = []
c['members'] = []
for uid in c["users"]:
a = session.query(User).where(User.id == uid).first()
if a:
c["members"].append(
{
"id": a.id,
"slug": a.slug,
"userpic": a.userpic,
"name": a.name,
"lastSeen": a.lastSeen,
"online": a.id in onliners,
}
)
c['members'].append({
"id": a.id,
"slug": a.slug,
"userpic": a.userpic,
"name": a.name,
"lastSeen": a.lastSeen,
"online": a.id in onliners
})
chats.append(c)
return {"chats": chats, "error": None}
return {
"chats": chats,
"error": None
}
@query.field("loadMessagesBy")
@login_required
async def load_messages_by(_, info, by, limit: int = 10, offset: int = 0):
"""load :limit messages of :chat_id with :offset"""
''' load :limit messages of :chat_id with :offset '''
auth: AuthCredentials = info.context["request"].auth
userchats = await redis.execute("SMEMBERS", "chats_by_user/" + str(auth.user_id))
userchats = [c.decode("utf-8") for c in userchats]
userchats = [c.decode('utf-8') for c in userchats]
# print('[inbox] userchats: %r' % userchats)
if userchats:
# print('[inbox] loading messages by...')
messages = []
by_chat = by.get("chat")
by_chat = by.get('chat')
if by_chat in userchats:
chat = await redis.execute("GET", f"chats/{by_chat}")
# print(chat)
if not chat:
return {"messages": [], "error": "chat not exist"}
return {
"messages": [],
"error": "chat not exist"
}
# everyone's messages in filtered chat
messages = await load_messages(by_chat, limit, offset)
return {
"messages": sorted(list(messages), key=lambda m: m["createdAt"]),
"error": None,
"messages": sorted(
list(messages),
key=lambda m: m['createdAt']
),
"error": None
}
else:
return {"error": "Cannot access messages of this chat"}
return {
"error": "Cannot access messages of this chat"
}
@query.field("loadRecipients")
@@ -127,14 +138,15 @@ async def load_recipients(_, info, limit=50, offset=0):
chat_users += session.query(User).where(User.emailConfirmed).limit(limit).offset(offset)
members = []
for a in chat_users:
members.append(
{
"id": a.id,
"slug": a.slug,
"userpic": a.userpic,
"name": a.name,
"lastSeen": a.lastSeen,
"online": a.id in onliners,
}
)
return {"members": members, "error": None}
members.append({
"id": a.id,
"slug": a.slug,
"userpic": a.userpic,
"name": a.name,
"lastSeen": a.lastSeen,
"online": a.id in onliners
})
return {
"members": members,
"error": None
}

View File

@@ -1,36 +1,41 @@
import asyncio
import json
from typing import Any
from datetime import datetime, timezone
from graphql.type import GraphQLResolveInfo
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.redis import redis
from base.resolvers import mutation
from datetime import datetime, timezone
from services.following import FollowingManager, FollowingResult
import json
from services.following import FollowingManager, FollowingResult, Following
from validations.inbox import Message
@mutation.field("createMessage")
@login_required
async def create_message(_, info, chat: str, body: str, replyTo=None):
"""create message with :body for :chat_id replying to :replyTo optionally"""
""" create message with :body for :chat_id replying to :replyTo optionally """
auth: AuthCredentials = info.context["request"].auth
chat = await redis.execute("GET", f"chats/{chat}")
if not chat:
return {"error": "chat is not exist"}
return {
"error": "chat is not exist"
}
else:
chat = dict(json.loads(chat))
message_id = await redis.execute("GET", f"chats/{chat['id']}/next_message_id")
message_id = int(message_id)
new_message = {
"chatId": chat["id"],
"chatId": chat['id'],
"id": message_id,
"author": auth.user_id,
"body": body,
"createdAt": int(datetime.now(tz=timezone.utc).timestamp()),
"createdAt": int(datetime.now(tz=timezone.utc).timestamp())
}
if replyTo:
new_message["replyTo"] = replyTo
chat["updatedAt"] = new_message["createdAt"]
new_message['replyTo'] = replyTo
chat['updatedAt'] = new_message['createdAt']
await redis.execute("SET", f"chats/{chat['id']}", json.dumps(chat))
print(f"[inbox] creating message {new_message}")
await redis.execute(
@@ -41,12 +46,17 @@ async def create_message(_, info, chat: str, body: str, replyTo=None):
users = chat["users"]
for user_slug in users:
await redis.execute("LPUSH", f"chats/{chat['id']}/unread/{user_slug}", str(message_id))
await redis.execute(
"LPUSH", f"chats/{chat['id']}/unread/{user_slug}", str(message_id)
)
result = FollowingResult("NEW", "chat", new_message)
await FollowingManager.push("chat", result)
result = FollowingResult("NEW", 'chat', new_message)
await FollowingManager.push('chat', result)
return {"message": new_message, "error": None}
return {
"message": new_message,
"error": None
}
@mutation.field("updateMessage")
@@ -71,10 +81,13 @@ async def update_message(_, info, chat_id: str, message_id: int, body: str):
await redis.execute("SET", f"chats/{chat_id}/messages/{message_id}", json.dumps(message))
result = FollowingResult("UPDATED", "chat", message)
await FollowingManager.push("chat", result)
result = FollowingResult("UPDATED", 'chat', message)
await FollowingManager.push('chat', result)
return {"message": message, "error": None}
return {
"message": message,
"error": None
}
@mutation.field("deleteMessage")
@@ -101,7 +114,7 @@ async def delete_message(_, info, chat_id: str, message_id: int):
for user_id in users:
await redis.execute("LREM", f"chats/{chat_id}/unread/{user_id}", 0, str(message_id))
result = FollowingResult("DELETED", "chat", message)
result = FollowingResult("DELETED", 'chat', message)
await FollowingManager.push(result)
return {}
@@ -124,4 +137,6 @@ async def mark_as_read(_, info, chat_id: str, messages: [int]):
for message_id in messages:
await redis.execute("LREM", f"chats/{chat_id}/unread/{auth.user_id}", 0, str(message_id))
return {"error": None}
return {
"error": None
}

View File

@@ -1,14 +1,13 @@
import json
from datetime import datetime, timezone, timedelta
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.orm import local_session
from base.redis import redis
from base.resolvers import query
from datetime import datetime, timedelta, timezone
from base.orm import local_session
from orm.user import AuthorFollower, User
from resolvers.inbox.load import load_messages
import json
@query.field("searchRecipients")
@login_required
@@ -18,7 +17,7 @@ async def search_recipients(_, info, query: str, limit: int = 50, offset: int =
auth: AuthCredentials = info.context["request"].auth
talk_before = await redis.execute("GET", f"/chats_by_user/{auth.user_id}")
if talk_before:
talk_before = list(json.loads(talk_before))[offset : offset + limit]
talk_before = list(json.loads(talk_before))[offset:offset + limit]
for chat_id in talk_before:
members = await redis.execute("GET", f"/chats/{chat_id}/users")
if members:
@@ -32,24 +31,23 @@ async def search_recipients(_, info, query: str, limit: int = 50, offset: int =
with local_session() as session:
# followings
result += (
session.query(AuthorFollower.author)
.join(User, User.id == AuthorFollower.follower)
.where(User.slug.startswith(query))
.offset(offset + len(result))
.limit(more_amount)
)
result += session.query(AuthorFollower.author).join(
User, User.id == AuthorFollower.follower
).where(
User.slug.startswith(query)
).offset(offset + len(result)).limit(more_amount)
more_amount = limit
# followers
result += (
session.query(AuthorFollower.follower)
.join(User, User.id == AuthorFollower.author)
.where(User.slug.startswith(query))
.offset(offset + len(result))
.limit(offset + len(result) + limit)
)
return {"members": list(result), "error": None}
result += session.query(AuthorFollower.follower).join(
User, User.id == AuthorFollower.author
).where(
User.slug.startswith(query)
).offset(offset + len(result)).limit(offset + len(result) + limit)
return {
"members": list(result),
"error": None
}
@query.field("searchMessages")
@@ -59,22 +57,22 @@ async def search_user_chats(by, messages, user_id: int, limit, offset):
cids.union(set(await redis.execute("SMEMBERS", "chats_by_user/" + str(user_id))))
messages = []
by_author = by.get("author")
by_author = by.get('author')
if by_author:
# all author's messages
cids.union(set(await redis.execute("SMEMBERS", f"chats_by_user/{by_author}")))
# author's messages in filtered chat
messages.union(set(filter(lambda m: m["author"] == by_author, list(messages))))
for c in cids:
c = c.decode("utf-8")
c = c.decode('utf-8')
messages = await load_messages(c, limit, offset)
body_like = by.get("body")
body_like = by.get('body')
if body_like:
# search in all messages in all user's chats
for c in cids:
# FIXME: use redis scan here
c = c.decode("utf-8")
c = c.decode('utf-8')
mmm = await load_messages(c, limit, offset)
for m in mmm:
if body_like in m["body"]:
@@ -85,12 +83,13 @@ async def search_user_chats(by, messages, user_id: int, limit, offset):
days = by.get("days")
if days:
messages.extend(
filter(
list(messages),
key=lambda m: (
datetime.now(tz=timezone.utc) - int(m["createdAt"]) < timedelta(days=by["days"])
),
messages.extend(filter(
list(messages),
key=lambda m: (
datetime.now(tz=timezone.utc) - int(m["createdAt"]) < timedelta(days=by["days"])
)
)
return {"messages": messages, "error": None}
))
return {
"messages": messages,
"error": None
}

View File

@@ -1,9 +1,10 @@
from auth.authenticate import login_required
from sqlalchemy import select, desc, and_, update
from auth.credentials import AuthCredentials
from base.resolvers import query, mutation
from auth.authenticate import login_required
from base.orm import local_session
from base.resolvers import mutation, query
from orm import Notification
from sqlalchemy import and_, desc, select, update
@query.field("loadNotifications")
@@ -15,26 +16,25 @@ async def load_notifications(_, info, params=None):
auth: AuthCredentials = info.context["request"].auth
user_id = auth.user_id
limit = params.get("limit", 50)
offset = params.get("offset", 0)
limit = params.get('limit', 50)
offset = params.get('offset', 0)
q = (
select(Notification)
.where(Notification.user == user_id)
.order_by(desc(Notification.createdAt))
.limit(limit)
.offset(offset)
)
q = select(Notification).where(
Notification.user == user_id
).order_by(desc(Notification.createdAt)).limit(limit).offset(offset)
notifications = []
with local_session() as session:
total_count = session.query(Notification).where(Notification.user == user_id).count()
total_count = session.query(Notification).where(
Notification.user == user_id
).count()
total_unread_count = (
session.query(Notification)
.where(and_(Notification.user == user_id, Notification.seen == False)) # noqa: E712
.count()
)
total_unread_count = session.query(Notification).where(
and_(
Notification.user == user_id,
Notification.seen == False
)
).count()
for [notification] in session.execute(q):
notification.type = notification.type.name
@@ -43,7 +43,7 @@ async def load_notifications(_, info, params=None):
return {
"notifications": notifications,
"totalCount": total_count,
"totalUnreadCount": total_unread_count,
"totalUnreadCount": total_unread_count
}
@@ -54,11 +54,9 @@ async def mark_notification_as_read(_, info, notification_id: int):
user_id = auth.user_id
with local_session() as session:
notification = (
session.query(Notification)
.where(and_(Notification.id == notification_id, Notification.user == user_id))
.one()
)
notification = session.query(Notification).where(
and_(Notification.id == notification_id, Notification.user == user_id)
).one()
notification.seen = True
session.commit()
@@ -71,11 +69,12 @@ async def mark_all_notifications_as_read(_, info):
auth: AuthCredentials = info.context["request"].auth
user_id = auth.user_id
statement = (
update(Notification)
.where(and_(Notification.user == user_id, Notification.seen == False)) # noqa: E712
.values(seen=True)
)
statement = update(Notification).where(
and_(
Notification.user == user_id,
Notification.seen == False
)
).values(seen=True)
with local_session() as session:
try:

View File

@@ -1,37 +1,34 @@
from botocore.exceptions import BotoCoreError, ClientError
from starlette.responses import JSONResponse
import boto3
import os
import shutil
import tempfile
import uuid
import boto3
from botocore.exceptions import BotoCoreError, ClientError
from starlette.responses import JSONResponse
STORJ_ACCESS_KEY = os.environ.get("STORJ_ACCESS_KEY")
STORJ_SECRET_KEY = os.environ.get("STORJ_SECRET_KEY")
STORJ_END_POINT = os.environ.get("STORJ_END_POINT")
STORJ_BUCKET_NAME = os.environ.get("STORJ_BUCKET_NAME")
CDN_DOMAIN = os.environ.get("CDN_DOMAIN")
STORJ_ACCESS_KEY = os.environ.get('STORJ_ACCESS_KEY')
STORJ_SECRET_KEY = os.environ.get('STORJ_SECRET_KEY')
STORJ_END_POINT = os.environ.get('STORJ_END_POINT')
STORJ_BUCKET_NAME = os.environ.get('STORJ_BUCKET_NAME')
CDN_DOMAIN = os.environ.get('CDN_DOMAIN')
async def upload_handler(request):
form = await request.form()
file = form.get("file")
file = form.get('file')
if file is None:
return JSONResponse({"error": "No file uploaded"}, status_code=400)
return JSONResponse({'error': 'No file uploaded'}, status_code=400)
file_name, file_extension = os.path.splitext(file.filename)
key = "files/" + str(uuid.uuid4()) + file_extension
key = 'files/' + str(uuid.uuid4()) + file_extension
# Create an S3 client with Storj configuration
s3 = boto3.client(
"s3",
aws_access_key_id=STORJ_ACCESS_KEY,
aws_secret_access_key=STORJ_SECRET_KEY,
endpoint_url=STORJ_END_POINT,
)
s3 = boto3.client('s3',
aws_access_key_id=STORJ_ACCESS_KEY,
aws_secret_access_key=STORJ_SECRET_KEY,
endpoint_url=STORJ_END_POINT)
try:
# Save the uploaded file to a temporary file
@@ -42,13 +39,18 @@ async def upload_handler(request):
Filename=tmp_file.name,
Bucket=STORJ_BUCKET_NAME,
Key=key,
ExtraArgs={"ContentType": file.content_type},
ExtraArgs={
"ContentType": file.content_type
}
)
url = "https://" + CDN_DOMAIN + "/" + key
url = 'https://' + CDN_DOMAIN + '/' + key
return JSONResponse({"url": url, "originalFilename": file.filename})
return JSONResponse({'url': url, 'originalFilename': file.filename})
except (BotoCoreError, ClientError) as e:
print(e)
return JSONResponse({"error": "Failed to upload file"}, status_code=500)
return JSONResponse({'error': 'Failed to upload file'}, status_code=500)

View File

@@ -1,12 +1,17 @@
import asyncio
from base.orm import local_session
from base.resolvers import mutation
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.resolvers import mutation
# from resolvers.community import community_follow, community_unfollow
from orm.user import AuthorFollower
from orm.topic import TopicFollower
from orm.shout import ShoutReactionsFollower
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
from services.following import FollowingManager, FollowingResult
from services.following import Following, FollowingManager, FollowingResult
from graphql.type import GraphQLResolveInfo
@mutation.field("follow")
@@ -17,20 +22,20 @@ async def follow(_, info, what, slug):
try:
if what == "AUTHOR":
if author_follow(auth.user_id, slug):
result = FollowingResult("NEW", "author", slug)
await FollowingManager.push("author", result)
result = FollowingResult("NEW", 'author', slug)
await FollowingManager.push('author', result)
elif what == "TOPIC":
if topic_follow(auth.user_id, slug):
result = FollowingResult("NEW", "topic", slug)
await FollowingManager.push("topic", result)
result = FollowingResult("NEW", 'topic', slug)
await FollowingManager.push('topic', result)
elif what == "COMMUNITY":
if False: # TODO: use community_follow(auth.user_id, slug):
result = FollowingResult("NEW", "community", slug)
await FollowingManager.push("community", result)
result = FollowingResult("NEW", 'community', slug)
await FollowingManager.push('community', result)
elif what == "REACTIONS":
if reactions_follow(auth.user_id, slug):
result = FollowingResult("NEW", "shout", slug)
await FollowingManager.push("shout", result)
result = FollowingResult("NEW", 'shout', slug)
await FollowingManager.push('shout', result)
except Exception as e:
print(Exception(e))
return {"error": str(e)}
@@ -46,20 +51,20 @@ async def unfollow(_, info, what, slug):
try:
if what == "AUTHOR":
if author_unfollow(auth.user_id, slug):
result = FollowingResult("DELETED", "author", slug)
await FollowingManager.push("author", result)
result = FollowingResult("DELETED", 'author', slug)
await FollowingManager.push('author', result)
elif what == "TOPIC":
if topic_unfollow(auth.user_id, slug):
result = FollowingResult("DELETED", "topic", slug)
await FollowingManager.push("topic", result)
result = FollowingResult("DELETED", 'topic', slug)
await FollowingManager.push('topic', result)
elif what == "COMMUNITY":
if False: # TODO: use community_unfollow(auth.user_id, slug):
result = FollowingResult("DELETED", "community", slug)
await FollowingManager.push("community", result)
result = FollowingResult("DELETED", 'community', slug)
await FollowingManager.push('community', result)
elif what == "REACTIONS":
if reactions_unfollow(auth.user_id, slug):
result = FollowingResult("DELETED", "shout", slug)
await FollowingManager.push("shout", result)
result = FollowingResult("DELETED", 'shout', slug)
await FollowingManager.push('shout', result)
except Exception as e:
return {"error": str(e)}

View File

@@ -1,47 +1,49 @@
from datetime import datetime, timedelta, timezone
from sqlalchemy.orm import joinedload, aliased
from sqlalchemy.sql.expression import desc, asc, select, func, case, and_, text, nulls_last
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.exceptions import ObjectNotExist
from base.exceptions import ObjectNotExist, OperationNotAllowed
from base.orm import local_session
from base.resolvers import query
from datetime import datetime, timedelta, timezone
from orm import TopicFollower
from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout, ShoutAuthor, ShoutTopic
from orm.user import AuthorFollower
from sqlalchemy.orm import aliased, joinedload
from sqlalchemy.sql.expression import and_, asc, case, desc, func, nulls_last, select
def add_stat_columns(q):
aliased_reaction = aliased(Reaction)
q = q.outerjoin(aliased_reaction).add_columns(
func.sum(aliased_reaction.id).label("reacted_stat"),
func.sum(case((aliased_reaction.kind == ReactionKind.COMMENT, 1), else_=0)).label(
"commented_stat"
),
func.sum(
aliased_reaction.id
).label('reacted_stat'),
func.sum(
case(
# do not count comments' reactions
(aliased_reaction.replyTo.is_not(None), 0),
(aliased_reaction.kind == ReactionKind.AGREE, 1),
(aliased_reaction.kind == ReactionKind.DISAGREE, -1),
(aliased_reaction.kind == ReactionKind.PROOF, 1),
(aliased_reaction.kind == ReactionKind.DISPROOF, -1),
(aliased_reaction.kind == ReactionKind.ACCEPT, 1),
(aliased_reaction.kind == ReactionKind.REJECT, -1),
(aliased_reaction.kind == ReactionKind.LIKE, 1),
(aliased_reaction.kind == ReactionKind.DISLIKE, -1),
else_=0,
(aliased_reaction.kind == ReactionKind.COMMENT, 1),
else_=0
)
).label("rating_stat"),
func.max(
case(
(aliased_reaction.kind != ReactionKind.COMMENT, None),
else_=aliased_reaction.createdAt,
)
).label("last_comment"),
)
).label('commented_stat'),
func.sum(case(
# do not count comments' reactions
(aliased_reaction.replyTo.is_not(None), 0),
(aliased_reaction.kind == ReactionKind.AGREE, 1),
(aliased_reaction.kind == ReactionKind.DISAGREE, -1),
(aliased_reaction.kind == ReactionKind.PROOF, 1),
(aliased_reaction.kind == ReactionKind.DISPROOF, -1),
(aliased_reaction.kind == ReactionKind.ACCEPT, 1),
(aliased_reaction.kind == ReactionKind.REJECT, -1),
(aliased_reaction.kind == ReactionKind.LIKE, 1),
(aliased_reaction.kind == ReactionKind.DISLIKE, -1),
else_=0)
).label('rating_stat'),
func.max(case(
(aliased_reaction.kind != ReactionKind.COMMENT, None),
else_=aliased_reaction.createdAt
)).label('last_comment'))
return q
@@ -58,7 +60,7 @@ def apply_filters(q, filters, user_id=None):
if filters.get("layout"):
q = q.filter(Shout.layout == filters.get("layout"))
if filters.get("excludeLayout"):
if filters.get('excludeLayout'):
q = q.filter(Shout.layout != filters.get("excludeLayout"))
if filters.get("author"):
q = q.filter(Shout.authors.any(slug=filters.get("author")))
@@ -85,27 +87,27 @@ async def load_shout(_, info, slug=None, shout_id=None):
q = add_stat_columns(q)
if slug is not None:
q = q.filter(Shout.slug == slug)
q = q.filter(
Shout.slug == slug
)
if shout_id is not None:
q = q.filter(Shout.id == shout_id)
q = q.filter(
Shout.id == shout_id
)
q = q.filter(Shout.deletedAt.is_(None)).group_by(Shout.id)
q = q.filter(
Shout.deletedAt.is_(None)
).group_by(Shout.id)
try:
[
shout,
reacted_stat,
commented_stat,
rating_stat,
last_comment,
] = session.execute(q).first()
[shout, reacted_stat, commented_stat, rating_stat, last_comment] = session.execute(q).first()
shout.stat = {
"viewed": shout.views,
"reacted": reacted_stat,
"commented": commented_stat,
"rating": rating_stat,
"rating": rating_stat
}
for author_caption in session.query(ShoutAuthor).join(Shout).where(Shout.slug == slug):
@@ -140,13 +142,14 @@ async def load_shouts_by(_, info, options):
:return: Shout[]
"""
q = (
select(Shout)
.options(
joinedload(Shout.authors),
joinedload(Shout.topics),
q = select(Shout).options(
joinedload(Shout.authors),
joinedload(Shout.topics),
).where(
and_(
Shout.deletedAt.is_(None),
Shout.layout.is_not(None)
)
.where(and_(Shout.deletedAt.is_(None), Shout.layout.is_not(None)))
)
q = add_stat_columns(q)
@@ -156,7 +159,7 @@ async def load_shouts_by(_, info, options):
order_by = options.get("order_by", Shout.publishedAt)
query_order_by = desc(order_by) if options.get("order_by_desc", True) else asc(order_by)
query_order_by = desc(order_by) if options.get('order_by_desc', True) else asc(order_by)
offset = options.get("offset", 0)
limit = options.get("limit", 10)
@@ -166,19 +169,13 @@ async def load_shouts_by(_, info, options):
with local_session() as session:
shouts_map = {}
for [
shout,
reacted_stat,
commented_stat,
rating_stat,
last_comment,
] in session.execute(q).unique():
for [shout, reacted_stat, commented_stat, rating_stat, last_comment] in session.execute(q).unique():
shouts.append(shout)
shout.stat = {
"viewed": shout.views,
"reacted": reacted_stat,
"commented": commented_stat,
"rating": rating_stat,
"rating": rating_stat
}
shouts_map[shout.id] = shout
@@ -191,13 +188,11 @@ async def get_drafts(_, info):
auth: AuthCredentials = info.context["request"].auth
user_id = auth.user_id
q = (
select(Shout)
.options(
joinedload(Shout.authors),
joinedload(Shout.topics),
)
.where(and_(Shout.deletedAt.is_(None), Shout.createdBy == user_id))
q = select(Shout).options(
joinedload(Shout.authors),
joinedload(Shout.topics),
).where(
and_(Shout.deletedAt.is_(None), Shout.createdBy == user_id)
)
q = q.group_by(Shout.id)
@@ -216,26 +211,24 @@ async def get_my_feed(_, info, options):
auth: AuthCredentials = info.context["request"].auth
user_id = auth.user_id
subquery = (
select(Shout.id)
.join(ShoutAuthor)
.join(AuthorFollower, AuthorFollower.follower == user_id)
.join(ShoutTopic)
.join(TopicFollower, TopicFollower.follower == user_id)
subquery = select(Shout.id).join(
ShoutAuthor
).join(
AuthorFollower, AuthorFollower.follower == user_id
).join(
ShoutTopic
).join(
TopicFollower, TopicFollower.follower == user_id
)
q = (
select(Shout)
.options(
joinedload(Shout.authors),
joinedload(Shout.topics),
)
.where(
and_(
Shout.publishedAt.is_not(None),
Shout.deletedAt.is_(None),
Shout.id.in_(subquery),
)
q = select(Shout).options(
joinedload(Shout.authors),
joinedload(Shout.topics),
).where(
and_(
Shout.publishedAt.is_not(None),
Shout.deletedAt.is_(None),
Shout.id.in_(subquery)
)
)
@@ -244,7 +237,7 @@ async def get_my_feed(_, info, options):
order_by = options.get("order_by", Shout.publishedAt)
query_order_by = desc(order_by) if options.get("order_by_desc", True) else asc(order_by)
query_order_by = desc(order_by) if options.get('order_by_desc', True) else asc(order_by)
offset = options.get("offset", 0)
limit = options.get("limit", 10)
@@ -253,19 +246,13 @@ async def get_my_feed(_, info, options):
shouts = []
with local_session() as session:
shouts_map = {}
for [
shout,
reacted_stat,
commented_stat,
rating_stat,
last_comment,
] in session.execute(q).unique():
for [shout, reacted_stat, commented_stat, rating_stat, last_comment] in session.execute(q).unique():
shouts.append(shout)
shout.stat = {
"viewed": shout.views,
"reacted": reacted_stat,
"commented": commented_stat,
"rating": rating_stat,
"rating": rating_stat
}
shouts_map[shout.id] = shout

View File

@@ -1,16 +1,17 @@
from typing import List
from datetime import datetime, timedelta, timezone
from sqlalchemy import and_, func, distinct, select, literal
from sqlalchemy.orm import aliased, joinedload
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.orm import local_session
from base.resolvers import mutation, query
from datetime import datetime, timedelta, timezone
from orm.reaction import Reaction, ReactionKind
from orm.shout import ShoutAuthor, ShoutTopic
from orm.topic import Topic, TopicFollower
from orm.user import AuthorFollower, Role, User, UserRating, UserRole
from resolvers.zine.topics import followed_by_user
from sqlalchemy import and_, distinct, func, literal, select
from sqlalchemy.orm import aliased, joinedload
from typing import List
def add_author_stat_columns(q):
@@ -20,24 +21,24 @@ def add_author_stat_columns(q):
# user_rating_aliased = aliased(UserRating)
q = q.outerjoin(shout_author_aliased).add_columns(
func.count(distinct(shout_author_aliased.shout)).label("shouts_stat")
func.count(distinct(shout_author_aliased.shout)).label('shouts_stat')
)
q = q.outerjoin(author_followers, author_followers.author == User.id).add_columns(
func.count(distinct(author_followers.follower)).label("followers_stat")
func.count(distinct(author_followers.follower)).label('followers_stat')
)
q = q.outerjoin(author_following, author_following.follower == User.id).add_columns(
func.count(distinct(author_following.author)).label("followings_stat")
func.count(distinct(author_following.author)).label('followings_stat')
)
q = q.add_columns(literal(0).label("rating_stat"))
q = q.add_columns(literal(0).label('rating_stat'))
# FIXME
# q = q.outerjoin(user_rating_aliased, user_rating_aliased.user == User.id).add_columns(
# # TODO: check
# func.sum(user_rating_aliased.value).label('rating_stat')
# )
q = q.add_columns(literal(0).label("commented_stat"))
q = q.add_columns(literal(0).label('commented_stat'))
# q = q.outerjoin(Reaction, and_(Reaction.createdBy == User.id, Reaction.body.is_not(None))).add_columns(
# func.count(distinct(Reaction.id)).label('commented_stat')
# )
@@ -48,19 +49,13 @@ def add_author_stat_columns(q):
def add_stat(author, stat_columns):
[
shouts_stat,
followers_stat,
followings_stat,
rating_stat,
commented_stat,
] = stat_columns
[shouts_stat, followers_stat, followings_stat, rating_stat, commented_stat] = stat_columns
author.stat = {
"shouts": shouts_stat,
"followers": followers_stat,
"followings": followings_stat,
"rating": rating_stat,
"commented": commented_stat,
"commented": commented_stat
}
return author
@@ -124,10 +119,10 @@ async def user_followers(_, _info, slug) -> List[User]:
q = add_author_stat_columns(q)
aliased_user = aliased(User)
q = (
q.join(AuthorFollower, AuthorFollower.follower == User.id)
.join(aliased_user, aliased_user.id == AuthorFollower.author)
.where(aliased_user.slug == slug)
q = q.join(AuthorFollower, AuthorFollower.follower == User.id).join(
aliased_user, aliased_user.id == AuthorFollower.author
).where(
aliased_user.slug == slug
)
return get_authors_from_query(q)
@@ -155,10 +150,15 @@ async def update_profile(_, info, profile):
with local_session() as session:
user = session.query(User).filter(User.id == user_id).one()
if not user:
return {"error": "canoot find user"}
return {
"error": "canoot find user"
}
user.update(profile)
session.commit()
return {"error": None, "author": user}
return {
"error": None,
"author": user
}
@mutation.field("rateUser")
@@ -200,10 +200,13 @@ def author_follow(user_id, slug):
def author_unfollow(user_id, slug):
with local_session() as session:
flw = (
session.query(AuthorFollower)
.join(User, User.id == AuthorFollower.author)
.filter(and_(AuthorFollower.follower == user_id, User.slug == slug))
.first()
session.query(
AuthorFollower
).join(User, User.id == AuthorFollower.author).filter(
and_(
AuthorFollower.follower == user_id, User.slug == slug
)
).first()
)
if flw:
session.delete(flw)
@@ -229,16 +232,12 @@ async def get_author(_, _info, slug):
[author] = get_authors_from_query(q)
with local_session() as session:
comments_count = (
session.query(Reaction)
.where(
and_(
Reaction.createdBy == author.id,
Reaction.kind == ReactionKind.COMMENT,
)
comments_count = session.query(Reaction).where(
and_(
Reaction.createdBy == author.id,
Reaction.kind == ReactionKind.COMMENT
)
.count()
)
).count()
author.stat["commented"] = comments_count
return author
@@ -261,7 +260,9 @@ async def load_authors_by(_, info, by, limit, offset):
days_before = datetime.now(tz=timezone.utc) - timedelta(days=by["createdAt"])
q = q.filter(User.createdAt > days_before)
q = q.order_by(by.get("order", User.createdAt)).limit(limit).offset(offset)
q = q.order_by(
by.get("order", User.createdAt)
).limit(limit).offset(offset)
return get_authors_from_query(q)
@@ -272,13 +273,13 @@ async def load_my_subscriptions(_, info):
auth = info.context["request"].auth
user_id = auth.user_id
authors_query = (
select(User)
.join(AuthorFollower, AuthorFollower.author == User.id)
.where(AuthorFollower.follower == user_id)
authors_query = select(User).join(AuthorFollower, AuthorFollower.author == User.id).where(
AuthorFollower.follower == user_id
)
topics_query = select(Topic).join(TopicFollower).where(TopicFollower.follower == user_id)
topics_query = select(Topic).join(TopicFollower).where(
TopicFollower.follower == user_id
)
topics = []
authors = []
@@ -290,4 +291,7 @@ async def load_my_subscriptions(_, info):
for [topic] in session.execute(topics_query):
topics.append(topic)
return {"topics": topics, "authors": authors}
return {
"topics": topics,
"authors": authors
}

View File

@@ -1,37 +1,42 @@
from datetime import datetime, timedelta, timezone
from sqlalchemy import and_, asc, desc, select, text, func, case
from sqlalchemy.orm import aliased
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.exceptions import OperationNotAllowed
from base.orm import local_session
from base.resolvers import mutation, query
from datetime import datetime, timedelta, timezone
from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout, ShoutReactionsFollower
from orm.user import User
from services.notifications.notification_service import notification_service
from sqlalchemy import and_, asc, case, desc, func, select, text
from sqlalchemy.orm import aliased
def add_reaction_stat_columns(q):
aliased_reaction = aliased(Reaction)
q = q.outerjoin(aliased_reaction, Reaction.id == aliased_reaction.replyTo).add_columns(
func.sum(aliased_reaction.id).label("reacted_stat"),
func.sum(case((aliased_reaction.body.is_not(None), 1), else_=0)).label("commented_stat"),
func.sum(
aliased_reaction.id
).label('reacted_stat'),
func.sum(
case(
(aliased_reaction.kind == ReactionKind.AGREE, 1),
(aliased_reaction.kind == ReactionKind.DISAGREE, -1),
(aliased_reaction.kind == ReactionKind.PROOF, 1),
(aliased_reaction.kind == ReactionKind.DISPROOF, -1),
(aliased_reaction.kind == ReactionKind.ACCEPT, 1),
(aliased_reaction.kind == ReactionKind.REJECT, -1),
(aliased_reaction.kind == ReactionKind.LIKE, 1),
(aliased_reaction.kind == ReactionKind.DISLIKE, -1),
else_=0,
(aliased_reaction.body.is_not(None), 1),
else_=0
)
).label("rating_stat"),
)
).label('commented_stat'),
func.sum(case(
(aliased_reaction.kind == ReactionKind.AGREE, 1),
(aliased_reaction.kind == ReactionKind.DISAGREE, -1),
(aliased_reaction.kind == ReactionKind.PROOF, 1),
(aliased_reaction.kind == ReactionKind.DISPROOF, -1),
(aliased_reaction.kind == ReactionKind.ACCEPT, 1),
(aliased_reaction.kind == ReactionKind.REJECT, -1),
(aliased_reaction.kind == ReactionKind.LIKE, 1),
(aliased_reaction.kind == ReactionKind.DISLIKE, -1),
else_=0)
).label('rating_stat'))
return q
@@ -42,19 +47,17 @@ def reactions_follow(user_id, shout_id: int, auto=False):
shout = session.query(Shout).where(Shout.id == shout_id).one()
following = (
session.query(ShoutReactionsFollower)
.where(
and_(
ShoutReactionsFollower.follower == user_id,
ShoutReactionsFollower.shout == shout.id,
)
)
.first()
session.query(ShoutReactionsFollower).where(and_(
ShoutReactionsFollower.follower == user_id,
ShoutReactionsFollower.shout == shout.id,
)).first()
)
if not following:
following = ShoutReactionsFollower.create(
follower=user_id, shout=shout.id, auto=auto
follower=user_id,
shout=shout.id,
auto=auto
)
session.add(following)
session.commit()
@@ -69,14 +72,10 @@ def reactions_unfollow(user_id: int, shout_id: int):
shout = session.query(Shout).where(Shout.id == shout_id).one()
following = (
session.query(ShoutReactionsFollower)
.where(
and_(
ShoutReactionsFollower.follower == user_id,
ShoutReactionsFollower.shout == shout.id,
)
)
.first()
session.query(ShoutReactionsFollower).where(and_(
ShoutReactionsFollower.follower == user_id,
ShoutReactionsFollower.shout == shout.id
)).first()
)
if following:
@@ -89,31 +88,30 @@ def reactions_unfollow(user_id: int, shout_id: int):
def is_published_author(session, user_id):
"""checks if user has at least one publication"""
return (
session.query(Shout)
.where(Shout.authors.contains(user_id))
.filter(and_(Shout.publishedAt.is_not(None), Shout.deletedAt.is_(None)))
.count()
> 0
)
''' checks if user has at least one publication '''
return session.query(
Shout
).where(
Shout.authors.contains(user_id)
).filter(
and_(
Shout.publishedAt.is_not(None),
Shout.deletedAt.is_(None)
)
).count() > 0
def check_to_publish(session, user_id, reaction):
"""set shout to public if publicated approvers amount > 4"""
''' set shout to public if publicated approvers amount > 4 '''
if not reaction.replyTo and reaction.kind in [
ReactionKind.ACCEPT,
ReactionKind.LIKE,
ReactionKind.PROOF,
ReactionKind.PROOF
]:
if is_published_author(user_id):
# now count how many approvers are voted already
approvers_reactions = (
session.query(Reaction).where(Reaction.shout == reaction.shout).all()
)
approvers = [
user_id,
]
approvers_reactions = session.query(Reaction).where(Reaction.shout == reaction.shout).all()
approvers = [user_id, ]
for ar in approvers_reactions:
a = ar.createdBy
if is_published_author(session, a):
@@ -124,11 +122,11 @@ def check_to_publish(session, user_id, reaction):
def check_to_hide(session, user_id, reaction):
"""hides any shout if 20% of reactions are negative"""
''' hides any shout if 20% of reactions are negative '''
if not reaction.replyTo and reaction.kind in [
ReactionKind.REJECT,
ReactionKind.DISLIKE,
ReactionKind.DISPROOF,
ReactionKind.DISPROOF
]:
# if is_published_author(user):
approvers_reactions = session.query(Reaction).where(Reaction.shout == reaction.shout).all()
@@ -137,7 +135,7 @@ def check_to_hide(session, user_id, reaction):
if r.kind in [
ReactionKind.REJECT,
ReactionKind.DISLIKE,
ReactionKind.DISPROOF,
ReactionKind.DISPROOF
]:
rejects += 1
if len(approvers_reactions) / rejects < 5:
@@ -148,14 +146,14 @@ def check_to_hide(session, user_id, reaction):
def set_published(session, shout_id):
s = session.query(Shout).where(Shout.id == shout_id).first()
s.publishedAt = datetime.now(tz=timezone.utc)
s.visibility = text("public")
s.visibility = text('public')
session.add(s)
session.commit()
def set_hidden(session, shout_id):
s = session.query(Shout).where(Shout.id == shout_id).first()
s.visibility = text("community")
s.visibility = text('community')
session.add(s)
session.commit()
@@ -164,46 +162,37 @@ def set_hidden(session, shout_id):
@login_required
async def create_reaction(_, info, reaction):
auth: AuthCredentials = info.context["request"].auth
reaction["createdBy"] = auth.user_id
reaction['createdBy'] = auth.user_id
rdict = {}
with local_session() as session:
shout = session.query(Shout).where(Shout.id == reaction["shout"]).one()
author = session.query(User).where(User.id == auth.user_id).one()
if reaction["kind"] in [ReactionKind.DISLIKE.name, ReactionKind.LIKE.name]:
existing_reaction = (
session.query(Reaction)
.where(
and_(
Reaction.shout == reaction["shout"],
Reaction.createdBy == auth.user_id,
Reaction.kind == reaction["kind"],
Reaction.replyTo == reaction.get("replyTo"),
)
if reaction["kind"] in [
ReactionKind.DISLIKE.name,
ReactionKind.LIKE.name
]:
existing_reaction = session.query(Reaction).where(
and_(
Reaction.shout == reaction["shout"],
Reaction.createdBy == auth.user_id,
Reaction.kind == reaction["kind"],
Reaction.replyTo == reaction.get("replyTo")
)
.first()
)
).first()
if existing_reaction is not None:
raise OperationNotAllowed("You can't vote twice")
opposite_reaction_kind = (
ReactionKind.DISLIKE
if reaction["kind"] == ReactionKind.LIKE.name
else ReactionKind.LIKE
)
opposite_reaction = (
session.query(Reaction)
.where(
opposite_reaction_kind = ReactionKind.DISLIKE if reaction["kind"] == ReactionKind.LIKE.name else ReactionKind.LIKE
opposite_reaction = session.query(Reaction).where(
and_(
Reaction.shout == reaction["shout"],
Reaction.createdBy == auth.user_id,
Reaction.kind == opposite_reaction_kind,
Reaction.replyTo == reaction.get("replyTo"),
Reaction.replyTo == reaction.get("replyTo")
)
)
.first()
)
).first()
if opposite_reaction is not None:
session.delete(opposite_reaction)
@@ -232,8 +221,8 @@ async def create_reaction(_, info, reaction):
await notification_service.handle_new_reaction(r.id)
rdict = r.dict()
rdict["shout"] = shout.dict()
rdict["createdBy"] = author.dict()
rdict['shout'] = shout.dict()
rdict['createdBy'] = author.dict()
# self-regulation mechanics
if check_to_hide(session, auth.user_id, r):
@@ -246,7 +235,11 @@ async def create_reaction(_, info, reaction):
except Exception as e:
print(f"[resolvers.reactions] error on reactions autofollowing: {e}")
rdict["stat"] = {"commented": 0, "reacted": 0, "rating": 0}
rdict['stat'] = {
"commented": 0,
"reacted": 0,
"rating": 0
}
return {"reaction": rdict}
@@ -279,7 +272,7 @@ async def update_reaction(_, info, id, reaction={}):
r.stat = {
"commented": commented_stat,
"reacted": reacted_stat,
"rating": rating_stat,
"rating": rating_stat
}
return {"reaction": r}
@@ -297,12 +290,17 @@ async def delete_reaction(_, info, id):
if r.createdBy != auth.user_id:
return {"error": "access denied"}
if r.kind in [ReactionKind.LIKE, ReactionKind.DISLIKE]:
if r.kind in [
ReactionKind.LIKE,
ReactionKind.DISLIKE
]:
session.delete(r)
else:
r.deletedAt = datetime.now(tz=timezone.utc)
session.commit()
return {"reaction": r}
return {
"reaction": r
}
@query.field("loadReactionsBy")
@@ -323,10 +321,12 @@ async def load_reactions_by(_, _info, by, limit=50, offset=0):
:return: Reaction[]
"""
q = (
select(Reaction, User, Shout)
.join(User, Reaction.createdBy == User.id)
.join(Shout, Reaction.shout == Shout.id)
q = select(
Reaction, User, Shout
).join(
User, Reaction.createdBy == User.id
).join(
Shout, Reaction.shout == Shout.id
)
if by.get("shout"):
@@ -344,7 +344,7 @@ async def load_reactions_by(_, _info, by, limit=50, offset=0):
if by.get("comment"):
q = q.filter(func.length(Reaction.body) > 0)
if len(by.get("search", "")) > 2:
if len(by.get('search', '')) > 2:
q = q.filter(Reaction.body.ilike(f'%{by["body"]}%'))
if by.get("days"):
@@ -352,9 +352,13 @@ async def load_reactions_by(_, _info, by, limit=50, offset=0):
q = q.filter(Reaction.createdAt > after)
order_way = asc if by.get("sort", "").startswith("-") else desc
order_field = by.get("sort", "").replace("-", "") or Reaction.createdAt
order_field = by.get("sort", "").replace('-', '') or Reaction.createdAt
q = q.group_by(Reaction.id, User.id, Shout.id).order_by(order_way(order_field))
q = q.group_by(
Reaction.id, User.id, Shout.id
).order_by(
order_way(order_field)
)
q = add_reaction_stat_columns(q)
@@ -363,20 +367,13 @@ async def load_reactions_by(_, _info, by, limit=50, offset=0):
reactions = []
with local_session() as session:
for [
reaction,
user,
shout,
reacted_stat,
commented_stat,
rating_stat,
] in session.execute(q):
for [reaction, user, shout, reacted_stat, commented_stat, rating_stat] in session.execute(q):
reaction.createdBy = user
reaction.shout = shout
reaction.stat = {
"rating": rating_stat,
"commented": commented_stat,
"reacted": reacted_stat,
"reacted": reacted_stat
}
reaction.kind = reaction.kind.name

View File

@@ -1,24 +1,24 @@
from sqlalchemy import and_, select, distinct, func
from sqlalchemy.orm import aliased
from auth.authenticate import login_required
from base.orm import local_session
from base.resolvers import mutation, query
from orm import User
from orm.shout import ShoutAuthor, ShoutTopic
from orm.shout import ShoutTopic, ShoutAuthor
from orm.topic import Topic, TopicFollower
from sqlalchemy import and_, distinct, func, select
from sqlalchemy.orm import aliased
from orm import User
def add_topic_stat_columns(q):
aliased_shout_author = aliased(ShoutAuthor)
aliased_topic_follower = aliased(TopicFollower)
q = (
q.outerjoin(ShoutTopic, Topic.id == ShoutTopic.topic)
.add_columns(func.count(distinct(ShoutTopic.shout)).label("shouts_stat"))
.outerjoin(aliased_shout_author, ShoutTopic.shout == aliased_shout_author.shout)
.add_columns(func.count(distinct(aliased_shout_author.user)).label("authors_stat"))
.outerjoin(aliased_topic_follower)
.add_columns(func.count(distinct(aliased_topic_follower.follower)).label("followers_stat"))
q = q.outerjoin(ShoutTopic, Topic.id == ShoutTopic.topic).add_columns(
func.count(distinct(ShoutTopic.shout)).label('shouts_stat')
).outerjoin(aliased_shout_author, ShoutTopic.shout == aliased_shout_author.shout).add_columns(
func.count(distinct(aliased_shout_author.user)).label('authors_stat')
).outerjoin(aliased_topic_follower).add_columns(
func.count(distinct(aliased_topic_follower.follower)).label('followers_stat')
)
q = q.group_by(Topic.id)
@@ -31,7 +31,7 @@ def add_stat(topic, stat_columns):
topic.stat = {
"shouts": shouts_stat,
"authors": authors_stat,
"followers": followers_stat,
"followers": followers_stat
}
return topic
@@ -133,10 +133,12 @@ def topic_unfollow(user_id, slug):
try:
with local_session() as session:
sub = (
session.query(TopicFollower)
.join(Topic)
.filter(and_(TopicFollower.follower == user_id, Topic.slug == slug))
.first()
session.query(TopicFollower).join(Topic).filter(
and_(
TopicFollower.follower == user_id,
Topic.slug == slug
)
).first()
)
if sub:
session.delete(sub)