Merge branch 'main' of testapi.discours.io:discoursio-api

This commit is contained in:
bniwredyc 2023-01-18 13:30:28 +01:00
commit 04f0438e07
13 changed files with 295 additions and 96 deletions

View File

@ -11,6 +11,7 @@ from migration.tables.content_items import get_shout_slug
from migration.tables.content_items import migrate as migrateShout from migration.tables.content_items import migrate as migrateShout
from migration.tables.topics import migrate as migrateTopic from migration.tables.topics import migrate as migrateTopic
from migration.tables.users import migrate as migrateUser from migration.tables.users import migrate as migrateUser
from migration.tables.remarks import migrate as migrateRemark
from migration.tables.users import migrate_2stage as migrateUser_2stage from migration.tables.users import migrate_2stage as migrateUser_2stage
from orm.reaction import Reaction from orm.reaction import Reaction
from orm import init_tables from orm import init_tables
@ -135,6 +136,15 @@ async def shouts_handle(storage, args):
print("[migration] " + str(anonymous_author) + " authored by @anonymous") print("[migration] " + str(anonymous_author) + " authored by @anonymous")
async def remarks_handle(storage):
print("[migration] comments")
c = 0
for entry_remark in storage["remarks"]["data"]:
remark = await migrateRemark(entry_remark, storage)
c += 1
print("[migration] " + str(c) + " remarks migrated")
async def comments_handle(storage): async def comments_handle(storage):
print("[migration] comments") print("[migration] comments")
id_map = {} id_map = {}
@ -170,6 +180,8 @@ async def all_handle(storage, args):
await topics_handle(storage) await topics_handle(storage)
print("[migration] users and topics are migrated") print("[migration] users and topics are migrated")
await shouts_handle(storage, args) await shouts_handle(storage, args)
print("[migration] remarks...")
await remarks_handle(storage)
print("[migration] migrating comments") print("[migration] migrating comments")
await comments_handle(storage) await comments_handle(storage)
# export_email_subscriptions() # export_email_subscriptions()
@ -190,6 +202,7 @@ def data_load():
"cats": [], "cats": [],
"tags": [], "tags": [],
}, },
"remarks": {"data": []},
"users": {"by_oid": {}, "by_slug": {}, "data": []}, "users": {"by_oid": {}, "by_slug": {}, "data": []},
"replacements": json.loads(open("migration/tables/replacements.json").read()), "replacements": json.loads(open("migration/tables/replacements.json").read()),
} }
@ -210,6 +223,11 @@ def data_load():
content_data = json.loads(open("migration/data/content_items.json").read()) content_data = json.loads(open("migration/data/content_items.json").read())
storage["shouts"]["data"] = content_data storage["shouts"]["data"] = content_data
print("[migration.load] " + str(len(content_data)) + " content items ") print("[migration.load] " + str(len(content_data)) + " content items ")
remarks_data = json.loads(open("migration/data/remarks.json").read())
storage["remarks"]["data"] = remarks_data
print("[migration.load] " + str(len(remarks_data)) + " remarks data ")
# fill out storage # fill out storage
for x in users_data: for x in users_data:
storage["users"]["by_oid"][x["_id"]] = x storage["users"]["by_oid"][x["_id"]] = x

View File

@ -0,0 +1,31 @@
from base.orm import local_session
from migration.extract import extract_md
from migration.html2text import html2text
from orm.remark import Remark
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 ''
),
entry["_id"]
)
}
with local_session() as session:
rmrk = Remark.create(**remark)
session.commit()
del rmrk["_sa_instance_state"]
return rmrk

View File

@ -1,27 +0,0 @@
from datetime import datetime
from sqlalchemy import Column, ForeignKey, DateTime, String
from sqlalchemy.orm import relationship
from base.orm import Base
from orm.user import User
class CollabAuthor(Base):
__tablename__ = "collab_author"
id = None # type: ignore
collab = Column(ForeignKey("collab.id"), primary_key=True)
author = Column(ForeignKey("user.id"), primary_key=True)
# accepted = Column(Boolean, default=False)
class Collab(Base):
__tablename__ = "collab"
title = Column(String, nullable=True, comment="Title")
body = Column(String, nullable=True, comment="Body")
pic = Column(String, nullable=True, comment="Picture")
authors = relationship(lambda: User, secondary=CollabAuthor.__tablename__)
# invites = relationship(lambda: User, secondary=CollabInvited.__tablename__)
createdAt = Column(DateTime, default=datetime.now, comment="Created At")
chat = Column(String, unique=True, nullable=False)

40
orm/draft.py Normal file
View File

@ -0,0 +1,40 @@
from datetime import datetime
from sqlalchemy import Boolean, Column, ForeignKey, DateTime, String
from sqlalchemy.orm import relationship
from base.orm import Base
from orm.user import User
from orm.topic import Topic
class DraftTopic(Base):
__tablename__ = "draft_topic"
id = None # type: ignore
collab = Column(ForeignKey("draft_collab.id"), primary_key=True)
topic = Column(ForeignKey("topic.id"), primary_key=True)
class DraftAuthor(Base):
__tablename__ = "draft_author"
id = None # type: ignore
collab = Column(ForeignKey("draft_collab.id"), primary_key=True)
author = Column(ForeignKey("user.id"), primary_key=True)
accepted = Column(Boolean, default=False)
class DraftCollab(Base):
__tablename__ = "draft_collab"
slug = Column(String, nullable=True, comment="Slug")
title = Column(String, nullable=True, comment="Title")
subtitle = Column(String, nullable=True, comment="Subtitle")
layout = Column(String, nullable=True, comment="Layout format")
body = Column(String, nullable=True, comment="Body")
cover = Column(String, nullable=True, comment="Cover")
authors = relationship(lambda: User, secondary=DraftAuthor.__tablename__)
topics = relationship(lambda: Topic, secondary=DraftTopic.__tablename__)
createdAt = Column(DateTime, default=datetime.now, comment="Created At")
updatedAt = Column(DateTime, default=datetime.now, comment="Updated At")
chat = Column(String, unique=True, nullable=True)

15
orm/remark.py Normal file
View File

@ -0,0 +1,15 @@
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")

View File

@ -50,7 +50,7 @@ class Shout(Base):
subtitle = Column(String, nullable=True) subtitle = Column(String, nullable=True)
layout = Column(String, nullable=True) layout = Column(String, nullable=True)
mainTopic = Column(ForeignKey("topic.slug"), nullable=True) mainTopic = Column(ForeignKey("topic.slug"), nullable=True)
cover = Column(String, nullable=True) cover = Column(String, nullable=True, comment="Cover")
authors = relationship(lambda: User, secondary=ShoutAuthor.__tablename__) authors = relationship(lambda: User, secondary=ShoutAuthor.__tablename__)
topics = relationship(lambda: Topic, secondary=ShoutTopic.__tablename__) topics = relationship(lambda: Topic, secondary=ShoutTopic.__tablename__)
reactions = relationship(lambda: Reaction) reactions = relationship(lambda: Reaction)

View File

@ -8,7 +8,8 @@ from resolvers.auth import (
get_current_user, get_current_user,
) )
from resolvers.create.collab import remove_coauthor, invite_coauthor from resolvers.create.collab import load_drafts, create_draft, update_draft, delete_draft,\
accept_coauthor, invite_coauthor
from resolvers.create.migrate import markdown_body from resolvers.create.migrate import markdown_body
from resolvers.create.editor import create_shout, delete_shout, update_shout from resolvers.create.editor import create_shout, delete_shout, update_shout
@ -93,8 +94,12 @@ __all__ = [
# create.migrate # create.migrate
"markdown_body", "markdown_body",
# create.collab # create.collab
"load_drafts",
"create_draft",
"update_draft",
"delete_draft",
"invite_coauthor", "invite_coauthor",
"remove_coauthor", "accept_coauthor",
# zine.topics # zine.topics
"topics_all", "topics_all",
"topics_by_community", "topics_by_community",

View File

@ -3,84 +3,130 @@ from auth.credentials import AuthCredentials
from base.orm import local_session from base.orm import local_session
from base.resolvers import query, mutation from base.resolvers import query, mutation
from base.exceptions import ObjectNotExist, BaseHttpException from base.exceptions import ObjectNotExist, BaseHttpException
from orm.collab import Collab, CollabAuthor from orm.draft import DraftCollab, DraftAuthor, DraftTopic
from orm.shout import Shout from orm.shout import Shout
from orm.user import User from orm.user import User
@query.field("getCollabs") @query.field("loadDrafts")
@login_required @login_required
async def get_collabs(_, info): async def load_drafts(_, info):
auth: AuthCredentials = info.context["request"].auth
drafts = []
with local_session() as session:
drafts = session.query(DraftCollab).filter(auth.user_id in DraftCollab.authors)
return drafts
@mutation.field("createDraft") # TODO
@login_required
async def create_draft(_, info, draft_input):
auth: AuthCredentials = info.context["request"].auth auth: AuthCredentials = info.context["request"].auth
with local_session() as session: with local_session() as session:
collabs = session.query(Collab).filter(auth.user_id in Collab.authors) collab = DraftCollab.create(**draft_input)
return collabs session.add(collab)
@mutation.field("inviteCoauthor")
@login_required
async def invite_coauthor(_, info, author: int = 0, shout: int = 0):
auth: AuthCredentials = info.context["request"].auth
with local_session() as session:
s = session.query(Shout).where(Shout.id == shout).one()
if not s:
raise ObjectNotExist("invalid shout id")
else:
c = session.query(Collab).where(Collab.shout == shout).one()
if auth.user_id not in c.authors:
raise BaseHttpException("you are not in authors list")
else:
invited_user = session.query(User).where(User.id == author).one()
c.invites.append(invited_user)
session.add(c)
session.commit() session.commit()
# TODO: email notify # TODO: email notify to all authors
return {} return {}
@mutation.field("removeCoauthor") @mutation.field("deleteDraft") # TODO
@login_required @login_required
async def remove_coauthor(_, info, author: int = 0, shout: int = 0): async def delete_draft(_, info, draft: int = 0):
auth: AuthCredentials = info.context["request"].auth auth: AuthCredentials = info.context["request"].auth
with local_session() as session: with local_session() as session:
s = session.query(Shout).where(Shout.id == shout).one() # raises Error when not found collab = session.query(DraftCollab).where(DraftCollab.id == draft_input.id).one()
if auth.user_id not in s.authors: if auth.user_id not in s.authors:
raise BaseHttpException("only owner can remove coauthors") # raise BaseHttpException("only owner can remove coauthors")
return {
"error": "Only authors can update a draft"
}
elif not collab:
return {
"error": "There is no draft with this id"
}
else: else:
c = session.query(Collab).where(Collab.shout == shout).one() session.delete(collab)
ca = session.query(CollabAuthor).join(User).where(c.shout == shout, User.id == author).one() session.commit()
session.remve(ca) return {}
c.invites = filter(lambda x: x.id == author, c.invites)
c.authors = filter(lambda x: x.id == author, c.authors)
session.add(c) @mutation.field("updateDraft") # TODO: draft input type
@login_required
async def update_draft(_, info, draft_input):
auth: AuthCredentials = info.context["request"].auth
with local_session() as session:
collab = session.query(DraftCollab).where(DraftCollab.id == draft_input.id).one() # raises Error when not found
if auth.user_id not in s.authors:
# raise BaseHttpException("only owner can remove coauthors")
return {
"error": "Only authors can update draft"
}
elif not s:
return {
"error": "There is no draft with this id"
}
else:
draft_input["updatedAt"] = datetime.now(tz=timezone.utc)
collab.update(draft_input)
session.commit() session.commit()
# TODO: email notify # TODO: email notify
return {} return {}
@mutation.field("inviteAuthor")
@mutation.field("acceptCoauthor")
@login_required @login_required
async def accept_coauthor(_, info, shout: int): async def invite_coauthor(_, info, author: int = 0, draft: int = 0):
auth: AuthCredentials = info.context["request"].auth auth: AuthCredentials = info.context["request"].auth
with local_session() as session: with local_session() as session:
s = session.query(Shout).where(Shout.id == shout).one() c = session.query(DraftCollab).where(DraftCollab.id == draft).one()
if not s: if auth.user_id not in c.authors:
raise ObjectNotExist("invalid shout id") # raise BaseHttpException("you are not in authors list")
else: return {
c = session.query(Collab).where(Collab.shout == shout).one() "error": "You are not in authors list"
accepted = filter(lambda x: x.id == auth.user_id, c.invites).pop() }
if accepted: elif c.id:
c.authors.append(accepted) invited_user = session.query(User).where(User.id == author).one()
s.authors.append(accepted) da = DraftAuthor.create({
session.add(s) "accepted": False,
session.add(c) "collab": c.id,
"author": invited_user.id
})
session.add(da)
session.commit() session.commit()
return {}
else: else:
raise BaseHttpException("only invited can accept") return {
"error": "Draft is not found"
}
# TODO: email notify
return {}
@mutation.field("inviteAccept")
@login_required
async def accept_coauthor(_, info, draft: int):
auth: AuthCredentials = info.context["request"].auth
with local_session() as session:
# c = session.query(DraftCollab).where(DraftCollab.id == draft).one()
a = session.query(DraftAuthor).where(DraftAuthor.collab == draft).filter(DraftAuthor.author == auth.user_id).one()
if not a.accepted:
a.accepted = True
session.commit()
# TODO: email notify
return {}
elif a.accepted == True:
return {
"error": "You have accepted invite before"
}
else:
# raise BaseHttpException("only invited can accept")
return {
"error": "You don't have an invitation yet"
}

View File

@ -14,7 +14,7 @@ from resolvers.zine.reactions import reactions_follow, reactions_unfollow
from services.zine.gittask import GitTask from services.zine.gittask import GitTask
from resolvers.inbox.chats import create_chat from resolvers.inbox.chats import create_chat
from services.inbox.storage import MessagesStorage from services.inbox.storage import MessagesStorage
from orm.collab import Collab from orm.draft import DraftCollab
@mutation.field("createShout") @mutation.field("createShout")

View File

@ -218,10 +218,13 @@ def author_unfollow(user_id, slug):
).first() ).first()
) )
if not flw: if not flw:
raise Exception("[resolvers.profile] follower not exist, cant unfollow") return {
"error": "Follower is not exist, cant unfollow"
}
else: else:
session.delete(flw) session.delete(flw)
session.commit() session.commit()
return {}
@query.field("authorsAll") @query.field("authorsAll")

View File

@ -196,7 +196,7 @@ async def update_reaction(_, info, reaction={}):
if not r: if not r:
return {"error": "invalid reaction id"} return {"error": "invalid reaction id"}
if r.createdBy != user.slug: if r.createdBy != user.id:
return {"error": "access denied"} return {"error": "access denied"}
r.body = reaction["body"] r.body = reaction["body"]

48
resolvers/zine/remark.py Normal file
View File

@ -0,0 +1,48 @@
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

View File

@ -69,6 +69,7 @@ type Result {
members: [ChatMember] members: [ChatMember]
shout: Shout shout: Shout
shouts: [Shout] shouts: [Shout]
drafts: [DraftCollab]
author: Author author: Author
authors: [Author] authors: [Author]
reaction: Reaction reaction: Reaction
@ -128,6 +129,17 @@ input TopicInput {
# parents: [String] # parents: [String]
} }
input DraftInput {
slug: String
topics: [Int]
authors: [Int]
title: String
subtitle: String
body: String
cover: String
}
input ReactionInput { input ReactionInput {
kind: ReactionKind! kind: ReactionKind!
shout: Int! shout: Int!
@ -189,10 +201,12 @@ type Mutation {
updateReaction(reaction: ReactionInput!): Result! updateReaction(reaction: ReactionInput!): Result!
deleteReaction(reaction: Int!): Result! deleteReaction(reaction: Int!): Result!
# collab # draft / collab
inviteCoauthor(author: String!, shout: Int!): Result! createDraft(draft: DraftInput!): Result!
removeCoauthor(author: String!, shout: Int!): Result! updateDraft(draft: DraftInput!): Result!
acceptCoauthor(shout: Int!): Result! deleteDraft(draft: Int!): Result!
inviteAccept(draft: Int!): Result!
inviteAuthor(draft: Int!, author: Int!): Result!
# following # following
follow(what: FollowingEntity!, slug: String!): Result! follow(what: FollowingEntity!, slug: String!): Result!
@ -289,8 +303,8 @@ type Query {
authorsAll: [Author]! authorsAll: [Author]!
getAuthor(slug: String!): User getAuthor(slug: String!): User
# collab # draft/collab
getCollabs: [Collab]! loadDrafts: [DraftCollab]!
# migrate # migrate
markdownBody(body: String!): String! markdownBody(body: String!): String!
@ -525,10 +539,16 @@ type Chat {
private: Boolean private: Boolean
} }
type Collab { type DraftCollab {
authors: [String]! slug: String
invites: [String] title: String
shout: Shout subtitle: String
body: String
cover: String
layout: String
authors: [Int]!
topics: [String]
chat: Chat chat: Chat
createdAt: Int! createdAt: Int!
updatedAt: Int
} }