Merge branch 'decorators_refacto' into dev
This commit is contained in:
		
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							@@ -11,6 +11,8 @@ build-client: lint-client
 | 
			
		||||
 | 
			
		||||
check-all: lint-all type-check test-python test-client
 | 
			
		||||
 | 
			
		||||
check-python: lint-python type-check test-python
 | 
			
		||||
 | 
			
		||||
clean:
 | 
			
		||||
	rm -rf .mypy_cache
 | 
			
		||||
	rm -rf .pytest_cache
 | 
			
		||||
 
 | 
			
		||||
@@ -191,13 +191,8 @@
 | 
			
		||||
</pre></div>
 | 
			
		||||
</div>
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<dt class="field-odd">Request JSON Object</dt>
 | 
			
		||||
<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>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Request JSON Object</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><strong>gpx_limit_import</strong> (<em>integer</em>) – max number of files in zip archive</p></li>
 | 
			
		||||
<li><p><strong>is_registration_enabled</strong> (<em>boolean</em>) – is registration enabled ?</p></li>
 | 
			
		||||
<li><p><strong>max_single_file_size</strong> (<em>integer</em>) – max size of a single file</p></li>
 | 
			
		||||
@@ -205,13 +200,13 @@
 | 
			
		||||
<li><p><strong>max_users</strong> (<em>integer</em>) – max users allowed to register on instance</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-odd">Request Headers</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<dt class="field-even">Request Headers</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://tools.ietf.org/html/rfc7235#section-4.2">Authorization</a></span> – OAuth 2.0 Bearer Token</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Status Codes</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<dt class="field-odd">Status Codes</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> – success</p></li>
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> – invalid payload</p></li>
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> – <ul>
 | 
			
		||||
 
 | 
			
		||||
@@ -212,18 +212,13 @@
 | 
			
		||||
</pre></div>
 | 
			
		||||
</div>
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<dt class="field-odd">Request Headers</dt>
 | 
			
		||||
<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>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Request Headers</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://tools.ietf.org/html/rfc7235#section-4.2">Authorization</a></span> – OAuth 2.0 Bearer Token</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-odd">Status Codes</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<dt class="field-even">Status Codes</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> – success</p></li>
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> – <ul>
 | 
			
		||||
<li><p>provide a valid auth token</p></li>
 | 
			
		||||
 
 | 
			
		||||
@@ -270,18 +270,13 @@
 | 
			
		||||
</pre></div>
 | 
			
		||||
</div>
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<dt class="field-odd">Request Headers</dt>
 | 
			
		||||
<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>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Request Headers</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://tools.ietf.org/html/rfc7235#section-4.2">Authorization</a></span> – OAuth 2.0 Bearer Token</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-odd">Status Codes</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<dt class="field-even">Status Codes</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> – success</p></li>
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> – <ul>
 | 
			
		||||
<li><p>provide a valid auth token</p></li>
 | 
			
		||||
@@ -368,7 +363,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>sport_id</strong> (<em>integer</em>) – sport id</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
@@ -444,7 +438,6 @@ Authenticated user must be an admin</p>
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>sport_id</strong> (<em>integer</em>) – sport id</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
 
 | 
			
		||||
@@ -206,7 +206,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>user_name</strong> (<em>integer</em>) – user name</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
@@ -321,7 +320,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>user_name</strong> (<em>integer</em>) – user name</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
@@ -378,18 +376,13 @@
 | 
			
		||||
</pre></div>
 | 
			
		||||
</div>
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<dt class="field-odd">Request Headers</dt>
 | 
			
		||||
<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>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Request Headers</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://tools.ietf.org/html/rfc7235#section-4.2">Authorization</a></span> – OAuth 2.0 Bearer Token</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-odd">Status Codes</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<dt class="field-even">Status Codes</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> – success</p></li>
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> – <ul>
 | 
			
		||||
<li><p>provide a valid auth token</p></li>
 | 
			
		||||
 
 | 
			
		||||
@@ -242,13 +242,8 @@
 | 
			
		||||
</pre></div>
 | 
			
		||||
</div>
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<dt class="field-odd">Query Parameters</dt>
 | 
			
		||||
<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>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Query Parameters</dt>
 | 
			
		||||
<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>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>
 | 
			
		||||
@@ -257,13 +252,13 @@
 | 
			
		||||
<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>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-odd">Request Headers</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<dt class="field-even">Request Headers</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://tools.ietf.org/html/rfc7235#section-4.2">Authorization</a></span> – OAuth 2.0 Bearer Token</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Status Codes</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<dt class="field-odd">Status Codes</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> – success</p></li>
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> – <ul>
 | 
			
		||||
<li><p>provide a valid auth token</p></li>
 | 
			
		||||
@@ -361,7 +356,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>user_name</strong> (<em>integer</em>) – user name</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
@@ -507,7 +501,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>user_name</strong> (<em>string</em>) – user name</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
@@ -561,7 +554,6 @@ one admin</p>
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>user_name</strong> (<em>string</em>) – user name</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
 
 | 
			
		||||
@@ -241,13 +241,8 @@
 | 
			
		||||
</pre></div>
 | 
			
		||||
</div>
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<dt class="field-odd">Query Parameters</dt>
 | 
			
		||||
<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>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Query Parameters</dt>
 | 
			
		||||
<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>per_page</strong> (<em>integer</em>) – number of workouts per page
 | 
			
		||||
(default: 5, max: 100)</p></li>
 | 
			
		||||
@@ -265,13 +260,13 @@
 | 
			
		||||
<li><p><strong>order</strong> (<em>string</em>) – sorting order (default: <code class="docutils literal notranslate"><span class="pre">desc</span></code>)</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-odd">Request Headers</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<dt class="field-even">Request Headers</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://tools.ietf.org/html/rfc7235#section-4.2">Authorization</a></span> – OAuth 2.0 Bearer Token</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Status Codes</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<dt class="field-odd">Status Codes</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> – success</p></li>
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> – <ul>
 | 
			
		||||
<li><p>provide a valid auth token</p></li>
 | 
			
		||||
@@ -355,7 +350,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
@@ -405,7 +399,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
@@ -478,7 +471,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
@@ -551,7 +543,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>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>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -603,7 +594,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>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>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -708,7 +698,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
@@ -820,24 +809,19 @@
 | 
			
		||||
</pre></div>
 | 
			
		||||
</div>
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<dt class="field-odd">Form Parameters</dt>
 | 
			
		||||
<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>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Form Parameters</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><strong>file</strong> – gpx file (allowed extensions: .gpx, .zip)</p></li>
 | 
			
		||||
<li><p><strong>data</strong> – sport id and notes (example: <code class="docutils literal notranslate"><span class="pre">{"sport_id":</span> <span class="pre">1,</span> <span class="pre">"notes":</span> <span class="pre">""}</span></code>)</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-odd">Request Headers</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<dt class="field-even">Request Headers</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://tools.ietf.org/html/rfc7235#section-4.2">Authorization</a></span> – OAuth 2.0 Bearer Token</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Status Codes</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<dt class="field-odd">Status Codes</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2">201 Created</a></span> – workout created</p></li>
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> – <ul>
 | 
			
		||||
<li><p>invalid payload</p></li>
 | 
			
		||||
@@ -948,13 +932,8 @@
 | 
			
		||||
</pre></div>
 | 
			
		||||
</div>
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<dt class="field-odd">Request JSON Object</dt>
 | 
			
		||||
<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>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Request JSON Object</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<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>) – workout distance in km</p></li>
 | 
			
		||||
<li><p><strong>duration</strong> (<em>integer</em>) – workout duration in seconds</p></li>
 | 
			
		||||
@@ -963,13 +942,13 @@
 | 
			
		||||
<li><p><strong>title</strong> (<em>string</em>) – workout title</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-odd">Request Headers</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<dt class="field-even">Request Headers</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://tools.ietf.org/html/rfc7235#section-4.2">Authorization</a></span> – OAuth 2.0 Bearer Token</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Status Codes</dt>
 | 
			
		||||
<dd class="field-even"><ul class="simple">
 | 
			
		||||
<dt class="field-odd">Status Codes</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2">201 Created</a></span> – workout created</p></li>
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> – invalid payload</p></li>
 | 
			
		||||
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> – <ul>
 | 
			
		||||
@@ -1075,7 +1054,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
@@ -1131,7 +1109,6 @@
 | 
			
		||||
<dl class="field-list simple">
 | 
			
		||||
<dt class="field-odd">Parameters</dt>
 | 
			
		||||
<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>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -10,6 +10,7 @@ from fittrackee.responses import (
 | 
			
		||||
    handle_error_and_return_response,
 | 
			
		||||
)
 | 
			
		||||
from fittrackee.users.decorators import authenticate_as_admin
 | 
			
		||||
from fittrackee.users.models import User
 | 
			
		||||
 | 
			
		||||
from .models import AppConfig
 | 
			
		||||
from .utils import update_app_config_from_database, verify_app_config
 | 
			
		||||
@@ -64,7 +65,7 @@ def get_application_config() -> Union[Dict, HttpResponse]:
 | 
			
		||||
 | 
			
		||||
@config_blueprint.route('/config', methods=['PATCH'])
 | 
			
		||||
@authenticate_as_admin
 | 
			
		||||
def update_application_config(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Update Application config
 | 
			
		||||
 | 
			
		||||
@@ -95,8 +96,6 @@ def update_application_config(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
 | 
			
		||||
    :<json integer gpx_limit_import: max number of files in zip archive
 | 
			
		||||
    :<json boolean is_registration_enabled: is registration enabled ?
 | 
			
		||||
    :<json integer max_single_file_size: max size of a single file
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ class TestStoppedSpeedThreshold:
 | 
			
		||||
        ) as gpx_track_segment_mock:
 | 
			
		||||
 | 
			
		||||
            process_files(
 | 
			
		||||
                auth_user_id=user_1.id,
 | 
			
		||||
                auth_user=user_1,
 | 
			
		||||
                folders=folders,
 | 
			
		||||
                workout_data={'sport_id': sport_id},
 | 
			
		||||
                workout_file=gpx_file_storage,
 | 
			
		||||
@@ -76,7 +76,7 @@ class TestStoppedSpeedThreshold:
 | 
			
		||||
        ) as gpx_track_segment_mock:
 | 
			
		||||
 | 
			
		||||
            process_files(
 | 
			
		||||
                auth_user_id=user_1.id,
 | 
			
		||||
                auth_user=user_1,
 | 
			
		||||
                folders=folders,
 | 
			
		||||
                workout_data={'sport_id': sport_1_cycling.id},
 | 
			
		||||
                workout_file=gpx_file_storage,
 | 
			
		||||
 
 | 
			
		||||
@@ -224,7 +224,7 @@ def login_user() -> Union[Dict, HttpResponse]:
 | 
			
		||||
 | 
			
		||||
@auth_blueprint.route('/auth/logout', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def logout_user(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
def logout_user(auth_user: User) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    user logout
 | 
			
		||||
 | 
			
		||||
@@ -274,7 +274,7 @@ def logout_user(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
 | 
			
		||||
    auth_token = auth_header.split(' ')[1]
 | 
			
		||||
    resp = User.decode_auth_token(auth_token)
 | 
			
		||||
    if isinstance(auth_user_id, str):
 | 
			
		||||
    if isinstance(resp, str):
 | 
			
		||||
        return UnauthorizedErrorResponse(resp)
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
@@ -286,7 +286,7 @@ def logout_user(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
@auth_blueprint.route('/auth/profile', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_authenticated_user_profile(
 | 
			
		||||
    auth_user_id: int,
 | 
			
		||||
    auth_user: User,
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    get authenticated user info
 | 
			
		||||
@@ -381,13 +381,12 @@ def get_authenticated_user_profile(
 | 
			
		||||
        - invalid token, please log in again
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    user = User.query.filter_by(id=auth_user_id).first()
 | 
			
		||||
    return {'status': 'success', 'data': user.serialize()}
 | 
			
		||||
    return {'status': 'success', 'data': auth_user.serialize()}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@auth_blueprint.route('/auth/profile/edit', methods=['POST'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def edit_user(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
def edit_user(auth_user: User) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    edit authenticated user
 | 
			
		||||
 | 
			
		||||
@@ -523,24 +522,23 @@ def edit_user(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
        ).decode()
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        user = User.query.filter_by(id=auth_user_id).first()
 | 
			
		||||
        user.first_name = first_name
 | 
			
		||||
        user.last_name = last_name
 | 
			
		||||
        user.bio = bio
 | 
			
		||||
        user.location = location
 | 
			
		||||
        user.birth_date = (
 | 
			
		||||
        auth_user.first_name = first_name
 | 
			
		||||
        auth_user.last_name = last_name
 | 
			
		||||
        auth_user.bio = bio
 | 
			
		||||
        auth_user.location = location
 | 
			
		||||
        auth_user.birth_date = (
 | 
			
		||||
            datetime.datetime.strptime(birth_date, '%Y-%m-%d')
 | 
			
		||||
            if birth_date
 | 
			
		||||
            else None
 | 
			
		||||
        )
 | 
			
		||||
        if password is not None and password != '':
 | 
			
		||||
            user.password = password
 | 
			
		||||
            auth_user.password = password
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            'status': 'success',
 | 
			
		||||
            'message': 'user profile updated',
 | 
			
		||||
            'data': user.serialize(),
 | 
			
		||||
            'data': auth_user.serialize(),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    # handler errors
 | 
			
		||||
@@ -550,7 +548,7 @@ def edit_user(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
 | 
			
		||||
@auth_blueprint.route('/auth/profile/edit/preferences', methods=['POST'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def edit_user_preferences(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    edit authenticated user preferences
 | 
			
		||||
 | 
			
		||||
@@ -670,17 +668,16 @@ def edit_user_preferences(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    weekm = post_data.get('weekm')
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        user = User.query.filter_by(id=auth_user_id).first()
 | 
			
		||||
        user.imperial_units = imperial_units
 | 
			
		||||
        user.language = language
 | 
			
		||||
        user.timezone = timezone
 | 
			
		||||
        user.weekm = weekm
 | 
			
		||||
        auth_user.imperial_units = imperial_units
 | 
			
		||||
        auth_user.language = language
 | 
			
		||||
        auth_user.timezone = timezone
 | 
			
		||||
        auth_user.weekm = weekm
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            'status': 'success',
 | 
			
		||||
            'message': 'user preferences updated',
 | 
			
		||||
            'data': user.serialize(),
 | 
			
		||||
            'data': auth_user.serialize(),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    # handler errors
 | 
			
		||||
@@ -691,7 +688,7 @@ def edit_user_preferences(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
@auth_blueprint.route('/auth/profile/edit/sports', methods=['POST'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def edit_user_sport_preferences(
 | 
			
		||||
    auth_user_id: int,
 | 
			
		||||
    auth_user: User,
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    edit authenticated user sport preferences
 | 
			
		||||
@@ -758,12 +755,12 @@ def edit_user_sport_preferences(
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        user_sport = UserSportPreference.query.filter_by(
 | 
			
		||||
            user_id=auth_user_id,
 | 
			
		||||
            user_id=auth_user.id,
 | 
			
		||||
            sport_id=sport_id,
 | 
			
		||||
        ).first()
 | 
			
		||||
        if not user_sport:
 | 
			
		||||
            user_sport = UserSportPreference(
 | 
			
		||||
                user_id=auth_user_id,
 | 
			
		||||
                user_id=auth_user.id,
 | 
			
		||||
                sport_id=sport_id,
 | 
			
		||||
                stopped_speed_threshold=sport.stopped_speed_threshold,
 | 
			
		||||
            )
 | 
			
		||||
@@ -792,7 +789,7 @@ def edit_user_sport_preferences(
 | 
			
		||||
 | 
			
		||||
@auth_blueprint.route('/auth/picture', methods=['POST'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def edit_picture(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
def edit_picture(auth_user: User) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    update authenticated user picture
 | 
			
		||||
 | 
			
		||||
@@ -848,23 +845,22 @@ def edit_picture(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    file = request.files['file']
 | 
			
		||||
    filename = secure_filename(file.filename)  # type: ignore
 | 
			
		||||
    dirpath = os.path.join(
 | 
			
		||||
        current_app.config['UPLOAD_FOLDER'], 'pictures', str(auth_user_id)
 | 
			
		||||
        current_app.config['UPLOAD_FOLDER'], 'pictures', str(auth_user.id)
 | 
			
		||||
    )
 | 
			
		||||
    if not os.path.exists(dirpath):
 | 
			
		||||
        os.makedirs(dirpath)
 | 
			
		||||
    absolute_picture_path = os.path.join(dirpath, filename)
 | 
			
		||||
    relative_picture_path = os.path.join(
 | 
			
		||||
        'pictures', str(auth_user_id), filename
 | 
			
		||||
        'pictures', str(auth_user.id), filename
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        user = User.query.filter_by(id=auth_user_id).first()
 | 
			
		||||
        if user.picture is not None:
 | 
			
		||||
            old_picture_path = get_absolute_file_path(user.picture)
 | 
			
		||||
        if auth_user.picture is not None:
 | 
			
		||||
            old_picture_path = get_absolute_file_path(auth_user.picture)
 | 
			
		||||
            if os.path.isfile(get_absolute_file_path(old_picture_path)):
 | 
			
		||||
                os.remove(old_picture_path)
 | 
			
		||||
        file.save(absolute_picture_path)
 | 
			
		||||
        user.picture = relative_picture_path
 | 
			
		||||
        auth_user.picture = relative_picture_path
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
        return {
 | 
			
		||||
            'status': 'success',
 | 
			
		||||
@@ -879,7 +875,7 @@ def edit_picture(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
 | 
			
		||||
@auth_blueprint.route('/auth/picture', methods=['DELETE'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def del_picture(auth_user_id: int) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
def del_picture(auth_user: User) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    delete authenticated user picture
 | 
			
		||||
 | 
			
		||||
@@ -908,11 +904,10 @@ def del_picture(auth_user_id: int) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        user = User.query.filter_by(id=auth_user_id).first()
 | 
			
		||||
        picture_path = get_absolute_file_path(user.picture)
 | 
			
		||||
        picture_path = get_absolute_file_path(auth_user.picture)
 | 
			
		||||
        if os.path.isfile(picture_path):
 | 
			
		||||
            os.remove(picture_path)
 | 
			
		||||
        user.picture = None
 | 
			
		||||
        auth_user.picture = None
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
        return {'status': 'no content'}, 204
 | 
			
		||||
    except (exc.IntegrityError, ValueError) as e:
 | 
			
		||||
 
 | 
			
		||||
@@ -8,16 +8,22 @@ from fittrackee.responses import HttpResponse
 | 
			
		||||
from .utils import verify_user
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def verify_auth_user(
 | 
			
		||||
    f: Callable, verify_admin: bool, *args: Any, **kwargs: Any
 | 
			
		||||
) -> Union[Callable, HttpResponse]:
 | 
			
		||||
    response_object, user = verify_user(request, verify_admin=verify_admin)
 | 
			
		||||
    if response_object:
 | 
			
		||||
        return response_object
 | 
			
		||||
    return f(user, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def authenticate(f: Callable) -> Callable:
 | 
			
		||||
    @wraps(f)
 | 
			
		||||
    def decorated_function(
 | 
			
		||||
        *args: Any, **kwargs: Any
 | 
			
		||||
    ) -> Union[Callable, HttpResponse]:
 | 
			
		||||
        verify_admin = False
 | 
			
		||||
        response_object, resp = verify_user(request, verify_admin)
 | 
			
		||||
        if response_object:
 | 
			
		||||
            return response_object
 | 
			
		||||
        return f(resp, *args, **kwargs)
 | 
			
		||||
        return verify_auth_user(f, verify_admin, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    return decorated_function
 | 
			
		||||
 | 
			
		||||
@@ -28,9 +34,6 @@ def authenticate_as_admin(f: Callable) -> Callable:
 | 
			
		||||
        *args: Any, **kwargs: Any
 | 
			
		||||
    ) -> Union[Callable, HttpResponse]:
 | 
			
		||||
        verify_admin = True
 | 
			
		||||
        response_object, resp = verify_user(request, verify_admin)
 | 
			
		||||
        if response_object:
 | 
			
		||||
            return response_object
 | 
			
		||||
        return f(resp, *args, **kwargs)
 | 
			
		||||
        return verify_auth_user(f, verify_admin, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    return decorated_function
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ USER_PER_PAGE = 10
 | 
			
		||||
 | 
			
		||||
@users_blueprint.route('/users', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_users(auth_user_id: int) -> Dict:
 | 
			
		||||
def get_users(auth_user: User) -> Dict:
 | 
			
		||||
    """
 | 
			
		||||
    Get all users
 | 
			
		||||
 | 
			
		||||
@@ -144,8 +144,6 @@ def get_users(auth_user_id: int) -> Dict:
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
 | 
			
		||||
    :query integer page: page if using pagination (default: 1)
 | 
			
		||||
    :query integer per_page: number of users per page (default: 10, max: 50)
 | 
			
		||||
    :query string q: query on user name
 | 
			
		||||
@@ -219,7 +217,7 @@ def get_users(auth_user_id: int) -> Dict:
 | 
			
		||||
@users_blueprint.route('/users/<user_name>', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_single_user(
 | 
			
		||||
    auth_user_id: int, user_name: str
 | 
			
		||||
    auth_user: User, user_name: str
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Get single user details
 | 
			
		||||
@@ -306,7 +304,6 @@ def get_single_user(
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param integer user_name: user name
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
@@ -371,9 +368,7 @@ def get_picture(user_name: str) -> Any:
 | 
			
		||||
 | 
			
		||||
@users_blueprint.route('/users/<user_name>', methods=['PATCH'])
 | 
			
		||||
@authenticate_as_admin
 | 
			
		||||
def update_user(
 | 
			
		||||
    auth_user_id: int, user_name: str
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
def update_user(auth_user: User, user_name: str) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Update user to add admin rights
 | 
			
		||||
 | 
			
		||||
@@ -461,7 +456,6 @@ def update_user(
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param string user_name: user name
 | 
			
		||||
 | 
			
		||||
    :<json boolean admin: does the user have administrator rights
 | 
			
		||||
@@ -500,7 +494,7 @@ def update_user(
 | 
			
		||||
@users_blueprint.route('/users/<user_name>', methods=['DELETE'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def delete_user(
 | 
			
		||||
    auth_user_id: int, user_name: str
 | 
			
		||||
    auth_user: User, user_name: str
 | 
			
		||||
) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Delete a user account
 | 
			
		||||
@@ -524,7 +518,6 @@ def delete_user(
 | 
			
		||||
      HTTP/1.1 204 NO CONTENT
 | 
			
		||||
      Content-Type: application/json
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param string user_name: user name
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
@@ -543,12 +536,11 @@ def delete_user(
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        auth_user = User.query.filter_by(id=auth_user_id).first()
 | 
			
		||||
        user = User.query.filter_by(username=user_name).first()
 | 
			
		||||
        if not user:
 | 
			
		||||
            return UserNotFoundErrorResponse()
 | 
			
		||||
 | 
			
		||||
        if user.id != auth_user_id and not auth_user.admin:
 | 
			
		||||
        if user.id != auth_user.id and not auth_user.admin:
 | 
			
		||||
            return ForbiddenErrorResponse()
 | 
			
		||||
        if (
 | 
			
		||||
            user.admin is True
 | 
			
		||||
 
 | 
			
		||||
@@ -12,14 +12,6 @@ from fittrackee.responses import (
 | 
			
		||||
from .models import User
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_admin(user_id: int) -> bool:
 | 
			
		||||
    """
 | 
			
		||||
    Return if user has admin rights
 | 
			
		||||
    """
 | 
			
		||||
    user = User.query.filter_by(id=user_id).first()
 | 
			
		||||
    return user.admin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_valid_email(email: str) -> bool:
 | 
			
		||||
    """
 | 
			
		||||
    Return if email format is valid
 | 
			
		||||
@@ -62,10 +54,10 @@ def register_controls(
 | 
			
		||||
 | 
			
		||||
def verify_user(
 | 
			
		||||
    current_request: Request, verify_admin: bool
 | 
			
		||||
) -> Tuple[Optional[HttpResponse], Optional[int]]:
 | 
			
		||||
) -> Tuple[Optional[HttpResponse], Optional[User]]:
 | 
			
		||||
    """
 | 
			
		||||
    Return user id, if the provided token is valid and if user has admin
 | 
			
		||||
    rights if 'verify_admin' is True
 | 
			
		||||
    Return authenticated user, if the provided token is valid and user has
 | 
			
		||||
    admin rights if 'verify_admin' is True
 | 
			
		||||
    """
 | 
			
		||||
    default_message = 'provide a valid auth token'
 | 
			
		||||
    auth_header = current_request.headers.get('Authorization')
 | 
			
		||||
@@ -78,9 +70,9 @@ def verify_user(
 | 
			
		||||
    user = User.query.filter_by(id=resp).first()
 | 
			
		||||
    if not user:
 | 
			
		||||
        return UnauthorizedErrorResponse(default_message), None
 | 
			
		||||
    if verify_admin and not is_admin(resp):
 | 
			
		||||
    if verify_admin and not user.admin:
 | 
			
		||||
        return ForbiddenErrorResponse(), None
 | 
			
		||||
    return None, resp
 | 
			
		||||
    return None, user
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def can_view_workout(
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ from typing import Dict
 | 
			
		||||
from flask import Blueprint
 | 
			
		||||
 | 
			
		||||
from fittrackee.users.decorators import authenticate
 | 
			
		||||
from fittrackee.users.models import User
 | 
			
		||||
 | 
			
		||||
from .models import Record
 | 
			
		||||
 | 
			
		||||
@@ -11,7 +12,7 @@ records_blueprint = Blueprint('records', __name__)
 | 
			
		||||
 | 
			
		||||
@records_blueprint.route('/records', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_records(auth_user_id: int) -> Dict:
 | 
			
		||||
def get_records(auth_user: User) -> Dict:
 | 
			
		||||
    """
 | 
			
		||||
    Get all records for authenticated user.
 | 
			
		||||
 | 
			
		||||
@@ -95,8 +96,6 @@ def get_records(auth_user_id: int) -> Dict:
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
 | 
			
		||||
    :statuscode 200: success
 | 
			
		||||
@@ -107,7 +106,7 @@ def get_records(auth_user_id: int) -> Dict:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    records = (
 | 
			
		||||
        Record.query.filter_by(user_id=auth_user_id)
 | 
			
		||||
        Record.query.filter_by(user_id=auth_user.id)
 | 
			
		||||
        .order_by(Record.sport_id.asc(), Record.record_type.asc())
 | 
			
		||||
        .all()
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ sports_blueprint = Blueprint('sports', __name__)
 | 
			
		||||
 | 
			
		||||
@sports_blueprint.route('/sports', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_sports(auth_user_id: int) -> Dict:
 | 
			
		||||
def get_sports(auth_user: User) -> Dict:
 | 
			
		||||
    """
 | 
			
		||||
    Get all sports
 | 
			
		||||
 | 
			
		||||
@@ -165,8 +165,6 @@ def get_sports(auth_user_id: int) -> Dict:
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
 | 
			
		||||
    :statuscode 200: success
 | 
			
		||||
@@ -176,16 +174,15 @@ def get_sports(auth_user_id: int) -> Dict:
 | 
			
		||||
        - invalid token, please log in again
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    user = User.query.filter_by(id=int(auth_user_id)).first()
 | 
			
		||||
    sports = Sport.query.order_by(Sport.id).all()
 | 
			
		||||
    sports_data = []
 | 
			
		||||
    for sport in sports:
 | 
			
		||||
        sport_preferences = UserSportPreference.query.filter_by(
 | 
			
		||||
            user_id=user.id, sport_id=sport.id
 | 
			
		||||
            user_id=auth_user.id, sport_id=sport.id
 | 
			
		||||
        ).first()
 | 
			
		||||
        sports_data.append(
 | 
			
		||||
            sport.serialize(
 | 
			
		||||
                is_admin=user.admin,
 | 
			
		||||
                is_admin=auth_user.admin,
 | 
			
		||||
                sport_preferences=sport_preferences.serialize()
 | 
			
		||||
                if sport_preferences
 | 
			
		||||
                else None,
 | 
			
		||||
@@ -199,7 +196,7 @@ def get_sports(auth_user_id: int) -> Dict:
 | 
			
		||||
 | 
			
		||||
@sports_blueprint.route('/sports/<int:sport_id>', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_sport(auth_user_id: int, sport_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
def get_sport(auth_user: User, sport_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Get a sport
 | 
			
		||||
 | 
			
		||||
@@ -273,7 +270,6 @@ def get_sport(auth_user_id: int, sport_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
        "status": "not found"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param integer sport_id: sport id
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
@@ -286,18 +282,17 @@ def get_sport(auth_user_id: int, sport_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    :statuscode 404: sport not found
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    user = User.query.filter_by(id=int(auth_user_id)).first()
 | 
			
		||||
    sport = Sport.query.filter_by(id=sport_id).first()
 | 
			
		||||
    if sport:
 | 
			
		||||
        sport_preferences = UserSportPreference.query.filter_by(
 | 
			
		||||
            user_id=user.id, sport_id=sport.id
 | 
			
		||||
            user_id=auth_user.id, sport_id=sport.id
 | 
			
		||||
        ).first()
 | 
			
		||||
        return {
 | 
			
		||||
            'status': 'success',
 | 
			
		||||
            'data': {
 | 
			
		||||
                'sports': [
 | 
			
		||||
                    sport.serialize(
 | 
			
		||||
                        is_admin=user.admin,
 | 
			
		||||
                        is_admin=auth_user.admin,
 | 
			
		||||
                        sport_preferences=sport_preferences.serialize()
 | 
			
		||||
                        if sport_preferences
 | 
			
		||||
                        else None,
 | 
			
		||||
@@ -310,9 +305,7 @@ def get_sport(auth_user_id: int, sport_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
 | 
			
		||||
@sports_blueprint.route('/sports/<int:sport_id>', methods=['PATCH'])
 | 
			
		||||
@authenticate_as_admin
 | 
			
		||||
def update_sport(
 | 
			
		||||
    auth_user_id: int, sport_id: int
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
def update_sport(auth_user: User, sport_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Update a sport
 | 
			
		||||
    Authenticated user must be an admin
 | 
			
		||||
@@ -364,7 +357,6 @@ def update_sport(
 | 
			
		||||
        "status": "not found"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param integer sport_id: sport id
 | 
			
		||||
 | 
			
		||||
    :<json string is_active: sport active status
 | 
			
		||||
@@ -387,7 +379,6 @@ def update_sport(
 | 
			
		||||
        return InvalidPayloadErrorResponse()
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        user = User.query.filter_by(id=int(auth_user_id)).first()
 | 
			
		||||
        sport = Sport.query.filter_by(id=sport_id).first()
 | 
			
		||||
        if not sport:
 | 
			
		||||
            return DataNotFoundErrorResponse('sports')
 | 
			
		||||
@@ -395,14 +386,14 @@ def update_sport(
 | 
			
		||||
        sport.is_active = sport_data.get('is_active')
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
        sport_preferences = UserSportPreference.query.filter_by(
 | 
			
		||||
            user_id=user.id, sport_id=sport.id
 | 
			
		||||
            user_id=auth_user.id, sport_id=sport.id
 | 
			
		||||
        ).first()
 | 
			
		||||
        return {
 | 
			
		||||
            'status': 'success',
 | 
			
		||||
            'data': {
 | 
			
		||||
                'sports': [
 | 
			
		||||
                    sport.serialize(
 | 
			
		||||
                        is_admin=user.admin,
 | 
			
		||||
                        is_admin=auth_user.admin,
 | 
			
		||||
                        sport_preferences=sport_preferences.serialize()
 | 
			
		||||
                        if sport_preferences
 | 
			
		||||
                        else None,
 | 
			
		||||
 
 | 
			
		||||
@@ -179,7 +179,7 @@ def get_workouts(
 | 
			
		||||
@stats_blueprint.route('/stats/<user_name>/by_time', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_workouts_by_time(
 | 
			
		||||
    auth_user_id: int, user_name: str
 | 
			
		||||
    auth_user: User, user_name: str
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Get workouts statistics for a user by time
 | 
			
		||||
@@ -258,7 +258,6 @@ def get_workouts_by_time(
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param integer user_name: user name
 | 
			
		||||
 | 
			
		||||
    :query string from: start date (format: ``%Y-%m-%d``)
 | 
			
		||||
@@ -287,7 +286,7 @@ def get_workouts_by_time(
 | 
			
		||||
@stats_blueprint.route('/stats/<user_name>/by_sport', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_workouts_by_sport(
 | 
			
		||||
    auth_user_id: int, user_name: str
 | 
			
		||||
    auth_user: User, user_name: str
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Get workouts statistics for a user by sport
 | 
			
		||||
@@ -361,7 +360,6 @@ def get_workouts_by_sport(
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param integer user_name: user name
 | 
			
		||||
 | 
			
		||||
    :query integer sport_id: sport id
 | 
			
		||||
@@ -383,7 +381,7 @@ def get_workouts_by_sport(
 | 
			
		||||
 | 
			
		||||
@stats_blueprint.route('/stats/all', methods=['GET'])
 | 
			
		||||
@authenticate_as_admin
 | 
			
		||||
def get_application_stats(auth_user_id: int) -> Dict:
 | 
			
		||||
def get_application_stats(auth_user: User) -> Dict:
 | 
			
		||||
    """
 | 
			
		||||
    Get all application statistics
 | 
			
		||||
 | 
			
		||||
@@ -411,8 +409,6 @@ def get_application_stats(auth_user_id: int) -> Dict:
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
 | 
			
		||||
    :statuscode 200: success
 | 
			
		||||
 
 | 
			
		||||
@@ -186,7 +186,7 @@ def update_workout(workout: Workout) -> Workout:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def edit_workout(
 | 
			
		||||
    workout: Workout, workout_data: Dict, auth_user_id: int
 | 
			
		||||
    workout: Workout, workout_data: Dict, auth_user: User
 | 
			
		||||
) -> Workout:
 | 
			
		||||
    """
 | 
			
		||||
    Edit an workout
 | 
			
		||||
@@ -195,7 +195,6 @@ def edit_workout(
 | 
			
		||||
    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'):
 | 
			
		||||
@@ -210,7 +209,7 @@ def edit_workout(
 | 
			
		||||
                workout_data['workout_date'], '%Y-%m-%d %H:%M'
 | 
			
		||||
            )
 | 
			
		||||
            _, workout.workout_date = get_datetime_with_tz(
 | 
			
		||||
                user.timezone, workout_date
 | 
			
		||||
                auth_user.timezone, workout_date
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if workout_data.get('duration'):
 | 
			
		||||
@@ -299,9 +298,9 @@ def process_one_gpx_file(
 | 
			
		||||
        gpx_data, map_data, weather_data = get_gpx_info(
 | 
			
		||||
            params['file_path'], stopped_speed_threshold
 | 
			
		||||
        )
 | 
			
		||||
        auth_user_id = params['user'].id
 | 
			
		||||
        auth_user = params['auth_user']
 | 
			
		||||
        new_filepath = get_new_file_path(
 | 
			
		||||
            auth_user_id=auth_user_id,
 | 
			
		||||
            auth_user_id=auth_user.id,
 | 
			
		||||
            workout_date=gpx_data['start'],
 | 
			
		||||
            old_filename=filename,
 | 
			
		||||
            sport=params['sport_label'],
 | 
			
		||||
@@ -311,7 +310,7 @@ def process_one_gpx_file(
 | 
			
		||||
        gpx_data['filename'] = new_filepath
 | 
			
		||||
 | 
			
		||||
        map_filepath = get_new_file_path(
 | 
			
		||||
            auth_user_id=auth_user_id,
 | 
			
		||||
            auth_user_id=auth_user.id,
 | 
			
		||||
            workout_date=gpx_data['start'],
 | 
			
		||||
            extension='.png',
 | 
			
		||||
            sport=params['sport_label'],
 | 
			
		||||
@@ -325,7 +324,7 @@ def process_one_gpx_file(
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        new_workout = create_workout(
 | 
			
		||||
            params['user'], params['workout_data'], gpx_data
 | 
			
		||||
            auth_user, params['workout_data'], gpx_data
 | 
			
		||||
        )
 | 
			
		||||
        new_workout.map = map_filepath
 | 
			
		||||
        new_workout.map_id = get_map_hash(map_filepath)
 | 
			
		||||
@@ -380,7 +379,7 @@ def process_zip_archive(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def process_files(
 | 
			
		||||
    auth_user_id: int,
 | 
			
		||||
    auth_user: User,
 | 
			
		||||
    workout_data: Dict,
 | 
			
		||||
    workout_file: FileStorage,
 | 
			
		||||
    folders: Dict,
 | 
			
		||||
@@ -399,9 +398,8 @@ def process_files(
 | 
			
		||||
            'error',
 | 
			
		||||
            f"Sport id: {workout_data.get('sport_id')} does not exist",
 | 
			
		||||
        )
 | 
			
		||||
    user = User.query.filter_by(id=auth_user_id).first()
 | 
			
		||||
    sport_preferences = UserSportPreference.query.filter_by(
 | 
			
		||||
        user_id=user.id, sport_id=sport.id
 | 
			
		||||
        user_id=auth_user.id, sport_id=sport.id
 | 
			
		||||
    ).first()
 | 
			
		||||
    stopped_speed_threshold = (
 | 
			
		||||
        sport.stopped_speed_threshold
 | 
			
		||||
@@ -410,7 +408,7 @@ def process_files(
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    common_params = {
 | 
			
		||||
        'user': user,
 | 
			
		||||
        'auth_user': auth_user,
 | 
			
		||||
        'workout_data': workout_data,
 | 
			
		||||
        'file_path': file_path,
 | 
			
		||||
        'sport_label': sport.label,
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@ MAX_WORKOUTS_PER_PAGE = 100
 | 
			
		||||
 | 
			
		||||
@workouts_blueprint.route('/workouts', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_workouts(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
def get_workouts(auth_user: User) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Get workouts for the authenticated user.
 | 
			
		||||
 | 
			
		||||
@@ -171,8 +171,6 @@ def get_workouts(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
            "status": "success"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
 | 
			
		||||
    :query integer page: page if using pagination (default: 1)
 | 
			
		||||
    :query integer per_page: number of workouts per page
 | 
			
		||||
                             (default: 5, max: 100)
 | 
			
		||||
@@ -200,10 +198,9 @@ def get_workouts(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        user = User.query.filter_by(id=auth_user_id).first()
 | 
			
		||||
        params = request.args.copy()
 | 
			
		||||
        page = int(params.get('page', 1))
 | 
			
		||||
        date_from, date_to = get_datetime_from_request_args(params, user)
 | 
			
		||||
        date_from, date_to = get_datetime_from_request_args(params, auth_user)
 | 
			
		||||
        distance_from = params.get('distance_from')
 | 
			
		||||
        distance_to = params.get('distance_to')
 | 
			
		||||
        duration_from = params.get('duration_from')
 | 
			
		||||
@@ -220,7 +217,7 @@ def get_workouts(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
            per_page = MAX_WORKOUTS_PER_PAGE
 | 
			
		||||
        workouts_pagination = (
 | 
			
		||||
            Workout.query.filter(
 | 
			
		||||
                Workout.user_id == auth_user_id,
 | 
			
		||||
                Workout.user_id == auth_user.id,
 | 
			
		||||
                Workout.sport_id == sport_id if sport_id else True,
 | 
			
		||||
                Workout.workout_date >= date_from if date_from else True,
 | 
			
		||||
                Workout.workout_date < date_to + timedelta(seconds=1)
 | 
			
		||||
@@ -302,7 +299,7 @@ def get_workouts(auth_user_id: int) -> Union[Dict, HttpResponse]:
 | 
			
		||||
)
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_workout(
 | 
			
		||||
    auth_user_id: int, workout_short_id: str
 | 
			
		||||
    auth_user: User, workout_short_id: str
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Get an workout
 | 
			
		||||
@@ -373,7 +370,6 @@ def get_workout(
 | 
			
		||||
          "status": "not found"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param string workout_short_id: workout short id
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
@@ -392,7 +388,7 @@ def get_workout(
 | 
			
		||||
    if not workout:
 | 
			
		||||
        return DataNotFoundErrorResponse('workouts')
 | 
			
		||||
 | 
			
		||||
    error_response = can_view_workout(auth_user_id, workout.user_id)
 | 
			
		||||
    error_response = can_view_workout(auth_user.id, workout.user_id)
 | 
			
		||||
    if error_response:
 | 
			
		||||
        return error_response
 | 
			
		||||
 | 
			
		||||
@@ -403,7 +399,7 @@ def get_workout(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_workout_data(
 | 
			
		||||
    auth_user_id: int,
 | 
			
		||||
    auth_user: User,
 | 
			
		||||
    workout_short_id: str,
 | 
			
		||||
    data_type: str,
 | 
			
		||||
    segment_id: Optional[int] = None,
 | 
			
		||||
@@ -417,7 +413,7 @@ def get_workout_data(
 | 
			
		||||
            message=f'workout not found (id: {workout_short_id})',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    error_response = can_view_workout(auth_user_id, workout.user_id)
 | 
			
		||||
    error_response = can_view_workout(auth_user.id, workout.user_id)
 | 
			
		||||
    if error_response:
 | 
			
		||||
        return error_response
 | 
			
		||||
    if not workout.gpx or workout.gpx == '':
 | 
			
		||||
@@ -467,7 +463,7 @@ def get_workout_data(
 | 
			
		||||
)
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_workout_gpx(
 | 
			
		||||
    auth_user_id: int, workout_short_id: str
 | 
			
		||||
    auth_user: User, workout_short_id: str
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Get gpx file for an workout displayed on map with Leaflet
 | 
			
		||||
@@ -494,7 +490,6 @@ def get_workout_gpx(
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param string workout_short_id: workout short id
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
@@ -510,7 +505,7 @@ def get_workout_gpx(
 | 
			
		||||
    :statuscode 500:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    return get_workout_data(auth_user_id, workout_short_id, 'gpx')
 | 
			
		||||
    return get_workout_data(auth_user, workout_short_id, 'gpx')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@workouts_blueprint.route(
 | 
			
		||||
@@ -518,7 +513,7 @@ def get_workout_gpx(
 | 
			
		||||
)
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_workout_chart_data(
 | 
			
		||||
    auth_user_id: int, workout_short_id: str
 | 
			
		||||
    auth_user: User, workout_short_id: str
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Get chart data from an workout gpx file, to display it with Recharts
 | 
			
		||||
@@ -564,7 +559,6 @@ def get_workout_chart_data(
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param string workout_short_id: workout short id
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
@@ -580,7 +574,7 @@ def get_workout_chart_data(
 | 
			
		||||
    :statuscode 500:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    return get_workout_data(auth_user_id, workout_short_id, 'chart_data')
 | 
			
		||||
    return get_workout_data(auth_user, workout_short_id, 'chart_data')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@workouts_blueprint.route(
 | 
			
		||||
@@ -589,7 +583,7 @@ def get_workout_chart_data(
 | 
			
		||||
)
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_segment_gpx(
 | 
			
		||||
    auth_user_id: int, workout_short_id: str, segment_id: int
 | 
			
		||||
    auth_user: User, workout_short_id: str, segment_id: int
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Get gpx file for an workout segment displayed on map with Leaflet
 | 
			
		||||
@@ -616,7 +610,6 @@ def get_segment_gpx(
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param string workout_short_id: workout short id
 | 
			
		||||
    :param integer segment_id: segment id
 | 
			
		||||
 | 
			
		||||
@@ -632,7 +625,7 @@ def get_segment_gpx(
 | 
			
		||||
    :statuscode 500:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    return get_workout_data(auth_user_id, workout_short_id, 'gpx', segment_id)
 | 
			
		||||
    return get_workout_data(auth_user, workout_short_id, 'gpx', segment_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@workouts_blueprint.route(
 | 
			
		||||
@@ -642,7 +635,7 @@ def get_segment_gpx(
 | 
			
		||||
)
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_segment_chart_data(
 | 
			
		||||
    auth_user_id: int, workout_short_id: str, segment_id: int
 | 
			
		||||
    auth_user: User, workout_short_id: str, segment_id: int
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Get chart data from an workout gpx file, to display it with Recharts
 | 
			
		||||
@@ -688,7 +681,6 @@ def get_segment_chart_data(
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param string workout_short_id: workout short id
 | 
			
		||||
    :param integer segment_id: segment id
 | 
			
		||||
 | 
			
		||||
@@ -705,7 +697,7 @@ def get_segment_chart_data(
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    return get_workout_data(
 | 
			
		||||
        auth_user_id, workout_short_id, 'chart_data', segment_id
 | 
			
		||||
        auth_user, workout_short_id, 'chart_data', segment_id
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -714,7 +706,7 @@ def get_segment_chart_data(
 | 
			
		||||
)
 | 
			
		||||
@authenticate
 | 
			
		||||
def download_workout_gpx(
 | 
			
		||||
    auth_user_id: int, workout_short_id: str
 | 
			
		||||
    auth_user: User, workout_short_id: str
 | 
			
		||||
) -> Union[HttpResponse, Response]:
 | 
			
		||||
    """
 | 
			
		||||
    Download gpx file
 | 
			
		||||
@@ -732,7 +724,6 @@ def download_workout_gpx(
 | 
			
		||||
      HTTP/1.1 200 OK
 | 
			
		||||
      Content-Type: application/gpx+xml
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param string workout_short_id: workout short id
 | 
			
		||||
 | 
			
		||||
    :statuscode 200: success
 | 
			
		||||
@@ -746,7 +737,7 @@ def download_workout_gpx(
 | 
			
		||||
    """
 | 
			
		||||
    workout_uuid = decode_short_id(workout_short_id)
 | 
			
		||||
    workout = Workout.query.filter_by(
 | 
			
		||||
        uuid=workout_uuid, user_id=auth_user_id
 | 
			
		||||
        uuid=workout_uuid, user_id=auth_user.id
 | 
			
		||||
    ).first()
 | 
			
		||||
    if not workout:
 | 
			
		||||
        return DataNotFoundErrorResponse(
 | 
			
		||||
@@ -852,7 +843,7 @@ def get_map_tile(s: str, z: str, x: str, y: str) -> Tuple[Response, int]:
 | 
			
		||||
 | 
			
		||||
@workouts_blueprint.route('/workouts', methods=['POST'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def post_workout(auth_user_id: int) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
def post_workout(auth_user: User) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Post an workout with a gpx file
 | 
			
		||||
 | 
			
		||||
@@ -944,8 +935,6 @@ def post_workout(auth_user_id: int) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
          "status": "success"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
 | 
			
		||||
    :form file: gpx file (allowed extensions: .gpx, .zip)
 | 
			
		||||
    :form data: sport id and notes (example: ``{"sport_id": 1, "notes": ""}``)
 | 
			
		||||
 | 
			
		||||
@@ -983,7 +972,7 @@ def post_workout(auth_user_id: int) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
 | 
			
		||||
    workout_file = request.files['file']
 | 
			
		||||
    upload_dir = os.path.join(
 | 
			
		||||
        current_app.config['UPLOAD_FOLDER'], 'workouts', str(auth_user_id)
 | 
			
		||||
        current_app.config['UPLOAD_FOLDER'], 'workouts', str(auth_user.id)
 | 
			
		||||
    )
 | 
			
		||||
    folders = {
 | 
			
		||||
        'extract_dir': os.path.join(upload_dir, 'extract'),
 | 
			
		||||
@@ -992,7 +981,7 @@ def post_workout(auth_user_id: int) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        new_workouts = process_files(
 | 
			
		||||
            auth_user_id, workout_data, workout_file, folders
 | 
			
		||||
            auth_user, workout_data, workout_file, folders
 | 
			
		||||
        )
 | 
			
		||||
        if len(new_workouts) > 0:
 | 
			
		||||
            response_object = {
 | 
			
		||||
@@ -1021,7 +1010,7 @@ def post_workout(auth_user_id: int) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
@workouts_blueprint.route('/workouts/no_gpx', methods=['POST'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def post_workout_no_gpx(
 | 
			
		||||
    auth_user_id: int,
 | 
			
		||||
    auth_user: User,
 | 
			
		||||
) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Post an workout without gpx file
 | 
			
		||||
@@ -1114,8 +1103,6 @@ def post_workout_no_gpx(
 | 
			
		||||
          "status": "success"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
 | 
			
		||||
    :<json string workout_date: workout date  (format: ``%Y-%m-%d %H:%M``)
 | 
			
		||||
    :<json float distance: workout distance in km
 | 
			
		||||
    :<json integer duration: workout duration in seconds
 | 
			
		||||
@@ -1145,8 +1132,7 @@ def post_workout_no_gpx(
 | 
			
		||||
        return InvalidPayloadErrorResponse()
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        user = User.query.filter_by(id=auth_user_id).first()
 | 
			
		||||
        new_workout = create_workout(user, workout_data)
 | 
			
		||||
        new_workout = create_workout(auth_user, workout_data)
 | 
			
		||||
        db.session.add(new_workout)
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 | 
			
		||||
@@ -1172,7 +1158,7 @@ def post_workout_no_gpx(
 | 
			
		||||
)
 | 
			
		||||
@authenticate
 | 
			
		||||
def update_workout(
 | 
			
		||||
    auth_user_id: int, workout_short_id: str
 | 
			
		||||
    auth_user: User, workout_short_id: str
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Update an workout
 | 
			
		||||
@@ -1265,7 +1251,6 @@ def update_workout(
 | 
			
		||||
          "status": "success"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param string workout_short_id: workout short id
 | 
			
		||||
 | 
			
		||||
    :<json string workout_date: workout date  (format: ``%Y-%m-%d %H:%M``)
 | 
			
		||||
@@ -1300,11 +1285,11 @@ def update_workout(
 | 
			
		||||
        if not workout:
 | 
			
		||||
            return DataNotFoundErrorResponse('workouts')
 | 
			
		||||
 | 
			
		||||
        response_object = can_view_workout(auth_user_id, workout.user_id)
 | 
			
		||||
        response_object = can_view_workout(auth_user.id, workout.user_id)
 | 
			
		||||
        if response_object:
 | 
			
		||||
            return response_object
 | 
			
		||||
 | 
			
		||||
        workout = edit_workout(workout, workout_data, auth_user_id)
 | 
			
		||||
        workout = edit_workout(workout, workout_data, auth_user)
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
        return {
 | 
			
		||||
            'status': 'success',
 | 
			
		||||
@@ -1320,7 +1305,7 @@ def update_workout(
 | 
			
		||||
)
 | 
			
		||||
@authenticate
 | 
			
		||||
def delete_workout(
 | 
			
		||||
    auth_user_id: int, workout_short_id: str
 | 
			
		||||
    auth_user: User, workout_short_id: str
 | 
			
		||||
) -> Union[Tuple[Dict, int], HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Delete an workout
 | 
			
		||||
@@ -1339,7 +1324,6 @@ def delete_workout(
 | 
			
		||||
      HTTP/1.1 204 NO CONTENT
 | 
			
		||||
      Content-Type: application/json
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
    :param string workout_short_id: workout short id
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
@@ -1359,7 +1343,7 @@ def delete_workout(
 | 
			
		||||
        workout = Workout.query.filter_by(uuid=workout_uuid).first()
 | 
			
		||||
        if not workout:
 | 
			
		||||
            return DataNotFoundErrorResponse('workouts')
 | 
			
		||||
        error_response = can_view_workout(auth_user_id, workout.user_id)
 | 
			
		||||
        error_response = can_view_workout(auth_user.id, workout.user_id)
 | 
			
		||||
        if error_response:
 | 
			
		||||
            return error_response
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user