This commit is contained in:
parent
80fed4049a
commit
9b03e625f4
|
@ -1,6 +1,7 @@
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
import logging
|
||||||
|
|
||||||
from models.chat import Chat, ChatUpdate
|
from models.chat import Chat, ChatUpdate
|
||||||
from services.auth import login_required
|
from services.auth import login_required
|
||||||
|
@ -8,6 +9,8 @@ from services.presence import notify_chat
|
||||||
from services.rediscache import redis
|
from services.rediscache import redis
|
||||||
from services.schema import mutation
|
from services.schema import mutation
|
||||||
|
|
||||||
|
logger = logging.getLogger("[resolvers.chats] ")
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
@mutation.field("update_chat")
|
@mutation.field("update_chat")
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -25,7 +28,7 @@ async def update_chat(_, info, chat_new: ChatUpdate):
|
||||||
chat_str = await redis.execute("GET", f"chats/{chat_id}")
|
chat_str = await redis.execute("GET", f"chats/{chat_id}")
|
||||||
if not chat_str:
|
if not chat_str:
|
||||||
return {"error": "chat not exist"}
|
return {"error": "chat not exist"}
|
||||||
else:
|
elif isinstance(chat_str, str):
|
||||||
chat: Chat = json.loads(chat_str)
|
chat: Chat = json.loads(chat_str)
|
||||||
if author_id in chat["admins"]:
|
if author_id in chat["admins"]:
|
||||||
chat.update(
|
chat.update(
|
||||||
|
@ -50,7 +53,7 @@ async def update_chat(_, info, chat_new: ChatUpdate):
|
||||||
async def create_chat(_, info, title="", members=None):
|
async def create_chat(_, info, title="", members=None):
|
||||||
members = members or []
|
members = members or []
|
||||||
author_id = info.context["author_id"]
|
author_id = info.context["author_id"]
|
||||||
chat = None
|
chat: Chat
|
||||||
if author_id:
|
if author_id:
|
||||||
if author_id not in members:
|
if author_id not in members:
|
||||||
members.append(int(author_id))
|
members.append(int(author_id))
|
||||||
|
@ -58,14 +61,18 @@ async def create_chat(_, info, title="", members=None):
|
||||||
# NOTE: private chats has no title
|
# NOTE: private chats has no title
|
||||||
# reuse private chat created before if exists
|
# reuse private chat created before if exists
|
||||||
if len(members) == 2 and title == "":
|
if len(members) == 2 and title == "":
|
||||||
chatset1 = set((await redis.execute("SMEMBERS", f"chats_by_author/{members[0]}")) or [])
|
chatdata1 = await redis.execute("SMEMBERS", f"chats_by_author/{members[0]}")
|
||||||
chatset2 = set((await redis.execute("SMEMBERS", f"chats_by_author/{members[1]}")) or [])
|
chatdata2 = await redis.execute("SMEMBERS", f"chats_by_author/{members[1]}")
|
||||||
|
if isinstance(chatdata1, list) and isinstance(chatdata2, list):
|
||||||
|
chatset1 = set(chatdata1)
|
||||||
|
chatset2 = set(chatdata2)
|
||||||
|
|
||||||
for c in chatset1.intersection(chatset2):
|
for c in chatset1.intersection(chatset2):
|
||||||
chat_data = await redis.execute("GET", f"chats/{c}")
|
chat_data = await redis.execute("GET", f"chats/{c}")
|
||||||
if chat_data:
|
if isinstance(chat_data, str):
|
||||||
chat = json.loads(chat_data)
|
chat = json.loads(chat_data)
|
||||||
if chat["title"] == "":
|
if chat["title"] == "":
|
||||||
print("[inbox] createChat found old chat")
|
logger.info("[inbox] createChat found old chat")
|
||||||
return {"chat": chat, "error": "existed"}
|
return {"chat": chat, "error": "existed"}
|
||||||
|
|
||||||
chat_id = str(uuid.uuid4())
|
chat_id = str(uuid.uuid4())
|
||||||
|
@ -90,6 +97,7 @@ async def create_chat(_, info, title="", members=None):
|
||||||
await redis.execute("SET", f"chats/{chat_id}/next_message_id", str(0))
|
await redis.execute("SET", f"chats/{chat_id}/next_message_id", str(0))
|
||||||
|
|
||||||
return {"error": None, "chat": chat}
|
return {"error": None, "chat": chat}
|
||||||
|
return {"error": "no chat was created"}
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("delete_chat")
|
@mutation.field("delete_chat")
|
||||||
|
@ -97,12 +105,11 @@ async def create_chat(_, info, title="", members=None):
|
||||||
async def delete_chat(_, info, chat_id: str):
|
async def delete_chat(_, info, chat_id: str):
|
||||||
author_id = info.context["author_id"]
|
author_id = info.context["author_id"]
|
||||||
chat_str = await redis.execute("GET", f"chats/{chat_id}")
|
chat_str = await redis.execute("GET", f"chats/{chat_id}")
|
||||||
if chat_str:
|
if isinstance(chat_str, str):
|
||||||
chat: Chat = json.loads(chat_str)
|
chat: Chat = json.loads(chat_str)
|
||||||
if author_id in chat["admins"]:
|
if author_id in chat["admins"]:
|
||||||
await redis.execute("DEL", f"chats/{chat_id}")
|
await redis.execute("DEL", f"chats/{chat_id}")
|
||||||
await redis.execute("SREM", f"chats_by_author/{author_id}", chat_id)
|
await redis.execute("SREM", f"chats_by_author/{author_id}", chat_id)
|
||||||
for member_id in chat["members"]:
|
for member_id in chat["members"]:
|
||||||
await notify_chat(chat, member_id, "delete")
|
await notify_chat(chat, member_id, "delete")
|
||||||
else:
|
|
||||||
return {"error": "chat not exist"}
|
return {"error": "chat not exist"}
|
||||||
|
|
|
@ -27,8 +27,9 @@ async def load_messages(
|
||||||
try:
|
try:
|
||||||
message_ids = [] + (ids or [])
|
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.execute("LRANGE", f"chats/{chat_id}/message_ids", offset, offset + limit)
|
||||||
message_ids += mids
|
if isinstance(mids, list):
|
||||||
|
message_ids.extend(mids)
|
||||||
if message_ids:
|
if message_ids:
|
||||||
message_keys = [f"chats/{chat_id}/messages/{mid}" for mid in message_ids]
|
message_keys = [f"chats/{chat_id}/messages/{mid}" for mid in message_ids]
|
||||||
messages = (await redis.mget(*message_keys)) or []
|
messages = (await redis.mget(*message_keys)) or []
|
||||||
|
|
|
@ -21,15 +21,15 @@ async def create_message(_, info, chat_id: str, body: str, reply_to=None):
|
||||||
# Если данных чата нет, возвращаем ошибку
|
# Если данных чата нет, возвращаем ошибку
|
||||||
if not chat_data:
|
if not chat_data:
|
||||||
return {"error": "chat is not exist"}
|
return {"error": "chat is not exist"}
|
||||||
else:
|
elif isinstance(chat_data, str):
|
||||||
# Преобразование данных чата из строки JSON в словарь
|
# Преобразование данных чата из строки JSON в словарь
|
||||||
chat_dict = json.loads(chat_data)
|
chat_dict = json.loads(chat_data)
|
||||||
print(chat_dict)
|
chat_id = chat_dict["id"]
|
||||||
|
|
||||||
# Получение 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")
|
||||||
|
if isinstance(message_id, str) or isinstance(message_id, int):
|
||||||
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"]
|
|
||||||
# Создание нового сообщения
|
# Создание нового сообщения
|
||||||
new_message: Message = {
|
new_message: Message = {
|
||||||
"chat_id": chat_id,
|
"chat_id": chat_id,
|
||||||
|
@ -38,6 +38,7 @@ async def create_message(_, info, chat_id: str, body: str, reply_to=None):
|
||||||
"body": body,
|
"body": body,
|
||||||
"created_at": int(time.time()),
|
"created_at": int(time.time()),
|
||||||
"updated_at": None,
|
"updated_at": None,
|
||||||
|
"reply_to": None
|
||||||
}
|
}
|
||||||
|
|
||||||
# Если есть ответ, добавляем его в сообщение
|
# Если есть ответ, добавляем его в сообщение
|
||||||
|
@ -74,6 +75,7 @@ async def create_message(_, info, chat_id: str, body: str, reply_to=None):
|
||||||
await notify_message(new_message, "create")
|
await notify_message(new_message, "create")
|
||||||
|
|
||||||
return {"message": new_message, "error": None}
|
return {"message": new_message, "error": None}
|
||||||
|
return {"error": "cannot create message"}
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("update_message")
|
@mutation.field("update_message")
|
||||||
|
@ -94,7 +96,7 @@ async def update_message(_, info, message):
|
||||||
message = await redis.execute("GET", f"chats/{chat_id}/messages/{message_id}")
|
message = await redis.execute("GET", f"chats/{chat_id}/messages/{message_id}")
|
||||||
if not message:
|
if not message:
|
||||||
return {"error": "message not exist"}
|
return {"error": "message not exist"}
|
||||||
|
elif isinstance(message, str):
|
||||||
message = json.loads(message)
|
message = json.loads(message)
|
||||||
if message["created_by"] != author_id:
|
if message["created_by"] != author_id:
|
||||||
return {"error": "access denied"}
|
return {"error": "access denied"}
|
||||||
|
@ -110,8 +112,8 @@ async def update_message(_, info, message):
|
||||||
await notify_message(message, "update")
|
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"}
|
||||||
|
|
||||||
|
|
||||||
@mutation.field("delete_message")
|
@mutation.field("delete_message")
|
||||||
|
@ -122,13 +124,15 @@ async def delete_message(_, info, chat_id: str, message_id: int):
|
||||||
chat_str = await redis.execute("GET", f"chats/{chat_id}")
|
chat_str = await redis.execute("GET", f"chats/{chat_id}")
|
||||||
if not chat_str:
|
if not chat_str:
|
||||||
return {"error": "chat not exist"}
|
return {"error": "chat not exist"}
|
||||||
|
elif isinstance(chat_str, str):
|
||||||
chat = json.loads(chat_str)
|
chat = json.loads(chat_str)
|
||||||
|
|
||||||
message_data = 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_data:
|
if not message_data:
|
||||||
return {"error": "message not exist"}
|
return {"error": "message not exist"}
|
||||||
|
elif isinstance(message_data, str):
|
||||||
message: Message = json.loads(message_data)
|
message: Message = json.loads(message_data)
|
||||||
if message["author"] != author_id:
|
if message["created_by"] != author_id:
|
||||||
return {"error": "access denied"}
|
return {"error": "access denied"}
|
||||||
|
|
||||||
await redis.execute("LREM", f"chats/{chat_id}/message_ids", 0, str(message_id))
|
await redis.execute("LREM", f"chats/{chat_id}/message_ids", 0, str(message_id))
|
||||||
|
@ -152,7 +156,7 @@ async def mark_as_read(_, info, chat_id: str, message_id: int):
|
||||||
chat_str = await redis.execute("GET", f"chats/{chat_id}")
|
chat_str = await redis.execute("GET", f"chats/{chat_id}")
|
||||||
if not chat_str:
|
if not chat_str:
|
||||||
return {"error": "chat not exist"}
|
return {"error": "chat not exist"}
|
||||||
|
if isinstance(chat_str, str):
|
||||||
chat = json.loads(chat_str)
|
chat = json.loads(chat_str)
|
||||||
members = set(chat["members"])
|
members = set(chat["members"])
|
||||||
if author_id not in members:
|
if author_id not in members:
|
||||||
|
@ -163,6 +167,7 @@ async def mark_as_read(_, info, chat_id: str, message_id: int):
|
||||||
message_data = 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_data:
|
if not message_data:
|
||||||
return {"error": "message not exist"}
|
return {"error": "message not exist"}
|
||||||
|
elif isinstance(message_data, str):
|
||||||
message: Message = json.loads(message_data)
|
message: Message = json.loads(message_data)
|
||||||
|
|
||||||
await notify_message(message, "seen")
|
await notify_message(message, "seen")
|
||||||
|
|
|
@ -19,11 +19,11 @@ 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"]
|
||||||
|
|
||||||
existed_chats = await redis.execute("SMEMBERS", f"/chats_by_author/{author_id}")
|
existed_chats = await redis.execute("SMEMBERS", f"/chats_by_author/{author_id}")
|
||||||
if isinstance(existed_chats, str):
|
if isinstance(existed_chats, set):
|
||||||
chats_list = list(json.loads(existed_chats))
|
chats_list = list(existed_chats)
|
||||||
for chat_id in chats_list[offset : (offset + limit)]:
|
for chat_id in chats_list[offset: (offset + limit)]:
|
||||||
members_ids = await redis.execute("GET", f"/chats/{chat_id}/members")
|
members_ids = await redis.execute("SMEMBERS", f"/chats/{chat_id}/members")
|
||||||
if isinstance(members_ids, list):
|
if isinstance(members_ids, set):
|
||||||
for member_id in members_ids:
|
for member_id in members_ids:
|
||||||
author = CacheStorage.authors_by_id.get(member_id)
|
author = CacheStorage.authors_by_id.get(member_id)
|
||||||
if author:
|
if author:
|
||||||
|
@ -36,6 +36,7 @@ async def search_recipients(_, info, text: str, limit: int = 50, offset: int = 0
|
||||||
return {"members": list(result), "error": None}
|
return {"members": list(result), "error": None}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@query.field("search_messages")
|
@query.field("search_messages")
|
||||||
@login_required
|
@login_required
|
||||||
async def search_messages(
|
async def search_messages(
|
||||||
|
|
|
@ -5,6 +5,10 @@ from starlette.exceptions import HTTPException
|
||||||
from services.core import get_author_by_user
|
from services.core import get_author_by_user
|
||||||
from settings import AUTH_URL
|
from settings import AUTH_URL
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("[services.auth] ")
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
async def check_auth(req) -> str | None:
|
async def check_auth(req) -> str | None:
|
||||||
token = req.headers.get("Authorization")
|
token = req.headers.get("Authorization")
|
||||||
|
@ -38,14 +42,14 @@ async def check_auth(req) -> str | None:
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
errors = data.get("errors")
|
errors = data.get("errors")
|
||||||
if errors:
|
if errors:
|
||||||
print(f"[services.auth] errors: {errors}")
|
logger.error(f"{errors}")
|
||||||
else:
|
else:
|
||||||
user_id = data.get("data", {}).get(query_name, {}).get("claims", {}).get("sub")
|
user_id = data.get("data", {}).get(query_name, {}).get("claims", {}).get("sub")
|
||||||
print(f"[services.auth] got user_id: {user_id}")
|
logger.info(f"[services.auth] got user_id: {user_id}")
|
||||||
return user_id
|
return user_id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Handling and logging exceptions during authentication check
|
# Handling and logging exceptions during authentication check
|
||||||
print(f"[services.auth] {e}")
|
logger.error(e)
|
||||||
|
|
||||||
if not user_id:
|
if not user_id:
|
||||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||||
|
|
|
@ -6,21 +6,30 @@ from datetime import datetime, timezone, timedelta
|
||||||
from models.member import ChatMember
|
from models.member import ChatMember
|
||||||
from settings import API_BASE
|
from settings import API_BASE
|
||||||
|
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("[services.core] ")
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
def _request_endpoint(query_name, body) -> dict:
|
def _request_endpoint(query_name, body) -> dict:
|
||||||
print(f"[services.core] requesting {query_name}...")
|
ts1 = time.time()
|
||||||
|
logger.debug(f"requesting {query_name}...")
|
||||||
response = requests.post(API_BASE, headers={"Content-Type": "application/json"}, json=body)
|
response = requests.post(API_BASE, headers={"Content-Type": "application/json"}, json=body)
|
||||||
print(f"[services.core] {query_name} response: <{response.status_code}> {response.text[:32]}..")
|
ts2 = time.time()
|
||||||
|
logger.debug(f"{query_name} response in {ts1-ts2} secs: <{response.status_code}> {response.text[:32]}..")
|
||||||
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
try:
|
try:
|
||||||
r = response.json()
|
r = response.json()
|
||||||
result = r.get("data", {}).get(query_name, {})
|
result = r.get("data", {}).get(query_name, {})
|
||||||
if result:
|
if result:
|
||||||
print(f"[services.core] entries amount in result: {len(result)} ")
|
logger.info(f"entries amount in result: {len(result)} ")
|
||||||
return result
|
return result
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(f"[services.core] Error decoding JSON response: {e}")
|
logger.error(f"Error decoding JSON response: {e}")
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -42,7 +51,7 @@ def get_author_by_user(user: str):
|
||||||
gql = {
|
gql = {
|
||||||
"query": f"query {operation}($user: String!) {{ {query_name}(user: $user){{ id }} }}",
|
"query": f"query {operation}($user: String!) {{ {query_name}(user: $user){{ id }} }}",
|
||||||
"operationName": operation,
|
"operationName": operation,
|
||||||
"variables": {"user": user},
|
"variables": {"user": user.strip()},
|
||||||
}
|
}
|
||||||
|
|
||||||
return _request_endpoint(query_name, gql)
|
return _request_endpoint(query_name, gql)
|
||||||
|
@ -74,14 +83,14 @@ class CacheStorage:
|
||||||
self = CacheStorage
|
self = CacheStorage
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
task = asyncio.create_task(self.worker())
|
task = asyncio.create_task(self.worker())
|
||||||
print(task)
|
logger.info(task)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def update_authors():
|
async def update_authors():
|
||||||
self = CacheStorage
|
self = CacheStorage
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
result = get_all_authors()
|
result = get_all_authors()
|
||||||
print(f"[services.core] loaded {len(result)}")
|
logger.info(f"cache loaded {len(result)}")
|
||||||
if result:
|
if result:
|
||||||
CacheStorage.authors = result
|
CacheStorage.authors = result
|
||||||
for a in result:
|
for a in result:
|
||||||
|
@ -95,20 +104,20 @@ class CacheStorage:
|
||||||
self = CacheStorage
|
self = CacheStorage
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
print("[services.core] - updating profiles data...")
|
logger.info(" - updating profiles data...")
|
||||||
await self.update_authors()
|
await self.update_authors()
|
||||||
failed = 0
|
failed = 0
|
||||||
except Exception as er:
|
except Exception as er:
|
||||||
failed += 1
|
failed += 1
|
||||||
print(f"[services.core] {er} - update failed #{failed}, wait 10 seconds")
|
logger.error(f"{er} - update failed #{failed}, wait 10 seconds")
|
||||||
if failed > 3:
|
if failed > 3:
|
||||||
print("[services.core] - not trying to update anymore")
|
logger.error(" - not trying to update anymore")
|
||||||
break
|
break
|
||||||
if failed == 0:
|
if failed == 0:
|
||||||
when = datetime.now(timezone.utc) + timedelta(seconds=self.period)
|
when = datetime.now(timezone.utc) + timedelta(seconds=self.period)
|
||||||
t = format(when.astimezone().isoformat())
|
t = format(when.astimezone().isoformat())
|
||||||
print("[services.core] ⎩ next update: %s" % (t.split("T")[0] + " " + t.split("T")[1].split(".")[0]))
|
logger.info(" ⎩ next update: %s" % (t.split("T")[0] + " " + t.split("T")[1].split(".")[0]))
|
||||||
await asyncio.sleep(self.period)
|
await asyncio.sleep(self.period)
|
||||||
else:
|
else:
|
||||||
await asyncio.sleep(10)
|
await asyncio.sleep(10)
|
||||||
print("[services.core] - trying to update data again")
|
logger.info(" - trying to update data again")
|
||||||
|
|
|
@ -24,7 +24,6 @@ class RedisCache:
|
||||||
return r
|
return r
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[redis] error: {e}")
|
print(f"[redis] error: {e}")
|
||||||
return None
|
|
||||||
|
|
||||||
async def subscribe(self, *channels):
|
async def subscribe(self, *channels):
|
||||||
if self._client:
|
if self._client:
|
||||||
|
@ -46,11 +45,6 @@ class RedisCache:
|
||||||
return
|
return
|
||||||
await self._client.publish(channel, data)
|
await self._client.publish(channel, data)
|
||||||
|
|
||||||
async def lrange(self, key, start, stop):
|
|
||||||
if self._client:
|
|
||||||
print(f"[redis] LRANGE {key} {start} {stop}")
|
|
||||||
return await self._client.lrange(key, start, stop)
|
|
||||||
|
|
||||||
async def mget(self, key, *keys):
|
async def mget(self, key, *keys):
|
||||||
if self._client:
|
if self._client:
|
||||||
print(f"[redis] MGET {key} {keys}")
|
print(f"[redis] MGET {key} {keys}")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user