diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 27bbeba..709f749 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,8 @@ +[0.2.10] +- middlwares removed +- orm removed +- added core api connector + [0.2.9] - starlette is back - auth middleware diff --git a/main.py b/main.py index f9aff34..b3d1b45 100644 --- a/main.py +++ b/main.py @@ -3,22 +3,12 @@ from os.path import exists from ariadne import load_schema_from_path, make_executable_schema from ariadne.asgi import GraphQL from starlette.applications import Starlette -from starlette.middleware import Middleware -from starlette.middleware.authentication import AuthenticationMiddleware -from starlette.middleware.sessions import SessionMiddleware - -from services.auth import JWTAuthenticate from services.redis import redis from resolvers import resolvers -from settings import DEV_SERVER_PID_FILE_NAME, SENTRY_DSN, SESSION_SECRET_KEY, MODE +from settings import DEV_SERVER_PID_FILE_NAME, SENTRY_DSN, MODE schema = make_executable_schema(load_schema_from_path("schema.graphql"), resolvers) # type: ignore -middleware = [ - Middleware(AuthenticationMiddleware, backend=JWTAuthenticate()), - Middleware(SessionMiddleware, secret_key=SESSION_SECRET_KEY), -] - async def start_up(): if MODE == "dev": diff --git a/orm/__init__.py b/orm/__init__.py deleted file mode 100644 index f4fc8f2..0000000 --- a/orm/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from services.db import Base, engine - - -def init_tables(): - Base.metadata.create_all(engine) - print("[orm] tables initialized") diff --git a/orm/author.py b/orm/author.py deleted file mode 100644 index 17530f8..0000000 --- a/orm/author.py +++ /dev/null @@ -1,41 +0,0 @@ -from datetime import datetime -from sqlalchemy import JSON as JSONType -from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String -from sqlalchemy.orm import relationship -from services.db import Base - - -class AuthorRating(Base): - __tablename__ = "author_rating" - - id = None # type: ignore - rater = Column(ForeignKey("author.id"), primary_key=True, index=True) - author = Column(ForeignKey("author.id"), primary_key=True, index=True) - value = Column(Integer) - - -class AuthorFollower(Base): - __tablename__ = "author_follower" - - id = None # type: ignore - follower = Column(ForeignKey("author.id"), primary_key=True, index=True) - author = Column(ForeignKey("author.id"), primary_key=True, index=True) - createdAt = Column(DateTime, nullable=False, default=datetime.now) - auto = Column(Boolean, nullable=False, default=False) - - -class Author(Base): - __tablename__ = "author" - - user = Column(Integer, nullable=False) # unbounded link with authorizer's User type - bio = Column(String, nullable=True, comment="Bio") # status description - about = Column(String, nullable=True, comment="About") # long and formatted - userpic = Column(String, nullable=True, comment="Userpic") - name = Column(String, nullable=True, comment="Display name") - slug = Column(String, unique=True, comment="Author's slug") - muted = Column(Boolean, default=False) - createdAt = Column(DateTime, nullable=False, default=datetime.now) - lastSeen = Column(DateTime, nullable=False, default=datetime.now) - deletedAt = Column(DateTime, nullable=True, comment="Deleted at") - links = Column(JSONType, nullable=True, comment="Links") - ratings = relationship(AuthorRating, foreign_keys=AuthorRating.author) diff --git a/resolvers/load.py b/resolvers/load.py index 29c29f2..61ccbb2 100644 --- a/resolvers/load.py +++ b/resolvers/load.py @@ -1,9 +1,9 @@ import json +from services.core import get_author from services.db import local_session from services.redis import redis from resolvers import query -from orm.author import Author from services.auth import login_required from .chats import create_chat from .unread import get_unread_counter @@ -63,7 +63,7 @@ async def load_chats(_, info, limit: int = 50, offset: int = 0): member_ids = c["members"].copy() c["members"] = [] for member_id in member_ids: - a = session.query(Author).where(Author.id == member_id).first() + a = await get_author(member_id) if a: c["members"].append( { diff --git a/resolvers/search.py b/resolvers/search.py index ed591dc..ec0676c 100644 --- a/resolvers/search.py +++ b/resolvers/search.py @@ -1,10 +1,9 @@ import json from datetime import datetime, timezone, timedelta from services.auth import login_required +from services.core import get_network from services.redis import redis from resolvers import query -from services.db import local_session -from orm.author import AuthorFollower, Author from resolvers.load import load_messages @@ -13,8 +12,8 @@ from resolvers.load import load_messages async def search_recipients(_, info, text: str, limit: int = 50, offset: int = 0): result = [] # TODO: maybe redis scan? - author = info.context["author"] - talk_before = await redis.execute("GET", f"/chats_by_author/{author.id}") + author_id = info.context["author_id"] + talk_before = await redis.execute("GET", f"/chats_by_author/{author_id}") if talk_before: talk_before = list(json.loads(talk_before))[offset : (offset + limit)] for chat_id in talk_before: @@ -27,25 +26,8 @@ async def search_recipients(_, info, text: str, limit: int = 50, offset: int = 0 result.append(member) more_amount = limit - len(result) - - with local_session() as session: - # followings - result += ( - session.query(AuthorFollower.author) - .join(Author, Author.id == AuthorFollower.follower) - .where(Author.slug.startswith(text)) - .offset(offset + len(result)) - .limit(more_amount) - ) - - # followers - result += ( - session.query(AuthorFollower.follower) - .join(Author, Author.id == AuthorFollower.author) - .where(Author.slug.startswith(text)) - .offset(offset + len(result)) - .limit(offset + len(result) + limit) - ) + if more_amount > 0: + result += await get_network(author_id, more_amount) return {"members": list(result), "error": None} @@ -84,7 +66,7 @@ async def search_in_chats(_, info, by, limit, offset): ) messages_set.union(set(mmm)) - + messages_sorted = list(messages_set).sort() return {"messages": messages_sorted, "error": None} diff --git a/server.py b/server.py index 368e4af..8e64658 100644 --- a/server.py +++ b/server.py @@ -48,6 +48,7 @@ local_headers = [ def exception_handler(exception_type, exception, traceback, debug_hook=sys.excepthook): + print(traceback) print("%s: %s" % (exception_type.__name__, exception)) diff --git a/services/auth.py b/services/auth.py index 16c1046..4b9ecc3 100644 --- a/services/auth.py +++ b/services/auth.py @@ -11,9 +11,7 @@ from settings import AUTH_URL from orm.author import Author -class AuthUser(BaseModel): - user_id: Optional[int] - username: Optional[str] +INTERNAL_AUTH_SERVER = "v2.discours" in AUTH_URL class AuthCredentials(BaseModel): @@ -26,17 +24,14 @@ class AuthCredentials(BaseModel): class JWTAuthenticate(AuthenticationBackend): async def authenticate(self, request: HTTPConnection): logged_in, user_id = await check_auth(request) - return ( - AuthCredentials(user_id=user_id, logged_in=logged_in), - AuthUser(user_id=user_id), - ) + return AuthCredentials(user_id=user_id, logged_in=logged_in), user_id async def check_auth(req): token = req.headers.get("Authorization") gql = ( {"mutation": "{ getSession { user { id } } }"} - if "v2" in AUTH_URL + if INTERNAL_AUTH_SERVER else {"query": "{ session { user { id } } }"} ) headers = {"Authorization": token, "Content-Type": "application/json"} @@ -84,8 +79,9 @@ def auth_request(f): if not is_authenticated: raise HTTPError("please, login first") else: - author_id = await author_id_by_user_id(user_id) - req["author_id"] = author_id + req["author_id"] = ( + user_id if INTERNAL_AUTH_SERVER else await author_id_by_user_id(user_id) + ) return await f(*args, **kwargs) return decorated_function diff --git a/services/core.py b/services/core.py new file mode 100644 index 0000000..de0ae90 --- /dev/null +++ b/services/core.py @@ -0,0 +1,54 @@ +from httpx import AsyncClient + +from settings import API_BASE + + +async def get_author(author_id): + gql = { + "query": "{ getAuthor(author_id: %s) { id slug userpic name lastSeen } }" + % author_id + } + headers = {"Content-Type": "application/json"} + try: + async with AsyncClient() as client: + response = await client.post(API_BASE, headers=headers, data=gql) + if response.status_code != 200: + return False, None + r = response.json() + author = r.get("data", {}).get("getAuthor") + return author + except Exception: + pass + + +async def get_network(author_id, limit=50, offset=0): + headers = {"Content-Type": "application/json"} + gql = { + "query": "{ authorFollowings(author_id: %s, limit: %s, offset: %s) { id slug userpic name } }" + % (author_id, limit, offset) + } + + followings = [] + followers = [] + try: + async with AsyncClient() as client: + response = await client.post(API_BASE, headers=headers, data=gql) + if response.status_code != 200: + return False, None + r = response.json() + followings = r.get("data", {}).get("authorFollowers", []) + more_amount = limit - len(followings) + if more_amount > 0: + gql = { + "query": "{ authorFollowers(author_id: %s, limit: %s) { id slug userpic name } }" + % (author_id, more_amount) + } + response = await client.post(API_BASE, headers=headers, data=gql) + if response.status_code != 200: + return False, None + r = response.json() + followers = r.get("data", {}).get("authorFollowers", []) + except Exception as e: + pass + + return followings + followers diff --git a/settings.py b/settings.py index 6899b5d..edaaa36 100644 --- a/settings.py +++ b/settings.py @@ -11,6 +11,4 @@ API_BASE = environ.get("API_BASE") or "" AUTH_URL = environ.get("AUTH_URL") or "" MODE = environ.get("MODE") or "production" SENTRY_DSN = environ.get("SENTRY_DSN") -SESSION_SECRET_KEY = environ.get("SESSION_SECRET_KEY") or "!secret" DEV_SERVER_PID_FILE_NAME = "dev-server.pid" -SESSION_TOKEN_HEADER = "Authorization"