API & Client: set file size limit for upload - fix #33
This commit is contained in:
		@@ -1,6 +1,8 @@
 | 
				
			|||||||
export REACT_APP_API_URL = http://$(HOST):$(API_PORT)
 | 
					export REACT_APP_API_URL = http://$(HOST):$(API_PORT)
 | 
				
			||||||
export REACT_APP_THUNDERFOREST_API_KEY=
 | 
					export REACT_APP_THUNDERFOREST_API_KEY=
 | 
				
			||||||
export REACT_APP_GPX_LIMIT_IMPORT=10
 | 
					export REACT_APP_GPX_LIMIT_IMPORT=10
 | 
				
			||||||
 | 
					export REACT_APP_MAX_SINGLE_FILE_SIZE=1048576
 | 
				
			||||||
 | 
					export REACT_APP_MAX_ZIP_FILE_SIZE=10485760
 | 
				
			||||||
export REACT_APP_ALLOW_REGISTRATION=true
 | 
					export REACT_APP_ALLOW_REGISTRATION=true
 | 
				
			||||||
export WEATHER_API=
 | 
					export WEATHER_API=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -498,6 +498,7 @@
 | 
				
			|||||||
<li><p>Invalid token. Please log in again.</p></li>
 | 
					<li><p>Invalid token. Please log in again.</p></li>
 | 
				
			||||||
</ul>
 | 
					</ul>
 | 
				
			||||||
</p></li>
 | 
					</p></li>
 | 
				
			||||||
 | 
					<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.14">413 Request Entity Too Large</a> – Error during picture update: file size exceeds 1.0MB.</p></li>
 | 
				
			||||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> – </p></li>
 | 
					<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> – </p></li>
 | 
				
			||||||
</ul>
 | 
					</ul>
 | 
				
			||||||
</dd>
 | 
					</dd>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -488,6 +488,7 @@
 | 
				
			|||||||
<li><p>Invalid token. Please log in again.</p></li>
 | 
					<li><p>Invalid token. Please log in again.</p></li>
 | 
				
			||||||
</ul>
 | 
					</ul>
 | 
				
			||||||
</p></li>
 | 
					</p></li>
 | 
				
			||||||
 | 
					<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.14">413 Request Entity Too Large</a> – Error during picture update: file size exceeds 1.0MB.</p></li>
 | 
				
			||||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> – Error during picture update.</p></li>
 | 
					<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> – Error during picture update.</p></li>
 | 
				
			||||||
</ul>
 | 
					</ul>
 | 
				
			||||||
</dd>
 | 
					</dd>
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -11,7 +11,7 @@ from ..users.utils import (
 | 
				
			|||||||
    User,
 | 
					    User,
 | 
				
			||||||
    authenticate,
 | 
					    authenticate,
 | 
				
			||||||
    can_view_activity,
 | 
					    can_view_activity,
 | 
				
			||||||
    verify_extension,
 | 
					    verify_extension_and_size,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .models import Activity
 | 
					from .models import Activity
 | 
				
			||||||
from .utils import (
 | 
					from .utils import (
 | 
				
			||||||
@@ -818,12 +818,15 @@ def post_activity(auth_user_id):
 | 
				
			|||||||
        - Provide a valid auth token.
 | 
					        - Provide a valid auth token.
 | 
				
			||||||
        - Signature expired. Please log in again.
 | 
					        - Signature expired. Please log in again.
 | 
				
			||||||
        - Invalid token. Please log in again.
 | 
					        - Invalid token. Please log in again.
 | 
				
			||||||
 | 
					    :statuscode 413: Error during picture update: file size exceeds 1.0MB.
 | 
				
			||||||
    :statuscode 500:
 | 
					    :statuscode 500:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    response_object = verify_extension('activity', request)
 | 
					    response_object, response_code = verify_extension_and_size(
 | 
				
			||||||
 | 
					        'activity', request
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    if response_object['status'] != 'success':
 | 
					    if response_object['status'] != 'success':
 | 
				
			||||||
        return jsonify(response_object), 400
 | 
					        return jsonify(response_object), response_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    activity_data = json.loads(request.form["data"])
 | 
					    activity_data = json.loads(request.form["data"])
 | 
				
			||||||
    if not activity_data or activity_data.get('sport_id') is None:
 | 
					    if not activity_data or activity_data.get('sport_id') is None:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,8 @@ 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"""
 | 
				
			||||||
@@ -13,6 +15,14 @@ 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 = (
 | 
					    REGISTRATION_ALLOWED = (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,11 +4,17 @@ import os
 | 
				
			|||||||
from fittrackee_api import appLog, bcrypt, db
 | 
					from fittrackee_api import appLog, bcrypt, db
 | 
				
			||||||
from flask import Blueprint, current_app, jsonify, request
 | 
					from flask import Blueprint, current_app, jsonify, request
 | 
				
			||||||
from sqlalchemy import exc, or_
 | 
					from sqlalchemy import exc, or_
 | 
				
			||||||
 | 
					from werkzeug.exceptions import RequestEntityTooLarge
 | 
				
			||||||
from werkzeug.utils import secure_filename
 | 
					from werkzeug.utils import secure_filename
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ..activities.utils_files import get_absolute_file_path
 | 
					from ..activities.utils_files import get_absolute_file_path
 | 
				
			||||||
from .models import User
 | 
					from .models import User
 | 
				
			||||||
from .utils import authenticate, register_controls, verify_extension
 | 
					from .utils import (
 | 
				
			||||||
 | 
					    authenticate,
 | 
				
			||||||
 | 
					    display_readable_file_size,
 | 
				
			||||||
 | 
					    register_controls,
 | 
				
			||||||
 | 
					    verify_extension_and_size,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
auth_blueprint = Blueprint('auth', __name__)
 | 
					auth_blueprint = Blueprint('auth', __name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -529,13 +535,25 @@ def edit_picture(user_id):
 | 
				
			|||||||
        - Provide a valid auth token.
 | 
					        - Provide a valid auth token.
 | 
				
			||||||
        - Signature expired. Please log in again.
 | 
					        - Signature expired. Please log in again.
 | 
				
			||||||
        - Invalid token. Please log in again.
 | 
					        - Invalid token. Please log in again.
 | 
				
			||||||
 | 
					    :statuscode 413: Error during picture update: file size exceeds 1.0MB.
 | 
				
			||||||
    :statuscode 500: Error during picture update.
 | 
					    :statuscode 500: Error during picture update.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    code = 400
 | 
					    try:
 | 
				
			||||||
    response_object = verify_extension('picture', request)
 | 
					        response_object, response_code = verify_extension_and_size(
 | 
				
			||||||
 | 
					            'picture', request
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except RequestEntityTooLarge as e:
 | 
				
			||||||
 | 
					        appLog.error(e)
 | 
				
			||||||
 | 
					        max_file_size = current_app.config['MAX_CONTENT_LENGTH']
 | 
				
			||||||
 | 
					        response_object = {
 | 
				
			||||||
 | 
					            'status': 'fail',
 | 
				
			||||||
 | 
					            'message': 'Error during picture update: file size exceeds '
 | 
				
			||||||
 | 
					            f'{display_readable_file_size(max_file_size)}.',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return jsonify(response_object), 413
 | 
				
			||||||
    if response_object['status'] != 'success':
 | 
					    if response_object['status'] != 'success':
 | 
				
			||||||
        return jsonify(response_object), code
 | 
					        return jsonify(response_object), response_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    file = request.files['file']
 | 
					    file = request.files['file']
 | 
				
			||||||
    filename = secure_filename(file.filename)
 | 
					    filename = secure_filename(file.filename)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,17 +29,18 @@ def register_controls(username, email, password, password_conf):
 | 
				
			|||||||
    return ret
 | 
					    return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def verify_extension(file_type, req):
 | 
					def verify_extension_and_size(file_type, req):
 | 
				
			||||||
    response_object = {'status': 'success'}
 | 
					    response_object = {'status': 'success'}
 | 
				
			||||||
 | 
					    code = 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if 'file' not in req.files:
 | 
					    if 'file' not in req.files:
 | 
				
			||||||
        response_object = {'status': 'fail', 'message': 'No file part.'}
 | 
					        response_object = {'status': 'fail', 'message': 'No file part.'}
 | 
				
			||||||
        return response_object
 | 
					        return response_object, code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    file = req.files['file']
 | 
					    file = req.files['file']
 | 
				
			||||||
    if file.filename == '':
 | 
					    if file.filename == '':
 | 
				
			||||||
        response_object = {'status': 'fail', 'message': 'No selected file.'}
 | 
					        response_object = {'status': 'fail', 'message': 'No selected file.'}
 | 
				
			||||||
        return response_object
 | 
					        return response_object, code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    allowed_extensions = (
 | 
					    allowed_extensions = (
 | 
				
			||||||
        'ACTIVITY_ALLOWED_EXTENSIONS'
 | 
					        'ACTIVITY_ALLOWED_EXTENSIONS'
 | 
				
			||||||
@@ -47,17 +48,30 @@ def verify_extension(file_type, req):
 | 
				
			|||||||
        else 'PICTURE_ALLOWED_EXTENSIONS'
 | 
					        else 'PICTURE_ALLOWED_EXTENSIONS'
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    file_extension = (
 | 
				
			||||||
 | 
					        file.filename.rsplit('.', 1)[1].lower()
 | 
				
			||||||
 | 
					        if '.' in file.filename
 | 
				
			||||||
 | 
					        else None
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    max_file_size = current_app.config['MAX_SINGLE_FILE']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not (
 | 
					    if not (
 | 
				
			||||||
        '.' in file.filename
 | 
					        file_extension
 | 
				
			||||||
        and file.filename.rsplit('.', 1)[1].lower()
 | 
					        and file_extension in current_app.config.get(allowed_extensions)
 | 
				
			||||||
        in current_app.config.get(allowed_extensions)
 | 
					 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
        response_object = {
 | 
					        response_object = {
 | 
				
			||||||
            'status': 'fail',
 | 
					            'status': 'fail',
 | 
				
			||||||
            'message': 'File extension not allowed.',
 | 
					            'message': 'File extension not allowed.',
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    elif file_extension != 'zip' and req.content_length > max_file_size:
 | 
				
			||||||
 | 
					        response_object = {
 | 
				
			||||||
 | 
					            'status': 'fail',
 | 
				
			||||||
 | 
					            'message': 'Error during picture update: file size exceeds '
 | 
				
			||||||
 | 
					            f'{display_readable_file_size(max_file_size)}.',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        code = 413
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return response_object
 | 
					    return response_object, code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def verify_user(current_request, verify_admin):
 | 
					def verify_user(current_request, verify_admin):
 | 
				
			||||||
@@ -116,3 +130,15 @@ def can_view_activity(auth_user_id, activity_user_id):
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return response_object, 403
 | 
					        return response_object, 403
 | 
				
			||||||
    return None, None
 | 
					    return None, None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def display_readable_file_size(size_in_bytes):
 | 
				
			||||||
 | 
					    if size_in_bytes == 0:
 | 
				
			||||||
 | 
					        return '0 bytes'
 | 
				
			||||||
 | 
					    if size_in_bytes == 1:
 | 
				
			||||||
 | 
					        return '1 byte'
 | 
				
			||||||
 | 
					    for unit in [' bytes', 'KB', 'MB', 'GB', 'TB']:
 | 
				
			||||||
 | 
					        if abs(size_in_bytes) < 1024.0:
 | 
				
			||||||
 | 
					            return f"{size_in_bytes:3.1f}{unit}"
 | 
				
			||||||
 | 
					        size_in_bytes /= 1024.0
 | 
				
			||||||
 | 
					    return f"{size_in_bytes} bytes"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,11 +4,14 @@ import { connect } from 'react-redux'
 | 
				
			|||||||
import { setLoading } from '../../../actions/index'
 | 
					import { setLoading } from '../../../actions/index'
 | 
				
			||||||
import { addActivity, editActivity } from '../../../actions/activities'
 | 
					import { addActivity, editActivity } from '../../../actions/activities'
 | 
				
			||||||
import { history } from '../../../index'
 | 
					import { history } from '../../../index'
 | 
				
			||||||
import { gpxLimit } from '../../../utils'
 | 
					import { fileSizeLimit, gpxLimit, zipSizeLimit } from '../../../utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function FormWithGpx(props) {
 | 
					function FormWithGpx(props) {
 | 
				
			||||||
  const { activity, loading, onAddActivity, onEditActivity, sports } = props
 | 
					  const { activity, loading, onAddActivity, onEditActivity, sports } = props
 | 
				
			||||||
  const sportId = activity ? activity.sport_id : ''
 | 
					  const sportId = activity ? activity.sport_id : ''
 | 
				
			||||||
 | 
					  // prettier-ignore
 | 
				
			||||||
 | 
					  const zipTooltip =
 | 
				
			||||||
 | 
					    `no folder inside, ${gpxLimit} files max, max size: ${zipSizeLimit}`
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <form
 | 
					    <form
 | 
				
			||||||
      encType="multipart/form-data"
 | 
					      encType="multipart/form-data"
 | 
				
			||||||
@@ -49,10 +52,28 @@ function FormWithGpx(props) {
 | 
				
			|||||||
      ) : (
 | 
					      ) : (
 | 
				
			||||||
        <div className="form-group">
 | 
					        <div className="form-group">
 | 
				
			||||||
          <label>
 | 
					          <label>
 | 
				
			||||||
            {/* prettier-ignore */}
 | 
					            <strong>gpx</strong> file
 | 
				
			||||||
            <strong>gpx</strong> file or <strong>zip</strong> file containing
 | 
					            <sup>
 | 
				
			||||||
            {/* prettier-ignore */}
 | 
					              <i
 | 
				
			||||||
            <strong> gpx</strong> (no folder inside, {gpxLimit} files max):
 | 
					                className="fa fa-question-circle"
 | 
				
			||||||
 | 
					                aria-hidden="true"
 | 
				
			||||||
 | 
					                data-toggle="tooltip"
 | 
				
			||||||
 | 
					                data-placement="top"
 | 
				
			||||||
 | 
					                title={`max size: ${fileSizeLimit}`}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </sup>{' '}
 | 
				
			||||||
 | 
					            or <strong> zip</strong> file containing <strong>gpx </strong>
 | 
				
			||||||
 | 
					            files
 | 
				
			||||||
 | 
					            <sup>
 | 
				
			||||||
 | 
					              <i
 | 
				
			||||||
 | 
					                className="fa fa-question-circle"
 | 
				
			||||||
 | 
					                aria-hidden="true"
 | 
				
			||||||
 | 
					                data-toggle="tooltip"
 | 
				
			||||||
 | 
					                data-placement="top"
 | 
				
			||||||
 | 
					                title={zipTooltip}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </sup>{' '}
 | 
				
			||||||
 | 
					            :
 | 
				
			||||||
            <input
 | 
					            <input
 | 
				
			||||||
              accept=".gpx, .zip"
 | 
					              accept=".gpx, .zip"
 | 
				
			||||||
              className="form-control form-control-file gpx-file"
 | 
					              className="form-control form-control-file gpx-file"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ import { connect } from 'react-redux'
 | 
				
			|||||||
import { Link } from 'react-router-dom'
 | 
					import { Link } from 'react-router-dom'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { deletePicture, uploadPicture } from '../../actions/user'
 | 
					import { deletePicture, uploadPicture } from '../../actions/user'
 | 
				
			||||||
import { apiUrl } from '../../utils'
 | 
					import { apiUrl, fileSizeLimit } from '../../utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function Profile({ message, onDeletePicture, onUploadPicture, user }) {
 | 
					function Profile({ message, onDeletePicture, onUploadPicture, user }) {
 | 
				
			||||||
  const createdAt = user.created_at
 | 
					  const createdAt = user.created_at
 | 
				
			||||||
@@ -76,7 +76,8 @@ function Profile({ message, onDeletePicture, onUploadPicture, user }) {
 | 
				
			|||||||
                        accept=".png,.jpg,.gif"
 | 
					                        accept=".png,.jpg,.gif"
 | 
				
			||||||
                      />
 | 
					                      />
 | 
				
			||||||
                      <br />
 | 
					                      <br />
 | 
				
			||||||
                      <button type="submit">Send</button>
 | 
					                      <button type="submit">Send</button> (max. size:{' '}
 | 
				
			||||||
 | 
					                      {fileSizeLimit})
 | 
				
			||||||
                    </form>
 | 
					                    </form>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,15 @@
 | 
				
			|||||||
import { format, parse } from 'date-fns'
 | 
					import { format, parse } from 'date-fns'
 | 
				
			||||||
import { DateTime } from 'luxon'
 | 
					import { DateTime } from 'luxon'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const suffixes = ['bytes', 'KB', 'MB', 'GB', 'TB']
 | 
				
			||||||
 | 
					const getFileSize = fileSize => {
 | 
				
			||||||
 | 
					  const i = Math.floor(Math.log(fileSize) / Math.log(1024))
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    (!fileSize && '0 bytes') ||
 | 
				
			||||||
 | 
					    `${(fileSize / Math.pow(1024, i)).toFixed(1)}${suffixes[i]}`
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const version = '0.2.1-beta' // version stored in 'utils' for now
 | 
					export const version = '0.2.1-beta' // version stored in 'utils' for now
 | 
				
			||||||
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
 | 
					export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
 | 
				
			||||||
/* prettier-ignore */
 | 
					/* prettier-ignore */
 | 
				
			||||||
@@ -8,6 +17,12 @@ export const thunderforestApiKey = `${
 | 
				
			|||||||
  process.env.REACT_APP_THUNDERFOREST_API_KEY
 | 
					  process.env.REACT_APP_THUNDERFOREST_API_KEY
 | 
				
			||||||
}`
 | 
					}`
 | 
				
			||||||
export const gpxLimit = `${process.env.REACT_APP_GPX_LIMIT_IMPORT}`
 | 
					export const gpxLimit = `${process.env.REACT_APP_GPX_LIMIT_IMPORT}`
 | 
				
			||||||
 | 
					export const fileSizeLimit = getFileSize(
 | 
				
			||||||
 | 
					  +process.env.REACT_APP_MAX_SINGLE_FILE_SIZE
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					export const zipSizeLimit = getFileSize(
 | 
				
			||||||
 | 
					  +process.env.REACT_APP_MAX_ZIP_FILE_SIZE
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
export const isRegistrationAllowed =
 | 
					export const isRegistrationAllowed =
 | 
				
			||||||
  process.env.REACT_APP_ALLOW_REGISTRATION !== 'false'
 | 
					  process.env.REACT_APP_ALLOW_REGISTRATION !== 'false'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user