├── frontend
├── .flaskenv
├── application
│ ├── frontend
│ │ ├── api
│ │ │ ├── __init__.py
│ │ │ ├── ProductClient.py
│ │ │ ├── OrderClient.py
│ │ │ └── UserClient.py
│ │ ├── __init__.py
│ │ ├── forms.py
│ │ └── views.py
│ ├── static
│ │ ├── images
│ │ │ ├── sample.jpg
│ │ │ ├── product1.jpg
│ │ │ └── product2.jpg
│ │ └── css
│ │ │ └── style.css
│ ├── templates
│ │ ├── order
│ │ │ └── thankyou.html
│ │ ├── base_col_1.html
│ │ ├── macros
│ │ │ ├── _macros_basket.html
│ │ │ └── _macros_form.html
│ │ ├── base_col_2.html
│ │ ├── login
│ │ │ └── index.html
│ │ ├── _messages.html
│ │ ├── admin
│ │ │ └── index.html
│ │ ├── register
│ │ │ └── index.html
│ │ ├── base.html
│ │ ├── nav_header.html
│ │ ├── home
│ │ │ └── index.html
│ │ └── product
│ │ │ └── index.html
│ └── __init__.py
├── .env
├── .gitignore
├── run.py
├── Dockerfile
├── README.md
├── docker-compose.yml
├── config.py
├── requirements.txt
└── docker-compose.deploy.yml
├── user-service
├── .flaskenv
├── .env
├── .gitignore
├── application
│ ├── user_api
│ │ ├── __init__.py
│ │ └── routes.py
│ ├── __init__.py
│ └── models.py
├── Dockerfile
├── README.md
├── config.py
├── docker-compose.yml
├── requirements.txt
└── run.py
├── order-service
├── .flaskenv
├── application
│ ├── order_api
│ │ ├── api
│ │ │ ├── __init__.py
│ │ │ └── UserClient.py
│ │ ├── __init__.py
│ │ └── routes.py
│ ├── __init__.py
│ └── models.py
├── .env
├── .gitignore
├── Dockerfile
├── run.py
├── README.md
├── config.py
├── docker-compose.yml
└── requirements.txt
├── product-service
├── .flaskenv
├── .env
├── .gitignore
├── application
│ ├── product_api
│ │ ├── __init__.py
│ │ └── routes.py
│ ├── __init__.py
│ └── models.py
├── Dockerfile
├── run.py
├── README.md
├── config.py
├── requirements.txt
└── docker-compose.yml
├── .gitignore
└── README.md
/frontend/.flaskenv:
--------------------------------------------------------------------------------
1 | FLASK_APP=run.py
--------------------------------------------------------------------------------
/user-service/.flaskenv:
--------------------------------------------------------------------------------
1 | FLASK_APP=run.py
--------------------------------------------------------------------------------
/order-service/.flaskenv:
--------------------------------------------------------------------------------
1 | FLASK_APP=run.py
--------------------------------------------------------------------------------
/product-service/.flaskenv:
--------------------------------------------------------------------------------
1 | FLASK_APP=run.py
--------------------------------------------------------------------------------
/frontend/application/frontend/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/order-service/application/order_api/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/.env:
--------------------------------------------------------------------------------
1 | #.env
2 | CONFIGURATION_SETUP="config.ProductionConfig"
--------------------------------------------------------------------------------
/user-service/.env:
--------------------------------------------------------------------------------
1 | # .env
2 | CONFIGURATION_SETUP="config.ProductionConfig"
--------------------------------------------------------------------------------
/order-service/.env:
--------------------------------------------------------------------------------
1 | # .env
2 | CONFIGURATION_SETUP="config.ProductionConfig"
--------------------------------------------------------------------------------
/product-service/.env:
--------------------------------------------------------------------------------
1 | #.env
2 | CONFIGURATION_SETUP="config.DevelopmentConfig"
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .git/
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .git/
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
6 | migrations/
--------------------------------------------------------------------------------
/order-service/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .git/
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
--------------------------------------------------------------------------------
/product-service/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .git/
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
--------------------------------------------------------------------------------
/user-service/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .git/
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
--------------------------------------------------------------------------------
/frontend/application/static/images/sample.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saqibbutt/python-flask-microservices/HEAD/frontend/application/static/images/sample.jpg
--------------------------------------------------------------------------------
/frontend/application/static/images/product1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saqibbutt/python-flask-microservices/HEAD/frontend/application/static/images/product1.jpg
--------------------------------------------------------------------------------
/frontend/application/static/images/product2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saqibbutt/python-flask-microservices/HEAD/frontend/application/static/images/product2.jpg
--------------------------------------------------------------------------------
/frontend/run.py:
--------------------------------------------------------------------------------
1 | # run.py
2 | from application import create_app
3 |
4 | app = create_app()
5 |
6 | if __name__ == '__main__':
7 | app.run(host='0.0.0.0', port=5000)
--------------------------------------------------------------------------------
/frontend/application/frontend/__init__.py:
--------------------------------------------------------------------------------
1 | # application/frontend/__init__.py
2 | from flask import Blueprint
3 |
4 | frontend_blueprint = Blueprint('frontend', __name__)
5 |
6 | from . import views
7 |
--------------------------------------------------------------------------------
/order-service/application/order_api/__init__.py:
--------------------------------------------------------------------------------
1 | # application/order_api/__init__.py
2 | from flask import Blueprint
3 |
4 | order_api_blueprint = Blueprint('order_api', __name__)
5 |
6 | from . import routes
--------------------------------------------------------------------------------
/user-service/application/user_api/__init__.py:
--------------------------------------------------------------------------------
1 | # application/user_api/__init__.py
2 | from flask import Blueprint
3 |
4 | user_api_blueprint = Blueprint('user_api', __name__)
5 |
6 | from . import routes
7 |
--------------------------------------------------------------------------------
/product-service/application/product_api/__init__.py:
--------------------------------------------------------------------------------
1 | # application/product_api/__init__.py
2 | from flask import Blueprint
3 |
4 | product_api_blueprint = Blueprint('product_api', __name__)
5 |
6 | from . import routes
7 |
--------------------------------------------------------------------------------
/user-service/Dockerfile:
--------------------------------------------------------------------------------
1 | # Dockerfile
2 | FROM python:3.7
3 | COPY requirements.txt /userapp/requirements.txt
4 | WORKDIR /userapp
5 | RUN pip install -r requirements.txt
6 | COPY . /userapp
7 | ENTRYPOINT ["python"]
8 | CMD ["run.py"]
--------------------------------------------------------------------------------
/order-service/Dockerfile:
--------------------------------------------------------------------------------
1 | # Dockerfile
2 | FROM python:3.7
3 | COPY requirements.txt /orderapp/requirements.txt
4 | WORKDIR /orderapp
5 | RUN pip install -r requirements.txt
6 | COPY . /orderapp
7 | ENTRYPOINT ["python"]
8 | CMD ["run.py"]
--------------------------------------------------------------------------------
/frontend/Dockerfile:
--------------------------------------------------------------------------------
1 | # Dockerfile
2 | FROM python:3.7
3 | COPY requirements.txt /frontendapp/requirements.txt
4 | WORKDIR /frontendapp
5 | RUN pip install -r requirements.txt
6 | COPY . /frontendapp
7 | ENTRYPOINT ["python"]
8 | CMD ["run.py"]
--------------------------------------------------------------------------------
/product-service/Dockerfile:
--------------------------------------------------------------------------------
1 | # Dockerfile
2 | FROM python:3.7
3 | COPY requirements.txt /productapp/requirements.txt
4 | WORKDIR /productapp
5 | RUN pip install -r requirements.txt
6 | COPY . /productapp
7 | ENTRYPOINT ["python"]
8 | CMD ["run.py"]
--------------------------------------------------------------------------------
/frontend/application/templates/order/thankyou.html:
--------------------------------------------------------------------------------
1 | {% extends "base_col_1.html" %}
2 | {% block title %}Thank you{% endblock %}
3 |
4 | {% block pageContent %}
5 |
Thank you
6 | your order is being processed
7 | {% endblock %}
--------------------------------------------------------------------------------
/order-service/run.py:
--------------------------------------------------------------------------------
1 | # run.py
2 | from application import create_app, db
3 | from flask_migrate import Migrate
4 | from application import models
5 |
6 | app = create_app()
7 | migrate = Migrate(app, db)
8 |
9 | if __name__ == '__main__':
10 | app.run(host='0.0.0.0', port=5003)
11 |
--------------------------------------------------------------------------------
/product-service/run.py:
--------------------------------------------------------------------------------
1 | # run.py
2 | from application import create_app, db
3 | from application import models
4 | from flask_migrate import Migrate
5 |
6 | app = create_app()
7 | migrate = Migrate(app, db)
8 |
9 | if __name__ == '__main__':
10 | app.run(host='0.0.0.0', port=5002)
11 |
--------------------------------------------------------------------------------
/order-service/README.md:
--------------------------------------------------------------------------------
1 | ## Running application in docker containers:
2 | #### Using Docker CLI
3 | ```
4 | docker network ls
5 | docker network create --driver bridge micro_network (skip if already created)
6 | docker build -t order-srv .
7 | docker run -p 5003:5003 --detach --name order-service --net=micro_network order-srv
8 | ```
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | ## Running application in docker containers:
2 | #### Using Docker CLI
3 | ```
4 | docker network ls
5 | docker network create --driver bridge micro_network (skip if already created)
6 | docker build -t frontend-srv .
7 | docker run -p 5000:5000 --detach --name frontend-service --net=micro_network frontend-srv
8 | ```
--------------------------------------------------------------------------------
/frontend/application/templates/base_col_1.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 | {% include 'nav_header.html' %}
5 |
6 |
7 | {% include '_messages.html' %}
8 | {% block pageContent %} {% endblock %}
9 |
10 |
11 | {% endblock %}
--------------------------------------------------------------------------------
/product-service/README.md:
--------------------------------------------------------------------------------
1 | ## Running application in docker containers:
2 | #### Using Docker CLI
3 | ```
4 | docker network ls
5 | docker network create --driver bridge micro_network (skip if already created)
6 | docker build -t product-srv .
7 | docker run -p 5002:5002 --detach --name product-service --net=micro_network product-srv
8 | ```
--------------------------------------------------------------------------------
/frontend/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # docker-compose.yml
2 | version: '3.8'
3 |
4 | networks:
5 | micro_network:
6 | external:
7 | name: micro_network
8 |
9 | services:
10 | frontend-app:
11 | container_name: cfrontend-app
12 | build:
13 | context: .
14 | ports:
15 | - "5000:5000"
16 | networks:
17 | - micro_network
18 | restart: always
--------------------------------------------------------------------------------
/frontend/application/templates/macros/_macros_basket.html:
--------------------------------------------------------------------------------
1 | {{ session['order'] | pprint }}
2 | {% macro count_items()%}
3 | {% set counter = namespace(a=0) %}
4 | {% if session['order'] %}
5 | {% for item in session['order']['items'] %}
6 | {% set counter.a = counter.a + item['quantity'] %}
7 | {% endfor %}
8 | {% endif %}
9 | {{ counter.a }}
10 | {% endmacro %}
--------------------------------------------------------------------------------
/frontend/application/templates/base_col_2.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 |
4 |
5 | {% block content %}
6 | {% include 'nav_header.html' %}
7 |
8 |
9 | {% include '_messages.html' %}
10 |
11 |
12 |
13 |
14 |
15 | {% block pageContent %} {% endblock %}
16 |
17 |
18 |
19 | {% endblock %}
--------------------------------------------------------------------------------
/order-service/application/order_api/api/UserClient.py:
--------------------------------------------------------------------------------
1 | # application/order_api/api/UserClient.py
2 | import requests
3 |
4 |
5 | class UserClient:
6 | @staticmethod
7 | def get_user(api_key):
8 | headers = {
9 | 'Authorization': api_key
10 | }
11 | response = requests.request(method="GET", url='http://cuser-service:5001/api/user', headers=headers)
12 | if response.status_code == 401:
13 | return False
14 | user = response.json()
15 | return user
16 |
--------------------------------------------------------------------------------
/frontend/application/templates/login/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base_col_1.html" %}
2 | {% from "macros/_macros_form.html" import render_field %}
3 | {% block title %}Login{% endblock %}
4 |
5 | {% block pageContent %}
6 | Login
7 | {{ message }}
8 |
9 |
17 |
18 | {% endblock %}
--------------------------------------------------------------------------------
/frontend/application/frontend/api/ProductClient.py:
--------------------------------------------------------------------------------
1 | # application/frontend/api/ProductClient.py
2 | import requests
3 |
4 |
5 | class ProductClient:
6 |
7 | @staticmethod
8 | def get_products():
9 | r = requests.get('http://cproduct-service:5002/api/products')
10 | products = r.json()
11 | return products
12 |
13 | @staticmethod
14 | def get_product(slug):
15 | response = requests.request(method="GET", url='http://cproduct-service:5002/api/product/' + slug)
16 | product = response.json()
17 | return product
18 |
--------------------------------------------------------------------------------
/frontend/application/templates/_messages.html:
--------------------------------------------------------------------------------
1 | {% for error_message in get_flashed_messages(category_filter=["error"]) %}
2 | {{ error_message }}
3 | {% endfor %}
4 |
5 | {% for success_message in get_flashed_messages(category_filter=["success"]) %}
6 | {{ success_message }}
7 | {% endfor %}
8 |
9 | {% for info_message in get_flashed_messages(category_filter=["info"]) %}
10 | {{ info_message }}
11 | {% endfor %}
--------------------------------------------------------------------------------
/frontend/config.py:
--------------------------------------------------------------------------------
1 | # config.py
2 | import os
3 | from dotenv import load_dotenv
4 |
5 | dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
6 |
7 | if os.path.exists(dotenv_path):
8 | load_dotenv(dotenv_path)
9 |
10 |
11 | class Config:
12 | SECRET_KEY = 'y2BH8xD9pyZhDT5qkyZZRgjcJCMHdQ'
13 | WTF_CSRF_SECRET_KEY = 'VyOyqv5Fm3Hs3qB1AmNeeuvPpdRqTJbTs5wKvWCS'
14 |
15 |
16 | class DevelopmentConfig(Config):
17 | ENV = "development"
18 | DEBUG = True
19 |
20 |
21 | class ProductionConfig(Config):
22 | ENV = "production"
23 | DEBUG = False
24 |
25 |
--------------------------------------------------------------------------------
/order-service/application/__init__.py:
--------------------------------------------------------------------------------
1 | # application/__init__.py
2 | import config
3 | import os
4 | from flask import Flask
5 | from flask_sqlalchemy import SQLAlchemy
6 |
7 | db = SQLAlchemy()
8 |
9 |
10 | def create_app():
11 | app = Flask(__name__)
12 | environment_configuration = os.environ['CONFIGURATION_SETUP']
13 | app.config.from_object(environment_configuration)
14 |
15 | db.init_app(app)
16 |
17 | with app.app_context():
18 | from .order_api import order_api_blueprint
19 | app.register_blueprint(order_api_blueprint)
20 | return app
21 |
--------------------------------------------------------------------------------
/product-service/application/__init__.py:
--------------------------------------------------------------------------------
1 | # application/__init__.py
2 | import config
3 | import os
4 | from flask import Flask
5 | from flask_sqlalchemy import SQLAlchemy
6 |
7 | db = SQLAlchemy()
8 |
9 |
10 | def create_app():
11 | app = Flask(__name__)
12 |
13 | environment_configuration = os.environ['CONFIGURATION_SETUP']
14 | app.config.from_object(environment_configuration)
15 |
16 | db.init_app(app)
17 |
18 | with app.app_context():
19 | from .product_api import product_api_blueprint
20 | app.register_blueprint(product_api_blueprint)
21 | return app
22 |
--------------------------------------------------------------------------------
/user-service/README.md:
--------------------------------------------------------------------------------
1 | ## Running application in docker containers:
2 | #### Using Docker CLI
3 |
4 | ```
5 | docker network create --driver bridge micro_network (skip if already created)
6 | docker build -t user-srv .
7 | docker run -p 5001:5001 --detach --name user-service --net=micro_network user-srv
8 | ```
9 |
10 | ## Using 'flask shell' to access flask application
11 | ```
12 | $ flask shell
13 | from application.models import User
14 | from application import db
15 | admin = User(username="foo", email="foo@admin.com",first_name="foo", last_name="bar", password="admin2020",is_admin=True)
16 | db.session.add(admin)
17 | db.session.commit()
18 | ```
--------------------------------------------------------------------------------
/frontend/application/templates/admin/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base_col_1.html" %}
2 | {% from "macros/_macros_form.html" import render_field %}
3 | {% block title %}Product page{% endblock %}
4 |
5 | {% block pageContent %}
6 | {{ message }}
7 | Add Product
8 |
9 |
10 |
17 |
18 |
19 | {% endblock %}
--------------------------------------------------------------------------------
/frontend/application/templates/register/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base_col_1.html" %}
2 | {% from "macros/_macros_form.html" import render_field %}
3 | {% block title %}Register{% endblock %}
4 |
5 | {% block pageContent %}
6 | Register
7 | {{ message }}
8 |
9 |
19 |
20 | {% endblock %}
--------------------------------------------------------------------------------
/product-service/config.py:
--------------------------------------------------------------------------------
1 | # config.py
2 | import os
3 | from dotenv import load_dotenv
4 |
5 | dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
6 | if os.path.exists(dotenv_path):
7 | load_dotenv(dotenv_path)
8 |
9 |
10 | class Config:
11 | SQLALCHEMY_TRACK_MODIFICATIONS = False
12 |
13 |
14 | class DevelopmentConfig(Config):
15 | ENV = "development"
16 | DEBUG = True
17 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://cloudacademy:pfm_2020@localhost:3306/product_dev'
18 |
19 |
20 | class ProductionConfig(Config):
21 | ENV = "production"
22 | DEBUG = False
23 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://cloudacademy:pfm_2020@product-db:3306/product'
24 |
--------------------------------------------------------------------------------
/user-service/application/__init__.py:
--------------------------------------------------------------------------------
1 | # application/__init__.py
2 | import config
3 | import os
4 | from flask import Flask
5 | from flask_login import LoginManager
6 | from flask_sqlalchemy import SQLAlchemy
7 |
8 | db = SQLAlchemy()
9 | login_manager = LoginManager()
10 |
11 |
12 | def create_app():
13 | app = Flask(__name__)
14 | environment_configuration = os.environ['CONFIGURATION_SETUP']
15 | app.config.from_object(environment_configuration)
16 |
17 | db.init_app(app)
18 | login_manager.init_app(app)
19 |
20 | with app.app_context():
21 | # Register blueprints
22 | from .user_api import user_api_blueprint
23 | app.register_blueprint(user_api_blueprint)
24 | return app
25 |
--------------------------------------------------------------------------------
/order-service/config.py:
--------------------------------------------------------------------------------
1 | # config.py
2 | import os
3 | from dotenv import load_dotenv
4 |
5 | dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
6 | if os.path.exists(dotenv_path):
7 | load_dotenv(dotenv_path)
8 |
9 |
10 | class Config:
11 | SQLALCHEMY_TRACK_MODIFICATIONS = False
12 |
13 |
14 | class DevelopmentConfig(Config):
15 | ENV = "development"
16 | DEBUG = True
17 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://cloudacademy:pfm_2020@host.docker.internal:3306/order_dev'
18 | SQLALCHEMY_ECHO = True
19 |
20 |
21 | class ProductionConfig(Config):
22 | ENV = "production"
23 | DEBUG = False
24 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://cloudacademy:pfm_2020@order-db:3306/order'
25 | SQLALCHEMY_ECHO = False
26 |
27 |
28 |
--------------------------------------------------------------------------------
/user-service/config.py:
--------------------------------------------------------------------------------
1 | # config.py
2 | import os
3 | from dotenv import load_dotenv
4 |
5 | dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
6 | if os.path.exists(dotenv_path):
7 | load_dotenv(dotenv_path)
8 |
9 |
10 | class Config:
11 | SECRET_KEY = "mrfrIMEngCl0pAKqIIBS_g"
12 | SQLALCHEMY_TRACK_MODIFICATIONS = False
13 |
14 |
15 | class DevelopmentConfig(Config):
16 | ENV = "development"
17 | DEBUG = True
18 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://cloudacademy:pfm_2020@host.docker.internal:3306/user_dev'
19 | SQLALCHEMY_ECHO = True
20 |
21 |
22 | class ProductionConfig(Config):
23 | ENV = "production"
24 | DEBUG = False
25 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://cloudacademy:pfm_2020@user-db:3306/user'
26 | SQLALCHEMY_ECHO = False
27 |
28 |
--------------------------------------------------------------------------------
/frontend/requirements.txt:
--------------------------------------------------------------------------------
1 | alembic==1.4.2
2 | autoenv==1.0.0
3 | blinker==1.4
4 | certifi==2019.11.28
5 | cffi==1.14.0
6 | chardet==3.0.4
7 | click==7.1.1
8 | cryptography==2.8
9 | dnspython==1.16.0
10 | dominate==2.5.1
11 | Flask==1.1.1
12 | Flask-Bootstrap==3.3.7.1
13 | Flask-DotEnv==0.1.2
14 | Flask-Login==0.5.0
15 | Flask-Migrate==2.5.3
16 | Flask-SQLAlchemy==2.4.1
17 | Flask-Uploads==0.2.1
18 | Flask-WTF==0.14.3
19 | idna==2.9
20 | itsdangerous==1.1.0
21 | Jinja2==2.11.1
22 | Mako==1.1.2
23 | MarkupSafe==1.1.1
24 | marshmallow==3.5.1
25 | passlib==1.7.2
26 | protobuf==3.6.1
27 | pycparser==2.20
28 | PyMySQL==0.9.3
29 | python-dateutil==2.8.1
30 | python-dotenv==0.12.0
31 | python-editor==1.0.4
32 | requests==2.23.0
33 | six==1.14.0
34 | SQLAlchemy==1.3.15
35 | urllib3==1.25.8
36 | visitor==0.1.3
37 | Werkzeug==1.0.1
38 | WTForms==2.2.1
39 |
--------------------------------------------------------------------------------
/order-service/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # docker-compose.yml
2 | version: '3.8'
3 | volumes:
4 | orderdb_vol:
5 |
6 | networks:
7 | micro_network:
8 | external:
9 | name: micro_network
10 |
11 | services:
12 | order-api:
13 | container_name: corder-service
14 | build:
15 | context: .
16 | ports:
17 | - "5003:5003"
18 | depends_on:
19 | - order-db
20 | networks:
21 | - micro_network
22 | restart: always
23 |
24 | order-db:
25 | container_name: corder_dbase
26 | image: mysql:8
27 | ports:
28 | - "32002:3306"
29 | environment:
30 | MYSQL_ROOT_PASSWORD: pfm_dc_2020
31 | MYSQL_DATABASE: order
32 | MYSQL_USER: cloudacademy
33 | MYSQL_PASSWORD: pfm_2020
34 | networks:
35 | - micro_network
36 | volumes:
37 | - orderdb_vol:/var/lib/mysql
--------------------------------------------------------------------------------
/user-service/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # docker-compose.yml
2 | version: '3.8'
3 |
4 | volumes:
5 | userdb_vol:
6 |
7 | networks:
8 | micro_network:
9 | external:
10 | name: micro_network
11 |
12 | services:
13 | user-api:
14 | container_name: cuser-service
15 | build:
16 | context: .
17 | ports:
18 | - "5001:5001"
19 | depends_on:
20 | - user-db
21 | networks:
22 | - micro_network
23 | restart: always
24 |
25 | user-db:
26 | container_name: cuser_dbase
27 | image: mysql:8
28 | ports:
29 | - "32000:3306"
30 | environment:
31 | MYSQL_ROOT_PASSWORD: pfm_dc_2020
32 | MYSQL_DATABASE: user
33 | MYSQL_USER: cloudacademy
34 | MYSQL_PASSWORD: pfm_2020
35 | networks:
36 | - micro_network
37 | volumes:
38 | - userdb_vol:/var/lib/mysql
--------------------------------------------------------------------------------
/order-service/requirements.txt:
--------------------------------------------------------------------------------
1 | alembic==1.4.2
2 | autoenv==1.0.0
3 | blinker==1.4
4 | certifi==2019.11.28
5 | cffi==1.14.0
6 | chardet==3.0.4
7 | click==7.1.1
8 | cryptography==2.8
9 | dnspython==1.16.0
10 | dominate==2.5.1
11 | Flask==1.1.1
12 | Flask-Bootstrap==3.3.7.1
13 | Flask-DotEnv==0.1.2
14 | Flask-Login==0.5.0
15 | Flask-Migrate==2.5.3
16 | Flask-SQLAlchemy==2.4.1
17 | Flask-Uploads==0.2.1
18 | Flask-WTF==0.14.3
19 | idna==2.9
20 | itsdangerous==1.1.0
21 | Jinja2==2.11.1
22 | Mako==1.1.2
23 | MarkupSafe==1.1.1
24 | marshmallow==3.5.1
25 | passlib==1.7.2
26 | protobuf==3.6.1
27 | pycparser==2.20
28 | PyMySQL==0.9.3
29 | python-dateutil==2.8.1
30 | python-dotenv==0.12.0
31 | python-editor==1.0.4
32 | requests==2.23.0
33 | six==1.14.0
34 | SQLAlchemy==1.3.15
35 | urllib3==1.25.8
36 | visitor==0.1.3
37 | Werkzeug==1.0.1
38 | WTForms==2.2.1
39 |
--------------------------------------------------------------------------------
/product-service/requirements.txt:
--------------------------------------------------------------------------------
1 | alembic==1.4.2
2 | autoenv==1.0.0
3 | blinker==1.4
4 | certifi==2019.11.28
5 | cffi==1.14.0
6 | chardet==3.0.4
7 | click==7.1.1
8 | cryptography==2.8
9 | dnspython==1.16.0
10 | dominate==2.5.1
11 | Flask==1.1.1
12 | Flask-Bootstrap==3.3.7.1
13 | Flask-DotEnv==0.1.2
14 | Flask-Login==0.5.0
15 | Flask-Migrate==2.5.3
16 | Flask-SQLAlchemy==2.4.1
17 | Flask-Uploads==0.2.1
18 | Flask-WTF==0.14.3
19 | idna==2.9
20 | itsdangerous==1.1.0
21 | Jinja2==2.11.1
22 | Mako==1.1.2
23 | MarkupSafe==1.1.1
24 | marshmallow==3.5.1
25 | passlib==1.7.2
26 | protobuf==3.6.1
27 | pycparser==2.20
28 | PyMySQL==0.9.3
29 | python-dateutil==2.8.1
30 | python-dotenv==0.12.0
31 | python-editor==1.0.4
32 | requests==2.23.0
33 | six==1.14.0
34 | SQLAlchemy==1.3.15
35 | urllib3==1.25.8
36 | visitor==0.1.3
37 | Werkzeug==1.0.1
38 | WTForms==2.2.1
39 |
--------------------------------------------------------------------------------
/product-service/application/models.py:
--------------------------------------------------------------------------------
1 | # application/models.py
2 | from . import db
3 | from datetime import datetime
4 |
5 |
6 | class Product(db.Model):
7 | id = db.Column(db.Integer, primary_key=True)
8 | name = db.Column(db.String(255), unique=True, nullable=False)
9 | slug = db.Column(db.String(255), unique=True, nullable=False)
10 | price = db.Column(db.Integer, nullable=False)
11 | image = db.Column(db.String(255), unique=False, nullable=True)
12 | date_added = db.Column(db.DateTime, default=datetime.utcnow)
13 | date_updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
14 |
15 | def to_json(self):
16 | return {
17 | 'id': self.id,
18 | 'name': self.name,
19 | 'slug': self.slug,
20 | 'price': self.price,
21 | 'image': self.image
22 | }
23 |
--------------------------------------------------------------------------------
/product-service/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # docker-compose.yml
2 | version: '3.8'
3 |
4 | volumes:
5 | productdb_vol:
6 |
7 | networks:
8 | micro_network:
9 | external:
10 | name: micro_network
11 |
12 | services:
13 | product-api:
14 | container_name: cproduct-service
15 | build:
16 | context: .
17 | ports:
18 | - "5002:5002"
19 | depends_on:
20 | - product-db
21 | networks:
22 | - micro_network
23 | restart: always
24 |
25 | product-db:
26 | container_name: cproduct_dbase
27 | image: mysql:8
28 | ports:
29 | - "32001:3306"
30 | environment:
31 | MYSQL_ROOT_PASSWORD: pfm_dc_2020
32 | MYSQL_DATABASE: product
33 | MYSQL_USER: cloudacademy
34 | MYSQL_PASSWORD: pfm_2020
35 | networks:
36 | - micro_network
37 | volumes:
38 | - productdb_vol:/var/lib/mysql
--------------------------------------------------------------------------------
/user-service/requirements.txt:
--------------------------------------------------------------------------------
1 | alembic==1.4.2
2 | autoenv==1.0.0
3 | blinker==1.4
4 | certifi==2019.11.28
5 | cffi==1.14.0
6 | chardet==3.0.4
7 | click==7.1.1
8 | cryptography==2.8
9 | dnspython==1.16.0
10 | dominate==2.5.1
11 | Flask==1.1.1
12 | Flask-Bootstrap==3.3.7.1
13 | Flask-DotEnv==0.1.2
14 | Flask-Login==0.5.0
15 | Flask-Migrate==2.5.3
16 | Flask-SQLAlchemy==2.4.1
17 | Flask-Uploads==0.2.1
18 | Flask-WTF==0.14.3
19 | gunicorn==20.0.4
20 | idna==2.9
21 | itsdangerous==1.1.0
22 | Jinja2==2.11.1
23 | Mako==1.1.2
24 | MarkupSafe==1.1.1
25 | marshmallow==3.5.1
26 | passlib==1.7.2
27 | protobuf==3.6.1
28 | pycparser==2.20
29 | PyMySQL==0.9.3
30 | python-dateutil==2.8.1
31 | python-dotenv==0.12.0
32 | python-editor==1.0.4
33 | requests==2.23.0
34 | six==1.14.0
35 | SQLAlchemy==1.3.15
36 | urllib3==1.25.8
37 | visitor==0.1.3
38 | Werkzeug==1.0.1
39 | WTForms==2.2.1
40 |
--------------------------------------------------------------------------------
/frontend/application/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% block title %} {% endblock %}
6 |
7 |
8 | {% block styles %}
9 |
10 |
11 |
12 |
15 |
16 | {% endblock %}
17 |
18 |
19 |
20 |
21 | {% block content %}
22 | {% endblock %}
23 |
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # python-flask-microservices
2 | ### Training material for https://cloudacademy.com/course/mastering-microservices-with-python-flask-docker-1118/course-introduction/
3 |
4 | ## Project Detail
5 | ###### 'python-flask-microservices' has 4 projects within.
6 |
7 | - frontend
8 | - order-service
9 | - product-service
10 | - user-service
11 |
12 | ###### Preferably configure a separate python virtual environment and install python packages.
13 | ```
14 | pip install -r requirements.txt
15 | ```
16 |
17 | ## Deployment in Production
18 | ###### For deployment in production, use docker-compose.deploy.yml in 'frontend' project folder.
19 |
20 | ## Python extensions reference
21 | ##### Flask-SQLAlchemy: https://flask-sqlalchemy.palletsprojects.com/en/2.x/
22 | ##### Flask-Login: https://flask-login.readthedocs.io/en/latest/
23 | ##### Flask-Migrate: https://github.com/miguelgrinberg/flask-migrate/
24 | ##### Requests: https://requests.readthedocs.io/en/master/
25 |
--------------------------------------------------------------------------------
/frontend/application/__init__.py:
--------------------------------------------------------------------------------
1 | # application/__init__.py
2 | import config
3 | import os
4 | from flask import Flask
5 | from flask_bootstrap import Bootstrap
6 | from flask_login import LoginManager
7 |
8 | login_manager = LoginManager()
9 | bootstrap = Bootstrap()
10 | UPLOAD_FOLDER = 'application/static/images'
11 |
12 |
13 | def create_app():
14 | app = Flask(__name__, static_folder='static')
15 | app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
16 |
17 | environment_configuration = os.environ['CONFIGURATION_SETUP']
18 | app.config.from_object(environment_configuration)
19 |
20 | login_manager.init_app(app)
21 | login_manager.login_message = "You must be login to access this page."
22 | login_manager.login_view = "frontend.login"
23 |
24 | bootstrap.init_app(app)
25 |
26 | with app.app_context():
27 | from .frontend import frontend_blueprint
28 | app.register_blueprint(frontend_blueprint)
29 |
30 | return app
31 |
--------------------------------------------------------------------------------
/user-service/run.py:
--------------------------------------------------------------------------------
1 | # run.py
2 | from application import create_app, db
3 | from application import models
4 | from flask_migrate import Migrate
5 |
6 | app = create_app()
7 | migrate = Migrate(app, db)
8 |
9 | from flask import g
10 | from flask.sessions import SecureCookieSessionInterface
11 | from flask_login import user_loaded_from_header
12 |
13 |
14 | class CustomSessionInterface(SecureCookieSessionInterface):
15 | """Prevent creating session from API requests."""
16 |
17 | def save_session(self, *args, **kwargs):
18 | if g.get('login_via_header'):
19 | return
20 | return super(CustomSessionInterface, self).save_session(*args,
21 | **kwargs)
22 |
23 |
24 | app.session_interface = CustomSessionInterface()
25 |
26 |
27 | @user_loaded_from_header.connect
28 | def user_loaded_from_header(self, user=None):
29 | g.login_via_header = True
30 |
31 |
32 | if __name__ == '__main__':
33 | app.run(host='0.0.0.0', port=5001)
34 |
--------------------------------------------------------------------------------
/frontend/application/templates/nav_header.html:
--------------------------------------------------------------------------------
1 | {% from "macros/_macros_basket.html" import count_items %}
2 |
--------------------------------------------------------------------------------
/frontend/application/templates/home/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base_col_1.html" %}
2 | {% block title %}Home Page{% endblock %}
3 |
4 | {% block pageContent %}
5 |
6 | {% if products | length > 0 %}
7 | {% for product in products.results %}
8 |
9 | {% set url = "/product/" + product.slug %}
10 | {% set imageSRC = "images/" + product.image %}
11 |
12 |
13 |
14 |
15 |
{{ product.name }}
16 |
17 |
18 |
19 |
 }})
20 |
21 |
22 |
28 |
29 |
30 | {% endfor %}
31 |
32 | {% else %}
33 | No products found.
34 | {% endif %}
35 |
36 |
37 | {% endblock %}
--------------------------------------------------------------------------------
/frontend/application/templates/product/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base_col_1.html" %}
2 | {% from "macros/_macros_form.html" import render_field %}
3 | {% block title %}Product page{% endblock %}
4 |
5 | {% block pageContent %}
6 |
7 | {% set url = "/product/" + product.slug %}
8 | {% set imageSRC = "images/" + product.image %}
9 |
10 |
11 |
12 |
{{ product.name }}
13 |
14 |
15 |
16 |
 }})
17 |
18 |
19 |
28 |
29 |
30 | {% endblock %}
--------------------------------------------------------------------------------
/frontend/application/static/css/style.css:
--------------------------------------------------------------------------------
1 | body, html {
2 | width: 100%;
3 | height: 100%;
4 | }
5 |
6 | body, h1, h2, h3 {
7 | font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
8 | font-weight: 700;
9 | }
10 |
11 | body{
12 | background-color:#DDD;
13 | }
14 |
15 | a, .navbar-default .navbar-brand, .navbar-default .navbar-nav > li > a {
16 | color: #aec251;
17 | }
18 |
19 | a:hover, .navbar-default .navbar-brand:hover, .navbar-default .navbar-nav > li > a:hover {
20 | color: #687430;
21 | }
22 |
23 | footer {
24 | padding: 50px 0;
25 | background-color: #f8f8f8;
26 | }
27 |
28 | p.copyright {
29 | margin: 15px 0 0;
30 | }
31 |
32 | .alert-info {
33 | width: 50%;
34 | margin: auto;
35 | color: #687430;
36 | background-color: #e6ecca;
37 | border-color: #aec251;
38 | }
39 |
40 | .btn-default {
41 | border-color: #aec251;
42 | color: #aec251;
43 | }
44 |
45 | .btn-default:hover {
46 | background-color: #aec251;
47 | }
48 |
49 | .center {
50 | margin: auto;
51 | width: 50%;
52 | padding: 10px;
53 | }
54 |
55 | .content-section {
56 | padding: 50px 0;
57 | border-top: 1px solid #e7e7e7;
58 | }
59 |
60 |
61 | .img-block{
62 | text-align:center;
63 | }
64 | .img-block img{
65 |
66 | max-width:200px;
67 | max-height:200px;
68 | }
69 |
--------------------------------------------------------------------------------
/frontend/application/templates/macros/_macros_form.html:
--------------------------------------------------------------------------------
1 | {% macro render_field(field) %}
2 |
3 | {{ field.label(class_="form-label") }}:
4 | {% if field.errors %}
5 |
6 | {% for error in field.errors %}
7 | {{ error }}
8 | {% endfor %}
9 |
10 | {% endif %}
11 |
12 |
13 | {{ field(class_="form-control") }}
14 |
15 | {% endmacro %}
16 |
17 |
18 |
19 | {% macro render_field_without_label(field) %}
20 | {{ field(**kwargs)|safe }}
21 | {% if field.errors %}
22 |
23 | {% for error in field.errors %}
24 | {{ error }}
25 | {% endfor %}
26 |
27 | {% endif %}
28 |
29 | {% endmacro %}
30 |
31 |
32 | {% macro render_boolean_field(field) %}
33 | {{ field(**kwargs)|safe }} {{ field.label }}
34 | {% if field.errors %}
35 |
36 | {% for error in field.errors %}
37 | {{ error }}
38 | {% endfor %}
39 |
40 | {% endif %}
41 |
42 | {% endmacro %}
43 |
44 |
45 | {% macro render_errors(field) %}
46 | {% if field.errors %}
47 |
48 | {% for error in field.errors %}
49 | {{ error }}
50 | {% endfor %}
51 |
52 | {% endif %}
53 | {% endmacro %}
54 |
--------------------------------------------------------------------------------
/frontend/application/frontend/forms.py:
--------------------------------------------------------------------------------
1 | # application/frontend/forms.py
2 | from flask_wtf import FlaskForm
3 | from wtforms import StringField, PasswordField, SubmitField, HiddenField, IntegerField
4 |
5 | from wtforms.validators import DataRequired, Email
6 |
7 |
8 | class LoginForm(FlaskForm):
9 | username = StringField('Username', validators=[DataRequired()])
10 | password = PasswordField('Password', validators=[DataRequired()])
11 | submit = SubmitField('Login')
12 |
13 |
14 | class RegistrationForm(FlaskForm):
15 | username = StringField('Username', validators=[DataRequired()])
16 | first_name = StringField('First name', validators=[DataRequired()])
17 | last_name = StringField('Last name', validators=[DataRequired()])
18 | email = StringField('Email address', validators=[DataRequired(), Email()])
19 | password = PasswordField('Password', validators=[DataRequired()])
20 | submit = SubmitField('Register')
21 |
22 |
23 | class OrderItemForm(FlaskForm):
24 | product_id = HiddenField(validators=[DataRequired()])
25 | quantity = IntegerField(validators=[DataRequired()])
26 | order_id = HiddenField()
27 | submit = SubmitField('Update')
28 |
29 |
30 | class ItemForm(FlaskForm):
31 | product_id = HiddenField(validators=[DataRequired()])
32 | quantity = HiddenField(validators=[DataRequired()], default=1)
33 |
--------------------------------------------------------------------------------
/product-service/application/product_api/routes.py:
--------------------------------------------------------------------------------
1 | # application/product_api/routes.py
2 | from . import product_api_blueprint
3 | from .. import db
4 | from ..models import Product
5 | from flask import jsonify, request
6 |
7 |
8 | @product_api_blueprint.route('/api/products', methods=['GET'])
9 | def products():
10 | items = []
11 | for row in Product.query.all():
12 | items.append(row.to_json())
13 |
14 | response = jsonify({'results': items})
15 | return response
16 |
17 |
18 | @product_api_blueprint.route('/api/product/create', methods=['POST'])
19 | def post_create():
20 | name = request.form['name']
21 | slug = request.form['slug']
22 | image = request.form['image']
23 | price = request.form['price']
24 |
25 | item = Product()
26 | item.name = name
27 | item.slug = slug
28 | item.image = image
29 | item.price = price
30 |
31 | db.session.add(item)
32 | db.session.commit()
33 |
34 | response = jsonify({'message': 'Product added', 'product': item.to_json()})
35 | return response
36 |
37 |
38 | @product_api_blueprint.route('/api/product/', methods=['GET'])
39 | def product(slug):
40 | item = Product.query.filter_by(slug=slug).first()
41 | if item is not None:
42 | response = jsonify({'result': item.to_json()})
43 | else:
44 | response = jsonify({'message': 'Cannot find product'}), 404
45 | return response
46 |
--------------------------------------------------------------------------------
/order-service/application/models.py:
--------------------------------------------------------------------------------
1 | # application/models.py
2 | from . import db
3 | from datetime import datetime
4 |
5 | class Order(db.Model):
6 | id = db.Column(db.Integer, primary_key=True)
7 | user_id = db.Column(db.Integer)
8 | items = db.relationship('OrderItem', backref='orderItem')
9 | is_open = db.Column(db.Boolean, default=True)
10 | date_added = db.Column(db.DateTime, default=datetime.utcnow)
11 | date_updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
12 |
13 | def create(self, user_id):
14 | self.user_id = user_id
15 | self.is_open = True
16 | return self
17 |
18 | def to_json(self):
19 | items = []
20 | for i in self.items:
21 | items.append(i.to_json())
22 |
23 | return {
24 | 'items': items,
25 | 'is_open': self.is_open,
26 | 'user_id': self.user_id
27 | }
28 |
29 |
30 | class OrderItem(db.Model):
31 | id = db.Column(db.Integer, primary_key=True)
32 | order_id = db.Column(db.Integer, db.ForeignKey('order.id'))
33 | product_id = db.Column(db.Integer)
34 | quantity = db.Column(db.Integer, default=1)
35 | date_added = db.Column(db.DateTime, default=datetime.utcnow)
36 | date_updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
37 |
38 | def __init__(self, product_id, quantity):
39 | self.product_id = product_id
40 | self.quantity = quantity
41 |
42 | def to_json(self):
43 | return {
44 | 'product': self.product_id,
45 | 'quantity': self.quantity
46 | }
--------------------------------------------------------------------------------
/frontend/application/frontend/api/OrderClient.py:
--------------------------------------------------------------------------------
1 | # application/frontend/api/OrderClient.py
2 | from flask import session
3 | import requests
4 |
5 |
6 | class OrderClient:
7 | @staticmethod
8 | def get_order():
9 | headers = {
10 | 'Authorization': 'Basic ' + session['user_api_key']
11 | }
12 | url = 'http://corder-service:5003/api/order'
13 | response = requests.request(method="GET", url=url, headers=headers)
14 | order = response.json()
15 | return order
16 |
17 | @staticmethod
18 | def post_add_to_cart(product_id, qty=1):
19 | payload = {
20 | 'product_id': product_id,
21 | 'qty': qty
22 | }
23 | url = 'http://corder-service:5003/api/order/add-item'
24 |
25 | headers = {
26 | 'Authorization': 'Basic ' + session['user_api_key']
27 | }
28 | response = requests.request("POST", url=url, data=payload, headers=headers)
29 | if response:
30 | order = response.json()
31 | return order
32 |
33 | @staticmethod
34 | def post_checkout():
35 | url = 'http://corder-service:5003/api/order/checkout'
36 |
37 | headers = {
38 | 'Authorization': 'Basic ' + session['user_api_key']
39 | }
40 | response = requests.request("POST", url=url, headers=headers)
41 | order = response.json()
42 | return order
43 |
44 | @staticmethod
45 | def get_order_from_session():
46 | default_order = {
47 | 'items': {},
48 | 'total': 0,
49 | }
50 | return session.get('order', default_order)
51 |
--------------------------------------------------------------------------------
/user-service/application/models.py:
--------------------------------------------------------------------------------
1 | # application/models.py
2 | from . import db
3 | from datetime import datetime
4 | from flask_login import UserMixin
5 | from passlib.hash import sha256_crypt
6 |
7 |
8 | class User(UserMixin, db.Model):
9 | id = db.Column(db.Integer, primary_key=True)
10 | username = db.Column(db.String(255), unique=True, nullable=False)
11 | email = db.Column(db.String(255), unique=True, nullable=False)
12 | first_name = db.Column(db.String(255), unique=False, nullable=True)
13 | last_name = db.Column(db.String(255), unique=False, nullable=True)
14 | password = db.Column(db.String(255), unique=False, nullable=False)
15 | is_admin = db.Column(db.Boolean, default=False)
16 | authenticated = db.Column(db.Boolean, default=False)
17 | api_key = db.Column(db.String(255), unique=True, nullable=True)
18 | date_added = db.Column(db.DateTime, default=datetime.utcnow)
19 | date_updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
20 |
21 | def encode_api_key(self):
22 | self.api_key = sha256_crypt.hash(self.username + str(datetime.utcnow))
23 |
24 | def encode_password(self):
25 | self.password = sha256_crypt.hash(self.password)
26 |
27 | def __repr__(self):
28 | return '' % (self.username)
29 |
30 | def to_json(self):
31 | return {
32 | 'first_name': self.first_name,
33 | 'last_name': self.last_name,
34 | 'username': self.username,
35 | 'email': self.email,
36 | 'id': self.id,
37 | 'api_key': self.api_key,
38 | 'is_active': True,
39 | 'is_admin': self.is_admin
40 | }
--------------------------------------------------------------------------------
/frontend/application/frontend/api/UserClient.py:
--------------------------------------------------------------------------------
1 | # application/frontend/api/UserClient.py
2 | import requests
3 | from flask import session, request
4 |
5 |
6 | class UserClient:
7 | @staticmethod
8 | def post_login(form):
9 | api_key = False
10 | payload = {
11 | 'username': form.username.data,
12 | 'password': form.password.data
13 | }
14 | url = 'http://cuser-service:5001/api/user/login'
15 | response = requests.request("POST", url=url, data=payload)
16 | if response:
17 | d = response.json()
18 | print("This is response from user api: " + str(d))
19 | if d['api_key'] is not None:
20 | api_key = d['api_key']
21 | return api_key
22 |
23 | @staticmethod
24 | def get_user():
25 |
26 | headers = {
27 | 'Authorization': 'Basic ' + session['user_api_key']
28 | }
29 | url = 'http://cuser-service:5001/api/user'
30 | response = requests.request(method="GET", url=url, headers=headers)
31 | user = response.json()
32 | return user
33 |
34 | @staticmethod
35 | def post_user_create(form):
36 | user = False
37 | payload = {
38 | 'email': form.email.data,
39 | 'password': form.password.data,
40 | 'first_name': form.first_name.data,
41 | 'last_name': form.last_name.data,
42 | 'username': form.username.data
43 | }
44 | url = 'http://cuser-service:5001/api/user/create'
45 | response = requests.request("POST", url=url, data=payload)
46 | if response:
47 | user = response.json()
48 | return user
49 |
50 | @staticmethod
51 | def does_exist(username):
52 | url = 'http://cuser-service:5001/api/user/' + username + '/exists'
53 | response = requests.request("GET", url=url)
54 | return response.status_code == 200
55 |
56 |
--------------------------------------------------------------------------------
/frontend/docker-compose.deploy.yml:
--------------------------------------------------------------------------------
1 | # docker-compose.deploy.yml
2 | version: '3.8'
3 |
4 | volumes:
5 | userdb_vol:
6 | productdb_vol:
7 | orderdb_vol:
8 |
9 | networks:
10 | micro_network:
11 | external:
12 | name: micro_network
13 |
14 | services:
15 | user-api:
16 | container_name: cuser-service
17 | build:
18 | context: ../user-service
19 | ports:
20 | - "5001:5001"
21 | depends_on:
22 | - user-db
23 | networks:
24 | - micro_network
25 | restart: always
26 |
27 | user-db:
28 | container_name: cuser_dbase
29 | image: mysql:8
30 | ports:
31 | - "32000:3306"
32 | environment:
33 | MYSQL_ROOT_PASSWORD: pfm_dc_2020
34 | MYSQL_DATABASE: user
35 | MYSQL_USER: cloudacademy
36 | MYSQL_PASSWORD: pfm_2020
37 | networks:
38 | - micro_network
39 | volumes:
40 | - userdb_vol:/var/lib/mysql
41 |
42 | product-api:
43 | container_name: cproduct-service
44 | build:
45 | context: ../product-service
46 | ports:
47 | - "5002:5002"
48 | depends_on:
49 | - product-db
50 | networks:
51 | - micro_network
52 | restart: always
53 |
54 | product-db:
55 | container_name: cproduct_dbase
56 | image: mysql:8
57 | ports:
58 | - "32001:3306"
59 | environment:
60 | MYSQL_ROOT_PASSWORD: pfm_dc_2020
61 | MYSQL_DATABASE: product
62 | MYSQL_USER: cloudacademy
63 | MYSQL_PASSWORD: pfm_2020
64 | networks:
65 | - micro_network
66 | volumes:
67 | - productdb_vol:/var/lib/mysql
68 |
69 | order-api:
70 | container_name: corder-service
71 | build:
72 | context: ../order-service
73 | ports:
74 | - "5003:5003"
75 | depends_on:
76 | - order-db
77 | networks:
78 | - micro_network
79 | restart: always
80 |
81 | order-db:
82 | container_name: corder_dbase
83 | image: mysql:8
84 | ports:
85 | - "32002:3306"
86 | environment:
87 | MYSQL_ROOT_PASSWORD: pfm_dc_2020
88 | MYSQL_DATABASE: order
89 | MYSQL_USER: cloudacademy
90 | MYSQL_PASSWORD: pfm_2020
91 | networks:
92 | - micro_network
93 | volumes:
94 | - orderdb_vol:/var/lib/mysql
95 |
96 | frontend-app:
97 | container_name: cfrontend-app
98 | build:
99 | context: .
100 | ports:
101 | - "5000:5000"
102 | networks:
103 | - micro_network
104 | restart: always
--------------------------------------------------------------------------------
/order-service/application/order_api/routes.py:
--------------------------------------------------------------------------------
1 | # application/order_api/routes.py
2 | from flask import jsonify, request, make_response
3 | from . import order_api_blueprint
4 | from .. import db
5 | from ..models import Order, OrderItem
6 | from .api.UserClient import UserClient
7 |
8 |
9 | @order_api_blueprint.route('/api/orders', methods=['GET'])
10 | def orders():
11 | items = []
12 | for row in Order.query.all():
13 | items.append(row.to_json())
14 |
15 | response = jsonify(items)
16 | return response
17 |
18 |
19 | @order_api_blueprint.route('/api/order/add-item', methods=['POST'])
20 | def order_add_item():
21 | api_key = request.headers.get('Authorization')
22 | response = UserClient.get_user(api_key)
23 |
24 | if not response:
25 | return make_response(jsonify({'message': 'Not logged in'}), 401)
26 |
27 | user = response['result']
28 | p_id = int(request.form['product_id'])
29 | qty = int(request.form['qty'])
30 | u_id = int(user['id'])
31 |
32 | known_order = Order.query.filter_by(user_id=u_id, is_open=1).first()
33 |
34 | if known_order is None:
35 | known_order = Order()
36 | known_order.is_open = True
37 | known_order.user_id = u_id
38 |
39 | order_item = OrderItem(p_id, qty)
40 | known_order.items.append(order_item)
41 | else:
42 | found = False
43 |
44 | for item in known_order.items:
45 | if item.product_id == p_id:
46 | found = True
47 | item.quantity += qty
48 |
49 | if found is False:
50 | order_item = OrderItem(p_id, qty)
51 | known_order.items.append(order_item)
52 |
53 | db.session.add(known_order)
54 | db.session.commit()
55 | response = jsonify({'result': known_order.to_json()})
56 | return response
57 |
58 |
59 | @order_api_blueprint.route('/api/order', methods=['GET'])
60 | def order():
61 | api_key = request.headers.get('Authorization')
62 |
63 | response = UserClient.get_user(api_key)
64 |
65 | if not response:
66 | return make_response(jsonify({'message': 'Not logged in'}), 401)
67 |
68 | user = response['result']
69 | open_order = Order.query.filter_by(user_id=user['id'], is_open=1).first()
70 |
71 | if open_order is None:
72 | response = jsonify({'message': 'No order found'})
73 | else:
74 | response = jsonify({'result': open_order.to_json()})
75 | return response
76 |
77 |
78 | @order_api_blueprint.route('/api/order/checkout', methods=['POST'])
79 | def checkout():
80 | api_key = request.headers.get('Authorization')
81 |
82 | response = UserClient.get_user(api_key)
83 |
84 | if not response:
85 | return make_response(jsonify({'message': 'Not logged in'}), 401)
86 |
87 | user = response['result']
88 |
89 | order_model = Order.query.filter_by(user_id=user['id'], is_open=1).first()
90 | order_model.is_open = 0
91 |
92 | db.session.add(order_model)
93 | db.session.commit()
94 |
95 | response = jsonify({'result': order_model.to_json()})
96 | return response
97 |
--------------------------------------------------------------------------------
/user-service/application/user_api/routes.py:
--------------------------------------------------------------------------------
1 | # application/user_api/routes.py
2 | from . import user_api_blueprint
3 | from .. import db, login_manager
4 | from ..models import User
5 | from flask import make_response, request, jsonify
6 | from flask_login import current_user, login_user, logout_user, login_required
7 |
8 | from passlib.hash import sha256_crypt
9 |
10 |
11 | @login_manager.user_loader
12 | def load_user(user_id):
13 | return User.query.filter_by(id=user_id).first()
14 |
15 |
16 | @login_manager.request_loader
17 | def load_user_from_request(request):
18 | api_key = request.headers.get('Authorization')
19 | if api_key:
20 | api_key = api_key.replace('Basic ', '', 1)
21 | user = User.query.filter_by(api_key=api_key).first()
22 | if user:
23 | return user
24 | return None
25 |
26 |
27 | @user_api_blueprint.route('/api/users', methods=['GET'])
28 | def get_users():
29 | data = []
30 | for row in User.query.all():
31 | data.append(row.to_json())
32 |
33 | response = jsonify(data)
34 | return response
35 |
36 | @user_api_blueprint.route('/api/user/create', methods=['POST'])
37 | def post_register():
38 | first_name = request.form['first_name']
39 | last_name = request.form['last_name']
40 | email = request.form['email']
41 | username = request.form['username']
42 |
43 | password = sha256_crypt.hash((str(request.form['password'])))
44 |
45 | user = User()
46 | user.email = email
47 | user.first_name = first_name
48 | user.last_name = last_name
49 | user.password = password
50 | user.username = username
51 | user.authenticated = True
52 |
53 | db.session.add(user)
54 | db.session.commit()
55 |
56 | response = jsonify({'message': 'User added', 'result': user.to_json()})
57 |
58 | return response
59 |
60 |
61 | @user_api_blueprint.route('/api/user/login', methods=['POST'])
62 | def post_login():
63 | username = request.form['username']
64 | user = User.query.filter_by(username=username).first()
65 | if user:
66 | if sha256_crypt.verify(str(request.form['password']), user.password):
67 | user.encode_api_key()
68 | db.session.commit()
69 | login_user(user)
70 |
71 | return make_response(jsonify({'message': 'Logged in', 'api_key': user.api_key}))
72 |
73 | return make_response(jsonify({'message': 'Not logged in'}), 401)
74 |
75 |
76 | @user_api_blueprint.route('/api/user/logout', methods=['POST'])
77 | def post_logout():
78 | if current_user.is_authenticated:
79 | logout_user()
80 | return make_response(jsonify({'message': 'You are logged out'}))
81 | return make_response(jsonify({'message': 'You are not logged in'}))
82 |
83 |
84 | @user_api_blueprint.route('/api/user//exists', methods=['GET'])
85 | def get_username(username):
86 | item = User.query.filter_by(username=username).first()
87 | if item is not None:
88 | response = jsonify({'result': True})
89 | else:
90 | response = jsonify({'message': 'Cannot find username'}), 404
91 | return response
92 |
93 |
94 | @login_required
95 | @user_api_blueprint.route('/api/user', methods=['GET'])
96 | def get_user():
97 | if current_user.is_authenticated:
98 | return make_response(jsonify({'result': current_user.to_json()}))
99 |
100 | return make_response(jsonify({'message': 'Not logged in'})), 401
--------------------------------------------------------------------------------
/frontend/application/frontend/views.py:
--------------------------------------------------------------------------------
1 | # application/frontend/views.py
2 | import requests
3 | from . import forms
4 | from . import frontend_blueprint
5 | from .. import login_manager
6 | from .api.UserClient import UserClient
7 | from .api.ProductClient import ProductClient
8 | from .api.OrderClient import OrderClient
9 | from flask import render_template, session, redirect, url_for, flash, request
10 |
11 | from flask_login import current_user
12 |
13 |
14 | @login_manager.user_loader
15 | def load_user(user_id):
16 | return None
17 |
18 |
19 | @frontend_blueprint.route('/', methods=['GET'])
20 | def home():
21 | if current_user.is_authenticated:
22 | session['order'] = OrderClient.get_order_from_session()
23 |
24 | try:
25 | products = ProductClient.get_products()
26 | except requests.exceptions.ConnectionError:
27 | products = {
28 | 'results': []
29 | }
30 |
31 | return render_template('home/index.html', products=products)
32 |
33 |
34 | @frontend_blueprint.route('/register', methods=['GET', 'POST'])
35 | def register():
36 | form = forms.RegistrationForm(request.form)
37 | if request.method == "POST":
38 | if form.validate_on_submit():
39 | username = form.username.data
40 |
41 | # Search for existing user
42 | user = UserClient.does_exist(username)
43 | if user:
44 | # Existing user found
45 | flash('Please try another username', 'error')
46 | return render_template('register/index.html', form=form)
47 | else:
48 | # Attempt to create new user
49 | user = UserClient.post_user_create(form)
50 | if user:
51 | flash('Thanks for registering, please login', 'success')
52 | return redirect(url_for('frontend.login'))
53 |
54 | else:
55 | flash('Errors found', 'error')
56 |
57 | return render_template('register/index.html', form=form)
58 |
59 |
60 | @frontend_blueprint.route('/login', methods=['GET', 'POST'])
61 | def login():
62 | if current_user.is_authenticated:
63 | return redirect(url_for('frontend.home'))
64 | form = forms.LoginForm()
65 | if request.method == "POST":
66 | if form.validate_on_submit():
67 | api_key = UserClient.post_login(form)
68 | if api_key:
69 | session['user_api_key'] = api_key
70 | user = UserClient.get_user()
71 | session['user'] = user['result']
72 |
73 | order = OrderClient.get_order()
74 | if order.get('result', False):
75 | session['order'] = order['result']
76 |
77 | flash('Welcome back, ' + user['result']['username'], 'success')
78 | return redirect(url_for('frontend.home'))
79 | else:
80 | flash('Cannot login', 'error')
81 | else:
82 | flash('Errors found', 'error')
83 | return render_template('login/index.html', form=form)
84 |
85 |
86 | @frontend_blueprint.route('/logout', methods=['GET'])
87 | def logout():
88 | session.clear()
89 | return redirect(url_for('frontend.home'))
90 |
91 |
92 | @frontend_blueprint.route('/product/', methods=['GET', 'POST'])
93 | def product(slug):
94 | response = ProductClient.get_product(slug)
95 | item = response['result']
96 |
97 | form = forms.ItemForm(product_id=item['id'])
98 |
99 | if request.method == "POST":
100 | if 'user' not in session:
101 | flash('Please login', 'error')
102 | return redirect(url_for('frontend.login'))
103 | order = OrderClient.post_add_to_cart(product_id=item['id'], qty=1)
104 | session['order'] = order['result']
105 | flash('Order has been updated', 'success')
106 | return render_template('product/index.html', product=item, form=form)
107 |
108 |
109 | @frontend_blueprint.route('/checkout', methods=['GET'])
110 | def summary():
111 | if 'user' not in session:
112 | flash('Please login', 'error')
113 | return redirect(url_for('frontend.login'))
114 |
115 | if 'order' not in session:
116 | flash('No order found', 'error')
117 | return redirect(url_for('frontend.home'))
118 | order = OrderClient.get_order()
119 |
120 | if len(order['result']['items']) == 0:
121 | flash('No order found', 'error')
122 | return redirect(url_for('frontend.home'))
123 |
124 | OrderClient.post_checkout()
125 |
126 | return redirect(url_for('frontend.thank_you'))
127 |
128 | @frontend_blueprint.route('/order/thank-you', methods=['GET'])
129 | def thank_you():
130 | if 'user' not in session:
131 | flash('Please login', 'error')
132 | return redirect(url_for('frontend.login'))
133 |
134 | if 'order' not in session:
135 | flash('No order found', 'error')
136 | return redirect(url_for('frontend.home'))
137 |
138 | session.pop('order', None)
139 | flash('Thank you for your order', 'success')
140 |
141 | return render_template('order/thankyou.html')
--------------------------------------------------------------------------------