formatted, linted, fixed

This commit is contained in:
tonyrewin 2022-09-04 20:20:38 +03:00
parent f7b9a066b9
commit 71f3ac5ed6
22 changed files with 357 additions and 303 deletions

View File

@ -1,5 +1,6 @@
[flake8] [flake8]
ignore = D203 ignore = E203,W504,W191
exclude = .git,__pycache__ exclude = .git,__pycache__,orm/rbac.py
max-complexity = 10 max-complexity = 10
max-line-length = 108 max-line-length = 108
indent-string = ' '

View File

@ -31,11 +31,17 @@ middleware = [
async def start_up(): async def start_up():
await redis.connect() await redis.connect()
viewed_storage_task = asyncio.create_task(ViewedStorage.worker()) viewed_storage_task = asyncio.create_task(ViewedStorage.worker())
# reacted_storage_task = asyncio.create_task(ReactedStorage.worker()) print(viewed_storage_task)
reacted_storage_task = asyncio.create_task(ReactedStorage.worker())
print(reacted_storage_task)
shouts_cache_task = asyncio.create_task(ShoutsCache.worker()) shouts_cache_task = asyncio.create_task(ShoutsCache.worker())
print(shouts_cache_task)
shout_author_task = asyncio.create_task(ShoutAuthorStorage.worker()) shout_author_task = asyncio.create_task(ShoutAuthorStorage.worker())
print(shout_author_task)
topic_stat_task = asyncio.create_task(TopicStat.worker()) topic_stat_task = asyncio.create_task(TopicStat.worker())
print(topic_stat_task)
git_task = asyncio.create_task(GitTask.git_task_worker()) git_task = asyncio.create_task(GitTask.git_task_worker())
print(git_task)
async def shutdown(): async def shutdown():

View File

@ -26,13 +26,13 @@ def replace_tooltips(body):
def place_tooltips(body): def place_tooltips(body):
parts = body.split("&&&") parts = body.split("&&&")
l = len(parts) lll = len(parts)
newparts = list(parts) newparts = list(parts)
placed = False placed = False
if l & 1: if lll & 1:
if l > 1: if lll > 1:
i = 1 i = 1
print("[extract] found %d tooltips" % (l - 1)) print("[extract] found %d tooltips" % (lll - 1))
for part in parts[1:]: for part in parts[1:]:
if i & 1: if i & 1:
placed = True placed = True
@ -60,7 +60,8 @@ def place_tooltips(body):
return ("".join(newparts), placed) return ("".join(newparts), placed)
IMG_REGEX = r"\!\[(.*?)\]\((data\:image\/(png|jpeg|jpg);base64\,((?:[A-Za-z\d+\/]{4})*(?:[A-Za-z\d+\/]{3}=|[A-Za-z\d+\/]{2}==)))\)" IMG_REGEX = r"\!\[(.*?)\]\((data\:image\/(png|jpeg|jpg);base64\,((?:[A-Za-z\d+\/]{4})*(?:[A-Za-z\d+\/]{3}="
IMG_REGEX += r"|[A-Za-z\d+\/]{2}==)))\)"
parentDir = "/".join(os.getcwd().split("/")[:-1]) parentDir = "/".join(os.getcwd().split("/")[:-1])
public = parentDir + "/discoursio-web/public" public = parentDir + "/discoursio-web/public"
@ -128,7 +129,7 @@ def extract_imageparts(bodyparts, prefix):
+ " image bytes been written" + " image bytes been written"
) )
cache[b64encoded] = name cache[b64encoded] = name
except: except Exception:
raise Exception raise Exception
# raise Exception('[extract] error decoding image %r' %b64encoded) # raise Exception('[extract] error decoding image %r' %b64encoded)
else: else:
@ -145,7 +146,7 @@ def extract_imageparts(bodyparts, prefix):
break break
return ( return (
extract_imageparts( extract_imageparts(
newparts[i] + newparts[i + 1] + b64.join(bodyparts[i + 2 :]), prefix newparts[i] + newparts[i + 1] + b64.join(bodyparts[(i + 2) :]), prefix
) )
if len(bodyparts) > (i + 1) if len(bodyparts) > (i + 1)
else "".join(newparts) else "".join(newparts)
@ -176,7 +177,7 @@ def extract_dataimages(parts, prefix):
open(public + link, "wb").write(content) open(public + link, "wb").write(content)
print("[extract] " + str(len(content)) + " image bytes") print("[extract] " + str(len(content)) + " image bytes")
cache[b64encoded] = name cache[b64encoded] = name
except: except Exception:
raise Exception raise Exception
# raise Exception('[extract] error decoding image %r' %b64encoded) # raise Exception('[extract] error decoding image %r' %b64encoded)
else: else:
@ -207,7 +208,6 @@ def extract_md_images(body, oid):
.replace(" [](" + di, " ![](" + di) .replace(" [](" + di, " ![](" + di)
) )
parts = body.split(di) parts = body.split(di)
i = 0
if len(parts) > 1: if len(parts) > 1:
newbody = extract_dataimages(parts, oid) newbody = extract_dataimages(parts, oid)
else: else:
@ -310,7 +310,8 @@ def prepare_html_body(entry):
elif "vimeoId" in m: elif "vimeoId" in m:
addon += '<iframe src="https://player.vimeo.com/video/' addon += '<iframe src="https://player.vimeo.com/video/'
addon += m["vimeoId"] addon += m["vimeoId"]
addon += ' width="420" height="345" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>' addon += ' width="420" height="345" frameborder="0" allow="autoplay; fullscreen"'
addon += " allowfullscreen></iframe>"
else: else:
print("[extract] media is not supported") print("[extract] media is not supported")
print(m) print(m)

View File

@ -1,28 +1,32 @@
from logging import exception
from migration.extract import extract_md, html2text from migration.extract import extract_md, html2text
from base.orm import local_session from base.orm import local_session
from orm import Topic, Community from orm import Topic, Community
def migrate(entry): def migrate(entry):
body_orig = entry.get('description', '').replace('&nbsp;', ' ') body_orig = entry.get("description", "").replace("&nbsp;", " ")
topic_dict = { topic_dict = {
'slug': entry['slug'], "slug": entry["slug"],
'oid': entry['_id'], "oid": entry["_id"],
'title': entry['title'].replace('&nbsp;', ' '), #.lower(), "title": entry["title"].replace("&nbsp;", " "),
'children': [], "children": [],
'community' : Community.default_community.slug "community": Community.default_community.slug,
} }
topic_dict['body'] = extract_md(html2text(body_orig), entry['_id']) topic_dict["body"] = extract_md(html2text(body_orig), entry["_id"])
with local_session() as session: with local_session() as session:
slug = topic_dict['slug'] slug = topic_dict["slug"]
t: Topic = session.query(Topic).filter(Topic.slug == slug).first() or Topic.create(**topic_d) or raise Exception('topic not created') # type: ignore topic = session.query(Topic).filter(Topic.slug == slug).first() or Topic.create(
if t: **topic_dict
if len(t.title) > len(topic_dict['title']): )
Topic.update(t, {'title': topic_dict['title']}) if not topic:
if len(t.body) < len(topic_dict['body']): raise Exception("no topic!")
Topic.update(t, { 'body': topic_dict['body'] }) if topic:
if len(topic.title) > len(topic_dict["title"]):
Topic.update(topic, {"title": topic_dict["title"]})
if len(topic.body) < len(topic_dict["body"]):
Topic.update(topic, {"body": topic_dict["body"]})
session.commit() session.commit()
# print(topic.__dict__) # print(topic.__dict__)
rt = t.__dict__.copy() rt = topic.__dict__.copy()
del rt['_sa_instance_state'] del rt["_sa_instance_state"]
return rt return rt

View File

@ -21,9 +21,7 @@ class Reaction(Base):
replyTo = Column( replyTo = Column(
ForeignKey("reaction.id"), nullable=True, comment="Reply to reaction ID" ForeignKey("reaction.id"), nullable=True, comment="Reply to reaction ID"
) )
range = Column( range = Column(String, nullable=True, comment="Range in format <start index>:<end>")
String, nullable=True, comment="Range in format <start index>:<end>"
)
kind = Column(Enum(ReactionKind), nullable=False, comment="Reaction kind") kind = Column(Enum(ReactionKind), nullable=False, comment="Reaction kind")
oid = Column(String, nullable=True, comment="Old ID") oid = Column(String, nullable=True, comment="Old ID")

View File

@ -16,7 +16,9 @@ class ShoutReactionsFollower(Base):
follower = Column(ForeignKey("user.slug"), primary_key=True) follower = Column(ForeignKey("user.slug"), primary_key=True)
shout = Column(ForeignKey("shout.slug"), primary_key=True) shout = Column(ForeignKey("shout.slug"), primary_key=True)
auto = Column(Boolean, nullable=False, default=False) auto = Column(Boolean, nullable=False, default=False)
createdAt = Column(DateTime, nullable=False, default=datetime.now, comment="Created at") createdAt = Column(
DateTime, nullable=False, default=datetime.now, comment="Created at"
)
deletedAt = Column(DateTime, nullable=True) deletedAt = Column(DateTime, nullable=True)
@ -42,9 +44,13 @@ class Shout(Base):
id = None # type: ignore id = None # type: ignore
slug = Column(String, primary_key=True) slug = Column(String, primary_key=True)
community = Column(Integer, ForeignKey("community.id"), nullable=False, comment="Community") community = Column(
Integer, ForeignKey("community.id"), nullable=False, comment="Community"
)
body = Column(String, nullable=False, comment="Body") body = Column(String, nullable=False, comment="Body")
createdAt = Column(DateTime, nullable=False, default=datetime.now, comment="Created at") createdAt = Column(
DateTime, nullable=False, default=datetime.now, comment="Created at"
)
updatedAt = Column(DateTime, nullable=True, comment="Updated at") updatedAt = Column(DateTime, nullable=True, comment="Updated at")
replyTo = Column(ForeignKey("shout.slug"), nullable=True) replyTo = Column(ForeignKey("shout.slug"), nullable=True)
versionOf = Column(ForeignKey("shout.slug"), nullable=True) versionOf = Column(ForeignKey("shout.slug"), nullable=True)

View File

@ -17,7 +17,9 @@ class TopicFollower(Base):
id = None # type: ignore id = None # type: ignore
follower = Column(ForeignKey("user.slug"), primary_key=True) follower = Column(ForeignKey("user.slug"), primary_key=True)
topic = Column(ForeignKey("topic.slug"), primary_key=True) topic = Column(ForeignKey("topic.slug"), primary_key=True)
createdAt = Column(DateTime, nullable=False, default=datetime.now, comment="Created at") createdAt = Column(
DateTime, nullable=False, default=datetime.now, comment="Created at"
)
class Topic(Base): class Topic(Base):
@ -28,6 +30,10 @@ class Topic(Base):
title = Column(String, nullable=False, comment="Title") title = Column(String, nullable=False, comment="Title")
body = Column(String, nullable=True, comment="Body") body = Column(String, nullable=True, comment="Body")
pic = Column(String, nullable=True, comment="Picture") pic = Column(String, nullable=True, comment="Picture")
children = Column(JSONType, nullable=True, default=[], comment="list of children topics") children = Column(
community = Column(ForeignKey("community.slug"), nullable=False, comment="Community") JSONType, nullable=True, default=[], comment="list of children topics"
)
community = Column(
ForeignKey("community.slug"), nullable=False, comment="Community"
)
oid = Column(String, nullable=True, comment="Old ID") oid = Column(String, nullable=True, comment="Old ID")

View File

@ -45,7 +45,9 @@ class AuthorFollower(Base):
id = None # type: ignore id = None # type: ignore
follower = Column(ForeignKey("user.slug"), primary_key=True) follower = Column(ForeignKey("user.slug"), primary_key=True)
author = Column(ForeignKey("user.slug"), primary_key=True) author = Column(ForeignKey("user.slug"), primary_key=True)
createdAt = Column(DateTime, nullable=False, default=datetime.now, comment="Created at") createdAt = Column(
DateTime, nullable=False, default=datetime.now, comment="Created at"
)
class User(Base): class User(Base):
@ -60,8 +62,12 @@ class User(Base):
slug = Column(String, unique=True, comment="User's slug") slug = Column(String, unique=True, comment="User's slug")
muted = Column(Boolean, default=False) muted = Column(Boolean, default=False)
emailConfirmed = Column(Boolean, default=False) emailConfirmed = Column(Boolean, default=False)
createdAt = Column(DateTime, nullable=False, default=datetime.now, comment="Created at") createdAt = Column(
wasOnlineAt = Column(DateTime, nullable=False, default=datetime.now, comment="Was online at") DateTime, nullable=False, default=datetime.now, comment="Created at"
)
wasOnlineAt = Column(
DateTime, nullable=False, default=datetime.now, comment="Was online at"
)
deletedAt = Column(DateTime, nullable=True, comment="Deleted at") deletedAt = Column(DateTime, nullable=True, comment="Deleted at")
links = Column(JSONType, nullable=True, comment="Links") links = Column(JSONType, nullable=True, comment="Links")
oauth = Column(String, nullable=True) oauth = Column(String, nullable=True)

View File

@ -42,7 +42,8 @@ from resolvers.reactions import (
update_reaction, update_reaction,
get_all_reactions, get_all_reactions,
) )
from resolvers.collab import invite_author, remove_author
# from resolvers.collab import invite_author, remove_author
from resolvers.editor import create_shout, delete_shout, update_shout from resolvers.editor import create_shout, delete_shout, update_shout
from resolvers.community import ( from resolvers.community import (
create_community, create_community,
@ -60,10 +61,12 @@ __all__ = [
"is_email_used", "is_email_used",
"confirm", "confirm",
"auth_forget", "auth_forget",
"auth_reset" "sign_out", "auth_reset",
"sign_out",
# profile # profile
"get_current_user", "get_current_user",
"get_users_by_slugs", "get_users_by_slugs",
"get_user_roles",
# zine # zine
"shouts_for_feed", "shouts_for_feed",
"my_candidates", "my_candidates",
@ -86,7 +89,7 @@ __all__ = [
"delete_shout", "delete_shout",
# collab # collab
"invite_author", "invite_author",
"remove_author" "remove_author",
# topics # topics
"topics_all", "topics_all",
"topics_by_community", "topics_by_community",

View File

@ -128,11 +128,11 @@ async def login(_, info: GraphQLResolveInfo, email: str, password: str = ""):
async def sign_out(_, info: GraphQLResolveInfo): async def sign_out(_, info: GraphQLResolveInfo):
token = info.context["request"].headers[JWT_AUTH_HEADER] token = info.context["request"].headers[JWT_AUTH_HEADER]
status = await Authorize.revoke(token) status = await Authorize.revoke(token)
return True return status
@query.field("isEmailUsed") @query.field("isEmailUsed")
async def is_email_used(_, info, email): async def is_email_used(_, info, email):
with local_session() as session: with local_session() as session:
user = session.query(User).filter(User.email == email).first() user = session.query(User).filter(User.email == email).first()
return not user is None return user is not None

View File

@ -4,15 +4,14 @@ from orm.user import User
from base.resolvers import mutation, query from base.resolvers import mutation, query
from auth.authenticate import login_required from auth.authenticate import login_required
from datetime import datetime from datetime import datetime
from typing import Collection
from sqlalchemy import and_ from sqlalchemy import and_
@mutation.field("createCollection") @mutation.field("createCollection")
@login_required @login_required
async def create_collection(_, info, input): async def create_collection(_, info, input):
auth = info.context["request"].auth # auth = info.context["request"].auth
user_id = auth.user_id # user_id = auth.user_id
collection = Collection.create( collection = Collection.create(
slug=input.get("slug", ""), slug=input.get("slug", ""),
title=input.get("title", ""), title=input.get("title", ""),
@ -73,35 +72,7 @@ async def get_user_collections(_, info, userslug):
collections = ( collections = (
session.query(Collection) session.query(Collection)
.where( .where(
and_( and_(Collection.createdBy == userslug, bool(Collection.publishedAt))
Collection.createdBy == userslug, Collection.publishedAt != None
)
)
.all()
)
for c in collections:
shouts = (
session.query(ShoutCollection)
.filter(ShoutCollection.collection == c.id)
.all()
)
c.amount = len(shouts)
return collections
@query.field("getMyCollections")
async def get_user_collections(_, info, userslug):
collections = []
with local_session() as session:
user = session.query(User).filter(User.slug == userslug).first()
if user:
# TODO: check rights here
collections = (
session.query(Collection)
.where(
and_(
Collection.createdBy == userslug, Collection.publishedAt != None
)
) )
.all() .all()
) )

View File

@ -13,12 +13,15 @@ from sqlalchemy import and_
async def create_community(_, info, input): async def create_community(_, info, input):
auth = info.context["request"].auth auth = info.context["request"].auth
user_id = auth.user_id user_id = auth.user_id
with local_session() as session:
user = session.query(User).where(User.id == user_id).first()
community = Community.create( community = Community.create(
slug=input.get("slug", ""), slug=input.get("slug", ""),
title=input.get("title", ""), title=input.get("title", ""),
desc=input.get("desc", ""), desc=input.get("desc", ""),
pic=input.get("pic", ""), pic=input.get("pic", ""),
createdBy=user.slug,
createdAt=datetime.now(),
) )
return {"community": community} return {"community": community}

View File

@ -28,10 +28,10 @@ async def create_shout(_, info, input):
topic_slugs.append(input["mainTopic"]) topic_slugs.append(input["mainTopic"])
for slug in topic_slugs: for slug in topic_slugs:
topic = ShoutTopic.create(shout=new_shout.slug, topic=slug) ShoutTopic.create(shout=new_shout.slug, topic=slug)
new_shout.topic_slugs = topic_slugs new_shout.topic_slugs = topic_slugs
task = GitTask(input, user.username, user.email, "new shout %s" % (new_shout.slug)) GitTask(input, user.username, user.email, "new shout %s" % (new_shout.slug))
# await ShoutCommentsStorage.send_shout(new_shout) # await ShoutCommentsStorage.send_shout(new_shout)
@ -54,10 +54,10 @@ async def update_shout(_, info, input):
return {"error": "shout not found"} return {"error": "shout not found"}
authors = [author.id for author in shout.authors] authors = [author.id for author in shout.authors]
if not user_id in authors: if user_id not in authors:
scopes = auth.scopes scopes = auth.scopes
print(scopes) print(scopes)
if not Resource.shout_id in scopes: if Resource.shout_id not in scopes:
return {"error": "access denied"} return {"error": "access denied"}
shout.update(input) shout.update(input)
@ -68,7 +68,7 @@ async def update_shout(_, info, input):
for topic in input.get("topic_slugs", []): for topic in input.get("topic_slugs", []):
ShoutTopic.create(shout=slug, topic=topic) ShoutTopic.create(shout=slug, topic=topic)
task = GitTask(input, user.username, user.email, "update shout %s" % (slug)) GitTask(input, user.username, user.email, "update shout %s" % (slug))
return {"shout": shout} return {"shout": shout}

View File

@ -41,7 +41,7 @@ async def user_unpublished_shouts(_, info, page=1, size=10) -> List[Shout]:
shouts = ( shouts = (
session.query(Shout) session.query(Shout)
.join(ShoutAuthor) .join(ShoutAuthor)
.where(and_(Shout.publishedAt == None, ShoutAuthor.user == user.slug)) .where(and_(not bool(Shout.publishedAt), ShoutAuthor.user == user.slug))
.order_by(desc(Shout.createdAt)) .order_by(desc(Shout.createdAt))
.limit(size) .limit(size)
.offset(page * size) .offset(page * size)

View File

@ -1,6 +1,8 @@
from base.resolvers import mutation, query, subscription from base.resolvers import mutation, query, subscription
from auth.authenticate import login_required from auth.authenticate import login_required
import asyncio, uuid, json import asyncio
import uuid
import json
from datetime import datetime from datetime import datetime
from base.redis import redis from base.redis import redis
@ -259,7 +261,7 @@ async def mark_as_read(_, info, chatId, ids):
chat = json.loads(chat) chat = json.loads(chat)
users = set(chat["users"]) users = set(chat["users"])
if not user.slug in users: if user.slug not in users:
return {"error": "access denied"} return {"error": "access denied"}
for id in ids: for id in ids:

View File

@ -1,5 +1,4 @@
from sqlalchemy import desc from sqlalchemy import desc
from sqlalchemy.orm import joinedload, selectinload
from orm.reaction import Reaction from orm.reaction import Reaction
from base.orm import local_session from base.orm import local_session
from orm.shout import ShoutReactionsFollower from orm.shout import ShoutReactionsFollower
@ -24,7 +23,7 @@ def reactions_follow(user, slug, auto=False):
if auto and fw: if auto and fw:
return return
elif not auto and fw: elif not auto and fw:
if not fw.deletedAt is None: if bool(fw.deletedAt):
fw.deletedAt = None fw.deletedAt = None
fw.auto = False fw.auto = False
session.commit() session.commit()
@ -130,17 +129,19 @@ async def get_shout_reactions(_, info, slug, page, size):
return reactions return reactions
@query.field("reactionsForSlugs") @query.field("reactionsForShouts")
async def get_shout_reactions(_, info, slugs, page, size): async def get_reactions_for_shouts(_, info, shoutslugs, page, size):
offset = page * size offset = page * size
reactions = [] reactions = []
with local_session() as session: with local_session() as session:
for slug in slugs: for slug in shoutslugs:
reactions += ( reactions += (
session.query(Reaction) session.query(Reaction)
.filter(Reaction.shout == slug) .filter(Reaction.shout == slug)
.limit(size) .where(not bool(Reaction.deletedAt))
.order_by(desc("createdAt"))
.offset(offset) .offset(offset)
.limit(size)
.all() .all()
) )
for r in reactions: for r in reactions:
@ -148,25 +149,6 @@ async def get_shout_reactions(_, info, slugs, page, size):
return reactions return reactions
@query.field("reactionsAll")
async def get_all_reactions(_, info, page=1, size=10):
offset = page * size
reactions = []
with local_session() as session:
reactions = (
session.query(Reaction)
.filter(Reaction.deletedAt == None)
.order_by(desc("createdAt"))
.offset(offset)
.limit(size)
)
for r in reactions:
r.createdBy = await UserStorage.get_user(r.createdBy or "discours")
reactions = list(reactions)
reactions.sort(key=lambda x: x.createdAt, reverse=True)
return reactions
@query.field("reactionsByAuthor") @query.field("reactionsByAuthor")
async def get_reactions_by_author(_, info, slug, page=1, size=50): async def get_reactions_by_author(_, info, slug, page=1, size=50):
offset = page * size offset = page * size

View File

@ -11,44 +11,44 @@ from resolvers.topics import topic_follow, topic_unfollow
from resolvers.community import community_follow, community_unfollow from resolvers.community import community_follow, community_unfollow
from resolvers.reactions import reactions_follow, reactions_unfollow from resolvers.reactions import reactions_follow, reactions_unfollow
from auth.authenticate import login_required from auth.authenticate import login_required
from sqlalchemy import select, desc, and_, text from sqlalchemy import select, desc, and_
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
@query.field("topViewed") @query.field("topViewed")
async def top_viewed(_, info, page, size): async def top_viewed(_, info, page, size):
async with ShoutsCache.lock: async with ShoutsCache.lock:
return ShoutsCache.top_viewed[(page - 1) * size : page * size] return ShoutsCache.top_viewed[((page - 1) * size) : (page * size)]
@query.field("topMonth") @query.field("topMonth")
async def top_month(_, info, page, size): async def top_month(_, info, page, size):
async with ShoutsCache.lock: async with ShoutsCache.lock:
return ShoutsCache.top_month[(page - 1) * size : page * size] return ShoutsCache.top_month[((page - 1) * size) : (page * size)]
@query.field("topOverall") @query.field("topOverall")
async def top_overall(_, info, page, size): async def top_overall(_, info, page, size):
async with ShoutsCache.lock: async with ShoutsCache.lock:
return ShoutsCache.top_overall[(page - 1) * size : page * size] return ShoutsCache.top_overall[((page - 1) * size) : (page * size)]
@query.field("recentPublished") @query.field("recentPublished")
async def recent_published(_, info, page, size): async def recent_published(_, info, page, size):
async with ShoutsCache.lock: async with ShoutsCache.lock:
return ShoutsCache.recent_published[(page - 1) * size : page * size] return ShoutsCache.recent_published[((page - 1) * size) : (page * size)]
@query.field("recentAll") @query.field("recentAll")
async def recent_all(_, info, page, size): async def recent_all(_, info, page, size):
async with ShoutsCache.lock: async with ShoutsCache.lock:
return ShoutsCache.recent_all[(page - 1) * size : page * size] return ShoutsCache.recent_all[((page - 1) * size) : (page * size)]
@query.field("recentReacted") @query.field("recentReacted")
async def recent_reacted(_, info, page, size): async def recent_reacted(_, info, page, size):
async with ShoutsCache.lock: async with ShoutsCache.lock:
return ShoutsCache.recent_reacted[(page - 1) * size : page * size] return ShoutsCache.recent_reacted[((page - 1) * size) : (page * size)]
@mutation.field("viewShout") @mutation.field("viewShout")
@ -66,10 +66,7 @@ async def get_shout_by_slug(_, info, slug):
select_options = [selectinload(getattr(Shout, field)) for field in selected_fields] select_options = [selectinload(getattr(Shout, field)) for field in selected_fields]
shout = {} shout = {}
with local_session() as session: with local_session() as session:
try: # s = text(open("src/queries/shout-by-slug.sql", "r").read() % slug)
s = text(open("src/queries/shout-by-slug.sql", "r").read() % slug)
except:
pass
shout = ( shout = (
session.query(Shout) session.query(Shout)
.options(select_options) .options(select_options)
@ -93,7 +90,7 @@ async def shouts_by_topics(_, info, slugs, page, size):
shouts = ( shouts = (
session.query(Shout) session.query(Shout)
.join(ShoutTopic) .join(ShoutTopic)
.where(and_(ShoutTopic.topic.in_(slugs), Shout.publishedAt != None)) .where(and_(ShoutTopic.topic.in_(slugs), bool(Shout.publishedAt)))
.order_by(desc(Shout.publishedAt)) .order_by(desc(Shout.publishedAt))
.limit(size) .limit(size)
.offset(page * size) .offset(page * size)
@ -106,14 +103,14 @@ async def shouts_by_topics(_, info, slugs, page, size):
@query.field("shoutsByCollection") @query.field("shoutsByCollection")
async def shouts_by_topics(_, info, collection, page, size): async def shouts_by_collection(_, info, collection, page, size):
page = page - 1 page = page - 1
shouts = [] shouts = []
with local_session() as session: with local_session() as session:
shouts = ( shouts = (
session.query(Shout) session.query(Shout)
.join(ShoutCollection, ShoutCollection.collection == collection) .join(ShoutCollection, ShoutCollection.collection == collection)
.where(and_(ShoutCollection.shout == Shout.slug, Shout.publishedAt != None)) .where(and_(ShoutCollection.shout == Shout.slug, bool(Shout.publishedAt)))
.order_by(desc(Shout.publishedAt)) .order_by(desc(Shout.publishedAt))
.limit(size) .limit(size)
.offset(page * size) .offset(page * size)
@ -132,7 +129,7 @@ async def shouts_by_authors(_, info, slugs, page, size):
shouts = ( shouts = (
session.query(Shout) session.query(Shout)
.join(ShoutAuthor) .join(ShoutAuthor)
.where(and_(ShoutAuthor.user.in_(slugs), Shout.publishedAt != None)) .where(and_(ShoutAuthor.user.in_(slugs), bool(Shout.publishedAt)))
.order_by(desc(Shout.publishedAt)) .order_by(desc(Shout.publishedAt))
.limit(size) .limit(size)
.offset(page * size) .offset(page * size)
@ -161,7 +158,7 @@ async def shouts_by_communities(_, info, slugs, page, size):
.join(ShoutTopic) .join(ShoutTopic)
.where( .where(
and_( and_(
Shout.publishedAt != None, bool(Shout.publishedAt),
ShoutTopic.topic.in_( ShoutTopic.topic.in_(
select(Topic.slug).where(Topic.community.in_(slugs)) select(Topic.slug).where(Topic.community.in_(slugs))
), ),

View File

@ -239,10 +239,9 @@ type Query {
recentAll(page: Int!, size: Int!): [Shout]! recentAll(page: Int!, size: Int!): [Shout]!
# reactons # reactons
reactionsAll(page: Int!, size: Int!): [Reaction]!
reactionsByAuthor(slug: String!, page: Int!, size: Int!): [Reaction]! reactionsByAuthor(slug: String!, page: Int!, size: Int!): [Reaction]!
reactionsByShout(slug: String!, page: Int!, size: Int!): [Reaction]! reactionsByShout(slug: String!, page: Int!, size: Int!): [Reaction]!
reactionsForSlugs(slugs: [String]!, page: Int!, size: Int!): [Reaction]! reactionsForShouts(shouts: [String]!, page: Int!, size: Int!): [Reaction]!
# collab # collab
getCollabs: [Collab]! getCollabs: [Collab]!

View File

@ -3,21 +3,28 @@ from settings import PORT
import sys import sys
if __name__ == '__main__': if __name__ == "__main__":
x = '' x = ""
if len(sys.argv) > 1: x = sys.argv[1] if len(sys.argv) > 1:
x = sys.argv[1]
if x == "dev": if x == "dev":
print("DEV MODE") print("DEV MODE")
headers = [ headers = [
("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD"), ("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD"),
("Access-Control-Allow-Origin", "http://localhost:3000"), ("Access-Control-Allow-Origin", "http://localhost:3000"),
("Access-Control-Allow-Headers", "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range"), (
"Access-Control-Allow-Headers",
"DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range",
),
("Access-Control-Expose-Headers", "Content-Length,Content-Range"), ("Access-Control-Expose-Headers", "Content-Length,Content-Range"),
("Access-Control-Allow-Credentials", "true") ("Access-Control-Allow-Credentials", "true"),
] ]
uvicorn.run("main:app", host="localhost", port=8080, headers=headers) #, ssl_keyfile="discours.key", ssl_certfile="discours.crt", reload=True) uvicorn.run(
"main:app", host="localhost", port=8080, headers=headers
) # , ssl_keyfile="discours.key", ssl_certfile="discours.crt", reload=True)
elif x == "migrate": elif x == "migrate":
from migration import migrate from migration import migrate
migrate() migrate()
else: else:
uvicorn.run("main:app", host="0.0.0.0", port=PORT) uvicorn.run("main:app", host="0.0.0.0", port=PORT)

View File

@ -1,8 +1,8 @@
import asyncio import asyncio
from datetime import datetime from datetime import datetime
from sqlalchemy import Column, DateTime, ForeignKey, Boolean from sqlalchemy import Column, DateTime, ForeignKey, Boolean
from base.orm import Base from sqlalchemy.orm.attributes import flag_modified
from orm.reaction import Reaction from base.orm import Base, local_session
from orm.topic import ShoutTopic from orm.topic import ShoutTopic
from enum import Enum as Enumeration from enum import Enum as Enumeration
from sqlalchemy.types import Enum as ColumnEnum from sqlalchemy.types import Enum as ColumnEnum
@ -96,7 +96,9 @@ class ReactedStorage:
self = ReactedStorage self = ReactedStorage
async with self.lock: async with self.lock:
return list( return list(
filter(lambda r: r.comment, self.reacted["reactions"].get(reaction_id, {})) filter(
lambda r: r.comment, self.reacted["reactions"].get(reaction_id, {})
)
) )
@staticmethod @staticmethod
@ -133,10 +135,18 @@ class ReactedStorage:
return rating return rating
@staticmethod @staticmethod
async def increment(reaction: Reaction): # type: ignore async def react(reaction):
self = ReactedStorage self = ReactedStorage
async with self.lock:
reactions = self.reacted["shouts"].get(reaction.shout)
if reaction.replyTo:
reactions = self.reacted["reactions"].get(reaction.id)
for r in reactions.values():
r = { r = {
"day": datetime.now().replace(hour=0, minute=0, second=0, microsecond=0), "day": datetime.now().replace(
hour=0, minute=0, second=0, microsecond=0
),
"reaction": reaction.id, "reaction": reaction.id,
"kind": reaction.kind, "kind": reaction.kind,
"shout": reaction.shout, "shout": reaction.shout,
@ -146,16 +156,24 @@ class ReactedStorage:
if reaction.body: if reaction.body:
r["comment"] = True r["comment"] = True
reaction: ReactedByDay = ReactedByDay.create(**r) # type: ignore reaction: ReactedByDay = ReactedByDay.create(**r) # type: ignore
self.reacted["shouts"][reaction.shout] = self.reacted["shouts"].get(reaction.shout, []) self.reacted["shouts"][reaction.shout] = self.reacted["shouts"].get(
reaction.shout, []
)
self.reacted["shouts"][reaction.shout].append(reaction) self.reacted["shouts"][reaction.shout].append(reaction)
if reaction.replyTo: if reaction.replyTo:
self.reacted["reaction"][reaction.replyTo] = self.reacted["reactions"].get(reaction.shout, []) self.reacted["reaction"][reaction.replyTo] = self.reacted[
"reactions"
].get(reaction.shout, [])
self.reacted["reaction"][reaction.replyTo].append(reaction) self.reacted["reaction"][reaction.replyTo].append(reaction)
self.rating["reactions"][reaction.replyTo] = \ self.rating["reactions"][reaction.replyTo] = self.rating[
self.rating["reactions"].get(reaction.replyTo, 0) + kind_to_rate(reaction.kind) "reactions"
].get(reaction.replyTo, 0) + kind_to_rate(reaction.kind)
else: else:
self.rating["shouts"][reaction.replyTo] = \ self.rating["shouts"][reaction.replyTo] = self.rating["shouts"].get(
self.rating["shouts"].get(reaction.shout, 0) + kind_to_rate(reaction.kind) reaction.shout, 0
) + kind_to_rate(reaction.kind)
flag_modified(r, "value")
@staticmethod @staticmethod
def init(session): def init(session):
@ -164,11 +182,15 @@ class ReactedStorage:
print("[stat.reacted] %d reactions total" % len(all_reactions)) print("[stat.reacted] %d reactions total" % len(all_reactions))
for reaction in all_reactions: for reaction in all_reactions:
shout = reaction.shout shout = reaction.shout
topics = (session.query(ShoutTopic.topic).where(ShoutTopic.shout == shout).all()) topics = (
session.query(ShoutTopic.topic).where(ShoutTopic.shout == shout).all()
)
kind = reaction.kind kind = reaction.kind
self.reacted["shouts"][shout] = self.reacted["shouts"].get(shout, []) self.reacted["shouts"][shout] = self.reacted["shouts"].get(shout, [])
self.reacted["shouts"][shout].append(reaction) self.reacted["shouts"][shout].append(reaction)
self.rating["shouts"][shout] = self.rating["shouts"].get(shout, 0) + kind_to_rate(kind) self.rating["shouts"][shout] = self.rating["shouts"].get(
shout, 0
) + kind_to_rate(kind)
for t in topics: for t in topics:
self.reacted["topics"][t] = self.reacted["topics"].get(t, []) self.reacted["topics"][t] = self.reacted["topics"].get(t, [])
@ -180,12 +202,54 @@ class ReactedStorage:
) # rating ) # rating
if reaction.replyTo: if reaction.replyTo:
self.reacted["reactions"][reaction.replyTo] = \ self.reacted["reactions"][reaction.replyTo] = self.reacted[
self.reacted["reactions"].get(reaction.replyTo, []) "reactions"
].get(reaction.replyTo, [])
self.reacted["reactions"][reaction.replyTo].append(reaction) self.reacted["reactions"][reaction.replyTo].append(reaction)
self.rating["reactions"][reaction.replyTo] = \ self.rating["reactions"][reaction.replyTo] = self.rating[
self.rating["reactions"].get(reaction.replyTo, 0) + kind_to_rate(reaction.kind) "reactions"
].get(reaction.replyTo, 0) + kind_to_rate(reaction.kind)
ttt = self.reacted["topics"].values() ttt = self.reacted["topics"].values()
print("[stat.reacted] %d topics reacted" % len(ttt)) print("[stat.reacted] %d topics reacted" % len(ttt))
print("[stat.reacted] %d shouts reacted" % len(self.reacted["shouts"])) print("[stat.reacted] %d shouts reacted" % len(self.reacted["shouts"]))
print("[stat.reacted] %d reactions reacted" % len(self.reacted["reactions"])) print("[stat.reacted] %d reactions reacted" % len(self.reacted["reactions"]))
@staticmethod
async def flush_changes(session):
self = ReactedStorage()
async with self.lock:
for slug in dict(self.reacted['shouts']).keys():
topics = session.query(ShoutTopic.topic).where(ShoutTopic.shout == slug).all()
reactions = self.reacted['shouts'][slug]
for ts in list(topics):
try:
tslug = ts.pop()
except Exception:
print(ts)
raise Exception('error')
topic_reactions = self.reacted["topics"][tslug]
if not topic_reactions:
topic_reactions = []
topic_reactions += reactions
print('[stat.reacted] topic {' + str(tslug) + "}: " + str(len(topic_reactions)))
reactions += list(self.reacted['reactions'].values())
for reaction in reactions:
if getattr(reaction, "modified", False):
session.add(reaction)
flag_modified(reaction, "value")
reaction.modified = False
for reaction in self.to_flush:
session.add(reaction)
self.to_flush.clear()
session.commit()
@staticmethod
async def worker():
while True:
try:
with local_session() as session:
await ReactedStorage().flush_changes(session)
print("[stat.reacted] periodical flush")
except Exception as err:
print("[stat.reacted] errror: %s" % (err))
await asyncio.sleep(ReactedStorage.period)

View File

@ -114,5 +114,5 @@ class ViewedStorage:
await ViewedStorage.flush_changes(session) await ViewedStorage.flush_changes(session)
print("[stat.viewed] periodical flush") print("[stat.viewed] periodical flush")
except Exception as err: except Exception as err:
print("[stat.viewed] errror: %s" % (err)) print("[stat.viewed] : %s" % (err))
await asyncio.sleep(ViewedStorage.period) await asyncio.sleep(ViewedStorage.period)

View File

@ -12,10 +12,8 @@ ERROR_URL_ON_FRONTEND = (
) )
DB_URL = ( DB_URL = (
environ.get("DATABASE_URL") environ.get("DATABASE_URL") or environ.get("DB_URL") or
or environ.get("DB_URL") "postgresql://postgres@localhost:5432/discoursio" or "sqlite:///db.sqlite3"
or "postgresql://postgres@localhost:5432/discoursio"
or "sqlite:///db.sqlite3"
) )
JWT_ALGORITHM = "HS256" JWT_ALGORITHM = "HS256"
JWT_SECRET_KEY = "8f1bd7696ffb482d8486dfbc6e7d16dd-secret-key" JWT_SECRET_KEY = "8f1bd7696ffb482d8486dfbc6e7d16dd-secret-key"