API - move some app configuration parameters in database
This commit is contained in:
		@@ -26,12 +26,26 @@ def create_app():
 | 
			
		||||
    bcrypt.init_app(app)
 | 
			
		||||
    migrate.init_app(app, db)
 | 
			
		||||
 | 
			
		||||
    # get configuration from database
 | 
			
		||||
    from .application.models import AppConfig
 | 
			
		||||
    from .application.utils import init_config, update_app_config_from_database
 | 
			
		||||
 | 
			
		||||
    with app.app_context():
 | 
			
		||||
        # Note: check if "app_config" table exist to avoid errors when
 | 
			
		||||
        # dropping tables on dev environments
 | 
			
		||||
        if db.engine.dialect.has_table(db.engine, 'app_config'):
 | 
			
		||||
            db_app_config = AppConfig.query.one_or_none()
 | 
			
		||||
            if not db_app_config:
 | 
			
		||||
                _, db_app_config = init_config()
 | 
			
		||||
            update_app_config_from_database(app, db_app_config)
 | 
			
		||||
 | 
			
		||||
    from .users.auth import auth_blueprint  # noqa
 | 
			
		||||
    from .users.users import users_blueprint  # noqa
 | 
			
		||||
    from .activities.activities import activities_blueprint  # noqa
 | 
			
		||||
    from .activities.records import records_blueprint  # noqa
 | 
			
		||||
    from .activities.sports import sports_blueprint  # noqa
 | 
			
		||||
    from .activities.stats import stats_blueprint  # noqa
 | 
			
		||||
    from .application.config import config_blueprint  # noqa
 | 
			
		||||
 | 
			
		||||
    app.register_blueprint(users_blueprint, url_prefix='/api')
 | 
			
		||||
    app.register_blueprint(auth_blueprint, url_prefix='/api')
 | 
			
		||||
@@ -39,6 +53,7 @@ def create_app():
 | 
			
		||||
    app.register_blueprint(records_blueprint, url_prefix='/api')
 | 
			
		||||
    app.register_blueprint(sports_blueprint, url_prefix='/api')
 | 
			
		||||
    app.register_blueprint(stats_blueprint, url_prefix='/api')
 | 
			
		||||
    app.register_blueprint(config_blueprint, url_prefix='/api')
 | 
			
		||||
 | 
			
		||||
    if app.debug:
 | 
			
		||||
        logging.getLogger('sqlalchemy').setLevel(logging.WARNING)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										146
									
								
								fittrackee_api/fittrackee_api/application/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								fittrackee_api/fittrackee_api/application/config.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
			
		||||
from fittrackee_api import appLog, db
 | 
			
		||||
from flask import Blueprint, current_app, jsonify, request
 | 
			
		||||
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
 | 
			
		||||
 | 
			
		||||
from ..users.utils import authenticate_as_admin
 | 
			
		||||
from .models import AppConfig
 | 
			
		||||
from .utils import update_app_config_from_database
 | 
			
		||||
 | 
			
		||||
config_blueprint = Blueprint('config', __name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@config_blueprint.route('/config', methods=['GET'])
 | 
			
		||||
def get_application_config():
 | 
			
		||||
    """
 | 
			
		||||
    Get Application config
 | 
			
		||||
 | 
			
		||||
    **Example request**:
 | 
			
		||||
 | 
			
		||||
    .. sourcecode:: http
 | 
			
		||||
 | 
			
		||||
      GET /api/config HTTP/1.1
 | 
			
		||||
      Content-Type: application/json
 | 
			
		||||
 | 
			
		||||
    **Example response**:
 | 
			
		||||
 | 
			
		||||
    .. sourcecode:: http
 | 
			
		||||
 | 
			
		||||
      HTTP/1.1 200 OK
 | 
			
		||||
      Content-Type: application/json
 | 
			
		||||
 | 
			
		||||
      {
 | 
			
		||||
        "data": {
 | 
			
		||||
          "gpx_limit_import": 10,
 | 
			
		||||
          "is_registration_enabled": false,
 | 
			
		||||
          "max_single_file_size": 1048576,
 | 
			
		||||
          "max_zip_file_size": 10485760,
 | 
			
		||||
          "max_users": 0,
 | 
			
		||||
          "registration": false
 | 
			
		||||
        },
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :statuscode 200: success
 | 
			
		||||
    :statuscode 500: Error on getting configuration.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        config = AppConfig.query.one()
 | 
			
		||||
        response_object = {'status': 'success', 'data': config.serialize()}
 | 
			
		||||
        return jsonify(response_object), 200
 | 
			
		||||
    except (MultipleResultsFound, NoResultFound) as e:
 | 
			
		||||
        appLog.error(e)
 | 
			
		||||
        response_object = {
 | 
			
		||||
            'status': 'error',
 | 
			
		||||
            'message': 'Error on getting configuration.',
 | 
			
		||||
        }
 | 
			
		||||
        return jsonify(response_object), 500
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@config_blueprint.route('/config', methods=['PATCH'])
 | 
			
		||||
@authenticate_as_admin
 | 
			
		||||
def update_application_config(auth_user_id):
 | 
			
		||||
    """
 | 
			
		||||
    Update Application config
 | 
			
		||||
 | 
			
		||||
    Authenticated user must be an admin
 | 
			
		||||
 | 
			
		||||
    **Example request**:
 | 
			
		||||
 | 
			
		||||
    .. sourcecode:: http
 | 
			
		||||
 | 
			
		||||
      GET /api/config HTTP/1.1
 | 
			
		||||
      Content-Type: application/json
 | 
			
		||||
 | 
			
		||||
    **Example response**:
 | 
			
		||||
 | 
			
		||||
    .. sourcecode:: http
 | 
			
		||||
 | 
			
		||||
      HTTP/1.1 200 OK
 | 
			
		||||
      Content-Type: application/json
 | 
			
		||||
 | 
			
		||||
      {
 | 
			
		||||
        "data": {
 | 
			
		||||
          "gpx_limit_import": 10,
 | 
			
		||||
          "is_registration_enabled": true,
 | 
			
		||||
          "max_single_file_size": 1048576,
 | 
			
		||||
          "max_zip_file_size": 10485760,
 | 
			
		||||
          "max_users": 10,
 | 
			
		||||
          "registration": true
 | 
			
		||||
        },
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
 | 
			
		||||
    :<json integrer gpx_limit_import: max number of files in zip archive
 | 
			
		||||
    :<json integrer max_single_file_size: max size of a single file
 | 
			
		||||
    :<json integrer max_zip_file_size: max size of a zip archive
 | 
			
		||||
    :<json integrer max_users: max users allowed to register on instance
 | 
			
		||||
    :<json boolean registration: is registration enabled ?
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
 | 
			
		||||
    :statuscode 200: success
 | 
			
		||||
    :statuscode 400: invalid payload
 | 
			
		||||
    :statuscode 401:
 | 
			
		||||
        - Provide a valid auth token.
 | 
			
		||||
        - Signature expired. Please log in again.
 | 
			
		||||
        - Invalid token. Please log in again.
 | 
			
		||||
    :statuscode 403: You do not have permissions.
 | 
			
		||||
    :statuscode 500: Error on updating configuration.
 | 
			
		||||
    """
 | 
			
		||||
    config_data = request.get_json()
 | 
			
		||||
    if not config_data:
 | 
			
		||||
        response_object = {'status': 'error', 'message': 'Invalid payload.'}
 | 
			
		||||
        return jsonify(response_object), 400
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        config = AppConfig.query.one()
 | 
			
		||||
        if 'gpx_limit_import' in config_data:
 | 
			
		||||
            config.gpx_limit_import = config_data.get('gpx_limit_import')
 | 
			
		||||
        if 'max_single_file_size' in config_data:
 | 
			
		||||
            config.max_single_file_size = config_data.get(
 | 
			
		||||
                'max_single_file_size'
 | 
			
		||||
            )
 | 
			
		||||
        if 'max_zip_file_size' in config_data:
 | 
			
		||||
            config.max_zip_file_size = config_data.get('max_zip_file_size')
 | 
			
		||||
        if 'max_users' in config_data:
 | 
			
		||||
            config.max_users = config_data.get('max_users')
 | 
			
		||||
        if 'registration' in config_data:
 | 
			
		||||
            config.registration = config_data.get('registration')
 | 
			
		||||
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
        update_app_config_from_database(current_app, config)
 | 
			
		||||
 | 
			
		||||
        response_object = {'status': 'success', 'data': config.serialize()}
 | 
			
		||||
        code = 200
 | 
			
		||||
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        appLog.error(e)
 | 
			
		||||
        response_object = {
 | 
			
		||||
            'status': 'error',
 | 
			
		||||
            'message': 'Error on updating configuration.',
 | 
			
		||||
        }
 | 
			
		||||
        code = 500
 | 
			
		||||
    return jsonify(response_object), code
 | 
			
		||||
							
								
								
									
										30
									
								
								fittrackee_api/fittrackee_api/application/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								fittrackee_api/fittrackee_api/application/models.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
from fittrackee_api import db
 | 
			
		||||
 | 
			
		||||
from ..users.models import User
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AppConfig(db.Model):
 | 
			
		||||
    __tablename__ = 'app_config'
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
 | 
			
		||||
    registration = db.Column(db.Boolean, default=False, nullable=False)
 | 
			
		||||
    max_users = db.Column(db.Integer, default=0, nullable=False)
 | 
			
		||||
    gpx_limit_import = db.Column(db.Integer, default=10, nullable=False)
 | 
			
		||||
    max_single_file_size = db.Column(
 | 
			
		||||
        db.Integer, default=1048576, nullable=False
 | 
			
		||||
    )
 | 
			
		||||
    max_zip_file_size = db.Column(db.Integer, default=10485760, nullable=False)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def is_registration_enabled(self):
 | 
			
		||||
        nb_users = User.query.count()
 | 
			
		||||
        return self.registration and nb_users < self.max_users
 | 
			
		||||
 | 
			
		||||
    def serialize(self):
 | 
			
		||||
        return {
 | 
			
		||||
            "gpx_limit_import": self.gpx_limit_import,
 | 
			
		||||
            "is_registration_enabled": self.is_registration_enabled,
 | 
			
		||||
            "max_single_file_size": self.max_single_file_size,
 | 
			
		||||
            "max_zip_file_size": self.max_zip_file_size,
 | 
			
		||||
            "max_users": self.max_users,
 | 
			
		||||
            "registration": self.registration,
 | 
			
		||||
        }
 | 
			
		||||
							
								
								
									
										46
									
								
								fittrackee_api/fittrackee_api/application/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								fittrackee_api/fittrackee_api/application/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from fittrackee_api import db
 | 
			
		||||
 | 
			
		||||
from .models import AppConfig
 | 
			
		||||
 | 
			
		||||
MAX_FILE_SIZE = 1 * 1024 * 1024  # 1MB
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def init_config():
 | 
			
		||||
    """
 | 
			
		||||
    init application configuration if not existing in database
 | 
			
		||||
 | 
			
		||||
    Note: get some configuration values from env variables (for FitTrackee versions
 | 
			
		||||
    prior to v0.3.0)
 | 
			
		||||
    """
 | 
			
		||||
    existing_config = AppConfig.query.one_or_none()
 | 
			
		||||
    if not existing_config:
 | 
			
		||||
        config = AppConfig()
 | 
			
		||||
        config.registration = (
 | 
			
		||||
            False
 | 
			
		||||
            if os.getenv('REACT_APP_ALLOW_REGISTRATION') == "false"
 | 
			
		||||
            else True
 | 
			
		||||
        )
 | 
			
		||||
        config.max_users = 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.commit()
 | 
			
		||||
        return True, config
 | 
			
		||||
    return False, existing_config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def update_app_config_from_database(current_app, db_config):
 | 
			
		||||
    current_app.config['gpx_limit_import'] = db_config.gpx_limit_import
 | 
			
		||||
    current_app.config['max_single_file_size'] = db_config.max_single_file_size
 | 
			
		||||
    current_app.config['MAX_CONTENT_LENGTH'] = db_config.max_zip_file_size
 | 
			
		||||
    current_app.config['max_users'] = db_config.max_users
 | 
			
		||||
    current_app.config['registration'] = db_config.registration
 | 
			
		||||
    current_app.config[
 | 
			
		||||
        'is_registration_enabled'
 | 
			
		||||
    ] = db_config.is_registration_enabled
 | 
			
		||||
@@ -2,8 +2,6 @@ import os
 | 
			
		||||
 | 
			
		||||
from flask import current_app
 | 
			
		||||
 | 
			
		||||
MAX_FILE_SIZE = 1 * 1024 * 1024  # 1MB
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseConfig:
 | 
			
		||||
    """Base configuration"""
 | 
			
		||||
@@ -15,19 +13,8 @@ class BaseConfig:
 | 
			
		||||
    TOKEN_EXPIRATION_DAYS = 30
 | 
			
		||||
    TOKEN_EXPIRATION_SECONDS = 0
 | 
			
		||||
    UPLOAD_FOLDER = os.path.join(current_app.root_path, 'uploads')
 | 
			
		||||
    # for gpx zip
 | 
			
		||||
    MAX_CONTENT_LENGTH = int(
 | 
			
		||||
        os.environ.get('REACT_APP_MAX_ZIP_FILE_SIZE', MAX_FILE_SIZE * 10)
 | 
			
		||||
    )
 | 
			
		||||
    # for single file (gpx or picture)
 | 
			
		||||
    MAX_SINGLE_FILE = int(
 | 
			
		||||
        os.environ.get('REACT_APP_MAX_SINGLE_FILE_SIZE', MAX_FILE_SIZE)
 | 
			
		||||
    )
 | 
			
		||||
    PICTURE_ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'}
 | 
			
		||||
    ACTIVITY_ALLOWED_EXTENSIONS = {'gpx', 'zip'}
 | 
			
		||||
    REGISTRATION_ALLOWED = (
 | 
			
		||||
        False if os.getenv('REACT_APP_ALLOW_REGISTRATION') == "false" else True
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DevelopmentConfig(BaseConfig):
 | 
			
		||||
 
 | 
			
		||||
@@ -4,16 +4,58 @@ import os
 | 
			
		||||
import pytest
 | 
			
		||||
from fittrackee_api import create_app, db
 | 
			
		||||
from fittrackee_api.activities.models import Activity, ActivitySegment, Sport
 | 
			
		||||
from fittrackee_api.application.models import AppConfig
 | 
			
		||||
from fittrackee_api.application.utils import update_app_config_from_database
 | 
			
		||||
from fittrackee_api.users.models import User
 | 
			
		||||
 | 
			
		||||
os.environ["FLASK_ENV"] = 'testing'
 | 
			
		||||
os.environ["APP_SETTINGS"] = 'fittrackee_api.config.TestingConfig'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def app_config_with_registration():
 | 
			
		||||
    config = AppConfig()
 | 
			
		||||
    config.gpx_limit_import = 10
 | 
			
		||||
    config.max_single_file_size = 1 * 1024 * 1024
 | 
			
		||||
    config.max_zip_file_size = 1 * 1024 * 1024 * 10
 | 
			
		||||
    config.max_users = 10
 | 
			
		||||
    config.registration = True
 | 
			
		||||
    db.session.add(config)
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def app_config_registration_disabled():
 | 
			
		||||
    config = AppConfig()
 | 
			
		||||
    config.gpx_limit_import = 20
 | 
			
		||||
    config.max_single_file_size = 10485
 | 
			
		||||
    config.max_zip_file_size = 104850
 | 
			
		||||
    config.max_users = 0
 | 
			
		||||
    config.registration = False
 | 
			
		||||
    db.session.add(config)
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def app():
 | 
			
		||||
    app = create_app()
 | 
			
		||||
    app.config['REGISTRATION_ALLOWED'] = True
 | 
			
		||||
    with app.app_context():
 | 
			
		||||
        db.create_all()
 | 
			
		||||
        app_db_config = app_config_with_registration()
 | 
			
		||||
        update_app_config_from_database(app, app_db_config)
 | 
			
		||||
        yield app
 | 
			
		||||
        db.session.remove()
 | 
			
		||||
        db.drop_all()
 | 
			
		||||
        # close unused idle connections => avoid the following error:
 | 
			
		||||
        # FATAL: remaining connection slots are reserved for non-replication
 | 
			
		||||
        # superuser connections
 | 
			
		||||
        db.engine.dispose()
 | 
			
		||||
        return app
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def app_no_config():
 | 
			
		||||
    app = create_app()
 | 
			
		||||
    with app.app_context():
 | 
			
		||||
        db.create_all()
 | 
			
		||||
        yield app
 | 
			
		||||
@@ -29,9 +71,10 @@ def app():
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def app_no_registration():
 | 
			
		||||
    app = create_app()
 | 
			
		||||
    app.config['REGISTRATION_ALLOWED'] = False
 | 
			
		||||
    with app.app_context():
 | 
			
		||||
        db.create_all()
 | 
			
		||||
        app_db_config = app_config_registration_disabled()
 | 
			
		||||
        update_app_config_from_database(app, app_db_config)
 | 
			
		||||
        yield app
 | 
			
		||||
        db.session.remove()
 | 
			
		||||
        db.drop_all()
 | 
			
		||||
@@ -39,6 +82,19 @@ def app_no_registration():
 | 
			
		||||
    return app
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture()
 | 
			
		||||
def app_config():
 | 
			
		||||
    config = AppConfig()
 | 
			
		||||
    config.gpx_limit_import = 10
 | 
			
		||||
    config.max_single_file_size = 1048576
 | 
			
		||||
    config.max_zip_file_size = 10485760
 | 
			
		||||
    config.max_users = 0
 | 
			
		||||
    config.registration = False
 | 
			
		||||
    db.session.add(config)
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture()
 | 
			
		||||
def user_1():
 | 
			
		||||
    user = User(username='test', email='test@test.com', password='12345678')
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										214
									
								
								fittrackee_api/fittrackee_api/tests/test_app_config_api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								fittrackee_api/fittrackee_api/tests/test_app_config_api.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,214 @@
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_config(app, user_1):
 | 
			
		||||
    client = app.test_client()
 | 
			
		||||
    resp_login = client.post(
 | 
			
		||||
        '/api/auth/login',
 | 
			
		||||
        data=json.dumps(dict(email='test@test.com', password='12345678')),
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
    )
 | 
			
		||||
    response = client.get(
 | 
			
		||||
        '/api/config',
 | 
			
		||||
        headers=dict(
 | 
			
		||||
            Authorization='Bearer '
 | 
			
		||||
            + json.loads(resp_login.data.decode())['auth_token']
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    data = json.loads(response.data.decode())
 | 
			
		||||
 | 
			
		||||
    assert response.status_code == 200
 | 
			
		||||
    assert 'success' in data['status']
 | 
			
		||||
 | 
			
		||||
    assert data['data']['gpx_limit_import'] == 10
 | 
			
		||||
    assert data['data']['is_registration_enabled'] is True
 | 
			
		||||
    assert data['data']['max_single_file_size'] == 1048576
 | 
			
		||||
    assert data['data']['max_zip_file_size'] == 10485760
 | 
			
		||||
    assert data['data']['max_users'] == 10
 | 
			
		||||
    assert data['data']['registration'] is True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_config_no_config(app_no_config, user_1_admin):
 | 
			
		||||
    client = app_no_config.test_client()
 | 
			
		||||
    resp_login = client.post(
 | 
			
		||||
        '/api/auth/login',
 | 
			
		||||
        data=json.dumps(dict(email='admin@example.com', password='12345678')),
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
    )
 | 
			
		||||
    response = client.get(
 | 
			
		||||
        '/api/config',
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
        headers=dict(
 | 
			
		||||
            Authorization='Bearer '
 | 
			
		||||
            + json.loads(resp_login.data.decode())['auth_token']
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    data = json.loads(response.data.decode())
 | 
			
		||||
 | 
			
		||||
    assert response.status_code == 500
 | 
			
		||||
    assert 'error' in data['status']
 | 
			
		||||
    assert 'Error on getting configuration.' in data['message']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_config_several_config(
 | 
			
		||||
    app, app_config, user_1_admin
 | 
			
		||||
):
 | 
			
		||||
    client = app.test_client()
 | 
			
		||||
    resp_login = client.post(
 | 
			
		||||
        '/api/auth/login',
 | 
			
		||||
        data=json.dumps(dict(email='admin@example.com', password='12345678')),
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
    )
 | 
			
		||||
    response = client.get(
 | 
			
		||||
        '/api/config',
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
        headers=dict(
 | 
			
		||||
            Authorization='Bearer '
 | 
			
		||||
            + json.loads(resp_login.data.decode())['auth_token']
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    data = json.loads(response.data.decode())
 | 
			
		||||
 | 
			
		||||
    assert response.status_code == 500
 | 
			
		||||
    assert 'error' in data['status']
 | 
			
		||||
    assert 'Error on getting configuration.' in data['message']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_update_config_as_admin(app, user_1_admin):
 | 
			
		||||
    client = app.test_client()
 | 
			
		||||
    resp_login = client.post(
 | 
			
		||||
        '/api/auth/login',
 | 
			
		||||
        data=json.dumps(dict(email='admin@example.com', password='12345678')),
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
    )
 | 
			
		||||
    response = client.patch(
 | 
			
		||||
        '/api/config',
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
        data=json.dumps(
 | 
			
		||||
            dict(registration=True, max_users=10)
 | 
			
		||||
        ),
 | 
			
		||||
        headers=dict(
 | 
			
		||||
            Authorization='Bearer '
 | 
			
		||||
            + json.loads(resp_login.data.decode())['auth_token']
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    data = json.loads(response.data.decode())
 | 
			
		||||
 | 
			
		||||
    assert response.status_code == 200
 | 
			
		||||
    assert 'success' in data['status']
 | 
			
		||||
    assert data['data']['gpx_limit_import'] == 10
 | 
			
		||||
    assert data['data']['is_registration_enabled'] is True
 | 
			
		||||
    assert data['data']['max_single_file_size'] == 1048576
 | 
			
		||||
    assert data['data']['max_zip_file_size'] == 10485760
 | 
			
		||||
    assert data['data']['max_users'] == 10
 | 
			
		||||
    assert data['data']['registration'] is True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_update_full_config_as_admin(app, user_1_admin):
 | 
			
		||||
    client = app.test_client()
 | 
			
		||||
    resp_login = client.post(
 | 
			
		||||
        '/api/auth/login',
 | 
			
		||||
        data=json.dumps(dict(email='admin@example.com', password='12345678')),
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
    )
 | 
			
		||||
    response = client.patch(
 | 
			
		||||
        '/api/config',
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
        data=json.dumps(
 | 
			
		||||
            dict(
 | 
			
		||||
                gpx_limit_import=20,
 | 
			
		||||
                max_single_file_size=10000,
 | 
			
		||||
                max_zip_file_size=25000,
 | 
			
		||||
                max_users=50,
 | 
			
		||||
                registration=True,
 | 
			
		||||
            )
 | 
			
		||||
        ),
 | 
			
		||||
        headers=dict(
 | 
			
		||||
            Authorization='Bearer '
 | 
			
		||||
            + json.loads(resp_login.data.decode())['auth_token']
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    data = json.loads(response.data.decode())
 | 
			
		||||
 | 
			
		||||
    assert response.status_code == 200
 | 
			
		||||
    assert 'success' in data['status']
 | 
			
		||||
    assert data['data']['gpx_limit_import'] == 20
 | 
			
		||||
    assert data['data']['is_registration_enabled'] is True
 | 
			
		||||
    assert data['data']['max_single_file_size'] == 10000
 | 
			
		||||
    assert data['data']['max_zip_file_size'] == 25000
 | 
			
		||||
    assert data['data']['max_users'] == 50
 | 
			
		||||
    assert data['data']['registration'] is True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_update_config_not_admin(app, user_1):
 | 
			
		||||
    client = app.test_client()
 | 
			
		||||
    resp_login = client.post(
 | 
			
		||||
        '/api/auth/login',
 | 
			
		||||
        data=json.dumps(dict(email='test@test.com', password='12345678')),
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
    )
 | 
			
		||||
    response = client.patch(
 | 
			
		||||
        '/api/config',
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
        data=json.dumps(
 | 
			
		||||
            dict(registration=True, max_users=10)
 | 
			
		||||
        ),
 | 
			
		||||
        headers=dict(
 | 
			
		||||
            Authorization='Bearer '
 | 
			
		||||
            + json.loads(resp_login.data.decode())['auth_token']
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    data = json.loads(response.data.decode())
 | 
			
		||||
 | 
			
		||||
    assert response.status_code == 403
 | 
			
		||||
    assert 'success' not in data['status']
 | 
			
		||||
    assert 'error' in data['status']
 | 
			
		||||
    assert 'You do not have permissions.' in data['message']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_update_config_invalid_payload(app, user_1_admin):
 | 
			
		||||
    client = app.test_client()
 | 
			
		||||
    resp_login = client.post(
 | 
			
		||||
        '/api/auth/login',
 | 
			
		||||
        data=json.dumps(dict(email='admin@example.com', password='12345678')),
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
    )
 | 
			
		||||
    response = client.patch(
 | 
			
		||||
        '/api/config',
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
        data=json.dumps(dict()),
 | 
			
		||||
        headers=dict(
 | 
			
		||||
            Authorization='Bearer '
 | 
			
		||||
            + json.loads(resp_login.data.decode())['auth_token']
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    data = json.loads(response.data.decode())
 | 
			
		||||
 | 
			
		||||
    assert response.status_code == 400
 | 
			
		||||
    assert 'error' in data['status']
 | 
			
		||||
    assert 'Invalid payload.' in data['message']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_update_config_no_config(app_no_config, user_1_admin):
 | 
			
		||||
    client = app_no_config.test_client()
 | 
			
		||||
    resp_login = client.post(
 | 
			
		||||
        '/api/auth/login',
 | 
			
		||||
        data=json.dumps(dict(email='admin@example.com', password='12345678')),
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
    )
 | 
			
		||||
    response = client.patch(
 | 
			
		||||
        '/api/config',
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
        data=json.dumps(
 | 
			
		||||
            dict(registration=True, max_users=10)
 | 
			
		||||
        ),
 | 
			
		||||
        headers=dict(
 | 
			
		||||
            Authorization='Bearer '
 | 
			
		||||
            + json.loads(resp_login.data.decode())['auth_token']
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    data = json.loads(response.data.decode())
 | 
			
		||||
 | 
			
		||||
    assert response.status_code == 500
 | 
			
		||||
    assert 'error' in data['status']
 | 
			
		||||
    assert 'Error on updating configuration.' in data['message']
 | 
			
		||||
							
								
								
									
										14
									
								
								fittrackee_api/fittrackee_api/tests/test_app_config_model.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								fittrackee_api/fittrackee_api/tests/test_app_config_model.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
from fittrackee_api.application.models import AppConfig
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_application_config(app):
 | 
			
		||||
    app_config = AppConfig.query.first()
 | 
			
		||||
    assert 1 == app_config.id
 | 
			
		||||
 | 
			
		||||
    serialized_app_config = app_config.serialize()
 | 
			
		||||
    assert serialized_app_config['gpx_limit_import'] == 10
 | 
			
		||||
    assert serialized_app_config['is_registration_enabled'] is True
 | 
			
		||||
    assert serialized_app_config['max_single_file_size'] == 1048576
 | 
			
		||||
    assert serialized_app_config['max_zip_file_size'] == 10485760
 | 
			
		||||
    assert serialized_app_config['max_users'] == 10
 | 
			
		||||
    assert serialized_app_config['registration'] is True
 | 
			
		||||
@@ -281,6 +281,46 @@ def test_user_registration_not_allowed(app_no_registration):
 | 
			
		||||
    assert data['message'] == 'Error. Registration is disabled.'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_user_registration_max_users_exceeded(
 | 
			
		||||
    app, user_1_admin, user_2, user_3
 | 
			
		||||
):
 | 
			
		||||
    client = app.test_client()
 | 
			
		||||
 | 
			
		||||
    resp_login = client.post(
 | 
			
		||||
        '/api/auth/login',
 | 
			
		||||
        data=json.dumps(dict(email='admin@example.com', password='12345678')),
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
    )
 | 
			
		||||
    client.patch(
 | 
			
		||||
        '/api/config',
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
        data=json.dumps(dict(max_users=3, registration=True)),
 | 
			
		||||
        headers=dict(
 | 
			
		||||
            Authorization='Bearer '
 | 
			
		||||
            + json.loads(resp_login.data.decode())['auth_token']
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    response = client.post(
 | 
			
		||||
        '/api/auth/register',
 | 
			
		||||
        data=json.dumps(
 | 
			
		||||
            dict(
 | 
			
		||||
                username='user4',
 | 
			
		||||
                email='user4@test.com',
 | 
			
		||||
                password='12345678',
 | 
			
		||||
                password_conf='12345678',
 | 
			
		||||
            )
 | 
			
		||||
        ),
 | 
			
		||||
        content_type='application/json',
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    assert response.content_type == 'application/json'
 | 
			
		||||
    assert response.status_code == 403
 | 
			
		||||
    data = json.loads(response.data.decode())
 | 
			
		||||
    assert data['status'] == 'error'
 | 
			
		||||
    assert data['message'] == 'Error. Registration is disabled.'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_login_registered_user(app, user_1):
 | 
			
		||||
    client = app.test_client()
 | 
			
		||||
    response = client.post(
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ def register_user():
 | 
			
		||||
        Error. Please try again or contact the administrator.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    if not current_app.config.get('REGISTRATION_ALLOWED'):
 | 
			
		||||
    if not current_app.config.get('is_registration_enabled'):
 | 
			
		||||
        response_object = {
 | 
			
		||||
            'status': 'error',
 | 
			
		||||
            'message': 'Error. Registration is disabled.',
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@ def verify_extension_and_size(file_type, req):
 | 
			
		||||
        if '.' in file.filename
 | 
			
		||||
        else None
 | 
			
		||||
    )
 | 
			
		||||
    max_file_size = current_app.config['MAX_SINGLE_FILE']
 | 
			
		||||
    max_file_size = current_app.config['max_single_file_size']
 | 
			
		||||
 | 
			
		||||
    if not (
 | 
			
		||||
        file_extension
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
"""empty message
 | 
			
		||||
 | 
			
		||||
Revision ID: 8a0aad4c838c
 | 
			
		||||
Revises: 1345afe3b11d
 | 
			
		||||
Create Date: 2019-11-13 13:14:20.147296
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from alembic import op
 | 
			
		||||
import sqlalchemy as sa
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# revision identifiers, used by Alembic.
 | 
			
		||||
revision = '8a0aad4c838c'
 | 
			
		||||
down_revision = '1345afe3b11d'
 | 
			
		||||
branch_labels = None
 | 
			
		||||
depends_on = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def upgrade():
 | 
			
		||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
			
		||||
    op.create_table('app_config',
 | 
			
		||||
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
 | 
			
		||||
    sa.Column('registration', sa.Boolean(), nullable=False),
 | 
			
		||||
    sa.Column('max_users', sa.Integer(), nullable=False),
 | 
			
		||||
    sa.Column('gpx_limit_import', sa.Integer(), nullable=False),
 | 
			
		||||
    sa.Column('max_single_file_size', sa.Integer(), nullable=False),
 | 
			
		||||
    sa.Column('max_zip_file_size', sa.Integer(), nullable=False),
 | 
			
		||||
    sa.PrimaryKeyConstraint('id')
 | 
			
		||||
    )
 | 
			
		||||
    # ### end Alembic commands ###
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def downgrade():
 | 
			
		||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
			
		||||
    op.drop_table('app_config')
 | 
			
		||||
    # ### end Alembic commands ###
 | 
			
		||||
@@ -3,6 +3,7 @@ import shutil
 | 
			
		||||
from fittrackee_api import create_app, db
 | 
			
		||||
from fittrackee_api.activities.models import Activity, Sport
 | 
			
		||||
from fittrackee_api.activities.utils import update_activity
 | 
			
		||||
from fittrackee_api.application.utils import init_config
 | 
			
		||||
from fittrackee_api.users.models import User
 | 
			
		||||
from tqdm import tqdm
 | 
			
		||||
 | 
			
		||||
@@ -75,5 +76,19 @@ def recalculate():
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.cli.command('init-app-config')
 | 
			
		||||
def init_app_config():
 | 
			
		||||
    """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."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    app.run()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user