diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 0ba4761..3bf6427 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,7 +1,11 @@ +[0.2.15] +- chore: schema service removed + + [0.2.14] - fix: update_message, load_messages - fix: auth service timeout added -- dx: ruff +- dx: ruff [0.2.13] diff --git a/inbox.graphql b/inbox.graphql index 04e8b6e..9fc2dd2 100644 --- a/inbox.graphql +++ b/inbox.graphql @@ -1,7 +1,3 @@ -type _Service { - sdl: String -} - enum MessageStatus { NEW UPDATED diff --git a/main.py b/main.py index cdf1ba6..0be786b 100644 --- a/main.py +++ b/main.py @@ -6,8 +6,8 @@ from ariadne import load_schema_from_path, make_executable_schema from ariadne.asgi import GraphQL from starlette.applications import Starlette -from services.rediscache import redis from services.schema import resolvers +from services.rediscache import redis from settings import DEV_SERVER_PID_FILE_NAME, SENTRY_DSN, MODE import_module("resolvers") diff --git a/validators/chat.py b/models/chat.py similarity index 89% rename from validators/chat.py rename to models/chat.py index 6f1fe87..e2506f6 100644 --- a/validators/chat.py +++ b/models/chat.py @@ -1,7 +1,7 @@ from typing import TypedDict, Optional, List -from validators.member import ChatMember -from validators.message import Message +from models.member import ChatMember +from models.message import Message class Chat(TypedDict): diff --git a/validators/member.py b/models/member.py similarity index 100% rename from validators/member.py rename to models/member.py diff --git a/validators/message.py b/models/message.py similarity index 100% rename from validators/message.py rename to models/message.py diff --git a/pyproject.toml b/pyproject.toml index 5ef0965..217941d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "discoursio-inbox" -version = "0.2.14" +version = "0.2.15" description = "Inbox server for discours.io" authors = ["Tony Rewin "] @@ -12,7 +12,7 @@ authors = ["Tony Rewin "] python = "^3.12" sentry-sdk = "^1.32.0" redis = { extras = ["hiredis"], version = "^5.0.1" } -ariadne = "^0.20.1" +ariadne = "^0.21" starlette = "^0.31.1" uvicorn = "^0.23.0" httpx = "^0.25.0" @@ -63,4 +63,4 @@ line_length = 120 select = ["E4", "E7", "E9", "F"] ignore = [] line-length = 120 -target-version = "py312" \ No newline at end of file +target-version = "py312" diff --git a/resolvers/chats.py b/resolvers/chats.py index 529006c..74e1e98 100644 --- a/resolvers/chats.py +++ b/resolvers/chats.py @@ -5,9 +5,10 @@ from datetime import datetime, timezone from services.auth import login_required from services.rediscache import redis from services.schema import mutation -from validators.chat import Chat, ChatUpdate +from models.chat import Chat, ChatUpdate from services.presence import notify_chat + @mutation.field("updateChat") @login_required async def update_chat(_, info, chat_new: ChatUpdate): diff --git a/resolvers/load.py b/resolvers/load.py index 8ffe705..427ad14 100644 --- a/resolvers/load.py +++ b/resolvers/load.py @@ -6,9 +6,9 @@ from services.auth import login_required from services.core import get_my_followings, get_all_authors from services.rediscache import redis from services.schema import query -from validators.chat import Message, ChatPayload -from validators.member import ChatMember -from .chats import create_chat +from models.chat import Message, ChatPayload +from models.member import ChatMember +from resolvers.chats import create_chat async def get_unread_counter(chat_id: str, author_id: int) -> int: @@ -19,13 +19,11 @@ async def get_unread_counter(chat_id: str, author_id: int) -> int: # NOTE: not an API handler async def load_messages( chat_id: str, limit: int = 5, offset: int = 0, ids: Optional[List[int]] = None -) -> List[Message]: +) -> List[Message|None]: """load :limit messages for :chat_id with :offset""" - if ids is None: - ids = [] messages = [] try: - message_ids = [] + ids + message_ids = [] + (ids or []) if limit: mids = (await redis.lrange(f"chats/{chat_id}/message_ids", offset, offset + limit)) or [] message_ids += mids @@ -119,15 +117,16 @@ async def load_recipients(_, _info, limit=50, offset=0): onliners = (await redis.execute("SMEMBERS", "authors-online")) or [] r = [] all_authors: List[ChatMember] = await get_all_authors() - my_followings = await get_my_followings() - if len(my_followings) < limit: - my_followings = my_followings + all_authors[0 : limit - len(my_followings)] - for a in my_followings: - a["online"] = a["id"] in onliners - r.append(a) + my_followings: List[ChatMember] = await get_my_followings() + if all_authors: + if len(my_followings) < limit: + my_followings = my_followings + all_authors[0 : limit - len(my_followings)] + for a in my_followings: + a["online"] = a["id"] in onliners + r.append(a) - # NOTE: maybe sort members here + # NOTE: maybe sort members here - print(f"[resolvers.load] loadRecipients found {len(r)} members") + print(f"[resolvers.load] loadRecipients found {len(r)} members") - return {"members": r, "error": None} + return {"members": r, "error": None} diff --git a/resolvers/messages.py b/resolvers/messages.py index eed20f3..c2d6bad 100644 --- a/resolvers/messages.py +++ b/resolvers/messages.py @@ -1,11 +1,11 @@ import json -from datetime import datetime, timezone +import time from services.auth import login_required from services.presence import notify_message from services.rediscache import redis from services.schema import mutation -from validators.chat import Message +from models.chat import Message @mutation.field("createMessage") @@ -29,14 +29,14 @@ async def create_message(_, info, chat_id: str, body: str, reply_to=None): # Получение ID следующего сообщения message_id = await redis.execute("GET", f"chats/{chat_dict['id']}/next_message_id") message_id = int(message_id) if message_id else 0 - chat_id = chat_dict['id'] + chat_id = chat_dict["id"] # Создание нового сообщения new_message: Message = { "chat_id": chat_id, "id": message_id, "created_by": author_id, "body": body, - "created_at": int(datetime.now(tz=timezone.utc).timestamp()), + "created_at": int(time.time()), "updated_at": None, } @@ -109,9 +109,9 @@ async def update_message(_, info, message): message["chat_id"] = chat_id await notify_message(message, "update") - return { "message": message, "error": None } + return {"message": message, "error": None} else: - return { "message": message, "error": "cannot update, no message_id" } + return {"message": message, "error": "cannot update, no message_id"} @mutation.field("deleteMessage") @@ -164,7 +164,7 @@ async def mark_as_read(_, info, chat_id: str, message_id: int): if not message_data: return {"error": "message not exist"} message: Message = json.loads(message_data) - + await notify_message(message, "seen") return {"error": None} diff --git a/services/auth.py b/services/auth.py index e78033f..873d116 100644 --- a/services/auth.py +++ b/services/auth.py @@ -1,23 +1,17 @@ from functools import wraps - from httpx import AsyncClient, HTTPError - from settings import AUTH_URL -INTERNAL_AUTH_SERVER = "auth.discours.io" not in AUTH_URL - async def check_auth(req): - print("%r" % req) token = req.headers.get("Authorization") + headers = {"Authorization": token, "Content-Type": "application/json"} # "Bearer " + removed print(f"[services.auth] checking auth token: {token}") - query_name = "getSession" if INTERNAL_AUTH_SERVER else "session" - query_type = "mutation" if INTERNAL_AUTH_SERVER else "query" + query_name = "getSession" if "v2." in AUTH_URL else "session" + query_type = "mutation" if "v2." in AUTH_URL else "query" operation = "GetUserId" - headers = {"Authorization": token, "Content-Type": "application/json"} # "Bearer " + removed - gql = { "query": query_type + " " + operation + " { " + query_name + " { user { id } } " + " }", "operationName": operation, @@ -26,21 +20,15 @@ async def check_auth(req): async with AsyncClient(timeout=30.0) as client: response = await client.post(AUTH_URL, headers=headers, json=gql) - print(f"[services.auth] response: {response.status_code} {response.text}") + print(f"[services.auth] {AUTH_URL} response: {response.status_code}") if response.status_code != 200: return False, None r = response.json() - try: - user_id = ( - r.get("data", {}).get(query_name, {}).get("user", {}).get("id", None) - if INTERNAL_AUTH_SERVER - else r.get("data", {}).get(query_name, {}).get("user", {}).get("id", None) - ) + if r: + 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: - print(f"{e}: {r}") - return False, None + return False, None def login_required(f): diff --git a/services/core.py b/services/core.py index 395b41e..768caac 100644 --- a/services/core.py +++ b/services/core.py @@ -1,65 +1,62 @@ from httpx import AsyncClient from settings import API_BASE +from typing import List +from models.member import ChatMember headers = {"Content-Type": "application/json"} -INTERNAL_AUTH_SERVER = "v2." in API_BASE -async def get_all_authors(): +async def get_all_authors() -> List[ChatMember]: query_name = "authorsAll" query_type = "query" operation = "AuthorsAll" query_fields = "id slug userpic name" - headers = {"Content-Type": "application/json"} # "Bearer " + removed 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, []) except Exception: import traceback traceback.print_exc() - - if response.status_code != 200: - return None - r = response.json() - return r.get("data", {}).get(query_name) + return [] -async def get_my_followings(): +async def get_my_followings() -> List[ChatMember]: query_name = "loadMySubscriptions" query_type = "query" operation = "LoadMySubscriptions" query_fields = "id slug userpic name" - headers = {"Content-Type": "application/json"} # "Bearer " + removed 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=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() - - if response.status_code != 200: - return None - r = response.json() - data = r.get("data") - if data: - d = data.get(query_name) - if d: - authors = d.get("authors", []) - return authors - return [] + return [] diff --git a/services/presence.py b/services/presence.py index 125dfa8..5ee6427 100644 --- a/services/presence.py +++ b/services/presence.py @@ -1,11 +1,11 @@ import json from services.rediscache import redis -from validators.chat import Message, ChatUpdate +from models.chat import Message, ChatUpdate async def notify_message(message: Message, action="create"): - channel_name = f"message:{message["chat_id"]}" + channel_name = f"message:{message['chat_id']}" data = {"payload": message, "action": action} try: await redis.publish(channel_name, json.dumps(data)) diff --git a/services/rediscache.py b/services/rediscache.py index d878609..5282471 100644 --- a/services/rediscache.py +++ b/services/rediscache.py @@ -1,5 +1,4 @@ import redis.asyncio as aredis - from settings import REDIS_URL diff --git a/services/schema.py b/services/schema.py index 55f7653..8bd983e 100644 --- a/services/schema.py +++ b/services/schema.py @@ -2,15 +2,4 @@ from ariadne import QueryType, MutationType query = QueryType() mutation = MutationType() - - -@query.field("_service") -def resolve_service(*_): - # Load the full SDL from your SDL file - with open("inbox.graphql", "r") as file: - full_sdl = file.read() - - return {"sdl": full_sdl} - - resolvers = [query, mutation]