This commit is contained in:
Igor Lobanov
2023-10-26 22:38:31 +02:00
parent 1c49780cd4
commit c2cc428abe
64 changed files with 631 additions and 626 deletions

View File

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

View File

@@ -1,13 +1,5 @@
# -*- coding: utf-8 -*-
import re
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
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from auth.email import send_auth_email
@@ -23,8 +15,15 @@ from base.exceptions import (
)
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
@mutation.field("getSession")
@@ -45,7 +44,7 @@ async def get_current_user(_, info):
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}")
@@ -68,9 +67,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
@@ -87,22 +86,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")
@@ -117,7 +116,7 @@ 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

View File

@@ -1,15 +1,13 @@
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")
@@ -18,15 +16,15 @@ 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", ''),
"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"),
@@ -128,7 +126,10 @@ async def update_shout(_, info, shout_id, shout_input=None, publish=False):
]
shout_topics_to_remove = session.query(ShoutTopic).filter(
and_(ShoutTopic.shout == shout.id, ShoutTopic.topic.in_(topic_to_unlink_ids))
and_(
ShoutTopic.shout == shout.id,
ShoutTopic.topic.in_(topic_to_unlink_ids),
)
)
for shout_topic_to_remove in shout_topics_to_remove:
@@ -136,13 +137,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,10 @@
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 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

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
@@ -49,7 +49,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,8 +71,8 @@ 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:
@@ -105,7 +105,7 @@ 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")

View File

@@ -1,5 +1,4 @@
import json
from .unread import get_unread_counter
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.orm import local_session
@@ -8,13 +7,13 @@ from base.resolvers import query
from orm.user import User
from resolvers.zine.profile import followed_authors
from .unread import get_unread_counter
import json
# from datetime import datetime, timedelta, timezone
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:
@@ -29,10 +28,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:
@@ -52,7 +51,7 @@ async def load_chats(_, info, limit: int = 50, offset: int = 0):
if cids:
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:
@@ -63,14 +62,14 @@ 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(
c["members"].append(
{
"id": a.id,
"slug": a.slug,
@@ -87,16 +86,16 @@ async def load_chats(_, info, limit: int = 50, offset: int = 0):
@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)
@@ -104,7 +103,10 @@ async def load_messages_by(_, info, by, limit: int = 10, offset: int = 0):
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}
return {
"messages": sorted(list(messages), key=lambda m: m["createdAt"]),
"error": None,
}
else:
return {"error": "Cannot access messages of this chat"}

View File

@@ -1,16 +1,11 @@
import asyncio
import json
from datetime import datetime, timezone
from typing import Any
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 services.following import Following, FollowingManager, FollowingResult
from validations.inbox import Message
from datetime import datetime, timezone
from services.following import FollowingManager, FollowingResult
import json
@mutation.field("createMessage")
@@ -27,15 +22,15 @@ async def create_message(_, info, chat: str, body: str, replyTo=None):
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()),
}
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(
@@ -48,8 +43,8 @@ async def create_message(_, info, chat: str, body: str, replyTo=None):
for user_slug in users:
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}
@@ -76,8 +71,8 @@ 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}
@@ -106,7 +101,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 {}

View File

@@ -1,14 +1,14 @@
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.resolvers import query
from datetime import datetime, timedelta, timezone
from orm.user import AuthorFollower, User
from resolvers.inbox.load import load_messages
import json
@query.field("searchRecipients")
@login_required
@@ -59,22 +59,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"]:

View File

@@ -1,10 +1,9 @@
from sqlalchemy import and_, desc, select, update
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.orm import local_session
from base.resolvers import mutation, query
from orm import Notification
from sqlalchemy import and_, desc, select, update
@query.field("loadNotifications")
@@ -16,8 +15,8 @@ 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)
@@ -33,7 +32,7 @@ async def load_notifications(_, info, params=None):
total_unread_count = (
session.query(Notification)
.where(and_(Notification.user == user_id, Notification.seen == False))
.where(and_(Notification.user == user_id, Notification.seen == False)) # noqa: E712
.count()
)
@@ -74,7 +73,7 @@ async def mark_all_notifications_as_read(_, info):
statement = (
update(Notification)
.where(and_(Notification.user == user_id, Notification.seen == False))
.where(and_(Notification.user == user_id, Notification.seen == False)) # noqa: E712
.values(seen=True)
)

View File

@@ -1,33 +1,33 @@
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',
"s3",
aws_access_key_id=STORJ_ACCESS_KEY,
aws_secret_access_key=STORJ_SECRET_KEY,
endpoint_url=STORJ_END_POINT,
@@ -45,10 +45,10 @@ async def upload_handler(request):
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,20 +1,12 @@
import asyncio
from graphql.type import GraphQLResolveInfo
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.orm import local_session
from base.resolvers import mutation
from orm.shout import ShoutReactionsFollower
from orm.topic import TopicFollower
# from resolvers.community import community_follow, community_unfollow
from orm.user import AuthorFollower
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 Following, FollowingManager, FollowingResult
from services.following import FollowingManager, FollowingResult
@mutation.field("follow")
@@ -25,20 +17,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)}
@@ -54,20 +46,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,26 +1,24 @@
from datetime import datetime, timedelta, timezone
from sqlalchemy.orm import aliased, joinedload
from sqlalchemy.sql.expression import and_, asc, case, desc, func, nulls_last, select, text
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.exceptions import ObjectNotExist, OperationNotAllowed
from base.exceptions import ObjectNotExist
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(aliased_reaction.id).label("reacted_stat"),
func.sum(case((aliased_reaction.kind == ReactionKind.COMMENT, 1), else_=0)).label(
'commented_stat'
"commented_stat"
),
func.sum(
case(
@@ -36,13 +34,13 @@ def add_stat_columns(q):
(aliased_reaction.kind == ReactionKind.DISLIKE, -1),
else_=0,
)
).label('rating_stat'),
).label("rating_stat"),
func.max(
case(
(aliased_reaction.kind != ReactionKind.COMMENT, None),
else_=aliased_reaction.createdAt,
)
).label('last_comment'),
).label("last_comment"),
)
return q
@@ -60,7 +58,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")))
@@ -95,9 +93,13 @@ async def load_shout(_, info, slug=None, shout_id=None):
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,
@@ -154,7 +156,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)
@@ -164,9 +166,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,
@@ -225,7 +231,11 @@ async def get_my_feed(_, info, options):
joinedload(Shout.topics),
)
.where(
and_(Shout.publishedAt.is_not(None), Shout.deletedAt.is_(None), Shout.id.in_(subquery))
and_(
Shout.publishedAt.is_not(None),
Shout.deletedAt.is_(None),
Shout.id.in_(subquery),
)
)
)
@@ -234,7 +244,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)
@@ -243,9 +253,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,

View File

@@ -1,18 +1,16 @@
from datetime import datetime, timedelta, timezone
from typing import List
from sqlalchemy import and_, distinct, func, literal, select
from sqlalchemy.orm import aliased, joinedload
from auth.authenticate import login_required
from auth.credentials import AuthCredentials
from base.orm import local_session
from base.resolvers import mutation, query
from 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):
@@ -22,24 +20,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')
# )
@@ -50,7 +48,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,
@@ -227,7 +231,12 @@ async def get_author(_, _info, slug):
with local_session() as session:
comments_count = (
session.query(Reaction)
.where(and_(Reaction.createdBy == author.id, Reaction.kind == ReactionKind.COMMENT))
.where(
and_(
Reaction.createdBy == author.id,
Reaction.kind == ReactionKind.COMMENT,
)
)
.count()
)
author.stat["commented"] = comments_count

View File

@@ -1,25 +1,23 @@
from datetime import datetime, timedelta, timezone
from sqlalchemy import and_, asc, case, desc, func, select, text
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.body.is_not(None), 1), else_=0)).label("commented_stat"),
func.sum(
case(
(aliased_reaction.kind == ReactionKind.AGREE, 1),
@@ -32,7 +30,7 @@ def add_reaction_stat_columns(q):
(aliased_reaction.kind == ReactionKind.DISLIKE, -1),
else_=0,
)
).label('rating_stat'),
).label("rating_stat"),
)
return q
@@ -91,7 +89,7 @@ def reactions_unfollow(user_id: int, shout_id: int):
def is_published_author(session, user_id):
'''checks if user has at least one publication'''
"""checks if user has at least one publication"""
return (
session.query(Shout)
.where(Shout.authors.contains(user_id))
@@ -102,7 +100,7 @@ def is_published_author(session, user_id):
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,
@@ -126,7 +124,7 @@ 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,
@@ -136,7 +134,11 @@ def check_to_hide(session, user_id, reaction):
approvers_reactions = session.query(Reaction).where(Reaction.shout == reaction.shout).all()
rejects = 0
for r in approvers_reactions:
if r.kind in [ReactionKind.REJECT, ReactionKind.DISLIKE, ReactionKind.DISPROOF]:
if r.kind in [
ReactionKind.REJECT,
ReactionKind.DISLIKE,
ReactionKind.DISPROOF,
]:
rejects += 1
if len(approvers_reactions) / rejects < 5:
return True
@@ -146,14 +148,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()
@@ -162,7 +164,7 @@ 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()
@@ -230,8 +232,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):
@@ -244,7 +246,7 @@ 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}
@@ -274,7 +276,11 @@ async def update_reaction(_, info, id, reaction={}):
if reaction.get("range"):
r.range = reaction.get("range")
session.commit()
r.stat = {"commented": commented_stat, "reacted": reacted_stat, "rating": rating_stat}
r.stat = {
"commented": commented_stat,
"reacted": reacted_stat,
"rating": rating_stat,
}
return {"reaction": r}
@@ -338,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"):
@@ -346,7 +352,7 @@ 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))
@@ -357,9 +363,14 @@ 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 = {

View File

@@ -1,12 +1,11 @@
from sqlalchemy import and_, distinct, func, select
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.topic import Topic, TopicFollower
from sqlalchemy import and_, distinct, func, select
from sqlalchemy.orm import aliased
def add_topic_stat_columns(q):
@@ -15,11 +14,11 @@ def add_topic_stat_columns(q):
q = (
q.outerjoin(ShoutTopic, Topic.id == ShoutTopic.topic)
.add_columns(func.count(distinct(ShoutTopic.shout)).label('shouts_stat'))
.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'))
.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'))
.add_columns(func.count(distinct(aliased_topic_follower.follower)).label("followers_stat"))
)
q = q.group_by(Topic.id)
@@ -29,7 +28,11 @@ def add_topic_stat_columns(q):
def add_stat(topic, stat_columns):
[shouts_stat, authors_stat, followers_stat] = stat_columns
topic.stat = {"shouts": shouts_stat, "authors": authors_stat, "followers": followers_stat}
topic.stat = {
"shouts": shouts_stat,
"authors": authors_stat,
"followers": followers_stat,
}
return topic