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 diff --git a/app/__init__.py b/app/__init__.py index eed71a8..7fe3a92 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,11 +1,26 @@ import os +import logging.config from flask import Flask +from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy 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() @@ -13,6 +28,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 @@ -31,12 +47,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 diff --git a/app/routes/v1/application.py b/app/routes/v1/application.py index f1f60dc..32a82fb 100644 --- a/app/routes/v1/application.py +++ b/app/routes/v1/application.py @@ -15,19 +15,27 @@ """ +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, GenreSchema 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_genres_schema = GenreSchema(many=True, strict=True) @application.route('/', methods=['GET']) -def get_applications(): +@cross_origin(supports_credentials=True) +def get_application_list(): """Get a list of all the Applications""" applications = Application.query.all() result = applications_schema.dump(applications).data @@ -35,6 +43,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) @@ -42,7 +51,26 @@ 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('/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(): """Create a new Application""" @@ -79,3 +107,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 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""" 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 92% rename from config.py rename to config/config.py index 049a776..dd7f2c2 100644 --- a/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!' 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 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