diff --git a/Dockerfile b/Dockerfile index 519dae6..c9d28e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,6 @@ WORKDIR /app EXPOSE 8080 ADD nginx.conf.sigil ./ COPY requirements.txt . -RUN apt update && apt install -y gcc && pip install -r requirements.txt +RUN apt-get update && apt-get install -y gcc && pip install -r requirements.txt COPY . . CMD ["python", "server.py"] diff --git a/requirements.txt b/requirements.txt index c5e2ee8..6255926 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,14 @@ sentry-sdk -aioredis -ariadne -starlette -sqlalchemy +aioredis~=2.0.1 +ariadne~=0.20.1 +starlette~=0.31.1 +sqlalchemy~=2.0.21 graphql-core gql -uvicorn -httpx +uvicorn~=0.23.2 +aiohttp~=3.8.5 itsdangerous -pydantic +pydantic~=2.4.2 psycopg2-binary ######## development deps isort diff --git a/resolvers/messages.py b/resolvers/messages.py index 5725a7d..97356d9 100644 --- a/resolvers/messages.py +++ b/resolvers/messages.py @@ -1,6 +1,7 @@ import json from datetime import datetime, timezone from services.auth import login_required +from services.presence import notify_message from services.redis import redis from resolvers import mutation @@ -53,11 +54,7 @@ async def create_message(_, info, chat: str, body: str, reply_to=None): # await FollowingManager.push("chat", result) # subscribe on updates - channel_name = ( - f"chat:{chat['id']}" if not chat["title"] else f"group:{chat['id']}" - ) - new_message["kind"] = "new_message" - await redis.execute_pubsub("PUBLISH", channel_name, json.dumps(new_message)) + await notify_message(new_message, chat["id"]) return {"message": new_message, "error": None} diff --git a/server.py b/server.py index b79c14d..acb6887 100644 --- a/server.py +++ b/server.py @@ -2,12 +2,6 @@ import sys import uvicorn from settings import PORT - -def exception_handler(exception_type, exception, traceback, debug_hook=sys.excepthook): - print(vars(traceback)) - print("%s: %s" % (exception_type.__name__, exception)) - - log_settings = { "version": 1, "disable_existing_loggers": True, @@ -53,6 +47,11 @@ local_headers = [ ] +def exception_handler(exception_type, exception, traceback, debug_hook=sys.excepthook): + traceback.print_exc() + print("%s: %s" % (exception_type.__name__, exception)) + + if __name__ == "__main__": sys.excepthook = exception_handler uvicorn.run( diff --git a/services/auth.py b/services/auth.py index 8dd8313..96ee73e 100644 --- a/services/auth.py +++ b/services/auth.py @@ -1,10 +1,11 @@ from typing import Optional + +from aiohttp.web import HTTPUnauthorized +from aiohttp.client import ClientSession from pydantic import BaseModel from functools import wraps from starlette.authentication import AuthenticationBackend from starlette.requests import HTTPConnection -from graphql.error import GraphQLError -from httpx import AsyncClient from services.db import local_session from settings import AUTH_URL from orm.author import Author @@ -24,19 +25,13 @@ class AuthCredentials(BaseModel): class JWTAuthenticate(AuthenticationBackend): async def authenticate(self, request: HTTPConnection): - scopes = {} # TODO: integrate await user.get_permission logged_in, user_id = await check_auth(request) return ( - AuthCredentials(user_id=user_id, scopes=scopes, logged_in=logged_in), - AuthUser(user_id=user_id, username=""), + AuthCredentials(user_id=user_id, logged_in=logged_in), + AuthUser(user_id=user_id), ) -class Unauthorized(GraphQLError): - code = 401 - message = "401 Unauthorized" - - async def check_auth(req): token = req.headers.get("Authorization") gql = ( @@ -45,14 +40,16 @@ async def check_auth(req): else {"query": "{ session { user { id } } }"} ) headers = {"Authorization": token, "Content-Type": "application/json"} - async with AsyncClient() as client: - response = await client.post(AUTH_URL, headers=headers, data=gql) - if response.status_code != 200: - return False, None - r = response.json() - user_id = r.get("data", {}).get("session", {}).get("user", {}).get("id", None) - is_authenticated = user_id is not None - return is_authenticated, user_id + async with ClientSession(headers=headers) as session: + async with session.post(AUTH_URL, data=gql) as response: + if response.status != 200: + return False, None + r = await response.json() + user_id = ( + r.get("data", {}).get("session", {}).get("user", {}).get("id", None) + ) + is_authenticated = user_id is not None + return is_authenticated, user_id async def author_id_by_user_id(user_id): @@ -87,7 +84,7 @@ def auth_request(f): req = args[0] is_authenticated, user_id = await check_auth(req) if not is_authenticated: - raise Unauthorized("You are not logged in") + raise HTTPUnauthorized() else: author_id = await author_id_by_user_id(user_id) req["author_id"] = author_id diff --git a/services/presence.py b/services/presence.py new file mode 100644 index 0000000..0447e19 --- /dev/null +++ b/services/presence.py @@ -0,0 +1,11 @@ +import json +from redis import redis + + +async def notify_message(message, chat_id: str): + channel_name = f"chat:{chat_id}" + data = {**message, "kind": "new_message"} + try: + await redis.execute_pubsub("PUBLISH", channel_name, json.dumps(data)) + except Exception as e: + print(f"Failed to publish to channel {channel_name}: {e}")