From b2dfddd9925e7324cf19346c50bacf7dd0c4964d Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Tue, 16 Apr 2019 23:37:35 +0530 Subject: [PATCH 01/11] chore(core): update endpoints :boom: --- app/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index eed71a8..c36d7bf 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -31,12 +31,12 @@ def create_app(config_name): app.register_blueprint(main_blueprint, url_prefix=root + '/') from app.routes.v1 import application as application_blueprint - app.register_blueprint(application_blueprint, url_prefix=root + '/application') + app.register_blueprint(application_blueprint, url_prefix=root + '/applications') from app.routes.v1 import session as session_blueprint - app.register_blueprint(session_blueprint, url_prefix=root + '/session') + app.register_blueprint(session_blueprint, url_prefix=root + '/sessions') from app.routes.v1 import questionnaire as questionnaire_blueprint - app.register_blueprint(questionnaire_blueprint, url_prefix=root + '/questionnaire') + app.register_blueprint(questionnaire_blueprint, url_prefix=root + '/questionnaires') return app From f24c8641eb694fe580e5986d9471154c3b1accec Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Tue, 16 Apr 2019 23:45:20 +0530 Subject: [PATCH 02/11] chore(deps): add flask-cors dependency :heavy_plus_sign: --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ea66db0..c901e77 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ marshmallow==2.14.0 flask_sqlalchemy==2.3.2 flask_marshmallow==0.8.0 marshmallow-sqlalchemy==0.13.2 -PyMySQL==0.9.3 \ No newline at end of file +PyMySQL==0.9.3 +flask-cors \ No newline at end of file From 5a7f057366cfe76e194f20aea92dcdd5c9b4bba9 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Tue, 16 Apr 2019 23:46:01 +0530 Subject: [PATCH 03/11] chore(core): wrap app with CORS from flask-cors --- app/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index c36d7bf..36b96e4 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,6 +1,7 @@ import os from flask import Flask +from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow from config import CONFIG @@ -13,6 +14,7 @@ def create_app(config_name): app = Flask(__name__) + CORS(app, support_credentials=True) app.config.from_object(CONFIG[config_name]) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # disabling sqlalchemy event system From f40a07575bf64790f6ab0da56abe0282b2868064 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Tue, 16 Apr 2019 23:46:32 +0530 Subject: [PATCH 04/11] feat(core): add cors support :sparkles: --- app/routes/v1/application.py | 4 ++++ app/routes/v1/main.py | 2 ++ app/routes/v1/questionnaire.py | 5 ++++- app/routes/v1/session.py | 4 ++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/routes/v1/application.py b/app/routes/v1/application.py index f1f60dc..8c33bce 100644 --- a/app/routes/v1/application.py +++ b/app/routes/v1/application.py @@ -16,6 +16,7 @@ """ import uuid +from flask_cors import cross_origin from flask import Blueprint, jsonify, request from app.models import Application, ApplicationType, ApplicationSchema, Genre from app import db @@ -27,6 +28,7 @@ @application.route('/', methods=['GET']) +@cross_origin(supports_credentials=True) def get_applications(): """Get a list of all the Applications""" applications = Application.query.all() @@ -35,6 +37,7 @@ def get_applications(): @application.route('/', methods=['GET']) +@cross_origin(supports_credentials=True) def get_application(id): """Get info on an Applications when an id is passed in""" application = Application.query.get(id) @@ -43,6 +46,7 @@ def get_application(id): @application.route('/', methods=['POST']) +@cross_origin(supports_credentials=True) def create_application(): """Create a new Application""" diff --git a/app/routes/v1/main.py b/app/routes/v1/main.py index 75ddba4..ad2edf7 100644 --- a/app/routes/v1/main.py +++ b/app/routes/v1/main.py @@ -1,8 +1,10 @@ from flask import Blueprint, jsonify +from flask_cors import cross_origin main = Blueprint('main', __name__) @main.route('/') +@cross_origin(supports_credentials=True) def index(): return jsonify({"message": "Welcome to CSSI REST API"}) diff --git a/app/routes/v1/questionnaire.py b/app/routes/v1/questionnaire.py index 4acb097..1370997 100644 --- a/app/routes/v1/questionnaire.py +++ b/app/routes/v1/questionnaire.py @@ -15,8 +15,8 @@ """ -import uuid from flask import Blueprint, jsonify, request +from flask_cors import cross_origin from app.models import Questionnaire, ApplicationType, QuestionnaireSchema, Genre from app import db @@ -27,6 +27,7 @@ @questionnaire.route('/', methods=['GET']) +@cross_origin(supports_credentials=True) def get_questionnaire_list(): """Get a list of all the Questionnaire""" questionnaires = Questionnaire.query.all() @@ -35,6 +36,7 @@ def get_questionnaire_list(): @questionnaire.route('/', methods=['GET']) +@cross_origin(supports_credentials=True) def get_questionnaire(id): """Get questionnaire when an id is passed in""" questionnaire = Questionnaire.query.get(id) @@ -43,6 +45,7 @@ def get_questionnaire(id): @questionnaire.route('/', methods=['POST']) +@cross_origin(supports_credentials=True) def create_questionnaire(): """Create a new Questionnaire""" pre = request.json['pre'] diff --git a/app/routes/v1/session.py b/app/routes/v1/session.py index 0930393..e116a12 100644 --- a/app/routes/v1/session.py +++ b/app/routes/v1/session.py @@ -16,6 +16,7 @@ """ from flask import Blueprint, jsonify, request +from flask_cors import cross_origin from app.models import Session, SessionSchema, Application, Questionnaire from app import db @@ -26,6 +27,7 @@ @session.route('/', methods=['GET']) +@cross_origin(supports_credentials=True) def get_sessions_list(): """Get a list of all the sessions""" sessions = Session.query.all() @@ -34,6 +36,7 @@ def get_sessions_list(): @session.route('/', methods=['GET']) +@cross_origin(supports_credentials=True) def get_session(id): """Get info on a session when an id is passed in""" session = Session.query.get(id) @@ -42,6 +45,7 @@ def get_session(id): @session.route('/', methods=['POST']) +@cross_origin(supports_credentials=True) def create_session(): """Create a new Session""" From d5993257e75b3f0abaabc6cd9eaf8bffde6cf345 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Wed, 17 Apr 2019 02:16:30 +0530 Subject: [PATCH 05/11] chore(logs): create logging config file :wrench: --- config/logging.conf | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 config/logging.conf diff --git a/config/logging.conf b/config/logging.conf new file mode 100644 index 0000000..9e8042c --- /dev/null +++ b/config/logging.conf @@ -0,0 +1,33 @@ +[loggers] +keys=root,cssRestApi + +[handlers] +keys=consoleHandler, fileHandler + +[formatters] +keys=formatter + +[logger_root] +level=DEBUG +handlers=consoleHandler + +[logger_cssRestApi] +level=DEBUG +handlers=fileHandler +qualname=CSSI_REST_API +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=DEBUG +formatter=formatter +args=(sys.stdout,) + +[handler_fileHandler] +class=logging.handlers.RotatingFileHandler +level=DEBUG +formatter=formatter +args=('logs/api.log','a+', 5*1024*1024, 2, None, 0) + +[formatter_formatter] +format=%(asctime)s - %(name)s - %(levelname)-8s - [%(module)s.%(filename)s:%(lineno)d] - %(message)s \ No newline at end of file From 46e6b17c3a88be2037942aa94bea4e5ee7980011 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Wed, 17 Apr 2019 02:26:40 +0530 Subject: [PATCH 06/11] refactor(config): move app config to new folder :truck: --- config/__init__.py | 1 + config.py => config/config.py | 0 2 files changed, 1 insertion(+) create mode 100644 config/__init__.py rename config.py => config/config.py (100%) diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..bfd12ae --- /dev/null +++ b/config/__init__.py @@ -0,0 +1 @@ +from .config import CONFIG # noqa diff --git a/config.py b/config/config.py similarity index 100% rename from config.py rename to config/config.py From 60914b451c29598e5a1e0a32128ebc16edf6779a Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Wed, 17 Apr 2019 02:29:24 +0530 Subject: [PATCH 07/11] chore(vcs): ignore logs from vcs :see_no_evil: --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 505ed8c..340c1a4 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,10 @@ MANIFEST *.manifest *.spec +# api logs +logs/ +*.log + # Installer logs pip-log.txt pip-delete-this-directory.txt From e2d76418fa8d7b30837b254424ff77c18c35d44c Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Wed, 17 Apr 2019 02:30:27 +0530 Subject: [PATCH 08/11] feat(core): add logging support :sparkles: --- app/__init__.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 36b96e4..7fe3a92 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,5 @@ import os +import logging.config from flask import Flask from flask_cors import CORS @@ -6,7 +7,20 @@ from flask_marshmallow import Marshmallow from config import CONFIG -basedir = os.path.abspath(os.path.dirname(__file__)) +BASE_DIR = os.path.abspath(os.path.dirname(__file__)) +LOG_FILES_PATH = os.path.split(BASE_DIR)[0] + '/logs' + +# Try to create a log folder +try: + if not os.path.exists(LOG_FILES_PATH): + os.makedirs(LOG_FILES_PATH) +except OSError: + pass + +# load logging config file +logging.config.fileConfig('config/logging.conf', disable_existing_loggers=False) +# init file logger +logger = logging.getLogger('CSSI_REST_API') db = SQLAlchemy() ma = Marshmallow() From 5f7b4a8bb54678e4a70ee0e2cb750b5619c0e915 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Wed, 17 Apr 2019 02:37:13 +0530 Subject: [PATCH 09/11] chore(config): add logs to config fire :loud_sound: --- config/config.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config/config.py b/config/config.py index 049a776..dd7f2c2 100644 --- a/config/config.py +++ b/config/config.py @@ -31,16 +31,17 @@ Brion Mario """ - +import logging import os -ENVIRONMENT_FILE_NAME = '.env' +logger = logging.getLogger('CSSI_REST_API') +ENVIRONMENT_FILE_NAME = '.env' BASE_DIR = os.path.abspath(os.path.dirname(__file__)) # Read the environment file if os.path.exists(ENVIRONMENT_FILE_NAME): - print('Importing environment from environment file') + logger.info('Importing environment from environment file') for line in open(ENVIRONMENT_FILE_NAME): var = line.strip().split('=') if len(var) == 2: @@ -69,7 +70,7 @@ class Config: SECRET_KEY = os.environ.get('SECRET_KEY') else: SECRET_KEY = 'SECRET_KEY_ENV_VAR_NOT_SET' - print('SECRET KEY ENV VAR NOT SET! SHOULD NOT SEE IN PRODUCTION') + logger.error('Secret key is not set in the environment') SQLALCHEMY_COMMIT_ON_TEARDOWN = True @staticmethod @@ -95,8 +96,7 @@ class DevelopmentConfig(Config): @classmethod def init_app(cls, app): - print('THIS APP IS IN DEBUG MODE. \ - YOU SHOULD NOT SEE THIS IN PRODUCTION.') + logger.info('The app is running in debug mode.') class TestingConfig(Config): @@ -117,8 +117,7 @@ class TestingConfig(Config): @classmethod def init_app(cls, app): - print('THIS APP IS IN TESTING MODE. \ - YOU SHOULD NOT SEE THIS IN PRODUCTION.') + logger.info('The app is running in testing mode.') class ProductionConfig(Config): @@ -139,6 +138,7 @@ class ProductionConfig(Config): @classmethod def init_app(cls, app): + logger.info('The app is running in production mode.') Config.init_app(app) assert os.environ.get('SECRET_KEY'), 'SECRET_KEY IS NOT SET!' From adff93ae474b4691b2af22ce912a9b8da836a78a Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Wed, 17 Apr 2019 02:59:36 +0530 Subject: [PATCH 10/11] chore(routes): add route to retrieve application types --- app/routes/v1/application.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/app/routes/v1/application.py b/app/routes/v1/application.py index 8c33bce..a47fd56 100644 --- a/app/routes/v1/application.py +++ b/app/routes/v1/application.py @@ -15,21 +15,26 @@ """ +import logging import uuid +import traceback from flask_cors import cross_origin from flask import Blueprint, jsonify, request -from app.models import Application, ApplicationType, ApplicationSchema, Genre +from app.models import Application, ApplicationType,ApplicationTypeSchema, ApplicationSchema, Genre from app import db +logger = logging.getLogger('CSSI_REST_API') + application = Blueprint('application', __name__) application_schema = ApplicationSchema(strict=True) applications_schema = ApplicationSchema(many=True, strict=True) +application_types_schema = ApplicationTypeSchema(many=True, strict=True) @application.route('/', methods=['GET']) @cross_origin(supports_credentials=True) -def get_applications(): +def get_application_list(): """Get a list of all the Applications""" applications = Application.query.all() result = applications_schema.dump(applications).data @@ -45,6 +50,15 @@ def get_application(id): return jsonify({'status': 'success', 'message': None, 'data': result}), 200 +@application.route('/types', methods=['GET']) +@cross_origin(supports_credentials=True) +def get_application_types(): + """Get all the available application types""" + application_types = ApplicationType.query.all() + result = application_types_schema.dump(application_types).data + return jsonify({'status': 'success', 'message': None, 'data': result}), 200 + + @application.route('/', methods=['POST']) @cross_origin(supports_credentials=True) def create_application(): @@ -83,3 +97,18 @@ def create_application(): result = application_schema.dump(new_application).data return jsonify({'status': 'success', 'message': 'Created new application {}.'.format(name), 'data': result}), 201 + + +@application.after_request +def after_request(response): + """Logs a debug message on every successful request.""" + logger.debug('%s %s %s %s %s', request.remote_addr, request.method, request.scheme, request.full_path, response.status) + return response + + +@application.errorhandler(Exception) +def exceptions(e): + """Logs an error message and stacktrace if a request ends in error.""" + tb = traceback.format_exc() + logger.error('%s %s %s %s 5xx INTERNAL SERVER ERROR\n%s', request.remote_addr, request.method, request.scheme, request.full_path, tb) + return e.status_code From 7b24b897cdb683fe3e9bd27b9563accf61bcff92 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Wed, 17 Apr 2019 03:02:55 +0530 Subject: [PATCH 11/11] chore(routes): add route to retrieve application genres --- app/routes/v1/application.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/routes/v1/application.py b/app/routes/v1/application.py index a47fd56..32a82fb 100644 --- a/app/routes/v1/application.py +++ b/app/routes/v1/application.py @@ -20,7 +20,7 @@ import traceback from flask_cors import cross_origin from flask import Blueprint, jsonify, request -from app.models import Application, ApplicationType,ApplicationTypeSchema, ApplicationSchema, Genre +from app.models import Application, ApplicationType,ApplicationTypeSchema, ApplicationSchema, Genre, GenreSchema from app import db logger = logging.getLogger('CSSI_REST_API') @@ -30,6 +30,7 @@ application_schema = ApplicationSchema(strict=True) applications_schema = ApplicationSchema(many=True, strict=True) application_types_schema = ApplicationTypeSchema(many=True, strict=True) +application_genres_schema = GenreSchema(many=True, strict=True) @application.route('/', methods=['GET']) @@ -59,6 +60,15 @@ def get_application_types(): return jsonify({'status': 'success', 'message': None, 'data': result}), 200 +@application.route('/genres', methods=['GET']) +@cross_origin(supports_credentials=True) +def get_application_genres(): + """Get all the available application genres""" + application_genres = Genre.query.all() + result = application_genres_schema.dump(application_genres).data + return jsonify({'status': 'success', 'message': None, 'data': result}), 200 + + @application.route('/', methods=['POST']) @cross_origin(supports_credentials=True) def create_application():