0.2.15
Some checks failed
deploy / deploy (push) Failing after 1m58s

This commit is contained in:
Untone 2023-11-24 02:00:28 +03:00
parent c150d28447
commit a63cf24812
12 changed files with 187 additions and 139 deletions

View File

@ -1,3 +1,11 @@
[0.2.15]
- schema: Shout.created_by removed
- schema: Shout.mainTopic removed
- services: cached elasticsearch connector
- services: auth is using user_id from authorizer
- resolvers: notify_* usage fixes
- resolvers: login_required usage fixes
[0.2.14]
- schema: some fixes from migrator
- schema: .days -> .time_ago
@ -5,7 +13,6 @@
- services: db access simpler, no contextmanager
- services: removed Base.create() method
- services: rediscache updated
- resolvers: many minor fixes
- resolvers: get_reacted_shouts_updates as followedReactions query
[0.2.13]

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "discoursio-core"
version = "0.2.14"
version = "0.2.15"
description = "core module for discours.io"
authors = ["discoursio devteam"]
license = "MIT"

View File

@ -1,11 +1,6 @@
from resolvers.editor import create_shout, delete_shout, update_shout
from resolvers.author import (
load_authors_by,
update_profile,
get_authors_all,
rate_author
)
from resolvers.author import load_authors_by, update_profile, get_authors_all, rate_author
from resolvers.reaction import (
create_reaction,
@ -25,7 +20,7 @@ from resolvers.topic import (
)
from resolvers.follower import follow, unfollow
from resolvers.reader import load_shout, load_shouts_by
from resolvers.reader import load_shout, load_shouts_by, search
from resolvers.community import get_community, get_communities_all
__all__ = [
@ -62,4 +57,6 @@ __all__ = [
# community
"get_community",
"get_communities_all",
# search
"search",
]

View File

@ -90,9 +90,9 @@ async def author_followings(author_id: int):
@mutation.field("updateProfile")
@login_required
async def update_profile(_, info, profile):
author_id = info.context["author_id"]
user_id = info.context["user_id"]
with local_session() as session:
author = session.query(Author).where(Author.id == author_id).first()
author = session.query(Author).where(Author.user == user_id).first()
Author.update(author, profile)
session.add(author)
session.commit()
@ -206,12 +206,13 @@ async def followed_authors(follower_id):
@mutation.field("rateAuthor")
@login_required
async def rate_author(_, info, rated_user_id, value):
author_id = info.context["author_id"]
user_id = info.context["user_id"]
with local_session() as session:
rater = session.query(Author).filter(Author.user == user_id).first()
rating = (
session.query(AuthorRating)
.filter(and_(AuthorRating.rater == author_id, AuthorRating.user == rated_user_id))
.filter(and_(AuthorRating.rater == rater.id, AuthorRating.user == rated_user_id))
.first()
)
if rating:
@ -221,7 +222,7 @@ async def rate_author(_, info, rated_user_id, value):
return {}
else:
try:
rating = AuthorRating(rater=author_id, user=rated_user_id, value=value)
rating = AuthorRating(rater=rater.id, user=rated_user_id, value=value)
session.add(rating)
session.commit()
except Exception as err:

View File

@ -1,6 +1,8 @@
import time # For Unix timestamps
from sqlalchemy import and_, select
from sqlalchemy.orm import joinedload
from orm.author import Author
from services.auth import login_required
from services.db import local_session
from services.schema import mutation, query
@ -11,8 +13,11 @@ from services.notify import notify_shout
@query.field("loadDrafts")
@login_required
async def get_drafts(_, info):
author = info.context["request"].author
user_id = info.context["user_id"]
with local_session() as session:
author = session.query(Author).filter(Author.user == user_id).first()
q = (
select(Shout)
.options(
@ -23,7 +28,6 @@ async def get_drafts(_, info):
)
q = q.group_by(Shout.id)
shouts = []
with local_session() as session:
for [shout] in session.execute(q).unique():
shouts.append(shout)
return shouts
@ -32,10 +36,13 @@ async def get_drafts(_, info):
@mutation.field("createShout")
@login_required
async def create_shout(_, info, inp):
author_id = info.context["author_id"]
user_id = info.context["user_id"]
with local_session() as session:
author = session.query(Author).filter(Author.user == user_id).first()
topics = session.query(Topic).filter(Topic.slug.in_(inp.get("topics", []))).all()
# Replace datetime with Unix timestamp
authors = inp.get("authors", [])
if author.id not in authors:
authors.insert(0, author.id)
current_time = int(time.time())
new_shout = Shout(
**{
@ -45,11 +52,10 @@ async def create_shout(_, info, inp):
"description": inp.get("description"),
"body": inp.get("body", ""),
"layout": inp.get("layout"),
"authors": inp.get("authors", []),
"authors": authors,
"slug": inp.get("slug"),
"topics": inp.get("topics"),
"visibility": ShoutVisibility.AUTHORS,
"created_id": author_id,
"created_at": current_time, # Set created_at as Unix timestamp
}
)
@ -57,10 +63,10 @@ async def create_shout(_, info, inp):
t = ShoutTopic(topic=topic.id, shout=new_shout.id)
session.add(t)
# NOTE: shout made by one first author
sa = ShoutAuthor(shout=new_shout.id, author=author_id)
sa = ShoutAuthor(shout=new_shout.id, author=author.id)
session.add(sa)
session.add(new_shout)
reactions_follow(author_id, new_shout.id, True)
reactions_follow(author.id, new_shout.id, True)
session.commit()
if new_shout.slug is None:
@ -74,8 +80,9 @@ async def create_shout(_, info, inp):
@mutation.field("updateShout")
@login_required
async def update_shout(_, info, shout_id, shout_input=None, publish=False):
author_id = info.context["author_id"]
user_id = info.context["user_id"]
with local_session() as session:
author = session.query(Author).filter(Author.user == user_id).first()
shout = (
session.query(Shout)
.options(
@ -87,7 +94,7 @@ async def update_shout(_, info, shout_id, shout_input=None, publish=False):
)
if not shout:
return {"error": "shout not found"}
if shout.created_by != author_id:
if shout.created_by != author.id:
return {"error": "access denied"}
updated = False
if shout_input is not None:
@ -154,12 +161,13 @@ async def update_shout(_, info, shout_id, shout_input=None, publish=False):
@mutation.field("deleteShout")
@login_required
async def delete_shout(_, info, shout_id):
author_id = info.context["author_id"]
user_id = info.context["user_id"]
with local_session() as session:
author = session.query(Author).filter(Author.id == user_id).first()
shout = session.query(Shout).filter(Shout.id == shout_id).first()
if not shout:
return {"error": "invalid shout id"}
if author_id != shout.created_by:
if author.id not in shout.authors:
return {"error": "access denied"}
for author_id in shout.authors:
reactions_unfollow(author_id, shout_id)

View File

@ -7,32 +7,37 @@ from services.following import FollowingManager, FollowingResult
from services.db import local_session
from orm.author import Author
from services.notify import notify_follower
from services.schema import mutation
@login_required
@mutation.field("follow")
async def follow(_, info, what, slug):
follower_id = info.context["author_id"]
user_id = info.context["user_id"]
try:
with local_session() as session:
actor = session.query(Author).filter(Author.user == user_id).first()
if actor:
follower_id = actor.id
if what == "AUTHOR":
if author_follow(follower_id, slug):
result = FollowingResult("NEW", 'author', slug)
await FollowingManager.push('author', result)
with local_session() as session:
result = FollowingResult("NEW", "author", slug)
await FollowingManager.push("author", result)
author = session.query(Author.id).where(Author.slug == slug).one()
follower = session.query(Author).where(Author.id == follower_id).one()
notify_follower(follower.dict(), author.id)
await notify_follower(follower.dict(), author.id)
elif what == "TOPIC":
if topic_follow(follower_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 community_follow(follower_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(follower_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)}
@ -41,30 +46,33 @@ async def follow(_, info, what, slug):
@login_required
@mutation.field("unfollow")
async def unfollow(_, info, what, slug):
follower_id = info.context["author_id"]
user_id = info.context["user_id"]
try:
with local_session() as session:
actor = session.query(Author).filter(Author.user == user_id).first()
if actor:
follower_id = actor.id
if what == "AUTHOR":
if author_unfollow(follower_id, slug):
result = FollowingResult("DELETED", 'author', slug)
await FollowingManager.push('author', result)
with local_session() as session:
result = FollowingResult("DELETED", "author", slug)
await FollowingManager.push("author", result)
author = session.query(Author.id).where(Author.slug == slug).one()
follower = session.query(Author).where(Author.id == follower_id).one()
notify_follower(follower.dict(), author.id, "unfollow")
await notify_follower(follower.dict(), author.id, "unfollow")
elif what == "TOPIC":
if topic_unfollow(follower_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 community_unfollow(follower_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(follower_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

@ -159,18 +159,18 @@ def set_hidden(session, shout_id):
@mutation.field("createReaction")
@login_required
async def create_reaction(_, info, reaction):
author_id = info.context["author_id"]
user_id = info.context["user_id"]
with local_session() as session:
reaction["created_by"] = author_id
shout = session.query(Shout).where(Shout.id == reaction["shout"]).one()
author = session.query(Author).where(Author.user == user_id).first()
reaction["created_by"] = author.id
if reaction["kind"] in [ReactionKind.DISLIKE.name, ReactionKind.LIKE.name]:
existing_reaction = (
session.query(Reaction)
.where(
and_(
Reaction.shout == reaction["shout"],
Reaction.created_by == author_id,
Reaction.created_by == author.id,
Reaction.kind == reaction["kind"],
Reaction.reply_to == reaction.get("reply_to"),
)
@ -189,7 +189,7 @@ async def create_reaction(_, info, reaction):
.where(
and_(
Reaction.shout == reaction["shout"],
Reaction.created_by == author_id,
Reaction.created_by == author.id,
Reaction.kind == opposite_reaction_kind,
Reaction.reply_to == reaction.get("reply_to"),
)
@ -203,7 +203,7 @@ async def create_reaction(_, info, reaction):
r = Reaction(**reaction)
# Proposal accepting logix
if r.reply_to is not None and r.kind == ReactionKind.ACCEPT and author_id in shout.dict()["authors"]:
if r.reply_to is not None and r.kind == ReactionKind.ACCEPT and author.id in shout.dict()["authors"]:
replied_reaction = session.query(Reaction).where(Reaction.id == r.reply_to).first()
if replied_reaction and replied_reaction.kind == ReactionKind.PROPOSE:
if replied_reaction.range:
@ -218,18 +218,17 @@ async def create_reaction(_, info, reaction):
session.commit()
rdict = r.dict()
rdict["shout"] = shout.dict()
author = session.query(Author).where(Author.id == author_id).first()
rdict["created_by"] = author.dict()
# self-regulation mechanics
if check_to_hide(session, r):
set_hidden(session, r.shout)
elif check_to_publish(session, author_id, r):
elif check_to_publish(session, author.id, r):
set_published(session, r.shout)
try:
reactions_follow(author_id, reaction["shout"], True)
reactions_follow(author.id, reaction["shout"], True)
except Exception as e:
print(f"[resolvers.reactions] error on reactions auto following: {e}")
@ -244,7 +243,7 @@ async def create_reaction(_, info, reaction):
@mutation.field("updateReaction")
@login_required
async def update_reaction(_, info, rid, reaction):
author_id = info.context["author_id"]
user_id = info.context["user_id"]
with local_session() as session:
q = select(Reaction).filter(Reaction.id == rid)
q = add_reaction_stat_columns(q)
@ -254,7 +253,9 @@ async def update_reaction(_, info, rid, reaction):
if not r:
return {"error": "invalid reaction id"}
if r.created_by != author_id:
author = session.query(Author).filter(Author.user == user_id).first()
if author:
if r.created_by != author.id:
return {"error": "access denied"}
body = reaction.get("body")
if body:
@ -278,17 +279,20 @@ async def update_reaction(_, info, rid, reaction):
await notify_reaction(r.dict(), "update")
return {"reaction": r}
else:
return {"error": "user"}
@mutation.field("deleteReaction")
@login_required
async def delete_reaction(_, info, rid):
author_id = info.context["author_id"]
user_id = info.context["user_id"]
with local_session() as session:
r = session.query(Reaction).filter(Reaction.id == rid).first()
if not r:
return {"error": "invalid reaction id"}
if r.created_by != author_id:
author = session.query(Author).filter(Author.user == user_id).first()
if not author or r.created_by != author.id:
return {"error": "access denied"}
if r.kind in [ReactionKind.LIKE, ReactionKind.DISLIKE]:
@ -400,6 +404,11 @@ def reacted_shouts_updates(follower_id):
@login_required
@query.field("followedReactions")
async def get_reacted_shouts(_, info) -> List[Shout]:
author_id = info.context["author_id"]
shouts = reacted_shouts_updates(author_id)
user_id = info.context["user_id"]
with local_session() as session:
author = session.query(Author).filter(Author.user == user_id).first()
if author:
shouts = reacted_shouts_updates(author.id)
return shouts
else:
return []

View File

@ -4,10 +4,12 @@ from sqlalchemy.sql.expression import desc, asc, select, func, case, and_, nulls
from services.auth import login_required
from services.db import local_session
from services.schema import query
from orm.topic import TopicFollower
from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout, ShoutAuthor, ShoutTopic
from orm.author import AuthorFollower
from orm.author import AuthorFollower, Author
from services.search import SearchService
from services.viewed import ViewedStorage
@ -69,6 +71,7 @@ def apply_filters(q, filters, author_id=None):
return q
@query.field("loadShout")
async def load_shout(_, _info, slug=None, shout_id=None):
with local_session() as session:
q = select(Shout).options(
@ -111,6 +114,7 @@ async def load_shout(_, _info, slug=None, shout_id=None):
return None
@query.field("loadShoutsBy")
async def load_shouts_by(_, info, options):
"""
:param _:
@ -145,8 +149,13 @@ async def load_shouts_by(_, info, options):
q = add_stat_columns(q)
author_id = info.context["author_id"]
q = apply_filters(q, options.get("filters", {}), author_id)
user_id = info.context["user_id"]
filters = options.get("filters")
if filters:
with local_session() as session:
author = session.query(Author).filter(Author.user == user_id).first()
if author:
q = apply_filters(q, filters, author.id)
order_by = options.get("order_by", Shout.published_at)
@ -180,11 +189,13 @@ async def load_shouts_by(_, info, options):
@login_required
@query.field("loadFeed")
async def get_my_feed(_, info, options):
author_id = info.context["author_id"]
user_id = info.context["user_id"]
with local_session() as session:
author_followed_authors = select(AuthorFollower.author).where(AuthorFollower.follower == author_id)
author_followed_topics = select(TopicFollower.topic).where(TopicFollower.follower == author_id)
author = session.query(Author).filter(Author.user == user_id).first()
author_followed_authors = select(AuthorFollower.author).where(AuthorFollower.follower == author.id)
author_followed_topics = select(TopicFollower.topic).where(TopicFollower.follower == author.id)
subquery = (
select(Shout.id)
@ -209,7 +220,7 @@ async def get_my_feed(_, info, options):
)
q = add_stat_columns(q)
q = apply_filters(q, options.get("filters", {}), author_id)
q = apply_filters(q, options.get("filters", {}), author.id)
order_by = options.get("order_by", Shout.published_at)
@ -235,3 +246,11 @@ async def get_my_feed(_, info, options):
}
shouts.append(shout)
return shouts
@query.field("search")
async def search(_, info, text, limit=50, offset=0):
if text and len(text) > 2:
return SearchService.search(text, limit, offset)
else:
return []

View File

@ -135,13 +135,13 @@ def topic_follow(follower_id, slug):
return False
def topic_unfollow(user_id, slug):
def topic_unfollow(follower_id, slug):
try:
with local_session() as session:
sub = (
session.query(TopicFollower)
.join(Topic)
.filter(and_(TopicFollower.follower == user_id, Topic.slug == slug))
.filter(and_(TopicFollower.follower == follower_id, Topic.slug == slug))
.first()
)
if sub:
@ -153,6 +153,7 @@ def topic_unfollow(user_id, slug):
return False
@query.field("topicsRandom")
async def topics_random(_, info, amount=12):
q = select(Topic)
q = q.join(ShoutTopic)

View File

@ -334,4 +334,6 @@ type Query {
communitiesAll: [Community]
getCommunity: Community
search(text: String!, limit: Int, offset: Int): [Shout]
}

View File

@ -11,7 +11,7 @@ async def check_auth(req):
query_type = "query"
operation = "GetUserId"
headers = {"Authorization": "Bearer " + token, "Content-Type": "application/json"}
headers = {"Authorization": token, "Content-Type": "application/json"}
gql = {
"query": query_type + " " + operation + " { " + query_name + " { user { id } } " + " }",
@ -26,9 +26,7 @@ async def check_auth(req):
return False, None
r = response.json()
try:
user_id = (
r.get("data", {}).get(query_name, {}).get("user", {}).get("id", None)
)
user_id = r.get("data", {}).get(query_name, {}).get("user", {}).get("id", None)
is_authenticated = user_id is not None
return is_authenticated, user_id
except Exception as e:
@ -47,7 +45,7 @@ def login_required(f):
raise Exception("You are not logged in")
else:
# Добавляем author_id в контекст
context["author_id"] = user_id
context["user_id"] = user_id
# Если пользователь аутентифицирован, выполняем резолвер
return await f(*args, **kwargs)
@ -63,7 +61,7 @@ def auth_request(f):
if not is_authenticated:
raise HTTPError("please, login first")
else:
req["author_id"] = user_id
req["user_id"] = user_id
return await f(*args, **kwargs)
return decorated_function

View File

@ -1,8 +1,8 @@
import asyncio
import json
import httpx
from services.rediscache import redis
from orm.shout import Shout
from resolvers.reader import load_shouts_by
class SearchService:
@ -20,15 +20,13 @@ class SearchService:
cached = await redis.execute("GET", text)
if not cached:
async with SearchService.lock:
options = {
"title": text,
"body": text,
"limit": limit,
"offset": offset,
}
# FIXME: use elastic request here
payload = await load_shouts_by(None, None, options)
await redis.execute("SET", text, json.dumps(payload))
return payload
# Use httpx to send a request to ElasticSearch
async with httpx.AsyncClient() as client:
search_url = f"https://search.discours.io/search?q={text}"
response = await client.get(search_url)
if response.status_code == 200:
payload = response.json()
await redis.execute("SET", text, payload)
return json.loads(payload)
else:
return json.loads(cached)