Skip to content

Yousif-CS/Slackr-Backend

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Changelog

  • 03/03: Added description for users_all
  • 05/03: Reiterated not to modify/move stub files as per week 2 lecture; submission open comment
  • 09/03:
    • "set handle" minimum length set as 2
    • Clarification that all ids uniquely identify an entity
    • All "between" references clarified to be inclusive
    • Clarity on how search results are sorted
    • password reset, message sendlater, message react/pin, profile pic uploads, standups, and permision changes added
  • 14/03: PLEASE READ
    • Added "reacts, is_pinned" to messages data type
    • Added "reacts" data type
    • Permission ID's clarified for global permissions

Overview

A Python backend server for a messaging platform that allows:

  1. Ability to login, register if not registered, and log out
  2. Ability to reset password if forgotten it
  3. Ability to see a list of channels
  4. Ability to create a channel, join a channel, invite someone else to a channel, and leave a channel
  5. Within a channel, ability to view all messages, view the members of the channel, and the details of the channel
  6. Within a channel, ability to send a message now, or to send a message at a specified time in the future
  7. Within a channel, ability to edit, remove, pin, unpin, react, or unreact to a message
  8. Ability to view user anyone's user profile, and modify a user's own profile (name, email, handle, and profile photo)
  9. Ability to search for messages based on a search string
  10. Ability to modify a user's privileges: (MEMBER, OWNER)
  11. Ability to begin a "standup", which is an X minute period where users can send messages that at the end of the period will automatically be collated and summarised to all users

Sprint 1: Test Driven Development

Completed

Sprint 2: Design and Implementation

Completed

Sprint 3: Maintenance and Extensions

Completed

  1. Fix any outstanding bugs and complete any components that were not full implemented for the iteration 2 deadline. In doing this, we should ensure that your code is of sufficiently high quality for it to be maintained. Consider principles like DRY, KISS, top-down thinking and encapsulation. Furthermore, ensure all functions are documented with Pydoc using a consistent style.

  2. Implement the /passwordreset/request and /passwordreset/reset routes. By doing this, the "Forgot your password" feature of the frontend should now work.

  3. Modify backend such that it is able to persist and reload its data store. The persistance should happen at regular intervals so that in the event of unexpected program termination (e.g. sudden power outage) a minimal amount of data is lost. We may implement this using whatever method of serialisation you prefer (e.g. pickle, JSON ) or Simply mySQL.

  4. Implement the /user/profile/uploadphoto route as described in table below. If done correctly, we should be able to set a profile image when using the frontend. Any additional packages you use need to be added to requirements.txt.

  5. Add support for slackr owners (Admins) to remove users from slackr. This requires modifying both the backend and frontend. Modify the backend by implementing /admin/user/remove in the table below. For the frontend, add an additional entry to the admin menu that provides an interface for removing users.

  6. Allow users to relax and play a game of Hangman in slackr. If the command /hangman is typed into a channel, it should start a game where the users of the channel cooperatively try to guess a word or phrase letter by letter.

Hangman

After a game of Hangman has been started any user in the channel can type /guess X where X is an individual letter. If that letter is contained in the word or phrase they're trying to guess, the app should indicate where it occurs. If it does not occur, more of the hangman is drawn. It can be done only by modifying the backend and relying on messages to communicate the state of the game (e.g. after making a guess, the "Hangman" posts a message with a drawing of the hangman in ASCII/emoji art). Alternatively we can modify the frontend.

The app should use words and phrases from an external source, not just a small handful hardcoded into the app. One suitable source is /usr/share/dict/words available on Unix-based systems. Alternatively, the python wikiquote module is available via pip and can be used to retrieve quotes and phrases from Wikiquote.

Interface specifications from the frontend

Data types

Variable name Type
named exactly email string
named exactly id integer
named exactly length integer
named exactly password string
named exactly token string
named exactly message string
contains substring name string
contains substring code string
has prefix is_ boolean
has prefix time_ integer (unix timestamp), check this out
has suffix _id integer
has suffix _url string
has suffix _str string
has suffix end integer
has suffix start integer
(outputs only) named exactly user Dictionary containing u_id, email, name_first, name_last, handle_str, profile_img_url
(outputs only) named exactly users List of dictionaries, where each dictionary contains types u_id, email, name_first, name_last, handle_str, profile_img_url
(outputs only) named exactly messages List of dictionaries, where each dictionary contains types { message_id, u_id, message, time_created, reacts, is_pinned }
(outputs only) named exactly channels List of dictionaries, where each dictionary contains types { channel_id, name }
(outputs only) name ends in members List of dictionaries, where each dictionary contains types { u_id, name_first, name_last, profile_img_url }
(outputs only) name ends in reacts List of dictionaries, where each dictionary contains types { react_id, u_ids, is_this_user_reacted } where react_id is the id of a react, and u_ids is a list of user id's of people who've reacted for that react. is_this_user_reacted is whether or not the authorised user has been one of the reacts to this post

profile_img_url & image uploads

For outputs with data pertaining to a user, a profile_img_url is present. When images are uploaded for a user profile, after processing them we should store them on the server such that the server now locally has a copy of the cropped image of the original file linked. Then, the profile_img_url should be a URL to the server, such as http://localhost:5001/imgurl/adfnajnerkn23k4234.jpg (a unique url)

Token

Many of these functions (nearly all of them) need to be called from the perspective of a user who is logged in already. When calling these "authorised" functions, we need to know:

  1. Which user is calling it
  2. That the person who claims they are that user, is actually that user

To solve this when a user logs in or registers the backend should return a "token" (an authorisation hash) that the front end will store and pass into most of your functions in future. When these "authorised" functions are called, you can check if a token is valid, and determine the user ID.

Error raising for the frontend

For errors to be appropriately raised on the frontend, they must be raised by the following:

if True: # condition here
    raise InputError(description='Description of problem')

The types in error.py have been modified appropriately for you.

Reacts

The only React ID currently associated with the frontend is React ID 1, which is a thumbs up.

Permissions:

  • Members in a channel have one of two channel permissions.
      1. Owner of the channel (the person who created it, and whoever else that creator adds)
      1. Members of the channel
  • Slackr user's have two global permissions
      1. Owners, who can also modify other owners' permissions. (permission_id 1)
      1. Members, who do not have any special permissions. (permission_id 2)
  • All slackr users are by default members, except for the very first user who signs up, who is an owner

A user's primary permissions are their global permissions. Then the channel permissions are layered on top. For example:

  • An owner of slackr has owner privileges in every channel they've joined
  • A member of slackr is a member in channels they are not owners of
  • A member of slackr is an owner in channels they are owners of

Standups

Once standups are finished, all of the messages sent to standup/send are packaged together in one single message posted by the user who started the standup and sent as a message to the channel the standup was started in, timestamped at the moment the standup finished.

The structure of the packaged message is like this:

Standups can be started on the frontend by typing "/standup X", where X is the number of seconds that the standup lasts for, into the message input and clicking send.

Errors for all functions

AccessError

  • For all functions except auth/register, auth/login
  • Error thrown when token passed in is not a valid token

Pagination

The behaviour in which channel_messages returns data is called pagination. It's a commonly used method when it comes to getting theoretially unbounded amounts of data from a server to display on a page in chunks. The chunks sent should be in the size of 50.

Other Comments

  • All IDs (e.g. user id, channel id) uniquely identify a data item for its entire existence. Even if that item is removed, the ID cannot be reused for any new or other existing items.

Interface

HTTP Route HTTP Method Parameters Return type Exceptions Description
auth/login POST (email, password) { u_id, token } InputError when any of:
  • Email entered is not a valid email using the method provided here (unless you feel you have a better method)
  • Email entered does not belong to a user
  • Password is not correct
Given a registered users' email and password and generates a valid token for the user to remain authenticated
auth/logout POST (token) { is_success } N/A Given an active token, invalidates the taken to log the user out. If a valid token is given, and the user is successfully logged out, it returns true, otherwise false.
auth/register POST (email, password, name_first, name_last) { u_id, token } InputError when any of:
  • Email entered is not a valid email using the method provided here (unless you feel you have a better method).
  • Email address is already being used by another user
  • Password entered is less than 6 characters long
  • name_first not is between 1 and 50 characters inclusive in length
  • name_last is not between 1 and 50 characters inclusive in length
Given a user's first and last name, email address, and password, create a new account for them and return a new token for authentication in their session. A handle is generated that is the concatentation of a lowercase-only first name and last name. If the concatenation is longer than 20 characters, it is cutoff at 20 characters. If the handle is already taken, you may modify the handle in any way you see fit to make it unique.
auth/passwordreset/request POST (email) {} N/A Given an email address, if the user is a registered user, send's them a an email containing a specific secret code, that when entered in auth_passwordreset_reset, shows that the user trying to reset the password is the one who got sent this email.
auth/passwordreset/reset POST (reset_code, new_password) {} InputError when any of:
  • reset_code is not a valid reset code
  • Password entered is not a valid password
Given a reset code for a user, set that user's new password to the password provided
channel/invite POST (token, channel_id, u_id) {} InputError when any of:
  • channel_id does not refer to a valid channel that the authorised user is part of.
  • u_id does not refer to a valid user
AccessError when
  • the authorised user is not already a member of the channel
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
channel/details GET (token, channel_id) { name, owner_members, all_members } InputError when any of:
  • Channel ID is not a valid channel
AccessError when
  • Authorised user is not a member of channel with channel_id
Given a Channel with ID channel_id that the authorised user is part of, provide basic details about the channel
channel/messages GET (token, channel_id, start) { messages, start, end } InputError when any of:
  • Channel ID is not a valid channel
  • start is greater than the total number of messages in the channel
AccessError when
  • Authorised user is not a member of channel with channel_id
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" exclusive. 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.
channel/leave POST (token, channel_id) {} InputError when any of:
  • Channel ID is not a valid channel
AccessError when
  • Authorised user is not a member of channel with channel_id
Given a channel ID, the user removed as a member of this channel
channel/join POST (token, channel_id) {} InputError when any of:
  • Channel ID is not a valid channel
AccessError when
  • channel_id refers to a channel that is private (when the authorised user is not an owner)
Given a channel_id of a channel that the authorised user can join, adds them to that channel
channel/addowner POST (token, channel_id, u_id) {} InputError when any of:
  • Channel ID is not a valid channel
  • When user with user id u_id is already an owner of the channel
AccessError when the authorised user is not an owner of the slackr, or an owner of this channel
Make user with user id u_id an owner of this channel
channel/removeowner POST (token, channel_id, u_id) {} InputError when any of:
  • Channel ID is not a valid channel
  • When user with user id u_id is not an owner of the channel
AccessError when the authorised user is not an owner of the slackr, or an owner of this channel
Remove user with user id u_id an owner of this channel
channels/list GET (token) { channels } N/A Provide a list of all channels (and their associated details) that the authorised user is part of
channels/listall GET (token) { channels } N/A Provide a list of all channels (and their associated details)
channels/create POST (token, name, is_public) { channel_id } InputError when any of:
  • Name is more than 20 characters long
Creates a new channel with that name that is either a public or private channel
message/send POST (token, channel_id, message) { message_id } InputError when any of:
  • Message is more than 1000 characters
AccessError when:
  • the authorised user has not joined the channel they are trying to post to
  • Send a message from authorised_user to the channel specified by channel_id
    message/sendlater POST (token, channel_id, message, time_sent) { message_id } InputError when any of:
    • Channel ID is not a valid channel
    • Message is more than 1000 characters
    • Time sent is a time in the past
    AccessError when:
  • the authorised user has not joined the channel they are trying to post to
  • Send a message from authorised_user to the channel specified by channel_id automatically at a specified time in the future
    message/react POST (token, message_id, react_id) {} InputError when any of:
    • message_id is not a valid message within a channel that the authorised user has joined
    • react_id is not a valid React ID. The only valid react ID the frontend has is 1
    • Message with ID message_id already contains an active React with ID react_id from the authorised user
    Given a message within a channel the authorised user is part of, add a "react" to that particular message
    message/unreact POST (token, message_id, react_id) {} InputError
    • message_id is not a valid message within a channel that the authorised user has joined
    • react_id is not a valid React ID
    • Message with ID message_id does not contain an active React with ID react_id
    Given a message within a channel the authorised user is part of, remove a "react" to that particular message
    message/pin POST (token, message_id) {} InputError when any of:
    • message_id is not a valid message
    • Message with ID message_id is already pinned
    AccessError when any of:
    • The authorised user is not a member of the channel that the message is within
    • The authorised user is not an owner
    Given a message within a channel, mark it as "pinned" to be given special display treatment by the frontend
    message/unpin POST (token, message_id) {} InputError when any of:
    • message_id is not a valid message
    • Message with ID message_id is already unpinned
    AccessError when any of:
    • The authorised user is not a member of the channel that the message is within
    • The authorised user is not an owner
    Given a message within a channel, remove it's mark as unpinned
    message/remove DELETE (token, message_id) {} InputError when any of:
    • Message (based on ID) no longer exists
    AccessError when none of the following are true:
    • Message with message_id was sent by the authorised user making this request
    • The authorised user is an owner of this channel or the slackr
    Given a message_id for a message, this message is removed from the channel
    message/edit PUT (token, message_id, message) {} AccessError when none of the following are true:
    • Message with message_id was sent by the authorised user making this request
    • The authorised user is an owner of this channel or the slackr
    Given a message, update it's text with new text. If the new message is an empty string, the message is deleted.
    user/profile GET (token, u_id) { user } InputError when any of:
    • User with u_id is not a valid user
    For a valid user, returns information about their user id, email, first name, last name, and handle
    user/profile/setname PUT (token, name_first, name_last) {} InputError when any of:
    • name_first is not between 1 and 50 characters inclusive in length
    • name_last is not between 1 and 50 characters inclusive in length
    Update the authorised user's first and last name
    /user/profile/setemail PUT (token, email) {} InputError when any of:
    • Email entered is not a valid email using the method provided here (unless you feel you have a better method).
    • Email address is already being used by another user
    Update the authorised user's email address
    /user/profile/sethandle PUT (token, handle_str) {} InputError when any of:
    • handle_str must be between 2 and 20 characters inclusive
    • handle is already used by another user
    Update the authorised user's handle (i.e. display name)
    /user/profile/uploadphoto POST (token, img_url, x_start, y_start, x_end, y_end) {} InputError when any of:
    • img_url returns an HTTP status other than 200.
    • any of x_start, y_start, x_end, y_end are not within the dimensions of the image at the URL.
    • Image uploaded is not a JPG
    Given a URL of an image on the internet, crops the image within bounds (x_start, y_start) and (x_end, y_end). Position (0,0) is the top left.
    users/all GET (token) { users} N/A Returns a list of all users and their associated details
    /search GET (token, query_str) { messages } N/A Given a query string, return a collection of messages in all of the channels that the user has joined that match the query. Results are sorted from most recent message to least recent message
    standup/start POST (token, channel_id, length) { time_finish } InputError when any of:
    • Channel ID is not a valid channel
    • An active standup is currently running in this channel
    For a given channel, start the standup period whereby for the next "length" seconds if someone calls "standup_send" with a message, it is buffered during the X second window then at the end of the X second window a message will be added to the message queue in the channel from the user who started the standup. X is an integer that denotes the number of seconds that the standup occurs for
    standup/active GET (token, channel_id) { is_active, time_finish } InputError when any of:
    • Channel ID is not a valid channel
    For a given channel, return whether a standup is active in it, and what time the standup finishes. If no standup is active, then time_finish returns None
    standup/send POST (token, channel_id, message) {} InputError when any of:
    • Channel ID is not a valid channel
    • Message is more than 1000 characters
    • An active standup is not currently running in this channel
    AccessError when
    • The authorised user is not a member of the channel that the message is within
    Sending a message to get buffered in the standup queue, assuming a standup is currently active
    admin/userpermission/change POST (token, u_id, permission_id) {} InputError when any of:
    • u_id does not refer to a valid user
    • permission_id does not refer to a value permission
    AccessError when
    • The authorised user is not an owner
    Given a User by their user ID, set their permissions to new permissions described by permission_id
    admin/user/remove DELETE (token, u_id) {} InputError when:
    • u_id does not refer to a valid user
    AccessError when
    • The authorised user is not an owner of the slackr
    Given a User by their user ID, remove the user from the slackr.
    workspace/reset POST () {} Resets the workspace state

    About

    No description, website, or topics provided.

    Resources

    Stars

    Watchers

    Forks

    Packages

    No packages published

    Languages