This commit is contained in:
108
services/core.py
108
services/core.py
@@ -1,82 +1,18 @@
|
||||
from httpx import AsyncClient
|
||||
from settings import API_BASE
|
||||
from typing import List
|
||||
from models.member import ChatMember
|
||||
|
||||
from httpx import AsyncClient
|
||||
|
||||
from orm.author import Author
|
||||
from orm.shout import Shout
|
||||
from settings import API_BASE
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
|
||||
async def get_all_authors() -> List[ChatMember]:
|
||||
query_name = "authorsAll"
|
||||
query_type = "query"
|
||||
operation = "AuthorsAll"
|
||||
query_fields = "id slug pic name"
|
||||
|
||||
gql = {
|
||||
"query": query_type + " " + operation + " { " + query_name + " { " + query_fields + " } " + " }",
|
||||
"operationName": operation,
|
||||
"variables": None,
|
||||
}
|
||||
|
||||
async def _request_endpoint(query_name, body):
|
||||
async with AsyncClient() as client:
|
||||
try:
|
||||
response = await client.post(API_BASE, headers=headers, json=gql)
|
||||
print(f"[services.core] {query_name}: [{response.status_code}] {len(response.text)} bytes")
|
||||
if response.status_code != 200:
|
||||
return []
|
||||
r = response.json()
|
||||
if r:
|
||||
return r.get("data", {}).get(query_name, [])
|
||||
except Exception:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return []
|
||||
|
||||
|
||||
async def get_my_followings() -> List[ChatMember]:
|
||||
query_name = "loadMySubscriptions"
|
||||
query_type = "query"
|
||||
operation = "LoadMySubscriptions"
|
||||
query_fields = "id slug pic name"
|
||||
|
||||
gql = {
|
||||
"query": query_type + " " + operation + " { " + query_name + " { " + query_fields + " } " + " }",
|
||||
"operationName": operation,
|
||||
"variables": None,
|
||||
}
|
||||
|
||||
async with AsyncClient() as client:
|
||||
try:
|
||||
response = await client.post(API_BASE, headers=headers, json=gql)
|
||||
print(f"[services.core] {query_name}: [{response.status_code}] {len(response.text)} bytes")
|
||||
if response.status_code != 200:
|
||||
return []
|
||||
r = response.json()
|
||||
if r:
|
||||
return r.get("data", {}).get(query_name, {}).get("authors", [])
|
||||
except Exception:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return []
|
||||
|
||||
|
||||
async def get_author(author_id) -> Author:
|
||||
query_name = "getAuthor"
|
||||
query_type = "query"
|
||||
operation = "GetAuthor"
|
||||
query_fields = "id slug pic name"
|
||||
|
||||
gql = {
|
||||
"query": query_type + " " + operation + " { " + query_name + " { authors {" + query_fields + "} } " + " }",
|
||||
"operationName": operation,
|
||||
"variables": None,
|
||||
}
|
||||
|
||||
async with AsyncClient() as client:
|
||||
try:
|
||||
response = await client.post(API_BASE, headers=headers, json=gql)
|
||||
response = await client.post(API_BASE, headers=headers, json=body)
|
||||
print(f"[services.core] {query_name}: [{response.status_code}] {len(response.text)} bytes")
|
||||
if response.status_code != 200:
|
||||
return []
|
||||
@@ -89,3 +25,31 @@ async def get_author(author_id) -> Author:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
async def get_author(author_id) -> Author:
|
||||
query_name = "getAuthor"
|
||||
query_type = "query"
|
||||
operation = "GetAuthor"
|
||||
query_fields = "id slug pic name"
|
||||
|
||||
gql = {
|
||||
"query": query_type + " " + operation + " { " + query_name + " { " + query_fields + "м} " + " }",
|
||||
"operationName": operation,
|
||||
"variables": None,
|
||||
}
|
||||
|
||||
return await _request_endpoint(query_name, gql)
|
||||
|
||||
|
||||
async def get_followed_shouts(author_id: int) -> List[Shout]:
|
||||
query_name = "getFollowedShouts"
|
||||
query_type = "query"
|
||||
operation = "GetFollowedShouts"
|
||||
query_fields = "id slug title"
|
||||
|
||||
query = f"{query_type} {operation}($author_id: Int!) {{ {query_name}(author_id: $author_id) {{ {query_fields} }} }}"
|
||||
|
||||
body = {"query": query, "operationName": operation, "variables": {"author_id": author_id}}
|
||||
|
||||
return await _request_endpoint(query_name, body)
|
||||
|
@@ -1,164 +0,0 @@
|
||||
import asyncio
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import and_
|
||||
|
||||
from services.db import local_session
|
||||
from orm.shout import Shout
|
||||
from orm.author import Author
|
||||
from orm.user import User
|
||||
from orm.notification import NotificationAction, NotificationEntity, Notification
|
||||
from orm.reaction import ReactionKind, Reaction
|
||||
|
||||
|
||||
def shout_to_shout_data(shout):
|
||||
return {"title": shout.title, "slug": shout.slug}
|
||||
|
||||
|
||||
def user_to_user_data(user):
|
||||
return {"id": user.id, "name": user.name, "slug": user.slug, "userpic": user.userpic}
|
||||
|
||||
|
||||
def update_prev_notification(notification, user, reaction):
|
||||
notification_data = json.loads(notification.data)
|
||||
|
||||
notification_data["users"] = [u for u in notification_data["users"] if u["id"] != user.id]
|
||||
notification_data["users"].append(user_to_user_data(user))
|
||||
|
||||
if notification_data["reactionIds"] is None:
|
||||
notification_data["reactionIds"] = []
|
||||
notification_data["reactionIds"].append(reaction.id)
|
||||
|
||||
notification.data = json.dumps(notification_data, ensure_ascii=False)
|
||||
notification.seen = False
|
||||
notification.occurrences = notification.occurrences + 1
|
||||
notification.createdAt = datetime.now(tz=timezone.utc)
|
||||
|
||||
|
||||
class NewReactionNotificator:
|
||||
def __init__(self, reaction_id):
|
||||
self.reaction_id = reaction_id
|
||||
|
||||
async def run(self):
|
||||
with local_session() as session:
|
||||
reaction = session.query(Reaction).where(Reaction.id == self.reaction_id).one()
|
||||
shout = session.query(Shout).where(Shout.id == reaction.shout).one()
|
||||
user = session.query(User).where(User.id == reaction.created_by).one()
|
||||
notify_user_ids = []
|
||||
|
||||
if reaction.kind == ReactionKind.COMMENT:
|
||||
parent_reaction = None
|
||||
if reaction.replyTo:
|
||||
parent_reaction = session.query(Reaction).where(Reaction.id == reaction.replyTo).one()
|
||||
if parent_reaction.createdBy != reaction.createdBy:
|
||||
prev_new_reply_notification = (
|
||||
session.query(Notification)
|
||||
.where(
|
||||
and_(
|
||||
Notification.user == shout.created_by,
|
||||
Notification.action == NotificationAction.CREATE,
|
||||
Notification.entity == NotificationAction.REACTION,
|
||||
# Notification.shout == shout.id,
|
||||
# Notification.reaction == parent_reaction.id,
|
||||
# TODO: filter by payload content
|
||||
Notification.seen == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if prev_new_reply_notification:
|
||||
update_prev_notification(prev_new_reply_notification, user, reaction)
|
||||
else:
|
||||
reply_notification_data = json.dumps(
|
||||
{
|
||||
"shout": shout_to_shout_data(shout),
|
||||
"users": [user_to_user_data(user)],
|
||||
"reactionIds": [reaction.id],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
reply_notification = Notification.create(
|
||||
**{
|
||||
"user": parent_reaction.created_by,
|
||||
"action": NotificationAction.CREATE,
|
||||
"entity": NotificationEntity.REACTION,
|
||||
# TODO: filter by payload content
|
||||
# "shout": shout.id,
|
||||
# "reaction": parent_reaction.id,
|
||||
"data": reply_notification_data,
|
||||
}
|
||||
)
|
||||
|
||||
session.add(reply_notification)
|
||||
|
||||
notify_user_ids.append(parent_reaction.createdBy)
|
||||
|
||||
if reaction.createdBy != shout.createdBy and (
|
||||
parent_reaction is None or parent_reaction.created_by != shout.created_by
|
||||
):
|
||||
prev_new_comment_notification = (
|
||||
session.query(Notification)
|
||||
.where(
|
||||
and_(
|
||||
Notification.user == shout.created_by,
|
||||
Notification.action == NotificationAction.CREATE,
|
||||
Notification.entity == NotificationEntity.REACTION,
|
||||
Notification.seen == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if prev_new_comment_notification:
|
||||
update_prev_notification(prev_new_comment_notification, user, reaction)
|
||||
else:
|
||||
notification_data_string = json.dumps(
|
||||
{
|
||||
"shout": shout_to_shout_data(shout),
|
||||
"users": [user_to_user_data(user)],
|
||||
"reactionIds": [reaction.id],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
author_notification = Notification.create(
|
||||
**{
|
||||
"user": shout.created_by,
|
||||
"entity": NotificationEntity.REACTION,
|
||||
"action": NotificationAction.CREATE,
|
||||
"shout": shout.id,
|
||||
"data": notification_data_string,
|
||||
}
|
||||
)
|
||||
|
||||
session.add(author_notification)
|
||||
|
||||
notify_user_ids.append(shout.created_by)
|
||||
|
||||
session.commit()
|
||||
|
||||
for user_id in notify_user_ids:
|
||||
await connection_manager.notify_user(user_id)
|
||||
|
||||
|
||||
class NotificationService:
|
||||
def __init__(self):
|
||||
self._queue = asyncio.Queue(maxsize=1000)
|
||||
|
||||
async def handle_reaction(self, reaction_id):
|
||||
notificator = NewReactionNotificator(reaction_id)
|
||||
await self._queue.put(notificator)
|
||||
|
||||
async def worker(self):
|
||||
while True:
|
||||
notificator = await self._queue.get()
|
||||
try:
|
||||
await notificator.run()
|
||||
except Exception as e:
|
||||
print(f"[NotificationService.worker] error: {str(e)}")
|
||||
|
||||
|
||||
notification_service = NotificationService()
|
@@ -1,43 +0,0 @@
|
||||
import json
|
||||
from services.rediscache import redis
|
||||
from servies.notifier import notification_service
|
||||
|
||||
|
||||
# Каналы для прослушивания
|
||||
channels = ["reaction", "shout"]
|
||||
pubsubs = []
|
||||
|
||||
|
||||
def create_notification_channel(redis_conn, channel_name):
|
||||
pubsub = redis_conn.pubsub()
|
||||
pubsub.subscribe(channel_name)
|
||||
return pubsub
|
||||
|
||||
|
||||
def close_notification_channel(pubsub):
|
||||
pubsub.unsubscribe()
|
||||
pubsub.close()
|
||||
|
||||
|
||||
def start():
|
||||
# Подписка на каналы
|
||||
pubsubs = [create_notification_channel(redis_conn, channel) for channel in channels]
|
||||
|
||||
try:
|
||||
# Бесконечный цикл прослушивания
|
||||
while True:
|
||||
for pubsub in pubsubs:
|
||||
msg = pubsub.get_message()
|
||||
notification_service.handle_reaction(msg["data"])
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
# Отписка от каналов при завершении
|
||||
for pubsub in pubsubs:
|
||||
close_notification_channel(pubsub)
|
||||
|
||||
|
||||
def stop():
|
||||
for pubsub in pubsubs:
|
||||
close_notification_channel(pubsub)
|
@@ -1,5 +0,0 @@
|
||||
from ariadne import QueryType, MutationType
|
||||
|
||||
query = QueryType()
|
||||
mutation = MutationType()
|
||||
resolvers = [query, mutation]
|
Reference in New Issue
Block a user