API - move some app configuration parameters in database
This commit is contained in:
parent
6447d22d97
commit
649b773ba9
3
Makefile
3
Makefile
@ -29,6 +29,9 @@ install-db:
|
|||||||
$(FLASK) db upgrade --directory $(MIGRATIONS)
|
$(FLASK) db upgrade --directory $(MIGRATIONS)
|
||||||
$(FLASK) initdata
|
$(FLASK) initdata
|
||||||
|
|
||||||
|
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)
|
||||||
|
@ -26,12 +26,26 @@ def create_app():
|
|||||||
bcrypt.init_app(app)
|
bcrypt.init_app(app)
|
||||||
migrate.init_app(app, db)
|
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.auth import auth_blueprint # noqa
|
||||||
from .users.users import users_blueprint # noqa
|
from .users.users import users_blueprint # noqa
|
||||||
from .activities.activities import activities_blueprint # noqa
|
from .activities.activities import activities_blueprint # noqa
|
||||||
from .activities.records import records_blueprint # noqa
|
from .activities.records import records_blueprint # noqa
|
||||||
from .activities.sports import sports_blueprint # noqa
|
from .activities.sports import sports_blueprint # noqa
|
||||||
from .activities.stats import stats_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(users_blueprint, url_prefix='/api')
|
||||||
app.register_blueprint(auth_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(records_blueprint, url_prefix='/api')
|
||||||
app.register_blueprint(sports_blueprint, url_prefix='/api')
|
app.register_blueprint(sports_blueprint, url_prefix='/api')
|
||||||
app.register_blueprint(stats_blueprint, url_prefix='/api')
|
app.register_blueprint(stats_blueprint, url_prefix='/api')
|
||||||
|
app.register_blueprint(config_blueprint, url_prefix='/api')
|
||||||
|
|
||||||
if app.debug:
|
if app.debug:
|
||||||
logging.getLogger('sqlalchemy').setLevel(logging.WARNING)
|
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
|
from flask import current_app
|
||||||
|
|
||||||
MAX_FILE_SIZE = 1 * 1024 * 1024 # 1MB
|
|
||||||
|
|
||||||
|
|
||||||
class BaseConfig:
|
class BaseConfig:
|
||||||
"""Base configuration"""
|
"""Base configuration"""
|
||||||
@ -15,19 +13,8 @@ class BaseConfig:
|
|||||||
TOKEN_EXPIRATION_DAYS = 30
|
TOKEN_EXPIRATION_DAYS = 30
|
||||||
TOKEN_EXPIRATION_SECONDS = 0
|
TOKEN_EXPIRATION_SECONDS = 0
|
||||||
UPLOAD_FOLDER = os.path.join(current_app.root_path, 'uploads')
|
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'}
|
PICTURE_ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'}
|
||||||
ACTIVITY_ALLOWED_EXTENSIONS = {'gpx', 'zip'}
|
ACTIVITY_ALLOWED_EXTENSIONS = {'gpx', 'zip'}
|
||||||
REGISTRATION_ALLOWED = (
|
|
||||||
False if os.getenv('REACT_APP_ALLOW_REGISTRATION') == "false" else True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DevelopmentConfig(BaseConfig):
|
class DevelopmentConfig(BaseConfig):
|
||||||
|
@ -4,16 +4,58 @@ import os
|
|||||||
import pytest
|
import pytest
|
||||||
from fittrackee_api import create_app, db
|
from fittrackee_api import create_app, db
|
||||||
from fittrackee_api.activities.models import Activity, ActivitySegment, Sport
|
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
|
from fittrackee_api.users.models import User
|
||||||
|
|
||||||
os.environ["FLASK_ENV"] = 'testing'
|
os.environ["FLASK_ENV"] = 'testing'
|
||||||
os.environ["APP_SETTINGS"] = 'fittrackee_api.config.TestingConfig'
|
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
|
@pytest.fixture
|
||||||
def app():
|
def app():
|
||||||
app = create_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():
|
with app.app_context():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
yield app
|
yield app
|
||||||
@ -29,9 +71,10 @@ def app():
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def app_no_registration():
|
def app_no_registration():
|
||||||
app = create_app()
|
app = create_app()
|
||||||
app.config['REGISTRATION_ALLOWED'] = False
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
app_db_config = app_config_registration_disabled()
|
||||||
|
update_app_config_from_database(app, app_db_config)
|
||||||
yield app
|
yield app
|
||||||
db.session.remove()
|
db.session.remove()
|
||||||
db.drop_all()
|
db.drop_all()
|
||||||
@ -39,6 +82,19 @@ def app_no_registration():
|
|||||||
return app
|
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()
|
@pytest.fixture()
|
||||||
def user_1():
|
def user_1():
|
||||||
user = User(username='test', email='test@test.com', password='12345678')
|
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.'
|
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):
|
def test_login_registered_user(app, user_1):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
response = client.post(
|
response = client.post(
|
||||||
|
@ -78,7 +78,7 @@ def register_user():
|
|||||||
Error. Please try again or contact the administrator.
|
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 = {
|
response_object = {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': 'Error. Registration is disabled.',
|
'message': 'Error. Registration is disabled.',
|
||||||
|
@ -53,7 +53,7 @@ def verify_extension_and_size(file_type, req):
|
|||||||
if '.' in file.filename
|
if '.' in file.filename
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
max_file_size = current_app.config['MAX_SINGLE_FILE']
|
max_file_size = current_app.config['max_single_file_size']
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
file_extension
|
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 import create_app, db
|
||||||
from fittrackee_api.activities.models import Activity, Sport
|
from fittrackee_api.activities.models import Activity, Sport
|
||||||
from fittrackee_api.activities.utils import update_activity
|
from fittrackee_api.activities.utils import update_activity
|
||||||
|
from fittrackee_api.application.utils import init_config
|
||||||
from fittrackee_api.users.models import User
|
from fittrackee_api.users.models import User
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
@ -75,5 +76,19 @@ def recalculate():
|
|||||||
db.session.commit()
|
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__':
|
if __name__ == '__main__':
|
||||||
app.run()
|
app.run()
|
||||||
|
Loading…
Reference in New Issue
Block a user