def channels_join(token, channel_id): """ Given a channel_id of a channel that the authorised user can join, adds them to that channel Parameters: token (str) channel_id (int) Returns: {} """ verify_token(token) user = get_user_from_token(token) if not user: raise AccessError(description="Invalid Token") selected_channel = select_channel(channel_id) if not selected_channel: raise InputError(description="Target channel is not a valid channel") # Check whether channel is private or not. Raise AccessError if it is if not selected_channel.visibility == "public": raise AccessError(description="Target channel isn't public") new_membership = MemberOf( user=user, channel=selected_channel, is_owner=False ) db.session.add(new_membership) db.session.commit() return {}
def channels_leave(token, channel_id): """ Removes the caller from the specified channel. Parameters: token (str) channel_id (str) Returns: {} """ verify_token(token) user = get_user_from_token(token) if not user: raise AccessError(description="Invalid Token") channels_list = Channel.query.all() selected_channel = select_channel(channel_id) # Check if Channel ID is not a valid channel if not selected_channel: raise InputError(description="Channel ID is not a valid channel") # Check user if the user is not currently a member of the selected channel if not is_user_member(user, selected_channel): raise AccessError(description="You are not a member of this channel") membership = MemberOf.query.filter_by(user_id=user.id, channel_id=selected_channel.id).first() db.session.delete(membership) db.session.commit() # If the user attempting to leave is the owner... Pass down ownership to someone else? Or delete channel # TODO: # If there is no members left in channel, delete channel # TODO: return {}
def channels_messages(token, channel_id, start, limit=50): """ Given a channel that the user is a member of, returns up to a specified maximum number of messages from a starting index. Messages are ordered in ascending recency, so the message at index 0 is the most recent. Parameters: token (str) channel_id (int) start (int) limit (int) Returns: { messages, exhausted } Where: messages: list of message dictionary: { message_id, user_id, message, time_created, is_author } exhausted: bool indicating whether the there any more messages left to fetch """ # check parameters are all valid and raise exception if they aren't # add user_id, associated first name and last name into channel_id dictionary (or storage) verify_token(token) user = get_user_from_token(token) if not user: raise AccessError(description="Invalid Token") selected_channel = select_channel(channel_id) if not selected_channel: raise InputError(description="Channel ID is not a valid channel") if is_user_member(user, selected_channel) is False: raise AccessError(description="You are not a member of this channel") # Loop through 50 message dictionaries of list starting from start index messsages_list = selected_channel.messages_sent messsages_list.sort(key=(lambda x: x.time_created)) payload = { "messages": [], "exhausted": True } if not messsages_list: # printColour("Results: {}".format(payload), colour="blue") return payload if start >= len(messsages_list): raise InputError(description="Starting index is greater than or equal to the total number of messages in the channel") if not (start + limit >= len(messsages_list)): messages["exhausted"] = False for i, message in enumerate(messsages_list[start :], start=1): payload["messages"].append({ "message_id": message.id, "user_id": message.user_id, "message": message.message, "time_created": message.time_created.timestamp(), "is_author": message.user_id == user.id }) if i == limit: break # printColour("Results: {}".format(payload), colour="blue") return payload
def channels_details(token, channel_id): """ Given a Channel with ID channel_id that the authorised user is part of, provide basic details about the channel Parameters: token (str) channel_id (channel_id) Returns: { name, description, visibility, channel_img_url, owner_members, all_members } (dict) Where: owner_members: [{ user_id, username, email, profile_img_url }, ...] (list of user objects) all_members: [{ user_id, username, email, profile_img_url }, ...] (list of user objects) """ verify_token(token) user = get_user_from_token(token) if not user: raise AccessError(description="Invalid Token") selected_channel = select_channel(channel_id) if not selected_channel: raise InputError(description=f"{channel_id} doesn't point to a valid channel") # Raise exception when the user is not a member of the channel with the given channel_id if not is_user_member(user, selected_channel): raise AccessError(description="You are not a member of this channel") channel_owners = [] channel_members = [] # Joining User with MemberOf, then filtering for users associated with the selected channel # TODO: Possible optimisation -> swap the ordering of the filtering memberships = db.session.query(User, MemberOf, Channel).outerjoin(MemberOf, MemberOf.user_id==User.id).outerjoin(Channel, Channel.id==MemberOf.channel_id).filter_by(id=channel_id).all() for each_membership in memberships: curr_member = each_membership[0] member_data = { "user_id": curr_member.id, "username": curr_member.username, "profile_img_url": curr_member.bio.profile_img_url, "email": curr_member.email } channel_members.append(member_data) if each_membership[1].is_owner: channel_owners.append(member_data) details = { "name": selected_channel.name, "description": selected_channel.description, "visibility": selected_channel.visibility, "channel_img_url": selected_channel.channel_img_url, "channel_cover_img_url": selected_channel.channel_cover_img_url, "owner_members": channel_owners, "all_members": channel_members } # printColour("Results: {}".format(details), colour="blue") return details
def message_send(token, channel_id, message): """ Sends a message to the selected channel. Parameters: token (str) channel_id (int) message (str) Returns: { message_id } """ verify_token(token) if not message: raise InputError("Message can't be empty") if len(message) > 1000: raise InputError("Message is over 1000 characters") user = get_user_from_token(token) selected_channel = select_channel(channel_id) # Raise error if the user is not a part of the channel they are trying to message in if not is_user_member(user, selected_channel): raise AccessError( description="User is not a member of channel with channel_id") sent_message = Message(channel=selected_channel, user=user, message=message) db.session.add(sent_message) db.session.commit() return {"message_id": sent_message.id}
def channels_invite(token, channel_id, user_id): """ Invites a user, with the given user_id, to join the channel with ID channel_id. Once invited, the user is added to the channel immediately. Parameters: token (str) channel_id (int) user_id (int) Returns: {} (dict) """ printColour("RECEIVED CHANNEL ID: {}".format(channel_id), colour="blue") printColour("RECEIVED USER ID: {}".format(user_id), colour="blue") verify_token(token) calling_user = get_user_from_token(token) if not calling_user: raise AccessError(description="Invalid Token") # If channel_id is invalid, raise input error selected_channel = select_channel(channel_id) if not selected_channel: raise InputError(description="Target channel is not a valid channel that the user is part of.") # Check the authorised user is not already a member of the channel if not is_user_member(calling_user, selected_channel): raise AccessError(description="You are not a member of this channel") invited_user = User.query.filter_by(id=user_id).first() # Check that the user exists (ie. the user_id is valid) if not invited_user: raise InputError(description="Target user is not a valid user") # Check if invited_user is already a member if is_user_member(invited_user, selected_channel): raise InputError(description="{} is already a member".format(invited_user.username)) # Granting membership new_membership = MemberOf( user=invited_user, channel=selected_channel, is_owner=False ) printColour("Trying to add user {} to channel {}".format(new_membership.user.id, new_membership.channel.id), colour="blue") db.session.add(new_membership) db.session.commit() return {}
def auth_logout(token): """ Given a valid token, verifies it and logs out the associated user. Parameters: token (str) Returns: { is_success(True/False) bool } """ if verify_token(token): return {"is_success": True} else: raise AccessError(description="Logout failed. Token is invalid")
def verify_token(token): """ Given a token, checks the database and returns true if the token exists. If the token doesn't exist, then return false. Unfortunately, we need to check ourselves whether that user_id associated with the token has access rights, etc. Parameters: token str Returns: True/False """ try: jwt.decode(token, os.getenv("SECRET_MESSAGE"), algorithms=["HS256"]) return True except jwt.DecodeError: # Token is invalid! raise AccessError(description="Token is invalid! {}".format(token))
def message_remove(token, message_id): """ Removes a message from the list of messages Returns: { old_message } """ verify_token(token) message_obj = Message.query.filter_by(id=message_id).first() if not message_obj: raise InputError("Message doesn't exist") calling_user = get_user_from_token(token) if calling_user.id != message_obj.user_id: raise AccessError("You can't modify someone else's message") # Removes message and saves changes db.session.delete(message_obj) db.session.commit() return {"old_message": message_obj.message}
def get_user_from_token(token): """ Given a token, checks if it exists. If it does, return the user data structure associated with the token. If not, return None Parameters: token str Returns: { user_id int username str email str password(hashed) str permission_id int } """ decoded_token = jwt.decode(token, os.getenv("SECRET_MESSAGE"), algorithms=["HS256"]) if not decoded_token: raise AccessError(description="Invalid token supplied") return User.query.filter_by(id=decoded_token["user_id"]).first()
def message_edit(token, message_id, message): """ Edits an existing message. Deletes it if the new message is empty """ verify_token(token) if len(message) > 1000: raise InputError("Message is over 1000 characters") if not message: raise InputError("New message can't be empty") user = get_user_from_token(token) message_obj = Message.query.filter_by(id=message_id).first() is_user_owner = user_is_owner(token, message_obj.channel_id) is_user_admin = user_is_admin(token) if message_obj.user_id == user.id or is_user_admin or is_user_owner: printColour("Editing message from '{}' to '{}'".format( message_obj.message, message), colour="red_1") message_obj = Message.query.filter_by(id=message_id).first() message_obj.message = message db.session.commit() else: printColour("Not permitted to edit message", colour="red_1") raise AccessError("You are not authorised to edit this message") return {}