2017-12-16 21:00:46 +01:00
|
|
|
import logging
|
2018-04-10 21:53:18 +02:00
|
|
|
import os
|
2022-03-23 18:29:49 +01:00
|
|
|
import re
|
2022-02-13 14:31:59 +01:00
|
|
|
import shutil
|
2020-05-17 16:42:44 +02:00
|
|
|
from importlib import import_module, reload
|
2021-01-02 19:28:03 +01:00
|
|
|
from typing import Any
|
2017-12-16 21:00:46 +01:00
|
|
|
|
2022-02-13 10:21:22 +01:00
|
|
|
from flask import (
|
|
|
|
Flask,
|
|
|
|
Response,
|
|
|
|
render_template,
|
|
|
|
send_file,
|
|
|
|
send_from_directory,
|
|
|
|
)
|
2017-12-16 21:00:46 +01:00
|
|
|
from flask_bcrypt import Bcrypt
|
2020-07-14 22:03:56 +02:00
|
|
|
from flask_dramatiq import Dramatiq
|
2018-01-20 19:12:34 +01:00
|
|
|
from flask_migrate import Migrate
|
2017-12-16 21:00:46 +01:00
|
|
|
from flask_sqlalchemy import SQLAlchemy
|
2022-03-23 18:29:49 +01:00
|
|
|
from sqlalchemy.exc import ProgrammingError
|
2017-12-16 21:00:46 +01:00
|
|
|
|
2022-02-16 13:35:38 +01:00
|
|
|
from fittrackee.emails.email import EmailService
|
2020-05-17 16:42:44 +02:00
|
|
|
|
2022-03-27 15:11:12 +02:00
|
|
|
VERSION = __version__ = '0.6.0'
|
2017-12-16 21:00:46 +01:00
|
|
|
db = SQLAlchemy()
|
|
|
|
bcrypt = Bcrypt()
|
2018-01-20 19:12:34 +01:00
|
|
|
migrate = Migrate()
|
2022-02-16 13:35:38 +01:00
|
|
|
email_service = EmailService()
|
2020-07-14 22:03:56 +02:00
|
|
|
dramatiq = Dramatiq()
|
2020-09-18 08:49:48 +02:00
|
|
|
log_file = os.getenv('APP_LOG')
|
|
|
|
logging.basicConfig(
|
|
|
|
filename=log_file,
|
|
|
|
format='%(asctime)s - %(name)s - %(levelname)s - ' '%(message)s',
|
|
|
|
datefmt='%Y/%m/%d %H:%M:%S',
|
|
|
|
)
|
2020-09-16 15:41:02 +02:00
|
|
|
appLog = logging.getLogger('fittrackee')
|
2017-12-16 21:00:46 +01:00
|
|
|
|
2018-04-09 22:09:58 +02:00
|
|
|
|
2021-01-02 19:28:03 +01:00
|
|
|
def create_app() -> Flask:
|
2018-04-09 22:09:58 +02:00
|
|
|
# instantiate the app
|
2020-09-16 15:41:02 +02:00
|
|
|
app = Flask(__name__, static_folder='dist/static', template_folder='dist')
|
2018-04-09 22:09:58 +02:00
|
|
|
|
|
|
|
# set config
|
|
|
|
with app.app_context():
|
2020-09-16 18:58:11 +02:00
|
|
|
app_settings = os.getenv(
|
|
|
|
'APP_SETTINGS', 'fittrackee.config.ProductionConfig'
|
|
|
|
)
|
2020-09-16 15:41:02 +02:00
|
|
|
if app_settings == 'fittrackee.config.TestingConfig':
|
2020-05-17 16:42:44 +02:00
|
|
|
# reload config on tests
|
2020-09-16 15:41:02 +02:00
|
|
|
config = import_module('fittrackee.config')
|
2020-05-17 16:42:44 +02:00
|
|
|
reload(config)
|
2018-04-10 21:53:18 +02:00
|
|
|
app.config.from_object(app_settings)
|
2018-04-09 22:09:58 +02:00
|
|
|
|
|
|
|
# set up extensions
|
|
|
|
db.init_app(app)
|
|
|
|
bcrypt.init_app(app)
|
|
|
|
migrate.init_app(app, db)
|
2020-07-14 22:03:56 +02:00
|
|
|
dramatiq.init_app(app)
|
2018-04-09 22:09:58 +02:00
|
|
|
|
2020-05-17 16:42:44 +02:00
|
|
|
# set up email
|
|
|
|
email_service.init_email(app)
|
|
|
|
|
2019-11-13 18:40:01 +01:00
|
|
|
# get configuration from database
|
2022-02-12 14:12:47 +01:00
|
|
|
from .application.utils import (
|
|
|
|
get_or_init_config,
|
|
|
|
update_app_config_from_database,
|
|
|
|
)
|
2019-11-13 18:40:01 +01:00
|
|
|
|
|
|
|
with app.app_context():
|
|
|
|
# Note: check if "app_config" table exist to avoid errors when
|
|
|
|
# dropping tables on dev environments
|
2022-03-23 18:29:49 +01:00
|
|
|
try:
|
|
|
|
if db.engine.dialect.has_table(db.engine.connect(), 'app_config'):
|
|
|
|
db_app_config = get_or_init_config()
|
|
|
|
update_app_config_from_database(app, db_app_config)
|
|
|
|
except ProgrammingError as e:
|
|
|
|
# avoid error on AppConfig migration
|
|
|
|
if re.match(
|
|
|
|
r'psycopg2.errors.UndefinedColumn(.*)app_config.', str(e)
|
|
|
|
):
|
|
|
|
pass
|
2019-11-13 18:40:01 +01:00
|
|
|
|
2019-11-13 19:14:13 +01:00
|
|
|
from .application.app_config import config_blueprint # noqa
|
2020-07-14 16:23:48 +02:00
|
|
|
from .users.auth import auth_blueprint # noqa
|
|
|
|
from .users.users import users_blueprint # noqa
|
2021-01-10 11:16:43 +01:00
|
|
|
from .workouts.records import records_blueprint # noqa
|
|
|
|
from .workouts.sports import sports_blueprint # noqa
|
|
|
|
from .workouts.stats import stats_blueprint # noqa
|
|
|
|
from .workouts.workouts import workouts_blueprint # noqa
|
2018-04-09 22:09:58 +02:00
|
|
|
|
|
|
|
app.register_blueprint(auth_blueprint, url_prefix='/api')
|
2021-01-10 11:16:43 +01:00
|
|
|
app.register_blueprint(config_blueprint, url_prefix='/api')
|
2018-05-11 23:12:25 +02:00
|
|
|
app.register_blueprint(records_blueprint, url_prefix='/api')
|
2018-05-01 16:17:12 +02:00
|
|
|
app.register_blueprint(sports_blueprint, url_prefix='/api')
|
2018-06-06 00:22:24 +02:00
|
|
|
app.register_blueprint(stats_blueprint, url_prefix='/api')
|
2021-01-10 11:16:43 +01:00
|
|
|
app.register_blueprint(users_blueprint, url_prefix='/api')
|
|
|
|
app.register_blueprint(workouts_blueprint, url_prefix='/api')
|
2018-04-09 22:09:58 +02:00
|
|
|
|
|
|
|
if app.debug:
|
|
|
|
logging.getLogger('sqlalchemy').setLevel(logging.WARNING)
|
2019-08-28 13:25:39 +02:00
|
|
|
logging.getLogger('sqlalchemy').handlers = logging.getLogger(
|
|
|
|
'werkzeug'
|
|
|
|
).handlers
|
2018-04-09 22:09:58 +02:00
|
|
|
logging.getLogger('sqlalchemy.orm').setLevel(logging.WARNING)
|
|
|
|
logging.getLogger('flake8').propagate = False
|
|
|
|
appLog.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
# Enable CORS
|
|
|
|
@app.after_request
|
2021-01-02 19:28:03 +01:00
|
|
|
def after_request(response: Response) -> Response:
|
2018-04-09 22:09:58 +02:00
|
|
|
response.headers.add('Access-Control-Allow-Origin', '*')
|
|
|
|
response.headers.add(
|
|
|
|
'Access-Control-Allow-Headers', 'Content-Type,Authorization'
|
|
|
|
)
|
|
|
|
response.headers.add(
|
|
|
|
'Access-Control-Allow-Methods',
|
2019-08-28 13:25:39 +02:00
|
|
|
'GET,PUT,POST,DELETE,PATCH,OPTIONS',
|
2018-04-09 22:09:58 +02:00
|
|
|
)
|
|
|
|
return response
|
|
|
|
|
2020-09-16 15:53:05 +02:00
|
|
|
@app.route('/favicon.ico')
|
2021-01-02 19:28:03 +01:00
|
|
|
def favicon() -> Any:
|
|
|
|
return send_file(
|
|
|
|
os.path.join(app.root_path, 'dist/favicon.ico') # type: ignore
|
|
|
|
)
|
2020-09-16 15:53:05 +02:00
|
|
|
|
2020-09-16 13:48:15 +02:00
|
|
|
@app.route('/', defaults={'path': ''})
|
|
|
|
@app.route('/<path:path>')
|
2021-01-02 19:28:03 +01:00
|
|
|
def catch_all(path: str) -> Any:
|
2020-09-16 15:53:05 +02:00
|
|
|
# workaround to serve images (not in static directory)
|
|
|
|
if path.startswith('img/'):
|
2022-02-13 10:21:22 +01:00
|
|
|
return send_from_directory(
|
|
|
|
directory=os.path.join(
|
2021-01-02 19:28:03 +01:00
|
|
|
app.root_path, # type: ignore
|
|
|
|
'dist',
|
2022-02-13 10:21:22 +01:00
|
|
|
),
|
|
|
|
path=path,
|
2021-01-02 19:28:03 +01:00
|
|
|
)
|
2020-09-16 15:53:05 +02:00
|
|
|
else:
|
|
|
|
return render_template('index.html')
|
2020-09-16 13:48:15 +02:00
|
|
|
|
2022-02-13 14:31:59 +01:00
|
|
|
@app.cli.command('drop-db')
|
|
|
|
def drop_db() -> None:
|
|
|
|
"""Empty database and delete uploaded files for dev environments."""
|
|
|
|
if app_settings == 'fittrackee.config.ProductionConfig':
|
|
|
|
print('This is a production server, aborting!')
|
|
|
|
return
|
|
|
|
db.engine.execute("DROP TABLE IF EXISTS alembic_version;")
|
|
|
|
db.drop_all()
|
|
|
|
db.session.commit()
|
|
|
|
print('Database dropped.')
|
|
|
|
shutil.rmtree(app.config['UPLOAD_FOLDER'], ignore_errors=True)
|
|
|
|
print('Uploaded files deleted.')
|
|
|
|
|
2018-04-09 22:09:58 +02:00
|
|
|
return app
|