The purpose of this API is helping assistants, directors & producers to manage the talent and the projects they are currently working on. It provides endpoints for handling information about actors that they are considering to cast, and data about future projects.
The main motivation of implementing this application is demonstrate the skills learned from Udacity Full Stack Nanodegree program, and it integrates some of the technologies used there: representing models using SQLAlchemy, implementing and API using Flask, provide authentication through Auth0.
Before installation, install or update your Python runtime to version 3.8,
Then clone this repository and set up a virtual environment by using venv
module. If you haven't done this before, you can follow a guide
right in this link.
After setting up the virtual environment, you can install the project dependencies using pip:
pip install -r requirements.txt
Before running the app, some environment variables should be set up. These variables are related with SQLAlchemy & Auth0 settings. Be sure to include them in the environment where you run this app:
DATABASE_URL
: contains the configuration for connecting to the database. Follow the SQLAlchemy guide for composing this URL.AUTH0_DOMAIN
: contains the URL of Auth0 domain which is used to validate authorization tokens.ALGORITHMS
: contains an array with algorithms used for encrypting the JWT.API_AUDIENCE
: contains the identifier of the Auth0 API which is associated with this API.
These variables are listed in the setup.sh
script, so you can load them
right from there:
source setup.sh
After setting up the corresponding variables, you can run the API locally using
Flask. Set up Flask for running the app
module in a development environment
and then run it:
export FLASK_APP=app.py
export FLASK_ENV=development
flask run
The app will be running in the URL http://localhost:5000.
An alternative way is to run the app.py
script directly:
python3 app.py
In this other way, the app is set up to run in port 8080 with debug mode on. You can change this by modifying the app.run sentence, located in the main block at the end of the script:
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=True)
A suite of pytest unit tests is provided
in test_app.py
module. Before running the tests, load the environment
variables from the setup.sh
script.
There are two pytest fixtures that include testing configuration. The main one
is the client
fixture:
@pytest.fixture(scope='module')
def client():
database_dialect = "postgresql"
database_user = "tester_user"
database_password = "password_testing_user"
database_location = "localhost:5432"
database_name = "testing_database"
test_config = {
'DATABASE_URL': f"{database_dialect}://{database_user}:{database_password}@{database_location}/{database_name}"
}
app = create_app(test_config)
with app.test_client() as testing_client:
with app.app_context():
db.create_all()
load_data()
yield testing_client
In this fixture you can configure a URL to connect to a local testing database,
through the database variables before test_config
.
Consult the SQLAlchemy guide
for filling the corresponding values.
This fixture also includes a load_data
function, imported from the
data_loader
module. It inserts some rows in the database before the tests.
Drop and create the testing database before running the tests. If you're using
PostgreSQL, this can be done in this way:
dropdb testing_database
createdb testing_database
The second fixture that includes testing configuration is token_header
:
@pytest.fixture(scope='module')
def token_header():
def get_authorization_header(token):
return {
'Authorization': f'Bearer {token}'
}
return {
"casting_assistant": get_authorization_header("casting_assistant_JWT"),
"casting_director": get_authorization_header("casting_director_JWT"),
"executive_producer": get_authorization_header("executive_producer_JWT")
}
It provides a dictionary with authorization headers for the three different
roles associated with this application (these roles are described in the next
section). If you want to include your own tokens, pass them to the
get_authorization_header
functions.
Note: This tests take the same configuration included in the Auth0
environment variables, it is necessary to load them from setup.sh
.
For running the test, just run pytest:
pytest
Or run the test_app
module:
python test_app.py
The access to this API's endpoint is segmented in 3 different roles: casting assistant, casting director and executive producer.
- The casting assistant helps a casting director in the search of actors & actresses for different projects.
- The casting director casts actors and checks to which movie assign them.
- The executive producer is the responsible of production of different movies.
The permissions assigned to these roles are the following:
- Casting assistant:
get:movies
get:actors
- Casting director:
- All the permissions of a casting assistant
post:actors
update:actors
delete:actors
update:movies
- Executive producer:
- All the permissions of a casting director
post:movies
delete:movies
The permissions map directly the endpoints of this API.
It returns a paginated list of movies & the total number of movies saved.
- Request arguments:
page
: indicate the page number requested (e. g./movies?page=3
)
- Response body fields:
success
: boolean value that indicates if the request has been successful.movies
: contains a list of movies, at most 10 for every page.total_movies
: total number of movies included in the database.
- Permission required:
get:movies
Response body example:
{
"success": true,
"movies": [
{
"id": 51,
"title": "Avengers",
"release_date": "2012-03-20"
},
{
"id": 52,
"title": "E. T.",
"release_date": "1982-07-20"
}
],
"total_movies": 412
}
It returns the movie with the specified ID.
- Response body fields:
success
: boolean value that indicates the request has been successful.movie
: JSON object with the following fields:id
: ID of the movie.title
: Title of the movie.release_date
: Release date of the movie.
- Permission required:
get:movies
Response body example:
{
"success": true,
"movie": {
"id": 51,
"title": "Avengers",
"release_date": "2012-03-20"
}
}
It adds a new movie to the database.
- Request body fields:
title
: Title of the movie.release_date
: Release date of the movie in ISO 8601 format.
- Response body fields:
success
: boolean value that indicates if the request has been successful.movie
: JSON object corresponding to the posted movie.
- Permission required:
post:movies
Request body example:
{
"title": "Godzilla vs. King Kong",
"release_date": "2021-03-28"
}
Response body example:
{
"success": true,
"movie": {
"id": 436,
"title": "Godzilla vs. King Kong",
"release_date": "2021-03-28"
}
}
It updates the indicated fields of an existent movie.
The request body can have one of the movie fields, or both. This endpoint will just update the fields passed in the request body.
- Request body fields:
title
: Title of the movie.release_date
: Release date of the movie in ISO 8601 format.
- Response body fields:
success
: boolean value that indicates if the request has been successful.movie
: JSON object corresponding to the updated movie.
- Permission required:
update:movies
Request body example:
{
"title": "Suspirium"
}
Response body example:
{
"success": true,
"movie": {
"id": 436,
"title": "Suspirium",
"release_date": "2021-03-28"
}
}
It deletes the movie with the specified ID.
- Response body fields:
success
: boolean value that indicates the request has been successful.
- Permission required:
delete:movies
Response body example:
{
"success": true
}
It returns a paginated list of actors & the total number of actors saved.
- Request arguments:
page
: indicate the page number requested (e. g./actors?page=3
)
- Response body fields:
success
: boolean value that indicates if the request has been succesful.actors
: contains a list of actors, at most 10 for every page.total_actors
: total number of actors included in the database.
- Permission required:
get:actors
Response body example:
{
"success": true,
"actors": [
{
"id": 34,
"name": "Julia Robertson",
"age": 47,
"gender": "female"
},
{
"id": 35,
"name": "Chris Reeves",
"age": 56,
"gender": "male"
}
],
"total_actors": 412
}
It returns the actor with the specified ID.
- Response body fields:
success
: boolean value that indicates the request has been successful.actor
: JSON object with the following fields:id
: ID of the actor.name
: Name of the actor.age
: Age of the actor.gender
: Gender of the actor.
- Permission required:
get:actors
Response body example:
{
"success": true,
"actor": {
"id": 320,
"name": "Robb Auerbach",
"age": 32,
"gender": "male"
}
}
It adds a new actor to the database.
- Request body fields:
name
: Name of the actor.age
: Age of the actor.gender
: A string indicating the gender of the actor. Just two strings are allowed:male
orfemale
.
- Response body fields:
success
: boolean value that indicates if the request has been successful.actor
: JSON object with the posted actor.
- Permission required:
post:actors
Request body example:
{
"name": "Armin Arlett",
"age": 18,
"gender": "male"
}
Response body example:
{
"success": true,
"actor": {
"id": 302,
"name": "Armin Arlett",
"age": 18,
"gender": "male"
}
}
It updates the indicated fields of an existent actor.
The request body can have one of the actor fields, or all of them. This endpoint will just update the fields passed in the request body.
- Request body fields:
name
: Name of the actor.age
: Age of the actor.gender
: Gender of the actor. As in the POST /actors endpoint, it just accepts two values:male
orfemale
.
- Response body fields:
success
: boolean value that indicates if the request has been successful.actor
: JSON object with the updated actor.
- Permission required:
update:actors
Request body example:
{
"name": "John Wock",
"age": 58
}
Response body example:
{
"success": true,
"actor": {
"id": 436,
"name": "John Wock",
"age": 58,
"gender": "male"
}
}
It deletes the actor with the specified ID.
- Response body fields:
success
: boolean value that indicates the request has been successful.
- Permission required:
delete:actors
Response body example:
{
"success": true
}
In the case of errors, a JSON response is returned.
- Response body fields:
success
: boolean value that indicates the request has failed by holding a false value.error
: HTTP status code (e. g. 404, 401, 500).message
: A short description of the error.
Response body example:
{
"success": false,
"error": 404,
"message": "Resource not found"
}
This app is hosted at Heroku, in the following URL: https://diegoehg-casting-agency-api.herokuapp.com/
For testing it, a Postman collection is provided in this repository, with JWTs for accessing the different endpoints. This JWTs are located in the variables section of the collection, at the end of the file:
{
"variable": [
{
"key": "casting-assistant-token",
"value": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllKTHRSdUJFM1QxVXVNR0VkTWdaeCJ9.eyJpc3MiOiJodHRwczovL2Rldi1iYWxpYW4udXMuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDYwMGRmNmI2ZmZjYmUyMDA2YTg4NmUyZCIsImF1ZCI6ImNhc3RpbmctYWdlbmN5LWF1dGgtYXBpIiwiaWF0IjoxNjE2NDk4NzM3LCJleHAiOjE2MTY1ODUxMzcsImF6cCI6Ik9yQTZkRFZiVmVYZ01YQkFsVHFuWHI4UE9RU2MyYVY4Iiwic2NvcGUiOiIiLCJwZXJtaXNzaW9ucyI6WyJnZXQ6YWN0b3JzIiwiZ2V0Om1vdmllcyJdfQ.YsYvZeKjOMN5Ko9rVLTlfgBFofVNf8itrv8EXZ_Dq0f7-BSzE9RNWCe2skLVDgUWyjMMFZmAh1P0MKJBo1LCpdXna1wVbgk_Xk5ZwKCQoSl78spKuDbnREcWg8yNJkMi5w3balsVeWe0JmNcX-ZP0Wcmc-10shQ5o4cZn2phRGqfPfdbTQ3X2LThHMqQfW5QhqaYa5ka2waT9NbmmXRcAnxA0sW-fQek9-azCQtgd6YJDRManVgk2kXcy1ZzLRmhaPL6eRb5mFcMKoWv-e8KzneBuPQEeLWW8BVDE_pu8JRKCCNecP7rsAsLvWOX0vezbUejkd49zEg6MpAZ5ioa0Q"
},
{
"key": "casting-director-token",
"value": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllKTHRSdUJFM1QxVXVNR0VkTWdaeCJ9.eyJpc3MiOiJodHRwczovL2Rldi1iYWxpYW4udXMuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDYwNTJiMmQ1ODQzNWRhMDA2ODg0ODI2OSIsImF1ZCI6ImNhc3RpbmctYWdlbmN5LWF1dGgtYXBpIiwiaWF0IjoxNjE2NDk4ODA3LCJleHAiOjE2MTY1ODUyMDcsImF6cCI6Ik9yQTZkRFZiVmVYZ01YQkFsVHFuWHI4UE9RU2MyYVY4Iiwic2NvcGUiOiIiLCJwZXJtaXNzaW9ucyI6WyJkZWxldGU6YWN0b3JzIiwiZ2V0OmFjdG9ycyIsImdldDptb3ZpZXMiLCJwb3N0OmFjdG9ycyIsInVwZGF0ZTphY3RvcnMiLCJ1cGRhdGU6bW92aWVzIl19.WxhvhNZkdiyK_6QS05AMrUDCABY8VhDzvitq9UcmqRgK0l73EaIRyDMksOWTEj5Q-wMjogkVWMqDa1CaC3z-oCLq8ccq2J9IK39BTKffxcaEtUwa3qEKLaNUFiBcXusmTlBNwDwn9PsruJeFs2WKvgpq9GAU6kx8Q-tsPPawBRu4-4Bap7v5d_jDslRDpHlKzMWuGkegkPg0AfDVm3D_PIHPiQBCRFdK4XoKoFw8jjHlhTTZdTioX7XMLevINcOT_8a_ObA3K1VCSsi_oJbpqQa8-euHvM7rbo6Csvej5Rg9soxcmfG7bXvmWuL8O_Dm86P7FcwGcCHwZjKPkV_UBQ"
},
{
"key": "executive-producer-token",
"value": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllKTHRSdUJFM1QxVXVNR0VkTWdaeCJ9.eyJpc3MiOiJodHRwczovL2Rldi1iYWxpYW4udXMuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDYwNTJiNDY2MjYyNjFhMDA2ZmE5MGJhOSIsImF1ZCI6ImNhc3RpbmctYWdlbmN5LWF1dGgtYXBpIiwiaWF0IjoxNjE2NDk4ODYwLCJleHAiOjE2MTY1ODUyNjAsImF6cCI6Ik9yQTZkRFZiVmVYZ01YQkFsVHFuWHI4UE9RU2MyYVY4Iiwic2NvcGUiOiIiLCJwZXJtaXNzaW9ucyI6WyJkZWxldGU6YWN0b3JzIiwiZGVsZXRlOm1vdmllcyIsImdldDphY3RvcnMiLCJnZXQ6bW92aWVzIiwicG9zdDphY3RvcnMiLCJwb3N0Om1vdmllcyIsInVwZGF0ZTphY3RvcnMiLCJ1cGRhdGU6bW92aWVzIl19.In8sjdeD89UWz1R1JFsjxTMtH0777L6ZGmgmV3zCWbLvJAkwoo05-ERkKmJca53Soi4-m_tk90ject9clUGegoyb9CftyAaWD8cay91xqwrW9mzMx3pGOnKjxCqCC49YerwmfFAL0LwyzNLDAyV_tFNgTBkI8g6aQBLn1j6j5vLsbT69MB-_NHjj4wcLW0j00j9Sp8yw_XlJVI2IzoOk02KEsgh6y5rWNeXy0JxhopnWTLkPfFbANbUIYbTI2uSzfpb0-B-2zfszJ5BfovPjaXMiWD6M-KE5pQT9302kGUwqMCXLr2ZsneGLCEOov4OFs1I1V_SoWj2a4LQD1OSRWA"
}
]
}
Load this collection from Postman and give it a try!
All of the files included in this project are covered by the MIT License.