API - replace 'Activity' with 'Workout' - #58

This commit is contained in:
Sam 2021-01-10 11:16:43 +01:00
parent 24ee5bbcfa
commit 3a80e01cc2
70 changed files with 2746 additions and 2511 deletions

View File

@ -19,6 +19,9 @@ clean-install:
rm -rf .pytest_cache rm -rf .pytest_cache
rm -rf dist/ rm -rf dist/
downgrade-db:
$(FLASK) db downgrade --directory $(MIGRATIONS)
html: html:
rm -rf docsrc/build rm -rf docsrc/build
rm -rf docs/* rm -rf docs/*

View File

@ -14,7 +14,7 @@
--- ---
This web application allows you to track your outdoor activities from gpx files and keep your data on your own server. This web application allows you to track your outdoor activities (workouts) from gpx files and keep your data on your own server.
No mobile app is developed yet, but several existing mobile apps can store workouts data locally and export them into a gpx file. No mobile app is developed yet, but several existing mobile apps can store workouts data locally and export them into a gpx file.
Examples (for Android): Examples (for Android):
* [Runner Up](https://github.com/jonasoreland/runnerup) (GPL v3) * [Runner Up](https://github.com/jonasoreland/runnerup) (GPL v3)

View File

@ -1,17 +0,0 @@
Activities
##########
.. autoflask:: fittrackee:create_app()
:endpoints:
activities.get_activities,
activities.get_activity,
activities.get_activity_gpx,
activities.get_activity_chart_data,
activities.get_segment_chart_data,
activities.get_segment_gpx,
activities.get_map,
activities.get_map_tile,
activities.post_activity,
activities.post_activity_no_gpx,
activities.update_activity,
activities.delete_activity

View File

@ -5,10 +5,10 @@ API documentation
:maxdepth: 2 :maxdepth: 2
:caption: Endpoints: :caption: Endpoints:
activities
auth auth
configuration configuration
records records
sports sports
stats stats
users users
workouts

View File

@ -3,6 +3,6 @@ Statistics
.. autoflask:: fittrackee:create_app() .. autoflask:: fittrackee:create_app()
:endpoints: :endpoints:
stats.get_activities_by_time, stats.get_workouts_by_time,
stats.get_activities_by_sport, stats.get_workouts_by_sport,
stats.get_application_stats stats.get_application_stats

View File

@ -0,0 +1,17 @@
Workouts
##########
.. autoflask:: fittrackee:create_app()
:endpoints:
workouts.get_workouts,
workouts.get_workout,
workouts.get_workout_gpx,
workouts.get_workout_chart_data,
workouts.get_segment_chart_data,
workouts.get_segment_gpx,
workouts.get_map,
workouts.get_map_tile,
workouts.post_workout,
workouts.post_workout_no_gpx,
workouts.update_workout,
workouts.delete_workout

View File

@ -25,7 +25,7 @@ Administration
- **Sports** - **Sports**
- enable or disable a sport (a sport can be disabled even if activity with this sport exists) - enable or disable a sport (a sport can be disabled even if workout with this sport exists)
Account Account
^^^^^^^ ^^^^^^^
@ -33,29 +33,29 @@ Account
- A user can reset his password (*new in 0.3.0*) - A user can reset his password (*new in 0.3.0*)
Activities/Workouts Workouts
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^
- 6 sports supported: - 6 sports are supported:
- Cycling (Sport) - Cycling (Sport)
- Cycling (Transport) - Cycling (Transport)
- Hiking - Hiking
- Montain Biking - Montain Biking
- Running - Running
- Walking - Walking
- Dashboard with month calendar displaying activities and record. The week can start on Sunday or Monday (which can be changed in the user settings) - Dashboard with month calendar displaying workouts and record. The week can start on Sunday or Monday (which can be changed in the user settings)
- Activity creation by uploading a gpx file. An activity can even be created without gpx (the user must enter date, time, duration and distance) - Workout creation by uploading a gpx file. A workout can even be created without gpx (the user must enter date, time, duration and distance)
- An activity with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed - A workout with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed
- Activity edition and deletion. User can add a note - Workout edition and deletion. User can add a note
- User statistics - User statistics
- User records by sports: - User records by sports:
- average speed - average speed
- farest distance - farest distance
- longest duration - longest duration
- maximum speed - maximum speed
- Activities list and filter - Workours list and filter
.. note:: .. note::
for now, only the owner of the activity can see the activity. for now, only the owner of the workout can see it.
Translations Translations
^^^^^^^^^^^^ ^^^^^^^^^^^^
@ -69,16 +69,16 @@ Dashboard
:alt: FitTrackee Dashboard :alt: FitTrackee Dashboard
Activity/workout detail Workout detail
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
.. figure:: _images/fittrackee_screenshot-02.png .. figure:: _images/fittrackee_screenshot-02.png
:alt: FitTrackee Activity :alt: FitTrackee Workout
Activities/workouts list Workouts list
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~
.. figure:: _images/fittrackee_screenshot-03.png .. figure:: _images/fittrackee_screenshot-03.png
:alt: FitTrackee Activities :alt: FitTrackee Workouts
Statistics Statistics

View File

@ -5,8 +5,8 @@
FitTrackee FitTrackee
========== ==========
| This web application allows you to track your outdoor activities from | This web application allows you to track your outdoor activities (workouts)
gpx files and keep your data on your own server. from gpx files and keep your data on your own server.
| No mobile app is developed yet, but several existing mobile apps can | No mobile app is developed yet, but several existing mobile apps can
store workouts data locally and export them into a gpx file. store workouts data locally and export them into a gpx file.
| Examples (for Android): | Examples (for Android):

View File

@ -16,7 +16,7 @@
<link rel="index" title="Index" href="../genindex.html" /> <link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" /> <link rel="search" title="Search" href="../search.html" />
<link rel="next" title="Configuration" href="configuration.html" /> <link rel="next" title="Configuration" href="configuration.html" />
<link rel="prev" title="Activities" href="activities.html" /> <link rel="prev" title="API documentation" href="index.html" />
<meta charset='utf-8'> <meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'> <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1'> <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1'>
@ -86,7 +86,7 @@
<li> <li>
<a href="activities.html" title="Previous Chapter: Activities"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">&laquo; Activities</span> <a href="index.html" title="Previous Chapter: API documentation"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">&laquo; API documentation</span>
</a> </a>
</li> </li>
<li> <li>
@ -321,8 +321,8 @@
<span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;en&quot;</span><span class="p">,</span> <span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;en&quot;</span><span class="p">,</span>
<span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span>
@ -383,8 +383,8 @@
<span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;en&quot;</span><span class="p">,</span> <span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;en&quot;</span><span class="p">,</span>
<span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span>

View File

@ -15,7 +15,7 @@
<script src="../_static/doctools.js"></script> <script src="../_static/doctools.js"></script>
<link rel="index" title="Index" href="../genindex.html" /> <link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" /> <link rel="search" title="Search" href="../search.html" />
<link rel="next" title="Activities" href="activities.html" /> <link rel="next" title="Authentication" href="auth.html" />
<link rel="prev" title="Features" href="../features.html" /> <link rel="prev" title="Features" href="../features.html" />
<meta charset='utf-8'> <meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'> <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
@ -90,7 +90,7 @@
</a> </a>
</li> </li>
<li> <li>
<a href="activities.html" title="Next Chapter: Activities"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Activities &raquo;</span> <a href="auth.html" title="Next Chapter: Authentication"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Authentication &raquo;</span>
</a> </a>
</li> </li>
@ -129,13 +129,13 @@
<div class="toctree-wrapper compound"> <div class="toctree-wrapper compound">
<p class="caption"><span class="caption-text">Endpoints:</span></p> <p class="caption"><span class="caption-text">Endpoints:</span></p>
<ul> <ul>
<li class="toctree-l1"><a class="reference internal" href="activities.html">Activities</a></li>
<li class="toctree-l1"><a class="reference internal" href="auth.html">Authentication</a></li> <li class="toctree-l1"><a class="reference internal" href="auth.html">Authentication</a></li>
<li class="toctree-l1"><a class="reference internal" href="configuration.html">Configuration</a></li> <li class="toctree-l1"><a class="reference internal" href="configuration.html">Configuration</a></li>
<li class="toctree-l1"><a class="reference internal" href="records.html">Records</a></li> <li class="toctree-l1"><a class="reference internal" href="records.html">Records</a></li>
<li class="toctree-l1"><a class="reference internal" href="sports.html">Sports</a></li> <li class="toctree-l1"><a class="reference internal" href="sports.html">Sports</a></li>
<li class="toctree-l1"><a class="reference internal" href="stats.html">Statistics</a></li> <li class="toctree-l1"><a class="reference internal" href="stats.html">Statistics</a></li>
<li class="toctree-l1"><a class="reference internal" href="users.html">Users</a></li> <li class="toctree-l1"><a class="reference internal" href="users.html">Users</a></li>
<li class="toctree-l1"><a class="reference internal" href="workouts.html">Workouts</a></li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -155,40 +155,40 @@
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">9</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">9</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;AS&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;AS&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mi">18</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mi">18</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;FD&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;FD&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mi">18</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mi">18</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">11</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">11</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;LD&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;LD&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="s2">&quot;1:01:00&quot;</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="s2">&quot;1:01:00&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">12</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">12</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;MS&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;MS&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mi">18</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mi">18</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span>
<span class="p">}</span> <span class="p">}</span>
<span class="p">]</span> <span class="p">]</span>
<span class="p">},</span> <span class="p">},</span>

View File

@ -197,42 +197,42 @@
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;has_activities&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/cycling-sport.png&quot;</span><span class="p">,</span> <span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/cycling-sport.png&quot;</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span> <span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;has_activities&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/cycling-transport.png&quot;</span><span class="p">,</span> <span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/cycling-transport.png&quot;</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Transport)&quot;</span> <span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Transport)&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;has_activities&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/hiking.png&quot;</span><span class="p">,</span> <span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/hiking.png&quot;</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Hiking&quot;</span> <span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Hiking&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;has_activities&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/mountain-biking.png&quot;</span><span class="p">,</span> <span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/mountain-biking.png&quot;</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Mountain Biking&quot;</span> <span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Mountain Biking&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;has_activities&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
<span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/running.png&quot;</span><span class="p">,</span> <span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/running.png&quot;</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Running&quot;</span> <span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Running&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;has_activities&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/walking.png&quot;</span><span class="p">,</span> <span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/walking.png&quot;</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
@ -310,7 +310,7 @@
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;has_activities&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/cycling-sport.png&quot;</span><span class="p">,</span> <span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/cycling-sport.png&quot;</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
@ -384,7 +384,7 @@ Authenticated user must be an admin</p>
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;has_activities&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/cycling-sport.png&quot;</span><span class="p">,</span> <span class="nt">&quot;img&quot;</span><span class="p">:</span> <span class="s2">&quot;/img/sports/cycling-sport.png&quot;</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>

View File

@ -129,7 +129,7 @@
<dl class="http get"> <dl class="http get">
<dt id="get--api-stats-(user_name)-by_time"> <dt id="get--api-stats-(user_name)-by_time">
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/stats/</code><span class="sig-paren">(</span><em class="sig-param">user_name</em><span class="sig-paren">)</span><code class="sig-name descname">/by_time</code><a class="headerlink" href="#get--api-stats-(user_name)-by_time" title="Permalink to this definition"></a></dt> <code class="sig-name descname">GET </code><code class="sig-name descname">/api/stats/</code><span class="sig-paren">(</span><em class="sig-param">user_name</em><span class="sig-paren">)</span><code class="sig-name descname">/by_time</code><a class="headerlink" href="#get--api-stats-(user_name)-by_time" title="Permalink to this definition"></a></dt>
<dd><p>Get activities statistics for a user by time</p> <dd><p>Get workouts statistics for a user by time</p>
<p><strong>Example requests</strong>:</p> <p><strong>Example requests</strong>:</p>
<ul class="simple"> <ul class="simple">
<li><p>without parameters</p></li> <li><p>without parameters</p></li>
@ -140,7 +140,8 @@
<ul class="simple"> <ul class="simple">
<li><p>with parameters</p></li> <li><p>with parameters</p></li>
</ul> </ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/stats/admin/by_time?from=2018-01-01&amp;to=2018-06-30&amp;time=week</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/stats/admin/by_time?from=2018-01-01&amp;to=2018-06-30&amp;time=week</span>
<span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
</pre></div> </pre></div>
</div> </div>
<p><strong>Example responses</strong>:</p> <p><strong>Example responses</strong>:</p>
@ -155,19 +156,19 @@
<span class="nt">&quot;statistics&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;statistics&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;2017&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;2017&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;3&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;3&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mf">15.282</span><span class="p">,</span> <span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mf">15.282</span><span class="p">,</span>
<span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">12341</span> <span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">12341</span>
<span class="p">}</span> <span class="p">}</span>
<span class="p">},</span> <span class="p">},</span>
<span class="nt">&quot;2019&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;2019&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;1&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;1&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mi">47</span><span class="p">,</span> <span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mi">47</span><span class="p">,</span>
<span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">9960</span> <span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">9960</span>
<span class="p">},</span> <span class="p">},</span>
<span class="nt">&quot;2&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;2&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mf">5.613</span><span class="p">,</span> <span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mf">5.613</span><span class="p">,</span>
<span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">1267</span> <span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">1267</span>
<span class="p">}</span> <span class="p">}</span>
@ -179,7 +180,7 @@
</pre></div> </pre></div>
</div> </div>
<ul class="simple"> <ul class="simple">
<li><p>no activities</p></li> <li><p>no workouts</p></li>
</ul> </ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -239,10 +240,10 @@
<dl class="http get"> <dl class="http get">
<dt id="get--api-stats-(user_name)-by_sport"> <dt id="get--api-stats-(user_name)-by_sport">
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/stats/</code><span class="sig-paren">(</span><em class="sig-param">user_name</em><span class="sig-paren">)</span><code class="sig-name descname">/by_sport</code><a class="headerlink" href="#get--api-stats-(user_name)-by_sport" title="Permalink to this definition"></a></dt> <code class="sig-name descname">GET </code><code class="sig-name descname">/api/stats/</code><span class="sig-paren">(</span><em class="sig-param">user_name</em><span class="sig-paren">)</span><code class="sig-name descname">/by_sport</code><a class="headerlink" href="#get--api-stats-(user_name)-by_sport" title="Permalink to this definition"></a></dt>
<dd><p>Get activities statistics for a user by sport</p> <dd><p>Get workouts statistics for a user by sport</p>
<p><strong>Example requests</strong>:</p> <p><strong>Example requests</strong>:</p>
<ul class="simple"> <ul class="simple">
<li><p>without parameters (get stats for all sports with activities)</p></li> <li><p>without parameters (get stats for all sports with workouts)</p></li>
</ul> </ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/stats/admin/by_sport</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/stats/admin/by_sport</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
</pre></div> </pre></div>
@ -264,17 +265,17 @@
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;statistics&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;statistics&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;1&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;1&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mi">47</span><span class="p">,</span> <span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mi">47</span><span class="p">,</span>
<span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">9960</span> <span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">9960</span>
<span class="p">},</span> <span class="p">},</span>
<span class="nt">&quot;2&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;2&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mf">5.613</span><span class="p">,</span> <span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mf">5.613</span><span class="p">,</span>
<span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">1267</span> <span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">1267</span>
<span class="p">},</span> <span class="p">},</span>
<span class="nt">&quot;3&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;3&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mf">15.282</span><span class="p">,</span> <span class="nt">&quot;total_distance&quot;</span><span class="p">:</span> <span class="mf">15.282</span><span class="p">,</span>
<span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">12341</span> <span class="nt">&quot;total_duration&quot;</span><span class="p">:</span> <span class="mi">12341</span>
<span class="p">}</span> <span class="p">}</span>
@ -285,7 +286,7 @@
</pre></div> </pre></div>
</div> </div>
<ul class="simple"> <ul class="simple">
<li><p>no activities</p></li> <li><p>no workouts</p></li>
</ul> </ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -348,10 +349,10 @@
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;activities&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;uploads_dir_size&quot;</span><span class="p">:</span> <span class="mi">1000</span><span class="p">,</span>
<span class="nt">&quot;users&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nt">&quot;users&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="nt">&quot;uploads_dir_size&quot;</span><span class="p">:</span> <span class="mi">1000</span> <span class="nt">&quot;workouts&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="p">},</span> <span class="p">},</span>
<span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;success&quot;</span> <span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;success&quot;</span>
<span class="p">}</span> <span class="p">}</span>

View File

@ -15,7 +15,7 @@
<script src="../_static/doctools.js"></script> <script src="../_static/doctools.js"></script>
<link rel="index" title="Index" href="../genindex.html" /> <link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" /> <link rel="search" title="Search" href="../search.html" />
<link rel="next" title="Troubleshooting" href="../troubleshooting/index.html" /> <link rel="next" title="Workouts" href="workouts.html" />
<link rel="prev" title="Statistics" href="stats.html" /> <link rel="prev" title="Statistics" href="stats.html" />
<meta charset='utf-8'> <meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'> <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
@ -90,7 +90,7 @@
</a> </a>
</li> </li>
<li> <li>
<a href="../troubleshooting/index.html" title="Next Chapter: Troubleshooting"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Troubleshooting &raquo;</span> <a href="workouts.html" title="Next Chapter: Workouts"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Workouts &raquo;</span>
</a> </a>
</li> </li>
@ -141,7 +141,7 @@
<ul class="simple"> <ul class="simple">
<li><p>with some query parameters</p></li> <li><p>with some query parameters</p></li>
</ul> </ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/users?order_by=activities_count&amp;par_page=5</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/users?order_by=workouts_count&amp;par_page=5</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div> </pre></div>
</div> </div>
@ -162,8 +162,8 @@
<span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;en&quot;</span><span class="p">,</span> <span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;en&quot;</span><span class="p">,</span>
<span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span>
@ -185,8 +185,8 @@
<span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;fr&quot;</span><span class="p">,</span> <span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;fr&quot;</span><span class="p">,</span>
<span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[],</span>
<span class="nt">&quot;timezone&quot;</span><span class="p">:</span> <span class="s2">&quot;Europe/Paris&quot;</span><span class="p">,</span> <span class="nt">&quot;timezone&quot;</span><span class="p">:</span> <span class="s2">&quot;Europe/Paris&quot;</span><span class="p">,</span>
@ -212,7 +212,7 @@
<li><p><strong>per_page</strong> (<em>integer</em>) number of users per page (default: 10, max: 50)</p></li> <li><p><strong>per_page</strong> (<em>integer</em>) number of users per page (default: 10, max: 50)</p></li>
<li><p><strong>q</strong> (<em>string</em>) query on user name</p></li> <li><p><strong>q</strong> (<em>string</em>) query on user name</p></li>
<li><p><strong>order_by</strong> (<em>string</em>) sorting criteria (<code class="docutils literal notranslate"><span class="pre">username</span></code>, <code class="docutils literal notranslate"><span class="pre">created_at</span></code>, <li><p><strong>order_by</strong> (<em>string</em>) sorting criteria (<code class="docutils literal notranslate"><span class="pre">username</span></code>, <code class="docutils literal notranslate"><span class="pre">created_at</span></code>,
<code class="docutils literal notranslate"><span class="pre">activities_count</span></code>, <code class="docutils literal notranslate"><span class="pre">admin</span></code>)</p></li> <code class="docutils literal notranslate"><span class="pre">workouts_count</span></code>, <code class="docutils literal notranslate"><span class="pre">admin</span></code>)</p></li>
<li><p><strong>order</strong> (<em>string</em>) sorting order (default: <code class="docutils literal notranslate"><span class="pre">asc</span></code>)</p></li> <li><p><strong>order</strong> (<em>string</em>) sorting order (default: <code class="docutils literal notranslate"><span class="pre">asc</span></code>)</p></li>
</ul> </ul>
</dd> </dd>
@ -260,8 +260,8 @@
<span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;en&quot;</span><span class="p">,</span> <span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;en&quot;</span><span class="p">,</span>
<span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span>
@ -367,7 +367,7 @@
<span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;en&quot;</span><span class="p">,</span> <span class="nt">&quot;language&quot;</span><span class="p">:</span> <span class="s2">&quot;en&quot;</span><span class="p">,</span>
<span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;last_name&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;location&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;nb_activities&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span> <span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&quot;picture&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;sports_list&quot;</span><span class="p">:</span> <span class="p">[</span>

View File

@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Activities &#8212; FitTrackee 0.4.2 <title>Workouts &#8212; FitTrackee 0.4.2
documentation</title> documentation</title>
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> <link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="../_static/bootstrap-sphinx.css" type="text/css" /> <link rel="stylesheet" href="../_static/bootstrap-sphinx.css" type="text/css" />
@ -15,8 +15,8 @@
<script src="../_static/doctools.js"></script> <script src="../_static/doctools.js"></script>
<link rel="index" title="Index" href="../genindex.html" /> <link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" /> <link rel="search" title="Search" href="../search.html" />
<link rel="next" title="Authentication" href="auth.html" /> <link rel="next" title="Troubleshooting" href="../troubleshooting/index.html" />
<link rel="prev" title="API documentation" href="index.html" /> <link rel="prev" title="Users" href="users.html" />
<meta charset='utf-8'> <meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'> <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1'> <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1'>
@ -76,7 +76,7 @@
<ul class="dropdown-menu localtoc" <ul class="dropdown-menu localtoc"
role="menu" role="menu"
aria-labelledby="dLabelLocalToc"><ul> aria-labelledby="dLabelLocalToc"><ul>
<li><a class="reference internal" href="#">Activities</a></li> <li><a class="reference internal" href="#">Workouts</a></li>
</ul> </ul>
</ul> </ul>
</li> </li>
@ -86,11 +86,11 @@
<li> <li>
<a href="index.html" title="Previous Chapter: API documentation"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">&laquo; API documentation</span> <a href="users.html" title="Previous Chapter: Users"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">&laquo; Users</span>
</a> </a>
</li> </li>
<li> <li>
<a href="auth.html" title="Next Chapter: Authentication"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Authentication &raquo;</span> <a href="../troubleshooting/index.html" title="Next Chapter: Troubleshooting"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Troubleshooting &raquo;</span>
</a> </a>
</li> </li>
@ -100,7 +100,7 @@
<li class="hidden-sm"> <li class="hidden-sm">
<div id="sourcelink"> <div id="sourcelink">
<a href="../_sources/api/activities.rst.txt" <a href="../_sources/api/workouts.rst.txt"
rel="nofollow">Source</a> rel="nofollow">Source</a>
</div></li> </div></li>
@ -124,37 +124,36 @@
<div class="row"> <div class="row">
<div class="body col-md-12 content" role="main"> <div class="body col-md-12 content" role="main">
<div class="section" id="activities"> <div class="section" id="workouts">
<h1>Activities<a class="headerlink" href="#activities" title="Permalink to this headline"></a></h1> <h1>Workouts<a class="headerlink" href="#workouts" title="Permalink to this headline"></a></h1>
<dl class="http get"> <dl class="http get">
<dt id="get--api-activities"> <dt id="get--api-workouts">
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities</code><a class="headerlink" href="#get--api-activities" title="Permalink to this definition"></a></dt> <code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts</code><a class="headerlink" href="#get--api-workouts" title="Permalink to this definition"></a></dt>
<dd><p>Get activities for the authenticated user.</p> <dd><p>Get workouts for the authenticated user.</p>
<p><strong>Example requests</strong>:</p> <p><strong>Example requests</strong>:</p>
<ul class="simple"> <ul class="simple">
<li><p>without parameters</p></li> <li><p>without parameters</p></li>
</ul> </ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
</pre></div> </pre></div>
</div> </div>
<ul class="simple"> <ul class="simple">
<li><p>with some query parameters</p></li> <li><p>with some query parameters</p></li>
</ul> </ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities?from=2019-07-02&amp;to=2019-07-31&amp;sport_id=1</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts?from=2019-07-02&amp;to=2019-07-31&amp;sport_id=1</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
</pre></div> </pre></div>
</div> </div>
<p><strong>Example responses</strong>:</p> <p><strong>Example responses</strong>:</p>
<ul class="simple"> <ul class="simple">
<li><p>returning at least one activity</p></li> <li><p>returning at least one workout</p></li>
</ul> </ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;activities&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;workouts&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;ascent&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;ascent&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;ave_speed&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span> <span class="nt">&quot;ave_speed&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;bounds&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;bounds&quot;</span><span class="p">:</span> <span class="p">[],</span>
@ -169,46 +168,46 @@
<span class="nt">&quot;min_alt&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;min_alt&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;modification_date&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;modification_date&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;moving&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span> <span class="nt">&quot;moving&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span>
<span class="nt">&quot;next_activity&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;next_workout&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;notes&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;notes&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;pauses&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;pauses&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;previous_activity&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;previous_workout&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;MS&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;MS&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;LD&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;LD&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;FD&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;FD&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;AS&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;AS&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">}</span> <span class="p">}</span>
<span class="p">],</span> <span class="p">],</span>
<span class="nt">&quot;segments&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;segments&quot;</span><span class="p">:</span> <span class="p">[],</span>
@ -217,7 +216,8 @@
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;weather_end&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;weather_end&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;weather_start&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;weather_start&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;with_gpx&quot;</span><span class="p">:</span> <span class="kc">false</span> <span class="nt">&quot;with_gpx&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span>
<span class="p">}</span> <span class="p">}</span>
<span class="p">]</span> <span class="p">]</span>
<span class="p">},</span> <span class="p">},</span>
@ -226,14 +226,14 @@
</pre></div> </pre></div>
</div> </div>
<ul class="simple"> <ul class="simple">
<li><p>returning no activities</p></li> <li><p>returning no workouts</p></li>
</ul> </ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;activities&quot;</span><span class="p">:</span> <span class="p">[]</span> <span class="nt">&quot;workouts&quot;</span><span class="p">:</span> <span class="p">[]</span>
<span class="p">},</span> <span class="p">},</span>
<span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;success&quot;</span> <span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;success&quot;</span>
<span class="p">}</span> <span class="p">}</span>
@ -248,7 +248,7 @@
<dt class="field-even">Query Parameters</dt> <dt class="field-even">Query Parameters</dt>
<dd class="field-even"><ul class="simple"> <dd class="field-even"><ul class="simple">
<li><p><strong>page</strong> (<em>integer</em>) page if using pagination (default: 1)</p></li> <li><p><strong>page</strong> (<em>integer</em>) page if using pagination (default: 1)</p></li>
<li><p><strong>per_page</strong> (<em>integer</em>) number of activities per page <li><p><strong>per_page</strong> (<em>integer</em>) number of workouts per page
(default: 5, max: 50)</p></li> (default: 5, max: 50)</p></li>
<li><p><strong>sport_id</strong> (<em>integer</em>) sport id</p></li> <li><p><strong>sport_id</strong> (<em>integer</em>) sport id</p></li>
<li><p><strong>from</strong> (<em>string</em>) start date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span></code>)</p></li> <li><p><strong>from</strong> (<em>string</em>) start date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span></code>)</p></li>
@ -285,11 +285,11 @@
</dd></dl> </dd></dl>
<dl class="http get"> <dl class="http get">
<dt id="get--api-activities-(string-activity_short_id)"> <dt id="get--api-workouts-(string-workout_short_id)">
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-activities-(string-activity_short_id)" title="Permalink to this definition"></a></dt> <code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)" title="Permalink to this definition"></a></dt>
<dd><p>Get an activity</p> <dd><p>Get an workout</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
</pre></div> </pre></div>
</div> </div>
<p><strong>Example responses</strong>:</p> <p><strong>Example responses</strong>:</p>
@ -301,9 +301,8 @@
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;activities&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;workouts&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 07 Jul 2019 07:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;ascent&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;ascent&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;ave_speed&quot;</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span> <span class="nt">&quot;ave_speed&quot;</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span>
<span class="nt">&quot;bounds&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;bounds&quot;</span><span class="p">:</span> <span class="p">[],</span>
@ -318,10 +317,10 @@
<span class="nt">&quot;min_alt&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;min_alt&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;modification_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 14 Jul 2019 18:57:22 GMT&quot;</span><span class="p">,</span> <span class="nt">&quot;modification_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 14 Jul 2019 18:57:22 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;moving&quot;</span><span class="p">:</span> <span class="s2">&quot;0:45:00&quot;</span><span class="p">,</span> <span class="nt">&quot;moving&quot;</span><span class="p">:</span> <span class="s2">&quot;0:45:00&quot;</span><span class="p">,</span>
<span class="nt">&quot;next_activity&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span> <span class="nt">&quot;next_workout&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="nt">&quot;notes&quot;</span><span class="p">:</span> <span class="s2">&quot;activity without gpx&quot;</span><span class="p">,</span> <span class="nt">&quot;notes&quot;</span><span class="p">:</span> <span class="s2">&quot;workout without gpx&quot;</span><span class="p">,</span>
<span class="nt">&quot;pauses&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;pauses&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;previous_activity&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;previous_workout&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[],</span>
<span class="nt">&quot;segments&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;segments&quot;</span><span class="p">:</span> <span class="p">[],</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
@ -329,7 +328,8 @@
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;weather_end&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;weather_end&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;weather_start&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;weather_start&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;with_gpx&quot;</span><span class="p">:</span> <span class="kc">false</span> <span class="nt">&quot;with_gpx&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Sun, 07 Jul 2019 07:00:00 GMT&quot;</span>
<span class="p">}</span> <span class="p">}</span>
<span class="p">]</span> <span class="p">]</span>
<span class="p">},</span> <span class="p">},</span>
@ -345,7 +345,7 @@
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;activities&quot;</span><span class="p">:</span> <span class="p">[]</span> <span class="nt">&quot;workouts&quot;</span><span class="p">:</span> <span class="p">[]</span>
<span class="p">},</span> <span class="p">},</span>
<span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;not found&quot;</span> <span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;not found&quot;</span>
<span class="p">}</span> <span class="p">}</span>
@ -355,7 +355,7 @@
<dt class="field-odd">Parameters</dt> <dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li> <li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li>
<li><p><strong>activity_short_id</strong> (<em>string</em>) activity short id</p></li> <li><p><strong>workout_short_id</strong> (<em>string</em>) workout short id</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-even">Request Headers</dt> <dt class="field-even">Request Headers</dt>
@ -373,18 +373,18 @@
</ul> </ul>
</p></li> </p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4">403 Forbidden</a> You do not have permissions.</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4">403 Forbidden</a> You do not have permissions.</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> activity not found</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> workout not found</p></li>
</ul> </ul>
</dd> </dd>
</dl> </dl>
</dd></dl> </dd></dl>
<dl class="http get"> <dl class="http get">
<dt id="get--api-activities-(string-activity_short_id)-gpx"> <dt id="get--api-workouts-(string-workout_short_id)-gpx">
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/gpx</code><a class="headerlink" href="#get--api-activities-(string-activity_short_id)-gpx" title="Permalink to this definition"></a></dt> <code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/gpx</code><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-gpx" title="Permalink to this definition"></a></dt>
<dd><p>Get gpx file for an activity displayed on map with Leaflet</p> <dd><p>Get gpx file for an workout displayed on map with Leaflet</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF/gpx</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/gpx</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div> </pre></div>
</div> </div>
@ -405,7 +405,7 @@
<dt class="field-odd">Parameters</dt> <dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li> <li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li>
<li><p><strong>activity_short_id</strong> (<em>string</em>) activity short id</p></li> <li><p><strong>workout_short_id</strong> (<em>string</em>) workout short id</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-even">Request Headers</dt> <dt class="field-even">Request Headers</dt>
@ -423,8 +423,8 @@
</ul> </ul>
</p></li> </p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> <ul> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> <ul>
<li><p>activity not found</p></li> <li><p>workout not found</p></li>
<li><p>no gpx file for this activity</p></li> <li><p>no gpx file for this workout</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.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>
@ -434,11 +434,11 @@
</dd></dl> </dd></dl>
<dl class="http get"> <dl class="http get">
<dt id="get--api-activities-(string-activity_short_id)-chart_data"> <dt id="get--api-workouts-(string-workout_short_id)-chart_data">
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/chart_data</code><a class="headerlink" href="#get--api-activities-(string-activity_short_id)-chart_data" title="Permalink to this definition"></a></dt> <code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/chart_data</code><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-chart_data" title="Permalink to this definition"></a></dt>
<dd><p>Get chart data from an activity gpx file, to display it with Recharts</p> <dd><p>Get chart data from an workout gpx file, to display it with Recharts</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF/chart</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/chart</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div> </pre></div>
</div> </div>
@ -478,7 +478,7 @@
<dt class="field-odd">Parameters</dt> <dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li> <li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li>
<li><p><strong>activity_short_id</strong> (<em>string</em>) activity short id</p></li> <li><p><strong>workout_short_id</strong> (<em>string</em>) workout short id</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-even">Request Headers</dt> <dt class="field-even">Request Headers</dt>
@ -496,8 +496,8 @@
</ul> </ul>
</p></li> </p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> <ul> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> <ul>
<li><p>activity not found</p></li> <li><p>workout not found</p></li>
<li><p>no gpx file for this activity</p></li> <li><p>no gpx file for this workout</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.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>
@ -507,11 +507,11 @@
</dd></dl> </dd></dl>
<dl class="http get"> <dl class="http get">
<dt id="get--api-activities-(string-activity_short_id)-chart_data-segment-(int-segment_id)"> <dt id="get--api-workouts-(string-workout_short_id)-chart_data-segment-(int-segment_id)">
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/chart_data/segment/</code><span class="sig-paren">(</span><em class="property">int: </em><em class="sig-param">segment_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-activities-(string-activity_short_id)-chart_data-segment-(int-segment_id)" title="Permalink to this definition"></a></dt> <code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/chart_data/segment/</code><span class="sig-paren">(</span><em class="property">int: </em><em class="sig-param">segment_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-chart_data-segment-(int-segment_id)" title="Permalink to this definition"></a></dt>
<dd><p>Get chart data from an activity gpx file, to display it with Recharts</p> <dd><p>Get chart data from an workout gpx file, to display it with Recharts</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF/chart/segment/0</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/chart/segment/0</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div> </pre></div>
</div> </div>
@ -551,7 +551,7 @@
<dt class="field-odd">Parameters</dt> <dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li> <li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li>
<li><p><strong>activity_short_id</strong> (<em>string</em>) activity short id</p></li> <li><p><strong>workout_short_id</strong> (<em>string</em>) workout short id</p></li>
<li><p><strong>segment_id</strong> (<em>integer</em>) segment id</p></li> <li><p><strong>segment_id</strong> (<em>integer</em>) segment id</p></li>
</ul> </ul>
</dd> </dd>
@ -563,14 +563,14 @@
<dt class="field-odd">Status Codes</dt> <dt class="field-odd">Status Codes</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> success</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> success</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> no gpx file for this activity</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> no gpx file for this workout</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> <ul> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> <ul>
<li><p>Provide a valid auth token.</p></li> <li><p>Provide a valid auth token.</p></li>
<li><p>Signature expired. Please log in again.</p></li> <li><p>Signature expired. Please log in again.</p></li>
<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.5">404 Not Found</a> activity not found</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> workout not found</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>
@ -578,11 +578,11 @@
</dd></dl> </dd></dl>
<dl class="http get"> <dl class="http get">
<dt id="get--api-activities-(string-activity_short_id)-gpx-segment-(int-segment_id)"> <dt id="get--api-workouts-(string-workout_short_id)-gpx-segment-(int-segment_id)">
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/gpx/segment/</code><span class="sig-paren">(</span><em class="property">int: </em><em class="sig-param">segment_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-activities-(string-activity_short_id)-gpx-segment-(int-segment_id)" title="Permalink to this definition"></a></dt> <code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/gpx/segment/</code><span class="sig-paren">(</span><em class="property">int: </em><em class="sig-param">segment_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-gpx-segment-(int-segment_id)" title="Permalink to this definition"></a></dt>
<dd><p>Get gpx file for an activity segment displayed on map with Leaflet</p> <dd><p>Get gpx file for an workout segment displayed on map with Leaflet</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF/gpx/segment/0</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/gpx/segment/0</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div> </pre></div>
</div> </div>
@ -603,7 +603,7 @@
<dt class="field-odd">Parameters</dt> <dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li> <li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li>
<li><p><strong>activity_short_id</strong> (<em>string</em>) activity short id</p></li> <li><p><strong>workout_short_id</strong> (<em>string</em>) workout short id</p></li>
<li><p><strong>segment_id</strong> (<em>integer</em>) segment id</p></li> <li><p><strong>segment_id</strong> (<em>integer</em>) segment id</p></li>
</ul> </ul>
</dd> </dd>
@ -615,14 +615,14 @@
<dt class="field-odd">Status Codes</dt> <dt class="field-odd">Status Codes</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> success</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> success</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> no gpx file for this activity</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> no gpx file for this workout</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> <ul> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> <ul>
<li><p>Provide a valid auth token.</p></li> <li><p>Provide a valid auth token.</p></li>
<li><p>Signature expired. Please log in again.</p></li> <li><p>Signature expired. Please log in again.</p></li>
<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.5">404 Not Found</a> activity not found</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> workout not found</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>
@ -630,11 +630,11 @@
</dd></dl> </dd></dl>
<dl class="http get"> <dl class="http get">
<dt id="get--api-activities-map-(map_id)"> <dt id="get--api-workouts-map-(map_id)">
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/map/</code><span class="sig-paren">(</span><em class="sig-param">map_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-activities-map-(map_id)" title="Permalink to this definition"></a></dt> <code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/map/</code><span class="sig-paren">(</span><em class="sig-param">map_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-workouts-map-(map_id)" title="Permalink to this definition"></a></dt>
<dd><p>Get map image for activities with gpx</p> <dd><p>Get map image for workouts with gpx</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/map/fa33f4d996844a5c73ecd1ae24456ab8?1563529507772</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/map/fa33f4d996844a5c73ecd1ae24456ab8?1563529507772</span>
<span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
</pre></div> </pre></div>
</div> </div>
@ -646,7 +646,7 @@
<dl class="field-list simple"> <dl class="field-list simple">
<dt class="field-odd">Parameters</dt> <dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>map_id</strong> (<em>string</em>) activity map id</p></li> <li><p><strong>map_id</strong> (<em>string</em>) workout map id</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-even">Status Codes</dt> <dt class="field-even">Status Codes</dt>
@ -666,11 +666,11 @@
</dd></dl> </dd></dl>
<dl class="http get"> <dl class="http get">
<dt id="get--api-activities-map_tile-(s)-(z)-(x)-(y).png"> <dt id="get--api-workouts-map_tile-(s)-(z)-(x)-(y).png">
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/map_tile/</code><span class="sig-paren">(</span><em class="sig-param">s</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">z</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">x</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">y</em><span class="sig-paren">)</span><code class="sig-name descname">.png</code><a class="headerlink" href="#get--api-activities-map_tile-(s)-(z)-(x)-(y).png" title="Permalink to this definition"></a></dt> <code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/map_tile/</code><span class="sig-paren">(</span><em class="sig-param">s</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">z</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">x</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">y</em><span class="sig-paren">)</span><code class="sig-name descname">.png</code><a class="headerlink" href="#get--api-workouts-map_tile-(s)-(z)-(x)-(y).png" title="Permalink to this definition"></a></dt>
<dd><p>Get map tile from tile server.</p> <dd><p>Get map tile from tile server.</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/map_tile/c/13/4109/2930.png</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/map_tile/c/13/4109/2930.png</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
</pre></div> </pre></div>
</div> </div>
<p><strong>Example response</strong>:</p> <p><strong>Example response</strong>:</p>
@ -692,11 +692,11 @@
</dd></dl> </dd></dl>
<dl class="http post"> <dl class="http post">
<dt id="post--api-activities"> <dt id="post--api-workouts">
<code class="sig-name descname">POST </code><code class="sig-name descname">/api/activities</code><a class="headerlink" href="#post--api-activities" title="Permalink to this definition"></a></dt> <code class="sig-name descname">POST </code><code class="sig-name descname">/api/workouts</code><a class="headerlink" href="#post--api-workouts" title="Permalink to this definition"></a></dt>
<dd><p>Post an activity with a gpx file</p> <dd><p>Post an workout with a gpx file</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/activities/</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/workouts/</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">multipart/form-data</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">multipart/form-data</span>
</pre></div> </pre></div>
</div> </div>
@ -706,9 +706,8 @@
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;activities&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;workouts&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;ascent&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;ascent&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;ave_speed&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span> <span class="nt">&quot;ave_speed&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;bounds&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;bounds&quot;</span><span class="p">:</span> <span class="p">[],</span>
@ -723,46 +722,46 @@
<span class="nt">&quot;min_alt&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;min_alt&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;modification_date&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;modification_date&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;moving&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span> <span class="nt">&quot;moving&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span>
<span class="nt">&quot;next_activity&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;next_workout&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;notes&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;notes&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;pauses&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;pauses&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;previous_activity&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;previous_workout&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;MS&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;MS&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;LD&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;LD&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;FD&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;FD&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;AS&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;AS&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">}</span> <span class="p">}</span>
<span class="p">],</span> <span class="p">],</span>
<span class="nt">&quot;segments&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;segments&quot;</span><span class="p">:</span> <span class="p">[],</span>
@ -771,7 +770,8 @@
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;weather_end&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;weather_end&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;weather_start&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;weather_start&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;with_gpx&quot;</span><span class="p">:</span> <span class="kc">false</span> <span class="nt">&quot;with_gpx&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span>
<span class="p">}</span> <span class="p">}</span>
<span class="p">]</span> <span class="p">]</span>
<span class="p">},</span> <span class="p">},</span>
@ -798,7 +798,7 @@
</dd> </dd>
<dt class="field-even">Status Codes</dt> <dt class="field-even">Status Codes</dt>
<dd class="field-even"><ul class="simple"> <dd class="field-even"><ul class="simple">
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2">201 Created</a> activity created</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2">201 Created</a> workout created</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> <ul> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> <ul>
<li><p>Invalid payload.</p></li> <li><p>Invalid payload.</p></li>
<li><p>No file part.</p></li> <li><p>No file part.</p></li>
@ -820,11 +820,11 @@
</dd></dl> </dd></dl>
<dl class="http post"> <dl class="http post">
<dt id="post--api-activities-no_gpx"> <dt id="post--api-workouts-no_gpx">
<code class="sig-name descname">POST </code><code class="sig-name descname">/api/activities/no_gpx</code><a class="headerlink" href="#post--api-activities-no_gpx" title="Permalink to this definition"></a></dt> <code class="sig-name descname">POST </code><code class="sig-name descname">/api/workouts/no_gpx</code><a class="headerlink" href="#post--api-workouts-no_gpx" title="Permalink to this definition"></a></dt>
<dd><p>Post an activity without gpx file</p> <dd><p>Post an workout without gpx file</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/activities/no_gpx</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/workouts/no_gpx</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div> </pre></div>
</div> </div>
@ -834,9 +834,8 @@
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;activities&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;workouts&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;ascent&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;ascent&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;ave_speed&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span> <span class="nt">&quot;ave_speed&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;bounds&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;bounds&quot;</span><span class="p">:</span> <span class="p">[],</span>
@ -850,46 +849,46 @@
<span class="nt">&quot;min_alt&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;min_alt&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;modification_date&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;modification_date&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;moving&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span> <span class="nt">&quot;moving&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span>
<span class="nt">&quot;next_activity&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;next_workout&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;notes&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;notes&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;pauses&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;pauses&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;previous_activity&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;previous_workout&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;MS&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;MS&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;LD&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;LD&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;FD&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;FD&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;AS&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;AS&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">}</span> <span class="p">}</span>
<span class="p">],</span> <span class="p">],</span>
<span class="nt">&quot;segments&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;segments&quot;</span><span class="p">:</span> <span class="p">[],</span>
@ -899,7 +898,8 @@
<span class="nt">&quot;uuid&quot;</span><span class="p">:</span> <span class="nt">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span> <span class="nt">&quot;uuid&quot;</span><span class="p">:</span> <span class="nt">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="nt">&quot;weather_end&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;weather_end&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;weather_start&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;weather_start&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;with_gpx&quot;</span><span class="p">:</span> <span class="kc">false</span> <span class="nt">&quot;with_gpx&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span>
<span class="p">}</span> <span class="p">}</span>
<span class="p">]</span> <span class="p">]</span>
<span class="p">},</span> <span class="p">},</span>
@ -915,12 +915,12 @@
</dd> </dd>
<dt class="field-even">Request JSON Object</dt> <dt class="field-even">Request JSON Object</dt>
<dd class="field-even"><ul class="simple"> <dd class="field-even"><ul class="simple">
<li><p><strong>activity_date</strong> (<em>string</em>) activity date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>)</p></li> <li><p><strong>workout_date</strong> (<em>string</em>) workout date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>)</p></li>
<li><p><strong>distance</strong> (<em>float</em>) activity distance in km</p></li> <li><p><strong>distance</strong> (<em>float</em>) workout distance in km</p></li>
<li><p><strong>duration</strong> (<em>integer</em>) activity duration in seconds</p></li> <li><p><strong>duration</strong> (<em>integer</em>) workout duration in seconds</p></li>
<li><p><strong>notes</strong> (<em>string</em>) notes (not mandatory)</p></li> <li><p><strong>notes</strong> (<em>string</em>) notes (not mandatory)</p></li>
<li><p><strong>sport_id</strong> (<em>integer</em>) activity sport id</p></li> <li><p><strong>sport_id</strong> (<em>integer</em>) workout sport id</p></li>
<li><p><strong>title</strong> (<em>string</em>) activity title</p></li> <li><p><strong>title</strong> (<em>string</em>) workout title</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-odd">Request Headers</dt> <dt class="field-odd">Request Headers</dt>
@ -930,7 +930,7 @@
</dd> </dd>
<dt class="field-even">Status Codes</dt> <dt class="field-even">Status Codes</dt>
<dd class="field-even"><ul class="simple"> <dd class="field-even"><ul class="simple">
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2">201 Created</a> activity created</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2">201 Created</a> workout created</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> invalid payload</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> invalid payload</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> <ul> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> <ul>
<li><p>Provide a valid auth token.</p></li> <li><p>Provide a valid auth token.</p></li>
@ -945,11 +945,11 @@
</dd></dl> </dd></dl>
<dl class="http patch"> <dl class="http patch">
<dt id="patch--api-activities-(string-activity_short_id)"> <dt id="patch--api-workouts-(string-workout_short_id)">
<code class="sig-name descname">PATCH </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#patch--api-activities-(string-activity_short_id)" title="Permalink to this definition"></a></dt> <code class="sig-name descname">PATCH </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#patch--api-workouts-(string-workout_short_id)" title="Permalink to this definition"></a></dt>
<dd><p>Update an activity</p> <dd><p>Update an workout</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">PATCH</span> <span class="nn">/api/activities/1</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">PATCH</span> <span class="nn">/api/workouts/1</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div> </pre></div>
</div> </div>
@ -959,9 +959,8 @@
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;activities&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;workouts&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;ascent&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;ascent&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;ave_speed&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span> <span class="nt">&quot;ave_speed&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;bounds&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;bounds&quot;</span><span class="p">:</span> <span class="p">[],</span>
@ -975,46 +974,46 @@
<span class="nt">&quot;min_alt&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;min_alt&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;modification_date&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;modification_date&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;moving&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span> <span class="nt">&quot;moving&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span>
<span class="nt">&quot;next_activity&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;next_workout&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;notes&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;notes&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;pauses&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;pauses&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;previous_activity&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;previous_workout&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[</span> <span class="nt">&quot;records&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;MS&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;MS&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;LD&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;LD&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="s2">&quot;0:17:04&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;FD&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;FD&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="p">},</span> <span class="p">},</span>
<span class="p">{</span> <span class="p">{</span>
<span class="nt">&quot;activity_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;activity_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;AS&quot;</span><span class="p">,</span> <span class="nt">&quot;record_type&quot;</span><span class="p">:</span> <span class="s2">&quot;AS&quot;</span><span class="p">,</span>
<span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nt">&quot;sport_id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span> <span class="nt">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span> <span class="nt">&quot;value&quot;</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span><span class="p">,</span>
<span class="nt">&quot;workout_id&quot;</span><span class="p">:</span> <span class="s2">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span><span class="p">,</span>
<span class="p">}</span> <span class="p">}</span>
<span class="p">],</span> <span class="p">],</span>
<span class="nt">&quot;segments&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="nt">&quot;segments&quot;</span><span class="p">:</span> <span class="p">[],</span>
@ -1024,7 +1023,8 @@
<span class="nt">&quot;uuid&quot;</span><span class="p">:</span> <span class="nt">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span> <span class="nt">&quot;uuid&quot;</span><span class="p">:</span> <span class="nt">&quot;kjxavSTUrJvoAh2wvCeGEF&quot;</span>
<span class="nt">&quot;weather_end&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;weather_end&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;weather_start&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="nt">&quot;weather_start&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;with_gpx&quot;</span><span class="p">:</span> <span class="kc">false</span> <span class="nt">&quot;with_gpx&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;workout_date&quot;</span><span class="p">:</span> <span class="s2">&quot;Mon, 01 Jan 2018 00:00:00 GMT&quot;</span>
<span class="p">}</span> <span class="p">}</span>
<span class="p">]</span> <span class="p">]</span>
<span class="p">},</span> <span class="p">},</span>
@ -1036,20 +1036,20 @@
<dt class="field-odd">Parameters</dt> <dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li> <li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li>
<li><p><strong>activity_short_id</strong> (<em>string</em>) activity short id</p></li> <li><p><strong>workout_short_id</strong> (<em>string</em>) workout short id</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-even">Request JSON Object</dt> <dt class="field-even">Request JSON Object</dt>
<dd class="field-even"><ul class="simple"> <dd class="field-even"><ul class="simple">
<li><p><strong>activity_date</strong> (<em>string</em>) activity date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>) <li><p><strong>workout_date</strong> (<em>string</em>) workout date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>)
(only for activity without gpx)</p></li> (only for workout without gpx)</p></li>
<li><p><strong>distance</strong> (<em>float</em>) activity distance in km <li><p><strong>distance</strong> (<em>float</em>) workout distance in km
(only for activity without gpx)</p></li> (only for workout without gpx)</p></li>
<li><p><strong>duration</strong> (<em>integer</em>) activity duration in seconds <li><p><strong>duration</strong> (<em>integer</em>) workout duration in seconds
(only for activity without gpx)</p></li> (only for workout without gpx)</p></li>
<li><p><strong>notes</strong> (<em>string</em>) notes</p></li> <li><p><strong>notes</strong> (<em>string</em>) notes</p></li>
<li><p><strong>sport_id</strong> (<em>integer</em>) activity sport id</p></li> <li><p><strong>sport_id</strong> (<em>integer</em>) workout sport id</p></li>
<li><p><strong>title</strong> (<em>string</em>) activity title</p></li> <li><p><strong>title</strong> (<em>string</em>) workout title</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-odd">Request Headers</dt> <dt class="field-odd">Request Headers</dt>
@ -1059,7 +1059,7 @@
</dd> </dd>
<dt class="field-even">Status Codes</dt> <dt class="field-even">Status Codes</dt>
<dd class="field-even"><ul class="simple"> <dd class="field-even"><ul class="simple">
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> activity updated</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> workout updated</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> invalid payload</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> invalid payload</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> <ul> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> <ul>
<li><p>Provide a valid auth token.</p></li> <li><p>Provide a valid auth token.</p></li>
@ -1067,7 +1067,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.5">404 Not Found</a> activity not found</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> workout not found</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>
@ -1075,11 +1075,11 @@
</dd></dl> </dd></dl>
<dl class="http delete"> <dl class="http delete">
<dt id="delete--api-activities-(string-activity_short_id)"> <dt id="delete--api-workouts-(string-workout_short_id)">
<code class="sig-name descname">DELETE </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#delete--api-activities-(string-activity_short_id)" title="Permalink to this definition"></a></dt> <code class="sig-name descname">DELETE </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#delete--api-workouts-(string-workout_short_id)" title="Permalink to this definition"></a></dt>
<dd><p>Delete an activity</p> <dd><p>Delete an workout</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">DELETE</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">DELETE</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div> </pre></div>
</div> </div>
@ -1092,7 +1092,7 @@
<dt class="field-odd">Parameters</dt> <dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li> <li><p><strong>auth_user_id</strong> (<em>integer</em>) authenticate user id (from JSON Web Token)</p></li>
<li><p><strong>activity_short_id</strong> (<em>string</em>) activity short id</p></li> <li><p><strong>workout_short_id</strong> (<em>string</em>) workout short id</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-even">Request Headers</dt> <dt class="field-even">Request Headers</dt>
@ -1102,14 +1102,14 @@
</dd> </dd>
<dt class="field-odd">Status Codes</dt> <dt class="field-odd">Status Codes</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5">204 No Content</a> activity deleted</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5">204 No Content</a> workout deleted</p></li>
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> <ul> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> <ul>
<li><p>Provide a valid auth token.</p></li> <li><p>Provide a valid auth token.</p></li>
<li><p>Signature expired. Please log in again.</p></li> <li><p>Signature expired. Please log in again.</p></li>
<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.5">404 Not Found</a> activity not found</p></li> <li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> workout not found</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. Please try again or contact the administrator.</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. Please try again or contact the administrator.</p></li>
</ul> </ul>
</dd> </dd>

View File

@ -80,13 +80,13 @@
<li><a class="reference internal" href="#list">List</a><ul> <li><a class="reference internal" href="#list">List</a><ul>
<li><a class="reference internal" href="#administration">Administration</a></li> <li><a class="reference internal" href="#administration">Administration</a></li>
<li><a class="reference internal" href="#account">Account</a></li> <li><a class="reference internal" href="#account">Account</a></li>
<li><a class="reference internal" href="#activities-workouts">Activities/Workouts</a></li> <li><a class="reference internal" href="#workouts">Workouts</a></li>
<li><a class="reference internal" href="#translations">Translations</a></li> <li><a class="reference internal" href="#translations">Translations</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#dashboard">Dashboard</a></li> <li><a class="reference internal" href="#dashboard">Dashboard</a></li>
<li><a class="reference internal" href="#activity-workout-detail">Activity/workout detail</a></li> <li><a class="reference internal" href="#workout-detail">Workout detail</a></li>
<li><a class="reference internal" href="#activities-workouts-list">Activities/workouts list</a></li> <li><a class="reference internal" href="#workouts-list">Workouts list</a></li>
<li><a class="reference internal" href="#statistics">Statistics</a></li> <li><a class="reference internal" href="#statistics">Statistics</a></li>
<li><a class="reference internal" href="#id1">Administration</a></li> <li><a class="reference internal" href="#id1">Administration</a></li>
</ul> </ul>
@ -164,7 +164,7 @@
</li> </li>
<li><p><strong>Sports</strong></p> <li><p><strong>Sports</strong></p>
<ul class="simple"> <ul class="simple">
<li><p>enable or disable a sport (a sport can be disabled even if activity with this sport exists)</p></li> <li><p>enable or disable a sport (a sport can be disabled even if workout with this sport exists)</p></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -176,11 +176,11 @@
<li><p>A user can reset his password (<em>new in 0.3.0</em>)</p></li> <li><p>A user can reset his password (<em>new in 0.3.0</em>)</p></li>
</ul> </ul>
</div> </div>
<div class="section" id="activities-workouts"> <div class="section" id="workouts">
<h3>Activities/Workouts<a class="headerlink" href="#activities-workouts" title="Permalink to this headline"></a></h3> <h3>Workouts<a class="headerlink" href="#workouts" title="Permalink to this headline"></a></h3>
<ul class="simple"> <ul class="simple">
<li><dl class="simple"> <li><dl class="simple">
<dt>6 sports supported:</dt><dd><ul> <dt>6 sports are supported:</dt><dd><ul>
<li><p>Cycling (Sport)</p></li> <li><p>Cycling (Sport)</p></li>
<li><p>Cycling (Transport)</p></li> <li><p>Cycling (Transport)</p></li>
<li><p>Hiking</p></li> <li><p>Hiking</p></li>
@ -191,10 +191,10 @@
</dd> </dd>
</dl> </dl>
</li> </li>
<li><p>Dashboard with month calendar displaying activities and record. The week can start on Sunday or Monday (which can be changed in the user settings)</p></li> <li><p>Dashboard with month calendar displaying workouts and record. The week can start on Sunday or Monday (which can be changed in the user settings)</p></li>
<li><p>Activity creation by uploading a gpx file. An activity can even be created without gpx (the user must enter date, time, duration and distance)</p></li> <li><p>Workout creation by uploading a gpx file. A workout can even be created without gpx (the user must enter date, time, duration and distance)</p></li>
<li><p>An activity with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed</p></li> <li><p>A workout with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed</p></li>
<li><p>Activity edition and deletion. User can add a note</p></li> <li><p>Workout edition and deletion. User can add a note</p></li>
<li><p>User statistics</p></li> <li><p>User statistics</p></li>
<li><dl class="simple"> <li><dl class="simple">
<dt>User records by sports:</dt><dd><ul> <dt>User records by sports:</dt><dd><ul>
@ -206,11 +206,11 @@
</dd> </dd>
</dl> </dl>
</li> </li>
<li><p>Activities list and filter</p></li> <li><p>Workours list and filter</p></li>
</ul> </ul>
<div class="admonition note"> <div class="admonition note">
<p class="admonition-title">Note</p> <p class="admonition-title">Note</p>
<p>for now, only the owner of the activity can see the activity.</p> <p>for now, only the owner of the workout can see it.</p>
</div> </div>
</div> </div>
<div class="section" id="translations"> <div class="section" id="translations">
@ -224,16 +224,16 @@
<img alt="FitTrackee Dashboard" src="_images/fittrackee_screenshot-01.png" /> <img alt="FitTrackee Dashboard" src="_images/fittrackee_screenshot-01.png" />
</div> </div>
</div> </div>
<div class="section" id="activity-workout-detail"> <div class="section" id="workout-detail">
<h2>Activity/workout detail<a class="headerlink" href="#activity-workout-detail" title="Permalink to this headline"></a></h2> <h2>Workout detail<a class="headerlink" href="#workout-detail" title="Permalink to this headline"></a></h2>
<div class="figure align-default"> <div class="figure align-default">
<img alt="FitTrackee Activity" src="_images/fittrackee_screenshot-02.png" /> <img alt="FitTrackee Workout" src="_images/fittrackee_screenshot-02.png" />
</div> </div>
</div> </div>
<div class="section" id="activities-workouts-list"> <div class="section" id="workouts-list">
<h2>Activities/workouts list<a class="headerlink" href="#activities-workouts-list" title="Permalink to this headline"></a></h2> <h2>Workouts list<a class="headerlink" href="#workouts-list" title="Permalink to this headline"></a></h2>
<div class="figure align-default"> <div class="figure align-default">
<img alt="FitTrackee Activities" src="_images/fittrackee_screenshot-03.png" /> <img alt="FitTrackee Workouts" src="_images/fittrackee_screenshot-03.png" />
</div> </div>
</div> </div>
<div class="section" id="statistics"> <div class="section" id="statistics">

View File

@ -125,46 +125,6 @@
<tr class="pcap"><td></td><td>&#160;</td><td></td></tr> <tr class="pcap"><td></td><td>&#160;</td><td></td></tr>
<tr class="cap" id="cap-/api"><td></td><td> <tr class="cap" id="cap-/api"><td></td><td>
<strong>/api</strong></td><td></td></tr> <strong>/api</strong></td><td></td></tr>
<tr>
<td></td>
<td>
<a href="api/activities.html#get--api-activities"><code class="xref">GET /api/activities</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/activities.html#get--api-activities-(string-activity_short_id)"><code class="xref">GET /api/activities/(string:activity_short_id)</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/activities.html#get--api-activities-(string-activity_short_id)-chart_data"><code class="xref">GET /api/activities/(string:activity_short_id)/chart_data</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/activities.html#get--api-activities-(string-activity_short_id)-chart_data-segment-(int-segment_id)"><code class="xref">GET /api/activities/(string:activity_short_id)/chart_data/segment/(int:segment_id)</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/activities.html#get--api-activities-(string-activity_short_id)-gpx"><code class="xref">GET /api/activities/(string:activity_short_id)/gpx</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/activities.html#get--api-activities-(string-activity_short_id)-gpx-segment-(int-segment_id)"><code class="xref">GET /api/activities/(string:activity_short_id)/gpx/segment/(int:segment_id)</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/activities.html#get--api-activities-map-(map_id)"><code class="xref">GET /api/activities/map/(map_id)</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/activities.html#get--api-activities-map_tile-(s)-(z)-(x)-(y).png"><code class="xref">GET /api/activities/map_tile/(s)/(z)/(x)/(y).png</code></a></td><td>
<em></em></td></tr>
<tr> <tr>
<td></td> <td></td>
<td> <td>
@ -233,12 +193,42 @@
<tr> <tr>
<td></td> <td></td>
<td> <td>
<a href="api/activities.html#post--api-activities"><code class="xref">POST /api/activities</code></a></td><td> <a href="api/workouts.html#get--api-workouts"><code class="xref">GET /api/workouts</code></a></td><td>
<em></em></td></tr> <em></em></td></tr>
<tr> <tr>
<td></td> <td></td>
<td> <td>
<a href="api/activities.html#post--api-activities-no_gpx"><code class="xref">POST /api/activities/no_gpx</code></a></td><td> <a href="api/workouts.html#get--api-workouts-(string-workout_short_id)"><code class="xref">GET /api/workouts/(string:workout_short_id)</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/workouts.html#get--api-workouts-(string-workout_short_id)-chart_data"><code class="xref">GET /api/workouts/(string:workout_short_id)/chart_data</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/workouts.html#get--api-workouts-(string-workout_short_id)-chart_data-segment-(int-segment_id)"><code class="xref">GET /api/workouts/(string:workout_short_id)/chart_data/segment/(int:segment_id)</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/workouts.html#get--api-workouts-(string-workout_short_id)-gpx"><code class="xref">GET /api/workouts/(string:workout_short_id)/gpx</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/workouts.html#get--api-workouts-(string-workout_short_id)-gpx-segment-(int-segment_id)"><code class="xref">GET /api/workouts/(string:workout_short_id)/gpx/segment/(int:segment_id)</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/workouts.html#get--api-workouts-map-(map_id)"><code class="xref">GET /api/workouts/map/(map_id)</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/workouts.html#get--api-workouts-map_tile-(s)-(z)-(x)-(y).png"><code class="xref">GET /api/workouts/map_tile/(s)/(z)/(x)/(y).png</code></a></td><td>
<em></em></td></tr> <em></em></td></tr>
<tr> <tr>
<td></td> <td></td>
@ -273,7 +263,12 @@
<tr> <tr>
<td></td> <td></td>
<td> <td>
<a href="api/activities.html#delete--api-activities-(string-activity_short_id)"><code class="xref">DELETE /api/activities/(string:activity_short_id)</code></a></td><td> <a href="api/workouts.html#post--api-workouts"><code class="xref">POST /api/workouts</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/workouts.html#post--api-workouts-no_gpx"><code class="xref">POST /api/workouts/no_gpx</code></a></td><td>
<em></em></td></tr> <em></em></td></tr>
<tr> <tr>
<td></td> <td></td>
@ -288,7 +283,7 @@
<tr> <tr>
<td></td> <td></td>
<td> <td>
<a href="api/activities.html#patch--api-activities-(string-activity_short_id)"><code class="xref">PATCH /api/activities/(string:activity_short_id)</code></a></td><td> <a href="api/workouts.html#delete--api-workouts-(string-workout_short_id)"><code class="xref">DELETE /api/workouts/(string:workout_short_id)</code></a></td><td>
<em></em></td></tr> <em></em></td></tr>
<tr> <tr>
<td></td> <td></td>
@ -305,6 +300,11 @@
<td> <td>
<a href="api/users.html#patch--api-users-(user_name)"><code class="xref">PATCH /api/users/(user_name)</code></a></td><td> <a href="api/users.html#patch--api-users-(user_name)"><code class="xref">PATCH /api/users/(user_name)</code></a></td><td>
<em></em></td></tr> <em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/workouts.html#patch--api-workouts-(string-workout_short_id)"><code class="xref">PATCH /api/workouts/(string:workout_short_id)</code></a></td><td>
<em></em></td></tr>
</table> </table>

View File

@ -123,8 +123,8 @@
<div class="section" id="fittrackee"> <div class="section" id="fittrackee">
<h1>FitTrackee<a class="headerlink" href="#fittrackee" title="Permalink to this headline"></a></h1> <h1>FitTrackee<a class="headerlink" href="#fittrackee" title="Permalink to this headline"></a></h1>
<div class="line-block"> <div class="line-block">
<div class="line">This web application allows you to track your outdoor activities from <div class="line">This web application allows you to track your outdoor activities (workouts)
gpx files and keep your data on your own server.</div> from gpx files and keep your data on your own server.</div>
<div class="line">No mobile app is developed yet, but several existing mobile apps can <div class="line">No mobile app is developed yet, but several existing mobile apps can
store workouts data locally and export them into a gpx file.</div> store workouts data locally and export them into a gpx file.</div>
<div class="line">Examples (for Android):</div> <div class="line">Examples (for Android):</div>
@ -164,20 +164,20 @@ Map</a>.</div>
<li class="toctree-l1"><a class="reference internal" href="features.html">Features</a><ul> <li class="toctree-l1"><a class="reference internal" href="features.html">Features</a><ul>
<li class="toctree-l2"><a class="reference internal" href="features.html#list">List</a></li> <li class="toctree-l2"><a class="reference internal" href="features.html#list">List</a></li>
<li class="toctree-l2"><a class="reference internal" href="features.html#dashboard">Dashboard</a></li> <li class="toctree-l2"><a class="reference internal" href="features.html#dashboard">Dashboard</a></li>
<li class="toctree-l2"><a class="reference internal" href="features.html#activity-workout-detail">Activity/workout detail</a></li> <li class="toctree-l2"><a class="reference internal" href="features.html#workout-detail">Workout detail</a></li>
<li class="toctree-l2"><a class="reference internal" href="features.html#activities-workouts-list">Activities/workouts list</a></li> <li class="toctree-l2"><a class="reference internal" href="features.html#workouts-list">Workouts list</a></li>
<li class="toctree-l2"><a class="reference internal" href="features.html#statistics">Statistics</a></li> <li class="toctree-l2"><a class="reference internal" href="features.html#statistics">Statistics</a></li>
<li class="toctree-l2"><a class="reference internal" href="features.html#id1">Administration</a></li> <li class="toctree-l2"><a class="reference internal" href="features.html#id1">Administration</a></li>
</ul> </ul>
</li> </li>
<li class="toctree-l1"><a class="reference internal" href="api/index.html">API documentation</a><ul> <li class="toctree-l1"><a class="reference internal" href="api/index.html">API documentation</a><ul>
<li class="toctree-l2"><a class="reference internal" href="api/activities.html">Activities</a></li>
<li class="toctree-l2"><a class="reference internal" href="api/auth.html">Authentication</a></li> <li class="toctree-l2"><a class="reference internal" href="api/auth.html">Authentication</a></li>
<li class="toctree-l2"><a class="reference internal" href="api/configuration.html">Configuration</a></li> <li class="toctree-l2"><a class="reference internal" href="api/configuration.html">Configuration</a></li>
<li class="toctree-l2"><a class="reference internal" href="api/records.html">Records</a></li> <li class="toctree-l2"><a class="reference internal" href="api/records.html">Records</a></li>
<li class="toctree-l2"><a class="reference internal" href="api/sports.html">Sports</a></li> <li class="toctree-l2"><a class="reference internal" href="api/sports.html">Sports</a></li>
<li class="toctree-l2"><a class="reference internal" href="api/stats.html">Statistics</a></li> <li class="toctree-l2"><a class="reference internal" href="api/stats.html">Statistics</a></li>
<li class="toctree-l2"><a class="reference internal" href="api/users.html">Users</a></li> <li class="toctree-l2"><a class="reference internal" href="api/users.html">Users</a></li>
<li class="toctree-l2"><a class="reference internal" href="api/workouts.html">Workouts</a></li>
</ul> </ul>
</li> </li>
<li class="toctree-l1"><a class="reference internal" href="troubleshooting/index.html">Troubleshooting</a><ul> <li class="toctree-l1"><a class="reference internal" href="troubleshooting/index.html">Troubleshooting</a><ul>

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,7 @@
<link rel="index" title="Index" href="../genindex.html" /> <link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" /> <link rel="search" title="Search" href="../search.html" />
<link rel="next" title="Administrator" href="administrator.html" /> <link rel="next" title="Administrator" href="administrator.html" />
<link rel="prev" title="Users" href="../api/users.html" /> <link rel="prev" title="Workouts" href="../api/workouts.html" />
<meta charset='utf-8'> <meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'> <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1'> <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1'>
@ -86,7 +86,7 @@
<li> <li>
<a href="../api/users.html" title="Previous Chapter: Users"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">&laquo; Users</span> <a href="../api/workouts.html" title="Previous Chapter: Workouts"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">&laquo; Workouts</span>
</a> </a>
</li> </li>
<li> <li>

View File

@ -1,17 +0,0 @@
Activities
##########
.. autoflask:: fittrackee:create_app()
:endpoints:
activities.get_activities,
activities.get_activity,
activities.get_activity_gpx,
activities.get_activity_chart_data,
activities.get_segment_chart_data,
activities.get_segment_gpx,
activities.get_map,
activities.get_map_tile,
activities.post_activity,
activities.post_activity_no_gpx,
activities.update_activity,
activities.delete_activity

View File

@ -5,10 +5,10 @@ API documentation
:maxdepth: 2 :maxdepth: 2
:caption: Endpoints: :caption: Endpoints:
activities
auth auth
configuration configuration
records records
sports sports
stats stats
users users
workouts

View File

@ -3,6 +3,6 @@ Statistics
.. autoflask:: fittrackee:create_app() .. autoflask:: fittrackee:create_app()
:endpoints: :endpoints:
stats.get_activities_by_time, stats.get_workouts_by_time,
stats.get_activities_by_sport, stats.get_workouts_by_sport,
stats.get_application_stats stats.get_application_stats

View File

@ -0,0 +1,17 @@
Workouts
##########
.. autoflask:: fittrackee:create_app()
:endpoints:
workouts.get_workouts,
workouts.get_workout,
workouts.get_workout_gpx,
workouts.get_workout_chart_data,
workouts.get_segment_chart_data,
workouts.get_segment_gpx,
workouts.get_map,
workouts.get_map_tile,
workouts.post_workout,
workouts.post_workout_no_gpx,
workouts.update_workout,
workouts.delete_workout

View File

@ -25,7 +25,7 @@ Administration
- **Sports** - **Sports**
- enable or disable a sport (a sport can be disabled even if activity with this sport exists) - enable or disable a sport (a sport can be disabled even if workout with this sport exists)
Account Account
^^^^^^^ ^^^^^^^
@ -33,29 +33,29 @@ Account
- A user can reset his password (*new in 0.3.0*) - A user can reset his password (*new in 0.3.0*)
Activities/Workouts Workouts
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^
- 6 sports supported: - 6 sports are supported:
- Cycling (Sport) - Cycling (Sport)
- Cycling (Transport) - Cycling (Transport)
- Hiking - Hiking
- Montain Biking - Montain Biking
- Running - Running
- Walking - Walking
- Dashboard with month calendar displaying activities and record. The week can start on Sunday or Monday (which can be changed in the user settings) - Dashboard with month calendar displaying workouts and record. The week can start on Sunday or Monday (which can be changed in the user settings)
- Activity creation by uploading a gpx file. An activity can even be created without gpx (the user must enter date, time, duration and distance) - Workout creation by uploading a gpx file. A workout can even be created without gpx (the user must enter date, time, duration and distance)
- An activity with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed - A workout with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed
- Activity edition and deletion. User can add a note - Workout edition and deletion. User can add a note
- User statistics - User statistics
- User records by sports: - User records by sports:
- average speed - average speed
- farest distance - farest distance
- longest duration - longest duration
- maximum speed - maximum speed
- Activities list and filter - Workours list and filter
.. note:: .. note::
for now, only the owner of the activity can see the activity. for now, only the owner of the workout can see it.
Translations Translations
^^^^^^^^^^^^ ^^^^^^^^^^^^
@ -69,16 +69,16 @@ Dashboard
:alt: FitTrackee Dashboard :alt: FitTrackee Dashboard
Activity/workout detail Workout detail
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
.. figure:: _images/fittrackee_screenshot-02.png .. figure:: _images/fittrackee_screenshot-02.png
:alt: FitTrackee Activity :alt: FitTrackee Workout
Activities/workouts list Workouts list
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~
.. figure:: _images/fittrackee_screenshot-03.png .. figure:: _images/fittrackee_screenshot-03.png
:alt: FitTrackee Activities :alt: FitTrackee Workouts
Statistics Statistics

View File

@ -5,8 +5,8 @@
FitTrackee FitTrackee
========== ==========
| This web application allows you to track your outdoor activities from | This web application allows you to track your outdoor activities (workouts)
gpx files and keep your data on your own server. from gpx files and keep your data on your own server.
| No mobile app is developed yet, but several existing mobile apps can | No mobile app is developed yet, but several existing mobile apps can
store workouts data locally and export them into a gpx file. store workouts data locally and export them into a gpx file.
| Examples (for Android): | Examples (for Android):

View File

@ -62,21 +62,21 @@ def create_app() -> Flask:
_, db_app_config = init_config() _, db_app_config = init_config()
update_app_config_from_database(app, db_app_config) update_app_config_from_database(app, db_app_config)
from .activities.activities import activities_blueprint # noqa
from .activities.records import records_blueprint # noqa
from .activities.sports import sports_blueprint # noqa
from .activities.stats import stats_blueprint # noqa
from .application.app_config import config_blueprint # noqa from .application.app_config import config_blueprint # noqa
from .users.auth import auth_blueprint # noqa from .users.auth import auth_blueprint # noqa
from .users.users import users_blueprint # noqa from .users.users import users_blueprint # noqa
from .workouts.records import records_blueprint # noqa
from .workouts.sports import sports_blueprint # noqa
from .workouts.stats import stats_blueprint # noqa
from .workouts.workouts import workouts_blueprint # noqa
app.register_blueprint(users_blueprint, url_prefix='/api')
app.register_blueprint(auth_blueprint, url_prefix='/api') app.register_blueprint(auth_blueprint, url_prefix='/api')
app.register_blueprint(activities_blueprint, url_prefix='/api') app.register_blueprint(config_blueprint, url_prefix='/api')
app.register_blueprint(records_blueprint, url_prefix='/api') app.register_blueprint(records_blueprint, url_prefix='/api')
app.register_blueprint(sports_blueprint, url_prefix='/api') app.register_blueprint(sports_blueprint, url_prefix='/api')
app.register_blueprint(stats_blueprint, url_prefix='/api') app.register_blueprint(stats_blueprint, url_prefix='/api')
app.register_blueprint(config_blueprint, url_prefix='/api') app.register_blueprint(users_blueprint, url_prefix='/api')
app.register_blueprint(workouts_blueprint, url_prefix='/api')
if app.debug: if app.debug:
logging.getLogger('sqlalchemy').setLevel(logging.WARNING) logging.getLogger('sqlalchemy').setLevel(logging.WARNING)

View File

@ -6,10 +6,10 @@ from typing import Dict, Optional
import gunicorn.app.base import gunicorn.app.base
from fittrackee import create_app, db from fittrackee import create_app, db
from fittrackee.activities.models import Activity
from fittrackee.activities.utils import update_activity
from fittrackee.application.utils import init_config from fittrackee.application.utils import init_config
from fittrackee.database_utils import init_database from fittrackee.database_utils import init_database
from fittrackee.workouts.models import Workout
from fittrackee.workouts.utils import update_workout
from flask import Flask from flask import Flask
from flask_dramatiq import worker from flask_dramatiq import worker
from flask_migrate import upgrade from flask_migrate import upgrade
@ -68,19 +68,19 @@ def init_data() -> None:
@app.cli.command() @app.cli.command()
def recalculate() -> None: def recalculate() -> None:
print("Starting activities data refresh") print("Starting workouts data refresh")
activities = ( workouts = (
Activity.query.filter(Activity.gpx != None) # noqa Workout.query.filter(Workout.gpx != None) # noqa
.order_by(Activity.activity_date.asc()) # noqa .order_by(Workout.workout_date.asc()) # noqa
.all() .all()
) )
if len(activities) == 0: if len(workouts) == 0:
print('➡️ no activities to upgrade.') print('➡️ no workouts to upgrade.')
return None return None
pbar = tqdm(activities) pbar = tqdm(workouts)
for activity in pbar: for workout in pbar:
update_activity(activity) update_workout(workout)
pbar.set_postfix(activitiy_id=activity.id) pbar.set_postfix(activitiy_id=workout.id)
db.session.commit() db.session.commit()

View File

@ -1,419 +0,0 @@
import hashlib
import os
import tempfile
import zipfile
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple, Union
from uuid import UUID
import gpxpy.gpx
import pytz
from fittrackee import appLog, db
from flask import current_app
from sqlalchemy import exc
from staticmap import Line, StaticMap
from werkzeug.datastructures import FileStorage
from werkzeug.utils import secure_filename
from ..users.models import User
from .models import Activity, ActivitySegment, Sport
from .utils_files import get_absolute_file_path
from .utils_gpx import get_gpx_info
class ActivityException(Exception):
def __init__(
self, status: str, message: str, e: Optional[Exception] = None
) -> None:
self.status = status
self.message = message
self.e = e
def get_datetime_with_tz(
timezone: str, activity_date: datetime, gpx_data: Optional[Dict] = None
) -> Tuple[Optional[datetime], datetime]:
"""
Return naive datetime and datetime with user timezone
"""
activity_date_tz = None
if timezone:
user_tz = pytz.timezone(timezone)
utc_tz = pytz.utc
if gpx_data:
# activity date in gpx is in UTC, but in naive datetime
fmt = '%Y-%m-%d %H:%M:%S'
activity_date_string = activity_date.strftime(fmt)
activity_date_tmp = utc_tz.localize(
datetime.strptime(activity_date_string, fmt)
)
activity_date_tz = activity_date_tmp.astimezone(user_tz)
else:
activity_date_tz = user_tz.localize(activity_date)
activity_date = activity_date_tz.astimezone(utc_tz)
# make datetime 'naive' like in gpx file
activity_date = activity_date.replace(tzinfo=None)
return activity_date_tz, activity_date
def update_activity_data(
activity: Union[Activity, ActivitySegment], gpx_data: Dict
) -> Union[Activity, ActivitySegment]:
"""
Update activity or activity segment with data from gpx file
"""
activity.pauses = gpx_data['stop_time']
activity.moving = gpx_data['moving_time']
activity.min_alt = gpx_data['elevation_min']
activity.max_alt = gpx_data['elevation_max']
activity.descent = gpx_data['downhill']
activity.ascent = gpx_data['uphill']
activity.max_speed = gpx_data['max_speed']
activity.ave_speed = gpx_data['average_speed']
return activity
def create_activity(
user: User, activity_data: Dict, gpx_data: Optional[Dict] = None
) -> Activity:
"""
Create Activity from data entered by user and from gpx if a gpx file is
provided
"""
activity_date = (
gpx_data['start']
if gpx_data
else datetime.strptime(
activity_data['activity_date'], '%Y-%m-%d %H:%M'
)
)
activity_date_tz, activity_date = get_datetime_with_tz(
user.timezone, activity_date, gpx_data
)
duration = (
gpx_data['duration']
if gpx_data
else timedelta(seconds=activity_data['duration'])
)
distance = gpx_data['distance'] if gpx_data else activity_data['distance']
title = gpx_data['name'] if gpx_data else activity_data.get('title', '')
new_activity = Activity(
user_id=user.id,
sport_id=activity_data['sport_id'],
activity_date=activity_date,
distance=distance,
duration=duration,
)
new_activity.notes = activity_data.get('notes')
if title is not None and title != '':
new_activity.title = title
else:
sport = Sport.query.filter_by(id=new_activity.sport_id).first()
fmt = "%Y-%m-%d %H:%M:%S"
activity_datetime = (
activity_date_tz.strftime(fmt)
if activity_date_tz
else new_activity.activity_date.strftime(fmt)
)
new_activity.title = f'{sport.label} - {activity_datetime}'
if gpx_data:
new_activity.gpx = gpx_data['filename']
new_activity.bounds = gpx_data['bounds']
update_activity_data(new_activity, gpx_data)
else:
new_activity.moving = duration
new_activity.ave_speed = (
None
if duration.seconds == 0
else float(new_activity.distance) / (duration.seconds / 3600)
)
new_activity.max_speed = new_activity.ave_speed
return new_activity
def create_segment(
activity_id: int, activity_uuid: UUID, segment_data: Dict
) -> ActivitySegment:
"""
Create Activity Segment from gpx data
"""
new_segment = ActivitySegment(
activity_id=activity_id,
activity_uuid=activity_uuid,
segment_id=segment_data['idx'],
)
new_segment.duration = segment_data['duration']
new_segment.distance = segment_data['distance']
update_activity_data(new_segment, segment_data)
return new_segment
def update_activity(activity: Activity) -> Activity:
"""
Update activity data from gpx file
"""
gpx_data, _, _ = get_gpx_info(
get_absolute_file_path(activity.gpx), False, False
)
updated_activity = update_activity_data(activity, gpx_data)
updated_activity.duration = gpx_data['duration']
updated_activity.distance = gpx_data['distance']
db.session.flush()
for segment_idx, segment in enumerate(updated_activity.segments):
segment_data = gpx_data['segments'][segment_idx]
updated_segment = update_activity_data(segment, segment_data)
updated_segment.duration = segment_data['duration']
updated_segment.distance = segment_data['distance']
db.session.flush()
return updated_activity
def edit_activity(
activity: Activity, activity_data: Dict, auth_user_id: int
) -> Activity:
"""
Edit an activity
Note: the gpx file is NOT modified
In a next version, map_data and weather_data will be updated
(case of a modified gpx file, see issue #7)
"""
user = User.query.filter_by(id=auth_user_id).first()
if activity_data.get('refresh'):
activity = update_activity(activity)
if activity_data.get('sport_id'):
activity.sport_id = activity_data.get('sport_id')
if activity_data.get('title'):
activity.title = activity_data.get('title')
if activity_data.get('notes'):
activity.notes = activity_data.get('notes')
if not activity.gpx:
if activity_data.get('activity_date'):
activity_date = datetime.strptime(
activity_data['activity_date'], '%Y-%m-%d %H:%M'
)
_, activity.activity_date = get_datetime_with_tz(
user.timezone, activity_date
)
if activity_data.get('duration'):
activity.duration = timedelta(seconds=activity_data['duration'])
activity.moving = activity.duration
if activity_data.get('distance'):
activity.distance = activity_data['distance']
activity.ave_speed = (
None
if activity.duration.seconds == 0
else float(activity.distance) / (activity.duration.seconds / 3600)
)
activity.max_speed = activity.ave_speed
return activity
def get_file_path(dir_path: str, filename: str) -> str:
"""
Get full path for a file
"""
if not os.path.exists(dir_path):
os.makedirs(dir_path)
file_path = os.path.join(dir_path, filename)
return file_path
def get_new_file_path(
auth_user_id: int,
activity_date: str,
sport: str,
old_filename: Optional[str] = None,
extension: Optional[str] = None,
) -> str:
"""
Generate a file path from user and activity data
"""
if not extension and old_filename:
extension = f".{old_filename.rsplit('.', 1)[1].lower()}"
_, new_filename = tempfile.mkstemp(
prefix=f'{activity_date}_{sport}_', suffix=extension
)
dir_path = os.path.join('activities', 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])
return file_path
def generate_map(map_filepath: str, map_data: List) -> None:
"""
Generate and save map image from map data
"""
m = StaticMap(400, 225, 10)
line = Line(map_data, '#3388FF', 4)
m.add_line(line)
image = m.render()
image.save(map_filepath)
def get_map_hash(map_filepath: str) -> str:
"""
Generate a md5 hash used as id instead of activity id, to retrieve map
image (maps are sensitive data)
"""
md5 = hashlib.md5()
absolute_map_filepath = get_absolute_file_path(map_filepath)
with open(absolute_map_filepath, 'rb') as f:
for chunk in iter(lambda: f.read(128 * md5.block_size), b''):
md5.update(chunk)
return md5.hexdigest()
def process_one_gpx_file(params: Dict, filename: str) -> Activity:
"""
Get all data from a gpx file to create an activity with map image
"""
try:
gpx_data, map_data, weather_data = get_gpx_info(params['file_path'])
auth_user_id = params['user'].id
new_filepath = get_new_file_path(
auth_user_id=auth_user_id,
activity_date=gpx_data['start'],
old_filename=filename,
sport=params['sport_label'],
)
absolute_gpx_filepath = get_absolute_file_path(new_filepath)
os.rename(params['file_path'], absolute_gpx_filepath)
gpx_data['filename'] = new_filepath
map_filepath = get_new_file_path(
auth_user_id=auth_user_id,
activity_date=gpx_data['start'],
extension='.png',
sport=params['sport_label'],
)
absolute_map_filepath = get_absolute_file_path(map_filepath)
generate_map(absolute_map_filepath, map_data)
except (gpxpy.gpx.GPXXMLSyntaxException, TypeError) as e:
raise ActivityException('error', 'Error during gpx file parsing.', e)
except Exception as e:
raise ActivityException('error', 'Error during gpx processing.', e)
try:
new_activity = create_activity(
params['user'], params['activity_data'], gpx_data
)
new_activity.map = map_filepath
new_activity.map_id = get_map_hash(map_filepath)
new_activity.weather_start = weather_data[0]
new_activity.weather_end = weather_data[1]
db.session.add(new_activity)
db.session.flush()
for segment_data in gpx_data['segments']:
new_segment = create_segment(
new_activity.id, new_activity.uuid, segment_data
)
db.session.add(new_segment)
db.session.commit()
return new_activity
except (exc.IntegrityError, ValueError) as e:
raise ActivityException('fail', 'Error during activity save.', e)
def process_zip_archive(common_params: Dict, extract_dir: str) -> List:
"""
Get files from a zip archive and create activities, if number of files
does not exceed defined limit.
"""
with zipfile.ZipFile(common_params['file_path'], "r") as zip_ref:
zip_ref.extractall(extract_dir)
new_activities = []
gpx_files_limit = os.getenv('REACT_APP_GPX_LIMIT_IMPORT', 10)
if (
gpx_files_limit
and isinstance(gpx_files_limit, str)
and gpx_files_limit.isdigit()
):
gpx_files_limit = int(gpx_files_limit)
else:
gpx_files_limit = 10
appLog.warning('GPX limit not configured, set to 10.')
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['ACTIVITY_ALLOWED_EXTENSIONS']
):
gpx_files_ok += 1
if gpx_files_ok > gpx_files_limit:
break
file_path = os.path.join(extract_dir, gpx_file)
params = common_params
params['file_path'] = file_path
new_activity = process_one_gpx_file(params, gpx_file)
new_activities.append(new_activity)
return new_activities
def process_files(
auth_user_id: int,
activity_data: Dict,
activity_file: FileStorage,
folders: Dict,
) -> List:
"""
Store gpx file or zip archive and create activities
"""
if activity_file.filename is None:
raise ActivityException('error', 'File has no filename.')
filename = secure_filename(activity_file.filename)
extension = f".{filename.rsplit('.', 1)[1].lower()}"
file_path = get_file_path(folders['tmp_dir'], filename)
sport = Sport.query.filter_by(id=activity_data.get('sport_id')).first()
if not sport:
raise ActivityException(
'error',
f"Sport id: {activity_data.get('sport_id')} does not exist",
)
user = User.query.filter_by(id=auth_user_id).first()
common_params = {
'user': user,
'activity_data': activity_data,
'file_path': file_path,
'sport_label': sport.label,
}
try:
activity_file.save(file_path)
except Exception as e:
raise ActivityException('error', 'Error during activity file save.', e)
if extension == ".gpx":
return [process_one_gpx_file(common_params, filename)]
else:
return process_zip_archive(common_params, folders['extract_dir'])
def get_upload_dir_size() -> int:
"""
Return upload directory size
"""
upload_path = get_absolute_file_path('')
total_size = 0
for dir_path, _, filenames in os.walk(upload_path):
for f in filenames:
fp = os.path.join(dir_path, f)
total_size += os.path.getsize(fp)
return total_size

View File

@ -25,7 +25,7 @@ class BaseConfig:
os.getenv('UPLOAD_FOLDER', current_app.root_path), 'uploads' os.getenv('UPLOAD_FOLDER', current_app.root_path), 'uploads'
) )
PICTURE_ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'} PICTURE_ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'}
ACTIVITY_ALLOWED_EXTENSIONS = {'gpx', 'zip'} WORKOUT_ALLOWED_EXTENSIONS = {'gpx', 'zip'}
TEMPLATES_FOLDER = os.path.join(current_app.root_path, 'email/templates') TEMPLATES_FOLDER = os.path.join(current_app.root_path, 'email/templates')
UI_URL = os.environ.get('UI_URL') UI_URL = os.environ.get('UI_URL')
EMAIL_URL = os.environ.get('EMAIL_URL') EMAIL_URL = os.environ.get('EMAIL_URL')

View File

@ -1,10 +1,10 @@
from fittrackee import db from fittrackee import db
from fittrackee.activities.models import Sport
from fittrackee.application.utils import ( from fittrackee.application.utils import (
init_config, init_config,
update_app_config_from_database, update_app_config_from_database,
) )
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport
from flask import Flask from flask import Flask

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,244 @@
"""rename 'activity' with 'workout'
Revision ID: 4e8597c50064
Revises: 3243cd25eca7
Create Date: 2021-01-09 19:41:26.589237
"""
import os
import sqlalchemy as sa
from alembic import op
from flask import current_app
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '4e8597c50064'
down_revision = '3243cd25eca7'
branch_labels = None
depends_on = None
def rename_upload_folder(src, dst):
upload_folder = current_app.config['UPLOAD_FOLDER']
src_directory = f'{upload_folder}/{src}'
if os.path.exists(src_directory):
try:
os.rename(src_directory, f'{upload_folder}/{dst}')
except Exception as e:
print(
f'ERROR: can not rename upload folder \'{src}\' to \'{dst}\':'
f'\n {e}.'
f'\n Please rename it manually.')
def upgrade():
op.create_table('workouts',
sa.Column('id', sa.Integer(), server_default=sa.text("nextval('workouts_id_seq'::regclass)"), autoincrement=True, nullable=False),
sa.Column('uuid', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('sport_id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=255), nullable=True),
sa.Column('gpx', sa.String(length=255), nullable=True),
sa.Column('creation_date', sa.DateTime(), nullable=True),
sa.Column('modification_date', sa.DateTime(), nullable=True),
sa.Column('workout_date', sa.DateTime(), nullable=False),
sa.Column('duration', sa.Interval(), nullable=False),
sa.Column('pauses', sa.Interval(), nullable=True),
sa.Column('moving', sa.Interval(), nullable=True),
sa.Column('distance', sa.Numeric(precision=6, scale=3), nullable=True),
sa.Column('min_alt', sa.Numeric(precision=6, scale=2), nullable=True),
sa.Column('max_alt', sa.Numeric(precision=6, scale=2), nullable=True),
sa.Column('descent', sa.Numeric(precision=7, scale=2), nullable=True),
sa.Column('ascent', sa.Numeric(precision=7, scale=2), nullable=True),
sa.Column('max_speed', sa.Numeric(precision=6, scale=2), nullable=True),
sa.Column('ave_speed', sa.Numeric(precision=6, scale=2), nullable=True),
sa.Column('bounds', postgresql.ARRAY(sa.Float()), nullable=True),
sa.Column('map', sa.String(length=255), nullable=True),
sa.Column('map_id', sa.String(length=50), nullable=True),
sa.Column('weather_start', sa.JSON(), nullable=True),
sa.Column('weather_end', sa.JSON(), nullable=True),
sa.Column('notes', sa.String(length=500), nullable=True),
sa.ForeignKeyConstraint(['sport_id'], ['sports.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('uuid')
)
op.execute(
"SELECT setval('workouts_id_seq', (SELECT max(id) FROM activities));"
)
op.create_table('workout_segments',
sa.Column('workout_id', sa.Integer(), nullable=False),
sa.Column('workout_uuid', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('segment_id', sa.Integer(), nullable=False),
sa.Column('duration', sa.Interval(), nullable=False),
sa.Column('pauses', sa.Interval(), nullable=True),
sa.Column('moving', sa.Interval(), nullable=True),
sa.Column('distance', sa.Numeric(precision=6, scale=3), nullable=True),
sa.Column('min_alt', sa.Numeric(precision=6, scale=2), nullable=True),
sa.Column('max_alt', sa.Numeric(precision=6, scale=2), nullable=True),
sa.Column('descent', sa.Numeric(precision=7, scale=2), nullable=True),
sa.Column('ascent', sa.Numeric(precision=7, scale=2), nullable=True),
sa.Column('max_speed', sa.Numeric(precision=6, scale=2), nullable=True),
sa.Column('ave_speed', sa.Numeric(precision=6, scale=2), nullable=True),
sa.ForeignKeyConstraint(['workout_id'], ['workouts.id'], ),
sa.PrimaryKeyConstraint('workout_id', 'segment_id')
)
op.add_column('records', sa.Column('workout_date', sa.DateTime(), nullable=True))
op.add_column('records', sa.Column('workout_id', sa.Integer(), nullable=True))
op.add_column('records', sa.Column('workout_uuid', postgresql.UUID(as_uuid=True), nullable=True))
op.create_foreign_key('records_workout_id_fkey', 'records', 'workouts', ['workout_id'], ['id'])
op.execute(
"""
INSERT INTO workouts (id, user_id, sport_id, title, gpx,
creation_date, modification_date, workout_date, duration, pauses,
moving, distance, min_alt, max_alt, descent, ascent, max_speed,
ave_speed, bounds, map, map_id, weather_start, weather_end, notes, uuid)
SELECT id, user_id, sport_id, title, REPLACE(gpx, 'activities/', 'workouts/'),
creation_date, modification_date, activity_date, duration, pauses,
moving, distance, min_alt, max_alt, descent, ascent, max_speed,
ave_speed, bounds, REPLACE(map, 'activities/', 'workouts/'), map_id,
weather_start, weather_end, notes, uuid
FROM activities;
"""
)
op.execute(
"""
INSERT INTO workout_segments (workout_id, workout_uuid, segment_id,
duration, pauses, moving, distance, min_alt, max_alt, descent,
ascent, max_speed, ave_speed)
SELECT activity_id, activity_uuid, segment_id,
duration, pauses, moving, distance, min_alt, max_alt, descent,
ascent, max_speed, ave_speed FROM activity_segments;
"""
)
op.execute(
'UPDATE records SET workout_id = activity_id, '
' workout_uuid = activity_uuid, '
' workout_date = activity_date;'
)
op.alter_column('records', 'workout_date', nullable=False)
op.alter_column('records', 'workout_id', nullable=False)
op.alter_column('records', 'workout_uuid', nullable=False)
op.drop_column('records', 'activity_date')
op.drop_column('records', 'activity_id')
op.drop_column('records', 'activity_uuid')
op.drop_table('activity_segments')
op.drop_table('activities')
rename_upload_folder('activities', 'workouts')
def downgrade():
op.create_table('activities',
sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('activities_id_seq'::regclass)"), autoincrement=True, nullable=False),
sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('sport_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('title', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.Column('gpx', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.Column('creation_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.Column('modification_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.Column('activity_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=False),
sa.Column('duration', postgresql.INTERVAL(), autoincrement=False, nullable=False),
sa.Column('pauses', postgresql.INTERVAL(), autoincrement=False, nullable=True),
sa.Column('moving', postgresql.INTERVAL(), autoincrement=False, nullable=True),
sa.Column('distance', sa.NUMERIC(precision=6, scale=3), autoincrement=False, nullable=True),
sa.Column('min_alt', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
sa.Column('max_alt', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
sa.Column('descent', sa.NUMERIC(precision=7, scale=2), autoincrement=False, nullable=True),
sa.Column('ascent', sa.NUMERIC(precision=7, scale=2), autoincrement=False, nullable=True),
sa.Column('max_speed', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
sa.Column('ave_speed', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
sa.Column('bounds', postgresql.ARRAY(postgresql.DOUBLE_PRECISION(precision=53)), autoincrement=False, nullable=True),
sa.Column('map', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.Column('map_id', sa.VARCHAR(length=50), autoincrement=False, nullable=True),
sa.Column('weather_end', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True),
sa.Column('weather_start', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True),
sa.Column('notes', sa.VARCHAR(length=500), autoincrement=False, nullable=True),
sa.Column('uuid', postgresql.UUID(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['sport_id'], ['sports.id'], name='activities_sport_id_fkey'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='activities_user_id_fkey'),
sa.PrimaryKeyConstraint('id', name='activities_pkey'),
sa.UniqueConstraint('uuid', name='activities_uuid_key'),
postgresql_ignore_search_path=False
)
op.execute(
"SELECT setval('activities_id_seq', (SELECT max(id) FROM workouts));"
)
op.create_table('activity_segments',
sa.Column('activity_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('segment_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('duration', postgresql.INTERVAL(), autoincrement=False, nullable=False),
sa.Column('pauses', postgresql.INTERVAL(), autoincrement=False, nullable=True),
sa.Column('moving', postgresql.INTERVAL(), autoincrement=False, nullable=True),
sa.Column('distance', sa.NUMERIC(precision=6, scale=3), autoincrement=False, nullable=True),
sa.Column('min_alt', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
sa.Column('max_alt', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
sa.Column('descent', sa.NUMERIC(precision=7, scale=2), autoincrement=False, nullable=True),
sa.Column('ascent', sa.NUMERIC(precision=7, scale=2), autoincrement=False, nullable=True),
sa.Column('max_speed', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
sa.Column('ave_speed', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
sa.Column('activity_uuid', postgresql.UUID(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['activity_id'], ['activities.id'], name='activity_segments_activity_id_fkey'),
sa.PrimaryKeyConstraint('activity_id', 'segment_id', name='activity_segments_pkey')
)
op.execute(
"""
INSERT INTO activities (id, user_id, sport_id, title, gpx,
creation_date, modification_date, activity_date, duration, pauses,
moving, distance, min_alt, max_alt, descent, ascent, max_speed,
ave_speed, bounds, map, map_id, weather_start, weather_end, notes, uuid)
SELECT id, user_id, sport_id, title, REPLACE(gpx, 'workouts/', 'activities/'),
creation_date, modification_date, workout_date, duration, pauses,
moving, distance, min_alt, max_alt, descent, ascent, max_speed,
ave_speed, bounds, REPLACE(map, 'workouts/', 'activities/'), map_id,
weather_start, weather_end, notes, uuid
FROM workouts;
"""
)
op.execute(
"""
INSERT INTO activity_segments (activity_id, activity_uuid, segment_id,
duration, pauses, moving, distance, min_alt, max_alt, descent,
ascent, max_speed, ave_speed)
SELECT workout_id, workout_uuid, segment_id,
duration, pauses, moving, distance, min_alt, max_alt, descent,
ascent, max_speed, ave_speed FROM workout_segments;
"""
)
op.add_column('records', sa.Column('activity_uuid', postgresql.UUID(), autoincrement=False, nullable=True))
op.add_column('records', sa.Column('activity_id', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('records', sa.Column('activity_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True))
op.create_foreign_key('records_activity_id_fkey', 'records', 'activities', ['activity_id'], ['id'])
op.execute(
'UPDATE records SET activity_id = workout_id, '
' activity_uuid = workout_uuid, '
' activity_date = workout_date;'
)
op.alter_column('records', 'activity_date', nullable=False)
op.alter_column('records', 'activity_id', nullable=False)
op.alter_column('records', 'activity_uuid', nullable=False)
op.drop_column('records', 'workout_uuid')
op.drop_column('records', 'workout_id')
op.drop_column('records', 'workout_date')
op.drop_table('workout_segments')
op.drop_table('workouts')
rename_upload_folder('workouts', 'activities')

View File

@ -1,75 +0,0 @@
from uuid import UUID
from fittrackee.activities.models import Activity, Sport
from fittrackee.activities.utils_id import decode_short_id
from fittrackee.users.models import User
from flask import Flask
class TestActivityModel:
def test_activity_model(
self,
app: Flask,
sport_1_cycling: Sport,
user_1: User,
activity_cycling_user_1: Activity,
) -> None:
activity_cycling_user_1.title = 'Test'
assert 1 == activity_cycling_user_1.id
assert activity_cycling_user_1.uuid is not None
assert 1 == activity_cycling_user_1.user_id
assert 1 == activity_cycling_user_1.sport_id
assert '2018-01-01 00:00:00' == str(
activity_cycling_user_1.activity_date
)
assert 10.0 == float(activity_cycling_user_1.distance)
assert '1:00:00' == str(activity_cycling_user_1.duration)
assert 'Test' == activity_cycling_user_1.title
assert '<Activity \'Cycling\' - 2018-01-01 00:00:00>' == str(
activity_cycling_user_1
)
serialized_activity = activity_cycling_user_1.serialize()
assert isinstance(decode_short_id(serialized_activity['id']), UUID)
assert 'test' == serialized_activity['user']
assert 1 == serialized_activity['sport_id']
assert serialized_activity['title'] == 'Test'
assert 'creation_date' in serialized_activity
assert serialized_activity['modification_date'] is not None
assert (
str(serialized_activity['activity_date']) == '2018-01-01 00:00:00'
)
assert serialized_activity['duration'] == '1:00:00'
assert serialized_activity['pauses'] is None
assert serialized_activity['moving'] == '1:00:00'
assert serialized_activity['distance'] == 10.0
assert serialized_activity['max_alt'] is None
assert serialized_activity['descent'] is None
assert serialized_activity['ascent'] is None
assert serialized_activity['max_speed'] == 10.0
assert serialized_activity['ave_speed'] == 10.0
assert serialized_activity['with_gpx'] is False
assert serialized_activity['bounds'] == []
assert serialized_activity['previous_activity'] is None
assert serialized_activity['next_activity'] is None
assert serialized_activity['segments'] == []
assert serialized_activity['records'] != []
assert serialized_activity['map'] is None
assert serialized_activity['weather_start'] is None
assert serialized_activity['weather_end'] is None
assert serialized_activity['notes'] is None
def test_activity_segment_model(
self,
app: Flask,
sport_1_cycling: Sport,
user_1: User,
activity_cycling_user_1: Activity,
activity_cycling_user_1_segment: Activity,
) -> None:
assert (
f'<Segment \'{activity_cycling_user_1_segment.segment_id}\' '
f'for activity \'{activity_cycling_user_1.short_id}\'>'
== str(activity_cycling_user_1_segment)
)

View File

@ -4,10 +4,10 @@ from typing import Generator, Optional
import pytest import pytest
from fittrackee import create_app, db from fittrackee import create_app, db
from fittrackee.activities.models import Activity, ActivitySegment, Sport
from fittrackee.application.models import AppConfig from fittrackee.application.models import AppConfig
from fittrackee.application.utils import update_app_config_from_database from fittrackee.application.utils import update_app_config_from_database
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout, WorkoutSegment
os.environ['FLASK_ENV'] = 'testing' os.environ['FLASK_ENV'] = 'testing'
os.environ['APP_SETTINGS'] = 'fittrackee.config.TestingConfig' os.environ['APP_SETTINGS'] = 'fittrackee.config.TestingConfig'
@ -180,149 +180,149 @@ def sport_2_running() -> Sport:
@pytest.fixture() @pytest.fixture()
def activity_cycling_user_1() -> Activity: def workout_cycling_user_1() -> Workout:
activity = Activity( workout = Workout(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
activity_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'), workout_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'),
distance=10, distance=10,
duration=datetime.timedelta(seconds=3600), duration=datetime.timedelta(seconds=3600),
) )
activity.max_speed = 10 workout.max_speed = 10
activity.ave_speed = 10 workout.ave_speed = 10
activity.moving = activity.duration workout.moving = workout.duration
db.session.add(activity) db.session.add(workout)
db.session.commit() db.session.commit()
return activity return workout
@pytest.fixture() @pytest.fixture()
def activity_cycling_user_1_segment( def workout_cycling_user_1_segment(
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> ActivitySegment: ) -> WorkoutSegment:
activity_segment = ActivitySegment( workout_segment = WorkoutSegment(
activity_id=activity_cycling_user_1.id, workout_id=workout_cycling_user_1.id,
activity_uuid=activity_cycling_user_1.uuid, workout_uuid=workout_cycling_user_1.uuid,
segment_id=0, segment_id=0,
) )
activity_segment.duration = datetime.timedelta(seconds=6000) workout_segment.duration = datetime.timedelta(seconds=6000)
activity_segment.moving = activity_segment.duration workout_segment.moving = workout_segment.duration
activity_segment.distance = 5 workout_segment.distance = 5
db.session.add(activity_segment) db.session.add(workout_segment)
db.session.commit() db.session.commit()
return activity_segment return workout_segment
@pytest.fixture() @pytest.fixture()
def activity_running_user_1() -> Activity: def workout_running_user_1() -> Workout:
activity = Activity( workout = Workout(
user_id=1, user_id=1,
sport_id=2, sport_id=2,
activity_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'), workout_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'),
distance=12, distance=12,
duration=datetime.timedelta(seconds=6000), duration=datetime.timedelta(seconds=6000),
) )
activity.moving = activity.duration workout.moving = workout.duration
db.session.add(activity) db.session.add(workout)
db.session.commit() db.session.commit()
return activity return workout
@pytest.fixture() @pytest.fixture()
def seven_activities_user_1() -> Activity: def seven_workouts_user_1() -> Workout:
activity = Activity( workout = Workout(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
activity_date=datetime.datetime.strptime('20/03/2017', '%d/%m/%Y'), workout_date=datetime.datetime.strptime('20/03/2017', '%d/%m/%Y'),
distance=5, distance=5,
duration=datetime.timedelta(seconds=1024), duration=datetime.timedelta(seconds=1024),
) )
activity.ave_speed = float(activity.distance) / (1024 / 3600) workout.ave_speed = float(workout.distance) / (1024 / 3600)
activity.moving = activity.duration workout.moving = workout.duration
db.session.add(activity) db.session.add(workout)
db.session.flush() db.session.flush()
activity = Activity( workout = Workout(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
activity_date=datetime.datetime.strptime('01/06/2017', '%d/%m/%Y'), workout_date=datetime.datetime.strptime('01/06/2017', '%d/%m/%Y'),
distance=10, distance=10,
duration=datetime.timedelta(seconds=3456), duration=datetime.timedelta(seconds=3456),
) )
activity.ave_speed = float(activity.distance) / (3456 / 3600) workout.ave_speed = float(workout.distance) / (3456 / 3600)
activity.moving = activity.duration workout.moving = workout.duration
db.session.add(activity) db.session.add(workout)
db.session.flush() db.session.flush()
activity = Activity( workout = Workout(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
activity_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'), workout_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'),
distance=10, distance=10,
duration=datetime.timedelta(seconds=1024), duration=datetime.timedelta(seconds=1024),
) )
activity.ave_speed = float(activity.distance) / (1024 / 3600) workout.ave_speed = float(workout.distance) / (1024 / 3600)
activity.moving = activity.duration workout.moving = workout.duration
db.session.add(activity) db.session.add(workout)
db.session.flush() db.session.flush()
activity = Activity( workout = Workout(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
activity_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'), workout_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
distance=1, distance=1,
duration=datetime.timedelta(seconds=600), duration=datetime.timedelta(seconds=600),
) )
activity.ave_speed = float(activity.distance) / (600 / 3600) workout.ave_speed = float(workout.distance) / (600 / 3600)
activity.moving = activity.duration workout.moving = workout.duration
db.session.add(activity) db.session.add(workout)
db.session.flush() db.session.flush()
activity = Activity( workout = Workout(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
activity_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'), workout_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
distance=10, distance=10,
duration=datetime.timedelta(seconds=1000), duration=datetime.timedelta(seconds=1000),
) )
activity.ave_speed = float(activity.distance) / (1000 / 3600) workout.ave_speed = float(workout.distance) / (1000 / 3600)
activity.moving = activity.duration workout.moving = workout.duration
db.session.add(activity) db.session.add(workout)
db.session.flush() db.session.flush()
activity = Activity( workout = Workout(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
activity_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'), workout_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'),
distance=8, distance=8,
duration=datetime.timedelta(seconds=6000), duration=datetime.timedelta(seconds=6000),
) )
activity.ave_speed = float(activity.distance) / (6000 / 3600) workout.ave_speed = float(workout.distance) / (6000 / 3600)
activity.moving = activity.duration workout.moving = workout.duration
db.session.add(activity) db.session.add(workout)
db.session.flush() db.session.flush()
activity = Activity( workout = Workout(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
activity_date=datetime.datetime.strptime('09/05/2018', '%d/%m/%Y'), workout_date=datetime.datetime.strptime('09/05/2018', '%d/%m/%Y'),
distance=10, distance=10,
duration=datetime.timedelta(seconds=3000), duration=datetime.timedelta(seconds=3000),
) )
activity.ave_speed = float(activity.distance) / (3000 / 3600) workout.ave_speed = float(workout.distance) / (3000 / 3600)
activity.moving = activity.duration workout.moving = workout.duration
db.session.add(activity) db.session.add(workout)
db.session.commit() db.session.commit()
return activity return workout
@pytest.fixture() @pytest.fixture()
def activity_cycling_user_2() -> Activity: def workout_cycling_user_2() -> Workout:
activity = Activity( workout = Workout(
user_id=2, user_id=2,
sport_id=1, sport_id=1,
activity_date=datetime.datetime.strptime('23/01/2018', '%d/%m/%Y'), workout_date=datetime.datetime.strptime('23/01/2018', '%d/%m/%Y'),
distance=15, distance=15,
duration=datetime.timedelta(seconds=3600), duration=datetime.timedelta(seconds=3600),
) )
activity.moving = activity.duration workout.moving = workout.duration
db.session.add(activity) db.session.add(workout)
db.session.commit() db.session.commit()
return activity return workout
@pytest.fixture() @pytest.fixture()
@ -332,7 +332,7 @@ def gpx_file() -> str:
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa '<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
' <metadata/>' ' <metadata/>'
' <trk>' ' <trk>'
' <name>just an activity</name>' ' <name>just a workout</name>'
' <trkseg>' ' <trkseg>'
' <trkpt lat="44.68095" lon="6.07367">' ' <trkpt lat="44.68095" lon="6.07367">'
' <ele>998</ele>' ' <ele>998</ele>'
@ -580,7 +580,7 @@ def gpx_file_with_segments() -> str:
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa '<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
' <metadata/>' ' <metadata/>'
' <trk>' ' <trk>'
' <name>just an activity</name>' ' <name>just a workout</name>'
' <trkseg>' ' <trkseg>'
' <trkpt lat="44.68095" lon="6.07367">' ' <trkpt lat="44.68095" lon="6.07367">'
' <ele>998</ele>' ' <ele>998</ele>'

Binary file not shown.

View File

@ -3,9 +3,9 @@ from datetime import datetime, timedelta
from io import BytesIO from io import BytesIO
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from fittrackee.activities.models import Activity, Sport
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.users.utils_token import get_user_token from fittrackee.users.utils_token import get_user_token
from fittrackee.workouts.models import Sport, Workout
from flask import Flask from flask import Flask
from freezegun import freeze_time from freezegun import freeze_time
@ -446,8 +446,8 @@ class TestUserProfile:
assert data['data']['timezone'] is None assert data['data']['timezone'] is None
assert data['data']['weekm'] is False assert data['data']['weekm'] is False
assert data['data']['language'] is None assert data['data']['language'] is None
assert data['data']['nb_activities'] == 0
assert data['data']['nb_sports'] == 0 assert data['data']['nb_sports'] == 0
assert data['data']['nb_workouts'] == 0
assert data['data']['sports_list'] == [] assert data['data']['sports_list'] == []
assert data['data']['total_distance'] == 0 assert data['data']['total_distance'] == 0
assert data['data']['total_duration'] == '0:00:00' assert data['data']['total_duration'] == '0:00:00'
@ -484,21 +484,21 @@ class TestUserProfile:
assert data['data']['timezone'] == 'America/New_York' assert data['data']['timezone'] == 'America/New_York'
assert data['data']['weekm'] is False assert data['data']['weekm'] is False
assert data['data']['language'] == 'en' assert data['data']['language'] == 'en'
assert data['data']['nb_activities'] == 0
assert data['data']['nb_sports'] == 0 assert data['data']['nb_sports'] == 0
assert data['data']['nb_workouts'] == 0
assert data['data']['sports_list'] == [] assert data['data']['sports_list'] == []
assert data['data']['total_distance'] == 0 assert data['data']['total_distance'] == 0
assert data['data']['total_duration'] == '0:00:00' assert data['data']['total_duration'] == '0:00:00'
assert response.status_code == 200 assert response.status_code == 200
def test_it_returns_user_profile_with_activities( def test_it_returns_user_profile_with_workouts(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -521,8 +521,8 @@ class TestUserProfile:
assert data['data']['created_at'] assert data['data']['created_at']
assert not data['data']['admin'] assert not data['data']['admin']
assert data['data']['timezone'] is None assert data['data']['timezone'] is None
assert data['data']['nb_activities'] == 2
assert data['data']['nb_sports'] == 2 assert data['data']['nb_sports'] == 2
assert data['data']['nb_workouts'] == 2
assert data['data']['sports_list'] == [1, 2] assert data['data']['sports_list'] == [1, 2]
assert data['data']['total_distance'] == 22 assert data['data']['total_distance'] == 22
assert data['data']['total_duration'] == '2:40:00' assert data['data']['total_duration'] == '2:40:00'
@ -585,8 +585,8 @@ class TestUserProfileUpdate:
assert data['data']['timezone'] == 'America/New_York' assert data['data']['timezone'] == 'America/New_York'
assert data['data']['weekm'] is True assert data['data']['weekm'] is True
assert data['data']['language'] == 'fr' assert data['data']['language'] == 'fr'
assert data['data']['nb_activities'] == 0
assert data['data']['nb_sports'] == 0 assert data['data']['nb_sports'] == 0
assert data['data']['nb_workouts'] == 0
assert data['data']['sports_list'] == [] assert data['data']['sports_list'] == []
assert data['data']['total_distance'] == 0 assert data['data']['total_distance'] == 0
assert data['data']['total_duration'] == '0:00:00' assert data['data']['total_duration'] == '0:00:00'
@ -636,8 +636,8 @@ class TestUserProfileUpdate:
assert data['data']['timezone'] == 'America/New_York' assert data['data']['timezone'] == 'America/New_York'
assert data['data']['weekm'] is True assert data['data']['weekm'] is True
assert data['data']['language'] == 'fr' assert data['data']['language'] == 'fr'
assert data['data']['nb_activities'] == 0
assert data['data']['nb_sports'] == 0 assert data['data']['nb_sports'] == 0
assert data['data']['nb_workouts'] == 0
assert data['data']['sports_list'] == [] assert data['data']['sports_list'] == []
assert data['data']['total_distance'] == 0 assert data['data']['total_distance'] == 0
assert data['data']['total_duration'] == '0:00:00' assert data['data']['total_duration'] == '0:00:00'

View File

@ -3,13 +3,13 @@ from datetime import datetime, timedelta
from io import BytesIO from io import BytesIO
from unittest.mock import patch from unittest.mock import patch
from fittrackee.activities.models import Activity, Sport
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout
from flask import Flask from flask import Flask
class TestGetUser: class TestGetUser:
def test_it_gets_single_user_without_activities( def test_it_gets_single_user_without_workouts(
self, app: Flask, user_1: User, user_2: User self, app: Flask, user_1: User, user_2: User
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
@ -45,20 +45,20 @@ class TestGetUser:
assert user['timezone'] is None assert user['timezone'] is None
assert user['weekm'] is False assert user['weekm'] is False
assert user['language'] is None assert user['language'] is None
assert user['nb_activities'] == 0
assert user['nb_sports'] == 0 assert user['nb_sports'] == 0
assert user['nb_workouts'] == 0
assert user['sports_list'] == [] assert user['sports_list'] == []
assert user['total_distance'] == 0 assert user['total_distance'] == 0
assert user['total_duration'] == '0:00:00' assert user['total_duration'] == '0:00:00'
def test_it_gets_single_user_with_activities( def test_it_gets_single_user_with_workouts(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -93,8 +93,8 @@ class TestGetUser:
assert user['timezone'] is None assert user['timezone'] is None
assert user['weekm'] is False assert user['weekm'] is False
assert user['language'] is None assert user['language'] is None
assert user['nb_activities'] == 2
assert user['nb_sports'] == 2 assert user['nb_sports'] == 2
assert user['nb_workouts'] == 2
assert user['sports_list'] == [1, 2] assert user['sports_list'] == [1, 2]
assert user['total_distance'] == 22 assert user['total_distance'] == 22
assert user['total_duration'] == '2:40:00' assert user['total_duration'] == '2:40:00'
@ -158,24 +158,24 @@ class TestGetUsers:
assert data['data']['users'][0]['timezone'] is None assert data['data']['users'][0]['timezone'] is None
assert data['data']['users'][0]['weekm'] is False assert data['data']['users'][0]['weekm'] is False
assert data['data']['users'][0]['language'] is None assert data['data']['users'][0]['language'] is None
assert data['data']['users'][0]['nb_activities'] == 0
assert data['data']['users'][0]['nb_sports'] == 0 assert data['data']['users'][0]['nb_sports'] == 0
assert data['data']['users'][0]['nb_workouts'] == 0
assert data['data']['users'][0]['sports_list'] == [] assert data['data']['users'][0]['sports_list'] == []
assert data['data']['users'][0]['total_distance'] == 0 assert data['data']['users'][0]['total_distance'] == 0
assert data['data']['users'][0]['total_duration'] == '0:00:00' assert data['data']['users'][0]['total_duration'] == '0:00:00'
assert data['data']['users'][1]['timezone'] is None assert data['data']['users'][1]['timezone'] is None
assert data['data']['users'][1]['weekm'] is False assert data['data']['users'][1]['weekm'] is False
assert data['data']['users'][1]['language'] is None assert data['data']['users'][1]['language'] is None
assert data['data']['users'][1]['nb_activities'] == 0
assert data['data']['users'][1]['nb_sports'] == 0 assert data['data']['users'][1]['nb_sports'] == 0
assert data['data']['users'][1]['nb_workouts'] == 0
assert data['data']['users'][1]['sports_list'] == [] assert data['data']['users'][1]['sports_list'] == []
assert data['data']['users'][1]['total_distance'] == 0 assert data['data']['users'][1]['total_distance'] == 0
assert data['data']['users'][1]['total_duration'] == '0:00:00' assert data['data']['users'][1]['total_duration'] == '0:00:00'
assert data['data']['users'][2]['timezone'] is None assert data['data']['users'][2]['timezone'] is None
assert data['data']['users'][2]['weekm'] is True assert data['data']['users'][2]['weekm'] is True
assert data['data']['users'][2]['language'] is None assert data['data']['users'][2]['language'] is None
assert data['data']['users'][2]['nb_activities'] == 0
assert data['data']['users'][2]['nb_sports'] == 0 assert data['data']['users'][2]['nb_sports'] == 0
assert data['data']['users'][2]['nb_workouts'] == 0
assert data['data']['users'][2]['sports_list'] == [] assert data['data']['users'][2]['sports_list'] == []
assert data['data']['users'][2]['total_distance'] == 0 assert data['data']['users'][2]['total_distance'] == 0
assert data['data']['users'][2]['total_duration'] == '0:00:00' assert data['data']['users'][2]['total_duration'] == '0:00:00'
@ -187,17 +187,17 @@ class TestGetUsers:
'total': 3, 'total': 3,
} }
def test_it_gets_users_list_with_activities( def test_it_gets_users_list_with_workouts(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
user_2: User, user_2: User,
user_3: User, user_3: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
sport_2_running: Sport, sport_2_running: Sport,
activity_running_user_1: Activity, workout_running_user_1: Workout,
activity_cycling_user_2: Activity, workout_cycling_user_2: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -229,22 +229,22 @@ class TestGetUsers:
assert 'sam@test.com' in data['data']['users'][2]['email'] assert 'sam@test.com' in data['data']['users'][2]['email']
assert data['data']['users'][0]['timezone'] is None assert data['data']['users'][0]['timezone'] is None
assert data['data']['users'][0]['weekm'] is False assert data['data']['users'][0]['weekm'] is False
assert data['data']['users'][0]['nb_activities'] == 2
assert data['data']['users'][0]['nb_sports'] == 2 assert data['data']['users'][0]['nb_sports'] == 2
assert data['data']['users'][0]['nb_workouts'] == 2
assert data['data']['users'][0]['sports_list'] == [1, 2] assert data['data']['users'][0]['sports_list'] == [1, 2]
assert data['data']['users'][0]['total_distance'] == 22.0 assert data['data']['users'][0]['total_distance'] == 22.0
assert data['data']['users'][0]['total_duration'] == '2:40:00' assert data['data']['users'][0]['total_duration'] == '2:40:00'
assert data['data']['users'][1]['timezone'] is None assert data['data']['users'][1]['timezone'] is None
assert data['data']['users'][1]['weekm'] is False assert data['data']['users'][1]['weekm'] is False
assert data['data']['users'][1]['nb_activities'] == 1
assert data['data']['users'][1]['nb_sports'] == 1 assert data['data']['users'][1]['nb_sports'] == 1
assert data['data']['users'][1]['nb_workouts'] == 1
assert data['data']['users'][1]['sports_list'] == [1] assert data['data']['users'][1]['sports_list'] == [1]
assert data['data']['users'][1]['total_distance'] == 15 assert data['data']['users'][1]['total_distance'] == 15
assert data['data']['users'][1]['total_duration'] == '1:00:00' assert data['data']['users'][1]['total_duration'] == '1:00:00'
assert data['data']['users'][2]['timezone'] is None assert data['data']['users'][2]['timezone'] is None
assert data['data']['users'][2]['weekm'] is True assert data['data']['users'][2]['weekm'] is True
assert data['data']['users'][2]['nb_activities'] == 0
assert data['data']['users'][2]['nb_sports'] == 0 assert data['data']['users'][2]['nb_sports'] == 0
assert data['data']['users'][2]['nb_workouts'] == 0
assert data['data']['users'][2]['sports_list'] == [] assert data['data']['users'][2]['sports_list'] == []
assert data['data']['users'][2]['total_distance'] == 0 assert data['data']['users'][2]['total_distance'] == 0
assert data['data']['users'][2]['total_duration'] == '0:00:00' assert data['data']['users'][2]['total_duration'] == '0:00:00'
@ -745,14 +745,14 @@ class TestGetUsers:
'total': 3, 'total': 3,
} }
def test_it_gets_users_list_ordered_by_activities_count( def test_it_gets_users_list_ordered_by_workouts_count(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
user_2: User, user_2: User,
user_3: User, user_3: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_2: Activity, workout_cycling_user_2: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -762,7 +762,7 @@ class TestGetUsers:
) )
response = client.get( response = client.get(
'/api/users?order_by=activities_count', '/api/users?order_by=workouts_count',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
@ -774,11 +774,11 @@ class TestGetUsers:
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['users']) == 3 assert len(data['data']['users']) == 3
assert 'test' in data['data']['users'][0]['username'] assert 'test' in data['data']['users'][0]['username']
assert 0 == data['data']['users'][0]['nb_activities'] assert 0 == data['data']['users'][0]['nb_workouts']
assert 'sam' in data['data']['users'][1]['username'] assert 'sam' in data['data']['users'][1]['username']
assert 0 == data['data']['users'][1]['nb_activities'] assert 0 == data['data']['users'][1]['nb_workouts']
assert 'toto' in data['data']['users'][2]['username'] assert 'toto' in data['data']['users'][2]['username']
assert 1 == data['data']['users'][2]['nb_activities'] assert 1 == data['data']['users'][2]['nb_workouts']
assert data['pagination'] == { assert data['pagination'] == {
'has_next': False, 'has_next': False,
'has_prev': False, 'has_prev': False,
@ -787,14 +787,14 @@ class TestGetUsers:
'total': 3, 'total': 3,
} }
def test_it_gets_users_list_ordered_by_activities_count_ascending( def test_it_gets_users_list_ordered_by_workouts_count_ascending(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
user_2: User, user_2: User,
user_3: User, user_3: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_2: Activity, workout_cycling_user_2: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -804,7 +804,7 @@ class TestGetUsers:
) )
response = client.get( response = client.get(
'/api/users?order_by=activities_count&order=asc', '/api/users?order_by=workouts_count&order=asc',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
@ -816,11 +816,11 @@ class TestGetUsers:
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['users']) == 3 assert len(data['data']['users']) == 3
assert 'test' in data['data']['users'][0]['username'] assert 'test' in data['data']['users'][0]['username']
assert 0 == data['data']['users'][0]['nb_activities'] assert 0 == data['data']['users'][0]['nb_workouts']
assert 'sam' in data['data']['users'][1]['username'] assert 'sam' in data['data']['users'][1]['username']
assert 0 == data['data']['users'][1]['nb_activities'] assert 0 == data['data']['users'][1]['nb_workouts']
assert 'toto' in data['data']['users'][2]['username'] assert 'toto' in data['data']['users'][2]['username']
assert 1 == data['data']['users'][2]['nb_activities'] assert 1 == data['data']['users'][2]['nb_workouts']
assert data['pagination'] == { assert data['pagination'] == {
'has_next': False, 'has_next': False,
'has_prev': False, 'has_prev': False,
@ -829,14 +829,14 @@ class TestGetUsers:
'total': 3, 'total': 3,
} }
def test_it_gets_users_list_ordered_by_activities_count_descending( def test_it_gets_users_list_ordered_by_workouts_count_descending(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
user_2: User, user_2: User,
user_3: User, user_3: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_2: Activity, workout_cycling_user_2: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -846,7 +846,7 @@ class TestGetUsers:
) )
response = client.get( response = client.get(
'/api/users?order_by=activities_count&order=desc', '/api/users?order_by=workouts_count&order=desc',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
@ -858,11 +858,11 @@ class TestGetUsers:
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['users']) == 3 assert len(data['data']['users']) == 3
assert 'toto' in data['data']['users'][0]['username'] assert 'toto' in data['data']['users'][0]['username']
assert 1 == data['data']['users'][0]['nb_activities'] assert 1 == data['data']['users'][0]['nb_workouts']
assert 'test' in data['data']['users'][1]['username'] assert 'test' in data['data']['users'][1]['username']
assert 0 == data['data']['users'][1]['nb_activities'] assert 0 == data['data']['users'][1]['nb_workouts']
assert 'sam' in data['data']['users'][2]['username'] assert 'sam' in data['data']['users'][2]['username']
assert 0 == data['data']['users'][2]['nb_activities'] assert 0 == data['data']['users'][2]['nb_workouts']
assert data['pagination'] == { assert data['pagination'] == {
'has_next': False, 'has_next': False,
'has_prev': False, 'has_prev': False,
@ -1156,7 +1156,7 @@ class TestDeleteUser:
assert response.status_code == 204 assert response.status_code == 204
def test_user_with_activity_can_delete_its_own_account( def test_user_with_workout_can_delete_its_own_account(
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:
client = app.test_client() client = app.test_client()
@ -1166,7 +1166,7 @@ class TestDeleteUser:
content_type='application/json', content_type='application/json',
) )
client.post( client.post(
'/api/activities', '/api/workouts',
data=dict( data=dict(
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'), file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
data='{"sport_id": 1}', data='{"sport_id": 1}',

View File

@ -19,8 +19,8 @@ class TestUserModel:
assert serialized_user['timezone'] is None assert serialized_user['timezone'] is None
assert serialized_user['weekm'] is False assert serialized_user['weekm'] is False
assert serialized_user['language'] is None assert serialized_user['language'] is None
assert serialized_user['nb_activities'] == 0
assert serialized_user['nb_sports'] == 0 assert serialized_user['nb_sports'] == 0
assert serialized_user['nb_workouts'] == 0
assert serialized_user['total_distance'] == 0 assert serialized_user['total_distance'] == 0
assert serialized_user['total_duration'] == '0:00:00' assert serialized_user['total_duration'] == '0:00:00'

View File

@ -1,7 +1,7 @@
import json import json
from fittrackee.activities.models import Activity, Sport
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout
from flask import Flask from flask import Flask
@ -13,8 +13,8 @@ class TestGetRecords:
user_2: User, user_2: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
activity_cycling_user_2: Activity, workout_cycling_user_2: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -37,64 +37,64 @@ class TestGetRecords:
assert ( assert (
'Mon, 01 Jan 2018 00:00:00 GMT' 'Mon, 01 Jan 2018 00:00:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) )
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert ( assert (
activity_cycling_user_1.short_id workout_cycling_user_1.short_id
== data['data']['records'][0]['activity_id'] == data['data']['records'][0]['workout_id']
) )
assert 'AS' == data['data']['records'][0]['record_type'] assert 'AS' == data['data']['records'][0]['record_type']
assert 'value' in data['data']['records'][0] assert 'value' in data['data']['records'][0]
assert ( assert (
'Mon, 01 Jan 2018 00:00:00 GMT' 'Mon, 01 Jan 2018 00:00:00 GMT'
== data['data']['records'][1]['activity_date'] == data['data']['records'][1]['workout_date']
) )
assert 'test' == data['data']['records'][1]['user'] assert 'test' == data['data']['records'][1]['user']
assert sport_1_cycling.id == data['data']['records'][1]['sport_id'] assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
assert ( assert (
activity_cycling_user_1.short_id workout_cycling_user_1.short_id
== data['data']['records'][1]['activity_id'] == data['data']['records'][1]['workout_id']
) )
assert 'FD' == data['data']['records'][1]['record_type'] assert 'FD' == data['data']['records'][1]['record_type']
assert 'value' in data['data']['records'][1] assert 'value' in data['data']['records'][1]
assert ( assert (
'Mon, 01 Jan 2018 00:00:00 GMT' 'Mon, 01 Jan 2018 00:00:00 GMT'
== data['data']['records'][2]['activity_date'] == data['data']['records'][2]['workout_date']
) )
assert 'test' == data['data']['records'][2]['user'] assert 'test' == data['data']['records'][2]['user']
assert sport_1_cycling.id == data['data']['records'][2]['sport_id'] assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
assert ( assert (
activity_cycling_user_1.short_id workout_cycling_user_1.short_id
== data['data']['records'][2]['activity_id'] == data['data']['records'][2]['workout_id']
) )
assert 'LD' == data['data']['records'][2]['record_type'] assert 'LD' == data['data']['records'][2]['record_type']
assert 'value' in data['data']['records'][2] assert 'value' in data['data']['records'][2]
assert ( assert (
'Mon, 01 Jan 2018 00:00:00 GMT' 'Mon, 01 Jan 2018 00:00:00 GMT'
== data['data']['records'][3]['activity_date'] == data['data']['records'][3]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][3]['user'] assert 'test' == data['data']['records'][3]['user']
assert sport_1_cycling.id == data['data']['records'][3]['sport_id'] assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
assert ( assert (
activity_cycling_user_1.short_id workout_cycling_user_1.short_id
== data['data']['records'][3]['activity_id'] == data['data']['records'][3]['workout_id']
) )
assert 'MS' == data['data']['records'][3]['record_type'] assert 'MS' == data['data']['records'][3]['record_type']
assert 'value' in data['data']['records'][3] assert 'value' in data['data']['records'][3]
def test_it_gets_no_records_if_user_has_no_activity( def test_it_gets_no_records_if_user_has_no_workout(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
user_2: User, user_2: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
activity_cycling_user_2: Activity, workout_cycling_user_2: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -115,7 +115,7 @@ class TestGetRecords:
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['records']) == 0 assert len(data['data']['records']) == 0
def test_it_gets_no_records_if_activity_has_zero_value( def test_it_gets_no_records_if_workout_has_zero_value(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
@ -129,15 +129,15 @@ class TestGetRecords:
content_type='application/json', content_type='application/json',
) )
client.post( client.post(
'/api/activities/no_gpx', '/api/workouts/no_gpx',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=1, sport_id=1,
duration=0, duration=0,
activity_date='2018-05-14 14:05', workout_date='2018-05-14 14:05',
distance=0, distance=0,
title='Activity test', title='Workout test',
) )
), ),
headers=dict( headers=dict(
@ -158,7 +158,7 @@ class TestGetRecords:
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['records']) == 0 assert len(data['data']['records']) == 0
def test_it_gets_updated_records_after_activities_post_and_patch( def test_it_gets_updated_records_after_workouts_post_and_patch(
self, app: Flask, user_1: User, sport_1_cycling: Sport self, app: Flask, user_1: User, sport_1_cycling: Sport
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
@ -168,15 +168,15 @@ class TestGetRecords:
content_type='application/json', content_type='application/json',
) )
response = client.post( response = client.post(
'/api/activities/no_gpx', '/api/workouts/no_gpx',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=1, sport_id=1,
duration=3600, duration=3600,
activity_date='2018-05-14 14:05', workout_date='2018-05-14 14:05',
distance=7, distance=7,
title='Activity test 1', title='Workout test 1',
) )
), ),
headers=dict( headers=dict(
@ -185,7 +185,7 @@ class TestGetRecords:
), ),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
activity_1_short_id = data['data']['activities'][0]['id'] workout_1_short_id = data['data']['workouts'][0]['id']
response = client.get( response = client.get(
'/api/records', '/api/records',
headers=dict( headers=dict(
@ -201,56 +201,56 @@ class TestGetRecords:
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_1_short_id == data['data']['records'][0]['activity_id'] assert workout_1_short_id == data['data']['records'][0]['workout_id']
assert 'AS' == data['data']['records'][0]['record_type'] assert 'AS' == data['data']['records'][0]['record_type']
assert 7.0 == data['data']['records'][0]['value'] assert 7.0 == data['data']['records'][0]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][1]['activity_date'] == data['data']['records'][1]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][1]['user'] assert 'test' == data['data']['records'][1]['user']
assert sport_1_cycling.id == data['data']['records'][1]['sport_id'] assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
assert activity_1_short_id == data['data']['records'][1]['activity_id'] assert workout_1_short_id == data['data']['records'][1]['workout_id']
assert 'FD' == data['data']['records'][1]['record_type'] assert 'FD' == data['data']['records'][1]['record_type']
assert 7.0 == data['data']['records'][1]['value'] assert 7.0 == data['data']['records'][1]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][2]['activity_date'] == data['data']['records'][2]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][2]['user'] assert 'test' == data['data']['records'][2]['user']
assert sport_1_cycling.id == data['data']['records'][2]['sport_id'] assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
assert activity_1_short_id == data['data']['records'][2]['activity_id'] assert workout_1_short_id == data['data']['records'][2]['workout_id']
assert 'LD' == data['data']['records'][2]['record_type'] assert 'LD' == data['data']['records'][2]['record_type']
assert '1:00:00' == data['data']['records'][2]['value'] assert '1:00:00' == data['data']['records'][2]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][3]['activity_date'] == data['data']['records'][3]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][3]['user'] assert 'test' == data['data']['records'][3]['user']
assert sport_1_cycling.id == data['data']['records'][3]['sport_id'] assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
assert activity_1_short_id == data['data']['records'][3]['activity_id'] assert workout_1_short_id == data['data']['records'][3]['workout_id']
assert 'MS' == data['data']['records'][3]['record_type'] assert 'MS' == data['data']['records'][3]['record_type']
assert 7.0 == data['data']['records'][3]['value'] assert 7.0 == data['data']['records'][3]['value']
# Post activity with lower duration (same sport) # Post workout with lower duration (same sport)
# => 2 new records: Average speed and Max speed # => 2 new records: Average speed and Max speed
response = client.post( response = client.post(
'/api/activities/no_gpx', '/api/workouts/no_gpx',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=1, sport_id=1,
duration=3000, duration=3000,
activity_date='2018-05-15 14:05', workout_date='2018-05-15 14:05',
distance=7, distance=7,
title='Activity test 2', title='Workout test 2',
) )
), ),
headers=dict( headers=dict(
@ -259,7 +259,7 @@ class TestGetRecords:
), ),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
activity_2_short_id = data['data']['activities'][0]['id'] workout_2_short_id = data['data']['workouts'][0]['id']
response = client.get( response = client.get(
'/api/records', '/api/records',
headers=dict( headers=dict(
@ -275,55 +275,55 @@ class TestGetRecords:
assert ( assert (
'Tue, 15 May 2018 14:05:00 GMT' 'Tue, 15 May 2018 14:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_2_short_id == data['data']['records'][0]['activity_id'] assert workout_2_short_id == data['data']['records'][0]['workout_id']
assert 'AS' == data['data']['records'][0]['record_type'] assert 'AS' == data['data']['records'][0]['record_type']
assert 8.4 == data['data']['records'][0]['value'] assert 8.4 == data['data']['records'][0]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][1]['activity_date'] == data['data']['records'][1]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][1]['user'] assert 'test' == data['data']['records'][1]['user']
assert sport_1_cycling.id == data['data']['records'][1]['sport_id'] assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
assert activity_1_short_id == data['data']['records'][1]['activity_id'] assert workout_1_short_id == data['data']['records'][1]['workout_id']
assert 'FD' == data['data']['records'][1]['record_type'] assert 'FD' == data['data']['records'][1]['record_type']
assert 7.0 == data['data']['records'][1]['value'] assert 7.0 == data['data']['records'][1]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][2]['activity_date'] == data['data']['records'][2]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][2]['user'] assert 'test' == data['data']['records'][2]['user']
assert sport_1_cycling.id == data['data']['records'][2]['sport_id'] assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
assert activity_1_short_id == data['data']['records'][2]['activity_id'] assert workout_1_short_id == data['data']['records'][2]['workout_id']
assert 'LD' == data['data']['records'][2]['record_type'] assert 'LD' == data['data']['records'][2]['record_type']
assert '1:00:00' == data['data']['records'][2]['value'] assert '1:00:00' == data['data']['records'][2]['value']
assert ( assert (
'Tue, 15 May 2018 14:05:00 GMT' 'Tue, 15 May 2018 14:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_2_short_id == data['data']['records'][0]['activity_id'] assert workout_2_short_id == data['data']['records'][0]['workout_id']
assert 'MS' == data['data']['records'][3]['record_type'] assert 'MS' == data['data']['records'][3]['record_type']
assert 8.4 == data['data']['records'][3]['value'] assert 8.4 == data['data']['records'][3]['value']
# Post activity with no new records # Post workout with no new records
response = client.post( response = client.post(
'/api/activities/no_gpx', '/api/workouts/no_gpx',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=1, sport_id=1,
duration=3500, duration=3500,
activity_date='2018-05-16 14:05', workout_date='2018-05-16 14:05',
distance=6.5, distance=6.5,
title='Activity test 3', title='Workout test 3',
) )
), ),
headers=dict( headers=dict(
@ -332,7 +332,7 @@ class TestGetRecords:
), ),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
activity_3_short_id = data['data']['activities'][0]['id'] workout_3_short_id = data['data']['workouts'][0]['id']
response = client.get( response = client.get(
'/api/records', '/api/records',
headers=dict( headers=dict(
@ -348,48 +348,48 @@ class TestGetRecords:
assert ( assert (
'Tue, 15 May 2018 14:05:00 GMT' 'Tue, 15 May 2018 14:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_2_short_id == data['data']['records'][0]['activity_id'] assert workout_2_short_id == data['data']['records'][0]['workout_id']
assert 'AS' == data['data']['records'][0]['record_type'] assert 'AS' == data['data']['records'][0]['record_type']
assert 8.4 == data['data']['records'][0]['value'] assert 8.4 == data['data']['records'][0]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][1]['activity_date'] == data['data']['records'][1]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][1]['user'] assert 'test' == data['data']['records'][1]['user']
assert sport_1_cycling.id == data['data']['records'][1]['sport_id'] assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
assert activity_1_short_id == data['data']['records'][1]['activity_id'] assert workout_1_short_id == data['data']['records'][1]['workout_id']
assert 'FD' == data['data']['records'][1]['record_type'] assert 'FD' == data['data']['records'][1]['record_type']
assert 7.0 == data['data']['records'][1]['value'] assert 7.0 == data['data']['records'][1]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][2]['activity_date'] == data['data']['records'][2]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][2]['user'] assert 'test' == data['data']['records'][2]['user']
assert sport_1_cycling.id == data['data']['records'][2]['sport_id'] assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
assert activity_1_short_id == data['data']['records'][2]['activity_id'] assert workout_1_short_id == data['data']['records'][2]['workout_id']
assert 'LD' == data['data']['records'][2]['record_type'] assert 'LD' == data['data']['records'][2]['record_type']
assert '1:00:00' == data['data']['records'][2]['value'] assert '1:00:00' == data['data']['records'][2]['value']
assert ( assert (
'Tue, 15 May 2018 14:05:00 GMT' 'Tue, 15 May 2018 14:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_2_short_id == data['data']['records'][0]['activity_id'] assert workout_2_short_id == data['data']['records'][0]['workout_id']
assert 'MS' == data['data']['records'][3]['record_type'] assert 'MS' == data['data']['records'][3]['record_type']
assert 8.4 == data['data']['records'][3]['value'] assert 8.4 == data['data']['records'][3]['value']
# Edit last activity # Edit last workout
# 1 new record: Longest duration # 1 new record: Longest duration
client.patch( client.patch(
f'/api/activities/{activity_3_short_id}', f'/api/workouts/{workout_3_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict(duration=4000)), data=json.dumps(dict(duration=4000)),
headers=dict( headers=dict(
@ -412,47 +412,47 @@ class TestGetRecords:
assert ( assert (
'Tue, 15 May 2018 14:05:00 GMT' 'Tue, 15 May 2018 14:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_2_short_id == data['data']['records'][0]['activity_id'] assert workout_2_short_id == data['data']['records'][0]['workout_id']
assert 'AS' == data['data']['records'][0]['record_type'] assert 'AS' == data['data']['records'][0]['record_type']
assert 8.4 == data['data']['records'][0]['value'] assert 8.4 == data['data']['records'][0]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][1]['activity_date'] == data['data']['records'][1]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][1]['user'] assert 'test' == data['data']['records'][1]['user']
assert sport_1_cycling.id == data['data']['records'][1]['sport_id'] assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
assert activity_1_short_id == data['data']['records'][1]['activity_id'] assert workout_1_short_id == data['data']['records'][1]['workout_id']
assert 'FD' == data['data']['records'][1]['record_type'] assert 'FD' == data['data']['records'][1]['record_type']
assert 7.0 == data['data']['records'][1]['value'] assert 7.0 == data['data']['records'][1]['value']
assert ( assert (
'Wed, 16 May 2018 14:05:00 GMT' 'Wed, 16 May 2018 14:05:00 GMT'
== data['data']['records'][2]['activity_date'] == data['data']['records'][2]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][2]['user'] assert 'test' == data['data']['records'][2]['user']
assert sport_1_cycling.id == data['data']['records'][2]['sport_id'] assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
assert activity_3_short_id == data['data']['records'][2]['activity_id'] assert workout_3_short_id == data['data']['records'][2]['workout_id']
assert 'LD' == data['data']['records'][2]['record_type'] assert 'LD' == data['data']['records'][2]['record_type']
assert '1:06:40' == data['data']['records'][2]['value'] assert '1:06:40' == data['data']['records'][2]['value']
assert ( assert (
'Tue, 15 May 2018 14:05:00 GMT' 'Tue, 15 May 2018 14:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_2_short_id == data['data']['records'][0]['activity_id'] assert workout_2_short_id == data['data']['records'][0]['workout_id']
assert 'MS' == data['data']['records'][3]['record_type'] assert 'MS' == data['data']['records'][3]['record_type']
assert 8.4 == data['data']['records'][3]['value'] assert 8.4 == data['data']['records'][3]['value']
# delete activity 2 => AS and MS record update # delete workout 2 => AS and MS record update
client.delete( client.delete(
f'/api/activities/{activity_2_short_id}', f'/api/workouts/{workout_2_short_id}',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
@ -473,56 +473,56 @@ class TestGetRecords:
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_1_short_id == data['data']['records'][0]['activity_id'] assert workout_1_short_id == data['data']['records'][0]['workout_id']
assert 'AS' == data['data']['records'][0]['record_type'] assert 'AS' == data['data']['records'][0]['record_type']
assert 7.0 == data['data']['records'][0]['value'] assert 7.0 == data['data']['records'][0]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][1]['activity_date'] == data['data']['records'][1]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][1]['user'] assert 'test' == data['data']['records'][1]['user']
assert sport_1_cycling.id == data['data']['records'][1]['sport_id'] assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
assert activity_1_short_id == data['data']['records'][1]['activity_id'] assert workout_1_short_id == data['data']['records'][1]['workout_id']
assert 'FD' == data['data']['records'][1]['record_type'] assert 'FD' == data['data']['records'][1]['record_type']
assert 7.0 == data['data']['records'][1]['value'] assert 7.0 == data['data']['records'][1]['value']
assert ( assert (
'Wed, 16 May 2018 14:05:00 GMT' 'Wed, 16 May 2018 14:05:00 GMT'
== data['data']['records'][2]['activity_date'] == data['data']['records'][2]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][2]['user'] assert 'test' == data['data']['records'][2]['user']
assert sport_1_cycling.id == data['data']['records'][2]['sport_id'] assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
assert activity_3_short_id == data['data']['records'][2]['activity_id'] assert workout_3_short_id == data['data']['records'][2]['workout_id']
assert 'LD' == data['data']['records'][2]['record_type'] assert 'LD' == data['data']['records'][2]['record_type']
assert '1:06:40' == data['data']['records'][2]['value'] assert '1:06:40' == data['data']['records'][2]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][3]['activity_date'] == data['data']['records'][3]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][3]['user'] assert 'test' == data['data']['records'][3]['user']
assert sport_1_cycling.id == data['data']['records'][3]['sport_id'] assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
assert activity_1_short_id == data['data']['records'][3]['activity_id'] assert workout_1_short_id == data['data']['records'][3]['workout_id']
assert 'MS' == data['data']['records'][3]['record_type'] assert 'MS' == data['data']['records'][3]['record_type']
assert 7.0 == data['data']['records'][3]['value'] assert 7.0 == data['data']['records'][3]['value']
# add an activity with the same data as activity 1 except with a # add a workout with the same data as workout 1 except with a
# later date => no change in record # later date => no change in record
response = client.post( response = client.post(
'/api/activities/no_gpx', '/api/workouts/no_gpx',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=1, sport_id=1,
duration=3600, duration=3600,
activity_date='2018-05-20 14:05', workout_date='2018-05-20 14:05',
distance=7, distance=7,
title='Activity test 4', title='Workout test 4',
) )
), ),
headers=dict( headers=dict(
@ -531,7 +531,7 @@ class TestGetRecords:
), ),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
activity_4_short_id = data['data']['activities'][0]['id'] workout_4_short_id = data['data']['workouts'][0]['id']
response = client.get( response = client.get(
'/api/records', '/api/records',
headers=dict( headers=dict(
@ -547,58 +547,58 @@ class TestGetRecords:
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_1_short_id == data['data']['records'][0]['activity_id'] assert workout_1_short_id == data['data']['records'][0]['workout_id']
assert 'AS' == data['data']['records'][0]['record_type'] assert 'AS' == data['data']['records'][0]['record_type']
assert 7.0 == data['data']['records'][0]['value'] assert 7.0 == data['data']['records'][0]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][1]['activity_date'] == data['data']['records'][1]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][1]['user'] assert 'test' == data['data']['records'][1]['user']
assert sport_1_cycling.id == data['data']['records'][1]['sport_id'] assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
assert activity_1_short_id == data['data']['records'][1]['activity_id'] assert workout_1_short_id == data['data']['records'][1]['workout_id']
assert 'FD' == data['data']['records'][1]['record_type'] assert 'FD' == data['data']['records'][1]['record_type']
assert 7.0 == data['data']['records'][1]['value'] assert 7.0 == data['data']['records'][1]['value']
assert ( assert (
'Wed, 16 May 2018 14:05:00 GMT' 'Wed, 16 May 2018 14:05:00 GMT'
== data['data']['records'][2]['activity_date'] == data['data']['records'][2]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][2]['user'] assert 'test' == data['data']['records'][2]['user']
assert sport_1_cycling.id == data['data']['records'][2]['sport_id'] assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
assert activity_3_short_id == data['data']['records'][2]['activity_id'] assert workout_3_short_id == data['data']['records'][2]['workout_id']
assert 'LD' == data['data']['records'][2]['record_type'] assert 'LD' == data['data']['records'][2]['record_type']
assert '1:06:40' == data['data']['records'][2]['value'] assert '1:06:40' == data['data']['records'][2]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][3]['activity_date'] == data['data']['records'][3]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][3]['user'] assert 'test' == data['data']['records'][3]['user']
assert sport_1_cycling.id == data['data']['records'][3]['sport_id'] assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
assert activity_1_short_id == data['data']['records'][3]['activity_id'] assert workout_1_short_id == data['data']['records'][3]['workout_id']
assert 'MS' == data['data']['records'][3]['record_type'] assert 'MS' == data['data']['records'][3]['record_type']
assert 7.0 == data['data']['records'][3]['value'] assert 7.0 == data['data']['records'][3]['value']
# add an activity with the same data as activity 1 except with # add a workout with the same data as workout 1 except with
# an earlier date # an earlier date
# => record update (activity 5 replace activity 1) # => record update (workout 5 replace workout 1)
response = client.post( response = client.post(
'/api/activities/no_gpx', '/api/workouts/no_gpx',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=1, sport_id=1,
duration=3600, duration=3600,
activity_date='2018-05-14 08:05', workout_date='2018-05-14 08:05',
distance=7, distance=7,
title='Activity test 5', title='Workout test 5',
) )
), ),
headers=dict( headers=dict(
@ -607,7 +607,7 @@ class TestGetRecords:
), ),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
activity_5_short_id = data['data']['activities'][0]['id'] workout_5_short_id = data['data']['workouts'][0]['id']
response = client.get( response = client.get(
'/api/records', '/api/records',
headers=dict( headers=dict(
@ -623,68 +623,68 @@ class TestGetRecords:
assert ( assert (
'Mon, 14 May 2018 08:05:00 GMT' 'Mon, 14 May 2018 08:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_5_short_id == data['data']['records'][0]['activity_id'] assert workout_5_short_id == data['data']['records'][0]['workout_id']
assert 'AS' == data['data']['records'][0]['record_type'] assert 'AS' == data['data']['records'][0]['record_type']
assert 7.0 == data['data']['records'][0]['value'] assert 7.0 == data['data']['records'][0]['value']
assert ( assert (
'Mon, 14 May 2018 08:05:00 GMT' 'Mon, 14 May 2018 08:05:00 GMT'
== data['data']['records'][1]['activity_date'] == data['data']['records'][1]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][1]['user'] assert 'test' == data['data']['records'][1]['user']
assert sport_1_cycling.id == data['data']['records'][1]['sport_id'] assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
assert activity_5_short_id == data['data']['records'][1]['activity_id'] assert workout_5_short_id == data['data']['records'][1]['workout_id']
assert 'FD' == data['data']['records'][1]['record_type'] assert 'FD' == data['data']['records'][1]['record_type']
assert 7.0 == data['data']['records'][1]['value'] assert 7.0 == data['data']['records'][1]['value']
assert ( assert (
'Wed, 16 May 2018 14:05:00 GMT' 'Wed, 16 May 2018 14:05:00 GMT'
== data['data']['records'][2]['activity_date'] == data['data']['records'][2]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][2]['user'] assert 'test' == data['data']['records'][2]['user']
assert sport_1_cycling.id == data['data']['records'][2]['sport_id'] assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
assert activity_3_short_id == data['data']['records'][2]['activity_id'] assert workout_3_short_id == data['data']['records'][2]['workout_id']
assert 'LD' == data['data']['records'][2]['record_type'] assert 'LD' == data['data']['records'][2]['record_type']
assert '1:06:40' == data['data']['records'][2]['value'] assert '1:06:40' == data['data']['records'][2]['value']
assert ( assert (
'Mon, 14 May 2018 08:05:00 GMT' 'Mon, 14 May 2018 08:05:00 GMT'
== data['data']['records'][3]['activity_date'] == data['data']['records'][3]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][3]['user'] assert 'test' == data['data']['records'][3]['user']
assert sport_1_cycling.id == data['data']['records'][3]['sport_id'] assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
assert activity_5_short_id == data['data']['records'][3]['activity_id'] assert workout_5_short_id == data['data']['records'][3]['workout_id']
assert 'MS' == data['data']['records'][3]['record_type'] assert 'MS' == data['data']['records'][3]['record_type']
assert 7.0 == data['data']['records'][3]['value'] assert 7.0 == data['data']['records'][3]['value']
# delete all activities - no more records # delete all workouts - no more records
client.delete( client.delete(
f'/api/activities/{activity_1_short_id}', f'/api/workouts/{workout_1_short_id}',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
), ),
) )
client.delete( client.delete(
f'/api/activities/{activity_3_short_id}', f'/api/workouts/{workout_3_short_id}',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
), ),
) )
client.delete( client.delete(
f'/api/activities/{activity_4_short_id}', f'/api/workouts/{workout_4_short_id}',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
), ),
) )
client.delete( client.delete(
f'/api/activities/{activity_5_short_id}', f'/api/workouts/{workout_5_short_id}',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
@ -717,15 +717,15 @@ class TestGetRecords:
content_type='application/json', content_type='application/json',
) )
response = client.post( response = client.post(
'/api/activities/no_gpx', '/api/workouts/no_gpx',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=1, sport_id=1,
duration=3600, duration=3600,
activity_date='2018-05-14 14:05', workout_date='2018-05-14 14:05',
distance=7, distance=7,
title='Activity test 1', title='Workout test 1',
) )
), ),
headers=dict( headers=dict(
@ -734,17 +734,17 @@ class TestGetRecords:
), ),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
activity_1_short_id = data['data']['activities'][0]['id'] workout_1_short_id = data['data']['workouts'][0]['id']
response = client.post( response = client.post(
'/api/activities/no_gpx', '/api/workouts/no_gpx',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=2, sport_id=2,
duration=3600, duration=3600,
activity_date='2018-05-16 16:05', workout_date='2018-05-16 16:05',
distance=20, distance=20,
title='Activity test 2', title='Workout test 2',
) )
), ),
headers=dict( headers=dict(
@ -753,17 +753,17 @@ class TestGetRecords:
), ),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
activity_2_short_id = data['data']['activities'][0]['id'] workout_2_short_id = data['data']['workouts'][0]['id']
client.post( client.post(
'/api/activities/no_gpx', '/api/workouts/no_gpx',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=1, sport_id=1,
duration=3000, duration=3000,
activity_date='2018-05-17 17:05', workout_date='2018-05-17 17:05',
distance=3, distance=3,
title='Activity test 3', title='Workout test 3',
) )
), ),
headers=dict( headers=dict(
@ -772,15 +772,15 @@ class TestGetRecords:
), ),
) )
response = client.post( response = client.post(
'/api/activities/no_gpx', '/api/workouts/no_gpx',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=2, sport_id=2,
duration=3000, duration=3000,
activity_date='2018-05-18 18:05', workout_date='2018-05-18 18:05',
distance=10, distance=10,
title='Activity test 4', title='Workout test 4',
) )
), ),
headers=dict( headers=dict(
@ -789,7 +789,7 @@ class TestGetRecords:
), ),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
activity_4_short_id = data['data']['activities'][0]['id'] workout_4_short_id = data['data']['workouts'][0]['id']
response = client.get( response = client.get(
'/api/records', '/api/records',
headers=dict( headers=dict(
@ -805,86 +805,86 @@ class TestGetRecords:
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_1_short_id == data['data']['records'][0]['activity_id'] assert workout_1_short_id == data['data']['records'][0]['workout_id']
assert 'AS' == data['data']['records'][0]['record_type'] assert 'AS' == data['data']['records'][0]['record_type']
assert 7.0 == data['data']['records'][0]['value'] assert 7.0 == data['data']['records'][0]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][1]['activity_date'] == data['data']['records'][1]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][1]['user'] assert 'test' == data['data']['records'][1]['user']
assert sport_1_cycling.id == data['data']['records'][1]['sport_id'] assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
assert activity_1_short_id == data['data']['records'][1]['activity_id'] assert workout_1_short_id == data['data']['records'][1]['workout_id']
assert 'FD' == data['data']['records'][1]['record_type'] assert 'FD' == data['data']['records'][1]['record_type']
assert 7.0 == data['data']['records'][1]['value'] assert 7.0 == data['data']['records'][1]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][2]['activity_date'] == data['data']['records'][2]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][2]['user'] assert 'test' == data['data']['records'][2]['user']
assert sport_1_cycling.id == data['data']['records'][2]['sport_id'] assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
assert activity_1_short_id == data['data']['records'][2]['activity_id'] assert workout_1_short_id == data['data']['records'][2]['workout_id']
assert 'LD' == data['data']['records'][2]['record_type'] assert 'LD' == data['data']['records'][2]['record_type']
assert '1:00:00' == data['data']['records'][2]['value'] assert '1:00:00' == data['data']['records'][2]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][3]['activity_date'] == data['data']['records'][3]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][3]['user'] assert 'test' == data['data']['records'][3]['user']
assert sport_1_cycling.id == data['data']['records'][3]['sport_id'] assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
assert activity_1_short_id == data['data']['records'][3]['activity_id'] assert workout_1_short_id == data['data']['records'][3]['workout_id']
assert 'MS' == data['data']['records'][3]['record_type'] assert 'MS' == data['data']['records'][3]['record_type']
assert 7.0 == data['data']['records'][3]['value'] assert 7.0 == data['data']['records'][3]['value']
assert ( assert (
'Wed, 16 May 2018 16:05:00 GMT' 'Wed, 16 May 2018 16:05:00 GMT'
== data['data']['records'][4]['activity_date'] == data['data']['records'][4]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][4]['user'] assert 'test' == data['data']['records'][4]['user']
assert sport_2_running.id == data['data']['records'][4]['sport_id'] assert sport_2_running.id == data['data']['records'][4]['sport_id']
assert activity_2_short_id == data['data']['records'][4]['activity_id'] assert workout_2_short_id == data['data']['records'][4]['workout_id']
assert 'AS' == data['data']['records'][4]['record_type'] assert 'AS' == data['data']['records'][4]['record_type']
assert 20.0 == data['data']['records'][4]['value'] assert 20.0 == data['data']['records'][4]['value']
assert ( assert (
'Wed, 16 May 2018 16:05:00 GMT' 'Wed, 16 May 2018 16:05:00 GMT'
== data['data']['records'][5]['activity_date'] == data['data']['records'][5]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][5]['user'] assert 'test' == data['data']['records'][5]['user']
assert sport_2_running.id == data['data']['records'][5]['sport_id'] assert sport_2_running.id == data['data']['records'][5]['sport_id']
assert activity_2_short_id == data['data']['records'][5]['activity_id'] assert workout_2_short_id == data['data']['records'][5]['workout_id']
assert 'FD' == data['data']['records'][5]['record_type'] assert 'FD' == data['data']['records'][5]['record_type']
assert 20.0 == data['data']['records'][5]['value'] assert 20.0 == data['data']['records'][5]['value']
assert ( assert (
'Wed, 16 May 2018 16:05:00 GMT' 'Wed, 16 May 2018 16:05:00 GMT'
== data['data']['records'][6]['activity_date'] == data['data']['records'][6]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][6]['user'] assert 'test' == data['data']['records'][6]['user']
assert sport_2_running.id == data['data']['records'][6]['sport_id'] assert sport_2_running.id == data['data']['records'][6]['sport_id']
assert activity_2_short_id == data['data']['records'][6]['activity_id'] assert workout_2_short_id == data['data']['records'][6]['workout_id']
assert 'LD' == data['data']['records'][6]['record_type'] assert 'LD' == data['data']['records'][6]['record_type']
assert '1:00:00' == data['data']['records'][6]['value'] assert '1:00:00' == data['data']['records'][6]['value']
assert ( assert (
'Wed, 16 May 2018 16:05:00 GMT' 'Wed, 16 May 2018 16:05:00 GMT'
== data['data']['records'][7]['activity_date'] == data['data']['records'][7]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][7]['user'] assert 'test' == data['data']['records'][7]['user']
assert sport_2_running.id == data['data']['records'][7]['sport_id'] assert sport_2_running.id == data['data']['records'][7]['sport_id']
assert activity_2_short_id == data['data']['records'][7]['activity_id'] assert workout_2_short_id == data['data']['records'][7]['workout_id']
assert 'MS' == data['data']['records'][7]['record_type'] assert 'MS' == data['data']['records'][7]['record_type']
assert 20.0 == data['data']['records'][7]['value'] assert 20.0 == data['data']['records'][7]['value']
client.patch( client.patch(
f'/api/activities/{activity_2_short_id}', f'/api/workouts/{workout_2_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict(sport_id=1)), data=json.dumps(dict(sport_id=1)),
headers=dict( headers=dict(
@ -907,80 +907,80 @@ class TestGetRecords:
assert ( assert (
'Wed, 16 May 2018 16:05:00 GMT' 'Wed, 16 May 2018 16:05:00 GMT'
== data['data']['records'][0]['activity_date'] == data['data']['records'][0]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][0]['user'] assert 'test' == data['data']['records'][0]['user']
assert sport_1_cycling.id == data['data']['records'][0]['sport_id'] assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
assert activity_2_short_id == data['data']['records'][0]['activity_id'] assert workout_2_short_id == data['data']['records'][0]['workout_id']
assert 'AS' == data['data']['records'][0]['record_type'] assert 'AS' == data['data']['records'][0]['record_type']
assert 20.0 == data['data']['records'][0]['value'] assert 20.0 == data['data']['records'][0]['value']
assert ( assert (
'Wed, 16 May 2018 16:05:00 GMT' 'Wed, 16 May 2018 16:05:00 GMT'
== data['data']['records'][1]['activity_date'] == data['data']['records'][1]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][1]['user'] assert 'test' == data['data']['records'][1]['user']
assert sport_1_cycling.id == data['data']['records'][1]['sport_id'] assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
assert activity_2_short_id == data['data']['records'][1]['activity_id'] assert workout_2_short_id == data['data']['records'][1]['workout_id']
assert 'FD' == data['data']['records'][1]['record_type'] assert 'FD' == data['data']['records'][1]['record_type']
assert 20.0 == data['data']['records'][1]['value'] assert 20.0 == data['data']['records'][1]['value']
assert ( assert (
'Mon, 14 May 2018 14:05:00 GMT' 'Mon, 14 May 2018 14:05:00 GMT'
== data['data']['records'][2]['activity_date'] == data['data']['records'][2]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][2]['user'] assert 'test' == data['data']['records'][2]['user']
assert sport_1_cycling.id == data['data']['records'][2]['sport_id'] assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
assert activity_1_short_id == data['data']['records'][2]['activity_id'] assert workout_1_short_id == data['data']['records'][2]['workout_id']
assert 'LD' == data['data']['records'][2]['record_type'] assert 'LD' == data['data']['records'][2]['record_type']
assert '1:00:00' == data['data']['records'][2]['value'] assert '1:00:00' == data['data']['records'][2]['value']
assert ( assert (
'Wed, 16 May 2018 16:05:00 GMT' 'Wed, 16 May 2018 16:05:00 GMT'
== data['data']['records'][3]['activity_date'] == data['data']['records'][3]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][3]['user'] assert 'test' == data['data']['records'][3]['user']
assert sport_1_cycling.id == data['data']['records'][3]['sport_id'] assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
assert activity_2_short_id == data['data']['records'][3]['activity_id'] assert workout_2_short_id == data['data']['records'][3]['workout_id']
assert 'MS' == data['data']['records'][3]['record_type'] assert 'MS' == data['data']['records'][3]['record_type']
assert 20.0 == data['data']['records'][3]['value'] assert 20.0 == data['data']['records'][3]['value']
assert ( assert (
'Fri, 18 May 2018 18:05:00 GMT' 'Fri, 18 May 2018 18:05:00 GMT'
== data['data']['records'][4]['activity_date'] == data['data']['records'][4]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][4]['user'] assert 'test' == data['data']['records'][4]['user']
assert sport_2_running.id == data['data']['records'][4]['sport_id'] assert sport_2_running.id == data['data']['records'][4]['sport_id']
assert activity_4_short_id == data['data']['records'][4]['activity_id'] assert workout_4_short_id == data['data']['records'][4]['workout_id']
assert 'AS' == data['data']['records'][4]['record_type'] assert 'AS' == data['data']['records'][4]['record_type']
assert 12.0 == data['data']['records'][4]['value'] assert 12.0 == data['data']['records'][4]['value']
assert ( assert (
'Fri, 18 May 2018 18:05:00 GMT' 'Fri, 18 May 2018 18:05:00 GMT'
== data['data']['records'][5]['activity_date'] == data['data']['records'][5]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][5]['user'] assert 'test' == data['data']['records'][5]['user']
assert sport_2_running.id == data['data']['records'][5]['sport_id'] assert sport_2_running.id == data['data']['records'][5]['sport_id']
assert activity_4_short_id == data['data']['records'][5]['activity_id'] assert workout_4_short_id == data['data']['records'][5]['workout_id']
assert 'FD' == data['data']['records'][5]['record_type'] assert 'FD' == data['data']['records'][5]['record_type']
assert 10.0 == data['data']['records'][5]['value'] assert 10.0 == data['data']['records'][5]['value']
assert ( assert (
'Fri, 18 May 2018 18:05:00 GMT' 'Fri, 18 May 2018 18:05:00 GMT'
== data['data']['records'][6]['activity_date'] == data['data']['records'][6]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][6]['user'] assert 'test' == data['data']['records'][6]['user']
assert sport_2_running.id == data['data']['records'][6]['sport_id'] assert sport_2_running.id == data['data']['records'][6]['sport_id']
assert activity_4_short_id == data['data']['records'][6]['activity_id'] assert workout_4_short_id == data['data']['records'][6]['workout_id']
assert 'LD' == data['data']['records'][6]['record_type'] assert 'LD' == data['data']['records'][6]['record_type']
assert '0:50:00' == data['data']['records'][6]['value'] assert '0:50:00' == data['data']['records'][6]['value']
assert ( assert (
'Fri, 18 May 2018 18:05:00 GMT' 'Fri, 18 May 2018 18:05:00 GMT'
== data['data']['records'][7]['activity_date'] == data['data']['records'][7]['workout_date']
) # noqa ) # noqa
assert 'test' == data['data']['records'][7]['user'] assert 'test' == data['data']['records'][7]['user']
assert sport_2_running.id == data['data']['records'][7]['sport_id'] assert sport_2_running.id == data['data']['records'][7]['sport_id']
assert activity_4_short_id == data['data']['records'][7]['activity_id'] assert workout_4_short_id == data['data']['records'][7]['workout_id']
assert 'MS' == data['data']['records'][7]['record_type'] assert 'MS' == data['data']['records'][7]['record_type']
assert 12.0 == data['data']['records'][7]['value'] assert 12.0 == data['data']['records'][7]['value']

View File

@ -1,7 +1,7 @@
import datetime import datetime
from fittrackee.activities.models import Activity, Record, Sport
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Record, Sport, Workout
from flask import Flask from flask import Flask
@ -11,27 +11,27 @@ class TestRecordModel:
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
record_ld = Record.query.filter_by( record_ld = Record.query.filter_by(
user_id=activity_cycling_user_1.user_id, user_id=workout_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id, sport_id=workout_cycling_user_1.sport_id,
record_type='LD', record_type='LD',
).first() ).first()
assert 'test' == record_ld.user.username assert 'test' == record_ld.user.username
assert 1 == record_ld.sport_id assert 1 == record_ld.sport_id
assert 1 == record_ld.activity_id assert 1 == record_ld.workout_id
assert 'LD' == record_ld.record_type assert 'LD' == record_ld.record_type
assert '2018-01-01 00:00:00' == str(record_ld.activity_date) assert '2018-01-01 00:00:00' == str(record_ld.workout_date)
assert '<Record Cycling - LD - 2018-01-01>' == str(record_ld) assert '<Record Cycling - LD - 2018-01-01>' == str(record_ld)
record_serialize = record_ld.serialize() record_serialize = record_ld.serialize()
assert 'id' in record_serialize assert 'id' in record_serialize
assert 'user' in record_serialize assert 'user' in record_serialize
assert 'sport_id' in record_serialize assert 'sport_id' in record_serialize
assert 'activity_id' in record_serialize assert 'workout_id' in record_serialize
assert 'record_type' in record_serialize assert 'record_type' in record_serialize
assert 'activity_date' in record_serialize assert 'workout_date' in record_serialize
assert 'value' in record_serialize assert 'value' in record_serialize
def test_record_model_with_none_value( def test_record_model_with_none_value(
@ -39,19 +39,19 @@ class TestRecordModel:
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
record_ld = Record.query.filter_by( record_ld = Record.query.filter_by(
user_id=activity_cycling_user_1.user_id, user_id=workout_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id, sport_id=workout_cycling_user_1.sport_id,
record_type='LD', record_type='LD',
).first() ).first()
record_ld.value = None record_ld.value = None
assert 'test' == record_ld.user.username assert 'test' == record_ld.user.username
assert 1 == record_ld.sport_id assert 1 == record_ld.sport_id
assert 1 == record_ld.activity_id assert 1 == record_ld.workout_id
assert 'LD' == record_ld.record_type assert 'LD' == record_ld.record_type
assert '2018-01-01 00:00:00' == str(record_ld.activity_date) assert '2018-01-01 00:00:00' == str(record_ld.workout_date)
assert '<Record Cycling - LD - 2018-01-01>' == str(record_ld) assert '<Record Cycling - LD - 2018-01-01>' == str(record_ld)
assert record_ld.value is None assert record_ld.value is None
@ -63,11 +63,11 @@ class TestRecordModel:
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
record_as = Record.query.filter_by( record_as = Record.query.filter_by(
user_id=activity_cycling_user_1.user_id, user_id=workout_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id, sport_id=workout_cycling_user_1.sport_id,
record_type='AS', record_type='AS',
).first() ).first()
@ -84,11 +84,11 @@ class TestRecordModel:
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
record_fd = Record.query.filter_by( record_fd = Record.query.filter_by(
user_id=activity_cycling_user_1.user_id, user_id=workout_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id, sport_id=workout_cycling_user_1.sport_id,
record_type='FD', record_type='FD',
).first() ).first()
@ -105,11 +105,11 @@ class TestRecordModel:
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
record_ld = Record.query.filter_by( record_ld = Record.query.filter_by(
user_id=activity_cycling_user_1.user_id, user_id=workout_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id, sport_id=workout_cycling_user_1.sport_id,
record_type='LD', record_type='LD',
).first() ).first()
@ -126,11 +126,11 @@ class TestRecordModel:
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
record_ld = Record.query.filter_by( record_ld = Record.query.filter_by(
user_id=activity_cycling_user_1.user_id, user_id=workout_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id, sport_id=workout_cycling_user_1.sport_id,
record_type='LD', record_type='LD',
).first() ).first()
record_ld.value = datetime.timedelta(seconds=0) record_ld.value = datetime.timedelta(seconds=0)
@ -148,11 +148,11 @@ class TestRecordModel:
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
record_ms = Record.query.filter_by( record_ms = Record.query.filter_by(
user_id=activity_cycling_user_1.user_id, user_id=workout_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id, sport_id=workout_cycling_user_1.sport_id,
record_type='MS', record_type='MS',
).first() ).first()

View File

@ -1,7 +1,7 @@
import json import json
from fittrackee.activities.models import Activity, Sport
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout
from flask import Flask from flask import Flask
expected_sport_1_cycling_result = { expected_sport_1_cycling_result = {
@ -11,7 +11,7 @@ expected_sport_1_cycling_result = {
'is_active': True, 'is_active': True,
} }
expected_sport_1_cycling_admin_result = expected_sport_1_cycling_result.copy() expected_sport_1_cycling_admin_result = expected_sport_1_cycling_result.copy()
expected_sport_1_cycling_admin_result['has_activities'] = False expected_sport_1_cycling_admin_result['has_workouts'] = False
expected_sport_2_running_result = { expected_sport_2_running_result = {
'id': 2, 'id': 2,
@ -20,7 +20,7 @@ expected_sport_2_running_result = {
'is_active': True, 'is_active': True,
} }
expected_sport_2_running_admin_result = expected_sport_2_running_result.copy() expected_sport_2_running_admin_result = expected_sport_2_running_result.copy()
expected_sport_2_running_admin_result['has_activities'] = False expected_sport_2_running_admin_result['has_workouts'] = False
expected_sport_1_cycling_inactive_result = { expected_sport_1_cycling_inactive_result = {
'id': 1, 'id': 1,
@ -31,7 +31,7 @@ expected_sport_1_cycling_inactive_result = {
expected_sport_1_cycling_inactive_admin_result = ( expected_sport_1_cycling_inactive_admin_result = (
expected_sport_1_cycling_inactive_result.copy() expected_sport_1_cycling_inactive_result.copy()
) )
expected_sport_1_cycling_inactive_admin_result['has_activities'] = False expected_sport_1_cycling_inactive_admin_result['has_workouts'] = False
class TestGetSports: class TestGetSports:
@ -266,7 +266,7 @@ class TestUpdateSport:
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['sports']) == 1 assert len(data['data']['sports']) == 1
assert data['data']['sports'][0]['is_active'] is False assert data['data']['sports'][0]['is_active'] is False
assert data['data']['sports'][0]['has_activities'] is False assert data['data']['sports'][0]['has_workouts'] is False
def test_it_enables_a_sport( def test_it_enables_a_sport(
self, app: Flask, user_1_admin: User, sport_1_cycling: Sport self, app: Flask, user_1_admin: User, sport_1_cycling: Sport
@ -296,14 +296,14 @@ class TestUpdateSport:
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['sports']) == 1 assert len(data['data']['sports']) == 1
assert data['data']['sports'][0]['is_active'] is True assert data['data']['sports'][0]['is_active'] is True
assert data['data']['sports'][0]['has_activities'] is False assert data['data']['sports'][0]['has_workouts'] is False
def test_it_disables_a_sport_with_activities( def test_it_disables_a_sport_with_workouts(
self, self,
app: Flask, app: Flask,
user_1_admin: User, user_1_admin: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -329,14 +329,14 @@ class TestUpdateSport:
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['sports']) == 1 assert len(data['data']['sports']) == 1
assert data['data']['sports'][0]['is_active'] is False assert data['data']['sports'][0]['is_active'] is False
assert data['data']['sports'][0]['has_activities'] is True assert data['data']['sports'][0]['has_workouts'] is True
def test_it_enables_a_sport_with_activities( def test_it_enables_a_sport_with_workouts(
self, self,
app: Flask, app: Flask,
user_1_admin: User, user_1_admin: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
sport_1_cycling.is_active = False sport_1_cycling.is_active = False
client = app.test_client() client = app.test_client()
@ -363,7 +363,7 @@ class TestUpdateSport:
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['sports']) == 1 assert len(data['data']['sports']) == 1
assert data['data']['sports'][0]['is_active'] is True assert data['data']['sports'][0]['is_active'] is True
assert data['data']['sports'][0]['has_activities'] is True assert data['data']['sports'][0]['has_workouts'] is True
def test_returns_error_if_user_has_no_admin_rights( def test_returns_error_if_user_has_no_admin_rights(
self, app: Flask, user_1: User, sport_1_cycling: Sport self, app: Flask, user_1: User, sport_1_cycling: Sport

View File

@ -1,7 +1,7 @@
from typing import Dict, Optional from typing import Dict, Optional
from fittrackee.activities.models import Activity, Sport
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout
from flask import Flask from flask import Flask
@ -22,24 +22,24 @@ class TestSportModel:
def test_sport_model(self, app: Flask, sport_1_cycling: Sport) -> None: def test_sport_model(self, app: Flask, sport_1_cycling: Sport) -> None:
serialized_sport = self.assert_sport_model(sport_1_cycling) serialized_sport = self.assert_sport_model(sport_1_cycling)
assert 'has_activities' not in serialized_sport assert 'has_workouts' not in serialized_sport
def test_sport_model_with_activity( def test_sport_model_with_workout(
self, self,
app: Flask, app: Flask,
sport_1_cycling: Sport, sport_1_cycling: Sport,
user_1: User, user_1: User,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
serialized_sport = self.assert_sport_model(sport_1_cycling) serialized_sport = self.assert_sport_model(sport_1_cycling)
assert 'has_activities' not in serialized_sport assert 'has_workouts' not in serialized_sport
def test_sport_model_with_activity_as_admin( def test_sport_model_with_workout_as_admin(
self, self,
app: Flask, app: Flask,
sport_1_cycling: Sport, sport_1_cycling: Sport,
user_1: User, user_1: User,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
serialized_sport = self.assert_sport_model(sport_1_cycling, True) serialized_sport = self.assert_sport_model(sport_1_cycling, True)
assert serialized_sport['has_activities'] is True assert serialized_sport['has_workouts'] is True

View File

@ -1,12 +1,12 @@
import json import json
from fittrackee.activities.models import Activity, Sport
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout
from flask import Flask from flask import Flask
class TestGetStatsByTime: class TestGetStatsByTime:
def test_it_gets_no_stats_when_user_has_no_activities( def test_it_gets_no_stats_when_user_has_no_workouts(
self, app: Flask, user_1: User self, app: Flask, user_1: User
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
@ -58,8 +58,8 @@ class TestGetStatsByTime:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -90,8 +90,8 @@ class TestGetStatsByTime:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -113,14 +113,14 @@ class TestGetStatsByTime:
assert 'fail' in data['status'] assert 'fail' in data['status']
assert 'Invalid time period.' in data['message'] assert 'Invalid time period.' in data['message']
def test_it_gets_stats_by_time_all_activities( def test_it_gets_stats_by_time_all_workouts(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -143,19 +143,19 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2017': { '2017': {
'1': { '1': {
'nb_activities': 2, 'nb_workouts': 2,
'total_distance': 15.0, 'total_distance': 15.0,
'total_duration': 4480, 'total_duration': 4480,
} }
}, },
'2018': { '2018': {
'1': { '1': {
'nb_activities': 5, 'nb_workouts': 5,
'total_distance': 39.0, 'total_distance': 39.0,
'total_duration': 11624, 'total_duration': 11624,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
@ -168,8 +168,8 @@ class TestGetStatsByTime:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -192,12 +192,12 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2018': { '2018': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 8.0, 'total_distance': 8.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
@ -210,8 +210,8 @@ class TestGetStatsByTime:
user_1_paris: User, user_1_paris: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -235,12 +235,12 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2018': { '2018': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 8.0, 'total_distance': 8.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
@ -253,8 +253,8 @@ class TestGetStatsByTime:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -277,19 +277,19 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2017': { '2017': {
'1': { '1': {
'nb_activities': 2, 'nb_workouts': 2,
'total_distance': 15.0, 'total_distance': 15.0,
'total_duration': 4480, 'total_duration': 4480,
} }
}, },
'2018': { '2018': {
'1': { '1': {
'nb_activities': 5, 'nb_workouts': 5,
'total_distance': 39.0, 'total_distance': 39.0,
'total_duration': 11624, 'total_duration': 11624,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
@ -302,8 +302,8 @@ class TestGetStatsByTime:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -326,12 +326,12 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2018': { '2018': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 8.0, 'total_distance': 8.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
@ -344,8 +344,8 @@ class TestGetStatsByTime:
user_1_paris: User, user_1_paris: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -368,12 +368,12 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2018': { '2018': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 8.0, 'total_distance': 8.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
@ -386,8 +386,8 @@ class TestGetStatsByTime:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -410,47 +410,47 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2017-03': { '2017-03': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 5.0, 'total_distance': 5.0,
'total_duration': 1024, 'total_duration': 1024,
} }
}, },
'2017-06': { '2017-06': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 3456, 'total_duration': 3456,
} }
}, },
'2018-01': { '2018-01': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 1024, 'total_duration': 1024,
} }
}, },
'2018-02': { '2018-02': {
'1': { '1': {
'nb_activities': 2, 'nb_workouts': 2,
'total_distance': 11.0, 'total_distance': 11.0,
'total_duration': 1600, 'total_duration': 1600,
} }
}, },
'2018-04': { '2018-04': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 8.0, 'total_distance': 8.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
}, },
'2018-05': { '2018-05': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 3000, 'total_duration': 3000,
} }
@ -463,8 +463,8 @@ class TestGetStatsByTime:
user_1_full: User, user_1_full: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -487,47 +487,47 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2017-03': { '2017-03': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 5.0, 'total_distance': 5.0,
'total_duration': 1024, 'total_duration': 1024,
} }
}, },
'2017-06': { '2017-06': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 3456, 'total_duration': 3456,
} }
}, },
'2018-01': { '2018-01': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 1024, 'total_duration': 1024,
} }
}, },
'2018-02': { '2018-02': {
'1': { '1': {
'nb_activities': 2, 'nb_workouts': 2,
'total_distance': 11.0, 'total_distance': 11.0,
'total_duration': 1600, 'total_duration': 1600,
} }
}, },
'2018-04': { '2018-04': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 8.0, 'total_distance': 8.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
}, },
'2018-05': { '2018-05': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 3000, 'total_duration': 3000,
} }
@ -540,8 +540,8 @@ class TestGetStatsByTime:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -564,12 +564,12 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2018-04': { '2018-04': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 8.0, 'total_distance': 8.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
@ -582,8 +582,8 @@ class TestGetStatsByTime:
user_1_full: User, user_1_full: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -606,47 +606,47 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2017-03-19': { '2017-03-19': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 5.0, 'total_distance': 5.0,
'total_duration': 1024, 'total_duration': 1024,
} }
}, },
'2017-05-28': { '2017-05-28': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 3456, 'total_duration': 3456,
} }
}, },
'2017-12-31': { '2017-12-31': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 1024, 'total_duration': 1024,
} }
}, },
'2018-02-18': { '2018-02-18': {
'1': { '1': {
'nb_activities': 2, 'nb_workouts': 2,
'total_distance': 11.0, 'total_distance': 11.0,
'total_duration': 1600, 'total_duration': 1600,
} }
}, },
'2018-04-01': { '2018-04-01': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 8.0, 'total_distance': 8.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
}, },
'2018-05-06': { '2018-05-06': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 3000, 'total_duration': 3000,
} }
@ -659,8 +659,8 @@ class TestGetStatsByTime:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -683,12 +683,12 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2018-04-01': { '2018-04-01': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 8.0, 'total_distance': 8.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
@ -701,8 +701,8 @@ class TestGetStatsByTime:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -725,47 +725,47 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2017-03-20': { '2017-03-20': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 5.0, 'total_distance': 5.0,
'total_duration': 1024, 'total_duration': 1024,
} }
}, },
'2017-05-29': { '2017-05-29': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 3456, 'total_duration': 3456,
} }
}, },
'2018-01-01': { '2018-01-01': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 1024, 'total_duration': 1024,
} }
}, },
'2018-02-19': { '2018-02-19': {
'1': { '1': {
'nb_activities': 2, 'nb_workouts': 2,
'total_distance': 11.0, 'total_distance': 11.0,
'total_duration': 1600, 'total_duration': 1600,
} }
}, },
'2018-03-26': { '2018-03-26': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 8.0, 'total_distance': 8.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
}, },
'2018-05-07': { '2018-05-07': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 10.0, 'total_distance': 10.0,
'total_duration': 3000, 'total_duration': 3000,
} }
@ -778,8 +778,8 @@ class TestGetStatsByTime:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -802,12 +802,12 @@ class TestGetStatsByTime:
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'2018-03-26': { '2018-03-26': {
'1': { '1': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 8.0, 'total_distance': 8.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
@ -822,8 +822,8 @@ class TestGetStatsBySport:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -845,12 +845,12 @@ class TestGetStatsBySport:
assert 'success' in data['status'] assert 'success' in data['status']
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'1': { '1': {
'nb_activities': 7, 'nb_workouts': 7,
'total_distance': 54.0, 'total_distance': 54.0,
'total_duration': 16104, 'total_duration': 16104,
}, },
'2': { '2': {
'nb_activities': 1, 'nb_workouts': 1,
'total_distance': 12.0, 'total_distance': 12.0,
'total_duration': 6000, 'total_duration': 6000,
}, },
@ -862,8 +862,8 @@ class TestGetStatsBySport:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -885,7 +885,7 @@ class TestGetStatsBySport:
assert 'success' in data['status'] assert 'success' in data['status']
assert data['data']['statistics'] == { assert data['data']['statistics'] == {
'1': { '1': {
'nb_activities': 7, 'nb_workouts': 7,
'total_distance': 54.0, 'total_distance': 54.0,
'total_duration': 16104, 'total_duration': 16104,
} }
@ -897,8 +897,8 @@ class TestGetStatsBySport:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -926,8 +926,8 @@ class TestGetStatsBySport:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -955,8 +955,8 @@ class TestGetStatsBySport:
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
seven_activities_user_1: Activity, seven_workouts_user_1: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -983,7 +983,7 @@ class TestGetStatsBySport:
class TestGetAllStats: class TestGetAllStats:
def test_it_returns_all_stats_when_users_have_no_activities( def test_it_returns_all_stats_when_users_have_no_workouts(
self, app: Flask, user_1_admin: User, user_2: User self, app: Flask, user_1_admin: User, user_2: User
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
@ -1006,12 +1006,12 @@ class TestGetAllStats:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert data['data']['activities'] == 0 assert data['data']['workouts'] == 0
assert data['data']['sports'] == 0 assert data['data']['sports'] == 0
assert data['data']['users'] == 2 assert data['data']['users'] == 2
assert 'uploads_dir_size' in data['data'] assert 'uploads_dir_size' in data['data']
def test_it_gets_app_all_stats_with_activities( def test_it_gets_app_all_stats_with_workouts(
self, self,
app: Flask, app: Flask,
user_1_admin: User, user_1_admin: User,
@ -1019,9 +1019,9 @@ class TestGetAllStats:
user_3: User, user_3: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
activity_cycling_user_2: Activity, workout_cycling_user_2: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -1043,7 +1043,7 @@ class TestGetAllStats:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert data['data']['activities'] == 3 assert data['data']['workouts'] == 3
assert data['data']['sports'] == 2 assert data['data']['sports'] == 2
assert data['data']['users'] == 3 assert data['data']['users'] == 3
assert 'uploads_dir_size' in data['data'] assert 'uploads_dir_size' in data['data']
@ -1056,9 +1056,9 @@ class TestGetAllStats:
user_3: User, user_3: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
activity_cycling_user_2: Activity, workout_cycling_user_2: Workout,
activity_running_user_1: Activity, workout_running_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(

View File

@ -1,59 +1,59 @@
import json import json
from typing import Dict from typing import Dict
from fittrackee.activities.models import Activity, Sport
from fittrackee.activities.utils_id import decode_short_id
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout
from fittrackee.workouts.utils_id import decode_short_id
from flask import Flask from flask import Flask
from .utils import get_random_short_id, post_an_activity from .utils import get_random_short_id, post_an_workout
def assert_activity_data_with_gpx(data: Dict, sport_id: int) -> None: def assert_workout_data_with_gpx(data: Dict, sport_id: int) -> None:
assert 'creation_date' in data['data']['activities'][0] assert 'creation_date' in data['data']['workouts'][0]
assert ( assert (
'Tue, 13 Mar 2018 12:44:45 GMT' 'Tue, 13 Mar 2018 12:44:45 GMT'
== data['data']['activities'][0]['activity_date'] == data['data']['workouts'][0]['workout_date']
) )
assert 'test' == data['data']['activities'][0]['user'] assert 'test' == data['data']['workouts'][0]['user']
assert '0:04:10' == data['data']['activities'][0]['duration'] assert '0:04:10' == data['data']['workouts'][0]['duration']
assert data['data']['activities'][0]['ascent'] == 0.4 assert data['data']['workouts'][0]['ascent'] == 0.4
assert data['data']['activities'][0]['ave_speed'] == 4.61 assert data['data']['workouts'][0]['ave_speed'] == 4.61
assert data['data']['activities'][0]['descent'] == 23.4 assert data['data']['workouts'][0]['descent'] == 23.4
assert data['data']['activities'][0]['distance'] == 0.32 assert data['data']['workouts'][0]['distance'] == 0.32
assert data['data']['activities'][0]['max_alt'] == 998.0 assert data['data']['workouts'][0]['max_alt'] == 998.0
assert data['data']['activities'][0]['max_speed'] == 5.12 assert data['data']['workouts'][0]['max_speed'] == 5.12
assert data['data']['activities'][0]['min_alt'] == 975.0 assert data['data']['workouts'][0]['min_alt'] == 975.0
assert data['data']['activities'][0]['moving'] == '0:04:10' assert data['data']['workouts'][0]['moving'] == '0:04:10'
assert data['data']['activities'][0]['pauses'] is None assert data['data']['workouts'][0]['pauses'] is None
assert data['data']['activities'][0]['with_gpx'] is True assert data['data']['workouts'][0]['with_gpx'] is True
records = data['data']['activities'][0]['records'] records = data['data']['workouts'][0]['records']
assert len(records) == 4 assert len(records) == 4
assert records[0]['sport_id'] == sport_id assert records[0]['sport_id'] == sport_id
assert records[0]['activity_id'] == data['data']['activities'][0]['id'] assert records[0]['workout_id'] == data['data']['workouts'][0]['id']
assert records[0]['record_type'] == 'MS' assert records[0]['record_type'] == 'MS'
assert records[0]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT' assert records[0]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
assert records[0]['value'] == 5.12 assert records[0]['value'] == 5.12
assert records[1]['sport_id'] == sport_id assert records[1]['sport_id'] == sport_id
assert records[1]['activity_id'] == data['data']['activities'][0]['id'] assert records[1]['workout_id'] == data['data']['workouts'][0]['id']
assert records[1]['record_type'] == 'LD' assert records[1]['record_type'] == 'LD'
assert records[1]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT' assert records[1]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
assert records[1]['value'] == '0:04:10' assert records[1]['value'] == '0:04:10'
assert records[2]['sport_id'] == sport_id assert records[2]['sport_id'] == sport_id
assert records[2]['activity_id'] == data['data']['activities'][0]['id'] assert records[2]['workout_id'] == data['data']['workouts'][0]['id']
assert records[2]['record_type'] == 'FD' assert records[2]['record_type'] == 'FD'
assert records[2]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT' assert records[2]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
assert records[2]['value'] == 0.32 assert records[2]['value'] == 0.32
assert records[3]['sport_id'] == sport_id assert records[3]['sport_id'] == sport_id
assert records[3]['activity_id'] == data['data']['activities'][0]['id'] assert records[3]['workout_id'] == data['data']['workouts'][0]['id']
assert records[3]['record_type'] == 'AS' assert records[3]['record_type'] == 'AS'
assert records[3]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT' assert records[3]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
assert records[3]['value'] == 4.61 assert records[3]['value'] == 4.61
class TestEditActivityWithGpx: class TestEditWorkoutWithGpx:
def test_it_updates_title_for_an_activity_with_gpx( def test_it_updates_title_for_an_workout_with_gpx(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
@ -61,25 +61,25 @@ class TestEditActivityWithGpx:
sport_2_running: Sport, sport_2_running: Sport,
gpx_file: str, gpx_file: str,
) -> None: ) -> None:
token, activity_short_id = post_an_activity(app, gpx_file) token, workout_short_id = post_an_workout(app, gpx_file)
client = app.test_client() client = app.test_client()
response = client.patch( response = client.patch(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict(sport_id=2, title="Activity test")), data=json.dumps(dict(sport_id=2, title="Workout test")),
headers=dict(Authorization=f'Bearer {token}'), headers=dict(Authorization=f'Bearer {token}'),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['activities']) == 1 assert len(data['data']['workouts']) == 1
assert sport_2_running.id == data['data']['activities'][0]['sport_id'] assert sport_2_running.id == data['data']['workouts'][0]['sport_id']
assert data['data']['activities'][0]['title'] == 'Activity test' assert data['data']['workouts'][0]['title'] == 'Workout test'
assert_activity_data_with_gpx(data, sport_2_running.id) assert_workout_data_with_gpx(data, sport_2_running.id)
def test_it_adds_notes_for_an_activity_with_gpx( def test_it_adds_notes_for_an_workout_with_gpx(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
@ -87,11 +87,11 @@ class TestEditActivityWithGpx:
sport_2_running: Sport, sport_2_running: Sport,
gpx_file: str, gpx_file: str,
) -> None: ) -> None:
token, activity_short_id = post_an_activity(app, gpx_file) token, workout_short_id = post_an_workout(app, gpx_file)
client = app.test_client() client = app.test_client()
response = client.patch( response = client.patch(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict(notes="test notes")), data=json.dumps(dict(notes="test notes")),
headers=dict(Authorization=f'Bearer {token}'), headers=dict(Authorization=f'Bearer {token}'),
@ -100,11 +100,11 @@ class TestEditActivityWithGpx:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['activities']) == 1 assert len(data['data']['workouts']) == 1
assert data['data']['activities'][0]['title'] == 'just an activity' assert data['data']['workouts'][0]['title'] == 'just a workout'
assert data['data']['activities'][0]['notes'] == 'test notes' assert data['data']['workouts'][0]['notes'] == 'test notes'
def test_it_raises_403_when_editing_an_activity_from_different_user( def test_it_raises_403_when_editing_an_workout_from_different_user(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
@ -113,7 +113,7 @@ class TestEditActivityWithGpx:
sport_2_running: Sport, sport_2_running: Sport,
gpx_file: str, gpx_file: str,
) -> None: ) -> None:
_, activity_short_id = post_an_activity(app, gpx_file) _, workout_short_id = post_an_workout(app, gpx_file)
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
'/api/auth/login', '/api/auth/login',
@ -122,9 +122,9 @@ class TestEditActivityWithGpx:
) )
response = client.patch( response = client.patch(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict(sport_id=2, title="Activity test")), data=json.dumps(dict(sport_id=2, title="Workout test")),
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
@ -144,11 +144,11 @@ class TestEditActivityWithGpx:
sport_2_running: Sport, sport_2_running: Sport,
gpx_file: str, gpx_file: str,
) -> None: ) -> None:
token, activity_short_id = post_an_activity(app, gpx_file) token, workout_short_id = post_an_workout(app, gpx_file)
client = app.test_client() client = app.test_client()
response = client.patch( response = client.patch(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict(sport_id=2)), data=json.dumps(dict(sport_id=2)),
headers=dict(Authorization=f'Bearer {token}'), headers=dict(Authorization=f'Bearer {token}'),
@ -157,19 +157,19 @@ class TestEditActivityWithGpx:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['activities']) == 1 assert len(data['data']['workouts']) == 1
assert sport_2_running.id == data['data']['activities'][0]['sport_id'] assert sport_2_running.id == data['data']['workouts'][0]['sport_id']
assert data['data']['activities'][0]['title'] == 'just an activity' assert data['data']['workouts'][0]['title'] == 'just a workout'
assert_activity_data_with_gpx(data, sport_2_running.id) assert_workout_data_with_gpx(data, sport_2_running.id)
def test_it_returns_400_if_payload_is_empty( def test_it_returns_400_if_payload_is_empty(
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:
token, activity_short_id = post_an_activity(app, gpx_file) token, workout_short_id = post_an_workout(app, gpx_file)
client = app.test_client() client = app.test_client()
response = client.patch( response = client.patch(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict()), data=json.dumps(dict()),
headers=dict(Authorization=f'Bearer {token}'), headers=dict(Authorization=f'Bearer {token}'),
@ -183,11 +183,11 @@ class TestEditActivityWithGpx:
def test_it_raises_500_if_sport_does_not_exists( def test_it_raises_500_if_sport_does_not_exists(
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:
token, activity_short_id = post_an_activity(app, gpx_file) token, workout_short_id = post_an_workout(app, gpx_file)
client = app.test_client() client = app.test_client()
response = client.patch( response = client.patch(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict(sport_id=2)), data=json.dumps(dict(sport_id=2)),
headers=dict(Authorization=f'Bearer {token}'), headers=dict(Authorization=f'Bearer {token}'),
@ -202,16 +202,16 @@ class TestEditActivityWithGpx:
) )
class TestEditActivityWithoutGpx: class TestEditWorkoutWithoutGpx:
def test_it_updates_an_activity_wo_gpx( def test_it_updates_an_workout_wo_gpx(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
activity_short_id = activity_cycling_user_1.short_id workout_short_id = workout_cycling_user_1.short_id
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
'/api/auth/login', '/api/auth/login',
@ -220,15 +220,15 @@ class TestEditActivityWithoutGpx:
) )
response = client.patch( response = client.patch(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=2, sport_id=2,
duration=3600, duration=3600,
activity_date='2018-05-15 15:05', workout_date='2018-05-15 15:05',
distance=8, distance=8,
title='Activity test', title='Workout test',
) )
), ),
headers=dict( headers=dict(
@ -241,62 +241,62 @@ class TestEditActivityWithoutGpx:
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['activities']) == 1 assert len(data['data']['workouts']) == 1
assert 'creation_date' in data['data']['activities'][0] assert 'creation_date' in data['data']['workouts'][0]
assert ( assert (
data['data']['activities'][0]['activity_date'] data['data']['workouts'][0]['workout_date']
== 'Tue, 15 May 2018 15:05:00 GMT' == 'Tue, 15 May 2018 15:05:00 GMT'
) )
assert data['data']['activities'][0]['user'] == 'test' assert data['data']['workouts'][0]['user'] == 'test'
assert data['data']['activities'][0]['sport_id'] == sport_2_running.id assert data['data']['workouts'][0]['sport_id'] == sport_2_running.id
assert data['data']['activities'][0]['duration'] == '1:00:00' assert data['data']['workouts'][0]['duration'] == '1:00:00'
assert data['data']['activities'][0]['title'] == 'Activity test' assert data['data']['workouts'][0]['title'] == 'Workout test'
assert data['data']['activities'][0]['ascent'] is None assert data['data']['workouts'][0]['ascent'] is None
assert data['data']['activities'][0]['ave_speed'] == 8.0 assert data['data']['workouts'][0]['ave_speed'] == 8.0
assert data['data']['activities'][0]['descent'] is None assert data['data']['workouts'][0]['descent'] is None
assert data['data']['activities'][0]['distance'] == 8.0 assert data['data']['workouts'][0]['distance'] == 8.0
assert data['data']['activities'][0]['max_alt'] is None assert data['data']['workouts'][0]['max_alt'] is None
assert data['data']['activities'][0]['max_speed'] == 8.0 assert data['data']['workouts'][0]['max_speed'] == 8.0
assert data['data']['activities'][0]['min_alt'] is None assert data['data']['workouts'][0]['min_alt'] is None
assert data['data']['activities'][0]['moving'] == '1:00:00' assert data['data']['workouts'][0]['moving'] == '1:00:00'
assert data['data']['activities'][0]['pauses'] is None assert data['data']['workouts'][0]['pauses'] is None
assert data['data']['activities'][0]['with_gpx'] is False assert data['data']['workouts'][0]['with_gpx'] is False
assert data['data']['activities'][0]['map'] is None assert data['data']['workouts'][0]['map'] is None
assert data['data']['activities'][0]['weather_start'] is None assert data['data']['workouts'][0]['weather_start'] is None
assert data['data']['activities'][0]['weather_end'] is None assert data['data']['workouts'][0]['weather_end'] is None
assert data['data']['activities'][0]['notes'] is None assert data['data']['workouts'][0]['notes'] is None
records = data['data']['activities'][0]['records'] records = data['data']['workouts'][0]['records']
assert len(records) == 4 assert len(records) == 4
assert records[0]['sport_id'] == sport_2_running.id assert records[0]['sport_id'] == sport_2_running.id
assert records[0]['activity_id'] == activity_short_id assert records[0]['workout_id'] == workout_short_id
assert records[0]['record_type'] == 'MS' assert records[0]['record_type'] == 'MS'
assert records[0]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT' assert records[0]['workout_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
assert records[0]['value'] == 8.0 assert records[0]['value'] == 8.0
assert records[1]['sport_id'] == sport_2_running.id assert records[1]['sport_id'] == sport_2_running.id
assert records[1]['activity_id'] == activity_short_id assert records[1]['workout_id'] == workout_short_id
assert records[1]['record_type'] == 'LD' assert records[1]['record_type'] == 'LD'
assert records[1]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT' assert records[1]['workout_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
assert records[1]['value'] == '1:00:00' assert records[1]['value'] == '1:00:00'
assert records[2]['sport_id'] == sport_2_running.id assert records[2]['sport_id'] == sport_2_running.id
assert records[2]['activity_id'] == activity_short_id assert records[2]['workout_id'] == workout_short_id
assert records[2]['record_type'] == 'FD' assert records[2]['record_type'] == 'FD'
assert records[2]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT' assert records[2]['workout_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
assert records[2]['value'] == 8.0 assert records[2]['value'] == 8.0
assert records[3]['sport_id'] == sport_2_running.id assert records[3]['sport_id'] == sport_2_running.id
assert records[3]['activity_id'] == activity_short_id assert records[3]['workout_id'] == workout_short_id
assert records[3]['record_type'] == 'AS' assert records[3]['record_type'] == 'AS'
assert records[3]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT' assert records[3]['workout_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
assert records[3]['value'] == 8.0 assert records[3]['value'] == 8.0
def test_it_adds_notes_to_an_activity_wo_gpx( def test_it_adds_notes_to_an_workout_wo_gpx(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
activity_short_id = activity_cycling_user_1.short_id workout_short_id = workout_cycling_user_1.short_id
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
'/api/auth/login', '/api/auth/login',
@ -305,7 +305,7 @@ class TestEditActivityWithoutGpx:
) )
response = client.patch( response = client.patch(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict(notes='test notes')), data=json.dumps(dict(notes='test notes')),
headers=dict( headers=dict(
@ -317,61 +317,61 @@ class TestEditActivityWithoutGpx:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['activities']) == 1 assert len(data['data']['workouts']) == 1
assert 'creation_date' in data['data']['activities'][0] assert 'creation_date' in data['data']['workouts'][0]
assert ( assert (
data['data']['activities'][0]['activity_date'] data['data']['workouts'][0]['workout_date']
== 'Mon, 01 Jan 2018 00:00:00 GMT' == 'Mon, 01 Jan 2018 00:00:00 GMT'
) )
assert data['data']['activities'][0]['user'] == 'test' assert data['data']['workouts'][0]['user'] == 'test'
assert data['data']['activities'][0]['sport_id'] == sport_1_cycling.id assert data['data']['workouts'][0]['sport_id'] == sport_1_cycling.id
assert data['data']['activities'][0]['duration'] == '1:00:00' assert data['data']['workouts'][0]['duration'] == '1:00:00'
assert data['data']['activities'][0]['title'] is None assert data['data']['workouts'][0]['title'] is None
assert data['data']['activities'][0]['ascent'] is None assert data['data']['workouts'][0]['ascent'] is None
assert data['data']['activities'][0]['ave_speed'] == 10.0 assert data['data']['workouts'][0]['ave_speed'] == 10.0
assert data['data']['activities'][0]['descent'] is None assert data['data']['workouts'][0]['descent'] is None
assert data['data']['activities'][0]['distance'] == 10.0 assert data['data']['workouts'][0]['distance'] == 10.0
assert data['data']['activities'][0]['max_alt'] is None assert data['data']['workouts'][0]['max_alt'] is None
assert data['data']['activities'][0]['max_speed'] == 10.0 assert data['data']['workouts'][0]['max_speed'] == 10.0
assert data['data']['activities'][0]['min_alt'] is None assert data['data']['workouts'][0]['min_alt'] is None
assert data['data']['activities'][0]['moving'] == '1:00:00' assert data['data']['workouts'][0]['moving'] == '1:00:00'
assert data['data']['activities'][0]['pauses'] is None assert data['data']['workouts'][0]['pauses'] is None
assert data['data']['activities'][0]['with_gpx'] is False assert data['data']['workouts'][0]['with_gpx'] is False
assert data['data']['activities'][0]['map'] is None assert data['data']['workouts'][0]['map'] is None
assert data['data']['activities'][0]['weather_start'] is None assert data['data']['workouts'][0]['weather_start'] is None
assert data['data']['activities'][0]['weather_end'] is None assert data['data']['workouts'][0]['weather_end'] is None
assert data['data']['activities'][0]['notes'] == 'test notes' assert data['data']['workouts'][0]['notes'] == 'test notes'
records = data['data']['activities'][0]['records'] records = data['data']['workouts'][0]['records']
assert len(records) == 4 assert len(records) == 4
assert records[0]['sport_id'] == sport_1_cycling.id assert records[0]['sport_id'] == sport_1_cycling.id
assert records[0]['activity_id'] == activity_short_id assert records[0]['workout_id'] == workout_short_id
assert records[0]['record_type'] == 'MS' assert records[0]['record_type'] == 'MS'
assert records[0]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT' assert records[0]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
assert records[0]['value'] == 10.0 assert records[0]['value'] == 10.0
assert records[1]['sport_id'] == sport_1_cycling.id assert records[1]['sport_id'] == sport_1_cycling.id
assert records[1]['activity_id'] == activity_short_id assert records[1]['workout_id'] == workout_short_id
assert records[1]['record_type'] == 'LD' assert records[1]['record_type'] == 'LD'
assert records[1]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT' assert records[1]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
assert records[1]['value'] == '1:00:00' assert records[1]['value'] == '1:00:00'
assert records[2]['sport_id'] == sport_1_cycling.id assert records[2]['sport_id'] == sport_1_cycling.id
assert records[2]['activity_id'] == activity_short_id assert records[2]['workout_id'] == workout_short_id
assert records[2]['record_type'] == 'FD' assert records[2]['record_type'] == 'FD'
assert records[2]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT' assert records[2]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
assert records[2]['value'] == 10.0 assert records[2]['value'] == 10.0
assert records[3]['sport_id'] == sport_1_cycling.id assert records[3]['sport_id'] == sport_1_cycling.id
assert records[3]['activity_id'] == activity_short_id assert records[3]['workout_id'] == workout_short_id
assert records[3]['record_type'] == 'AS' assert records[3]['record_type'] == 'AS'
assert records[3]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT' assert records[3]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
assert records[3]['value'] == 10.0 assert records[3]['value'] == 10.0
def test_returns_403_when_editing_an_activity_wo_gpx_from_different_user( def test_returns_403_when_editing_an_workout_wo_gpx_from_different_user(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
user_2: User, user_2: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_2: Activity, workout_cycling_user_2: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -381,15 +381,15 @@ class TestEditActivityWithoutGpx:
) )
response = client.patch( response = client.patch(
f'/api/activities/{activity_cycling_user_2.short_id}', f'/api/workouts/{workout_cycling_user_2.short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=2, sport_id=2,
duration=3600, duration=3600,
activity_date='2018-05-15 15:05', workout_date='2018-05-15 15:05',
distance=8, distance=8,
title='Activity test', title='Workout test',
) )
), ),
headers=dict( headers=dict(
@ -403,15 +403,15 @@ class TestEditActivityWithoutGpx:
assert 'error' in data['status'] assert 'error' in data['status']
assert 'You do not have permissions.' in data['message'] assert 'You do not have permissions.' in data['message']
def test_it_updates_an_activity_wo_gpx_with_timezone( def test_it_updates_an_workout_wo_gpx_with_timezone(
self, self,
app: Flask, app: Flask,
user_1_paris: User, user_1_paris: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
activity_short_id = activity_cycling_user_1.short_id workout_short_id = workout_cycling_user_1.short_id
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
'/api/auth/login', '/api/auth/login',
@ -420,15 +420,15 @@ class TestEditActivityWithoutGpx:
) )
response = client.patch( response = client.patch(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=2, sport_id=2,
duration=3600, duration=3600,
activity_date='2018-05-15 15:05', workout_date='2018-05-15 15:05',
distance=8, distance=8,
title='Activity test', title='Workout test',
) )
), ),
headers=dict( headers=dict(
@ -440,59 +440,59 @@ class TestEditActivityWithoutGpx:
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['activities']) == 1 assert len(data['data']['workouts']) == 1
assert 'creation_date' in data['data']['activities'][0] assert 'creation_date' in data['data']['workouts'][0]
assert ( assert (
data['data']['activities'][0]['activity_date'] data['data']['workouts'][0]['workout_date']
== 'Tue, 15 May 2018 13:05:00 GMT' == 'Tue, 15 May 2018 13:05:00 GMT'
) )
assert data['data']['activities'][0]['user'] == 'test' assert data['data']['workouts'][0]['user'] == 'test'
assert data['data']['activities'][0]['sport_id'] == sport_2_running.id assert data['data']['workouts'][0]['sport_id'] == sport_2_running.id
assert data['data']['activities'][0]['duration'] == '1:00:00' assert data['data']['workouts'][0]['duration'] == '1:00:00'
assert data['data']['activities'][0]['title'] == 'Activity test' assert data['data']['workouts'][0]['title'] == 'Workout test'
assert data['data']['activities'][0]['ascent'] is None assert data['data']['workouts'][0]['ascent'] is None
assert data['data']['activities'][0]['ave_speed'] == 8.0 assert data['data']['workouts'][0]['ave_speed'] == 8.0
assert data['data']['activities'][0]['descent'] is None assert data['data']['workouts'][0]['descent'] is None
assert data['data']['activities'][0]['distance'] == 8.0 assert data['data']['workouts'][0]['distance'] == 8.0
assert data['data']['activities'][0]['max_alt'] is None assert data['data']['workouts'][0]['max_alt'] is None
assert data['data']['activities'][0]['max_speed'] == 8.0 assert data['data']['workouts'][0]['max_speed'] == 8.0
assert data['data']['activities'][0]['min_alt'] is None assert data['data']['workouts'][0]['min_alt'] is None
assert data['data']['activities'][0]['moving'] == '1:00:00' assert data['data']['workouts'][0]['moving'] == '1:00:00'
assert data['data']['activities'][0]['pauses'] is None assert data['data']['workouts'][0]['pauses'] is None
assert data['data']['activities'][0]['with_gpx'] is False assert data['data']['workouts'][0]['with_gpx'] is False
records = data['data']['activities'][0]['records'] records = data['data']['workouts'][0]['records']
assert len(records) == 4 assert len(records) == 4
assert records[0]['sport_id'] == sport_2_running.id assert records[0]['sport_id'] == sport_2_running.id
assert records[0]['activity_id'] == activity_short_id assert records[0]['workout_id'] == workout_short_id
assert records[0]['record_type'] == 'MS' assert records[0]['record_type'] == 'MS'
assert records[0]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT' assert records[0]['workout_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
assert records[0]['value'] == 8.0 assert records[0]['value'] == 8.0
assert records[1]['sport_id'] == sport_2_running.id assert records[1]['sport_id'] == sport_2_running.id
assert records[1]['activity_id'] == activity_short_id assert records[1]['workout_id'] == workout_short_id
assert records[1]['record_type'] == 'LD' assert records[1]['record_type'] == 'LD'
assert records[1]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT' assert records[1]['workout_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
assert records[1]['value'] == '1:00:00' assert records[1]['value'] == '1:00:00'
assert records[2]['sport_id'] == sport_2_running.id assert records[2]['sport_id'] == sport_2_running.id
assert records[2]['activity_id'] == activity_short_id assert records[2]['workout_id'] == workout_short_id
assert records[2]['record_type'] == 'FD' assert records[2]['record_type'] == 'FD'
assert records[2]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT' assert records[2]['workout_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
assert records[2]['value'] == 8.0 assert records[2]['value'] == 8.0
assert records[3]['sport_id'] == sport_2_running.id assert records[3]['sport_id'] == sport_2_running.id
assert records[3]['activity_id'] == activity_short_id assert records[3]['workout_id'] == workout_short_id
assert records[3]['record_type'] == 'AS' assert records[3]['record_type'] == 'AS'
assert records[3]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT' assert records[3]['workout_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
assert records[3]['value'] == 8.0 assert records[3]['value'] == 8.0
def test_it_updates_only_sport_and_distance_an_activity_wo_gpx( def test_it_updates_only_sport_and_distance_an_workout_wo_gpx(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
sport_2_running: Sport, sport_2_running: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
activity_short_id = activity_cycling_user_1.short_id workout_short_id = workout_cycling_user_1.short_id
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
'/api/auth/login', '/api/auth/login',
@ -501,7 +501,7 @@ class TestEditActivityWithoutGpx:
) )
response = client.patch( response = client.patch(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict(sport_id=2, distance=20)), data=json.dumps(dict(sport_id=2, distance=20)),
headers=dict( headers=dict(
@ -513,48 +513,48 @@ class TestEditActivityWithoutGpx:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['activities']) == 1 assert len(data['data']['workouts']) == 1
assert 'creation_date' in data['data']['activities'][0] assert 'creation_date' in data['data']['workouts'][0]
assert ( assert (
data['data']['activities'][0]['activity_date'] data['data']['workouts'][0]['workout_date']
== 'Mon, 01 Jan 2018 00:00:00 GMT' == 'Mon, 01 Jan 2018 00:00:00 GMT'
) )
assert data['data']['activities'][0]['user'] == 'test' assert data['data']['workouts'][0]['user'] == 'test'
assert data['data']['activities'][0]['sport_id'] == sport_2_running.id assert data['data']['workouts'][0]['sport_id'] == sport_2_running.id
assert data['data']['activities'][0]['duration'] == '1:00:00' assert data['data']['workouts'][0]['duration'] == '1:00:00'
assert data['data']['activities'][0]['title'] is None assert data['data']['workouts'][0]['title'] is None
assert data['data']['activities'][0]['ascent'] is None assert data['data']['workouts'][0]['ascent'] is None
assert data['data']['activities'][0]['ave_speed'] == 20.0 assert data['data']['workouts'][0]['ave_speed'] == 20.0
assert data['data']['activities'][0]['descent'] is None assert data['data']['workouts'][0]['descent'] is None
assert data['data']['activities'][0]['distance'] == 20.0 assert data['data']['workouts'][0]['distance'] == 20.0
assert data['data']['activities'][0]['max_alt'] is None assert data['data']['workouts'][0]['max_alt'] is None
assert data['data']['activities'][0]['max_speed'] == 20.0 assert data['data']['workouts'][0]['max_speed'] == 20.0
assert data['data']['activities'][0]['min_alt'] is None assert data['data']['workouts'][0]['min_alt'] is None
assert data['data']['activities'][0]['moving'] == '1:00:00' assert data['data']['workouts'][0]['moving'] == '1:00:00'
assert data['data']['activities'][0]['pauses'] is None assert data['data']['workouts'][0]['pauses'] is None
assert data['data']['activities'][0]['with_gpx'] is False assert data['data']['workouts'][0]['with_gpx'] is False
records = data['data']['activities'][0]['records'] records = data['data']['workouts'][0]['records']
assert len(records) == 4 assert len(records) == 4
assert records[0]['sport_id'] == sport_2_running.id assert records[0]['sport_id'] == sport_2_running.id
assert records[0]['activity_id'] == activity_short_id assert records[0]['workout_id'] == workout_short_id
assert records[0]['record_type'] == 'MS' assert records[0]['record_type'] == 'MS'
assert records[0]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT' assert records[0]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
assert records[0]['value'] == 20.0 assert records[0]['value'] == 20.0
assert records[1]['sport_id'] == sport_2_running.id assert records[1]['sport_id'] == sport_2_running.id
assert records[1]['activity_id'] == activity_short_id assert records[1]['workout_id'] == workout_short_id
assert records[1]['record_type'] == 'LD' assert records[1]['record_type'] == 'LD'
assert records[1]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT' assert records[1]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
assert records[1]['value'] == '1:00:00' assert records[1]['value'] == '1:00:00'
assert records[2]['sport_id'] == sport_2_running.id assert records[2]['sport_id'] == sport_2_running.id
assert records[2]['activity_id'] == activity_short_id assert records[2]['workout_id'] == workout_short_id
assert records[2]['record_type'] == 'FD' assert records[2]['record_type'] == 'FD'
assert records[2]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT' assert records[2]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
assert records[2]['value'] == 20.0 assert records[2]['value'] == 20.0
assert records[3]['sport_id'] == sport_2_running.id assert records[3]['sport_id'] == sport_2_running.id
assert records[3]['activity_id'] == activity_short_id assert records[3]['workout_id'] == workout_short_id
assert records[3]['record_type'] == 'AS' assert records[3]['record_type'] == 'AS'
assert records[3]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT' assert records[3]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
assert records[3]['value'] == 20.0 assert records[3]['value'] == 20.0
def test_it_returns_400_if_payload_is_empty( def test_it_returns_400_if_payload_is_empty(
@ -562,7 +562,7 @@ class TestEditActivityWithoutGpx:
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -572,7 +572,7 @@ class TestEditActivityWithoutGpx:
) )
response = client.patch( response = client.patch(
f'/api/activities/{activity_cycling_user_1.short_id}', f'/api/workouts/{workout_cycling_user_1.short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict()), data=json.dumps(dict()),
headers=dict( headers=dict(
@ -591,7 +591,7 @@ class TestEditActivityWithoutGpx:
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -600,13 +600,13 @@ class TestEditActivityWithoutGpx:
content_type='application/json', content_type='application/json',
) )
response = client.patch( response = client.patch(
f'/api/activities/{activity_cycling_user_1.short_id}', f'/api/workouts/{workout_cycling_user_1.short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=1, sport_id=1,
duration=3600, duration=3600,
activity_date='15/2018', workout_date='15/2018',
distance=10, distance=10,
) )
), ),
@ -625,7 +625,7 @@ class TestEditActivityWithoutGpx:
in data['message'] in data['message']
) )
def test_it_returns_404_if_edited_activity_does_not_exists( def test_it_returns_404_if_edited_workout_does_not_exists(
self, app: Flask, user_1: User, sport_1_cycling: Sport self, app: Flask, user_1: User, sport_1_cycling: Sport
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
@ -635,13 +635,13 @@ class TestEditActivityWithoutGpx:
content_type='application/json', content_type='application/json',
) )
response = client.patch( response = client.patch(
f'/api/activities/{get_random_short_id()}', f'/api/workouts/{get_random_short_id()}',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
sport_id=1, sport_id=1,
duration=3600, duration=3600,
activity_date='2018-05-15 14:05', workout_date='2018-05-15 14:05',
distance=10, distance=10,
) )
), ),
@ -654,11 +654,11 @@ class TestEditActivityWithoutGpx:
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 404 assert response.status_code == 404
assert 'not found' in data['status'] assert 'not found' in data['status']
assert len(data['data']['activities']) == 0 assert len(data['data']['workouts']) == 0
class TestRefreshActivityWithGpx: class TestRefreshWorkoutWithGpx:
def test_refresh_an_activity_with_gpx( def test_refresh_an_workout_with_gpx(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
@ -666,17 +666,17 @@ class TestRefreshActivityWithGpx:
sport_2_running: Sport, sport_2_running: Sport,
gpx_file: str, gpx_file: str,
) -> None: ) -> None:
token, activity_short_id = post_an_activity(app, gpx_file) token, workout_short_id = post_an_workout(app, gpx_file)
activity_uuid = decode_short_id(activity_short_id) workout_uuid = decode_short_id(workout_short_id)
client = app.test_client() client = app.test_client()
# Edit some activity data # Edit some workout data
activity = Activity.query.filter_by(uuid=activity_uuid).first() workout = Workout.query.filter_by(uuid=workout_uuid).first()
activity.ascent = 1000 workout.ascent = 1000
activity.min_alt = -100 workout.min_alt = -100
response = client.patch( response = client.patch(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
content_type='application/json', content_type='application/json',
data=json.dumps(dict(refresh=True)), data=json.dumps(dict(refresh=True)),
headers=dict(Authorization=f'Bearer {token}'), headers=dict(Authorization=f'Bearer {token}'),
@ -685,7 +685,7 @@ class TestRefreshActivityWithGpx:
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['activities']) == 1 assert len(data['data']['workouts']) == 1
assert 1 == data['data']['activities'][0]['sport_id'] assert 1 == data['data']['workouts'][0]['sport_id']
assert 0.4 == data['data']['activities'][0]['ascent'] assert 0.4 == data['data']['workouts'][0]['ascent']
assert 975.0 == data['data']['activities'][0]['min_alt'] assert 975.0 == data['data']['workouts'][0]['min_alt']

View File

@ -1,34 +1,34 @@
import json import json
import os import os
from fittrackee.activities.models import Activity, Sport
from fittrackee.activities.utils 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.utils import get_absolute_file_path
from flask import Flask from flask import Flask
from .utils import get_random_short_id, post_an_activity from .utils import get_random_short_id, post_an_workout
def get_gpx_filepath(activity_id: int) -> str: def get_gpx_filepath(workout_id: int) -> str:
activity = Activity.query.filter_by(id=activity_id).first() workout = Workout.query.filter_by(id=workout_id).first()
return activity.gpx return workout.gpx
class TestDeleteActivityWithGpx: class TestDeleteWorkoutWithGpx:
def test_it_deletes_an_activity_with_gpx( def test_it_deletes_an_workout_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:
token, activity_short_id = post_an_activity(app, gpx_file) token, workout_short_id = post_an_workout(app, gpx_file)
client = app.test_client() client = app.test_client()
response = client.delete( response = client.delete(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
headers=dict(Authorization=f'Bearer {token}'), headers=dict(Authorization=f'Bearer {token}'),
) )
assert response.status_code == 204 assert response.status_code == 204
def test_it_returns_403_when_deleting_an_activity_from_different_user( def test_it_returns_403_when_deleting_an_workout_from_different_user(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
@ -36,7 +36,7 @@ class TestDeleteActivityWithGpx:
sport_1_cycling: Sport, sport_1_cycling: Sport,
gpx_file: str, gpx_file: str,
) -> None: ) -> None:
_, activity_short_id = post_an_activity(app, gpx_file) _, workout_short_id = post_an_workout(app, gpx_file)
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
'/api/auth/login', '/api/auth/login',
@ -45,7 +45,7 @@ class TestDeleteActivityWithGpx:
) )
response = client.delete( response = client.delete(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
@ -58,7 +58,7 @@ class TestDeleteActivityWithGpx:
assert 'error' in data['status'] assert 'error' in data['status']
assert 'You do not have permissions.' in data['message'] assert 'You do not have permissions.' in data['message']
def test_it_returns_404_if_activity_does_not_exist( def test_it_returns_404_if_workout_does_not_exist(
self, app: Flask, user_1: User self, app: Flask, user_1: User
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
@ -68,7 +68,7 @@ class TestDeleteActivityWithGpx:
content_type='application/json', content_type='application/json',
) )
response = client.delete( response = client.delete(
f'/api/activities/{get_random_short_id()}', f'/api/workouts/{get_random_short_id()}',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
@ -78,17 +78,17 @@ class TestDeleteActivityWithGpx:
assert response.status_code == 404 assert response.status_code == 404
assert 'not found' in data['status'] assert 'not found' in data['status']
def test_it_returns_500_when_deleting_an_activity_with_gpx_invalid_file( def test_it_returns_500_when_deleting_an_workout_with_gpx_invalid_file(
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:
token, activity_short_id = post_an_activity(app, gpx_file) token, workout_short_id = post_an_workout(app, gpx_file)
client = app.test_client() client = app.test_client()
gpx_filepath = get_gpx_filepath(1) gpx_filepath = get_gpx_filepath(1)
gpx_filepath = get_absolute_file_path(gpx_filepath) gpx_filepath = get_absolute_file_path(gpx_filepath)
os.remove(gpx_filepath) os.remove(gpx_filepath)
response = client.delete( response = client.delete(
f'/api/activities/{activity_short_id}', f'/api/workouts/{workout_short_id}',
headers=dict(Authorization=f'Bearer {token}'), headers=dict(Authorization=f'Bearer {token}'),
) )
@ -102,13 +102,13 @@ class TestDeleteActivityWithGpx:
) )
class TestDeleteActivityWithoutGpx: class TestDeleteWorkoutWithoutGpx:
def test_it_deletes_an_activity_wo_gpx( def test_it_deletes_an_workout_wo_gpx(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -117,7 +117,7 @@ class TestDeleteActivityWithoutGpx:
content_type='application/json', content_type='application/json',
) )
response = client.delete( response = client.delete(
f'/api/activities/{activity_cycling_user_1.short_id}', f'/api/workouts/{workout_cycling_user_1.short_id}',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']
@ -125,13 +125,13 @@ class TestDeleteActivityWithoutGpx:
) )
assert response.status_code == 204 assert response.status_code == 204
def test_it_returns_403_when_deleting_an_activity_from_different_user( def test_it_returns_403_when_deleting_an_workout_from_different_user(
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
user_2: User, user_2: User,
sport_1_cycling: Sport, sport_1_cycling: Sport,
activity_cycling_user_1: Activity, workout_cycling_user_1: Workout,
) -> None: ) -> None:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -140,7 +140,7 @@ class TestDeleteActivityWithoutGpx:
content_type='application/json', content_type='application/json',
) )
response = client.delete( response = client.delete(
f'/api/activities/{activity_cycling_user_1.short_id}', f'/api/workouts/{workout_cycling_user_1.short_id}',
headers=dict( headers=dict(
Authorization='Bearer ' Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token'] + json.loads(resp_login.data.decode())['auth_token']

View File

@ -0,0 +1,73 @@
from uuid import UUID
from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout
from fittrackee.workouts.utils_id import decode_short_id
from flask import Flask
class TestWorkoutModel:
def test_workout_model(
self,
app: Flask,
sport_1_cycling: Sport,
user_1: User,
workout_cycling_user_1: Workout,
) -> None:
workout_cycling_user_1.title = 'Test'
assert 1 == workout_cycling_user_1.id
assert workout_cycling_user_1.uuid is not None
assert 1 == workout_cycling_user_1.user_id
assert 1 == workout_cycling_user_1.sport_id
assert '2018-01-01 00:00:00' == str(
workout_cycling_user_1.workout_date
)
assert 10.0 == float(workout_cycling_user_1.distance)
assert '1:00:00' == str(workout_cycling_user_1.duration)
assert 'Test' == workout_cycling_user_1.title
assert '<Workout \'Cycling\' - 2018-01-01 00:00:00>' == str(
workout_cycling_user_1
)
serialized_workout = workout_cycling_user_1.serialize()
assert isinstance(decode_short_id(serialized_workout['id']), UUID)
assert 'test' == serialized_workout['user']
assert 1 == serialized_workout['sport_id']
assert serialized_workout['title'] == 'Test'
assert 'creation_date' in serialized_workout
assert serialized_workout['modification_date'] is not None
assert str(serialized_workout['workout_date']) == '2018-01-01 00:00:00'
assert serialized_workout['duration'] == '1:00:00'
assert serialized_workout['pauses'] is None
assert serialized_workout['moving'] == '1:00:00'
assert serialized_workout['distance'] == 10.0
assert serialized_workout['max_alt'] is None
assert serialized_workout['descent'] is None
assert serialized_workout['ascent'] is None
assert serialized_workout['max_speed'] == 10.0
assert serialized_workout['ave_speed'] == 10.0
assert serialized_workout['with_gpx'] is False
assert serialized_workout['bounds'] == []
assert serialized_workout['previous_workout'] is None
assert serialized_workout['next_workout'] is None
assert serialized_workout['segments'] == []
assert serialized_workout['records'] != []
assert serialized_workout['map'] is None
assert serialized_workout['weather_start'] is None
assert serialized_workout['weather_end'] is None
assert serialized_workout['notes'] is None
def test_workout_segment_model(
self,
app: Flask,
sport_1_cycling: Sport,
user_1: User,
workout_cycling_user_1: Workout,
workout_cycling_user_1_segment: Workout,
) -> None:
assert (
f'<Segment \'{workout_cycling_user_1_segment.segment_id}\' '
f'for workout \'{workout_cycling_user_1.short_id}\'>'
== str(workout_cycling_user_1_segment)
)

View File

@ -3,7 +3,7 @@ from io import BytesIO
from typing import Tuple from typing import Tuple
from uuid import uuid4 from uuid import uuid4
from fittrackee.activities.utils_id import encode_uuid from fittrackee.workouts.utils_id import encode_uuid
from flask import Flask from flask import Flask
@ -11,7 +11,7 @@ def get_random_short_id() -> str:
return encode_uuid(uuid4()) return encode_uuid(uuid4())
def post_an_activity(app: Flask, gpx_file: str) -> Tuple[str, str]: def post_an_workout(app: Flask, gpx_file: str) -> Tuple[str, str]:
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
'/api/auth/login', '/api/auth/login',
@ -20,7 +20,7 @@ def post_an_activity(app: Flask, gpx_file: str) -> Tuple[str, str]:
) )
token = json.loads(resp_login.data.decode())['auth_token'] token = json.loads(resp_login.data.decode())['auth_token']
response = client.post( response = client.post(
'/api/activities', '/api/workouts',
data=dict( data=dict(
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'), file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
data='{"sport_id": 1}', data='{"sport_id": 1}',
@ -30,4 +30,4 @@ def post_an_activity(app: Flask, gpx_file: str) -> Tuple[str, str]:
), ),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
return token, data['data']['activities'][0]['id'] return token, data['data']['workouts'][0]['id']

View File

@ -18,7 +18,7 @@ from sqlalchemy import exc, or_
from werkzeug.exceptions import RequestEntityTooLarge 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 ..workouts.utils_files import get_absolute_file_path
from .models import User from .models import User
from .utils import ( from .utils import (
authenticate, authenticate,
@ -310,8 +310,8 @@ def get_authenticated_user_profile(
"language": "en", "language": "en",
"last_name": null, "last_name": null,
"location": null, "location": null,
"nb_activities": 6,
"nb_sports": 3, "nb_sports": 3,
"nb_workouts": 6,
"picture": false, "picture": false,
"sports_list": [ "sports_list": [
1, 1,
@ -371,8 +371,8 @@ def edit_user(auth_user_id: int) -> Union[Dict, HttpResponse]:
"language": "en", "language": "en",
"last_name": null, "last_name": null,
"location": null, "location": null,
"nb_activities": 6,
"nb_sports": 3, "nb_sports": 3,
"nb_workouts": 6,
"picture": false, "picture": false,
"sports_list": [ "sports_list": [
1, 1,

View File

@ -9,7 +9,7 @@ from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.sql.expression import select from sqlalchemy.sql.expression import select
from ..activities.models import Activity from ..workouts.models import Workout
from .utils_token import decode_user_token, get_user_token from .utils_token import decode_user_token, get_user_token
BaseModel: DeclarativeMeta = db.Model BaseModel: DeclarativeMeta = db.Model
@ -32,8 +32,8 @@ class User(BaseModel):
timezone = db.Column(db.String(50), nullable=True) timezone = db.Column(db.String(50), nullable=True)
# does the week start Monday? # does the week start Monday?
weekm = db.Column(db.Boolean(50), default=False, nullable=False) weekm = db.Column(db.Boolean(50), default=False, nullable=False)
activities = db.relationship( workouts = db.relationship(
'Activity', lazy=True, backref=db.backref('user', lazy='joined') 'Workout', lazy=True, backref=db.backref('user', lazy='joined')
) )
records = db.relationship( records = db.relationship(
'Record', lazy=True, backref=db.backref('user', lazy='joined') 'Record', lazy=True, backref=db.backref('user', lazy='joined')
@ -90,33 +90,33 @@ class User(BaseModel):
return 'Invalid token. Please log in again.' return 'Invalid token. Please log in again.'
@hybrid_property @hybrid_property
def activities_count(self) -> int: def workouts_count(self) -> int:
return Activity.query.filter(Activity.user_id == self.id).count() return Workout.query.filter(Workout.user_id == self.id).count()
@activities_count.expression # type: ignore @workouts_count.expression # type: ignore
def activities_count(self) -> int: def workouts_count(self) -> int:
return ( return (
select([func.count(Activity.id)]) select([func.count(Workout.id)])
.where(Activity.user_id == self.id) .where(Workout.user_id == self.id)
.label('activities_count') .label('workouts_count')
) )
def serialize(self) -> Dict: def serialize(self) -> Dict:
sports = [] sports = []
total = (0, '0:00:00') total = (0, '0:00:00')
if self.activities_count > 0: # type: ignore if self.workouts_count > 0: # type: ignore
sports = ( sports = (
db.session.query(Activity.sport_id) db.session.query(Workout.sport_id)
.filter(Activity.user_id == self.id) .filter(Workout.user_id == self.id)
.group_by(Activity.sport_id) .group_by(Workout.sport_id)
.order_by(Activity.sport_id) .order_by(Workout.sport_id)
.all() .all()
) )
total = ( total = (
db.session.query( db.session.query(
func.sum(Activity.distance), func.sum(Activity.duration) func.sum(Workout.distance), func.sum(Workout.duration)
) )
.filter(Activity.user_id == self.id) .filter(Workout.user_id == self.id)
.first() .first()
) )
return { return {
@ -133,8 +133,8 @@ class User(BaseModel):
'timezone': self.timezone, 'timezone': self.timezone,
'weekm': self.weekm, 'weekm': self.weekm,
'language': self.language, 'language': self.language,
'nb_activities': self.activities_count,
'nb_sports': len(sports), 'nb_sports': len(sports),
'nb_workouts': self.workouts_count,
'sports_list': [ 'sports_list': [
sport for sportslist in sports for sport in sportslist sport for sportslist in sports for sport in sportslist
], ],

View File

@ -14,8 +14,8 @@ from fittrackee.responses import (
from flask import Blueprint, request, send_file from flask import Blueprint, request, send_file
from sqlalchemy import exc from sqlalchemy import exc
from ..activities.utils_files import get_absolute_file_path from ..workouts.utils_files import get_absolute_file_path
from .models import Activity, User from .models import User, Workout
from .utils import authenticate, authenticate_as_admin from .utils import authenticate, authenticate_as_admin
users_blueprint = Blueprint('users', __name__) users_blueprint = Blueprint('users', __name__)
@ -42,7 +42,7 @@ def get_users(auth_user_id: int) -> Dict:
.. sourcecode:: http .. sourcecode:: http
GET /api/users?order_by=activities_count&par_page=5 HTTP/1.1 GET /api/users?order_by=workouts_count&par_page=5 HTTP/1.1
Content-Type: application/json Content-Type: application/json
**Example response**: **Example response**:
@ -65,8 +65,8 @@ def get_users(auth_user_id: int) -> Dict:
"language": "en", "language": "en",
"last_name": null, "last_name": null,
"location": null, "location": null,
"nb_activities": 6,
"nb_sports": 3, "nb_sports": 3,
"nb_workouts": 6,
"picture": false, "picture": false,
"sports_list": [ "sports_list": [
1, 1,
@ -88,8 +88,8 @@ def get_users(auth_user_id: int) -> Dict:
"language": "fr", "language": "fr",
"last_name": null, "last_name": null,
"location": null, "location": null,
"nb_activities": 0,
"nb_sports": 0, "nb_sports": 0,
"nb_workouts": 0,
"picture": false, "picture": false,
"sports_list": [], "sports_list": [],
"timezone": "Europe/Paris", "timezone": "Europe/Paris",
@ -108,7 +108,7 @@ def get_users(auth_user_id: int) -> Dict:
:query integer per_page: number of users per page (default: 10, max: 50) :query integer per_page: number of users per page (default: 10, max: 50)
:query string q: query on user name :query string q: query on user name
:query string order_by: sorting criteria (``username``, ``created_at``, :query string order_by: sorting criteria (``username``, ``created_at``,
``activities_count``, ``admin``) ``workouts_count``, ``admin``)
:query string order: sorting order (default: ``asc``) :query string order: sorting order (default: ``asc``)
:reqheader Authorization: OAuth 2.0 Bearer Token :reqheader Authorization: OAuth 2.0 Bearer Token
@ -137,11 +137,11 @@ def get_users(auth_user_id: int) -> Dict:
User.username.like('%' + query + '%') if query else True, User.username.like('%' + query + '%') if query else True,
) )
.order_by( .order_by(
User.activities_count.asc() # type: ignore User.workouts_count.asc() # type: ignore
if order_by == 'activities_count' and order == 'asc' if order_by == 'workouts_count' and order == 'asc'
else True, else True,
User.activities_count.desc() # type: ignore User.workouts_count.desc() # type: ignore
if order_by == 'activities_count' and order == 'desc' if order_by == 'workouts_count' and order == 'desc'
else True, else True,
User.username.asc() User.username.asc()
if order_by == 'username' and order == 'asc' if order_by == 'username' and order == 'asc'
@ -212,8 +212,8 @@ def get_single_user(
"language": "en", "language": "en",
"last_name": null, "last_name": null,
"location": null, "location": null,
"nb_activities": 6,
"nb_sports": 3, "nb_sports": 3,
"nb_workouts": 6,
"picture": false, "picture": false,
"sports_list": [ "sports_list": [
1, 1,
@ -328,7 +328,7 @@ def update_user(
"language": "en", "language": "en",
"last_name": null, "last_name": null,
"location": null, "location": null,
"nb_activities": 6, "nb_workouts": 6,
"nb_sports": 3, "nb_sports": 3,
"picture": false, "picture": false,
"sports_list": [ "sports_list": [
@ -443,8 +443,8 @@ def delete_user(
'no other user has admin rights.' 'no other user has admin rights.'
) )
for activity in Activity.query.filter_by(user_id=user.id).all(): for workout in Workout.query.filter_by(user_id=user.id).all():
db.session.delete(activity) db.session.delete(workout)
db.session.flush() db.session.flush()
user_picture = user.picture user_picture = user.picture
db.session.delete(user) db.session.delete(user)
@ -454,7 +454,7 @@ def delete_user(
if os.path.isfile(picture_path): if os.path.isfile(picture_path):
os.remove(picture_path) os.remove(picture_path)
shutil.rmtree( shutil.rmtree(
get_absolute_file_path(f'activities/{user.id}'), get_absolute_file_path(f'workouts/{user.id}'),
ignore_errors=True, ignore_errors=True,
) )
shutil.rmtree( shutil.rmtree(

View File

@ -78,8 +78,8 @@ def verify_extension_and_size(
return InvalidPayloadErrorResponse('No selected file.', 'fail') return InvalidPayloadErrorResponse('No selected file.', 'fail')
allowed_extensions = ( allowed_extensions = (
'ACTIVITY_ALLOWED_EXTENSIONS' 'WORKOUT_ALLOWED_EXTENSIONS'
if file_type == 'activity' if file_type == 'workout'
else 'PICTURE_ALLOWED_EXTENSIONS' else 'PICTURE_ALLOWED_EXTENSIONS'
) )
@ -158,13 +158,13 @@ def authenticate_as_admin(f: Callable) -> Callable:
return decorated_function return decorated_function
def can_view_activity( def can_view_workout(
auth_user_id: int, activity_user_id: int auth_user_id: int, workout_user_id: int
) -> Optional[HttpResponse]: ) -> Optional[HttpResponse]:
""" """
Return error response if user has no right to view activity Return error response if user has no right to view workout
""" """
if auth_user_id != activity_user_id: if auth_user_id != workout_user_id:
return ForbiddenErrorResponse() return ForbiddenErrorResponse()
return None return None

View File

@ -30,7 +30,7 @@ def update_records(
user_id: int, sport_id: int, connection: Connection, session: Session user_id: int, sport_id: int, connection: Connection, session: Session
) -> None: ) -> None:
record_table = Record.__table__ record_table = Record.__table__
new_records = Activity.get_user_activity_records(user_id, sport_id) new_records = Workout.get_user_workout_records(user_id, sport_id)
for record_type, record_data in new_records.items(): for record_type, record_data in new_records.items():
if record_data['record_value']: if record_data['record_value']:
record = Record.query.filter_by( record = Record.query.filter_by(
@ -45,14 +45,14 @@ def update_records(
.where(record_table.c.id == record.id) .where(record_table.c.id == record.id)
.values( .values(
value=value, value=value,
activity_id=record_data['activity'].id, workout_id=record_data['workout'].id,
activity_uuid=record_data['activity'].uuid, workout_uuid=record_data['workout'].uuid,
activity_date=record_data['activity'].activity_date, workout_date=record_data['workout'].workout_date,
) )
) )
else: else:
new_record = Record( new_record = Record(
activity=record_data['activity'], record_type=record_type workout=record_data['workout'], record_type=record_type
) )
new_record.value = record_data['record_value'] # type: ignore new_record.value = record_data['record_value'] # type: ignore
session.add(new_record) session.add(new_record)
@ -66,13 +66,13 @@ def update_records(
class Sport(BaseModel): class Sport(BaseModel):
__tablename__ = "sports" __tablename__ = 'sports'
id = db.Column(db.Integer, primary_key=True, autoincrement=True) id = db.Column(db.Integer, primary_key=True, autoincrement=True)
label = db.Column(db.String(50), unique=True, nullable=False) label = db.Column(db.String(50), unique=True, nullable=False)
img = db.Column(db.String(255), unique=True, nullable=True) img = db.Column(db.String(255), unique=True, nullable=True)
is_active = db.Column(db.Boolean, default=True, nullable=False) is_active = db.Column(db.Boolean, default=True, nullable=False)
activities = db.relationship( workouts = db.relationship(
'Activity', lazy=True, backref=db.backref('sports', lazy='joined') 'Workout', lazy=True, backref=db.backref('sports', lazy='joined')
) )
records = db.relationship( records = db.relationship(
'Record', lazy=True, backref=db.backref('sports', lazy='joined') 'Record', lazy=True, backref=db.backref('sports', lazy='joined')
@ -92,12 +92,12 @@ class Sport(BaseModel):
'is_active': self.is_active, 'is_active': self.is_active,
} }
if is_admin: if is_admin:
serialized_sport['has_activities'] = len(self.activities) > 0 serialized_sport['has_workouts'] = len(self.workouts) > 0
return serialized_sport return serialized_sport
class Activity(BaseModel): class Workout(BaseModel):
__tablename__ = "activities" __tablename__ = 'workouts'
id = db.Column(db.Integer, primary_key=True, autoincrement=True) id = db.Column(db.Integer, primary_key=True, autoincrement=True)
uuid = db.Column( uuid = db.Column(
postgresql.UUID(as_uuid=True), postgresql.UUID(as_uuid=True),
@ -115,7 +115,7 @@ class Activity(BaseModel):
modification_date = db.Column( modification_date = db.Column(
db.DateTime, onupdate=datetime.datetime.utcnow db.DateTime, onupdate=datetime.datetime.utcnow
) )
activity_date = db.Column(db.DateTime, nullable=False) workout_date = db.Column(db.DateTime, nullable=False)
duration = db.Column(db.Interval, nullable=False) duration = db.Column(db.Interval, nullable=False)
pauses = db.Column(db.Interval, nullable=True) pauses = db.Column(db.Interval, nullable=True)
moving = db.Column(db.Interval, nullable=True) moving = db.Column(db.Interval, nullable=True)
@ -133,32 +133,32 @@ class Activity(BaseModel):
weather_end = db.Column(JSON, nullable=True) weather_end = db.Column(JSON, nullable=True)
notes = db.Column(db.String(500), nullable=True) notes = db.Column(db.String(500), nullable=True)
segments = db.relationship( segments = db.relationship(
'ActivitySegment', 'WorkoutSegment',
lazy=True, lazy=True,
cascade='all, delete', cascade='all, delete',
backref=db.backref('activities', lazy='joined', single_parent=True), backref=db.backref('workouts', lazy='joined', single_parent=True),
) )
records = db.relationship( records = db.relationship(
'Record', 'Record',
lazy=True, lazy=True,
cascade='all, delete', cascade='all, delete',
backref=db.backref('activities', lazy='joined', single_parent=True), backref=db.backref('workouts', lazy='joined', single_parent=True),
) )
def __str__(self) -> str: def __str__(self) -> str:
return f'<Activity \'{self.sports.label}\' - {self.activity_date}>' return f'<Workout \'{self.sports.label}\' - {self.workout_date}>'
def __init__( def __init__(
self, self,
user_id: int, user_id: int,
sport_id: int, sport_id: int,
activity_date: datetime.datetime, workout_date: datetime.datetime,
distance: float, distance: float,
duration: datetime.timedelta, duration: datetime.timedelta,
) -> None: ) -> None:
self.user_id = user_id self.user_id = user_id
self.sport_id = sport_id self.sport_id = sport_id
self.activity_date = activity_date self.workout_date = workout_date
self.distance = distance self.distance = distance
self.duration = duration self.duration = duration
@ -178,78 +178,78 @@ class Activity(BaseModel):
max_speed_from = params.get('max_speed_from') if params else None max_speed_from = params.get('max_speed_from') if params else None
max_speed_to = params.get('max_speed_to') if params else None max_speed_to = params.get('max_speed_to') if params else None
sport_id = params.get('sport_id') if params else None sport_id = params.get('sport_id') if params else None
previous_activity = ( previous_workout = (
Activity.query.filter( Workout.query.filter(
Activity.id != self.id, Workout.id != self.id,
Activity.user_id == self.user_id, Workout.user_id == self.user_id,
Activity.sport_id == sport_id if sport_id else True, Workout.sport_id == sport_id if sport_id else True,
Activity.activity_date <= self.activity_date, Workout.workout_date <= self.workout_date,
Activity.activity_date Workout.workout_date
>= datetime.datetime.strptime(date_from, '%Y-%m-%d') >= datetime.datetime.strptime(date_from, '%Y-%m-%d')
if date_from if date_from
else True, else True,
Activity.activity_date Workout.workout_date
<= datetime.datetime.strptime(date_to, '%Y-%m-%d') <= datetime.datetime.strptime(date_to, '%Y-%m-%d')
if date_to if date_to
else True, else True,
Activity.distance >= int(distance_from) Workout.distance >= int(distance_from)
if distance_from if distance_from
else True, else True,
Activity.distance <= int(distance_to) if distance_to else True, Workout.distance <= int(distance_to) if distance_to else True,
Activity.duration >= convert_in_duration(duration_from) Workout.duration >= convert_in_duration(duration_from)
if duration_from if duration_from
else True, else True,
Activity.duration <= convert_in_duration(duration_to) Workout.duration <= convert_in_duration(duration_to)
if duration_to if duration_to
else True, else True,
Activity.ave_speed >= float(ave_speed_from) Workout.ave_speed >= float(ave_speed_from)
if ave_speed_from if ave_speed_from
else True, else True,
Activity.ave_speed <= float(ave_speed_to) Workout.ave_speed <= float(ave_speed_to)
if ave_speed_to if ave_speed_to
else True, else True,
Activity.max_speed >= float(max_speed_from) Workout.max_speed >= float(max_speed_from)
if max_speed_from if max_speed_from
else True, else True,
Activity.max_speed <= float(max_speed_to) Workout.max_speed <= float(max_speed_to)
if max_speed_to if max_speed_to
else True, else True,
) )
.order_by(Activity.activity_date.desc()) .order_by(Workout.workout_date.desc())
.first() .first()
) )
next_activity = ( next_workout = (
Activity.query.filter( Workout.query.filter(
Activity.id != self.id, Workout.id != self.id,
Activity.user_id == self.user_id, Workout.user_id == self.user_id,
Activity.sport_id == sport_id if sport_id else True, Workout.sport_id == sport_id if sport_id else True,
Activity.activity_date >= self.activity_date, Workout.workout_date >= self.workout_date,
Activity.activity_date Workout.workout_date
>= datetime.datetime.strptime(date_from, '%Y-%m-%d') >= datetime.datetime.strptime(date_from, '%Y-%m-%d')
if date_from if date_from
else True, else True,
Activity.activity_date Workout.workout_date
<= datetime.datetime.strptime(date_to, '%Y-%m-%d') <= datetime.datetime.strptime(date_to, '%Y-%m-%d')
if date_to if date_to
else True, else True,
Activity.distance >= int(distance_from) Workout.distance >= int(distance_from)
if distance_from if distance_from
else True, else True,
Activity.distance <= int(distance_to) if distance_to else True, Workout.distance <= int(distance_to) if distance_to else True,
Activity.duration >= convert_in_duration(duration_from) Workout.duration >= convert_in_duration(duration_from)
if duration_from if duration_from
else True, else True,
Activity.duration <= convert_in_duration(duration_to) Workout.duration <= convert_in_duration(duration_to)
if duration_to if duration_to
else True, else True,
Activity.ave_speed >= float(ave_speed_from) Workout.ave_speed >= float(ave_speed_from)
if ave_speed_from if ave_speed_from
else True, else True,
Activity.ave_speed <= float(ave_speed_to) Workout.ave_speed <= float(ave_speed_to)
if ave_speed_to if ave_speed_to
else True, else True,
) )
.order_by(Activity.activity_date.asc()) .order_by(Workout.workout_date.asc())
.first() .first()
) )
return { return {
@ -259,7 +259,7 @@ class Activity(BaseModel):
'title': self.title, 'title': self.title,
'creation_date': self.creation_date, 'creation_date': self.creation_date,
'modification_date': self.modification_date, 'modification_date': self.modification_date,
'activity_date': self.activity_date, 'workout_date': self.workout_date,
'duration': str(self.duration) if self.duration else None, 'duration': str(self.duration) if self.duration else None,
'pauses': str(self.pauses) if self.pauses else None, 'pauses': str(self.pauses) if self.pauses else None,
'moving': str(self.moving) if self.moving else None, 'moving': str(self.moving) if self.moving else None,
@ -274,10 +274,10 @@ class Activity(BaseModel):
'bounds': [float(bound) for bound in self.bounds] 'bounds': [float(bound) for bound in self.bounds]
if self.bounds if self.bounds
else [], # noqa else [], # noqa
'previous_activity': previous_activity.short_id 'previous_workout': previous_workout.short_id
if previous_activity if previous_workout
else None, # noqa else None, # noqa
'next_activity': next_activity.short_id if next_activity else None, 'next_workout': next_workout.short_id if next_workout else None,
'segments': [segment.serialize() for segment in self.segments], 'segments': [segment.serialize() for segment in self.segments],
'records': [record.serialize() for record in self.records], 'records': [record.serialize() for record in self.records],
'map': self.map_id if self.map else None, 'map': self.map_id if self.map else None,
@ -287,7 +287,7 @@ class Activity(BaseModel):
} }
@classmethod @classmethod
def get_user_activity_records( def get_user_workout_records(
cls, user_id: int, sport_id: int, as_integer: Optional[bool] = False cls, user_id: int, sport_id: int, as_integer: Optional[bool] = False
) -> Dict: ) -> Dict:
record_types_columns = { record_types_columns = {
@ -298,55 +298,53 @@ class Activity(BaseModel):
} }
records = {} records = {}
for record_type, column in record_types_columns.items(): for record_type, column in record_types_columns.items():
column_sorted = getattr(getattr(Activity, column), 'desc')() column_sorted = getattr(getattr(Workout, column), 'desc')()
record_activity = ( record_workout = (
Activity.query.filter_by(user_id=user_id, sport_id=sport_id) Workout.query.filter_by(user_id=user_id, sport_id=sport_id)
.order_by(column_sorted, Activity.activity_date) .order_by(column_sorted, Workout.workout_date)
.first() .first()
) )
records[record_type] = dict( records[record_type] = dict(
record_value=( record_value=(
getattr(record_activity, column) getattr(record_workout, column) if record_workout else None
if record_activity
else None
), ),
activity=record_activity, workout=record_workout,
) )
return records return records
@listens_for(Activity, 'after_insert') @listens_for(Workout, 'after_insert')
def on_activity_insert( def on_workout_insert(
mapper: Mapper, connection: Connection, activity: Activity mapper: Mapper, connection: Connection, workout: Workout
) -> None: ) -> None:
@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:
update_records( update_records(
activity.user_id, activity.sport_id, connection, session workout.user_id, workout.sport_id, connection, session
) # noqa ) # noqa
@listens_for(Activity, 'after_update') @listens_for(Workout, 'after_update')
def on_activity_update( def on_workout_update(
mapper: Mapper, connection: Connection, activity: Activity mapper: Mapper, connection: Connection, workout: Workout
) -> None: ) -> None:
if object_session(activity).is_modified( if object_session(workout).is_modified(
activity, include_collections=True workout, include_collections=True
): # noqa ): # noqa
@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:
sports_list = [activity.sport_id] sports_list = [workout.sport_id]
records = Record.query.filter_by(activity_id=activity.id).all() records = Record.query.filter_by(workout_id=workout.id).all()
for rec in records: for rec in records:
if rec.sport_id not in sports_list: if rec.sport_id not in sports_list:
sports_list.append(rec.sport_id) sports_list.append(rec.sport_id)
for sport_id in sports_list: for sport_id in sports_list:
update_records(activity.user_id, sport_id, connection, session) update_records(workout.user_id, sport_id, connection, session)
@listens_for(Activity, 'after_delete') @listens_for(Workout, 'after_delete')
def on_activity_delete( def on_workout_delete(
mapper: Mapper, connection: Connection, old_record: 'Record' mapper: Mapper, connection: Connection, old_record: 'Record'
) -> None: ) -> None:
@listens_for(db.Session, 'after_flush', once=True) @listens_for(db.Session, 'after_flush', once=True)
@ -357,12 +355,12 @@ def on_activity_delete(
os.remove(get_absolute_file_path(old_record.gpx)) os.remove(get_absolute_file_path(old_record.gpx))
class ActivitySegment(BaseModel): class WorkoutSegment(BaseModel):
__tablename__ = "activity_segments" __tablename__ = 'workout_segments'
activity_id = db.Column( workout_id = db.Column(
db.Integer, db.ForeignKey('activities.id'), primary_key=True db.Integer, db.ForeignKey('workouts.id'), primary_key=True
) )
activity_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False) workout_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False)
segment_id = db.Column(db.Integer, primary_key=True) segment_id = db.Column(db.Integer, primary_key=True)
duration = db.Column(db.Interval, nullable=False) duration = db.Column(db.Interval, nullable=False)
pauses = db.Column(db.Interval, nullable=True) pauses = db.Column(db.Interval, nullable=True)
@ -378,19 +376,19 @@ class ActivitySegment(BaseModel):
def __str__(self) -> str: def __str__(self) -> str:
return ( return (
f'<Segment \'{self.segment_id}\' ' f'<Segment \'{self.segment_id}\' '
f'for activity \'{encode_uuid(self.activity_uuid)}\'>' f'for workout \'{encode_uuid(self.workout_uuid)}\'>'
) )
def __init__( def __init__(
self, segment_id: int, activity_id: int, activity_uuid: UUID self, segment_id: int, workout_id: int, workout_uuid: UUID
) -> None: ) -> None:
self.segment_id = segment_id self.segment_id = segment_id
self.activity_id = activity_id self.workout_id = workout_id
self.activity_uuid = activity_uuid self.workout_uuid = workout_uuid
def serialize(self) -> Dict: def serialize(self) -> Dict:
return { return {
'activity_id': encode_uuid(self.activity_uuid), 'workout_id': encode_uuid(self.workout_uuid),
'segment_id': self.segment_id, 'segment_id': self.segment_id,
'duration': str(self.duration) if self.duration else None, 'duration': str(self.duration) if self.duration else None,
'pauses': str(self.pauses) if self.pauses else None, 'pauses': str(self.pauses) if self.pauses else None,
@ -417,28 +415,28 @@ class Record(BaseModel):
sport_id = db.Column( sport_id = db.Column(
db.Integer, db.ForeignKey('sports.id'), nullable=False db.Integer, db.ForeignKey('sports.id'), nullable=False
) )
activity_id = db.Column( workout_id = db.Column(
db.Integer, db.ForeignKey('activities.id'), nullable=False db.Integer, db.ForeignKey('workouts.id'), nullable=False
) )
activity_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False) workout_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False)
record_type = db.Column(Enum(*record_types, name="record_types")) record_type = db.Column(Enum(*record_types, name="record_types"))
activity_date = db.Column(db.DateTime, nullable=False) workout_date = db.Column(db.DateTime, nullable=False)
_value = db.Column("value", db.Integer, nullable=True) _value = db.Column("value", db.Integer, nullable=True)
def __str__(self) -> str: def __str__(self) -> str:
return ( return (
f'<Record {self.sports.label} - ' f'<Record {self.sports.label} - '
f'{self.record_type} - ' f'{self.record_type} - '
f"{self.activity_date.strftime('%Y-%m-%d')}>" f"{self.workout_date.strftime('%Y-%m-%d')}>"
) )
def __init__(self, activity: Activity, record_type: str) -> None: def __init__(self, workout: Workout, record_type: str) -> None:
self.user_id = activity.user_id self.user_id = workout.user_id
self.sport_id = activity.sport_id self.sport_id = workout.sport_id
self.activity_id = activity.id self.workout_id = workout.id
self.activity_uuid = activity.uuid self.workout_uuid = workout.uuid
self.record_type = record_type self.record_type = record_type
self.activity_date = activity.activity_date self.workout_date = workout.workout_date
@hybrid_property @hybrid_property
def value(self) -> Optional[Union[datetime.timedelta, float]]: def value(self) -> Optional[Union[datetime.timedelta, float]]:
@ -467,9 +465,9 @@ class Record(BaseModel):
'id': self.id, 'id': self.id,
'user': self.user.username, 'user': self.user.username,
'sport_id': self.sport_id, 'sport_id': self.sport_id,
'activity_id': encode_uuid(self.activity_uuid), 'workout_id': encode_uuid(self.workout_uuid),
'record_type': self.record_type, 'record_type': self.record_type,
'activity_date': self.activity_date, 'workout_date': self.workout_date,
'value': value, 'value': value,
} }
@ -480,9 +478,9 @@ def on_record_delete(
) -> None: ) -> None:
@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:
activity = old_record.activities workout = old_record.workouts
new_records = Activity.get_user_activity_records( new_records = Workout.get_user_workout_records(
activity.user_id, activity.sport_id workout.user_id, workout.sport_id
) )
for record_type, record_data in new_records.items(): for record_type, record_data in new_records.items():
if ( if (
@ -490,7 +488,7 @@ def on_record_delete(
and record_type == old_record.record_type and record_type == old_record.record_type
): ):
new_record = Record( new_record = Record(
activity=record_data['activity'], record_type=record_type workout=record_data['workout'], record_type=record_type
) )
new_record.value = record_data['record_value'] # type: ignore new_record.value = record_data['record_value'] # type: ignore
session.add(new_record) session.add(new_record)

View File

@ -40,40 +40,40 @@ def get_records(auth_user_id: int) -> Dict:
"data": { "data": {
"records": [ "records": [
{ {
"activity_date": "Sun, 07 Jul 2019 08:00:00 GMT",
"activity_id": "hvYBqYBRa7wwXpaStWR4V2",
"id": 9, "id": 9,
"record_type": "AS", "record_type": "AS",
"sport_id": 1, "sport_id": 1,
"user": "admin", "user": "admin",
"value": 18 "value": 18,
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
}, },
{ {
"activity_date": "Sun, 07 Jul 2019 08:00:00 GMT",
"activity_id": "hvYBqYBRa7wwXpaStWR4V2",
"id": 10, "id": 10,
"record_type": "FD", "record_type": "FD",
"sport_id": 1, "sport_id": 1,
"user": "admin", "user": "admin",
"value": 18 "value": 18,
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
}, },
{ {
"activity_date": "Sun, 07 Jul 2019 08:00:00 GMT",
"activity_id": "hvYBqYBRa7wwXpaStWR4V2",
"id": 11, "id": 11,
"record_type": "LD", "record_type": "LD",
"sport_id": 1, "sport_id": 1,
"user": "admin", "user": "admin",
"value": "1:01:00" "value": "1:01:00",
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
}, },
{ {
"activity_date": "Sun, 07 Jul 2019 08:00:00 GMT",
"activity_id": "hvYBqYBRa7wwXpaStWR4V2",
"id": 12, "id": 12,
"record_type": "MS", "record_type": "MS",
"sport_id": 1, "sport_id": 1,
"user": "admin", "user": "admin",
"value": 18 "value": 18,
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
} }
] ]
}, },

View File

@ -94,42 +94,42 @@ def get_sports(auth_user_id: int) -> Dict:
"data": { "data": {
"sports": [ "sports": [
{ {
"has_activities": true, "has_workouts": true,
"id": 1, "id": 1,
"img": "/img/sports/cycling-sport.png", "img": "/img/sports/cycling-sport.png",
"is_active": true, "is_active": true,
"label": "Cycling (Sport)" "label": "Cycling (Sport)"
}, },
{ {
"has_activities": false, "has_workouts": false,
"id": 2, "id": 2,
"img": "/img/sports/cycling-transport.png", "img": "/img/sports/cycling-transport.png",
"is_active": true, "is_active": true,
"label": "Cycling (Transport)" "label": "Cycling (Transport)"
}, },
{ {
"has_activities": false, "has_workouts": false,
"id": 3, "id": 3,
"img": "/img/sports/hiking.png", "img": "/img/sports/hiking.png",
"is_active": true, "is_active": true,
"label": "Hiking" "label": "Hiking"
}, },
{ {
"has_activities": false, "has_workouts": false,
"id": 4, "id": 4,
"img": "/img/sports/mountain-biking.png", "img": "/img/sports/mountain-biking.png",
"is_active": true, "is_active": true,
"label": "Mountain Biking" "label": "Mountain Biking"
}, },
{ {
"has_activities": false, "has_workouts": false,
"id": 5, "id": 5,
"img": "/img/sports/running.png", "img": "/img/sports/running.png",
"is_active": true, "is_active": true,
"label": "Running" "label": "Running"
}, },
{ {
"has_activities": false, "has_workouts": false,
"id": 6, "id": 6,
"img": "/img/sports/walking.png", "img": "/img/sports/walking.png",
"is_active": true, "is_active": true,
@ -206,7 +206,7 @@ def get_sport(auth_user_id: int, sport_id: int) -> Union[Dict, HttpResponse]:
"data": { "data": {
"sports": [ "sports": [
{ {
"has_activities": false, "has_workouts": false,
"id": 1, "id": 1,
"img": "/img/sports/cycling-sport.png", "img": "/img/sports/cycling-sport.png",
"is_active": true, "is_active": true,
@ -283,7 +283,7 @@ def update_sport(
"data": { "data": {
"sports": [ "sports": [
{ {
"has_activities": false, "has_workouts": false,
"id": 1, "id": 1,
"img": "/img/sports/cycling-sport.png", "img": "/img/sports/cycling-sport.png",
"is_active": false, "is_active": false,

View File

@ -14,18 +14,18 @@ from sqlalchemy import func
from ..users.models import User from ..users.models import User
from ..users.utils import authenticate, authenticate_as_admin from ..users.utils import authenticate, authenticate_as_admin
from .models import Activity, Sport from .models import Sport, Workout
from .utils import get_datetime_with_tz, get_upload_dir_size from .utils import get_datetime_with_tz, get_upload_dir_size
from .utils_format import convert_timedelta_to_integer from .utils_format import convert_timedelta_to_integer
stats_blueprint = Blueprint('stats', __name__) stats_blueprint = Blueprint('stats', __name__)
def get_activities( def get_workouts(
user_name: str, filter_type: str user_name: str, filter_type: str
) -> Union[Dict, HttpResponse]: ) -> Union[Dict, HttpResponse]:
""" """
Return user activities by sport or by time Return user workouts by sport or by time
""" """
try: try:
user = User.query.filter_by(username=user_name).first() user = User.query.filter_by(username=user_name).first()
@ -52,91 +52,89 @@ def get_activities(
if not sport: if not sport:
return NotFoundErrorResponse('Sport does not exist.') return NotFoundErrorResponse('Sport does not exist.')
activities = ( workouts = (
Activity.query.filter( Workout.query.filter(
Activity.user_id == user.id, Workout.user_id == user.id,
Activity.activity_date >= date_from if date_from else True, Workout.workout_date >= date_from if date_from else True,
Activity.activity_date < date_to + timedelta(seconds=1) Workout.workout_date < date_to + timedelta(seconds=1)
if date_to if date_to
else True, else True,
Activity.sport_id == sport_id if sport_id else True, Workout.sport_id == sport_id if sport_id else True,
) )
.order_by(Activity.activity_date.asc()) .order_by(Workout.workout_date.asc())
.all() .all()
) )
activities_list_by_sport = {} workouts_list_by_sport = {}
activities_list_by_time = {} # type: ignore workouts_list_by_time = {} # type: ignore
for activity in activities: for workout in workouts:
if filter_type == 'by_sport': if filter_type == 'by_sport':
sport_id = activity.sport_id sport_id = workout.sport_id
if sport_id not in activities_list_by_sport: if sport_id not in workouts_list_by_sport:
activities_list_by_sport[sport_id] = { workouts_list_by_sport[sport_id] = {
'nb_activities': 0, 'nb_workouts': 0,
'total_distance': 0.0, 'total_distance': 0.0,
'total_duration': 0, 'total_duration': 0,
} }
activities_list_by_sport[sport_id]['nb_activities'] += 1 workouts_list_by_sport[sport_id]['nb_workouts'] += 1
activities_list_by_sport[sport_id]['total_distance'] += float( workouts_list_by_sport[sport_id]['total_distance'] += float(
activity.distance workout.distance
) )
activities_list_by_sport[sport_id][ workouts_list_by_sport[sport_id][
'total_duration' 'total_duration'
] += convert_timedelta_to_integer(activity.moving) ] += convert_timedelta_to_integer(workout.moving)
# filter_type == 'by_time' # filter_type == 'by_time'
else: else:
if time == 'week': if time == 'week':
activity_date = activity.activity_date - timedelta( workout_date = workout.workout_date - timedelta(
days=( days=(
activity.activity_date.isoweekday() workout.workout_date.isoweekday()
if activity.activity_date.isoweekday() < 7 if workout.workout_date.isoweekday() < 7
else 0 else 0
) )
) )
time_period = datetime.strftime(activity_date, "%Y-%m-%d") time_period = datetime.strftime(workout_date, "%Y-%m-%d")
elif time == 'weekm': # week start Monday elif time == 'weekm': # week start Monday
activity_date = activity.activity_date - timedelta( workout_date = workout.workout_date - timedelta(
days=activity.activity_date.weekday() days=workout.workout_date.weekday()
) )
time_period = datetime.strftime(activity_date, "%Y-%m-%d") time_period = datetime.strftime(workout_date, "%Y-%m-%d")
elif time == 'month': elif time == 'month':
time_period = datetime.strftime( time_period = datetime.strftime(
activity.activity_date, "%Y-%m" workout.workout_date, "%Y-%m"
) )
elif time == 'year' or not time: elif time == 'year' or not time:
time_period = datetime.strftime( time_period = datetime.strftime(workout.workout_date, "%Y")
activity.activity_date, "%Y"
)
else: else:
return InvalidPayloadErrorResponse( return InvalidPayloadErrorResponse(
'Invalid time period.', 'fail' 'Invalid time period.', 'fail'
) )
sport_id = activity.sport_id sport_id = workout.sport_id
if time_period not in activities_list_by_time: if time_period not in workouts_list_by_time:
activities_list_by_time[time_period] = {} workouts_list_by_time[time_period] = {}
if sport_id not in activities_list_by_time[time_period]: if sport_id not in workouts_list_by_time[time_period]:
activities_list_by_time[time_period][sport_id] = { workouts_list_by_time[time_period][sport_id] = {
'nb_activities': 0, 'nb_workouts': 0,
'total_distance': 0.0, 'total_distance': 0.0,
'total_duration': 0, 'total_duration': 0,
} }
activities_list_by_time[time_period][sport_id][ workouts_list_by_time[time_period][sport_id][
'nb_activities' 'nb_workouts'
] += 1 ] += 1
activities_list_by_time[time_period][sport_id][ workouts_list_by_time[time_period][sport_id][
'total_distance' 'total_distance'
] += float(activity.distance) ] += float(workout.distance)
activities_list_by_time[time_period][sport_id][ workouts_list_by_time[time_period][sport_id][
'total_duration' 'total_duration'
] += convert_timedelta_to_integer(activity.moving) ] += convert_timedelta_to_integer(workout.moving)
return { return {
'status': 'success', 'status': 'success',
'data': { 'data': {
'statistics': activities_list_by_sport 'statistics': workouts_list_by_sport
if filter_type == 'by_sport' if filter_type == 'by_sport'
else activities_list_by_time else workouts_list_by_time
}, },
} }
except Exception as e: except Exception as e:
@ -145,11 +143,11 @@ def get_activities(
@stats_blueprint.route('/stats/<user_name>/by_time', methods=['GET']) @stats_blueprint.route('/stats/<user_name>/by_time', methods=['GET'])
@authenticate @authenticate
def get_activities_by_time( def get_workouts_by_time(
auth_user_id: int, user_name: str auth_user_id: int, user_name: str
) -> Union[Dict, HttpResponse]: ) -> Union[Dict, HttpResponse]:
""" """
Get activities statistics for a user by time Get workouts statistics for a user by time
**Example requests**: **Example requests**:
@ -163,7 +161,8 @@ def get_activities_by_time(
.. sourcecode:: http .. sourcecode:: http
GET /api/stats/admin/by_time?from=2018-01-01&to=2018-06-30&time=week HTTP/1.1 GET /api/stats/admin/by_time?from=2018-01-01&to=2018-06-30&time=week
HTTP/1.1
**Example responses**: **Example responses**:
@ -179,19 +178,19 @@ def get_activities_by_time(
"statistics": { "statistics": {
"2017": { "2017": {
"3": { "3": {
"nb_activities": 2, "nb_workouts": 2,
"total_distance": 15.282, "total_distance": 15.282,
"total_duration": 12341 "total_duration": 12341
} }
}, },
"2019": { "2019": {
"1": { "1": {
"nb_activities": 3, "nb_workouts": 3,
"total_distance": 47, "total_distance": 47,
"total_duration": 9960 "total_duration": 9960
}, },
"2": { "2": {
"nb_activities": 1, "nb_workouts": 1,
"total_distance": 5.613, "total_distance": 5.613,
"total_duration": 1267 "total_duration": 1267
} }
@ -201,7 +200,7 @@ def get_activities_by_time(
"status": "success" "status": "success"
} }
- no activities - no workouts
.. sourcecode:: http .. sourcecode:: http
@ -238,20 +237,20 @@ def get_activities_by_time(
- User does not exist. - User does not exist.
""" """
return get_activities(user_name, 'by_time') return get_workouts(user_name, 'by_time')
@stats_blueprint.route('/stats/<user_name>/by_sport', methods=['GET']) @stats_blueprint.route('/stats/<user_name>/by_sport', methods=['GET'])
@authenticate @authenticate
def get_activities_by_sport( def get_workouts_by_sport(
auth_user_id: int, user_name: str auth_user_id: int, user_name: str
) -> Union[Dict, HttpResponse]: ) -> Union[Dict, HttpResponse]:
""" """
Get activities statistics for a user by sport Get workouts statistics for a user by sport
**Example requests**: **Example requests**:
- without parameters (get stats for all sports with activities) - without parameters (get stats for all sports with workouts)
.. sourcecode:: http .. sourcecode:: http
@ -276,17 +275,17 @@ def get_activities_by_sport(
"data": { "data": {
"statistics": { "statistics": {
"1": { "1": {
"nb_activities": 3, "nb_workouts": 3,
"total_distance": 47, "total_distance": 47,
"total_duration": 9960 "total_duration": 9960
}, },
"2": { "2": {
"nb_activities": 1, "nb_workouts": 1,
"total_distance": 5.613, "total_distance": 5.613,
"total_duration": 1267 "total_duration": 1267
}, },
"3": { "3": {
"nb_activities": 2, "nb_workouts": 2,
"total_distance": 15.282, "total_distance": 15.282,
"total_duration": 12341 "total_duration": 12341
} }
@ -295,7 +294,7 @@ def get_activities_by_sport(
"status": "success" "status": "success"
} }
- no activities - no workouts
.. sourcecode:: http .. sourcecode:: http
@ -326,7 +325,7 @@ def get_activities_by_sport(
- Sport does not exist. - Sport does not exist.
""" """
return get_activities(user_name, 'by_sport') return get_workouts(user_name, 'by_sport')
@stats_blueprint.route('/stats/all', methods=['GET']) @stats_blueprint.route('/stats/all', methods=['GET'])
@ -351,10 +350,10 @@ def get_application_stats(auth_user_id: int) -> Dict:
{ {
"data": { "data": {
"activities": 3,
"sports": 3, "sports": 3,
"uploads_dir_size": 1000,
"users": 2, "users": 2,
"uploads_dir_size": 1000 "workouts": 3,
}, },
"status": "success" "status": "success"
} }
@ -371,17 +370,17 @@ def get_application_stats(auth_user_id: int) -> Dict:
:statuscode 403: You do not have permissions. :statuscode 403: You do not have permissions.
""" """
nb_activities = Activity.query.filter().count() nb_workouts = Workout.query.filter().count()
nb_users = User.query.filter().count() nb_users = User.query.filter().count()
nb_sports = ( nb_sports = (
db.session.query(func.count(Activity.sport_id)) db.session.query(func.count(Workout.sport_id))
.group_by(Activity.sport_id) .group_by(Workout.sport_id)
.count() .count()
) )
return { return {
'status': 'success', 'status': 'success',
'data': { 'data': {
'activities': nb_activities, 'workouts': nb_workouts,
'sports': nb_sports, 'sports': nb_sports,
'users': nb_users, 'users': nb_users,
'uploads_dir_size': get_upload_dir_size(), 'uploads_dir_size': get_upload_dir_size(),

View File

@ -0,0 +1,417 @@
import hashlib
import os
import tempfile
import zipfile
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple, Union
from uuid import UUID
import gpxpy.gpx
import pytz
from fittrackee import appLog, db
from flask import current_app
from sqlalchemy import exc
from staticmap import Line, StaticMap
from werkzeug.datastructures import FileStorage
from werkzeug.utils import secure_filename
from ..users.models import User
from .models import Sport, Workout, WorkoutSegment
from .utils_files import get_absolute_file_path
from .utils_gpx import get_gpx_info
class WorkoutException(Exception):
def __init__(
self, status: str, message: str, e: Optional[Exception] = None
) -> None:
self.status = status
self.message = message
self.e = e
def get_datetime_with_tz(
timezone: str, workout_date: datetime, gpx_data: Optional[Dict] = None
) -> Tuple[Optional[datetime], datetime]:
"""
Return naive datetime and datetime with user timezone
"""
workout_date_tz = None
if timezone:
user_tz = pytz.timezone(timezone)
utc_tz = pytz.utc
if gpx_data:
# workout date in gpx is in UTC, but in naive datetime
fmt = '%Y-%m-%d %H:%M:%S'
workout_date_string = workout_date.strftime(fmt)
workout_date_tmp = utc_tz.localize(
datetime.strptime(workout_date_string, fmt)
)
workout_date_tz = workout_date_tmp.astimezone(user_tz)
else:
workout_date_tz = user_tz.localize(workout_date)
workout_date = workout_date_tz.astimezone(utc_tz)
# make datetime 'naive' like in gpx file
workout_date = workout_date.replace(tzinfo=None)
return workout_date_tz, workout_date
def update_workout_data(
workout: Union[Workout, WorkoutSegment], gpx_data: Dict
) -> Union[Workout, WorkoutSegment]:
"""
Update workout or workout segment with data from gpx file
"""
workout.pauses = gpx_data['stop_time']
workout.moving = gpx_data['moving_time']
workout.min_alt = gpx_data['elevation_min']
workout.max_alt = gpx_data['elevation_max']
workout.descent = gpx_data['downhill']
workout.ascent = gpx_data['uphill']
workout.max_speed = gpx_data['max_speed']
workout.ave_speed = gpx_data['average_speed']
return workout
def create_workout(
user: User, workout_data: Dict, gpx_data: Optional[Dict] = None
) -> Workout:
"""
Create Workout from data entered by user and from gpx if a gpx file is
provided
"""
workout_date = (
gpx_data['start']
if gpx_data
else datetime.strptime(workout_data['workout_date'], '%Y-%m-%d %H:%M')
)
workout_date_tz, workout_date = get_datetime_with_tz(
user.timezone, workout_date, gpx_data
)
duration = (
gpx_data['duration']
if gpx_data
else timedelta(seconds=workout_data['duration'])
)
distance = gpx_data['distance'] if gpx_data else workout_data['distance']
title = gpx_data['name'] if gpx_data else workout_data.get('title', '')
new_workout = Workout(
user_id=user.id,
sport_id=workout_data['sport_id'],
workout_date=workout_date,
distance=distance,
duration=duration,
)
new_workout.notes = workout_data.get('notes')
if title is not None and title != '':
new_workout.title = title
else:
sport = Sport.query.filter_by(id=new_workout.sport_id).first()
fmt = "%Y-%m-%d %H:%M:%S"
workout_datetime = (
workout_date_tz.strftime(fmt)
if workout_date_tz
else new_workout.workout_date.strftime(fmt)
)
new_workout.title = f'{sport.label} - {workout_datetime}'
if gpx_data:
new_workout.gpx = gpx_data['filename']
new_workout.bounds = gpx_data['bounds']
update_workout_data(new_workout, gpx_data)
else:
new_workout.moving = duration
new_workout.ave_speed = (
None
if duration.seconds == 0
else float(new_workout.distance) / (duration.seconds / 3600)
)
new_workout.max_speed = new_workout.ave_speed
return new_workout
def create_segment(
workout_id: int, workout_uuid: UUID, segment_data: Dict
) -> WorkoutSegment:
"""
Create Workout Segment from gpx data
"""
new_segment = WorkoutSegment(
workout_id=workout_id,
workout_uuid=workout_uuid,
segment_id=segment_data['idx'],
)
new_segment.duration = segment_data['duration']
new_segment.distance = segment_data['distance']
update_workout_data(new_segment, segment_data)
return new_segment
def update_workout(workout: Workout) -> Workout:
"""
Update workout data from gpx file
"""
gpx_data, _, _ = get_gpx_info(
get_absolute_file_path(workout.gpx), False, False
)
updated_workout = update_workout_data(workout, gpx_data)
updated_workout.duration = gpx_data['duration']
updated_workout.distance = gpx_data['distance']
db.session.flush()
for segment_idx, segment in enumerate(updated_workout.segments):
segment_data = gpx_data['segments'][segment_idx]
updated_segment = update_workout_data(segment, segment_data)
updated_segment.duration = segment_data['duration']
updated_segment.distance = segment_data['distance']
db.session.flush()
return updated_workout
def edit_workout(
workout: Workout, workout_data: Dict, auth_user_id: int
) -> Workout:
"""
Edit an workout
Note: the gpx file is NOT modified
In a next version, map_data and weather_data will be updated
(case of a modified gpx file, see issue #7)
"""
user = User.query.filter_by(id=auth_user_id).first()
if workout_data.get('refresh'):
workout = update_workout(workout)
if workout_data.get('sport_id'):
workout.sport_id = workout_data.get('sport_id')
if workout_data.get('title'):
workout.title = workout_data.get('title')
if workout_data.get('notes'):
workout.notes = workout_data.get('notes')
if not workout.gpx:
if workout_data.get('workout_date'):
workout_date = datetime.strptime(
workout_data['workout_date'], '%Y-%m-%d %H:%M'
)
_, workout.workout_date = get_datetime_with_tz(
user.timezone, workout_date
)
if workout_data.get('duration'):
workout.duration = timedelta(seconds=workout_data['duration'])
workout.moving = workout.duration
if workout_data.get('distance'):
workout.distance = workout_data['distance']
workout.ave_speed = (
None
if workout.duration.seconds == 0
else float(workout.distance) / (workout.duration.seconds / 3600)
)
workout.max_speed = workout.ave_speed
return workout
def get_file_path(dir_path: str, filename: str) -> str:
"""
Get full path for a file
"""
if not os.path.exists(dir_path):
os.makedirs(dir_path)
file_path = os.path.join(dir_path, filename)
return file_path
def get_new_file_path(
auth_user_id: int,
workout_date: str,
sport: str,
old_filename: Optional[str] = None,
extension: Optional[str] = None,
) -> str:
"""
Generate a file path from user and workout data
"""
if not extension and old_filename:
extension = f".{old_filename.rsplit('.', 1)[1].lower()}"
_, new_filename = tempfile.mkstemp(
prefix=f'{workout_date}_{sport}_', suffix=extension
)
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])
return file_path
def generate_map(map_filepath: str, map_data: List) -> None:
"""
Generate and save map image from map data
"""
m = StaticMap(400, 225, 10)
line = Line(map_data, '#3388FF', 4)
m.add_line(line)
image = m.render()
image.save(map_filepath)
def get_map_hash(map_filepath: str) -> str:
"""
Generate a md5 hash used as id instead of workout id, to retrieve map
image (maps are sensitive data)
"""
md5 = hashlib.md5()
absolute_map_filepath = get_absolute_file_path(map_filepath)
with open(absolute_map_filepath, 'rb') as f:
for chunk in iter(lambda: f.read(128 * md5.block_size), b''):
md5.update(chunk)
return md5.hexdigest()
def process_one_gpx_file(params: Dict, filename: str) -> Workout:
"""
Get all data from a gpx file to create an workout with map image
"""
try:
gpx_data, map_data, weather_data = get_gpx_info(params['file_path'])
auth_user_id = params['user'].id
new_filepath = get_new_file_path(
auth_user_id=auth_user_id,
workout_date=gpx_data['start'],
old_filename=filename,
sport=params['sport_label'],
)
absolute_gpx_filepath = get_absolute_file_path(new_filepath)
os.rename(params['file_path'], absolute_gpx_filepath)
gpx_data['filename'] = new_filepath
map_filepath = get_new_file_path(
auth_user_id=auth_user_id,
workout_date=gpx_data['start'],
extension='.png',
sport=params['sport_label'],
)
absolute_map_filepath = get_absolute_file_path(map_filepath)
generate_map(absolute_map_filepath, map_data)
except (gpxpy.gpx.GPXXMLSyntaxException, TypeError) as e:
raise WorkoutException('error', 'Error during gpx file parsing.', e)
except Exception as e:
raise WorkoutException('error', 'Error during gpx processing.', e)
try:
new_workout = create_workout(
params['user'], params['workout_data'], gpx_data
)
new_workout.map = map_filepath
new_workout.map_id = get_map_hash(map_filepath)
new_workout.weather_start = weather_data[0]
new_workout.weather_end = weather_data[1]
db.session.add(new_workout)
db.session.flush()
for segment_data in gpx_data['segments']:
new_segment = create_segment(
new_workout.id, new_workout.uuid, segment_data
)
db.session.add(new_segment)
db.session.commit()
return new_workout
except (exc.IntegrityError, ValueError) as e:
raise WorkoutException('fail', 'Error during workout save.', e)
def process_zip_archive(common_params: Dict, extract_dir: str) -> List:
"""
Get files from a zip archive and create workouts, if number of files
does not exceed defined limit.
"""
with zipfile.ZipFile(common_params['file_path'], "r") as zip_ref:
zip_ref.extractall(extract_dir)
new_workouts = []
gpx_files_limit = os.getenv('REACT_APP_GPX_LIMIT_IMPORT', 10)
if (
gpx_files_limit
and isinstance(gpx_files_limit, str)
and gpx_files_limit.isdigit()
):
gpx_files_limit = int(gpx_files_limit)
else:
gpx_files_limit = 10
appLog.warning('GPX limit not configured, set to 10.')
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
file_path = os.path.join(extract_dir, gpx_file)
params = common_params
params['file_path'] = file_path
new_workout = process_one_gpx_file(params, gpx_file)
new_workouts.append(new_workout)
return new_workouts
def process_files(
auth_user_id: int,
workout_data: Dict,
workout_file: FileStorage,
folders: Dict,
) -> List:
"""
Store gpx file or zip archive and create workouts
"""
if workout_file.filename is None:
raise WorkoutException('error', 'File has no filename.')
filename = secure_filename(workout_file.filename)
extension = f".{filename.rsplit('.', 1)[1].lower()}"
file_path = get_file_path(folders['tmp_dir'], filename)
sport = Sport.query.filter_by(id=workout_data.get('sport_id')).first()
if not sport:
raise WorkoutException(
'error',
f"Sport id: {workout_data.get('sport_id')} does not exist",
)
user = User.query.filter_by(id=auth_user_id).first()
common_params = {
'user': user,
'workout_data': workout_data,
'file_path': file_path,
'sport_label': sport.label,
}
try:
workout_file.save(file_path)
except Exception as e:
raise WorkoutException('error', 'Error during workout file save.', e)
if extension == ".gpx":
return [process_one_gpx_file(common_params, filename)]
else:
return process_zip_archive(common_params, folders['extract_dir'])
def get_upload_dir_size() -> int:
"""
Return upload directory size
"""
upload_path = get_absolute_file_path('')
total_size = 0
for dir_path, _, filenames in os.walk(upload_path):
for f in filenames:
fp = os.path.join(dir_path, f)
total_size += os.path.getsize(fp)
return total_size

View File

@ -6,7 +6,7 @@ import gpxpy.gpx
from .utils_weather import get_weather from .utils_weather import get_weather
class ActivityGPXException(Exception): class WorkoutGPXException(Exception):
def __init__( def __init__(
self, status: str, message: str, e: Optional[Exception] = None self, status: str, message: str, e: Optional[Exception] = None
) -> None: ) -> None:
@ -74,7 +74,7 @@ def get_gpx_info(
""" """
gpx = open_gpx_file(gpx_file) gpx = open_gpx_file(gpx_file)
if gpx is None: if gpx is None:
raise ActivityGPXException('not found', 'No gpx file') raise WorkoutGPXException('not found', 'No gpx file')
gpx_data = {'name': gpx.tracks[0].name, 'segments': []} gpx_data = {'name': gpx.tracks[0].name, 'segments': []}
max_speed = 0 max_speed = 0
@ -153,11 +153,11 @@ def get_gpx_segments(
if segment_id is not None: if segment_id is not None:
segment_index = segment_id - 1 segment_index = segment_id - 1
if segment_index > (len(track_segments) - 1): if segment_index > (len(track_segments) - 1):
raise ActivityGPXException( raise WorkoutGPXException(
'not found', f'No segment with id \'{segment_id}\'', None 'not found', f'No segment with id \'{segment_id}\'', None
) )
if segment_index < 0: if segment_index < 0:
raise ActivityGPXException('error', 'Incorrect segment id', None) raise WorkoutGPXException('error', 'Incorrect segment id', None)
segments = [track_segments[segment_index]] segments = [track_segments[segment_index]]
else: else:
segments = track_segments segments = track_segments