About
Server Deployment
Routings
Candy Delivery API is based on REST principles to handle information about couriers and orders, make orders' assignment, reflect completion, calculate courier's earnings amount and rating. API is based on Flask framework and created as a part of the Yandex Backend School entrance test. PostgreSQL was chosen as a database, NGINX as a proxy server and Gunicorn as WSGI HTTP Server.
Docker containers technology was used to make the installation process of Candy Delivery API rapid and easy. To deploy the server you just have to complete a few steps:
-
Download and install Docker Desktop: https://www.docker.com/products/docker-desktop
For Linux users you can find the manual here. -
Install Docker Compose according to the guide: https://docs.docker.com/compose/install.
-
Download all the files from current GitHub repository to your server and go inside the folder using terminal:
git clone https://github.com/Linkerin/backend_school.git cd /your/destination/backend_school -
Inside the API folder you should edit
.env.dband.envto set your own database password and Flask secret key:.env.db
# Change POSTGRES_PASSWORD POSTGRES_USER=flask_admin POSTGRES_PASSWORD=your_secret_password POSTGRES_DB=candy_delivery
.env
# Change SECRET_KEY FLASK_APP=app/__init__.py FLASK_RUN_HOST=0.0.0.0 FLASK_ENV=production SECRET_KEY='YOUR_SECRET_KEY' # POSTGRES_PASSWORD from .env.db instead of 'your_secret_password' DATABASE_URL='postgresql://flask_admin:your_secret_password@flaskapp_postgres:5432/candy_delivery'
SECRET_KEYis used for securely signing the session cookie and other security reasons. There are several methods how you can get a long random string and here is one of them using Python:import secrets key = secrets.token_hex(16) print(f'Your secret key is {key}') # Example output: Your secret key is 35a72ed52d040d3f466d34a93315b5f6
-
Build docker containers using the following command:
sudo docker-compose up -d --build
-
After that you should create tables in the database:
sudo docker-compose exec app python manage.py create_db -
That's it! Now everything should be working just fine. Test it out connecting to your server IP address. You will see the following message:
Candy Delivery App API
Note: NGINX server runs on port 80, Flask application itself on port 8080.
If you need to stop and remove all the containers you can run this command:
sudo docker-compose down -v-
Input: JSON
Allowed methods: POST
Response options:- HTTP 201 Created
- HTTP 400 Bad Request
- HTTP 405 Method Not Allowed
This routing is used to create information about the couriers. It receives a JSON with obligatory fields and in case of successful validation creates couriers in the database and returns a JSON with a list of created couriers' IDs.
Example:
POST /couriers { "data": [ { "courier_id": 1, "courier_type": "foot", "regions": [1, 12, 22], "working_hours": ["11:35-14:05", "09:00-11:00"] } ] }
Sucessful creation response:
HTTP 201 Created { "couriers": [{"id": 1}] }
For example, if the courier was already created the response will be the following:
HTTP 400 Bad Request { "Errors": [ {"id 1": "id already exists"} ], "validation error": { "couriers": [ {"id": 1} ] } }
Fields description:
Field Type Description courier_id Integer, positive Unique courier's ID courier_type String Possible values: 'foot', 'bike', 'car' regions Array of positive integers List of regions' IDs where a courier can work working_hours Array of strings Courier's work schedule in the following format: "HH:MM-HH:MM"Courier's load capacity depends on his type:
- foot - 10 kg;
- bike - 15 kg;
- car - 50 kg.
-
Input: JSON
Allowed methods: PATCH, GET
Response options:- HTTP 200 OK
- HTTP 400 Bad Request
- HTTP 404 Not Found
- HTTP 405 Method Not Allowed
This routing is used to change the courier's information and accepts JSON with any fields from the list:
courier_type,regions,working_hours. Additional properties are not allowed: in this case the server will returnHTTP 400 Bad Requestresponse.
Keep in mind that the change of courier's attributes will affect the set of orders already assigned to him. For example, if you changecourier_type(which means you change his load capacity) total weight of all uncompleted couriers's assigned orders will be recalculated and exceeding orders will become available for assignment.
Data format requirements are the same as for/couriersrouting.GETmethod for this routing returns courier's information. It also returns his rating and total earnings if the courier had at least one completed orders assignment.Example:
PATCH /couriers/1 { "courier_type": "bike" }
Successful update response:
HTTP 200 OK { "courier_id": 1, "courier_type": "bike", "regions": [1, 12, 22], "working_hours": ["11:35-14:05", "09:00-11:00"] }
Attempt to update a non-existent courier:
PATCH /couriers/200 { "regions": [7, 23, 78], "working_hours": ["08:00-17:00"] }
And the response wil be
HTTP 404 Not Found.
GETrequest example:GET /couriers/1 { "courier_id": 1, "courier_type": "bike", "earnings": 2500, "rating": 1.72, "regions": [1, 12, 22], "working_hours": ["11:35-14:05", "09:00-11:00"] }
-
Input: JSON
Allowed methods: POST
Response options:- HTTP 201 Created
- HTTP 400 Bad Request
- HTTP 405 Method Not Allowed
This routing is used to create information about the orders. It receives a JSON with obligatory fields and in case of successful validation creates orders in the database and returns a JSON with a list of created orders' IDs.
Example:
POST /orders { "data": [ { "order_id": 1, "weight": 0.23, "region": 12, "delivery_hours": ["09:00-18:00"] } ] }
Sucessful creation response:
HTTP 201 Created { "orders": [{"id": 1}] }
Fields description:
Field Type Description order_id Integer, positive Unique order's ID weight Number, precision: 2 digits, positive Order's weight should be greater than or equal to 0.01 and less than or equal to 50 region Integer, positive List of regions' IDs where a courier can work delivery_hours Array of strings Time periods when the customer can accept the delivery in the following format: "HH:MM-HH:MM"And here is an example of invalid
"region"field type:POST /orders { "data": [ { "order_id": 258, "weight": 29.7, "region": "1", "delivery_hours": ["09:00-18:00"] } ] }
Response:
HTTP 400 Bad Request { "Errors": [ {"id 258": "{'region': ['Not a valid integer.']}"} ], "validation error": { "orders": [ {"id": 258} ] } }
Input: JSON
Allowed methods: POST
Response options:- HTTP 200 OK
- HTTP 400 Bad Request
- HTTP 405 Method Not Allowed
This routing is used to assign orders to a courier. The routing accepts only
"courier_id"property inside JSON with a valid ID. In case of successful assignment the server will return a JSON containing a list of assigned orders and assign time in ISO 8601 format. If there are no available orders for this courier, the server will return only an empty list of order IDs without"assign_time"property.POST example:
POST /orders/assign { "courier_id": 2 }
Response:
HTTP 200 OK { "assign_time": "2021-03-29T11:18:03.496970+00:00", "orders": [{"id": 1}, {"id": 2}, {"id": 3}] }
If you make a
POSTrequest for the courier who already has assigned orders you will receive information about his orders that are currently uncomleted in the same JSON format.Input: JSON
Allowed methods: POST
Response options:- HTTP 200 OK
- HTTP 400 Bad Request
- HTTP 405 Method Not Allowed
This routing is used for sending the order completion information. JSON should include valid
"courier_id","order_id"and"complete_time"in ISO 8601 or RFC 3339 format. The order couldn't be completed before its' assign time or before the completion time of the previous order delivered by the courier. If everything is correct, the server will return an order ID. Keep in mind that the order should be assigned to the courier stated in the"courier_id"field.POST example:
POST /orders/assign { "courier_id": 2, "order_id": 1, "complete_time": "2021-03-29T11:44:03.31Z" }
Response:
HTTP 200 OK { "order_id": 1 }
If now you make a
POSTrequest containing{"courier_id": 2}to/orders/assignrouting, you will see that order_id 1 was removed from the response:HTTP 200 OK { "assign_time": "2021-03-29T11:18:03.496970+00:00", "orders": [{"id": 2}, {"id": 3}] }
In case you want to start a Flask app without deploying it using Docker all the required pip packages are listed in the requirements.txt inside this repository. Install them using the following command:
pip install -r requirements.txt