Merge pull request #150 from SamR1/improve-app-init

Improve application initialization
This commit is contained in:
Sam 2022-02-13 13:38:55 +01:00 committed by GitHub
commit f3e0f5e4b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 296 additions and 264 deletions

View File

@ -93,7 +93,6 @@ firefox:
- poetry config virtualenvs.create false - poetry config virtualenvs.create false
- poetry install --no-interaction --quiet - poetry install --no-interaction --quiet
- flask db upgrade --directory fittrackee/migrations - flask db upgrade --directory fittrackee/migrations
- flask init-data
- setsid nohup flask run --with-threads -h 0.0.0.0 -p 5000 >> nohup.out 2>&1 & - setsid nohup flask run --with-threads -h 0.0.0.0 -p 5000 >> nohup.out 2>&1 &
- export TEST_APP_URL=http://$(hostname --ip-address):5000 - export TEST_APP_URL=http://$(hostname --ip-address):5000
- sleep 5 - sleep 5

View File

@ -59,6 +59,9 @@ docker-serve-client:
docker-compose -f docker-compose-dev.yml up -d fittrackee_client docker-compose -f docker-compose-dev.yml up -d fittrackee_client
docker-compose -f docker-compose-dev.yml exec fittrackee_client yarn serve docker-compose -f docker-compose-dev.yml exec fittrackee_client yarn serve
docker-set-admin:
docker-compose -f docker-compose-dev.yml exec fittrackee docker/set-admin.sh $(USERNAME)
docker-shell: docker-shell:
docker-compose -f docker-compose-dev.yml exec fittrackee docker/shell.sh docker-compose -f docker-compose-dev.yml exec fittrackee docker/shell.sh
@ -85,15 +88,10 @@ html:
install-db: install-db:
psql -U postgres -f db/create.sql psql -U postgres -f db/create.sql
$(FLASK) db upgrade --directory $(MIGRATIONS) $(FLASK) db upgrade --directory $(MIGRATIONS)
$(FLASK) init-data
init-app-config:
$(FLASK) init-app-config
init-db: init-db:
$(FLASK) drop-db $(FLASK) drop-db
$(FLASK) db upgrade --directory $(MIGRATIONS) $(FLASK) db upgrade --directory $(MIGRATIONS)
$(FLASK) init-data
install: install-client install-python install: install-client install-python
@ -133,9 +131,6 @@ mail:
migrate-db: migrate-db:
$(FLASK) db migrate --directory $(MIGRATIONS) $(FLASK) db migrate --directory $(MIGRATIONS)
recalculate:
$(FLASK) recalculate
revision: revision:
$(FLASK) db revision --directory $(MIGRATIONS) --message $(MIGRATION_MESSAGE) $(FLASK) db revision --directory $(MIGRATIONS) --message $(MIGRATION_MESSAGE)
@ -166,6 +161,9 @@ serve-python-dev:
echo 'Running on https://$(HOST):$(PORT)' echo 'Running on https://$(HOST):$(PORT)'
$(FLASK) run --with-threads -h $(HOST) -p $(PORT) --cert=adhoc $(FLASK) run --with-threads -h $(HOST) -p $(PORT) --cert=adhoc
set-admin:
$(FLASK) set-admin $(USERNAME)
test-e2e: init-db test-e2e: init-db
$(PYTEST) e2e --driver firefox $(PYTEST_ARGS) $(PYTEST) e2e --driver firefox $(PYTEST_ARGS)

View File

@ -6,4 +6,3 @@ source .env.docker
flask drop-db flask drop-db
flask db upgrade --directory fittrackee/migrations flask db upgrade --directory fittrackee/migrations
flask init-data

7
docker/set-admin.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
set -e
cd /usr/src/app
source .env.docker
flask set-admin $1

View File

@ -269,19 +269,12 @@ For instance, copy and update ``.env`` file from ``.env.example`` and source the
$ nano .env $ nano .env
$ source .env $ source .env
- Initialize database schema
- Upgrade database schema
.. code-block:: bash .. code-block:: bash
$ fittrackee_upgrade_db $ fittrackee_upgrade_db
- Initialize database
.. code-block:: bash
$ fittrackee_init_data
- Start the application - Start the application
.. code-block:: bash .. code-block:: bash
@ -297,6 +290,14 @@ For instance, copy and update ``.env`` file from ``.env.example`` and source the
.. note:: .. note::
| To start application and workers with **systemd** service, see `Deployment <installation.html#deployment>`__ | To start application and workers with **systemd** service, see `Deployment <installation.html#deployment>`__
- Open http://localhost:3000 and register
- To set admin rights to the newly created account, use the following command:
.. code:: bash
$ fittrackee_set_admin <username>
From sources From sources
^^^^^^^^^^^^ ^^^^^^^^^^^^
@ -349,8 +350,13 @@ Dev environment
$ make run-workers $ make run-workers
Open http://localhost:3000 and log in (the email is ``admin@example.com`` - Open http://localhost:3000 and register
and the password ``mpwoadmin``) or register
- To set admin rights to the newly created account, use the following command:
.. code:: bash
$ make set-admin USERNAME=<username>
Production environment Production environment
@ -390,9 +396,13 @@ Production environment
$ make run $ make run
Open http://localhost:5000, log in as admin (the email is - Open http://localhost:5000 and register
``admin@example.com`` and the password ``mpwoadmin``) and change the
password - To set admin rights to the newly created account, use the following command:
.. code:: bash
$ make set-admin USERNAME=<username>
Upgrade Upgrade
@ -642,10 +652,16 @@ installing **FitTrackee** from **sources**.
$ cd FitTrackee $ cd FitTrackee
$ make docker-build docker-run docker-init $ make docker-build docker-run docker-init
Open http://localhost:5000, log in as admin (the email is `admin@example.com` and the password `mpwoadmin`) or register. Open http://localhost:5000 and register.
Open http://localhost:8025 to access `MailHog interface <https://github.com/mailhog/MailHog>`_ (email testing tool) Open http://localhost:8025 to access `MailHog interface <https://github.com/mailhog/MailHog>`_ (email testing tool)
- To set admin rights to the newly created account, use the following command:
.. code:: bash
$ make docker-set-admin USERNAME=<username>
- To stop **Fittrackee**: - To stop **Fittrackee**:
.. code-block:: bash .. code-block:: bash
@ -683,8 +699,7 @@ Development
$ make docker-serve-client $ make docker-serve-client
Open http://localhost:3000 and log in (the email is ``admin@example.com`` Open http://localhost:3000
and the password ``mpwoadmin``) or register
.. note:: .. note::
Some environment variables need to be updated like `UI_URL` Some environment variables need to be updated like `UI_URL`

View File

@ -527,18 +527,12 @@ $ <span class="nb">source</span> .env
</pre></div> </pre></div>
</div> </div>
<ul class="simple"> <ul class="simple">
<li><p>Upgrade database schema</p></li> <li><p>Initialize database schema</p></li>
</ul> </ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ fittrackee_upgrade_db <div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ fittrackee_upgrade_db
</pre></div> </pre></div>
</div> </div>
<ul class="simple"> <ul class="simple">
<li><p>Initialize database</p></li>
</ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ fittrackee_init_data
</pre></div>
</div>
<ul class="simple">
<li><p>Start the application</p></li> <li><p>Start the application</p></li>
</ul> </ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ fittrackee <div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ fittrackee
@ -556,6 +550,13 @@ $ <span class="nb">source</span> .env
<div class="line">To start application and workers with <strong>systemd</strong> service, see <a class="reference external" href="installation.html#deployment">Deployment</a></div> <div class="line">To start application and workers with <strong>systemd</strong> service, see <a class="reference external" href="installation.html#deployment">Deployment</a></div>
</div> </div>
</div> </div>
<ul class="simple">
<li><p>Open <a class="reference external" href="http://localhost:3000">http://localhost:3000</a> and register</p></li>
<li><p>To set admin rights to the newly created account, use the following command:</p></li>
</ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ fittrackee_set_admin &lt;username&gt;
</pre></div>
</div>
</section> </section>
<section id="from-sources"> <section id="from-sources">
<h3>From sources<a class="headerlink" href="#from-sources" title="Permalink to this headline"></a></h3> <h3>From sources<a class="headerlink" href="#from-sources" title="Permalink to this headline"></a></h3>
@ -606,8 +607,13 @@ $ make install-db
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make run-workers <div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make run-workers
</pre></div> </pre></div>
</div> </div>
<p>Open <a class="reference external" href="http://localhost:3000">http://localhost:3000</a> and log in (the email is <code class="docutils literal notranslate"><span class="pre">admin&#64;example.com</span></code> <ul class="simple">
and the password <code class="docutils literal notranslate"><span class="pre">mpwoadmin</span></code>) or register</p> <li><p>Open <a class="reference external" href="http://localhost:3000">http://localhost:3000</a> and register</p></li>
<li><p>To set admin rights to the newly created account, use the following command:</p></li>
</ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make set-admin <span class="nv">USERNAME</span><span class="o">=</span>&lt;username&gt;
</pre></div>
</div>
</section> </section>
<section id="production-environment"> <section id="production-environment">
<h4>Production environment<a class="headerlink" href="#production-environment" title="Permalink to this headline"></a></h4> <h4>Production environment<a class="headerlink" href="#production-environment" title="Permalink to this headline"></a></h4>
@ -647,9 +653,13 @@ database credentials</strong>):</p></li>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make run <div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make run
</pre></div> </pre></div>
</div> </div>
<p>Open <a class="reference external" href="http://localhost:5000">http://localhost:5000</a>, log in as admin (the email is <ul class="simple">
<code class="docutils literal notranslate"><span class="pre">admin&#64;example.com</span></code> and the password <code class="docutils literal notranslate"><span class="pre">mpwoadmin</span></code>) and change the <li><p>Open <a class="reference external" href="http://localhost:5000">http://localhost:5000</a> and register</p></li>
password</p> <li><p>To set admin rights to the newly created account, use the following command:</p></li>
</ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make set-admin <span class="nv">USERNAME</span><span class="o">=</span>&lt;username&gt;
</pre></div>
</div>
</section> </section>
</section> </section>
</section> </section>
@ -892,9 +902,15 @@ $ <span class="nb">cd</span> FitTrackee
$ make docker-build docker-run docker-init $ make docker-build docker-run docker-init
</pre></div> </pre></div>
</div> </div>
<p>Open <a class="reference external" href="http://localhost:5000">http://localhost:5000</a>, log in as admin (the email is <cite>admin&#64;example.com</cite> and the password <cite>mpwoadmin</cite>) or register.</p> <p>Open <a class="reference external" href="http://localhost:5000">http://localhost:5000</a> and register.</p>
<p>Open <a class="reference external" href="http://localhost:8025">http://localhost:8025</a> to access <a class="reference external" href="https://github.com/mailhog/MailHog">MailHog interface</a> (email testing tool)</p> <p>Open <a class="reference external" href="http://localhost:8025">http://localhost:8025</a> to access <a class="reference external" href="https://github.com/mailhog/MailHog">MailHog interface</a> (email testing tool)</p>
<ul class="simple"> <ul class="simple">
<li><p>To set admin rights to the newly created account, use the following command:</p></li>
</ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make docker-set-admin <span class="nv">USERNAME</span><span class="o">=</span>&lt;username&gt;
</pre></div>
</div>
<ul class="simple">
<li><p>To stop <strong>Fittrackee</strong>:</p></li> <li><p>To stop <strong>Fittrackee</strong>:</p></li>
</ul> </ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make docker-stop <div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make docker-stop
@ -930,8 +946,7 @@ $ make docker-build docker-run docker-init
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make docker-serve-client <div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make docker-serve-client
</pre></div> </pre></div>
</div> </div>
<p>Open <a class="reference external" href="http://localhost:3000">http://localhost:3000</a> and log in (the email is <code class="docutils literal notranslate"><span class="pre">admin&#64;example.com</span></code> <p>Open <a class="reference external" href="http://localhost:3000">http://localhost:3000</a></p>
and the password <code class="docutils literal notranslate"><span class="pre">mpwoadmin</span></code>) or register</p>
<div class="admonition note"> <div class="admonition note">
<p class="admonition-title">Note</p> <p class="admonition-title">Note</p>
<p>Some environment variables need to be updated like <cite>UI_URL</cite></p> <p>Some environment variables need to be updated like <cite>UI_URL</cite></p>

File diff suppressed because one or more lines are too long

View File

@ -269,19 +269,12 @@ For instance, copy and update ``.env`` file from ``.env.example`` and source the
$ nano .env $ nano .env
$ source .env $ source .env
- Initialize database schema
- Upgrade database schema
.. code-block:: bash .. code-block:: bash
$ fittrackee_upgrade_db $ fittrackee_upgrade_db
- Initialize database
.. code-block:: bash
$ fittrackee_init_data
- Start the application - Start the application
.. code-block:: bash .. code-block:: bash
@ -297,6 +290,14 @@ For instance, copy and update ``.env`` file from ``.env.example`` and source the
.. note:: .. note::
| To start application and workers with **systemd** service, see `Deployment <installation.html#deployment>`__ | To start application and workers with **systemd** service, see `Deployment <installation.html#deployment>`__
- Open http://localhost:3000 and register
- To set admin rights to the newly created account, use the following command:
.. code:: bash
$ fittrackee_set_admin <username>
From sources From sources
^^^^^^^^^^^^ ^^^^^^^^^^^^
@ -349,8 +350,13 @@ Dev environment
$ make run-workers $ make run-workers
Open http://localhost:3000 and log in (the email is ``admin@example.com`` - Open http://localhost:3000 and register
and the password ``mpwoadmin``) or register
- To set admin rights to the newly created account, use the following command:
.. code:: bash
$ make set-admin USERNAME=<username>
Production environment Production environment
@ -390,9 +396,13 @@ Production environment
$ make run $ make run
Open http://localhost:5000, log in as admin (the email is - Open http://localhost:5000 and register
``admin@example.com`` and the password ``mpwoadmin``) and change the
password - To set admin rights to the newly created account, use the following command:
.. code:: bash
$ make set-admin USERNAME=<username>
Upgrade Upgrade
@ -642,10 +652,16 @@ installing **FitTrackee** from **sources**.
$ cd FitTrackee $ cd FitTrackee
$ make docker-build docker-run docker-init $ make docker-build docker-run docker-init
Open http://localhost:5000, log in as admin (the email is `admin@example.com` and the password `mpwoadmin`) or register. Open http://localhost:5000 and register.
Open http://localhost:8025 to access `MailHog interface <https://github.com/mailhog/MailHog>`_ (email testing tool) Open http://localhost:8025 to access `MailHog interface <https://github.com/mailhog/MailHog>`_ (email testing tool)
- To set admin rights to the newly created account, use the following command:
.. code:: bash
$ make docker-set-admin USERNAME=<username>
- To stop **Fittrackee**: - To stop **Fittrackee**:
.. code-block:: bash .. code-block:: bash
@ -683,8 +699,7 @@ Development
$ make docker-serve-client $ make docker-serve-client
Open http://localhost:3000 and log in (the email is ``admin@example.com`` Open http://localhost:3000
and the password ``mpwoadmin``) or register
.. note:: .. note::
Some environment variables need to be updated like `UI_URL` Some environment variables need to be updated like `UI_URL`

View File

@ -1,4 +1,9 @@
from .utils import TEST_URL, assert_navbar, login_valid_user from .utils import (
TEST_URL,
assert_navbar,
login_valid_user,
register_valid_user_and_logout,
)
URL = f'{TEST_URL}/login' URL = f'{TEST_URL}/login'
@ -31,11 +36,7 @@ class TestLogin:
assert 'Forgot password?' in links[1].text assert 'Forgot password?' in links[1].text
def test_user_can_log_in(self, selenium): def test_user_can_log_in(self, selenium):
user = { user = register_valid_user_and_logout(selenium)
'username': 'admin',
'email': 'admin@example.com',
'password': 'mpwoadmin',
}
login_valid_user(selenium, user) login_valid_user(selenium, user)

View File

@ -4,6 +4,7 @@ from .utils import (
random_string, random_string,
register, register,
register_valid_user, register_valid_user,
register_valid_user_and_logout,
) )
URL = f'{TEST_URL}/register' URL = f'{TEST_URL}/register'
@ -57,30 +58,20 @@ class TestRegistration:
def test_user_can_not_register_if_username_is_already_taken( def test_user_can_not_register_if_username_is_already_taken(
self, selenium self, selenium
): ):
user_name = random_string() user = register_valid_user_and_logout(selenium)
user_infos = { user['email'] = f'{random_string()}@example.com'
'username': 'admin',
'email': f'{user_name}@example.com',
'password': 'p@ssw0rd',
'password_conf': 'p@ssw0rd',
}
register(selenium, user_infos) register(selenium, user)
assert selenium.current_url == URL assert selenium.current_url == URL
errors = selenium.find_element_by_class_name('error-message').text errors = selenium.find_element_by_class_name('error-message').text
assert 'Sorry, that user already exists.' in errors assert 'Sorry, that user already exists.' in errors
def test_user_can_not_register_if_email_is_already_taken(self, selenium): def test_user_can_not_register_if_email_is_already_taken(self, selenium):
user_name = random_string() user = register_valid_user_and_logout(selenium)
user_infos = { user['username'] = random_string()
'username': user_name,
'email': 'admin@example.com',
'password': 'p@ssw0rd',
'password_conf': 'p@ssw0rd',
}
register(selenium, user_infos) register(selenium, user)
assert selenium.current_url == URL assert selenium.current_url == URL
errors = selenium.find_element_by_class_name('error-message').text errors = selenium.find_element_by_class_name('error-message').text

View File

@ -54,6 +54,24 @@ def register_valid_user(selenium):
return user return user
def register_valid_user_and_logout(selenium):
user_name = random_string()
user = {
'username': user_name,
'email': f'{user_name}@example.com',
'password': 'p@ssw0rd',
'password_conf': 'p@ssw0rd',
}
register(selenium, user)
WebDriverWait(selenium, 15).until(EC.url_changes(f"{TEST_URL}/register"))
user_menu = selenium.find_element_by_class_name('nav-items-user-menu')
logout_link = user_menu.find_elements_by_class_name('nav-item')[2]
logout_link.click()
selenium.implicitly_wait(1)
return user
def login_valid_user(selenium, user): def login_valid_user(selenium, user):
login(selenium, user) login(selenium, user)
WebDriverWait(selenium, 10).until(EC.url_changes(f"{TEST_URL}/login")) WebDriverWait(selenium, 10).until(EC.url_changes(f"{TEST_URL}/login"))

View File

@ -51,16 +51,16 @@ def create_app() -> Flask:
email_service.init_email(app) email_service.init_email(app)
# get configuration from database # get configuration from database
from .application.models import AppConfig from .application.utils import (
from .application.utils import init_config, update_app_config_from_database get_or_init_config,
update_app_config_from_database,
)
with app.app_context(): with app.app_context():
# Note: check if "app_config" table exist to avoid errors when # Note: check if "app_config" table exist to avoid errors when
# dropping tables on dev environments # dropping tables on dev environments
if db.engine.dialect.has_table(db.engine.connect(), 'app_config'): if db.engine.dialect.has_table(db.engine.connect(), 'app_config'):
db_app_config = AppConfig.query.one_or_none() db_app_config = get_or_init_config()
if not db_app_config:
_, db_app_config = init_config()
update_app_config_from_database(app, db_app_config) update_app_config_from_database(app, db_app_config)
from .application.app_config import config_blueprint # noqa from .application.app_config import config_blueprint # noqa

View File

@ -4,17 +4,15 @@ import os
import shutil import shutil
from typing import Dict, Optional from typing import Dict, Optional
import click
import gunicorn.app.base import gunicorn.app.base
from flask import Flask from flask import Flask
from flask_dramatiq import worker from flask_dramatiq import worker
from flask_migrate import upgrade from flask_migrate import upgrade
from tqdm import tqdm
from fittrackee import create_app, db from fittrackee import create_app, db
from fittrackee.application.utils import init_config from fittrackee.users.exceptions import UserNotFoundException
from fittrackee.database_utils import init_database from fittrackee.users.utils import set_admin_rights
from fittrackee.workouts.models import Workout
from fittrackee.workouts.utils import update_workout
HOST = os.getenv('HOST', '0.0.0.0') HOST = os.getenv('HOST', '0.0.0.0')
PORT = os.getenv('PORT', '5000') PORT = os.getenv('PORT', '5000')
@ -52,7 +50,7 @@ def upgrade_db() -> None:
@app.cli.command('drop-db') @app.cli.command('drop-db')
def drop_db() -> None: def drop_db() -> None:
"""Empty database for dev environments.""" """Empty database and delete uploaded files for dev environments."""
db.engine.execute("DROP TABLE IF EXISTS alembic_version;") db.engine.execute("DROP TABLE IF EXISTS alembic_version;")
db.drop_all() db.drop_all()
db.session.commit() db.session.commit()
@ -61,42 +59,15 @@ def drop_db() -> None:
print('Uploaded files deleted.') print('Uploaded files deleted.')
@app.cli.command('init-data') @app.cli.command('set-admin')
def init_data() -> None: @click.argument('username')
"""Init the database and application config.""" def set_admin(username: str) -> None:
init_database(app) """Set admin rights for given user"""
try:
set_admin_rights(username)
@app.cli.command() print(f"User '{username}' updated.")
def recalculate() -> None: except UserNotFoundException:
print("Starting workouts data refresh") print(f"User '{username}' not found.")
workouts = (
Workout.query.filter(Workout.gpx != None) # noqa
.order_by(Workout.workout_date.asc()) # noqa
.all()
)
if len(workouts) == 0:
print('➡️ no workouts to upgrade.')
return None
pbar = tqdm(workouts)
for workout in pbar:
update_workout(workout)
pbar.set_postfix(activitiy_id=workout.id)
db.session.commit()
@app.cli.command('init-app-config')
def init_app_config() -> None:
"""Init application configuration."""
print("Init application configuration")
config_created, _ = init_config()
if config_created:
print("Creation done!")
else:
print(
"Application configuration already existing in database. "
"Please use web application to update it."
)
def main() -> None: def main() -> None:

View File

@ -1,42 +1,28 @@
import os from typing import Dict, List
from typing import Dict, List, Tuple
from flask import Flask from flask import Flask
from fittrackee import db from fittrackee import db
from fittrackee.users.models import User
from .models import AppConfig from .models import AppConfig
MAX_FILE_SIZE = 1 * 1024 * 1024 # 1MB MAX_FILE_SIZE = 1 * 1024 * 1024 # 1MB
def init_config() -> Tuple[bool, AppConfig]: def get_or_init_config() -> AppConfig:
""" """
init application configuration if not existing in database Init application configuration.
Note: get some configuration values from env variables
(for FitTrackee versions prior to v0.3.0)
""" """
existing_config = AppConfig.query.one_or_none() existing_config = AppConfig.query.one_or_none()
nb_users = User.query.count() if existing_config:
if not existing_config: return existing_config
config = AppConfig() config = AppConfig()
config.max_users = ( config.max_users = 0 # no limitation
nb_users config.max_single_file_size = MAX_FILE_SIZE
if os.getenv('REACT_APP_ALLOW_REGISTRATION') == "false" config.max_zip_file_size = MAX_FILE_SIZE * 10
else 0
)
config.max_single_file_size = os.environ.get(
'REACT_APP_MAX_SINGLE_FILE_SIZE', MAX_FILE_SIZE
)
config.max_zip_file_size = os.environ.get(
'REACT_APP_MAX_ZIP_FILE_SIZE', MAX_FILE_SIZE * 10
)
db.session.add(config) db.session.add(config)
db.session.commit() db.session.commit()
return True, config return config
return False, existing_config
def update_app_config_from_database( def update_app_config_from_database(

View File

@ -54,8 +54,6 @@ class DevelopmentConfig(BaseConfig):
DEBUG = True DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
SECRET_KEY = 'development key' SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'
BCRYPT_LOG_ROUNDS = 4 BCRYPT_LOG_ROUNDS = 4
DRAMATIQ_BROKER_URL = os.getenv('REDIS_URL', 'redis://') DRAMATIQ_BROKER_URL = os.getenv('REDIS_URL', 'redis://')
@ -67,8 +65,6 @@ class TestingConfig(BaseConfig):
TESTING = True TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL') SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL')
SECRET_KEY = 'test key' SECRET_KEY = 'test key'
USERNAME = 'admin'
PASSWORD = 'default'
BCRYPT_LOG_ROUNDS = 4 BCRYPT_LOG_ROUNDS = 4
TOKEN_EXPIRATION_DAYS = 0 TOKEN_EXPIRATION_DAYS = 0
TOKEN_EXPIRATION_SECONDS = 3 TOKEN_EXPIRATION_SECONDS = 3

View File

@ -1,23 +0,0 @@
from flask import Flask
from fittrackee import db
from fittrackee.application.utils import (
init_config,
update_app_config_from_database,
)
from fittrackee.users.models import User
def init_database(app: Flask) -> None:
"""Init the database."""
admin = User(
username='admin', email='admin@example.com', password='mpwoadmin'
)
admin.admin = True
admin.timezone = 'Europe/Paris'
db.session.add(admin)
db.session.commit()
_, db_app_config = init_config()
update_app_config_from_database(app, db_app_config)
print('Initial data stored in database.')

View File

@ -0,0 +1,48 @@
from flask import Flask
from fittrackee.application.models import AppConfig
from fittrackee.application.utils import get_or_init_config
class TestGetOrInitAppConfig:
def test_it_creates_app_config(self, app_no_config: Flask) -> None:
get_or_init_config()
assert AppConfig.query.count() == 1
def test_it_inits_max_users_with_default_value(
self, app_no_config: Flask
) -> None:
get_or_init_config()
app_config = AppConfig.query.first()
assert app_config.max_users == 0
def test_it_inits_max_single_file_size_with_default_value(
self, app_no_config: Flask
) -> None:
get_or_init_config()
app_config = AppConfig.query.first()
assert app_config.max_single_file_size == 1048576 # 1MB
def test_it_inits_max_zip_file_size_with_default_value(
self, app_no_config: Flask
) -> None:
get_or_init_config()
app_config = AppConfig.query.first()
assert app_config.max_zip_file_size == 10485760 # 10MB
def test_it_inits_gpx_limit_import_with_default_value(
self, app_no_config: Flask
) -> None:
get_or_init_config()
app_config = AppConfig.query.first()
assert app_config.gpx_limit_import == 10
def test_it_returns_existing_config(self, app: Flask) -> None:
app_config = get_or_init_config()
assert app_config.max_users == 100

View File

@ -0,0 +1,30 @@
import pytest
from flask import Flask
from fittrackee.users.exceptions import UserNotFoundException
from fittrackee.users.models import User
from fittrackee.users.utils import set_admin_rights
from ..utils import random_string
class TestSetAdminRights:
def test_it_raises_exception_if_user_does_not_exist(
self, app: Flask
) -> None:
with pytest.raises(UserNotFoundException):
set_admin_rights(random_string())
def test_it_sets_admin_right_for_a_given_user(
self, app: Flask, user_1: User
) -> None:
set_admin_rights(user_1.username)
assert user_1.admin is True
def test_it_does_not_raise_exception_when_user_has_already_admin_right(
self, app: Flask, user_1_admin: User
) -> None:
set_admin_rights(user_1_admin.username)
assert user_1_admin.admin is True

12
fittrackee/tests/utils.py Normal file
View File

@ -0,0 +1,12 @@
import random
import string
from typing import Optional
def random_string(length: Optional[int] = None) -> str:
if length is None:
length = 10
return ''.join(
random.choice(string.ascii_letters + string.digits)
for _ in range(length)
)

View File

@ -7,7 +7,6 @@ from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from fittrackee.workouts.utils_id import decode_short_id
from ..api_test_case import ApiTestCaseMixin from ..api_test_case import ApiTestCaseMixin
from .utils import get_random_short_id, post_an_workout from .utils import get_random_short_id, post_an_workout
@ -619,37 +618,3 @@ class TestEditWorkoutWithoutGpx(ApiTestCaseMixin):
assert response.status_code == 404 assert response.status_code == 404
assert 'not found' in data['status'] assert 'not found' in data['status']
assert len(data['data']['workouts']) == 0 assert len(data['data']['workouts']) == 0
class TestRefreshWorkoutWithGpx:
def test_refresh_an_workout_with_gpx(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
sport_2_running: Sport,
gpx_file: str,
) -> None:
token, workout_short_id = post_an_workout(app, gpx_file)
workout_uuid = decode_short_id(workout_short_id)
client = app.test_client()
# Edit some workout data
workout = Workout.query.filter_by(uuid=workout_uuid).first()
workout.ascent = 1000
workout.min_alt = -100
response = client.patch(
f'/api/workouts/{workout_short_id}',
content_type='application/json',
data=json.dumps(dict(refresh=True)),
headers=dict(Authorization=f'Bearer {token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['workouts']) == 1
assert 1 == data['data']['workouts'][0]['sport_id']
assert 0.4 == data['data']['workouts'][0]['ascent']
assert 975.0 == data['data']['workouts'][0]['min_alt']

View File

@ -0,0 +1,2 @@
class UserNotFoundException(Exception):
...

View File

@ -3,12 +3,14 @@ from typing import Optional, Tuple
from flask import Request from flask import Request
from fittrackee import db
from fittrackee.responses import ( from fittrackee.responses import (
ForbiddenErrorResponse, ForbiddenErrorResponse,
HttpResponse, HttpResponse,
UnauthorizedErrorResponse, UnauthorizedErrorResponse,
) )
from .exceptions import UserNotFoundException
from .models import User from .models import User
@ -84,3 +86,11 @@ def can_view_workout(
if auth_user_id != workout_user_id: if auth_user_id != workout_user_id:
return ForbiddenErrorResponse() return ForbiddenErrorResponse()
return None return None
def set_admin_rights(username: str) -> None:
user = User.query.filter_by(username=username).first()
if not user:
raise UserNotFoundException()
user.admin = True
db.session.commit()

View File

@ -189,14 +189,12 @@ def edit_workout(
workout: Workout, workout_data: Dict, auth_user: User workout: Workout, workout_data: Dict, auth_user: User
) -> Workout: ) -> Workout:
""" """
Edit an workout Edit a workout
Note: the gpx file is NOT modified Note: the gpx file is NOT modified
In a next version, map_data and weather_data will be updated In a next version, map_data and weather_data will be updated
(case of a modified gpx file, see issue #7) (case of a modified gpx file, see issue #7)
""" """
if workout_data.get('refresh'):
workout = update_workout(workout)
if workout_data.get('sport_id'): if workout_data.get('sport_id'):
workout.sport_id = workout_data.get('sport_id') workout.sport_id = workout_data.get('sport_id')
if workout_data.get('title'): if workout_data.get('title'):

64
poetry.lock generated
View File

@ -407,7 +407,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]] [[package]]
name = "importlib-metadata" name = "importlib-metadata"
version = "4.10.1" version = "4.11.0"
description = "Read metadata from Python packages" description = "Read metadata from Python packages"
category = "main" category = "main"
optional = false optional = false
@ -578,7 +578,7 @@ python-versions = ">=3.7"
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "2.4.1" version = "2.5.0"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev" category = "dev"
optional = false optional = false
@ -704,7 +704,7 @@ diagrams = ["jinja2", "railroad-diagrams"]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "7.0.0" version = "7.0.1"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
category = "dev" category = "dev"
optional = false optional = false
@ -907,7 +907,7 @@ sphinx = ">=1.3.1"
[[package]] [[package]]
name = "redis" name = "redis"
version = "4.1.2" version = "4.1.3"
description = "Python client for Redis database and key-value store" description = "Python client for Redis database and key-value store"
category = "main" category = "main"
optional = false optional = false
@ -1200,28 +1200,12 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.0" version = "2.0.1"
description = "A lil' TOML parser" description = "A lil' TOML parser"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
[[package]]
name = "tqdm"
version = "4.62.3"
description = "Fast, Extensible Progress Meter"
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[package.extras]
dev = ["py-make (>=0.1.0)", "twine", "wheel"]
notebook = ["ipywidgets (>=6)"]
telegram = ["requests"]
[[package]] [[package]]
name = "trio" name = "trio"
version = "0.19.0" version = "0.19.0"
@ -1278,7 +1262,7 @@ python-versions = "*"
[[package]] [[package]]
name = "types-requests" name = "types-requests"
version = "2.27.8" version = "2.27.9"
description = "Typing stubs for requests" description = "Typing stubs for requests"
category = "dev" category = "dev"
optional = false optional = false
@ -1324,7 +1308,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]] [[package]]
name = "werkzeug" name = "werkzeug"
version = "2.0.2" version = "2.0.3"
description = "The comprehensive WSGI web application library." description = "The comprehensive WSGI web application library."
category = "main" category = "main"
optional = false optional = false
@ -1367,7 +1351,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7" python-versions = "^3.7"
content-hash = "efbc9200e445df97c991ac1e6b72873262c32e0f09b21dc682593fd5034686ab" content-hash = "ec6da4aaa4cef6ee6c235ef4d2f101b533409097ee8b5169596373a5b4c60cdb"
[metadata.files] [metadata.files]
alabaster = [ alabaster = [
@ -1679,8 +1663,8 @@ imagesize = [
{file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"},
] ]
importlib-metadata = [ importlib-metadata = [
{file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, {file = "importlib_metadata-4.11.0-py3-none-any.whl", hash = "sha256:6affcdb3aec542dd98df8211e730bba6c5f2bec8288d47bacacde898f548c9ad"},
{file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, {file = "importlib_metadata-4.11.0.tar.gz", hash = "sha256:9e5e553bbba1843cb4a00823014b907616be46ee503d2b9ba001d214a8da218f"},
] ]
importlib-resources = [ importlib-resources = [
{file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"}, {file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"},
@ -1822,8 +1806,8 @@ pillow = [
{file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
] ]
platformdirs = [ platformdirs = [
{file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, {file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"},
{file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, {file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"},
] ]
pluggy = [ pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
@ -1924,8 +1908,8 @@ pyparsing = [
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
] ]
pytest = [ pytest = [
{file = "pytest-7.0.0-py3-none-any.whl", hash = "sha256:42901e6bd4bd4a0e533358a86e848427a49005a3256f657c5c8f8dd35ef137a9"}, {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"},
{file = "pytest-7.0.0.tar.gz", hash = "sha256:dad48ffda394e5ad9aa3b7d7ddf339ed502e5e365b1350e0af65f4a602344b11"}, {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"},
] ]
pytest-base-url = [ pytest-base-url = [
{file = "pytest-base-url-1.4.2.tar.gz", hash = "sha256:7f1f32e08c2ee751e59e7f5880235b46e83496adc5cba5a01ca218c6fe81333d"}, {file = "pytest-base-url-1.4.2.tar.gz", hash = "sha256:7f1f32e08c2ee751e59e7f5880235b46e83496adc5cba5a01ca218c6fe81333d"},
@ -1982,8 +1966,8 @@ recommonmark = [
{file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"},
] ]
redis = [ redis = [
{file = "redis-4.1.2-py3-none-any.whl", hash = "sha256:f13eea4254e302485add677cadedaf1305c1b3a4e07535e23b7b239798ce9301"}, {file = "redis-4.1.3-py3-none-any.whl", hash = "sha256:267e89e476eb684517584e8988f1e5d755f483a368c133020c4c40e8b676bc5d"},
{file = "redis-4.1.2.tar.gz", hash = "sha256:bf86397be532fc0a888d5976a5313a3a70d8f912d52bc0c09bffda4b8425a1d4"}, {file = "redis-4.1.3.tar.gz", hash = "sha256:f2715caad9f0e8c6ff8df46d3c4c9022a3929001f530f66b62747554d3067068"},
] ]
requests = [ requests = [
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
@ -2102,12 +2086,8 @@ toml = [
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
] ]
tomli = [ tomli = [
{file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
tqdm = [
{file = "tqdm-4.62.3-py2.py3-none-any.whl", hash = "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c"},
{file = "tqdm-4.62.3.tar.gz", hash = "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"},
] ]
trio = [ trio = [
{file = "trio-0.19.0-py3-none-any.whl", hash = "sha256:c27c231e66336183c484fbfe080fa6cc954149366c15dc21db8b7290081ec7b8"}, {file = "trio-0.19.0-py3-none-any.whl", hash = "sha256:c27c231e66336183c484fbfe080fa6cc954149366c15dc21db8b7290081ec7b8"},
@ -2152,8 +2132,8 @@ types-pytz = [
{file = "types_pytz-2021.3.4-py3-none-any.whl", hash = "sha256:ccfa2ed29f816e3de2f882541c06ad2791f808a79cfe38265411820190999f0f"}, {file = "types_pytz-2021.3.4-py3-none-any.whl", hash = "sha256:ccfa2ed29f816e3de2f882541c06ad2791f808a79cfe38265411820190999f0f"},
] ]
types-requests = [ types-requests = [
{file = "types-requests-2.27.8.tar.gz", hash = "sha256:c2f4e4754d07ca0a88fd8a89bbc6c8a9f90fb441f9c9b572fd5c484f04817486"}, {file = "types-requests-2.27.9.tar.gz", hash = "sha256:7368974534d297939492efdfdab232930440b11e2203f6df1f0c40e3242a87ea"},
{file = "types_requests-2.27.8-py3-none-any.whl", hash = "sha256:8ec9f5f84adc6f579f53943312c28a84e87dc70201b54f7c4fbc7d22ecfa8a3e"}, {file = "types_requests-2.27.9-py3-none-any.whl", hash = "sha256:74070045418faf710f3154403d6a16c9e67db50e5119906ca6955f1658d20f7b"},
] ]
types-urllib3 = [ types-urllib3 = [
{file = "types-urllib3-1.26.9.tar.gz", hash = "sha256:abd2d4857837482b1834b4817f0587678dcc531dbc9abe4cde4da28cef3f522c"}, {file = "types-urllib3-1.26.9.tar.gz", hash = "sha256:abd2d4857837482b1834b4817f0587678dcc531dbc9abe4cde4da28cef3f522c"},
@ -2168,8 +2148,8 @@ urllib3 = [
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
] ]
werkzeug = [ werkzeug = [
{file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"}, {file = "Werkzeug-2.0.3-py3-none-any.whl", hash = "sha256:1421ebfc7648a39a5c58c601b154165d05cf47a3cd0ccb70857cbdacf6c8f2b8"},
{file = "Werkzeug-2.0.2.tar.gz", hash = "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a"}, {file = "Werkzeug-2.0.3.tar.gz", hash = "sha256:b863f8ff057c522164b6067c9e28b041161b4be5ba4d0daceeaa50a163822d3c"},
] ]
wrapt = [ wrapt = [
{file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"},

View File

@ -38,7 +38,6 @@ python-forecastio = "^1.4"
pytz = "^2021.3" pytz = "^2021.3"
shortuuid = "^1.0.8" shortuuid = "^1.0.8"
staticmap = "^0.5.4" staticmap = "^0.5.4"
tqdm = "^4.62"
SQLAlchemy = "1.4.31" SQLAlchemy = "1.4.31"
pyOpenSSL = "^22.0" pyOpenSSL = "^22.0"
@ -63,7 +62,7 @@ Sphinx = "^4.4.0"
[tool.poetry.scripts] [tool.poetry.scripts]
fittrackee = 'fittrackee.__main__:main' fittrackee = 'fittrackee.__main__:main'
fittrackee_init_data = 'fittrackee.__main__:init_data' fittrackee_set_admin = 'fittrackee.__main__:set_admin'
fittrackee_upgrade_db = 'fittrackee.__main__:upgrade_db' fittrackee_upgrade_db = 'fittrackee.__main__:upgrade_db'
fittrackee_worker = 'fittrackee.__main__:dramatiq_worker' fittrackee_worker = 'fittrackee.__main__:dramatiq_worker'