Merge pull request #262 from SamR1/fix-zip-import
Fix zip archive import
This commit is contained in:
commit
18ecdc582c
2
fittrackee/dist/index.html
vendored
2
fittrackee/dist/index.html
vendored
@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.50f5c7a2.js"></script><script defer="defer" src="/static/js/app.f7049224.js"></script><link href="/static/css/app.564b9516.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.50f5c7a2.js"></script><script defer="defer" src="/static/js/app.3ce255f9.js"></script><link href="/static/css/app.564b9516.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
2
fittrackee/dist/service-worker.js
vendored
2
fittrackee/dist/service-worker.js
vendored
File diff suppressed because one or more lines are too long
2
fittrackee/dist/service-worker.js.map
vendored
2
fittrackee/dist/service-worker.js.map
vendored
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/app.3ce255f9.js
vendored
Normal file
2
fittrackee/dist/static/js/app.3ce255f9.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/js/app.3ce255f9.js.map
vendored
Normal file
1
fittrackee/dist/static/js/app.3ce255f9.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/app.f7049224.js
vendored
2
fittrackee/dist/static/js/app.f7049224.js
vendored
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
@ -1,2 +1,2 @@
|
||||
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{7885:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});var a=t(6252),r=t(2262),l=t(3577),o=(t(7658),t(9150)),n=t(3170);const c={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,a.aZ)({__name:"StatsMenu",emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){const t=(0,r.iH)("month"),o=["week","month","year"];function n(e){t.value=e,s("timeFrameUpdate",e)}return(e,r)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:r[0]||(r[0]=e=>s("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(s=>(0,a._)("div",{class:"time-frame custom-checkbox",key:s},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:s,name:s,checked:t.value===s,onInput:e=>n(s)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:r[1]||(r[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const _=(0,k.Z)(v,[["__scopeId","data-v-22d55de2"]]);var S=_,w=t(631);const f={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,a.aZ)({__name:"StatsSportsMenu",props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(t),u=(0,a.Fl)((()=>(0,w.xH)(t.userSports,n)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",f,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,r.SU)(u),(e=>((0,a.wg)(),(0,a.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,r.SU)(c)[e.label]})},[(0,a._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,r.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,a.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,a._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({__name:"index",props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(s),i=(0,r.iH)("month"),u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,w.xH)(s.sports,t))),p=(0,r.iH)(_(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function _(e){return e.map((e=>e.id))}function f(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,a.YP)((()=>s.sports),(e=>{p.value=_(e)})),(e,s)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(S,{onTimeFrameUpdate:m,onArrowClick:k}),(0,a.Wm)(n.Z,{sports:(0,r.SU)(l),user:(0,r.SU)(c),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,a.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,r.SU)(l),onSelectedSportIdsUpdate:f},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-30799d13"]]);var Z=F,x=t(5630),D=t(5801),H=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({__name:"StatisticsView",setup(e){const s=(0,H.o)(),t=(0,a.Fl)((()=>s.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>s.getters[D.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(t).username?((0,a.wg)(),(0,a.iD)("div",R,[(0,a.Wm)(n,null,{title:(0,a.w5)((()=>[(0,a.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,a.w5)((()=>[(0,a.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,r.SU)(t).nb_workouts}),user:(0,r.SU)(t),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(t).nb_workouts?((0,a.wg)(),(0,a.j4)(x.Z,{key:0})):(0,a.kq)("",!0)])):(0,a.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-2e341d4e"]]);var A=P}}]);
|
||||
//# sourceMappingURL=statistics.c9a8011a.js.map
|
||||
//# sourceMappingURL=statistics.04a17f2e.js.map
|
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
@ -1112,7 +1112,7 @@ class TestPostWorkoutWithZipArchive(ApiTestCaseMixin):
|
||||
data = self.assert_500(response, 'error during gpx processing')
|
||||
assert 'data' not in data
|
||||
|
||||
def test_it_imports_only_max_number_of_files(
|
||||
def test_it_returns_400_when_files_in_archive_exceed_limit(
|
||||
self,
|
||||
app_with_max_workouts: Flask,
|
||||
user_1: User,
|
||||
@ -1127,7 +1127,7 @@ class TestPostWorkoutWithZipArchive(ApiTestCaseMixin):
|
||||
app_with_max_workouts, user_1.email
|
||||
)
|
||||
|
||||
client.post(
|
||||
response = client.post(
|
||||
'/api/workouts',
|
||||
data=dict(
|
||||
file=(zip_file, 'gpx_test.zip'), data='{"sport_id": 1}'
|
||||
@ -1138,12 +1138,11 @@ class TestPostWorkoutWithZipArchive(ApiTestCaseMixin):
|
||||
),
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
'/api/workouts',
|
||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||
self.assert_400(
|
||||
response,
|
||||
'the number of files in the archive exceeds the limit',
|
||||
'fail',
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert len(data['data']['workouts']) == 2
|
||||
|
||||
def test_it_returns_error_if_archive_size_exceeds_limit(
|
||||
self,
|
||||
@ -1178,6 +1177,40 @@ class TestPostWorkoutWithZipArchive(ApiTestCaseMixin):
|
||||
)
|
||||
assert 'data' not in data
|
||||
|
||||
def test_it_returns_error_if_a_file_from_archive_size_exceeds_limit(
|
||||
self,
|
||||
app_with_max_file_size: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
) -> None:
|
||||
file_path = os.path.join(
|
||||
app_with_max_file_size.root_path, 'tests/files/gpx_test.zip'
|
||||
)
|
||||
# 'gpx_test.zip' contains 3 gpx files (same data) and 1 non-gpx file
|
||||
with open(file_path, 'rb') as zip_file:
|
||||
client, auth_token = self.get_test_client_and_auth_token(
|
||||
app_with_max_file_size, user_1.email
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
'/api/workouts',
|
||||
data=dict(
|
||||
file=(zip_file, 'gpx_test.zip'), data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization=f'Bearer {auth_token}',
|
||||
),
|
||||
)
|
||||
|
||||
data = self.assert_400(
|
||||
response,
|
||||
'at least one file in zip archive exceeds size limit, '
|
||||
'please check the archive',
|
||||
'fail',
|
||||
)
|
||||
assert 'data' not in data
|
||||
|
||||
|
||||
class TestPostAndGetWorkoutWithGpx(ApiTestCaseMixin):
|
||||
def workout_assertion(
|
||||
|
@ -333,6 +333,14 @@ def process_one_gpx_file(
|
||||
raise WorkoutException('fail', 'Error during workout save.', e)
|
||||
|
||||
|
||||
def is_gpx_file(filename: str) -> bool:
|
||||
return (
|
||||
'.' in filename
|
||||
and filename.rsplit('.', 1)[1].lower()
|
||||
in current_app.config['WORKOUT_ALLOWED_EXTENSIONS']
|
||||
)
|
||||
|
||||
|
||||
def process_zip_archive(
|
||||
common_params: Dict, extract_dir: str, stopped_speed_threshold: float
|
||||
) -> List:
|
||||
@ -341,21 +349,33 @@ def process_zip_archive(
|
||||
does not exceed defined limit.
|
||||
"""
|
||||
with zipfile.ZipFile(common_params['file_path'], "r") as zip_ref:
|
||||
max_file_size = current_app.config['max_single_file_size']
|
||||
gpx_files_count = 0
|
||||
files_with_invalid_size_count = 0
|
||||
for zip_info in zip_ref.infolist():
|
||||
if is_gpx_file(zip_info.filename):
|
||||
gpx_files_count += 1
|
||||
if zip_info.file_size > max_file_size:
|
||||
files_with_invalid_size_count += 1
|
||||
|
||||
if gpx_files_count > current_app.config['gpx_limit_import']:
|
||||
raise WorkoutException(
|
||||
'fail', 'the number of files in the archive exceeds the limit'
|
||||
)
|
||||
|
||||
if files_with_invalid_size_count > 0:
|
||||
raise WorkoutException(
|
||||
'fail',
|
||||
'at least one file in zip archive exceeds size limit, '
|
||||
'please check the archive',
|
||||
)
|
||||
|
||||
zip_ref.extractall(extract_dir)
|
||||
|
||||
new_workouts = []
|
||||
gpx_files_limit = current_app.config['gpx_limit_import']
|
||||
gpx_files_ok = 0
|
||||
|
||||
for gpx_file in os.listdir(extract_dir):
|
||||
if (
|
||||
'.' in gpx_file
|
||||
and gpx_file.rsplit('.', 1)[1].lower()
|
||||
in current_app.config['WORKOUT_ALLOWED_EXTENSIONS']
|
||||
):
|
||||
gpx_files_ok += 1
|
||||
if gpx_files_ok > gpx_files_limit:
|
||||
break
|
||||
if is_gpx_file(gpx_file):
|
||||
file_path = os.path.join(extract_dir, gpx_file)
|
||||
params = common_params
|
||||
params['file_path'] = file_path
|
||||
|
@ -1022,7 +1022,7 @@ def post_workout(auth_user: User) -> Union[Tuple[Dict, int], HttpResponse]:
|
||||
appLog.error(e.e)
|
||||
if e.status == 'error':
|
||||
return InternalServerErrorResponse(e.message)
|
||||
return InvalidPayloadErrorResponse(e.message)
|
||||
return InvalidPayloadErrorResponse(e.message, e.status)
|
||||
|
||||
shutil.rmtree(folders['extract_dir'], ignore_errors=True)
|
||||
shutil.rmtree(folders['tmp_dir'], ignore_errors=True)
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"ERROR": {
|
||||
"UNKNOWN": "Error. Please try again or contact the administrator.",
|
||||
"at least one file in zip archive exceeds size limit, please check the archive": "At least one file in zip archive exceeds size limit, please check the archive.",
|
||||
"email: valid email must be provided": "Email: valid email must be provided.",
|
||||
"error during gpx processing": "Error during gpx processing.",
|
||||
"error during gpx file parsing": "Error during gpx file parsing.",
|
||||
@ -18,6 +19,7 @@
|
||||
"new email must be different than curent email": "The new email must be different than curent email",
|
||||
"no file part": "No file provided.",
|
||||
"no selected file": "No selected file.",
|
||||
"the number of files in the archive exceeds the limit": "The number of files in the archive exceeds the limit.",
|
||||
"password: password and password confirmation do not match": "Password: password and password confirmation don't match.",
|
||||
"provide a valid auth token": "Provide a valid auth token.",
|
||||
"sorry, that username is already taken": "Sorry, that username is already taken.",
|
||||
|
@ -10,11 +10,11 @@
|
||||
"ADMIN_CONTACT": "Email de l'administrateur pour contact ",
|
||||
"MAX_USERS_LABEL": "Nombre maximum d'utilisateurs actifs ",
|
||||
"MAX_USERS_HELP": "Si égal à 0, pas limite d'inscription",
|
||||
"MAX_FILES_IN_ZIP_LABEL": "Taille max. des archives zip (en Mo) ",
|
||||
"MAX_FILES_IN_ZIP_LABEL": "Nombre max. de fichiers dans une archive zip ",
|
||||
"NO_CONTACT_EMAIL": "non renseigné",
|
||||
"SINGLE_UPLOAD_MAX_SIZE_LABEL": "Taille max. des fichiers (en Mo) ",
|
||||
"TITLE": "Configuration de l'application",
|
||||
"ZIP_UPLOAD_MAX_SIZE_LABEL": "Nombre max. de fichiers dans une archive zip "
|
||||
"ZIP_UPLOAD_MAX_SIZE_LABEL": "Taille max. des archives zip (en Mo) "
|
||||
},
|
||||
"BACK_TO_ADMIN": "Revenir à l'admin",
|
||||
"CONFIRM_USER_ACCOUNT_DELETION": "Êtes-vous sûr de vouloir supprimer le compte de l'utilisateur {0} ? Toutes les données seront définitivement.",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"ERROR": {
|
||||
"UNKNOWN": "Erreur. Veuillez réessayer ou contacter l'administrateur.",
|
||||
"at least one file in zip archive exceeds size limit, please check the archive": "Au moins un fichier de l'archive zip dépasse la taille maximale, veuillez vérifier l'archive.",
|
||||
"email: valid email must be provided": "Courriel : une adresse électronique valide doit être fournie.",
|
||||
"error during gpx processing": "Erreur lors du traitement du fichier gpx.",
|
||||
"error during gpx file parsing": "Erreur lors de l'analyse du fichier.",
|
||||
@ -24,6 +25,7 @@
|
||||
"signature expired, please log in again": "Signature expirée. Merci de vous reconnecter.",
|
||||
"sorry, that username is already taken": "Désolé, ce nom d'utilisateur est déjà utilisé.",
|
||||
"successfully registered": "Inscription validée.",
|
||||
"the number of files in the archive exceeds the limit": "Le nombre de fichiers de l'archive dépasse la limite.",
|
||||
"user does not exist": "L'utilisateur n'existe pas.",
|
||||
"valid email must be provided for admin contact": "Une adresse électronique doit être fournie pour le contact de l'administrateur",
|
||||
"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.",
|
||||
|
Loading…
Reference in New Issue
Block a user