From 0240005ed1f9044c4b7ba16614b7dcc4934b35e1 Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 28 Nov 2023 13:46:06 +0300 Subject: [PATCH] invite-feature --- CHANGELOG.txt | 2 + orm/invite.py | 25 ++++++++ resolvers/collab.py | 136 +++++++++++++++++++++++++++++++++++++++++++ schemas/core.graphql | 9 ++- 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 orm/invite.py create mode 100644 resolvers/collab.py diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 3bb3717a..e648419b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,4 +1,6 @@ [0.2.16] +- resolvers: collab inviting logics +- orm: invite entity - schema: Reaction.range -> Reaction.quote - resolvers: queries and mutations revision and renaming - resolvers: delete_topic(slug) implemented diff --git a/orm/invite.py b/orm/invite.py new file mode 100644 index 00000000..3032693f --- /dev/null +++ b/orm/invite.py @@ -0,0 +1,25 @@ +from sqlalchemy import Column, ForeignKey, Enum +from sqlalchemy.orm import relationship +from services.db import Base +from orm.author import Author +from orm.shout import Shout +from enum import Enum as Enumeration + + +class InviteStatus(Enumeration): + PENDING = 0 + ACCEPTED = 1 + REJECTED = 2 + + +class Invite(Base): + __tablename__ = "invite" + + inviter_id = Column(ForeignKey("author.id"), nullable=False, index=True) + invitee_id = Column(ForeignKey("author.id"), nullable=False, index=True) + shout_id = Column(ForeignKey("shout.id"), nullable=False, index=True) + status = Column(Enum(InviteStatus), default=InviteStatus.PENDING) + + inviter = relationship(Author, foreign_keys=[inviter_id]) + invitee = relationship(Author, foreign_keys=[invitee_id]) + shout = relationship(Shout) diff --git a/resolvers/collab.py b/resolvers/collab.py new file mode 100644 index 00000000..3eb9b966 --- /dev/null +++ b/resolvers/collab.py @@ -0,0 +1,136 @@ +from services.auth import login_required +from services.db import local_session +from services.schema import mutation +from orm.invite import Invite, InviteStatus +from orm.author import Author +from orm.shout import Shout + + +@mutation.field("accept_invite") +@login_required +async def accept_invite(_, info, invite_id: int): + user_id = info.context["user_id"] + + # Check if the user exists + with local_session() as session: + author = session.query(Author).filter(Author.user == user_id).first() + if author: + # Check if the invite exists + invite = session.query(Invite).filter(Invite.id == invite_id).first() + if invite and invite.invitee_id == author.id and invite.status == InviteStatus.PENDING: + # Add the user to the shout authors + shout = session.query(Shout).filter(Shout.id == invite.shout_id).first() + if shout: + shout.authors.append(author) + session.delete(invite) + session.commit() + return {"success": True, "message": "Invite accepted"} + else: + return {"error": "Shout not found"} + else: + return {"error": "Invalid invite or already accepted/rejected"} + else: + return {"error": "User not found"} + + +@mutation.field("reject_invite") +@login_required +async def reject_invite(_, info, invite_id: int): + user_id = info.context["user_id"] + + # Check if the user exists + with local_session() as session: + author = session.query(Author).filter(Author.user == user_id).first() + if author: + # Check if the invite exists + invite = session.query(Invite).filter(Invite.id == invite_id).first() + if invite and invite.invitee_id == author.id and invite.status == InviteStatus.PENDING: + # Delete the invite + session.delete(invite) + session.commit() + return {"success": True, "message": "Invite rejected"} + else: + return {"error": "Invalid invite or already accepted/rejected"} + else: + return {"error": "User not found"} + + +@mutation.field("create_invite") +@login_required +async def create_invite(_, info, slug: str = "", author_id: int = None, user: str = ""): + user_id = info.context["user_id"] + + # Check if the inviter is the owner of the shout + with local_session() as session: + shout = session.query(Shout).filter(Shout.slug == slug).first() + if shout and shout.authors and user_id in [author.id for author in shout.authors]: + # Check if the invitee is a valid author + invitee = session.query(Author).filter(Author.id == author_id).first() + if invitee: + # Check if an invite already exists + existing_invite = ( + session.query(Invite) + .filter( + Invite.inviter_id == user_id, + Invite.invitee_id == author_id, + Invite.shout_id == shout.id, + Invite.status == InviteStatus.PENDING, + ) + .first() + ) + if existing_invite: + return {"error": "Invite already sent"} + + # Create a new invite + new_invite = Invite( + inviter_id=user_id, invitee_id=author_id, shout_id=shout.id, status=InviteStatus.PENDING + ) + session.add(new_invite) + session.commit() + + return {"error": None, "invite": new_invite} + else: + return {"error": "Invalid invitee"} + else: + return {"error": "Access denied"} + + +@mutation.field("remove_author") +@login_required +async def remove_author(_, info, slug: str = "", author_id: int = None, user: str = ""): + user_id = info.context["user_id"] + with local_session() as session: + author = session.query(Author).filter(Author.user == user_id).first() + if author: + shout = session.query(Shout).filter(Shout.slug == slug).first() + # NOTE: owner should be first in a list + if shout and author.id == shout.authors.index(0): + shout.authors = [author for author in shout.authors if author.id != author_id] + session.commit() + return {} + return {"error": "Access denied"} + + +@mutation.field("remove_invite") +@login_required +async def remove_invite(_, info, invite_id: int): + user_id = info.context["user_id"] + + # Check if the user exists + with local_session() as session: + author = session.query(Author).filter(Author.user == user_id).first() + if author: + # Check if the invite exists + invite = session.query(Invite).filter(Invite.id == invite_id).first() + shout = session.query(Shout).filter(Shout.id == invite.shout_id).first() + if shout and shout.deleted_at is None and invite: + if invite.inviter_id == author.id or author.id == shout.authors.index(0): + if invite.status == InviteStatus.PENDING: + # Delete the invite + session.delete(invite) + session.commit() + return {} + else: + return {"error": "Invalid invite or already accepted/rejected"} + else: + return {"error": "Author not found"} diff --git a/schemas/core.graphql b/schemas/core.graphql index aa0a5a4e..9134d326 100644 --- a/schemas/core.graphql +++ b/schemas/core.graphql @@ -313,6 +313,13 @@ type Mutation { create_reaction(reaction: ReactionInput!): Result! update_reaction(id: Int!, reaction: ReactionInput!): Result! delete_reaction(id: Int!): Result! + + # collab + create_invite(slug: String, authorId: Int, user: String): Result! + remove_author(slug: String, authorId: Int, user: String): Result! + remove_invite(invite_id: Int!): Result! + accept_invite(invite_id: Int!): Result! + reject_invite(invite_id: Int!): Result! } @@ -342,7 +349,7 @@ type Query { # reader get_shout(slug: String, shout_id: Int): Shout - load_shouts_followed(follower_id: Int!, limit: Int, offset: Int): [Shout] + load_shouts_followed(follower_id: Int!, limit: Int, offset: Int): [Shout] # userReactedShouts load_shouts_by(options: LoadShoutsOptions): [Shout] load_shouts_search(text: String!, limit: Int, offset: Int): [Shout] load_shouts_feed(options: LoadShoutsOptions): [Shout]