some-fixes
Some checks failed
deploy / deploy (push) Failing after 1m12s

This commit is contained in:
Untone 2023-11-22 15:09:24 +03:00
parent 4530b2a1e9
commit 856a331836
15 changed files with 63 additions and 90 deletions

View File

@ -1,7 +1,11 @@
[0.2.15]
- chore: schema service removed
[0.2.14] [0.2.14]
- fix: update_message, load_messages - fix: update_message, load_messages
- fix: auth service timeout added - fix: auth service timeout added
- dx: ruff - dx: ruff
[0.2.13] [0.2.13]

View File

@ -1,7 +1,3 @@
type _Service {
sdl: String
}
enum MessageStatus { enum MessageStatus {
NEW NEW
UPDATED UPDATED

View File

@ -6,8 +6,8 @@ from ariadne import load_schema_from_path, make_executable_schema
from ariadne.asgi import GraphQL from ariadne.asgi import GraphQL
from starlette.applications import Starlette from starlette.applications import Starlette
from services.rediscache import redis
from services.schema import resolvers from services.schema import resolvers
from services.rediscache import redis
from settings import DEV_SERVER_PID_FILE_NAME, SENTRY_DSN, MODE from settings import DEV_SERVER_PID_FILE_NAME, SENTRY_DSN, MODE
import_module("resolvers") import_module("resolvers")

View File

@ -1,7 +1,7 @@
from typing import TypedDict, Optional, List from typing import TypedDict, Optional, List
from validators.member import ChatMember from models.member import ChatMember
from validators.message import Message from models.message import Message
class Chat(TypedDict): class Chat(TypedDict):

View File

@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry] [tool.poetry]
name = "discoursio-inbox" name = "discoursio-inbox"
version = "0.2.14" version = "0.2.15"
description = "Inbox server for discours.io" description = "Inbox server for discours.io"
authors = ["Tony Rewin <anton.rewin@gmail.com>"] authors = ["Tony Rewin <anton.rewin@gmail.com>"]
@ -12,7 +12,7 @@ authors = ["Tony Rewin <anton.rewin@gmail.com>"]
python = "^3.12" python = "^3.12"
sentry-sdk = "^1.32.0" sentry-sdk = "^1.32.0"
redis = { extras = ["hiredis"], version = "^5.0.1" } redis = { extras = ["hiredis"], version = "^5.0.1" }
ariadne = "^0.20.1" ariadne = "^0.21"
starlette = "^0.31.1" starlette = "^0.31.1"
uvicorn = "^0.23.0" uvicorn = "^0.23.0"
httpx = "^0.25.0" httpx = "^0.25.0"
@ -63,4 +63,4 @@ line_length = 120
select = ["E4", "E7", "E9", "F"] select = ["E4", "E7", "E9", "F"]
ignore = [] ignore = []
line-length = 120 line-length = 120
target-version = "py312" target-version = "py312"

View File

@ -5,9 +5,10 @@ from datetime import datetime, timezone
from services.auth import login_required from services.auth import login_required
from services.rediscache import redis from services.rediscache import redis
from services.schema import mutation from services.schema import mutation
from validators.chat import Chat, ChatUpdate from models.chat import Chat, ChatUpdate
from services.presence import notify_chat from services.presence import notify_chat
@mutation.field("updateChat") @mutation.field("updateChat")
@login_required @login_required
async def update_chat(_, info, chat_new: ChatUpdate): async def update_chat(_, info, chat_new: ChatUpdate):

View File

@ -6,9 +6,9 @@ from services.auth import login_required
from services.core import get_my_followings, get_all_authors from services.core import get_my_followings, get_all_authors
from services.rediscache import redis from services.rediscache import redis
from services.schema import query from services.schema import query
from validators.chat import Message, ChatPayload from models.chat import Message, ChatPayload
from validators.member import ChatMember from models.member import ChatMember
from .chats import create_chat from resolvers.chats import create_chat
async def get_unread_counter(chat_id: str, author_id: int) -> int: 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 # NOTE: not an API handler
async def load_messages( async def load_messages(
chat_id: str, limit: int = 5, offset: int = 0, ids: Optional[List[int]] = None 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""" """load :limit messages for :chat_id with :offset"""
if ids is None:
ids = []
messages = [] messages = []
try: try:
message_ids = [] + ids message_ids = [] + (ids or [])
if limit: if limit:
mids = (await redis.lrange(f"chats/{chat_id}/message_ids", offset, offset + limit)) or [] mids = (await redis.lrange(f"chats/{chat_id}/message_ids", offset, offset + limit)) or []
message_ids += mids message_ids += mids
@ -119,15 +117,16 @@ async def load_recipients(_, _info, limit=50, offset=0):
onliners = (await redis.execute("SMEMBERS", "authors-online")) or [] onliners = (await redis.execute("SMEMBERS", "authors-online")) or []
r = [] r = []
all_authors: List[ChatMember] = await get_all_authors() all_authors: List[ChatMember] = await get_all_authors()
my_followings = await get_my_followings() my_followings: List[ChatMember] = await get_my_followings()
if len(my_followings) < limit: if all_authors:
my_followings = my_followings + all_authors[0 : limit - len(my_followings)] if len(my_followings) < limit:
for a in my_followings: my_followings = my_followings + all_authors[0 : limit - len(my_followings)]
a["online"] = a["id"] in onliners for a in my_followings:
r.append(a) 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}

View File

@ -1,11 +1,11 @@
import json import json
from datetime import datetime, timezone import time
from services.auth import login_required from services.auth import login_required
from services.presence import notify_message from services.presence import notify_message
from services.rediscache import redis from services.rediscache import redis
from services.schema import mutation from services.schema import mutation
from validators.chat import Message from models.chat import Message
@mutation.field("createMessage") @mutation.field("createMessage")
@ -29,14 +29,14 @@ async def create_message(_, info, chat_id: str, body: str, reply_to=None):
# Получение ID следующего сообщения # Получение ID следующего сообщения
message_id = await redis.execute("GET", f"chats/{chat_dict['id']}/next_message_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 message_id = int(message_id) if message_id else 0
chat_id = chat_dict['id'] chat_id = chat_dict["id"]
# Создание нового сообщения # Создание нового сообщения
new_message: Message = { new_message: Message = {
"chat_id": chat_id, "chat_id": chat_id,
"id": message_id, "id": message_id,
"created_by": author_id, "created_by": author_id,
"body": body, "body": body,
"created_at": int(datetime.now(tz=timezone.utc).timestamp()), "created_at": int(time.time()),
"updated_at": None, "updated_at": None,
} }
@ -109,9 +109,9 @@ async def update_message(_, info, message):
message["chat_id"] = chat_id message["chat_id"] = chat_id
await notify_message(message, "update") await notify_message(message, "update")
return { "message": message, "error": None } return {"message": message, "error": None}
else: else:
return { "message": message, "error": "cannot update, no message_id" } return {"message": message, "error": "cannot update, no message_id"}
@mutation.field("deleteMessage") @mutation.field("deleteMessage")
@ -164,7 +164,7 @@ async def mark_as_read(_, info, chat_id: str, message_id: int):
if not message_data: if not message_data:
return {"error": "message not exist"} return {"error": "message not exist"}
message: Message = json.loads(message_data) message: Message = json.loads(message_data)
await notify_message(message, "seen") await notify_message(message, "seen")
return {"error": None} return {"error": None}

View File

@ -1,23 +1,17 @@
from functools import wraps from functools import wraps
from httpx import AsyncClient, HTTPError from httpx import AsyncClient, HTTPError
from settings import AUTH_URL from settings import AUTH_URL
INTERNAL_AUTH_SERVER = "auth.discours.io" not in AUTH_URL
async def check_auth(req): async def check_auth(req):
print("%r" % req)
token = req.headers.get("Authorization") token = req.headers.get("Authorization")
headers = {"Authorization": token, "Content-Type": "application/json"} # "Bearer " + removed
print(f"[services.auth] checking auth token: {token}") print(f"[services.auth] checking auth token: {token}")
query_name = "getSession" if INTERNAL_AUTH_SERVER else "session" query_name = "getSession" if "v2." in AUTH_URL else "session"
query_type = "mutation" if INTERNAL_AUTH_SERVER else "query" query_type = "mutation" if "v2." in AUTH_URL else "query"
operation = "GetUserId" operation = "GetUserId"
headers = {"Authorization": token, "Content-Type": "application/json"} # "Bearer " + removed
gql = { gql = {
"query": query_type + " " + operation + " { " + query_name + " { user { id } } " + " }", "query": query_type + " " + operation + " { " + query_name + " { user { id } } " + " }",
"operationName": operation, "operationName": operation,
@ -26,21 +20,15 @@ async def check_auth(req):
async with AsyncClient(timeout=30.0) as client: async with AsyncClient(timeout=30.0) as client:
response = await client.post(AUTH_URL, headers=headers, json=gql) 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: if response.status_code != 200:
return False, None return False, None
r = response.json() r = response.json()
try: if r:
user_id = ( user_id = r.get("data", {}).get(query_name, {}).get("user", {}).get("id", None)
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)
)
is_authenticated = user_id is not None is_authenticated = user_id is not None
return is_authenticated, user_id return is_authenticated, user_id
except Exception as e: return False, None
print(f"{e}: {r}")
return False, None
def login_required(f): def login_required(f):

View File

@ -1,65 +1,62 @@
from httpx import AsyncClient from httpx import AsyncClient
from settings import API_BASE from settings import API_BASE
from typing import List
from models.member import ChatMember
headers = {"Content-Type": "application/json"} 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_name = "authorsAll"
query_type = "query" query_type = "query"
operation = "AuthorsAll" operation = "AuthorsAll"
query_fields = "id slug userpic name" query_fields = "id slug userpic name"
headers = {"Content-Type": "application/json"} # "Bearer " + removed
gql = { gql = {
"query": query_type + " " + operation + " { " + query_name + " { " + query_fields + " } " + " }", "query": query_type + " " + operation + " { " + query_name + " { " + query_fields + " } " + " }",
"operationName": operation, "operationName": operation,
"variables": None, "variables": None,
} }
async with AsyncClient() as client: async with AsyncClient() as client:
try: 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") 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: except Exception:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return []
if response.status_code != 200:
return None
r = response.json()
return r.get("data", {}).get(query_name)
async def get_my_followings(): async def get_my_followings() -> List[ChatMember]:
query_name = "loadMySubscriptions" query_name = "loadMySubscriptions"
query_type = "query" query_type = "query"
operation = "LoadMySubscriptions" operation = "LoadMySubscriptions"
query_fields = "id slug userpic name" query_fields = "id slug userpic name"
headers = {"Content-Type": "application/json"} # "Bearer " + removed
gql = { gql = {
"query": query_type + " " + operation + " { " + query_name + " { authors {" + query_fields + "} } " + " }", "query": query_type + " " + operation + " { " + query_name + " { authors {" + query_fields + "} } " + " }",
"operationName": operation, "operationName": operation,
"variables": None, "variables": None,
} }
async with AsyncClient() as client: async with AsyncClient() as client:
try: 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") 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: except Exception:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return []
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 []

View File

@ -1,11 +1,11 @@
import json import json
from services.rediscache import redis 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"): 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} data = {"payload": message, "action": action}
try: try:
await redis.publish(channel_name, json.dumps(data)) await redis.publish(channel_name, json.dumps(data))

View File

@ -1,5 +1,4 @@
import redis.asyncio as aredis import redis.asyncio as aredis
from settings import REDIS_URL from settings import REDIS_URL

View File

@ -2,15 +2,4 @@ from ariadne import QueryType, MutationType
query = QueryType() query = QueryType()
mutation = MutationType() 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] resolvers = [query, mutation]