formatted
This commit is contained in:
parent
4ddc424f29
commit
154633c114
|
@ -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
|
||||||
|
|
11
README.md
11
README.md
|
@ -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>`, в котором публикуюутся обновления всех переписок.
|
||||||
|
|
2
main.py
2
main.py
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"}
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
@ -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:
|
||||||
|
|
|
@ -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"}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ async def search_recipients(_, info, text: str, limit: int = 50, offset: int = 0
|
||||||
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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from uvicorn.main import logger
|
from uvicorn.main import logger
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import redis.asyncio as aredis
|
import redis.asyncio as aredis
|
||||||
|
|
||||||
from settings import REDIS_URL
|
from settings import REDIS_URL
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,17 +11,19 @@ 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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user