diff --git a/README.md b/README.md index c69428a9..a0b778b2 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,8 @@ apt install redis nginx First, install Postgres. Then you'll need some data ``` - -psql -U postgres -> create database discoursio; -> \q +brew install postgres +createdb discoursio python server.py migrate ``` @@ -42,3 +40,7 @@ python3 server.py dev Put the header 'Authorization' with token from signIn query or registerUser mutation. +# How to debug Ackee + +Set ACKEE_TOKEN var + diff --git a/auth/authenticate.py b/auth/authenticate.py index 95695604..242d2793 100644 --- a/auth/authenticate.py +++ b/auth/authenticate.py @@ -9,7 +9,7 @@ from auth.credentials import AuthCredentials, AuthUser from services.auth.users import UserStorage from settings import SESSION_TOKEN_HEADER from auth.tokenstorage import SessionToken -from base.exceptions import InvalidToken +from base.exceptions import InvalidToken, OperationNotAllowed, Unauthorized class JWTAuthenticate(AuthenticationBackend): @@ -30,27 +30,26 @@ class JWTAuthenticate(AuthenticationBackend): try: if len(token.split('.')) > 1: payload = await SessionToken.verify(token) + if payload is None: + return AuthCredentials(scopes=[]), AuthUser(user_id=None) + user = await UserStorage.get_user(payload.user_id) + if not user: + return AuthCredentials(scopes=[]), AuthUser(user_id=None) + scopes = await user.get_permission() + return ( + AuthCredentials( + user_id=payload.user_id, + scopes=scopes, + logged_in=True + ), + user, + ) else: InvalidToken("please try again") except Exception as exc: print("[auth.authenticate] session token verify error") print(exc) - return AuthCredentials(scopes=[], error_message=str(exc)), AuthUser( - user_id=None - ) - - if payload is None: - return AuthCredentials(scopes=[]), AuthUser(user_id=None) - - user = await UserStorage.get_user(payload.user_id) - if not user: - return AuthCredentials(scopes=[]), AuthUser(user_id=None) - - scopes = await user.get_permission() - return ( - AuthCredentials(user_id=payload.user_id, scopes=scopes, logged_in=True), - user, - ) + return AuthCredentials(scopes=[], error_message=str(exc)), AuthUser(user_id=None) def login_required(func): @@ -58,10 +57,9 @@ def login_required(func): async def wrap(parent, info: GraphQLResolveInfo, *args, **kwargs): # print('[auth.authenticate] login required for %r with info %r' % (func, info)) # debug only auth: AuthCredentials = info.context["request"].auth - if auth and auth.user_id: - print(auth) # debug only + # print(auth) if not auth.logged_in: - return {"error": auth.error_message or "Please login"} + raise OperationNotAllowed(auth.error_message or "Please login") return await func(parent, info, *args, **kwargs) return wrap @@ -73,9 +71,9 @@ def permission_required(resource, operation, func): print('[auth.authenticate] permission_required for %r with info %r' % (func, info)) # debug only auth: AuthCredentials = info.context["request"].auth if not auth.logged_in: - return {"error": auth.error_message or "Please login"} + raise Unauthorized(auth.error_message or "Please login") - # TODO: add check permission logix + # TODO: add actual check permission logix here return await func(parent, info, *args, **kwargs) diff --git a/auth/credentials.py b/auth/credentials.py index 401ae420..15738d16 100644 --- a/auth/credentials.py +++ b/auth/credentials.py @@ -2,7 +2,7 @@ from typing import List, Optional, Text from pydantic import BaseModel -from base.exceptions import OperationNotAllowed +from base.exceptions import Unauthorized class Permission(BaseModel): @@ -17,11 +17,13 @@ class AuthCredentials(BaseModel): @property def is_admin(self): + # TODO: check admin logix return True async def permissions(self) -> List[Permission]: if self.user_id is None: - raise OperationNotAllowed("Please login first") + raise Unauthorized("Please login first") + # TODO: implement permissions logix return NotImplemented() diff --git a/auth/email.py b/auth/email.py index c0e2286a..8a1f8dfd 100644 --- a/auth/email.py +++ b/auth/email.py @@ -10,13 +10,13 @@ lang_subject = { } -async def send_auth_email(user, token, lang="ru"): +async def send_auth_email(user, token, template="email_confirmation", lang="ru"): try: to = "%s <%s>" % (user.name, user.email) if lang not in ['ru', 'en']: lang = 'ru' subject = lang_subject.get(lang, lang_subject["en"]) - template = "email_confirmation_" + lang + template = template + "_" + lang payload = { "from": noreply, "to": to, diff --git a/auth/jwtcodec.py b/auth/jwtcodec.py index c2feacd3..387df057 100644 --- a/auth/jwtcodec.py +++ b/auth/jwtcodec.py @@ -34,7 +34,7 @@ class JWTCodec: issuer="discours" ) r = TokenPayload(**payload) - print('[auth.jwtcodec] debug payload %r' % r) + # print('[auth.jwtcodec] debug payload %r' % r) return r except jwt.InvalidIssuedAtError: print('[auth.jwtcodec] invalid issued at: %r' % r) diff --git a/auth/oauth.py b/auth/oauth.py index 88950c8d..54b5f11a 100644 --- a/auth/oauth.py +++ b/auth/oauth.py @@ -2,7 +2,7 @@ from authlib.integrations.starlette_client import OAuth from starlette.responses import RedirectResponse from auth.identity import Identity from auth.tokenstorage import TokenStorage -from settings import OAUTH_CLIENTS +from settings import OAUTH_CLIENTS, FRONTEND_URL oauth = OAuth() @@ -84,6 +84,6 @@ async def oauth_authorize(request): } user = Identity.oauth(user_input) session_token = await TokenStorage.create_session(user) - response = RedirectResponse(url="https://new.discours.io/confirm") + response = RedirectResponse(url=FRONTEND_URL + "/confirm") response.set_cookie("token", session_token) return response diff --git a/base/redis.py b/base/redis.py index 7468af0a..ccc3758a 100644 --- a/base/redis.py +++ b/base/redis.py @@ -12,6 +12,7 @@ class RedisCache: if self._instance is not None: return self._instance = await from_url(self._uri, encoding="utf-8") + # print(self._instance) async def disconnect(self): if self._instance is None: @@ -23,10 +24,11 @@ class RedisCache: async def execute(self, command, *args, **kwargs): while not self._instance: await sleep(1) - try: - await self._instance.execute_command(command, *args, **kwargs) - except Exception: - pass + try: + print("[redis] " + command + ' ' + ' '.join(args)) + return await self._instance.execute_command(command, *args, **kwargs) + except Exception: + pass async def lrange(self, key, start, stop): return await self._instance.lrange(key, start, stop) diff --git a/migration/__init__.py b/migration/__init__.py index 2d195e06..4a25931d 100644 --- a/migration/__init__.py +++ b/migration/__init__.py @@ -314,9 +314,6 @@ async def handle_auto(): async def main(): if len(sys.argv) > 1: - cmd = sys.argv[1] - if type(cmd) == str: - print("[migration] command: " + cmd) init_tables() await handle_auto() else: diff --git a/migration/export.py b/migration/export.py index 988ab35d..102cfb14 100644 --- a/migration/export.py +++ b/migration/export.py @@ -4,7 +4,7 @@ from datetime import datetime, timezone import frontmatter -from .extract import extract_html, prepare_html_body +from .extract import extract_html, extract_media from .utils import DateTimeEncoder OLD_DATE = "2016-03-05 22:22:00.350000" @@ -50,11 +50,12 @@ def export_mdx(r): def export_body(shout, storage): entry = storage["content_items"]["by_oid"][shout["oid"]] if entry: - shout["body"], media = prepare_html_body(entry) # prepare_md_body(entry) + body = extract_html(entry) + media = extract_media(entry) + shout["body"] = body # prepare_html_body(entry) # prepare_md_body(entry) shout["media"] = media export_mdx(shout) print("[export] html for %s" % shout["slug"]) - body, _media = extract_html(entry) open(contentDir + shout["slug"] + ".html", "w").write(body) else: raise Exception("no content_items entry found") diff --git a/migration/extract.py b/migration/extract.py index 4ea44d04..5dd7ccba 100644 --- a/migration/extract.py +++ b/migration/extract.py @@ -3,7 +3,8 @@ import os import re import uuid -from .html2text import html2text +from bs4 import BeautifulSoup + TOOLTIP_REGEX = r"(\/\/\/(.+)\/\/\/)" contentDir = os.path.join( @@ -258,47 +259,44 @@ def extract_md(body, oid=""): return newbody -def prepare_md_body(entry): - # body modifications - body = "" +def extract_media(entry): + ''' normalized media extraction method ''' + # media [ { title pic url body } ]} kind = entry.get("type") - addon = "" - if kind == "Video": - addon = "" - for m in entry.get("media", []): - if "youtubeId" in m: - addon += "\n" + if not kind: + print(entry) + raise Exception("shout no layout") + media = [] + for m in entry.get("media") or []: + # title + title = m.get("title", "").replace("\n", " ").replace(" ", " ") + artist = m.get("performer") or m.get("artist") + if artist: + title = artist + " - " + title + + # pic + url = m.get("fileUrl") or m.get("url", "") + pic = "" + if m.get("thumborId"): + pic = cdn + "/unsafe/1600x/" + m["thumborId"] + + # url + if not url: + if kind == "Image": + url = pic + elif "youtubeId" in m: + url = "https://youtube.com/?watch=" + m["youtubeId"] elif "vimeoId" in m: - addon += "\n" - else: - print("[extract] media is not supported") - print(m) - body = "import VideoPlayer from '$/components/Article/VideoPlayer'\n\n" + addon - - elif kind == "Music": - addon = "" - for m in entry.get("media", []): - artist = m.get("performer") - trackname = "" - if artist: - trackname += artist + " - " - if "title" in m: - trackname += m.get("title", "") - addon += ( - '\n' - ) - body = "import AudioPlayer from '$/components/Article/AudioPlayer'\n\n" + addon - - body_orig, media = extract_html(entry) - if body_orig: - body += extract_md(html2text(body_orig), entry["_id"]) - if not body: - print("[extract] empty MDX body") - return body, media + url = "https://vimeo.com/" + m["vimeoId"] + # body + body = m.get("body") or m.get("literatureBody") or "" + media.append({ + "url": url, + "pic": pic, + "title": title, + "body": body + }) + return media def prepare_html_body(entry): @@ -308,7 +306,7 @@ def prepare_html_body(entry): addon = "" if kind == "Video": addon = "" - for m in entry.get("media", []): + for m in entry.get("media") or []: if "youtubeId" in m: addon += '