diff --git a/migration/extract.py b/migration/extract.py index 5dd7ccba..d67275a9 100644 --- a/migration/extract.py +++ b/migration/extract.py @@ -27,6 +27,39 @@ def replace_tooltips(body): return newbody + +def extract_footnotes(body, shout_dict): + parts = body.split("&&&") + lll = len(parts) + newparts = list(parts) + placed = False + if lll & 1: + if lll > 1: + i = 1 + print("[extract] found %d footnotes in body" % (lll - 1)) + for part in parts[1:]: + if i & 1: + placed = True + if 'a class="footnote-url" href=' in part: + print("[extract] footnote: " + part) + fn = 'a class="footnote-url" href="' + exxtracted_link = part.split(fn, 1)[1].split('"', 1)[0] + extracted_body = part.split(fn, 1)[1].split('>', 1)[1].split('', 1)[0] + print("[extract] footnote link: " + extracted_link) + with local_session() as session: + Reaction.create({ + "shout": shout_dict['id'], + "kind": ReactionKind.FOOTNOTE, + "body": extracted_body, + "range": str(body.index(fn + link) - len('<')) + ':' + str(body.index(extracted_body) + len('')) + }) + newparts[i] = "ℹ️" + else: + newparts[i] = part + i += 1 + return ("".join(newparts), placed) + + def place_tooltips(body): parts = body.split("&&&") lll = len(parts) @@ -203,7 +236,7 @@ def extract_dataimages(parts, prefix): di = "data:image" -def extract_md_images(body, oid): +def extract_md_images(body, prefix): newbody = "" body = ( body.replace("\n! [](" + di, "\n ![](" + di) @@ -212,7 +245,7 @@ def extract_md_images(body, oid): ) parts = body.split(di) if len(parts) > 1: - newbody = extract_dataimages(parts, oid) + newbody = extract_dataimages(parts, prefix) else: newbody = body return newbody @@ -238,24 +271,24 @@ def cleanup(body): return newbody -def extract_md(body, oid=""): +def extract_md(body, shout_dict = None): newbody = body if newbody: - uid = oid or uuid.uuid4() - newbody = extract_md_images(newbody, uid) - if not newbody: - raise Exception("extract_images error") - newbody = cleanup(newbody) if not newbody: raise Exception("cleanup error") - newbody, placed = place_tooltips(newbody) - if not newbody: - raise Exception("place_tooltips error") + if shout_dict: + + uid = shout_dict['id'] or uuid.uuid4() + newbody = extract_md_images(newbody, uid) + if not newbody: + raise Exception("extract_images error") + + newbody, placed = extract_footnotes(body, shout_dict) + if not newbody: + raise Exception("extract_footnotes error") - if placed: - newbody = "import Tooltip from '$/components/Article/Tooltip'\n\n" + newbody return newbody @@ -342,7 +375,9 @@ def prepare_html_body(entry): return body -def extract_html(entry): +def extract_html(entry, shout_id = None): body_orig = (entry.get("body") or "").replace('\(', '(').replace('\)', ')') + if shout_id: + extract_footnotes(body_orig, shout_id) body_html = str(BeautifulSoup(body_orig, features="html.parser")) return body_html diff --git a/migration/tables/content_items.py b/migration/tables/content_items.py index d44b19dc..e638a690 100644 --- a/migration/tables/content_items.py +++ b/migration/tables/content_items.py @@ -47,18 +47,27 @@ def create_author_from_app(app): if not user: print('[migration] creating user...') name = app.get('name') - slug = translit(name, "ru", reversed=True).lower() - slug = re.sub('[^0-9a-zA-Z]+', '-', slug) - # check if nameslug is used - user = session.query(User).where(User.slug == slug).first() - # get slug from email - if user: - slug = app['email'].split('@')[0] - user = session.query(User).where(User.slug == slug).first() - # one more try - if user: - slug += '-author' - user = session.query(User).where(User.slug == slug).first() + if name: + slug = translit(name, "ru", reversed=True).lower() + slug = re.sub('[^0-9a-zA-Z]+', '-', slug) + # check if slug is used + if slug: + user = session.query(User).where(User.slug == slug).first() + + # get slug from email + if user: + slug = app['email'].split('@')[0] + user = session.query(User).where(User.slug == slug).first() + # one more try + if user: + slug += '-author' + user = session.query(User).where(User.slug == slug).first() + else: + print(f'[migration] author @{slug} is found by email') + + else: + print(f'[migration] author @{slug} is found') + # create user with application data if not user: @@ -84,7 +93,7 @@ def create_author_from_app(app): async def create_shout(shout_dict): s = Shout.create(**shout_dict) - author = shout_dict['authors'][0] + author = s.authors[0] with local_session() as session: srf = session.query(ShoutReactionsFollower).where( ShoutReactionsFollower.shout == s.id @@ -109,8 +118,9 @@ async def get_user(entry, storage): userdata = anondict # cleanup slug slug = userdata.get("slug", "") - slug = re.sub('[^0-9a-zA-Z]+', '-', slug) - userdata["slug"] = slug + if slug: + slug = re.sub('[^0-9a-zA-Z]+', '-', slug) + userdata["slug"] = slug user = await process_user(userdata, storage, user_oid) return user, user_oid diff --git a/migration/tables/remarks.py b/migration/tables/remarks.py index 78f52c92..026b95c6 100644 --- a/migration/tables/remarks.py +++ b/migration/tables/remarks.py @@ -1,31 +1,42 @@ from base.orm import local_session from migration.extract import extract_md from migration.html2text import html2text -from orm.remark import Remark +from orm.reaction import Reaction, ReactionKind def migrate(entry, storage): post_oid = entry['contentItem'] print(post_oid) shout_dict = storage['shouts']['by_oid'].get(post_oid) - remark = { - "shout": shout_dict['id'], - "body": extract_md( - html2text(entry['body']), - entry['_id'] - ), - "desc": extract_md( - html2text( - entry['textAfter'] or '' + \ - entry['textBefore'] or '' + \ - entry['textSelected'] or '' + if shout_dict: + print(shout_dict['body']) + remark = { + "shout": shout_dict['id'], + "body": extract_md( + html2text(entry['body']), + shout_dict ), - entry["_id"] - ) - } + "kind": ReactionKind.REMARK + } - with local_session() as session: - rmrk = Remark.create(**remark) - session.commit() - del rmrk["_sa_instance_state"] - return rmrk + if entry.get('textBefore'): + remark['range'] = str( + shout_dict['body'] + .index( + entry['textBefore'] or '' + ) + ) + ':' + str( + shout_dict['body'] + .index( + entry['textAfter'] or '' + ) + len( + entry['textAfter'] or '' + ) + ) + + with local_session() as session: + rmrk = Reaction.create(**remark) + session.commit() + del rmrk["_sa_instance_state"] + return rmrk + return diff --git a/migration/tables/topics.py b/migration/tables/topics.py index 3287adb7..17804376 100644 --- a/migration/tables/topics.py +++ b/migration/tables/topics.py @@ -10,7 +10,7 @@ def migrate(entry): "slug": entry["slug"], "oid": entry["_id"], "title": entry["title"].replace(" ", " "), - "body": extract_md(html2text(body_orig), entry["_id"]) + "body": extract_md(html2text(body_orig)) } with local_session() as session: diff --git a/orm/reaction.py b/orm/reaction.py index f484808a..3ff769cd 100644 --- a/orm/reaction.py +++ b/orm/reaction.py @@ -11,7 +11,7 @@ class ReactionKind(Enumeration): DISAGREE = 2 # -1 PROOF = 3 # +1 DISPROOF = 4 # -1 - ASK = 5 # +0 bookmark + ASK = 5 # +0 PROPOSE = 6 # +0 QUOTE = 7 # +0 bookmark COMMENT = 8 # +0 @@ -19,6 +19,8 @@ class ReactionKind(Enumeration): REJECT = 0 # -1 LIKE = 11 # +1 DISLIKE = 12 # -1 + REMARK = 13 # 0 + FOOTNOTE = 14 # 0 # TYPE = # rating diff diff --git a/orm/remark.py b/orm/remark.py deleted file mode 100644 index 9432a3f5..00000000 --- a/orm/remark.py +++ /dev/null @@ -1,15 +0,0 @@ -from datetime import datetime -from enum import Enum as Enumeration - -from sqlalchemy import Column, DateTime, Enum, ForeignKey, String - -from base.orm import Base - - -class Remark(Base): - - __tablename__ = "remark" - - body = Column(String, nullable=False) - desc = Column(String, default='') - shout = Column(ForeignKey("shout.id"), nullable=True, index=True, comment="Shout") diff --git a/resolvers/inbox/chats.py b/resolvers/inbox/chats.py index 828f769c..853defab 100644 --- a/resolvers/inbox/chats.py +++ b/resolvers/inbox/chats.py @@ -75,7 +75,7 @@ async def create_chat(_, info, title="", members=[]): if chat: chat = json.loads(chat) if chat['title'] == "": - print('[inbox] craeteChat found old chat') + print('[inbox] createChat found old chat') print(chat) break if chat: diff --git a/resolvers/inbox/load.py b/resolvers/inbox/load.py index fd746df9..b5909d8c 100644 --- a/resolvers/inbox/load.py +++ b/resolvers/inbox/load.py @@ -124,15 +124,25 @@ async def load_messages_by(_, info, by, limit: int = 10, offset: int = 0): async def load_recipients(_, info, limit=50, offset=0): chat_users = [] auth: AuthCredentials = info.context["request"].auth - try: + onliners = await redis.execute("SMEMBERS", "users-online") chat_users += await followed_authors(auth.user_id) limit = limit - len(chat_users) except Exception: pass with local_session() as session: chat_users += session.query(User).where(User.emailConfirmed).limit(limit).offset(offset) + members = [] + for a in chat_users: + members.append({ + "id": a.id, + "slug": a.slug, + "userpic": a.userpic, + "name": a.name, + "lastSeen": a.lastSeen, + "online": a.id in onliners + }) return { - "members": chat_users, + "members": members, "error": None } diff --git a/resolvers/zine/reactions.py b/resolvers/zine/reactions.py index f935920c..94ead0ca 100644 --- a/resolvers/zine/reactions.py +++ b/resolvers/zine/reactions.py @@ -157,6 +157,23 @@ async def create_reaction(_, info, reaction={}): reaction['createdBy'] = auth.user_id with local_session() as session: r = Reaction.create(**reaction) + shout = session.query(Shout).where(Shout.id == r.shout).one() + + # Proposal accepting logix + if r.replyTo is not None and \ + r.kind == ReactionKind.ACCEPT and \ + user_id in shout.dict()['authors']: + replied_reaction = session.query(Reaction).when(Reaction.id == r.replyTo).first() + if replied_reaction and replied_reaction.kind == ReactionKind.PROPOSE: + if replied_reaction.range: + old_body = shout.body + start, end = replied_reaction.range.split(':') + start = int(start) + end = int(end) + new_body = old_body[:start] + replied_reaction.body + old_body[end:] + shout.body = new_body + # TODO: update git version control + session.add(r) session.commit() @@ -230,9 +247,9 @@ async def delete_reaction(_, info, reaction=None): return {"error": "access denied"} r.deletedAt = datetime.now(tz=timezone.utc) session.commit() - return { - "reaction": r - } + return { + "reaction": r + } @query.field("loadReactionsBy") diff --git a/resolvers/zine/remark.py b/resolvers/zine/remark.py deleted file mode 100644 index 6f5f9d48..00000000 --- a/resolvers/zine/remark.py +++ /dev/null @@ -1,48 +0,0 @@ - -from datetime import datetime, timedelta, timezone -from sqlalchemy.orm import joinedload, aliased -from sqlalchemy.sql.expression import desc, asc, select, func -from base.orm import local_session -from base.resolvers import query, mutation -from base.exceptions import ObjectNotExist -from orm.remark import Remark - - -@mutation.field("createRemark") -@login_required -async def create_remark(_, info, slug, body): - auth = info.context["request"].auth - user_id = auth.user_id - with local_session() as session: - tt = Remark.create(slug=slug, body=body) - session.commit() - return - -@mutation.field("updateRemark") -@login_required -async def update_remark(_, info, slug, body = ''): - auth = info.context["request"].auth - user_id = auth.user_id - with local_session() as session: - rmrk = session.query(Remark).where(Remark.slug == slug).one() - if body: - tt.body = body - session.add(rmrk) - session.commit() - return - -@mutation.field("deleteRemark") -@login_required -async def delete_remark(_, info, slug): - auth = info.context["request"].auth - user_id = auth.user_id - with local_session() as session: - rmrk = session.query(Remark).where(Remark.slug == slug).one() - rmrk.remove() - session.commit() - return - -@query.field("loadRemark") -@login_required -async def load_remark(_, info, slug): - pass diff --git a/schema.graphql b/schema.graphql index 080cb371..cf66d24d 100644 --- a/schema.graphql +++ b/schema.graphql @@ -407,6 +407,9 @@ enum ReactionKind { PROPOSE ASK + REMARK + FOOTNOTE + ACCEPT REJECT }