commit
e3ba0259f1
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.e335684a.js"></script><script defer="defer" src="/static/js/app.8517c25d.js"></script><link href="/static/css/app.e8b7692c.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.e335684a.js"></script><script defer="defer" src="/static/js/app.69114670.js"></script><link href="/static/css/app.e8b7692c.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
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/css/workouts.a05b455f.css
vendored
Normal file
1
fittrackee/dist/static/css/workouts.a05b455f.css
vendored
Normal file
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
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],{7749:function(e,t,s){s.r(t),s.d(t,{default:function(){return A}});s(6699);var a=s(6252),r=s(2262),l=s(3577),o=s(3324),n=s(7402);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:t}){let s=(0,r.iH)("month");const o=["week","month","year"];function n(e){s.value=e,t("timeFrameUpdate",e)}return(e,v)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:v[0]||(v[0]=e=>t("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(t=>(0,a._)("div",{class:"time-frame custom-checkbox",key:t},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:t,name:t,checked:(0,r.SU)(s)===t,onInput:e=>n(t)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${t}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:v[1]||(v[1]=e=>t("arrowClick",!1))})])]))}}),k=s(3744);const S=(0,k.Z)(v,[["__scopeId","data-v-af15954c"]]);var w=S,f=s(631);const _={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:t}){const s=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(s),u=(0,a.Fl)((()=>(0,f.xH)(s.userSports,n)));function d(e){t("selectedSportIdsUpdate",e)}return(e,t)=>{const s=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",_,[((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:t=>d(e.id)},null,40,h),(0,a.Wm)(s,{"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=s(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({name:"index",props:{sports:null,user:null},setup(e){const t=e,{t:s}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(t);let i=(0,r.iH)("month");const u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,f.xH)(t.sports,s))),p=(0,r.iH)(S(t.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,t.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,t.user.weekm)}function S(e){return e.map((e=>e.id))}function _(e){p.value.includes(e)?p.value=p.value.filter((t=>t!==e)):p.value.push(e)}return(0,a.YP)((()=>t.sports),(e=>{p.value=S(e)})),(e,t)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(w,{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:_},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-7d54529b"]]);var Z=F,x=s(5630),D=s(8602),H=s(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({name:"StatisticsView",setup(e){const t=(0,H.o)(),s=(0,a.Fl)((()=>t.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>t.getters[D.O8.GETTERS.SPORTS].filter((e=>s.value.sports_list.includes(e.id)))));return(e,t)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(s).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)(s).nb_workouts}),user:(0,r.SU)(s),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(s).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}}]);
|
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{7749:function(e,t,s){s.r(t),s.d(t,{default:function(){return A}});s(6699);var a=s(6252),r=s(2262),l=s(3577),o=s(3324),n=s(7402);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:t}){let s=(0,r.iH)("month");const o=["week","month","year"];function n(e){s.value=e,t("timeFrameUpdate",e)}return(e,v)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:v[0]||(v[0]=e=>t("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(t=>(0,a._)("div",{class:"time-frame custom-checkbox",key:t},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:t,name:t,checked:(0,r.SU)(s)===t,onInput:e=>n(t)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${t}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:v[1]||(v[1]=e=>t("arrowClick",!1))})])]))}}),k=s(3744);const S=(0,k.Z)(v,[["__scopeId","data-v-af15954c"]]);var w=S,f=s(631);const _={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:t}){const s=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(s),u=(0,a.Fl)((()=>(0,f.xH)(s.userSports,n)));function d(e){t("selectedSportIdsUpdate",e)}return(e,t)=>{const s=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",_,[((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:t=>d(e.id)},null,40,h),(0,a.Wm)(s,{"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=s(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({name:"index",props:{sports:null,user:null},setup(e){const t=e,{t:s}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(t);let i=(0,r.iH)("month");const u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,f.xH)(t.sports,s))),p=(0,r.iH)(S(t.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,t.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,t.user.weekm)}function S(e){return e.map((e=>e.id))}function _(e){p.value.includes(e)?p.value=p.value.filter((t=>t!==e)):p.value.push(e)}return(0,a.YP)((()=>t.sports),(e=>{p.value=S(e)})),(e,t)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(w,{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:_},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-7d54529b"]]);var Z=F,x=s(5630),D=s(8602),H=s(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({name:"StatisticsView",setup(e){const t=(0,H.o)(),s=(0,a.Fl)((()=>t.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>t.getters[D.O8.GETTERS.SPORTS].filter((e=>s.value.sports_list.includes(e.id)))));return(e,t)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(s).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)(s).nb_workouts}),user:(0,r.SU)(s),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(s).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.ea7ff674.js.map
|
//# sourceMappingURL=statistics.7aabfecc.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
1
fittrackee/dist/static/js/workouts.be32aa18.js.map
vendored
Normal file
1
fittrackee/dist/static/js/workouts.be32aa18.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1158,6 +1158,27 @@ class TestGetWorkout(ApiTestCaseMixin):
|
|||||||
|
|
||||||
self.assert_404_with_message(response, 'Map does not exist')
|
self.assert_404_with_message(response, 'Map does not exist')
|
||||||
|
|
||||||
|
def test_it_returns_404_if_map_file_not_found(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
user_1: User,
|
||||||
|
sport_1_cycling: Sport,
|
||||||
|
workout_cycling_user_1: Workout,
|
||||||
|
) -> None:
|
||||||
|
map_ip = self.random_string()
|
||||||
|
workout_cycling_user_1.map = self.random_string()
|
||||||
|
workout_cycling_user_1.map_id = map_ip
|
||||||
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
|
app, user_1.email
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get(
|
||||||
|
f'/api/workouts/map/{map_ip}',
|
||||||
|
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assert_404_with_message(response, 'Map file does not exist')
|
||||||
|
|
||||||
|
|
||||||
class TestDownloadWorkoutGpx(ApiTestCaseMixin):
|
class TestDownloadWorkoutGpx(ApiTestCaseMixin):
|
||||||
def test_it_returns_404_if_workout_does_not_exist(
|
def test_it_returns_404_if_workout_does_not_exist(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
@ -10,7 +11,6 @@ from flask import Flask
|
|||||||
|
|
||||||
from fittrackee.users.models import User
|
from fittrackee.users.models import User
|
||||||
from fittrackee.workouts.models import Sport, Workout
|
from fittrackee.workouts.models import Sport, Workout
|
||||||
from fittrackee.workouts.utils.short_id import decode_short_id
|
|
||||||
|
|
||||||
from ..mixins import ApiTestCaseMixin, CallArgsMixin
|
from ..mixins import ApiTestCaseMixin, CallArgsMixin
|
||||||
|
|
||||||
@ -251,6 +251,56 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
|
|||||||
assert 'just a workout' == data['data']['workouts'][0]['title']
|
assert 'just a workout' == data['data']['workouts'][0]['title']
|
||||||
assert_workout_data_with_gpx(data)
|
assert_workout_data_with_gpx(data)
|
||||||
|
|
||||||
|
def test_it_creates_workout_with_expecting_gpx_path(
|
||||||
|
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
||||||
|
) -> None:
|
||||||
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
|
app, user_1.email
|
||||||
|
)
|
||||||
|
|
||||||
|
client.post(
|
||||||
|
'/api/workouts',
|
||||||
|
data=dict(
|
||||||
|
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||||
|
data='{"sport_id": 1}',
|
||||||
|
),
|
||||||
|
headers=dict(
|
||||||
|
content_type='multipart/form-data',
|
||||||
|
Authorization=f'Bearer {auth_token}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
workout = Workout.query.first()
|
||||||
|
assert re.match(
|
||||||
|
r'^workouts/1/2018-03-13_12-44-45_1_([\w\d_-]*).gpx$',
|
||||||
|
workout.gpx,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_it_creates_workout_with_expecting_map_path(
|
||||||
|
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
||||||
|
) -> None:
|
||||||
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
|
app, user_1.email
|
||||||
|
)
|
||||||
|
|
||||||
|
client.post(
|
||||||
|
'/api/workouts',
|
||||||
|
data=dict(
|
||||||
|
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||||
|
data='{"sport_id": 1}',
|
||||||
|
),
|
||||||
|
headers=dict(
|
||||||
|
content_type='multipart/form-data',
|
||||||
|
Authorization=f'Bearer {auth_token}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
workout = Workout.query.first()
|
||||||
|
assert re.match(
|
||||||
|
r'^workouts/1/2018-03-13_12-44-45_1_([\w\d_-]*).png$',
|
||||||
|
workout.map,
|
||||||
|
)
|
||||||
|
|
||||||
def test_it_adds_a_workout_with_gpx_without_name(
|
def test_it_adds_a_workout_with_gpx_without_name(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
@ -974,23 +1024,6 @@ class TestPostAndGetWorkoutWithGpx(ApiTestCaseMixin):
|
|||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# error case in the same test to avoid generate a new map file
|
|
||||||
workout_uuid = decode_short_id(workout_short_id)
|
|
||||||
workout = Workout.query.filter_by(uuid=workout_uuid).first()
|
|
||||||
workout.map = 'incorrect path'
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert 'success' in data['status']
|
|
||||||
assert '' in data['message']
|
|
||||||
assert len(data['data']['gpx']) != ''
|
|
||||||
|
|
||||||
response = client.get(
|
|
||||||
f'/api/workouts/map/{map_id}',
|
|
||||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assert_500(response)
|
|
||||||
|
|
||||||
def test_it_gets_a_workout_created_with_gpx(
|
def test_it_gets_a_workout_created_with_gpx(
|
||||||
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
from fittrackee.files import get_absolute_file_path
|
|
||||||
from fittrackee.users.models import User
|
from fittrackee.users.models import User
|
||||||
from fittrackee.workouts.models import Sport, Workout
|
from fittrackee.workouts.models import Sport, Workout
|
||||||
|
|
||||||
@ -64,21 +61,45 @@ class TestDeleteWorkoutWithGpx(ApiTestCaseMixin):
|
|||||||
data = self.assert_404(response)
|
data = self.assert_404(response)
|
||||||
assert 'not found' in data['status']
|
assert 'not found' in data['status']
|
||||||
|
|
||||||
def test_it_returns_500_when_deleting_a_workout_with_gpx_invalid_file(
|
def test_a_workout_with_gpx_can_be_deleted_if_gpx_file_is_invalid(
|
||||||
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
self,
|
||||||
|
app: Flask,
|
||||||
|
user_1: User,
|
||||||
|
sport_1_cycling: Sport,
|
||||||
|
workout_cycling_user_1: Workout,
|
||||||
) -> None:
|
) -> None:
|
||||||
token, workout_short_id = post_a_workout(app, gpx_file)
|
workout_cycling_user_1.gpx = self.random_string()
|
||||||
client = app.test_client()
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
gpx_filepath = get_gpx_filepath(1)
|
app, user_1.email
|
||||||
gpx_filepath = get_absolute_file_path(gpx_filepath)
|
|
||||||
os.remove(gpx_filepath)
|
|
||||||
|
|
||||||
response = client.delete(
|
|
||||||
f'/api/workouts/{workout_short_id}',
|
|
||||||
headers=dict(Authorization=f'Bearer {token}'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assert_500(response)
|
response = client.delete(
|
||||||
|
f'/api/workouts/{workout_cycling_user_1.short_id}',
|
||||||
|
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
def test_a_workout_with_gpx_can_be_deleted_if_map_file_is_invalid(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
user_1: User,
|
||||||
|
sport_1_cycling: Sport,
|
||||||
|
workout_cycling_user_1: Workout,
|
||||||
|
) -> None:
|
||||||
|
map_ip = self.random_string()
|
||||||
|
workout_cycling_user_1.map = self.random_string()
|
||||||
|
workout_cycling_user_1.map_id = map_ip
|
||||||
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
|
app, user_1.email
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.delete(
|
||||||
|
f'/api/workouts/{workout_cycling_user_1.short_id}',
|
||||||
|
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
class TestDeleteWorkoutWithoutGpx(ApiTestCaseMixin):
|
class TestDeleteWorkoutWithoutGpx(ApiTestCaseMixin):
|
||||||
|
@ -12,7 +12,7 @@ from sqlalchemy.orm.mapper import Mapper
|
|||||||
from sqlalchemy.orm.session import Session, object_session
|
from sqlalchemy.orm.session import Session, object_session
|
||||||
from sqlalchemy.types import JSON, Enum
|
from sqlalchemy.types import JSON, Enum
|
||||||
|
|
||||||
from fittrackee import db
|
from fittrackee import appLog, db
|
||||||
from fittrackee.files import get_absolute_file_path
|
from fittrackee.files import get_absolute_file_path
|
||||||
|
|
||||||
from .utils.convert import convert_in_duration, convert_value_to_integer
|
from .utils.convert import convert_in_duration, convert_value_to_integer
|
||||||
@ -379,9 +379,15 @@ def on_workout_delete(
|
|||||||
@listens_for(db.Session, 'after_flush', once=True)
|
@listens_for(db.Session, 'after_flush', once=True)
|
||||||
def receive_after_flush(session: Session, context: Any) -> None:
|
def receive_after_flush(session: Session, context: Any) -> None:
|
||||||
if old_record.map:
|
if old_record.map:
|
||||||
os.remove(get_absolute_file_path(old_record.map))
|
try:
|
||||||
|
os.remove(get_absolute_file_path(old_record.map))
|
||||||
|
except OSError:
|
||||||
|
appLog.error('map file not found when deleting workout')
|
||||||
if old_record.gpx:
|
if old_record.gpx:
|
||||||
os.remove(get_absolute_file_path(old_record.gpx))
|
try:
|
||||||
|
os.remove(get_absolute_file_path(old_record.gpx))
|
||||||
|
except OSError:
|
||||||
|
appLog.error('gpx file not found when deleting workout')
|
||||||
|
|
||||||
|
|
||||||
class WorkoutSegment(BaseModel):
|
class WorkoutSegment(BaseModel):
|
||||||
|
@ -255,7 +255,7 @@ def get_file_path(dir_path: str, filename: str) -> str:
|
|||||||
def get_new_file_path(
|
def get_new_file_path(
|
||||||
auth_user_id: int,
|
auth_user_id: int,
|
||||||
workout_date: str,
|
workout_date: str,
|
||||||
sport: str,
|
sport_id: int,
|
||||||
old_filename: Optional[str] = None,
|
old_filename: Optional[str] = None,
|
||||||
extension: Optional[str] = None,
|
extension: Optional[str] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
@ -265,11 +265,9 @@ def get_new_file_path(
|
|||||||
if not extension and old_filename:
|
if not extension and old_filename:
|
||||||
extension = f".{old_filename.rsplit('.', 1)[1].lower()}"
|
extension = f".{old_filename.rsplit('.', 1)[1].lower()}"
|
||||||
_, new_filename = tempfile.mkstemp(
|
_, new_filename = tempfile.mkstemp(
|
||||||
prefix=f'{workout_date}_{sport}_', suffix=extension
|
prefix=f'{workout_date}_{sport_id}_', suffix=extension
|
||||||
)
|
)
|
||||||
dir_path = os.path.join('workouts', str(auth_user_id))
|
dir_path = os.path.join('workouts', str(auth_user_id))
|
||||||
if not os.path.exists(dir_path):
|
|
||||||
os.makedirs(dir_path)
|
|
||||||
file_path = os.path.join(dir_path, new_filename.split('/')[-1])
|
file_path = os.path.join(dir_path, new_filename.split('/')[-1])
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
@ -285,11 +283,16 @@ def process_one_gpx_file(
|
|||||||
params['file_path'], stopped_speed_threshold
|
params['file_path'], stopped_speed_threshold
|
||||||
)
|
)
|
||||||
auth_user = params['auth_user']
|
auth_user = params['auth_user']
|
||||||
|
workout_date, _ = get_workout_datetime(
|
||||||
|
workout_date=gpx_data['start'],
|
||||||
|
date_str_format=None if gpx_data else '%Y-%m-%d %H:%M',
|
||||||
|
user_timezone=None,
|
||||||
|
)
|
||||||
new_filepath = get_new_file_path(
|
new_filepath = get_new_file_path(
|
||||||
auth_user_id=auth_user.id,
|
auth_user_id=auth_user.id,
|
||||||
workout_date=gpx_data['start'],
|
workout_date=workout_date.strftime('%Y-%m-%d_%H-%M-%S'),
|
||||||
old_filename=filename,
|
old_filename=filename,
|
||||||
sport=params['sport_label'],
|
sport_id=params['sport_id'],
|
||||||
)
|
)
|
||||||
absolute_gpx_filepath = get_absolute_file_path(new_filepath)
|
absolute_gpx_filepath = get_absolute_file_path(new_filepath)
|
||||||
os.rename(params['file_path'], absolute_gpx_filepath)
|
os.rename(params['file_path'], absolute_gpx_filepath)
|
||||||
@ -297,9 +300,9 @@ def process_one_gpx_file(
|
|||||||
|
|
||||||
map_filepath = get_new_file_path(
|
map_filepath = get_new_file_path(
|
||||||
auth_user_id=auth_user.id,
|
auth_user_id=auth_user.id,
|
||||||
workout_date=gpx_data['start'],
|
workout_date=workout_date.strftime('%Y-%m-%d_%H-%M-%S'),
|
||||||
extension='.png',
|
extension='.png',
|
||||||
sport=params['sport_label'],
|
sport_id=params['sport_id'],
|
||||||
)
|
)
|
||||||
absolute_map_filepath = get_absolute_file_path(map_filepath)
|
absolute_map_filepath = get_absolute_file_path(map_filepath)
|
||||||
generate_map(absolute_map_filepath, map_data)
|
generate_map(absolute_map_filepath, map_data)
|
||||||
@ -397,7 +400,7 @@ def process_files(
|
|||||||
'auth_user': auth_user,
|
'auth_user': auth_user,
|
||||||
'workout_data': workout_data,
|
'workout_data': workout_data,
|
||||||
'file_path': file_path,
|
'file_path': file_path,
|
||||||
'sport_label': sport.label,
|
'sport_id': sport.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -13,7 +13,7 @@ from flask import (
|
|||||||
send_from_directory,
|
send_from_directory,
|
||||||
)
|
)
|
||||||
from sqlalchemy import exc
|
from sqlalchemy import exc
|
||||||
from werkzeug.exceptions import RequestEntityTooLarge
|
from werkzeug.exceptions import NotFound, RequestEntityTooLarge
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from fittrackee import appLog, db
|
from fittrackee import appLog, db
|
||||||
@ -798,6 +798,8 @@ def get_map(map_id: int) -> Union[HttpResponse, Response]:
|
|||||||
current_app.config['UPLOAD_FOLDER'],
|
current_app.config['UPLOAD_FOLDER'],
|
||||||
workout.map,
|
workout.map,
|
||||||
)
|
)
|
||||||
|
except NotFound:
|
||||||
|
return NotFoundErrorResponse('Map file does not exist.')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return handle_error_and_return_response(e)
|
return handle_error_and_return_response(e)
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
class="fa fa-map-o"
|
class="fa fa-map-o"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
{{ workout.title }}
|
<span class="title">{{ workout.title }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<StaticMap
|
<StaticMap
|
||||||
v-if="workout.with_gpx && hoverWorkoutId === workout.id"
|
v-if="workout.with_gpx && hoverWorkoutId === workout.id"
|
||||||
@ -182,7 +182,7 @@
|
|||||||
import { WORKOUTS_STORE } from '@/store/constants'
|
import { WORKOUTS_STORE } from '@/store/constants'
|
||||||
import { IPagination } from '@/types/api'
|
import { IPagination } from '@/types/api'
|
||||||
import { ITranslatedSport } from '@/types/sports'
|
import { ITranslatedSport } from '@/types/sports'
|
||||||
import { IUserProfile } from '@/types/user'
|
import { IAuthUserProfile } from '@/types/user'
|
||||||
import { IWorkout, TWorkoutsPayload } from '@/types/workouts'
|
import { IWorkout, TWorkoutsPayload } from '@/types/workouts'
|
||||||
import { useStore } from '@/use/useStore'
|
import { useStore } from '@/use/useStore'
|
||||||
import { getQuery, sortList, workoutsPayloadKeys } from '@/utils/api'
|
import { getQuery, sortList, workoutsPayloadKeys } from '@/utils/api'
|
||||||
@ -192,7 +192,7 @@
|
|||||||
import { defaultOrder } from '@/utils/workouts'
|
import { defaultOrder } from '@/utils/workouts'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: IUserProfile
|
user: IAuthUserProfile
|
||||||
sports: ITranslatedSport[]
|
sports: ITranslatedSport[]
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
@ -258,7 +258,7 @@
|
|||||||
...payload,
|
...payload,
|
||||||
}
|
}
|
||||||
Object.entries(convertedPayload).map((entry) => {
|
Object.entries(convertedPayload).map((entry) => {
|
||||||
if (entry[0].match('speed|distance')) {
|
if (entry[0].match('speed|distance') && entry[1]) {
|
||||||
convertedPayload[entry[0]] = convertDistance(+entry[1], 'mi', 'km')
|
convertedPayload[entry[0]] = convertDistance(+entry[1], 'mi', 'km')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -326,6 +326,14 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
.fa-map-o {
|
.fa-map-o {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
|
padding-right: $default-padding * 0.5;
|
||||||
|
}
|
||||||
|
.nav-item {
|
||||||
|
white-space: nowrap;
|
||||||
|
.title {
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.static-map {
|
.static-map {
|
||||||
display: none;
|
display: none;
|
||||||
|
Loading…
Reference in New Issue
Block a user