Maintainer(s): @seanyboy49, @jinazhu
Movie Night Fight is a mobile-first app for groups of movie lovers who can never figure out which movie to watch when it's their turn to choose. It keeps track of whose turn it is to choose the evening's entertainment, and allows each person to keep a curated list of movies to choose from when it's their turn.
There are two main components of this app. A Flask app which serves as an API, and a React App which serves as the client.
The Flask app uses pipenv to manage its virtual environment and python packages. So make sure that any Flask command you run (e.g, flask run, flask shell
) are run in the context of a pipenv shell.
The React app is created from an ejected create-react-app, and is served statically from the Flask app.
We use flask praetorian to manage authentication and authorization. It uses JWT's to make sure that users accessing the APIs protected endpoints are provisioned with the correct roles for access.
We can protect API routes using a simple decorator function from this library. For example:
@api.route('/api/protected')
@flask_praetorian.auth_required
def protected():
"""
A protected endpoint. The auth_required decorator will require a header
containing a valid JWT
.. example::
$ curl http://loclahost:5000/api/protected -X GET \
-H "Authorization: Bearer <your_token>"
"""
return {"message": f'protected endpoint (allowed user {flask_praetorian.current_user().username})'}
This is used in conjunction with react token auth to manage the auth token on the frontend.
The auth setup for this app is heavily inspired by this article.
Create your local database
createdb movie_night_fight
Run migrations We use flask migrate to handle database migrations. It generates migration files based on detected changes to your models that you can then run to upgrade or downgrade your database. When you run the app locally for the first time, make sure to run migrations in order to create the necessary tables in your local database
// activate your pipenv environment
pipenv shell
// run migrations
flask db migrate
Populate the database This app exposes custom CLI commands through click. You can populate the database with a seeds file using one of these commands.
pipenv run flask run_seeds
Run the server
Make sure you have a .env
file with the required values, as the flask app will use it to load environment variables.
// activate your pipenv environment
pipenv shell
// start the flask server
flask run
Run the frontend The client is in a subfolder so make sure to cd into the right directory.
cd client
npm run start
Flask will serve the React app by looking for static assets in the /client/build
folder.
Previously we had to remember to run npm run build
before pushing changes to Github. This commit adds a package.json
to the root of the project with a heroku-postbuild
script that will build the react app and generate the build
folder in the client in Heroku's file system.
Fetch a user's watchlist
There is no need to send a user_id
, since the server keeps track of the currently logged in user making the request.
Returns a list of movies filtered where movie.watched_at
is not Null
[
{
"id": 1,
"name": "Star Wars: Episode V - The Empire Strikes Back",
"omdb_id": "tt0080684",
"poster_url": "https://m.media-amazon.com/images/M/MV5BYmU1NDRjNDgtMzhiMi00NjZmLTg5NGItZDNiZjU5NTU4OTE0XkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_SX300.jpg"
},
{
"id": 3,
"name": "Aladdin",
"omdb_id": "tt6139732",
"poster_url": "https://m.media-amazon.com/images/M/MV5BMjQ2ODIyMjY4MF5BMl5BanBnXkFtZTgwNzY4ODI2NzM@._V1_SX300.jpg"
},
{
"id": 4,
"name": "Love Actually",
"omdb_id": "tt0314331",
"poster_url": "https://m.media-amazon.com/images/M/MV5BMTY4NjQ5NDc0Nl5BMl5BanBnXkFtZTYwNjk5NDM3._V1_SX300.jpg"
}
]
Add to a user's watchlist. Expects content-type: application/json
.
Example Request Body
{
"poster_url": "https://m.media-amazon.com/images/M/MV5BOGUyZDUxZjEtMmIzMC00MzlmLTg4MGItZWJmMzBhZjE0Mjc1XkEyXkFqcGdeQXVyMTMxODk2OTU@._V1_SX300.jpg",
"name": "The Social Network",
"omdb_id": "tt1285016"
}
{
"message": "Movie added to watchlist"
}
If the user's watchlist already contains the movie, the endpoint will return 200.
{
"message": "Movie already added to watchlist"
}
Remove a movie from a user's watchlist. Deleting a resource is idempotent, which means that subsequent requests to this endpoint with the same arguments should not change behavior. Deleting the resource the first time will yield 204 Response, as will subsequent requests.
Failure to supply a movie_id
in the url will result in 405 Method Not Allowed Response.
Marks a movie in a watchlist as watched by updating the watched_at
field with the current time.
Query Parameters
-
houseId
(required) Specifies which house the user is choosing a movie for. This is to generate a history of turns for each house. -
movieId
(required) Specify which movie to mark as watched in the user's watchlist.
This endpoint first verifies if the user making the request is indeed allowed to to make the request, since the app should keep track of whose turn it is.
Returns 204 No Content
Search for movies by name
This endpoint makes a request to OMDB's free movie database api with the query string sent by the client. Clients should be prepared to handle movies that do not have a Poster url.
Query Parameters
- search (required) A string sent by the client that specifies a movie title to search for.
Example request
curl http://0.0.0.0:8000/api/movies?search=batman
The server will return the OMDB api response with no modification. Therefore, clients should check if the server response Response
property is "True"
.
Successful 200 response
{
"Response": "True",
"Search": [
{
"Poster": "https://m.media-amazon.com/images/M/MV5BOGZmYzVkMmItM2NiOS00MDI3LWI4ZWQtMTg0YWZkODRkMmViXkEyXkFqcGdeQXVyODY0NzcxNw@@._V1_SX300.jpg",
"Title": "Batman Returns",
"Type": "movie",
"Year": "1992",
"imdbID": "tt0103776"
},
{
"Poster": "https://m.media-amazon.com/images/M/MV5BNDdjYmFiYWEtYzBhZS00YTZkLWFlODgtY2I5MDE0NzZmMDljXkEyXkFqcGdeQXVyMTMxODk2OTU@._V1_SX300.jpg",
"Title": "Batman Forever",
"Type": "movie",
"Year": "1995",
"imdbID": "tt0112462"
},
{
"Poster": "N/A",
"Title": "Batman & Robin",
"Type": "movie",
"Year": "1997",
"imdbID": "tt0118688"
}
],
"totalResults": "399"
}
Movie not found response
{
"Error": "Movie not found!",
"Response": "False"
}
Get all houses that a user has joined
Returns a list of houses and its users.
[
{
"id": 1,
"name": "Winterfell",
"users": [
{
"role": "admin",
"user": "sean"
},
{
"role": "house_mate",
"user": "jina"
}
]
},
{
"id": 2,
"name": "House of Mirrors",
"users": [
{
"role": "admin",
"user": "sean"
}
]
}
]
Search for a house by name.
Query Parameters
- search (required) A string sent by the client that specifies a house name to search for.
Example request
curl http://0.0.0.0:8000/api/houses?search=mirrors
Successful 200 response
[
{
"id": 2,
"name": "House of Mirrors",
"users": [
{
"role": "admin",
"user": "sean"
}
]
}
]
Create a house.
A user who creates a house will automatically join the house as admin
.
Example request body
{
"name": "Rich Mohagany"
}
Returns the newly created house.
{
"id": 11,
"name": "The Batcave",
"users": [
{
"role": "admin",
"user": "sean"
}
]
}
Join a house.
A user who joins a house will automatically join the house as a house_mate
role.
The route will add the current_user to the house's users
list.
Example request
curl -X POST http://0.0.0.0:8000/api/houses/7/memberships -X
Returns the house with the new user. 201 Response
{
"id": 11,
"name": "The Batcave",
"users": [
{
"role": "admin",
"user": "sean"
},
{
"role": "house_mate",
"user": "seb"
}
]
}
Leave a house.
Example request
curl -X DELETE http://0.0.0.0:8000/api/houses/7/memberships
There are three possible outcomes to this. All are 200 Response
- The user is the last person left in the house. Therefore, house gets deleted when the last user leaves.
{
"message": "Successfully left and deleted House of Mirrors"
}
- The user has an
admin
role. The user leaves, and we automatically assign admin role to another person in the house.
{
"message": "Successfully left House of Mirrors. Jina is now admin."
}
- The user is just a
house_mate
. The user leaves the house.
{
"message": "Successfully left House of Mirrors."
}
Get current and next turns for a house, as well as a history of turns.
A house with only one user will simply return null
for the next_turn
.
{
"current_turn": {
"id": 3,
"username": "seb"
},
"next_turn": {
"id": 1,
"username": "sean"
}
"history": [
{
"created_at": "Sun, 03 Jan 2021 23:21:32 GMT",
"movie": "Aladdin",
"user": "sean"
},
{
"created_at": "Sun, 03 Jan 2021 23:45:53 GMT",
"movie": "Aladdin",
"user": "jina"
}
],
}