API & Client - archive max size must be greater than files size - #70

This commit is contained in:
Sam 2021-02-20 14:14:26 +01:00
parent ae53aaecd7
commit c4e579dd25
14 changed files with 92 additions and 23 deletions

View File

@ -130,6 +130,11 @@ def update_application_config(auth_user_id: int) -> Union[Dict, HttpResponse]:
if 'max_users' in config_data:
config.max_users = config_data.get('max_users')
if config.max_zip_file_size < config.max_single_file_size:
return InvalidPayloadErrorResponse(
'Max. size of zip archive must be equal or greater than '
'max. size of uploaded files'
)
db.session.commit()
update_app_config_from_database(current_app, config)
return {'status': 'success', 'data': config.serialize()}

View File

@ -1,8 +1,8 @@
{
"files": {
"main.css": "/static/css/main.376b8924.chunk.css",
"main.js": "/static/js/main.8faa878d.chunk.js",
"main.js.map": "/static/js/main.8faa878d.chunk.js.map",
"main.js": "/static/js/main.7c5c861a.chunk.js",
"main.js.map": "/static/js/main.7c5c861a.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.1240af94.js",
"runtime-main.js.map": "/static/js/runtime-main.1240af94.js.map",
"static/js/2.301144a0.chunk.js": "/static/js/2.301144a0.chunk.js",
@ -19,6 +19,6 @@
"static/js/runtime-main.1240af94.js",
"static/js/2.301144a0.chunk.js",
"static/css/main.376b8924.chunk.css",
"static/js/main.8faa878d.chunk.js"
"static/js/main.7c5c861a.chunk.js"
]
}

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/foundation-icons/3.0/foundation-icons.min.css"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""><title>FitTrackee</title><link href="/static/css/main.376b8924.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script><script type="text/javascript">$(document).ready((function(){$("li.nav-item").click((function(){$("button.navbar-toggler").toggleClass("collapsed"),$("#navbarSupportedContent").toggleClass("show")}))}))</script><script>!function(e){function t(t){for(var n,i,l=t[0],f=t[1],a=t[2],p=0,s=[];p<l.length;p++)i=l[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(t);s.length;)s.shift()();return u.push.apply(u,a||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,l=1;l<r.length;l++){var f=r[l];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},o={1:0},u=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/";var l=this.webpackJsonpfittrackee_client=this.webpackJsonpfittrackee_client||[],f=l.push.bind(l);l.push=t,l=l.slice();for(var a=0;a<l.length;a++)t(l[a]);var c=f;r()}([])</script><script src="/static/js/2.301144a0.chunk.js"></script><script src="/static/js/main.8faa878d.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/foundation-icons/3.0/foundation-icons.min.css"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""><title>FitTrackee</title><link href="/static/css/main.376b8924.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script><script type="text/javascript">$(document).ready((function(){$("li.nav-item").click((function(){$("button.navbar-toggler").toggleClass("collapsed"),$("#navbarSupportedContent").toggleClass("show")}))}))</script><script>!function(e){function t(t){for(var n,i,l=t[0],f=t[1],a=t[2],p=0,s=[];p<l.length;p++)i=l[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(t);s.length;)s.shift()();return u.push.apply(u,a||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,l=1;l<r.length;l++){var f=r[l];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},o={1:0},u=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/";var l=this.webpackJsonpfittrackee_client=this.webpackJsonpfittrackee_client||[],f=l.push.bind(l);l.push=t,l=l.slice();for(var a=0;a<l.length;a++)t(l[a]);var c=f;r()}([])</script><script src="/static/js/2.301144a0.chunk.js"></script><script src="/static/js/main.7c5c861a.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -239,3 +239,40 @@ class TestUpdateConfig:
assert response.status_code == 500
assert 'error' in data['status']
assert 'Error on updating configuration.' in data['message']
def test_it_raises_error_if_archive_max_size_is_below_files_max_size(
self, app: Flask, user_1_admin: User
) -> None:
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=1000,
max_users=50,
)
),
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 (
'Max. size of zip archive must be equal or greater than max. size '
'of uploaded files'
) in data['message']

View File

@ -1,4 +1,5 @@
import FitTrackeeGenericApi from '../fitTrackeeApi'
import { history } from '../index'
import { setError } from './index'
export const setAppConfig = data => ({
@ -31,6 +32,7 @@ export const updateAppConfig = formData => dispatch =>
.then(ret => {
if (ret.status === 'success') {
dispatch(setAppConfig(ret.data))
history.push('/admin/application')
} else {
dispatch(setError(`application|${ret.message}`))
}

View File

@ -2,7 +2,7 @@ import React from 'react'
import { connect } from 'react-redux'
import Message from '../Common/Message'
import { updateAppConfig } from '../../actions/application'
import { getAppData, updateAppConfig } from '../../actions/application'
import { history } from '../../index'
import { getFileSizeInMB } from '../../utils'
@ -11,7 +11,6 @@ class AdminApplication extends React.Component {
super(props, context)
this.state = {
formData: {},
isInEdition: false,
}
}
@ -44,15 +43,15 @@ class AdminApplication extends React.Component {
this.setState(formData)
}
toggleInEdition(e) {
e.preventDefault()
const { isInEdition } = this.state
this.setState({ isInEdition: !isInEdition })
}
render() {
const { message, onHandleConfigFormSubmit, t } = this.props
const { formData, isInEdition } = this.state
const {
isInEdition,
loadAppConfig,
message,
onHandleConfigFormSubmit,
t,
} = this.props
const { formData } = this.state
return (
<div>
{message && <Message message={message} t={t} />}
@ -71,7 +70,7 @@ class AdminApplication extends React.Component {
isInEdition ? '' : 'form-disabled'
}`}
onSubmit={e => {
this.toggleInEdition(e)
e.preventDefault()
onHandleConfigFormSubmit(formData)
}}
>
@ -169,7 +168,11 @@ class AdminApplication extends React.Component {
<input
type="submit"
className="btn btn-secondary"
onClick={e => this.toggleInEdition(e)}
onClick={e => {
e.preventDefault()
loadAppConfig()
history.push('/admin/application')
}}
value={t('common:Cancel')}
/>
</>
@ -179,7 +182,8 @@ class AdminApplication extends React.Component {
type="submit"
className="btn btn-primary"
onClick={e => {
this.toggleInEdition(e)
e.preventDefault()
history.push('/admin/application/edit')
}}
value={t('common:Edit')}
/>
@ -207,6 +211,9 @@ export default connect(
message: state.message,
}),
dispatch => ({
loadAppConfig: () => {
dispatch(getAppData('config'))
},
onHandleConfigFormSubmit: formData => {
const data = Object.assign({}, formData)
data.max_single_file_size *= 1048576

View File

@ -28,7 +28,20 @@ function Admin(props) {
<Route
exact
path="/admin/application"
render={() => <AdminApplication appConfig={appConfig} t={t} />}
render={() => (
<AdminApplication
appConfig={appConfig}
t={t}
isInEdition={false}
/>
)}
/>
<Route
exact
path="/admin/application/edit"
render={() => (
<AdminApplication appConfig={appConfig} t={t} isInEdition />
)}
/>
<Route
exact

View File

@ -2,7 +2,7 @@
"3 to 12 characters required for username.": "3 to 12 characters required for username.",
"8 characters required for password.": "8 characters required for password.",
"An error occurred. Please contact the administrator.": "An error occurred. Please contact the administrator.",
"workouts": "workouts",
"application": "application",
"Error during picture deletion.": "Error during picture deletion.",
"Error during picture update.": "Error during picture update.",
"Error during picture update, file size exceeds max size.": "Error during picture update, file size exceeds max size.",
@ -14,6 +14,7 @@
"Invalid credentials.": "Invalid credentials.",
"Invalid payload.": "Invalid payload.",
"Invalid token. Please log in again.": "Invalid token. Please log in again.",
"Max. size of zip archive must be equal or greater than max. size of uploaded files": "Max. size of zip archive must be equal or greater than max. size of uploaded files",
"No file part.": "No file part.",
"No picture.": "No picture.",
"No selected file.": "No selected file.",
@ -30,6 +31,7 @@
"statistics": "statistiques",
"User does not exist.": "User does not exist.",
"Valid email must be provided.\n": "Valid email must be provided.",
"workouts": "workouts",
"You can not delete your account, no other user has admin rights.": "You can not delete your account, no other user has admin rights.",
"You do not have permissions.": "You do not have permissions."
}

View File

@ -2,7 +2,7 @@
"3 to 12 characters required for username.": "3 à 12 caractères requis pour le nom.",
"8 characters required for password.": "8 caractères minimum pour le mot de passe.",
"An error occurred. Please contact the administrator.": "Une erreur s'est produite. Merci de contacter l'administrateur.",
"workouts": "séances",
"application": "application",
"Error during picture deletion.": "Erreur lors de la suppression de l'image.",
"Error during picture update.": "Erreur lors de la mise à jour de l'image.",
"Error during picture update, file size exceeds max size.": "Erreur lors de la mise à jour de l'image, la taille du ficher dépasse la taille maximum autorisée",
@ -14,6 +14,7 @@
"Invalid credentials.": "Identifiants invalides.",
"Invalid payload.": "Données incorrectes.",
"Invalid token. Please log in again.": "Jeton invalide. Merci de vous reconnecter.",
"Max. size of zip archive must be equal or greater than max. size of uploaded files": "La taille max. d'une archive doit être supérieure ou égale à la taille max. d'un fichier",
"No file part.": "Pas de fichier fourni.",
"No picture.": "Pas d'image.",
"No selected file.": "Pas de fichier sélectionné.",
@ -30,6 +31,7 @@
"statistics": "statistics",
"User does not exist.": "L'utilisateur n'existe pas.",
"Valid email must be provided.\n": "L'email fourni n'est pas valide.",
"workouts": "séances",
"You can not delete your account, no other user has admin rights.": "Vous ne pouvez pas supprimer votre compte, aucun autre utilisateur n'a des droits d'administration.",
"You do not have permissions.": "Vous n'avez pas les permissions nécessaires."
}

View File

@ -110,6 +110,7 @@ const message = (state = initial.message, action) => {
return action.message
case 'LOGOUT':
case 'PROFILE_SUCCESS':
case 'SET_APP_CONFIG':
case 'SET_RESULTS':
case '@@router/LOCATION_CHANGE':
return ''