formatted

This commit is contained in:
Untone 2023-10-14 15:59:43 +03:00
parent 4ddc424f29
commit 154633c114
15 changed files with 63 additions and 48 deletions

View File

@ -4,5 +4,5 @@ root = true
indent_size = 4 indent_size = 4
end_of_line = lf end_of_line = lf
charset = utf-8 charset = utf-8
trim_trailing_whitespace=true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true

View File

@ -1,11 +1,14 @@
## `inbox`: Сервер для внутренних переписок ## `inbox`: Сервер для внутренних переписок
### ENV ### ENV
- REDIS_URL
- AUTH_URL - REDIS_URL
- API_BASE - AUTH_URL
- API_BASE
### Как это работает ### Как это работает
__Redis__: __Redis__:
- Для каждого пользователя создаётся запись в хранилищах `chats_by_author/<chat_id>` и `chats/<chat_id>` и канал redis `chat:<chat_id>`, в котором публикуюутся обновления всех переписок.
- Для каждого пользователя создаётся запись в хранилищах `chats_by_author/<chat_id>` и `chats/<chat_id>` и канал
redis `chat:<chat_id>`, в котором публикуюутся обновления всех переписок.

View File

@ -1,9 +1,11 @@
import os import os
from importlib import import_module from importlib import import_module
from os.path import exists from os.path import exists
from ariadne import load_schema_from_path, make_executable_schema 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.redis import redis from services.redis import redis
from services.schema import resolvers from services.schema import resolvers
from settings import DEV_SERVER_PID_FILE_NAME, SENTRY_DSN, MODE from settings import DEV_SERVER_PID_FILE_NAME, SENTRY_DSN, MODE

View File

@ -11,7 +11,7 @@ authors = ["Tony Rewin <anton.rewin@gmail.com>"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
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.20.1"
starlette = "^0.31.1" starlette = "^0.31.1"
uvicorn = "^0.23.0" uvicorn = "^0.23.0"
@ -20,6 +20,7 @@ itsdangerous = "^2.1.2"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^7.4.2" pytest = "^7.4.2"
black = { version = "^23.9.1", python = ">=3.12" }
[tool.black] [tool.black]
line-length = 120 line-length = 120

View File

@ -1,14 +1,13 @@
from resolvers.chats import create_chat, delete_chat, update_chat from resolvers.chats import create_chat, delete_chat, update_chat
from resolvers.load import load_chats, load_messages_by, load_recipients
from resolvers.messages import ( from resolvers.messages import (
create_message, create_message,
delete_message, delete_message,
update_message, update_message,
mark_as_read, mark_as_read,
) )
from resolvers.load import load_chats, load_messages_by, load_recipients
from resolvers.search import search_recipients from resolvers.search import search_recipients
__all__ = [ __all__ = [
# inbox # inbox
"load_chats", "load_chats",

View File

@ -1,10 +1,11 @@
import json import json
import uuid import uuid
from datetime import datetime, timezone from datetime import datetime, timezone
from validators.inbox import Chat
from services.auth import login_required from services.auth import login_required
from services.redis import redis from services.redis import redis
from services.schema import mutation from services.schema import mutation
from validators.inbox import Chat
@mutation.field("updateChat") @mutation.field("updateChat")
@ -107,4 +108,3 @@ async def delete_chat(_, info, chat_id: str):
await redis.execute("SREM", f"chats_by_author/{author_id}", chat_id) await redis.execute("SREM", f"chats_by_author/{author_id}", chat_id)
else: else:
return {"error": "chat not exist"} return {"error": "chat not exist"}

View File

@ -1,13 +1,15 @@
import asyncio
import json import json
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from services.auth import login_required
from services.core import get_author, get_network from services.core import get_author, get_network
from services.redis import redis from services.redis import redis
from services.auth import login_required
from services.schema import query from services.schema import query
from validators.inbox import Message, Chat, ChatMember from validators.inbox import Message, Chat, ChatMember
from .chats import create_chat from .chats import create_chat
from .unread import get_unread_counter from .unread import get_unread_counter
import asyncio
# NOTE: not an API handler # NOTE: not an API handler
async def load_messages( async def load_messages(
@ -21,10 +23,10 @@ async def load_messages(
message_ids = [] + ids message_ids = [] + ids
if limit: if limit:
mids = ( mids = (
await redis.lrange( await redis.lrange(
f"chats/{chat_id}/message_ids", offset, offset + limit f"chats/{chat_id}/message_ids", offset, offset + limit
) )
) or [] ) or []
mids = [mid for mid in mids] mids = [mid for mid in mids]
message_ids += mids message_ids += mids
if message_ids: if message_ids:
@ -54,7 +56,7 @@ async def load_chats(
author_id = info.context["author_id"] author_id = info.context["author_id"]
cids = (await redis.execute("SMEMBERS", f"chats_by_author/{author_id}")) or [] cids = (await redis.execute("SMEMBERS", f"chats_by_author/{author_id}")) or []
members_online = (await redis.execute("SMEMBERS", "authors-online")) or [] members_online = (await redis.execute("SMEMBERS", "authors-online")) or []
cids = list(cids)[offset : (offset + limit)] cids = list(cids)[offset: (offset + limit)]
chats = [] chats = []
lock = asyncio.Lock() lock = asyncio.Lock()
if len(cids) == 0: if len(cids) == 0:
@ -87,8 +89,8 @@ async def load_messages_by(_, info, by, limit: int = 10, offset: int = 0):
"""load :limit messages of :chat_id with :offset""" """load :limit messages of :chat_id with :offset"""
author_id = info.context["author_id"] author_id = info.context["author_id"]
user_chats = ( user_chats = (
await redis.execute("SMEMBERS", "chats_by_author/" + str(author_id)) await redis.execute("SMEMBERS", "chats_by_author/" + str(author_id))
) or [] ) or []
user_chats = [c for c in user_chats] user_chats = [c for c in user_chats]
if user_chats: if user_chats:
messages = [] messages = []

View File

@ -1,11 +1,12 @@
import json import json
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import List from typing import List
from validators.inbox import Message
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.redis import redis from services.redis import redis
from services.schema import mutation from services.schema import mutation
from validators.inbox import Message
@mutation.field("createMessage") @mutation.field("createMessage")
@ -26,16 +27,15 @@ async def create_message(_, info, chat: str, body: str, reply_to=None):
else: else:
chat_dict = json.loads(chat_data) chat_dict = json.loads(chat_data)
print(chat_dict) print(chat_dict)
message_id = ( message_id = await redis.execute("GET", f"chats/{chat_dict['id']}/next_message_id")
await redis.execute("GET", f"chats/{chat_dict['id']}/next_message_id") message_id = int(message_id) if message_id else 0
) or 0
message_id = int(message_id)
new_message: Message = { new_message: Message = {
"chat": chat_dict["id"], "chat": chat_dict["id"],
"id": message_id, "id": message_id,
"author": author_id, "author": author_id,
"body": body, "body": body,
"createdAt": int(datetime.now(tz=timezone.utc).timestamp()), "createdAt": int(datetime.now(tz=timezone.utc).timestamp()),
"updatedAt": None
} }
if reply_to: if reply_to:
new_message["replyTo"] = reply_to new_message["replyTo"] = reply_to
@ -110,10 +110,10 @@ async def delete_message(_, info, chat_id: str, message_id: int):
return {"error": "chat not exist"} return {"error": "chat not exist"}
chat = json.loads(chat) chat = json.loads(chat)
message = await redis.execute("GET", f"chats/{chat_id}/messages/{str(message_id)}") message_data = await redis.execute("GET", f"chats/{chat_id}/messages/{str(message_id)}")
if not message: if not message_data:
return {"error": "message not exist"} return {"error": "message not exist"}
message: Message = json.loads(message) message: Message = json.loads(message_data)
if message["author"] != author_id: if message["author"] != author_id:
return {"error": "access denied"} return {"error": "access denied"}

View File

@ -1,10 +1,11 @@
import json import json
from typing import Dict, Union, List, Any
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
from typing import Dict, Union, List, Any
from resolvers.load import load_messages
from services.auth import login_required from services.auth import login_required
from services.core import get_network from services.core import get_network
from services.redis import redis from services.redis import redis
from resolvers.load import load_messages
from services.schema import query from services.schema import query
@ -14,11 +15,11 @@ async def search_recipients(_, info, text: str, limit: int = 50, offset: int = 0
result = [] result = []
# TODO: maybe redis scan? # TODO: maybe redis scan?
author_id = info.context["author_id"] author_id = info.context["author_id"]
talk_before = await redis.execute("GET", f"/chats_by_author/{author_id}") talk_before = await redis.execute("GET", f"/chats_by_author/{author_id}")
if talk_before: if talk_before:
talk_before = list(json.loads(talk_before))[offset : (offset + limit)] talk_before = list(json.loads(talk_before))[offset: (offset + limit)]
for chat_id in talk_before: for chat_id in talk_before:
members = await redis.execute("GET", f"/chats/{chat_id}/members") members = await redis.execute("GET", f"/chats/{chat_id}/members")
if members: if members:
@ -68,8 +69,8 @@ async def search_in_chats(
mmm = list( mmm = list(
filter( filter(
lambda msg: int(datetime.now(tz=timezone.utc)) lambda msg: int(datetime.now(tz=timezone.utc))
- int(msg["createdAt"]) - int(msg["createdAt"])
< timedelta(days=days_ago), < timedelta(days=days_ago),
mmm, mmm,
) )
) )

View File

@ -1,7 +1,7 @@
from services.redis import redis
import json import json
from services.auth import login_required from services.auth import login_required
from services.redis import redis
async def get_unread_counter(chat_id: str, author_id: int) -> int: async def get_unread_counter(chat_id: str, author_id: int) -> int:

View File

@ -1,4 +1,5 @@
import sys import sys
import uvicorn import uvicorn
from uvicorn.main import logger from uvicorn.main import logger

View File

@ -1,8 +1,9 @@
import json import json
from functools import wraps from functools import wraps
from httpx import AsyncClient, HTTPError
from settings import AUTH_URL
from httpx import AsyncClient, HTTPError
from settings import AUTH_URL
INTERNAL_AUTH_SERVER = "v2.discours" in AUTH_URL or "testapi.discours" in AUTH_URL INTERNAL_AUTH_SERVER = "v2.discours" in AUTH_URL or "testapi.discours" in AUTH_URL
@ -19,12 +20,12 @@ async def check_auth(req):
gql = { gql = {
"query": query_type "query": query_type
+ " " + " "
+ operation + operation
+ " { " + " { "
+ query_name + query_name
+ " { user { id } } " + " { user { id } } "
+ " }", + " }",
"operationName": operation, "operationName": operation,
"variables": None, "variables": None,
} }

View File

@ -1,4 +1,5 @@
import json import json
from services.redis import redis from services.redis import redis
from validators.inbox import Message from validators.inbox import Message

View File

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

View File

@ -1,6 +1,7 @@
from typing import Dict, Optional, List from typing import TypedDict, Optional, List
class Message(Dict):
class Message(TypedDict):
id: int id: int
chat: str chat: str
author: int author: int
@ -10,20 +11,22 @@ class Message(Dict):
createdAt: int createdAt: int
updatedAt: Optional[int] updatedAt: Optional[int]
class Chat(Dict):
class Chat(TypedDict):
id: str id: str
members: List[int] members: List[int]
admins: List[int] admins: List[int]
title: str title: str
updatedAt: int updatedAt: Optional[int]
createdAt: int createdAt: int
createdBy: int createdBy: int
description: Optional[str] description: Optional[str]
class ChatMember(Dict):
class ChatMember(TypedDict):
id: int id: int
slug: str slug: str
name: str name: str
userpic: Optional[str] userpic: Optional[str]
lastSeen: int lastSeen: int
online: Optional[bool] online: Optional[bool]