def channel_invite(token, channel_id, u_id): """Invites a user (with user id u_id) to join a channel with ID channel_id. Once invited the user is added to the channel immediately""" # Convert channel_id and u_id to integer channel_id = int(channel_id) u_id = int(u_id) # Validate token, channel and user if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) if not validator.is_valid_channel(channel_id): raise ValueError(INVALID_CHANNEL) if not validator.is_valid_user(u_id): raise ValueError(INVALID_USER) # Find invitor invitor_id = jwt_handler.decode_token(token) # Locate channel in database channel = database.get_channel_by_id(channel_id) # Check that the invitor is authorised auth_list = channel["auth_ids"] if invitor_id not in auth_list: raise AccessError(CHANNEL_INV_NO_AUTH) if u_id in auth_list: raise ValueError(CHANNEL_ALREADY_JOINED) auth_list.append(u_id) database.update_channel_by_id(channel_id, { "auth_ids": auth_list }) return {}
def channel_removeowner(token, channel_id, u_id): """Remove user with user id u_id an owner of this channel""" # Convert channel_id and u_id to integer channel_id = int(channel_id) u_id = int(u_id) # Validate token, channel and user if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) if not validator.is_valid_channel(channel_id): raise ValueError(INVALID_CHANNEL) if not validator.is_valid_user(u_id): raise ValueError(INVALID_USER) # Locate channel and user in database channel = database.get_channel_by_id(channel_id) auth_user_id = jwt_handler.decode_token(token) auth_user = database.get_user_by_id(auth_user_id) owner_list = channel["owner_ids"] if auth_user["permission"] not in [OWNER, ADMIN] or\ auth_user_id not in channel["owner_ids"]: raise AccessError(CHANNEL_DEL_OWNER_NO_AUTH) # Check that the added_user is currently an owner if u_id not in owner_list: raise ValueError(CHANNEL_NOT_OWNER) # Remove user from owner_ids owner_list.remove(u_id) database.update_channel_by_id(channel_id, { "owner_ids": owner_list }) return {}
def message_remove(token, message_id): """ Given a message_id for a message, this message is removed from the chxnel """ # Validate token if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) if not isinstance(message_id, int): message_id = int(message_id) # Convert token to u_id u_id = jwt_handler.decode_token(token) # Check u_id is authorised user = database.get_user_by_id(u_id) message = database.get_message_by_id(message_id) if message is None: raise ValueError(MESSAGE_REMOVE_NOT_EXIST) channel_id = message["channel_id"] channel = database.get_channel_by_id(channel_id) # if sender of message if u_id == message["u_id"] or\ user["permission"] in [OWNER, ADMIN] or\ u_id in channel["owner_ids"]: # Delete message from database database.delete_message_by_id(message_id) # Delete message from channel channel["messages"].remove(message_id) database.update_channel_by_id(channel_id, channel) return {} raise AccessError(MESSAGE_EDIT_NO_AUTH)
def message_pin(token, message_id): """ Given a message within a channel, mark it as "pinned" to be given special display treatment by the frontend """ # Validate token if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) # Decode token u_id = jwt_handler.decode_token(token) user = database.get_user_by_id(u_id) message = database.get_message_by_id(message_id) if message is None: raise ValueError(MESSAGE_PIN_NOT_EXIST) channel = database.get_channel_by_id(message["channel_id"]) if user["permission"] not in [OWNER, ADMIN] \ and u_id not in channel["owner_ids"]: raise ValueError(MESSAGE_PIN_NO_AUTH) # Checking data if u_id not in channel["auth_ids"]: raise AccessError(CHANNEL_CANT_VIEW_DETAIL) if message["is_pinned"] is True: raise ValueError(MESSAGE_ALREADY_PINNED) # Pinning message database.update_message_by_id(message_id, {"is_pinned": True}) return {}
def message_unpin(token, message_id): """Given a message within a channel, remove it's mark as unpinned""" # Validate token if not validator.is_valid_token(token): raise ValueError(INVALID_TOKEN) # Decode token u_id = jwt_handler.decode_token(token) user = database.get_user_by_id(u_id) message = database.get_message_by_id(message_id) if message is None: raise ValueError(MESSAGE_UNPIN_NOT_EXIST) channel = database.get_channel_by_id(message["channel_id"]) if user["permission"] not in [OWNER, ADMIN] \ and u_id not in channel["owner_ids"]: raise AccessError(MESSAGE_UNPIN_NO_AUTH) # Checking data if u_id not in channel["auth_ids"]: raise AccessError(CHANNEL_CANT_VIEW_DETAIL) if message["is_pinned"] is False: raise ValueError(MESSAGE_NOT_PINNED) # Unpinning message database.update_message_by_id(message_id, {"is_pinned": False}) return {}
def channel_join(token, channel_id): """Given a channel_id of a channel that the authorised user can join, adds them to that channel""" # Validate token and channel ID if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) if not validator.is_valid_channel(channel_id): raise ValueError(INVALID_CHANNEL) # Convert token to u_id u_id = jwt_handler.decode_token(token) user = database.get_user_by_id(u_id) # Locate channel in database channel = database.get_channel_by_id(channel_id) # If channel is private if not channel["is_public"] and user["permission"] == MEMBER: # If the user is not an admin/owner raise AccessError(CHANNEL_CANT_VIEW_DETAIL) # Add user to authorised users in channel auth_list = channel["auth_ids"] if u_id not in auth_list: auth_list.append(u_id) database.update_channel_by_id(channel_id, { "auth_ids": auth_list }) else: raise ValueError(CHANNEL_ALREADY_JOINED) return {}
def channel_leave(token, channel_id): """Given a channel ID, the user removed as a member of this channel""" # Validate token and channel ID if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) if not validator.is_valid_channel(channel_id): raise ValueError(INVALID_CHANNEL) # Convert token to u_id u_id = jwt_handler.decode_token(token) # Locate channel channel = database.get_channel_by_id(channel_id) # Validate user is in channel auth_list = channel["auth_ids"] if u_id not in auth_list: raise AccessError(NOT_IN_CHANNEL) # Remove user from auth_list auth_list.remove(u_id) database.update_channel_by_id(channel_id, { "auth_ids": auth_list }) # If owner, remove from owner_list owner_list = channel["owner_ids"] if u_id in owner_list: owner_list.remove(u_id) database.update_channel_by_id(channel_id, { "owner_ids": owner_list }) return {}
def message_edit(token, message_id, message): """Given a message, update it's text with new text""" # Validate token if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) # If the message is empty, perform deletion instead if message == "": return message_remove(token, message_id) if not validator.is_valid_message(message): raise ValueError(INVALID_MESSAGE) # Convert token to u_id u_id = jwt_handler.decode_token(token) user = database.get_user_by_id(u_id) message_dict = database.get_message_by_id(message_id) if message_dict is None: raise ValueError(MESSAGE_EDIT_NOT_EXIST) channel_id = message_dict["channel_id"] channel = database.get_channel_by_id(channel_id) # Check if user can edit this message if u_id == message_dict["u_id"] or user["permission"] in [OWNER, ADMIN] \ or u_id in channel["owner_ids"]: database.update_message_by_id(message_id, {"message": message}) return {} raise AccessError(MESSAGE_EDIT_NO_AUTH)
def message_sendlater(token, channel_id, message, time_sent): """ Send a message from authorised_user to the channel specified by channel_id """ # Validate data if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) if not validator.is_valid_message(message): raise ValueError(INVALID_MESSAGE) # Convert token to u_id u_id = jwt_handler.decode_token(token) # Check channel exists channel = database.get_channel_by_id(channel_id) if channel is None: raise ValueError(INVALID_CHANNEL) # Read time_sent time_to_send = json_time_translator.timestamp_to_datetime(time_sent) # Check time_to_send is not in the past if time_to_send <= datetime.utcnow(): raise ValueError(MESSAGE_TIME_INVALID) if u_id not in channel["auth_ids"]: raise AccessError(MESSAGE_SEND_NO_AUTH) # Create message message_id = database.add_message({ "u_id": u_id, "message": message, "channel_id": int(channel_id), "time_created": json_time_translator.datetime_to_json(time_to_send), "is_pinned": False, "reacts": [] }) # Insert into channel's list of messages in order of time message_id_list = channel["messages"] inserted = False for i in reversed(range(len(message_id_list))): json_time = database.get_message_by_id( message_id_list[i])["time_created"] timestamp = json_time_translator.json_to_timestamp(json_time) if timestamp <= int(float(time_sent)): message_id_list.insert(i + 1, message_id) inserted = True break if inserted is False: message_id_list.insert(0, message_id) database.update_channel_by_id(channel_id, {"messages": message_id_list}) return {"message_id": message_id}
def message_react(token, message_id, react_id): """ Given a message within a channel the authorised user is part of, add a "react" to that particular message """ # Validate if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) # Decode token u_id = jwt_handler.decode_token(token) # Change react_id to an integer if not isinstance(react_id, int): react_id = int(react_id) if not isinstance(message_id, int): message_id = int(message_id) # Validate data message = database.get_message_by_id(message_id) channel = database.get_channel_by_id(message["channel_id"]) if u_id not in channel["auth_ids"]: raise AccessError(CHANNEL_CANT_VIEW_DETAIL) if react_id != 1: print(react_id) raise ValueError(INVALID_REACT) react_id_exists = False for react in message["reacts"]: if react_id == react["react_id"]: react_id_exists = True if u_id in react["u_id"]: raise ValueError(MESSAGE_ALREADY_REACTED) break # Add react to message react_list = message["reacts"] if not react_id_exists: react_list.append({"react_id": react_id, "u_id": [u_id]}) else: for react in react_list: if react_id == react["react_id"]: react["u_id"].append(u_id) break database.update_message_by_id(message_id, {"reacts": react_list}) return {}
def standup_start(token, channel_id, length): """Will return standup finish time if user and channel are both valid""" # Check valid token if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) length = int(length) # Check channel existence channel = database.get_channel_by_id(channel_id) if channel is None: raise ValueError(INVALID_CHANNEL) # Check user is member of channel u_id = jwt_handler.decode_token(token) if u_id not in channel["auth_ids"]: raise AccessError(CHANNEL_CANT_VIEW_DETAIL) # Check the startup is not currently active in this channel old_time_end = database.get_standup_finish_time(channel_id) if old_time_end is not None and\ json_time_translator.json_to_datetime(old_time_end) >= datetime.utcnow(): raise ValueError(STANDUP_RUNNING) # Check for non-zero length if length <= 0: raise ValueError(STANDUP_TIME_INVALID) # Set channel standup_end to now+X min and add finish time to database time_end_datetime = datetime.utcnow() + timedelta(seconds=length) time_end_json = json_time_translator.datetime_to_json(time_end_datetime) # Move old standup messages database.move_standup_to_unused_by_id(channel_id) database.update_standup_by_id(channel_id, { "time_finish": time_end_json, "length": length }) time_end_timestamp = int( json_time_translator.datetime_to_timestamp(time_end_datetime)) # Start threaded timer standup_timer_start(token, channel_id, length) return {"time_finish": time_end_timestamp}
def message_unreact(token, message_id, react_id): """ Given a message within a channel the authorised user is part of, remove a "react" to that particular message """ # Validate if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) # Decode token u_id = jwt_handler.decode_token(token) # Change react_id to an integer if not isinstance(react_id, int): react_id = int(react_id) if not isinstance(message_id, int): message_id = int(message_id) # Validate data message = database.get_message_by_id(message_id) channel = database.get_channel_by_id(message["channel_id"]) if u_id not in channel["auth_ids"]: raise AccessError(CHANNEL_CANT_VIEW_DETAIL) if react_id != 1: print(react_id) raise ValueError(INVALID_REACT) react_list = message["reacts"] react_found = False for react in react_list: if react_id == react["react_id"] and u_id in react["u_id"]: react_found = True break if not react_found: raise ValueError(MESSAGE_NOT_REACTED) # Remove react from message for react in react_list: if react_id == react["react_id"]: react["u_id"].remove(u_id) database.update_message_by_id(message_id, {"reacts": react_list}) return {}
def channel_details(token, channel_id): """Given a Channel with ID channel_id that the authorised user is part of, provide basic details about the channel""" # Validate token and channel if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) if not validator.is_valid_channel(channel_id): raise ValueError(INVALID_CHANNEL) # Find user in database u_id = jwt_handler.decode_token(token) # Locate in database channel = database.get_channel_by_id(channel_id) user = database.get_user_by_id(u_id) # Check user is authorised if u_id not in channel["auth_ids"] and user["permission"] == MEMBER: raise AccessError(CHANNEL_CANT_VIEW_DETAIL) # Compile channel details owner_list = channel["owner_ids"] auth_list = channel["auth_ids"] owner_list_with_details = [] auth_list_with_details = [] for owner_id in owner_list: owner = database.get_user_by_id(owner_id) owner_list_with_details.append({ "u_id": owner_id, "name_first": owner["name_first"], "name_last": owner["name_last"], "profile_img_url": owner["profile_img_url"]}) for auth_id in auth_list: auth = database.get_user_by_id(auth_id) auth_list_with_details.append({ "u_id": auth_id, "name_first": auth["name_first"], "name_last": auth["name_last"], "profile_img_url": auth["profile_img_url"]}) return { "name": channel["name"], "owner_members": owner_list_with_details, "all_members": auth_list_with_details }
def channel_addowner(token, channel_id, u_id): """Make user with user id u_id an owner of this channel""" # Convert channel_id and u_id to integer channel_id = int(channel_id) u_id = int(u_id) # Validate token, channel and user if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) if not validator.is_valid_channel(channel_id): raise ValueError(INVALID_CHANNEL) if not validator.is_valid_user(u_id): raise ValueError(INVALID_USER) # Locate channel and user in database channel = database.get_channel_by_id(channel_id) auth_user_id = jwt_handler.decode_token(token) auth_user = database.get_user_by_id(auth_user_id) # Check auth_user permissions if auth_user["permission"] not in [OWNER, ADMIN] or\ auth_user_id not in channel["owner_ids"]: raise AccessError(CHANNEL_ADD_OWNER_NO_AUTH) # Add user to auth_ids auth_list = channel["auth_ids"] if u_id not in auth_list: auth_list.append(u_id) database.update_channel_by_id(channel_id, { "auth_ids": auth_list }) # Add user to owner_ids owner_list = channel["owner_ids"] if u_id in owner_list: raise ValueError(CHANNEL_ALREADY_OWNER) owner_list.append(u_id) database.update_channel_by_id(channel_id, { "owner_ids": owner_list }) return {}
def standup_active(token, channel_id): """Adds message to standup buffer if standup is currently active""" # Check valid token if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) # Check channel existence channel = database.get_channel_by_id(channel_id) if channel is None: raise ValueError(INVALID_CHANNEL) is_active = is_standup_running(channel_id) time_finish = None if is_active: time_finish = int( json_time_translator.json_to_timestamp( database.get_standup_finish_time(channel_id))) return {"is_active": is_active, "time_finish": time_finish}
def standup_send(token, channel_id, message): """Adds message to standup buffer if standup is currently active""" # Check valid token if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) # Check channel existence channel = database.get_channel_by_id(channel_id) if channel is None: raise ValueError(INVALID_CHANNEL) # Check user is member of channel u_id = jwt_handler.decode_token(token) if u_id not in channel["auth_ids"]: raise AccessError(CHANNEL_CANT_VIEW_DETAIL) # Check the startup is not currently active in this channel old_time_end = database.get_standup_finish_time(channel_id) if old_time_end is None or\ json_time_translator.json_to_datetime(old_time_end) < datetime.utcnow(): raise ValueError(STANDUP_NOT_RUNNING) # Check valid message if not validator.is_valid_message(message): raise ValueError(INVALID_MESSAGE) # Save message database.add_standup_message( channel_id, { "u_id": u_id, "message": message, "time_created": json_time_translator.datetime_to_json(datetime.utcnow()), }) return {}
def is_valid_channel(channel_id): """Returns whether or not a channel is valid""" if database.get_channel_by_id(channel_id) is None: return False return True
def channel_messages(token, channel_id, start): """Given a Channel with ID channel_id that the authorised user is part of, return up to 50 messages between index "start" and "start + 50". Message with index 0 is the most recent message in the channel. This function returns a new index "end" which is the value of "start + 50", or, if this function has returned the least recent messages in the channel, returns -1 in "end" to indicate there are no more messages to load after this return.""" # Convert 'start' to an integer start = int(start) # Validate token and channel ID if not validator.is_valid_token(token): raise AccessError(INVALID_TOKEN) if not validator.is_valid_channel(channel_id): raise ValueError(INVALID_CHANNEL) # Locate channel channel = database.get_channel_by_id(channel_id) # Validate that token user is in channel u_id = jwt_handler.decode_token(token) if u_id not in channel["auth_ids"]: raise AccessError(CHANNEL_CANT_VIEW_MSG) # Validate messages exist total_messages = len(channel['messages']) if total_messages <= 0: return { "messages": [], "start": start, "end": -1 } # Validate start point if start > (total_messages - 1) or start < 0: raise ValueError(CHANNEL_NO_MORE_MSG) # Find index for the most recent message (reference list backwards) start_index = len(channel['messages']) - 1 - start # Get all recent messages up to 50 messages = [] end = start + 49 for msg_num in range(50): index = start_index - msg_num # If there are less than 50 msgs if index < 0: end = -1 break # Get message object message_id = channel['messages'][index] message = database.get_message_by_id(message_id) # If message is sent later or not there if message is None or\ json_time_translator.json_to_datetime(message["time_created"]) >= datetime.utcnow(): continue # Create output-able list of reacts react_list = [] reacted_ids = [] all_reacts = database.get_all_reacts(message_id) react_id = 1 for react in all_reacts: is_this_user_reacted = False if react["react_id"] == react_id: reacted_ids.extend(react["u_id"]) if u_id in react["u_id"]: is_this_user_reacted = True # Only add the reaction if it has at least one count of a user react_list.append({ "react_id": react_id, "u_ids": reacted_ids, "is_this_user_reacted": is_this_user_reacted }) # print(message["time_created"]) # print(json_time_translator.json_to_datetime(message["time_created"])) # print(json_time_translator.json_to_timestamp(message["time_created"])) # Append to file messages.append({ "message_id": message_id, "u_id": message["u_id"], "message": message["message"], "time_created": json_time_translator.json_to_timestamp(message["time_created"]), "reacts": react_list, "is_pinned": message["is_pinned"] }) return { "messages": messages, "start": start, "end": end }