From 11f81d46ce397e8a475b3eb9636b6827c85baabe Mon Sep 17 00:00:00 2001 From: tonyrewin Date: Sun, 19 Jun 2022 20:54:39 +0300 Subject: [PATCH] proposals and refactoring --- orm/__init__.py | 10 +- orm/proposal.py | 42 +++++--- resolvers/__init__.py | 108 +++++++++++++------- resolvers/auth.py | 13 ++- resolvers/collab.py | 191 +++++++++++++++++++++++++++++++++++ resolvers/editor.py | 186 ++++++++++++++++------------------- resolvers/profile.py | 107 +++++++++++++++----- resolvers/zine.py | 224 ++++++++---------------------------------- schema.graphql | 34 ++++++- 9 files changed, 545 insertions(+), 370 deletions(-) create mode 100644 resolvers/collab.py diff --git a/orm/__init__.py b/orm/__init__.py index 5aa2c2ee..958a9310 100644 --- a/orm/__init__.py +++ b/orm/__init__.py @@ -6,9 +6,13 @@ from orm.notification import Notification from orm.shout import Shout, ShoutAuthor, ShoutTopic, ShoutRating, ShoutViewByDay,\ ShoutRatingStorage, ShoutViewStorage from orm.base import Base, engine, local_session -from orm.comment import Comment, CommentRating +from orm.comment import Comment, CommentRating #, CommentRatingStorage +from orm.proposal import Proposal, ProposalRating #, ProposalRatingStorage -__all__ = ["User", "Role", "Community", "Operation", "Permission", "Shout", "Topic", "TopicSubscription", "Notification", "ShoutRating", "Comment", "CommentRating", "UserRating"] +__all__ = ["User", "Role", "Community", "Operation", \ + "Permission", "Shout", "Topic", "TopicSubscription", \ + "Notification", "ShoutRating", "Comment", "CommentRating", \ + "UserRating", "Proposal", "ProposalRating"] Base.metadata.create_all(engine) Operation.init_table() @@ -19,6 +23,8 @@ Role.init_table() with local_session() as session: ShoutRatingStorage.init(session) + # CommentRatingStorage.init(session) + # ProposalRatingStorage.init(session) ShoutViewStorage.init(session) RoleStorage.init(session) UserStorage.init(session) diff --git a/orm/proposal.py b/orm/proposal.py index 2311b8e0..5b4d81f4 100644 --- a/orm/proposal.py +++ b/orm/proposal.py @@ -1,23 +1,33 @@ from typing import List from datetime import datetime -from sqlalchemy import Column, Integer, String, ForeignKey, Datetime - +from sqlalchemy import Column, Integer, String, ForeignKey, DateTime +from sqlalchemy.orm import relationship from orm import Permission from orm.base import Base -class Proposal(Base): - __tablename__ = 'proposal' +class ProposalRating(Base): + __tablename__ = "comment_rating" - shout: int = Column(Integer, ForeignKey("shout.id"), nullable=False, comment="Shout") - range: str = Column(String, nullable=True, comment="Range in format :") - body: str = Column(String, nullable=False, comment="Body") - createdBy: int = Column(Integer, ForeignKey("user.id"), nullable=False, comment="Author") - createdAt: str = Column(datetime, nullable=False, comment="Created at") - updatedAt: str = Column(datetime, nullable=True, comment="Updated at") - acceptedAt: str = Column(datetime, nullable=True, comment="Accepted at") - acceptedBy: str = Column(datetime, nullable=True, comment="Accepted by") - deletedAt: str = Column(datetime, nullable=True, comment="Deleted at") - declinedAt: str = Column(datetime, nullable=True, comment="Declined at) - declinedBy: str = Column(datetime, nullable=True, comment="Declined by") - # TODO: debug, logix \ No newline at end of file + id = None + proposal_id = Column(ForeignKey('proposal.id'), primary_key = True) + createdBy = Column(ForeignKey('user.slug'), primary_key = True) + createdAt: str = Column(DateTime, nullable=False, default = datetime.now, comment="Timestamp") + value = Column(Integer) + +class Proposal(Base): + __tablename__ = 'proposal' + + shout: str = Column(String, ForeignKey("shout.slug"), nullable=False, comment="Shout") + range: str = Column(String, nullable=True, comment="Range in format :") + body: str = Column(String, nullable=False, comment="Body") + createdBy: int = Column(Integer, ForeignKey("user.id"), nullable=False, comment="Author") + createdAt: str = Column(DateTime, nullable=False, comment="Created at") + updatedAt: str = Column(DateTime, nullable=True, comment="Updated at") + acceptedAt: str = Column(DateTime, nullable=True, comment="Accepted at") + acceptedBy: str = Column(Integer, ForeignKey("user.id"), nullable=True, comment="Accepted by") + declinedAt: str = Column(DateTime, nullable=True, comment="Declined at") + declinedBy: str = Column(Integer, ForeignKey("user.id"), nullable=True, comment="Declined by") + ratings = relationship(ProposalRating, foreign_keys=ProposalRating.proposal_id) + deletedAt: str = Column(DateTime, nullable=True, comment="Deleted at") + # TODO: debug, logix \ No newline at end of file diff --git a/resolvers/__init__.py b/resolvers/__init__.py index 89e5c0c3..3e818f73 100644 --- a/resolvers/__init__.py +++ b/resolvers/__init__.py @@ -1,42 +1,74 @@ -from resolvers.auth import login, sign_out, is_email_used, register, confirm -from resolvers.zine import create_shout, get_shout_by_slug, \ - top_month, top_overall, recent_published, recent_all, top_viewed, \ - shouts_by_authors, shouts_by_topics, shouts_by_communities, \ - shouts_reviewed, shouts_subscribed -from resolvers.profile import get_users_by_slugs, get_current_user +from resolvers.auth import login, sign_out, is_email_used, register, confirm, auth_forget, auth_reset +from resolvers.zine import get_shout_by_slug, subscribe, unsubscribe, view_shout, rate_shout, \ + top_month, top_overall, recent_published, recent_all, top_viewed, \ + shouts_by_authors, shouts_by_topics, shouts_by_communities +from resolvers.profile import get_users_by_slugs, get_current_user, shouts_reviewed, shouts_subscribed from resolvers.topics import topic_subscribe, topic_unsubscribe, topics_by_author, \ - topics_by_community, topics_by_slugs -from resolvers.comments import create_comment + topics_by_community, topics_by_slugs +from resolvers.comments import create_comment, delete_comment, update_comment, rate_comment +from resolvers.collab import get_shout_proposals, create_proposal, delete_proposal, \ + update_proposal, rate_proposal, decline_proposal, disable_proposal, accept_proposal +from resolvers.editor import create_shout, delete_shout, update_shout from resolvers.community import create_community, delete_community, get_community, get_communities __all__ = [ - "login", - "register", - "is_email_used", - "confirm", - # TODO: "reset_password_code", - # TODO: "reset_password_confirm", - "create_shout", - "get_current_user", - "get_users_by_slugs", - "get_shout_by_slug", - "recent_published", - "recent_all", - "shouts_by_topics", - "shouts_by_authors", - "shouts_by_communities", - "shouts_subscribed", - "shouts_reviewed", - "top_month", - "top_overall", - "top_viewed", - "topics_by_slugs", - "topics_by_community", - "topics_by_author", - "topic_subscribe", - "topic_unsubscribe", - "create_community", - "delete_community", - "get_community", - "get_communities" - ] + # auth + "login", + "register", + "is_email_used", + "confirm", + "auth_forget", + "auth_reset" + + # profile + "get_current_user", + "get_users_by_slugs", + + # zine + "recent_published", + "recent_all", + "shouts_by_topics", + "shouts_by_authors", + "shouts_by_communities", + "shouts_subscribed", + "shouts_reviewed", + "top_month", + "top_overall", + "top_viewed", + "rate_shout", + "view_shout", + "get_shout_by_slug", + + # editor + "create_shout", + "update_shout", + "delete_shout", + + # topics + "topics_by_slugs", + "topics_by_community", + "topics_by_author", + "topic_subscribe", + "topic_unsubscribe", + + # communities + "get_community", + "get_communities", + "create_community", + "delete_community", + + # comments + "get_shout_comments", + "create_comment", + "update_comment", + "delete_comment", + + # collab + "get_shout_proposals", + "create_proposal", + "update_proposal", + "disable_proposal", + "accept_proposal", + "decline_proposal", + "delete_proposal" + ] diff --git a/resolvers/auth.py b/resolvers/auth.py index 15a24b3b..a64accb5 100644 --- a/resolvers/auth.py +++ b/resolvers/auth.py @@ -18,6 +18,7 @@ from settings import JWT_AUTH_HEADER @mutation.field("confirmEmail") async def confirm(*_, confirm_token): + ''' confirm owning email address ''' auth_token, user = await Authorize.confirm(confirm_token) if auth_token: user.emailConfirmed = True @@ -29,6 +30,7 @@ async def confirm(*_, confirm_token): @mutation.field("registerUser") async def register(*_, email: str, password: str = ""): + ''' creates new user account ''' with local_session() as session: user = session.query(User).filter(User.email == email).first() if user: @@ -51,7 +53,8 @@ async def register(*_, email: str, password: str = ""): return { "user": user } @mutation.field("requestPasswordUpdate") -async def request_password_update(_, info, email): +async def auth_forget(_, info, email): + ''' send email to recover account ''' with local_session() as session: user = session.query(User).filter(User.email == email).first() if not user: @@ -62,9 +65,10 @@ async def request_password_update(_, info, email): return {} @mutation.field("updatePassword") -async def update_password(_, info, password, token): +async def auth_reset(_, info, password, resetToken): + ''' set the new password ''' try: - user_id = await ResetPassword.verify(token) + user_id = await ResetPassword.verify(resetToken) except InvalidToken as e: return {"error" : e.message} @@ -79,6 +83,7 @@ async def update_password(_, info, password, token): @query.field("signIn") async def login(_, info: GraphQLResolveInfo, email: str, password: str = ""): + with local_session() as session: orm_user = session.query(User).filter(User.email == email).first() if orm_user is None: @@ -126,4 +131,4 @@ async def sign_out(_, info: GraphQLResolveInfo): async def is_email_used(_, info, email): with local_session() as session: user = session.query(User).filter(User.email == email).first() - return not user is None + return not user is None \ No newline at end of file diff --git a/resolvers/collab.py b/resolvers/collab.py new file mode 100644 index 00000000..929f65f4 --- /dev/null +++ b/resolvers/collab.py @@ -0,0 +1,191 @@ +from orm import Proposal, ProposalRating, UserStorage +from orm.base import local_session +from resolvers.base import mutation, query, subscription +from auth.authenticate import login_required +import asyncio +from datetime import datetime + + +class ProposalResult: + def __init__(self, status, proposal): + self.status = status + self.proposal = proposal + +@query.field("getShoutProposals") +@login_required +async def get_shout_proposals(_, info, slug): + auth = info.context["request"].auth + user_id = auth.user_id + with local_session() as session: + proposals = session.query(Proposal).\ + options(selectinload(Proposal.ratings)).\ + filter(Proposal.shout == slug).\ + group_by(Proposal.id).all() + shout = session.query(Shout).filter(Shout.slug == slug).first() + authors = [author.id for author in shout.authors] + if user_id not in authors: + return {"error": "access denied"} + for proposal in proposals: + proposal.createdBy = await UserStorage.get_user(proposal.createdBy) + return proposals + + +@mutation.field("createProposal") +@login_required +async def create_proposal(_, info, body, shout, range = None): + auth = info.context["request"].auth + user_id = auth.user_id + + proposal = Proposal.create( + createdBy = user_id, + body = body, + shout = shout, + range = range + ) + + result = ProposalResult("NEW", proposal) + await ProposalSubscriptions.put(result) + + return {"proposal": proposal} + +@mutation.field("updateProposal") +@login_required +async def update_proposal(_, info, id, body): + auth = info.context["request"].auth + user_id = auth.user_id + + with local_session() as session: + proposal = session.query(Proposal).filter(Proposal.id == id).first() + shout = session.query(Shout).filter(Shout.sllug == proposal.shout).first() + authors = [author.id for author in shout.authors] + if not proposal: + return {"error": "invalid proposal id"} + if proposal.author in authors: + return {"error": "access denied"} + proposal.body = body + proposal.updatedAt = datetime.now() + session.commit() + + result = ProposalResult("UPDATED", proposal) + await ProposalSubscriptions.put(result) + + return {"proposal": proposal} + +@mutation.field("deleteProposal") +@login_required +async def delete_proposal(_, info, id): + auth = info.context["request"].auth + user_id = auth.user_id + + with local_session() as session: + proposal = session.query(Proposal).filter(Proposal.id == id).first() + if not proposal: + return {"error": "invalid proposal id"} + if proposal.createdBy != user_id: + return {"error": "access denied"} + + proposal.deletedAt = datetime.now() + session.commit() + + result = ProposalResult("DELETED", proposal) + await ProposalSubscriptions.put(result) + + return {} + +@mutation.field("disableProposal") +@login_required +async def disable_proposal(_, info, id): + auth = info.context["request"].auth + user_id = auth.user_id + + with local_session() as session: + proposal = session.query(Proposal).filter(Proposal.id == id).first() + if not proposal: + return {"error": "invalid proposal id"} + if proposal.createdBy != user_id: + return {"error": "access denied"} + + proposal.deletedAt = datetime.now() + session.commit() + + result = ProposalResult("DISABLED", proposal) + await ProposalSubscriptions.put(result) + + return {} + +@mutation.field("rateProposal") +@login_required +async def rate_proposal(_, info, id, value): + auth = info.context["request"].auth + user_id = auth.user_id + + with local_session() as session: + proposal = session.query(Proposal).filter(Proposal.id == id).first() + if not proposal: + return {"error": "invalid proposal id"} + + rating = session.query(ProposalRating).\ + filter(ProposalRating.proposal_id == id and ProposalRating.createdBy == user_id).first() + if rating: + rating.value = value + session.commit() + + if not rating: + ProposalRating.create( + proposal_id = id, + createdBy = user_id, + value = value) + + result = ProposalResult("UPDATED_RATING", proposal) + await ProposalSubscriptions.put(result) + + return {} + + +@mutation.field("acceptProposal") +@login_required +async def accept_proposal(_, info, id): + auth = info.context["request"].auth + user_id = auth.user_id + + with local_session() as session: + proposal = session.query(Proposal).filter(Proposal.id == id).first() + shout = session.query(Shout).filter(Shout.slug == proposal.shout).first() + authors = [author.id for author in shout.authors] + if not proposal: + return {"error": "invalid proposal id"} + if user_id not in authors: + return {"error": "access denied"} + + proposal.acceptedAt = datetime.now() + proposal.acceptedBy = user_id + session.commit() + + result = ProposalResult("ACCEPTED", proposal) + await ProposalSubscriptions.put(result) + + return {} + +@mutation.field("declineProposal") +@login_required +async def decline_proposal(_, info, id): + auth = info.context["request"].auth + user_id = auth.user_id + + with local_session() as session: + proposal = session.query(Proposal).filter(Proposal.id == id).first() + shout = session.query(Shout).filter(Shout.slug == proposal.shout).first() + authors = [author.id for author in shout.authors] + if not proposal: + return {"error": "invalid proposal id"} + if user_id not in authors: + return {"error": "access denied"} + + proposal.acceptedAt = datetime.now() + proposal.acceptedBy = user_id + session.commit() + + result = ProposalResult("DECLINED", proposal) + await ProposalSubscriptions.put(result) + + return {} diff --git a/resolvers/editor.py b/resolvers/editor.py index 9e70632d..17d4c841 100644 --- a/resolvers/editor.py +++ b/resolvers/editor.py @@ -1,123 +1,109 @@ -from orm import Proposal, ProposalRating +from orm import Shout, ShoutRating, ShoutRatingStorage from orm.base import local_session from resolvers.base import mutation, query, subscription from auth.authenticate import login_required import asyncio from datetime import datetime -class ProposalResult: - def __init__(self, status, proposal): - self.status = status - self.proposal = proposal -@mutation.field("createProposal") +@mutation.field("createShout") @login_required -async def create_proposal(_, info, body, shout, range = None): +async def create_shout(_, info, input): + user = info.context["request"].user + + topic_slugs = input.get("topic_slugs", []) + if topic_slugs: + del input["topic_slugs"] + + new_shout = Shout.create(**input) + ShoutAuthor.create( + shout = new_shout.slug, + user = user.slug) + + if "mainTopic" in input: + topic_slugs.append(input["mainTopic"]) + + for slug in topic_slugs: + topic = ShoutTopic.create( + shout = new_shout.slug, + topic = slug) + new_shout.topic_slugs = topic_slugs + + task = GitTask( + input, + user.username, + user.email, + "new shout %s" % (new_shout.slug) + ) + + await ShoutSubscriptions.send_shout(new_shout) + + return { + "shout" : new_shout + } + +@mutation.field("updateShout") +@login_required +async def update_shout(_, info, input): auth = info.context["request"].auth user_id = auth.user_id - proposal = Proposal.create( - createdBy = user_id, - body = body, - shout = shout, - range = range + slug = input["slug"] + + session = local_session() + user = session.query(User).filter(User.id == user_id).first() + shout = session.query(Shout).filter(Shout.slug == slug).first() + + if not shout: + return { + "error" : "shout not found" + } + + authors = [author.id for author in shout.authors] + if not user_id in authors: + scopes = auth.scopes + print(scopes) + if not Resource.shout_id in scopes: + return { + "error" : "access denied" + } + + shout.update(input) + shout.updatedAt = datetime.now() + session.commit() + session.close() + + for topic in input.get("topic_slugs", []): + ShoutTopic.create( + shout = slug, + topic = topic) + + task = GitTask( + input, + user.username, + user.email, + "update shout %s" % (slug) ) - result = ProposalResult("NEW", proposal) - await ProposalSubscriptions.put(result) + return { + "shout" : shout + } - return {"proposal": proposal} - -@mutation.field("updateProposal") +@mutation.field("deleteShout") @login_required -async def update_proposal(_, info, id, body): +async def delete_shout(_, info, slug): auth = info.context["request"].auth user_id = auth.user_id with local_session() as session: - proposal = session.query(Proposal).filter(Proposal.id == id).first() - shout = session.query(Shout.slug === proposal.shout) - if not proposal: - return {"error": "invalid proposal id"} - if proposal.author != user_id: - return {"error": "access denied"} - proposal.body = body - proposal.updatedAt = datetime.now() - session.commit() - - result = ProposalResult("UPDATED", proposal) - await ProposalSubscriptions.put(result) - - return {"proposal": proposal} - -@mutation.field("deleteProposal") -@login_required -async def delete_proposal(_, info, id): - auth = info.context["request"].auth - user_id = auth.user_id - - with local_session() as session: - proposal = session.query(Proposal).filter(Proposal.id == id).first() - if not proposal: - return {"error": "invalid proposal id"} - if proposal.createdBy != user_id: + shout = session.query(Shout).filter(Shout.slug == slug).first() + authors = [author.id for author in shout.authors] + if not comment: + return {"error": "invalid shout slug"} + if user_id not in authors: return {"error": "access denied"} - proposal.deletedAt = datetime.now() + shout.deletedAt = datetime.now() session.commit() - result = ProposalResult("DELETED", proposal) - await ProposalSubscriptions.put(result) - - return {} - -@mutation.field("rateProposal") -@login_required -async def rate_proposal(_, info, id, value): - auth = info.context["request"].auth - user_id = auth.user_id - - with local_session() as session: - proposal = session.query(Proposal).filter(Proposal.id == id).first() - if not proposal: - return {"error": "invalid proposal id"} - - rating = session.query(ProposalRating).\ - filter(ProposalRating.proposal_id == id and ProposalRating.createdBy == user_id).first() - if rating: - rating.value = value - session.commit() - - if not rating: - ProposalRating.create( - proposal_id = id, - createdBy = user_id, - value = value) - - result = ProposalResult("UPDATED_RATING", proposal) - await ProposalSubscriptions.put(result) - - return {} - - -@mutation.field("acceptProposal") -@login_required -async def accept_proposal(_, info, id): - auth = info.context["request"].auth - user_id = auth.user_id - - with local_session() as session: - proposal = session.query(Proposal).filter(Proposal.id == id).first() - if not proposal: - return {"error": "invalid proposal id"} - if proposal.acceptedBy == user_id: # TODO: manage ACL here to give access all editors - return {"error": "access denied"} - - proposal.acceptedAt = datetime.now() - proposal.acceptedBy = user_id - session.commit() - - result = ProposalResult("ACCEPTED", proposal) - await ProposalSubscriptions.put(result) - - return {} + return {} \ No newline at end of file diff --git a/resolvers/profile.py b/resolvers/profile.py index a0bf0c51..43ed42d1 100644 --- a/resolvers/profile.py +++ b/resolvers/profile.py @@ -153,36 +153,95 @@ def author_unsubscribe(user, slug): session.delete(sub) session.commit() -@mutation.field("subscribe") +@query.field("shoutsRatedByUser") @login_required -async def subscribe(_, info, subscription, slug): +async def shouts_rated_by_user(_, info, page, size): user = info.context["request"].user - try: - if subscription == "AUTHOR": - author_subscribe(user, slug) - elif subscription == "TOPIC": - topic_subscribe(user, slug) - elif subscription == "COMMUNITY": - community_subscribe(user, slug) - except Exception as e: - return {"error" : e} + with local_session() as session: + shouts = session.query(Shout).\ + join(ShoutRating).\ + where(ShoutRating.rater == user.slug).\ + order_by(desc(ShoutRating.ts)).\ + limit(size).\ + offset( (page - 1) * size) - return {} + return { + "shouts" : shouts + } -@mutation.field("unsubscribe") +@query.field("userUnpublishedShouts") @login_required -async def unsubscribe(_, info, subscription, slug): +async def user_unpublished_shouts(_, info, page, size): user = info.context["request"].user - try: - if subscription == "AUTHOR": - author_unsubscribe(user, slug) - elif subscription == "TOPIC": - topic_unsubscribe(user, slug) - elif subscription == "COMMUNITY": - community_unsubscribe(user, slug) - except Exception as e: - return {"error" : e} + with local_session() as session: + shouts = session.query(Shout).\ + join(ShoutAuthor).\ + where(and_(Shout.publishedAt == None, ShoutAuthor.user == user.slug)).\ + order_by(desc(Shout.createdAt)).\ + limit(size).\ + offset( (page - 1) * size) + + return { + "shouts" : shouts + } + +@query.field("shoutsReviewed") +@login_required +async def shouts_reviewed(_, info, page, size): + user = info.context["request"].user + with local_session() as session: + shouts_by_rating = session.query(Shout).\ + join(ShoutRating).\ + where(and_(Shout.publishedAt != None, ShoutRating.rater == user.slug)) + shouts_by_comment = session.query(Shout).\ + join(Comment).\ + where(and_(Shout.publishedAt != None, Comment.author == user.id)) + shouts = shouts_by_rating.union(shouts_by_comment).\ + order_by(desc(Shout.publishedAt)).\ + limit(size).\ + offset( (page - 1) * size) + + return shouts + +@query.field("shoutsSubscribed") +@login_required +async def shouts_subscribed(_, info, page, size): + user = info.context["request"].user + with local_session() as session: + shouts_by_topic = session.query(Shout).\ + join(ShoutTopic).\ + join(TopicSubscription, ShoutTopic.topic == TopicSubscription.topic).\ + where(TopicSubscription.subscriber == user.slug) + shouts_by_author = session.query(Shout).\ + join(ShoutAuthor).\ + join(AuthorSubscription, ShoutAuthor.user == AuthorSubscription.author).\ + where(AuthorSubscription.subscriber == user.slug) + shouts_by_community = session.query(Shout).\ + join(Community).\ + join(CommunitySubscription).\ + where(CommunitySubscription.subscriber == user.slug) + shouts = shouts_by_topic.union(shouts_by_author).\ + union(shouts_by_community).\ + order_by(desc(Shout.createdAt)).\ + limit(size).\ + offset( (page - 1) * size) + + return shouts + +@query.field("shoutsCommentedByUser") +async def shouts_commented_by_user(_, info, slug, page, size): + user = await UserStorage.get_user_by_slug(slug) + if not user: + return {} + + with local_session() as session: + shouts = session.query(Shout).\ + join(Comment).\ + where(Comment.author == user.id).\ + order_by(desc(Comment.createdAt)).\ + limit(size).\ + offset( (page - 1) * size) + return shouts - return {} diff --git a/resolvers/zine.py b/resolvers/zine.py index 19be614f..e7d550ec 100644 --- a/resolvers/zine.py +++ b/resolvers/zine.py @@ -257,113 +257,6 @@ async def recent_commented(_, info, page, size): async with ShoutsCache.lock: return ShoutsCache.recent_commented[(page - 1) * size : page * size] -@mutation.field("createShout") -@login_required -async def create_shout(_, info, input): - user = info.context["request"].user - - topic_slugs = input.get("topic_slugs", []) - if topic_slugs: - del input["topic_slugs"] - - new_shout = Shout.create(**input) - ShoutAuthor.create( - shout = new_shout.slug, - user = user.slug) - - if "mainTopic" in input: - topic_slugs.append(input["mainTopic"]) - - for slug in topic_slugs: - topic = ShoutTopic.create( - shout = new_shout.slug, - topic = slug) - new_shout.topic_slugs = topic_slugs - - task = GitTask( - input, - user.username, - user.email, - "new shout %s" % (new_shout.slug) - ) - - await ShoutSubscriptions.send_shout(new_shout) - - return { - "shout" : new_shout - } - -@mutation.field("updateShout") -@login_required -async def update_shout(_, info, input): - auth = info.context["request"].auth - user_id = auth.user_id - - slug = input["slug"] - - session = local_session() - user = session.query(User).filter(User.id == user_id).first() - shout = session.query(Shout).filter(Shout.slug == slug).first() - - if not shout: - return { - "error" : "shout not found" - } - - authors = [author.id for author in shout.authors] - if not user_id in authors: - scopes = auth.scopes - print(scopes) - if not Resource.shout_id in scopes: - return { - "error" : "access denied" - } - - shout.update(input) - shout.updatedAt = datetime.now() - session.commit() - session.close() - - for topic in input.get("topic_slugs", []): - ShoutTopic.create( - shout = slug, - topic = topic) - - task = GitTask( - input, - user.username, - user.email, - "update shout %s" % (slug) - ) - - return { - "shout" : shout - } - -@mutation.field("rateShout") -@login_required -async def rate_shout(_, info, slug, value): - auth = info.context["request"].auth - user = info.context["request"].user - - with local_session() as session: - rating = session.query(ShoutRating).\ - filter(and_(ShoutRating.rater == user.slug, ShoutRating.shout == slug)).first() - if rating: - rating.value = value; - rating.ts = datetime.now() - session.commit() - else: - rating = ShoutRating.create( - rater = user.slug, - shout = slug, - value = value - ) - - await ShoutRatingStorage.update_rating(rating) - - return {"error" : ""} - @mutation.field("viewShout") async def view_shout(_, info, slug): await ShoutViewStorage.inc_view(slug) @@ -439,94 +332,61 @@ async def shouts_by_communities(_, info, slugs, page, size): offset(page * size) return shouts -@query.field("shoutsSubscribed") +@mutation.field("subscribe") @login_required -async def shouts_subscribed(_, info, page, size): +async def subscribe(_, info, subscription, slug): user = info.context["request"].user - with local_session() as session: - shouts_by_topic = session.query(Shout).\ - join(ShoutTopic).\ - join(TopicSubscription, ShoutTopic.topic == TopicSubscription.topic).\ - where(TopicSubscription.subscriber == user.slug) - shouts_by_author = session.query(Shout).\ - join(ShoutAuthor).\ - join(AuthorSubscription, ShoutAuthor.user == AuthorSubscription.author).\ - where(AuthorSubscription.subscriber == user.slug) - shouts_by_community = session.query(Shout).\ - join(Community).\ - join(CommunitySubscription).\ - where(CommunitySubscription.subscriber == user.slug) - shouts = shouts_by_topic.union(shouts_by_author).\ - union(shouts_by_community).\ - order_by(desc(Shout.createdAt)).\ - limit(size).\ - offset( (page - 1) * size) - return shouts + try: + if subscription == "AUTHOR": + author_subscribe(user, slug) + elif subscription == "TOPIC": + topic_subscribe(user, slug) + elif subscription == "COMMUNITY": + community_subscribe(user, slug) + except Exception as e: + return {"error" : e} -@query.field("shoutsReviewed") + return {} + +@mutation.field("unsubscribe") @login_required -async def shouts_reviewed(_, info, page, size): +async def unsubscribe(_, info, subscription, slug): user = info.context["request"].user - with local_session() as session: - shouts_by_rating = session.query(Shout).\ - join(ShoutRating).\ - where(and_(Shout.publishedAt != None, ShoutRating.rater == user.slug)) - shouts_by_comment = session.query(Shout).\ - join(Comment).\ - where(and_(Shout.publishedAt != None, Comment.author == user.id)) - shouts = shouts_by_rating.union(shouts_by_comment).\ - order_by(desc(Shout.publishedAt)).\ - limit(size).\ - offset( (page - 1) * size) - return shouts + try: + if subscription == "AUTHOR": + author_unsubscribe(user, slug) + elif subscription == "TOPIC": + topic_unsubscribe(user, slug) + elif subscription == "COMMUNITY": + community_unsubscribe(user, slug) + except Exception as e: + return {"error" : e} -@query.field("shoutsCommentedByUser") -async def shouts_commented_by_user(_, info, slug, page, size): - user = await UserStorage.get_user_by_slug(slug) - if not user: - return {} + return {} - with local_session() as session: - shouts = session.query(Shout).\ - join(Comment).\ - where(Comment.author == user.id).\ - order_by(desc(Comment.createdAt)).\ - limit(size).\ - offset( (page - 1) * size) - return shouts -@query.field("shoutsRatedByUser") +@mutation.field("rateShout") @login_required -async def shouts_rated_by_user(_, info, page, size): +async def rate_shout(_, info, slug, value): + auth = info.context["request"].auth user = info.context["request"].user with local_session() as session: - shouts = session.query(Shout).\ - join(ShoutRating).\ - where(ShoutRating.rater == user.slug).\ - order_by(desc(ShoutRating.ts)).\ - limit(size).\ - offset( (page - 1) * size) + rating = session.query(ShoutRating).\ + filter(and_(ShoutRating.rater == user.slug, ShoutRating.shout == slug)).first() + if rating: + rating.value = value; + rating.ts = datetime.now() + session.commit() + else: + rating = ShoutRating.create( + rater = user.slug, + shout = slug, + value = value + ) - return { - "shouts" : shouts - } + await ShoutRatingStorage.update_rating(rating) -@query.field("userUnpublishedShouts") -@login_required -async def user_unpublished_shouts(_, info, page, size): - user = info.context["request"].user - - with local_session() as session: - shouts = session.query(Shout).\ - join(ShoutAuthor).\ - where(and_(Shout.publishedAt == None, ShoutAuthor.user == user.slug)).\ - order_by(desc(Shout.createdAt)).\ - limit(size).\ - offset( (page - 1) * size) - - return { - "shouts" : shouts - } + return {"error" : ""} \ No newline at end of file diff --git a/schema.graphql b/schema.graphql index 469e20e4..45f2b1e9 100644 --- a/schema.graphql +++ b/schema.graphql @@ -126,15 +126,26 @@ type Mutation { createTopic(input: TopicInput!): TopicResult! updateTopic(input: TopicInput!): TopicResult! + # comments createComment(body: String!, shout: String!, replyTo: Int): CommentResult! updateComment(id: Int!, body: String!): CommentResult! deleteComment(id: Int!): Result! rateComment(id: Int!, value: Int!): Result! + # community createCommunity(title: String!, desc: String!): Community! updateCommunity(community: CommunityInput!): Community! deleteCommunity(id: Int!): Result! + # proposal + createProposal(body: String!, range: String): Proposal! + updateProposal(body: String!, range: String): Proposal! + acceptProposal(id: Int!): Result! + declineProposal(id: Int!): Result! + disableProposal(id: Int!): Result! + deleteProposal(id: Int!): Result! + rateProposal(id: Int!): Result! + subscribe(what: SubscriptionType!, slug: String!): Result! unsubscribe(what: SubscriptionType!, slug: String!): Result! } @@ -167,6 +178,9 @@ type Query { shoutsByCommunities(slugs: [String]!, page: Int!, size: Int!): [Shout]! getShoutComments(slug: String!): [Comment]! + # collab + getShoutProposals(slug: String!): [Proposal]! + # mainpage topViewed(page: Int!, size: Int!): [Shout]! topMonth(page: Int!, size: Int!): [Shout]! @@ -353,17 +367,29 @@ type Topic { topicStat: TopicStat } +enum ProposalStatus { + NEW + UPDATED + UPDATED_RATING + ACCEPTED + DECLINED + DISABLED + DELETED +} + type Proposal { - createdBy: String! - shout: string! + shout: String! range: String # full / 0:2340 body: String! createdAt: DateTime! + createdBy: String! updatedAt: DateTime acceptedAt: DateTime - acceptedBy: string + acceptedBy: Int declinedAt: DateTime - declinedBy: string + declinedBy: Int + disabledAt: DateTime + disabledBy: Int } type Token {