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: if 'max_users' in config_data:
config.max_users = config_data.get('max_users') 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() db.session.commit()
update_app_config_from_database(current_app, config) update_app_config_from_database(current_app, config)
return {'status': 'success', 'data': config.serialize()} return {'status': 'success', 'data': config.serialize()}

View File

@ -1,8 +1,8 @@
{ {
"files": { "files": {
"main.css": "/static/css/main.376b8924.chunk.css", "main.css": "/static/css/main.376b8924.chunk.css",
"main.js": "/static/js/main.8faa878d.chunk.js", "main.js": "/static/js/main.7c5c861a.chunk.js",
"main.js.map": "/static/js/main.8faa878d.chunk.js.map", "main.js.map": "/static/js/main.7c5c861a.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.1240af94.js", "runtime-main.js": "/static/js/runtime-main.1240af94.js",
"runtime-main.js.map": "/static/js/runtime-main.1240af94.js.map", "runtime-main.js.map": "/static/js/runtime-main.1240af94.js.map",
"static/js/2.301144a0.chunk.js": "/static/js/2.301144a0.chunk.js", "static/js/2.301144a0.chunk.js": "/static/js/2.301144a0.chunk.js",
@ -19,6 +19,6 @@
"static/js/runtime-main.1240af94.js", "static/js/runtime-main.1240af94.js",
"static/js/2.301144a0.chunk.js", "static/js/2.301144a0.chunk.js",
"static/css/main.376b8924.chunk.css", "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 response.status_code == 500
assert 'error' in data['status'] assert 'error' in data['status']
assert 'Error on updating configuration.' in data['message'] 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 FitTrackeeGenericApi from '../fitTrackeeApi'
import { history } from '../index'
import { setError } from './index' import { setError } from './index'
export const setAppConfig = data => ({ export const setAppConfig = data => ({
@ -31,6 +32,7 @@ export const updateAppConfig = formData => dispatch =>
.then(ret => { .then(ret => {
if (ret.status === 'success') { if (ret.status === 'success') {
dispatch(setAppConfig(ret.data)) dispatch(setAppConfig(ret.data))
history.push('/admin/application')
} else { } else {
dispatch(setError(`application|${ret.message}`)) dispatch(setError(`application|${ret.message}`))
} }

View File

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

View File

@ -28,7 +28,20 @@ function Admin(props) {
<Route <Route
exact exact
path="/admin/application" 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 <Route
exact exact

View File

@ -2,7 +2,7 @@
"3 to 12 characters required for username.": "3 to 12 characters required for username.", "3 to 12 characters required for username.": "3 to 12 characters required for username.",
"8 characters required for password.": "8 characters required for password.", "8 characters required for password.": "8 characters required for password.",
"An error occurred. Please contact the administrator.": "An error occurred. Please contact the administrator.", "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 deletion.": "Error during picture deletion.",
"Error during picture update.": "Error during picture update.", "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.", "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 credentials.": "Invalid credentials.",
"Invalid payload.": "Invalid payload.", "Invalid payload.": "Invalid payload.",
"Invalid token. Please log in again.": "Invalid token. Please log in again.", "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 file part.": "No file part.",
"No picture.": "No picture.", "No picture.": "No picture.",
"No selected file.": "No selected file.", "No selected file.": "No selected file.",
@ -30,6 +31,7 @@
"statistics": "statistiques", "statistics": "statistiques",
"User does not exist.": "User does not exist.", "User does not exist.": "User does not exist.",
"Valid email must be provided.\n": "Valid email must be provided.", "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 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." "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.", "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.", "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.", "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 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.": "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", "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 credentials.": "Identifiants invalides.",
"Invalid payload.": "Données incorrectes.", "Invalid payload.": "Données incorrectes.",
"Invalid token. Please log in again.": "Jeton invalide. Merci de vous reconnecter.", "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 file part.": "Pas de fichier fourni.",
"No picture.": "Pas d'image.", "No picture.": "Pas d'image.",
"No selected file.": "Pas de fichier sélectionné.", "No selected file.": "Pas de fichier sélectionné.",
@ -30,6 +31,7 @@
"statistics": "statistics", "statistics": "statistics",
"User does not exist.": "L'utilisateur n'existe pas.", "User does not exist.": "L'utilisateur n'existe pas.",
"Valid email must be provided.\n": "L'email fourni n'est pas valide.", "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 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." "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 return action.message
case 'LOGOUT': case 'LOGOUT':
case 'PROFILE_SUCCESS': case 'PROFILE_SUCCESS':
case 'SET_APP_CONFIG':
case 'SET_RESULTS': case 'SET_RESULTS':
case '@@router/LOCATION_CHANGE': case '@@router/LOCATION_CHANGE':
return '' return ''