Merge branch 'release-v0.6.2'
							
								
								
									
										12
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						@@ -1,5 +1,17 @@
 | 
			
		||||
# Change log
 | 
			
		||||
 | 
			
		||||
## Version 0.6.2 (2022/04/03)
 | 
			
		||||
 | 
			
		||||
### Issues Closed
 | 
			
		||||
 | 
			
		||||
#### Bugs Fixed
 | 
			
		||||
 | 
			
		||||
* [#175](https://github.com/SamR1/FitTrackee/issues/175) - Distance card on dashboard is not refreshed
 | 
			
		||||
* [#173](https://github.com/SamR1/FitTrackee/issues/173) - link to user profile in workout card is incorrect
 | 
			
		||||
 | 
			
		||||
In this release 2 issues were closed.  
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Version 0.6.1 (2022/03/27)
 | 
			
		||||
 | 
			
		||||
### Issues Closed
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,14 @@
 | 
			
		||||
 | 
			
		||||
[](https://pypi.org/project/fittrackee/) 
 | 
			
		||||
[](https://python.org)
 | 
			
		||||
[](http://flask.pocoo.org/) 
 | 
			
		||||
[](http://flask.pocoo.org/) 
 | 
			
		||||
[](https://github.com/psf/black) 
 | 
			
		||||
[](http://mypy-lang.org/)  
 | 
			
		||||
[](https://v3.vuejs.org/) 
 | 
			
		||||
[](https://www.typescriptlang.org/) 
 | 
			
		||||
[](https://github.com/prettier/prettier)  
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
[](https://github.com/SamR1/FitTrackee/actions/workflows/.tests-python.yml)
 | 
			
		||||
[](https://github.com/SamR1/FitTrackee/actions/workflows/.tests-javascript.yml)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# Sphinx build info version 1
 | 
			
		||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
 | 
			
		||||
config: 61330c9d18ff48d9802f7a0c5ced8d16
 | 
			
		||||
config: 2ca26683378198d52362ecd6dd5b71fe
 | 
			
		||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 538 KiB After Width: | Height: | Size: 539 KiB  | 
| 
		 Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 368 KiB  | 
| 
		 Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 202 KiB  | 
| 
		 Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB  | 
| 
		 Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB  | 
| 
		 Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 103 KiB  | 
@@ -1,5 +1,17 @@
 | 
			
		||||
# Change log
 | 
			
		||||
 | 
			
		||||
## Version 0.6.2 (2022/04/03)
 | 
			
		||||
 | 
			
		||||
### Issues Closed
 | 
			
		||||
 | 
			
		||||
#### Bugs Fixed
 | 
			
		||||
 | 
			
		||||
* [#175](https://github.com/SamR1/FitTrackee/issues/175) - Distance card on dashboard is not refreshed
 | 
			
		||||
* [#173](https://github.com/SamR1/FitTrackee/issues/173) - link to user profile in workout card is incorrect
 | 
			
		||||
 | 
			
		||||
In this release 2 issues were closed.  
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Version 0.6.1 (2022/03/27)
 | 
			
		||||
 | 
			
		||||
### Issues Closed
 | 
			
		||||
 
 | 
			
		||||
@@ -369,13 +369,13 @@ Production environment
 | 
			
		||||
.. warning::
 | 
			
		||||
    | Note that FitTrackee is under heavy development, some features may be unstable.
 | 
			
		||||
 | 
			
		||||
-  Download the last release (for now, it is the release v0.6.1):
 | 
			
		||||
-  Download the last release (for now, it is the release v0.6.2):
 | 
			
		||||
 | 
			
		||||
.. code:: bash
 | 
			
		||||
 | 
			
		||||
   $ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
 | 
			
		||||
   $ tar -xzf v0.6.1.tar.gz
 | 
			
		||||
   $ mv FitTrackee-0.6.1 FitTrackee
 | 
			
		||||
   $ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
 | 
			
		||||
   $ tar -xzf v0.6.2.tar.gz
 | 
			
		||||
   $ mv FitTrackee-0.6.2 FitTrackee
 | 
			
		||||
   $ cd FitTrackee
 | 
			
		||||
 | 
			
		||||
-  Create **.env** from example and update it
 | 
			
		||||
@@ -493,13 +493,13 @@ Prod environment
 | 
			
		||||
 | 
			
		||||
- Change to the directory where FitTrackee directory is located
 | 
			
		||||
 | 
			
		||||
- Download the last release (for now, it is the release v0.6.1) and overwrite existing files:
 | 
			
		||||
- Download the last release (for now, it is the release v0.6.2) and overwrite existing files:
 | 
			
		||||
 | 
			
		||||
.. code:: bash
 | 
			
		||||
 | 
			
		||||
   $ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
 | 
			
		||||
   $ tar -xzf v0.6.1.tar.gz
 | 
			
		||||
   $ cp -R FitTrackee-0.6.1/* FitTrackee/
 | 
			
		||||
   $ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
 | 
			
		||||
   $ tar -xzf v0.6.2.tar.gz
 | 
			
		||||
   $ cp -R FitTrackee-0.6.2/* FitTrackee/
 | 
			
		||||
   $ cd FitTrackee
 | 
			
		||||
 | 
			
		||||
- Update **.env** if needed (see `Environment variables <installation.html#environment-variables>`__).
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										72
									
								
								docs/_static/doctools.js
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -154,9 +154,7 @@ var Documentation = {
 | 
			
		||||
    this.fixFirefoxAnchorBug();
 | 
			
		||||
    this.highlightSearchWords();
 | 
			
		||||
    this.initIndexTable();
 | 
			
		||||
    if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) {
 | 
			
		||||
      this.initOnKeyListeners();
 | 
			
		||||
    }
 | 
			
		||||
    this.initOnKeyListeners();
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -269,6 +267,13 @@ var Documentation = {
 | 
			
		||||
    window.history.replaceState({}, '', url);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
   /**
 | 
			
		||||
   * helper function to focus on search bar
 | 
			
		||||
   */
 | 
			
		||||
  focusSearchBar : function() {
 | 
			
		||||
    $('input[name=q]').first().focus();
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * make the url absolute
 | 
			
		||||
   */
 | 
			
		||||
@@ -291,27 +296,54 @@ var Documentation = {
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  initOnKeyListeners: function() {
 | 
			
		||||
    // only install a listener if it is really needed
 | 
			
		||||
    if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
 | 
			
		||||
        !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    $(document).keydown(function(event) {
 | 
			
		||||
      var activeElementType = document.activeElement.tagName;
 | 
			
		||||
      // don't navigate when in search box, textarea, dropdown or button
 | 
			
		||||
      if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT'
 | 
			
		||||
          && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey
 | 
			
		||||
          && !event.shiftKey) {
 | 
			
		||||
        switch (event.keyCode) {
 | 
			
		||||
          case 37: // left
 | 
			
		||||
            var prevHref = $('link[rel="prev"]').prop('href');
 | 
			
		||||
            if (prevHref) {
 | 
			
		||||
              window.location.href = prevHref;
 | 
			
		||||
              return false;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
          case 39: // right
 | 
			
		||||
            var nextHref = $('link[rel="next"]').prop('href');
 | 
			
		||||
            if (nextHref) {
 | 
			
		||||
              window.location.href = nextHref;
 | 
			
		||||
              return false;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
          && activeElementType !== 'BUTTON') {
 | 
			
		||||
        if (event.altKey || event.ctrlKey || event.metaKey)
 | 
			
		||||
          return;
 | 
			
		||||
 | 
			
		||||
          if (!event.shiftKey) {
 | 
			
		||||
            switch (event.key) {
 | 
			
		||||
              case 'ArrowLeft':
 | 
			
		||||
                if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS)
 | 
			
		||||
                  break;
 | 
			
		||||
                var prevHref = $('link[rel="prev"]').prop('href');
 | 
			
		||||
                if (prevHref) {
 | 
			
		||||
                  window.location.href = prevHref;
 | 
			
		||||
                  return false;
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
              case 'ArrowRight':
 | 
			
		||||
                if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS)
 | 
			
		||||
                  break;
 | 
			
		||||
                var nextHref = $('link[rel="next"]').prop('href');
 | 
			
		||||
                if (nextHref) {
 | 
			
		||||
                  window.location.href = nextHref;
 | 
			
		||||
                  return false;
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
              case 'Escape':
 | 
			
		||||
                if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS)
 | 
			
		||||
                  break;
 | 
			
		||||
                Documentation.hideSearchWords();
 | 
			
		||||
                return false;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // some keyboard layouts may need Shift to get /
 | 
			
		||||
        switch (event.key) {
 | 
			
		||||
          case '/':
 | 
			
		||||
            if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS)
 | 
			
		||||
              break;
 | 
			
		||||
            Documentation.focusSearchBar();
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								docs/_static/documentation_options.js
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,6 +1,6 @@
 | 
			
		||||
var DOCUMENTATION_OPTIONS = {
 | 
			
		||||
    URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
 | 
			
		||||
    VERSION: '0.6.1',
 | 
			
		||||
    VERSION: '0.6.2',
 | 
			
		||||
    LANGUAGE: 'None',
 | 
			
		||||
    COLLAPSE_INDEX: false,
 | 
			
		||||
    BUILDER: 'html',
 | 
			
		||||
@@ -8,5 +8,7 @@ var DOCUMENTATION_OPTIONS = {
 | 
			
		||||
    LINK_SUFFIX: '.html',
 | 
			
		||||
    HAS_SOURCE: true,
 | 
			
		||||
    SOURCELINK_SUFFIX: '.txt',
 | 
			
		||||
    NAVIGATION_WITH_KEYS: false
 | 
			
		||||
    NAVIGATION_WITH_KEYS: false,
 | 
			
		||||
    SHOW_SEARCH_SUMMARY: true,
 | 
			
		||||
    ENABLE_SEARCH_SHORTCUTS: true,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										8
									
								
								docs/_static/searchtools.js
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -172,10 +172,6 @@ var Search = {
 | 
			
		||||
      }
 | 
			
		||||
      // stem the word
 | 
			
		||||
      var word = stemmer.stemWord(tmp[i].toLowerCase());
 | 
			
		||||
      // prevent stemmer from cutting word smaller than two chars
 | 
			
		||||
      if(word.length < 3 && tmp[i].length >= 3) {
 | 
			
		||||
        word = tmp[i];
 | 
			
		||||
      }
 | 
			
		||||
      var toAppend;
 | 
			
		||||
      // select the correct list
 | 
			
		||||
      if (word[0] == '-') {
 | 
			
		||||
@@ -276,7 +272,7 @@ var Search = {
 | 
			
		||||
          setTimeout(function() {
 | 
			
		||||
            displayNextItem();
 | 
			
		||||
          }, 5);
 | 
			
		||||
        } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
 | 
			
		||||
        } else if (DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY) {
 | 
			
		||||
          $.ajax({url: requestUrl,
 | 
			
		||||
                  dataType: "text",
 | 
			
		||||
                  complete: function(jqxhr, textstatus) {
 | 
			
		||||
@@ -293,7 +289,7 @@ var Search = {
 | 
			
		||||
                    }, 5);
 | 
			
		||||
                  }});
 | 
			
		||||
        } else {
 | 
			
		||||
          // no source available, just display title
 | 
			
		||||
          // just display title
 | 
			
		||||
          Search.output.append(listItem);
 | 
			
		||||
          setTimeout(function() {
 | 
			
		||||
            displayNextItem();
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Authentication — FitTrackee 0.6.1
 | 
			
		||||
    <title>Authentication — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="../index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -628,8 +628,9 @@ character “_” allowed</p></li>
 | 
			
		||||
<dt class="field-odd">Request JSON Object</dt>
 | 
			
		||||
<dd class="field-odd"><ul class="simple">
 | 
			
		||||
<li><p><strong>timezone</strong> (<em>string</em>) – user time zone</p></li>
 | 
			
		||||
<li><p><strong>weekm</strong> (<em>string</em>) – does week start on Monday?</p></li>
 | 
			
		||||
<li><p><strong>weekm</strong> (<em>boolean</em>) – does week start on Monday?</p></li>
 | 
			
		||||
<li><p><strong>language</strong> (<em>string</em>) – language preferences</p></li>
 | 
			
		||||
<li><p><strong>imperial_units</strong> (<em>boolean</em>) – display distance in imperial units</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Request Headers</dt>
 | 
			
		||||
@@ -1103,7 +1104,7 @@ character “_” allowed</p></li>
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Configuration — FitTrackee 0.6.1
 | 
			
		||||
    <title>Configuration — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="../index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -142,13 +142,14 @@
 | 
			
		||||
 | 
			
		||||
<span class="p">{</span><span class="w"></span>
 | 
			
		||||
<span class="w">  </span><span class="nt">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"admin_contact"</span><span class="p">:</span><span class="w"> </span><span class="s2">"admin@example.com"</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"gpx_limit_import"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"is_registration_enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"max_single_file_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">1048576</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"max_zip_file_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">10485760</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"max_users"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"max_zip_file_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">10485760</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"map_attribution"</span><span class="p">:</span><span class="w"> </span><span class="nt">"&copy; <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.6.1"</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.6.2"</span><span class="w"></span>
 | 
			
		||||
<span class="w">  </span><span class="p">},</span><span class="w"></span>
 | 
			
		||||
<span class="w">  </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"success"</span><span class="w"></span>
 | 
			
		||||
<span class="p">}</span><span class="w"></span>
 | 
			
		||||
@@ -182,10 +183,12 @@
 | 
			
		||||
<span class="w">  </span><span class="nt">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"admin_contact"</span><span class="p">:</span><span class="w"> </span><span class="s2">"admin@example.com"</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"gpx_limit_import"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"is_registration_enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"is_registration_enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"max_single_file_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">1048576</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"max_users"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"max_zip_file_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">10485760</span><span class="p">,</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"max_users"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"map_attribution"</span><span class="p">:</span><span class="w"> </span><span class="nt">"&copy; <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.6.2"</span><span class="w"></span>
 | 
			
		||||
<span class="w">  </span><span class="p">},</span><span class="w"></span>
 | 
			
		||||
<span class="w">  </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"success"</span><span class="w"></span>
 | 
			
		||||
<span class="p">}</span><span class="w"></span>
 | 
			
		||||
@@ -198,8 +201,8 @@
 | 
			
		||||
<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>
 | 
			
		||||
<li><p><strong>max_zip_file_size</strong> (<em>integer</em>) – max size of a zip archive</p></li>
 | 
			
		||||
<li><p><strong>max_users</strong> (<em>integer</em>) – max users allowed to register on instance</p></li>
 | 
			
		||||
<li><p><strong>max_zip_file_size</strong> (<em>integer</em>) – max size of a zip archive</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Request Headers</dt>
 | 
			
		||||
@@ -268,7 +271,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>API documentation — FitTrackee 0.6.1
 | 
			
		||||
    <title>API documentation — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="../index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -154,7 +154,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Records — FitTrackee 0.6.1
 | 
			
		||||
    <title>Records — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="../index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -246,7 +246,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Sports — FitTrackee 0.6.1
 | 
			
		||||
    <title>Sports — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="../index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -484,7 +484,7 @@ Authenticated user must be an admin</p>
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Statistics — FitTrackee 0.6.1
 | 
			
		||||
    <title>Statistics — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="../index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -411,7 +411,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Users — FitTrackee 0.6.1
 | 
			
		||||
    <title>Users — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="../index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -279,7 +279,8 @@ has admin rights</p>
 | 
			
		||||
<dl class="http get">
 | 
			
		||||
<dt class="sig sig-object http" id="get--api-users-(user_name)">
 | 
			
		||||
<span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/users/</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="pre">user_name</span></em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-users-(user_name)" title="Permalink to this definition">¶</a></dt>
 | 
			
		||||
<dd><p>Get single user details. Only user with admin rights can get user details.</p>
 | 
			
		||||
<dd><p>Get single user details. Only user with admin rights can get other users
 | 
			
		||||
details.</p>
 | 
			
		||||
<p>It returns user preferences only for authenticated user.</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/users/admin</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
 | 
			
		||||
@@ -624,7 +625,7 @@ one admin</p>
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Workouts — FitTrackee 0.6.1
 | 
			
		||||
    <title>Workouts — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="../index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -1148,7 +1148,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Change log — FitTrackee 0.6.1
 | 
			
		||||
    <title>Change log — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -39,7 +39,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -77,223 +77,230 @@
 | 
			
		||||
      role="menu"
 | 
			
		||||
      aria-labelledby="dLabelLocalToc"><ul>
 | 
			
		||||
<li><a class="reference internal" href="#">Change log</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-6-1-2022-03-27">Version 0.6.1 (2022/03/27)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-6-2-2022-04-03">Version 0.6.2 (2022/04/03)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#issues-closed">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#bugs-fixed">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-6-0-2022-03-27">Version 0.6.0 (2022/03/27)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-6-1-2022-03-27">Version 0.6.1 (2022/03/27)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id1">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#features">Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id2">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-6-0-2022-03-27">Version 0.6.0 (2022/03/27)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id3">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#features">Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id4">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#pull-requests">Pull Requests</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id3">Bugs Fixed</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id5">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-7-2022-02-13">Version 0.5.7 (2022/02/13)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id4">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id6">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#misc">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#id5">Pull Requests</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id7">Pull Requests</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#security">Security</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id6">Misc</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id8">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-6-2022-02-05">Version 0.5.6 (2022/02/05)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id7">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id8">Bugs Fixed</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id9">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id10">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#id9">Pull Requests</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id11">Pull Requests</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-5-2022-01-19">Version 0.5.5 (2022/01/19)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id10">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#new-features">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id11">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-4-2022-01-01">Version 0.5.4 (2022/01/01)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id12">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#new-features">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id13">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-3-2022-01-01">Version 0.5.3 (2022/01/01)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-4-2022-01-01">Version 0.5.4 (2022/01/01)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id14">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id15">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-2-2021-12-19">Version 0.5.2 (2021/12/19)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-3-2022-01-01">Version 0.5.3 (2022/01/01)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id16">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id17">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id17">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-1-2021-11-30">Version 0.5.1 (2021/11/30)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-2-2021-12-19">Version 0.5.2 (2021/12/19)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id18">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id19">New Features</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-0-2021-11-14">Version 0.5.0 (2021/11/14)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-1-2021-11-30">Version 0.5.1 (2021/11/30)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id20">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id21">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id22">Bugs Fixed</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id23">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#id24">Pull Requests</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-5-0-2021-11-14">Version 0.5.0 (2021/11/14)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id22">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id23">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id24">Bugs Fixed</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id25">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#id26">Pull Requests</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-9-2021-07-16">Version 0.4.9 (2021/07/16)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id25">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id26">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id27">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-8-2021-07-03">Version 0.4.8 (2021/07/03)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id28">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id27">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id28">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id29">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-7-2021-04-07">Version 0.4.7 (2021/04/07)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-8-2021-07-03">Version 0.4.8 (2021/07/03)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id30">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id31">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#id32">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-7-2021-04-07">Version 0.4.7 (2021/04/07)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id32">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id33">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#id34">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-6-2021-02-21">Version 0.4.6 (2021/02/21)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id33">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id34">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-5-2021-02-17">Version 0.4.5 (2021/02/17)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id35">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id36">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-4-2021-01-31">Version 0.4.4 (2021/01/31)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-5-2021-02-17">Version 0.4.5 (2021/02/17)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id37">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id38">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#id39">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-4-2021-01-31">Version 0.4.4 (2021/01/31)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id39">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id40">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#id41">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-3-2021-01-10">Version 0.4.3 (2021/01/10)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id40">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id41">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id42">Bugs Fixed</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id42">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id43">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id44">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-2-2021-01-03">Version 0.4.2 (2021/01/03)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id43">Misc</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id45">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-1-2020-12-31">Version 0.4.1 (2020/12/31)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id44">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id45">New Features</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-0-fittrackee-on-pypi-2020-09-19">Version 0.4.0 - FitTrackee on PyPI (2020/09/19)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id46">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id47">New Features</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-3-0-administration-2020-07-15">Version 0.3.0 - Administration (2020/07/15)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-4-0-fittrackee-on-pypi-2020-09-19">Version 0.4.0 - FitTrackee on PyPI (2020/09/19)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id48">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id49">New Features</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-3-0-administration-2020-07-15">Version 0.3.0 - Administration (2020/07/15)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id50">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id51">New Features</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-2-5-fix-and-improvements-2020-01-31">Version 0.2.5 - Fix and improvements (2020/01/31)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id50">Misc</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id52">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-2-4-minor-fix-2020-01-30">Version 0.2.4 - Minor fix (2020/01/30)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id51">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id52">Bugs Fixed</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id53">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id54">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-2-3-fittrackee-available-in-french-2019-12-29">Version 0.2.3 - FitTrackee available in French (2019/12/29)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id53">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id54">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id55">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-2-2-statistics-fix-2019-09-23">Version 0.2.2 - Statistics fix (2019/09/23)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id56">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id55">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id56">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id57">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-2-1-fix-and-improvements-2019-09-01">Version 0.2.1 - Fix and improvements (2019/09/01)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-2-2-statistics-fix-2019-09-23">Version 0.2.2 - Statistics fix (2019/09/23)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id58">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id59">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id60">Bugs Fixed</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id59">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#id61">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-2-1-fix-and-improvements-2019-09-01">Version 0.2.1 - Fix and improvements (2019/09/01)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id60">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id61">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id62">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#id63">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-2-0-statistics-2019-07-07">Version 0.2.0 - Statistics (2019/07/07)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id62">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id63">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id64">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id65">New Features</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#id64">Misc</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id66">Misc</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-1-1-fix-and-improvements-2019-02-07">Version 0.1.1 - Fix and improvements (2019/02/07)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id65">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id66">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id67">Bugs Fixed</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id67">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id68">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id69">Bugs Fixed</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
<li><a class="reference internal" href="#version-0-1-0-first-release-2018-07-04">Version 0.1.0 - First release 🎉 (2018-07-04)</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id68">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id69">New Features</a></li>
 | 
			
		||||
<li><a class="reference internal" href="#id70">Issues Closed</a><ul>
 | 
			
		||||
<li><a class="reference internal" href="#id71">New Features</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -345,13 +352,27 @@
 | 
			
		||||
      
 | 
			
		||||
  <section id="change-log">
 | 
			
		||||
<h1>Change log<a class="headerlink" href="#change-log" title="Permalink to this headline">¶</a></h1>
 | 
			
		||||
<section id="version-0-6-1-2022-03-27">
 | 
			
		||||
<h2>Version 0.6.1 (2022/03/27)<a class="headerlink" href="#version-0-6-1-2022-03-27" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="version-0-6-2-2022-04-03">
 | 
			
		||||
<h2>Version 0.6.2 (2022/04/03)<a class="headerlink" href="#version-0-6-2-2022-04-03" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="issues-closed">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#issues-closed" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="bugs-fixed">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#bugs-fixed" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/175">#175</a> - Distance card on dashboard is not refreshed</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/173">#173</a> - link to user profile in workout card is incorrect</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
<p>In this release 2 issues were closed.</p>
 | 
			
		||||
</section>
 | 
			
		||||
</section>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-6-1-2022-03-27">
 | 
			
		||||
<h2>Version 0.6.1 (2022/03/27)<a class="headerlink" href="#version-0-6-1-2022-03-27" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id1">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id1" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id2">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id2" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/171">#171</a> - Stats chart is not updated correctly</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
<p>In this release 1 issue was closed.</p>
 | 
			
		||||
@@ -361,8 +382,8 @@
 | 
			
		||||
<section id="version-0-6-0-2022-03-27">
 | 
			
		||||
<h2>Version 0.6.0 (2022/03/27)<a class="headerlink" href="#version-0-6-0-2022-03-27" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<p>This version introduces some changes on <a class="reference external" href="https://samr1.github.io/FitTrackee/features.html#account-preferences">user registration</a>.<br />From now on, a user needs to confirm his account after registration (an email with confirmation instructions is sent after registration).</p>
 | 
			
		||||
<section id="id1">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id1" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id3">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id3" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="features">
 | 
			
		||||
<h4>Features<a class="headerlink" href="#features" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
@@ -370,8 +391,8 @@
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/106">#106</a> -  Allow user to update email</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id2">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id2" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id4">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id4" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/169">#169</a> -  user picture is not refreshed after update</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -379,8 +400,8 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="pull-requests">
 | 
			
		||||
<h3>Pull Requests<a class="headerlink" href="#pull-requests" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id3">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id3" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id5">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id5" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/161">#161</a> - Minor translation issue on ‘Farthest’</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/160">#160</a> - Minor translation issue on APP_ERROR</p></li>
 | 
			
		||||
@@ -395,8 +416,8 @@
 | 
			
		||||
<p>This release contains several fixes including security fixes.<br />Thanks to @DanielSiersleben for the report.</p>
 | 
			
		||||
<p>And from now on, admin account is not created on application initialization.<br />A new command is added to set administration rights on the account created after registration
 | 
			
		||||
(see <a class="reference external" href="https://samr1.github.io/FitTrackee/installation.html#upgrade">documentation</a>)</p>
 | 
			
		||||
<section id="id4">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id4" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id6">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id6" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="misc">
 | 
			
		||||
<h4>Misc<a class="headerlink" href="#misc" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
@@ -404,8 +425,8 @@
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id5">
 | 
			
		||||
<h3>Pull Requests<a class="headerlink" href="#id5" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id7">
 | 
			
		||||
<h3>Pull Requests<a class="headerlink" href="#id7" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="security">
 | 
			
		||||
<h4>Security<a class="headerlink" href="#security" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
@@ -423,8 +444,8 @@
 | 
			
		||||
</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id6">
 | 
			
		||||
<h4>Misc<a class="headerlink" href="#id6" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id8">
 | 
			
		||||
<h4>Misc<a class="headerlink" href="#id8" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/152">#152</a> - Fixes and improvements:</p>
 | 
			
		||||
<ul>
 | 
			
		||||
@@ -440,17 +461,17 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-5-6-2022-02-05">
 | 
			
		||||
<h2>Version 0.5.6 (2022/02/05)<a class="headerlink" href="#version-0-5-6-2022-02-05" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id7">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id7" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id8">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id8" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id9">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id9" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id10">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id10" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/146">#146</a> - incorrect label on workouts filters</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id9">
 | 
			
		||||
<h3>Pull Requests<a class="headerlink" href="#id9" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id11">
 | 
			
		||||
<h3>Pull Requests<a class="headerlink" href="#id11" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/145">#145</a> - fix on database models</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -459,8 +480,8 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-5-5-2022-01-19">
 | 
			
		||||
<h2>Version 0.5.5 (2022/01/19)<a class="headerlink" href="#version-0-5-5-2022-01-19" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id10">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id10" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id12">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id12" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="new-features">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#new-features" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
@@ -470,8 +491,8 @@
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/134">#134</a> - Wind direction</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id11">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id11" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id13">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id13" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/commit/877fa0faaabc0130402638905fe04f84563eb278">877fa0f</a> - fix sport icon color (when changed) on calendar on small resolutions</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -481,10 +502,10 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-5-4-2022-01-01">
 | 
			
		||||
<h2>Version 0.5.4 (2022/01/01)<a class="headerlink" href="#version-0-5-4-2022-01-01" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id12">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id12" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id13">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id13" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id14">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id14" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id15">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id15" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/131">#131</a> - No workouts displayed on calendar</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -494,10 +515,10 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-5-3-2022-01-01">
 | 
			
		||||
<h2>Version 0.5.3 (2022/01/01)<a class="headerlink" href="#version-0-5-3-2022-01-01" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id14">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id14" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id15">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id15" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id16">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id16" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id17">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id17" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/129">#129</a> - Display only active sports when editing a workout</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/127">#127</a> - parse_email_url() can’t validate a legitimate EMAIL_URI such as “smtp://localhost:25”</p></li>
 | 
			
		||||
@@ -508,10 +529,10 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-5-2-2021-12-19">
 | 
			
		||||
<h2>Version 0.5.2 (2021/12/19)<a class="headerlink" href="#version-0-5-2-2021-12-19" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id16">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id16" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id17">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id17" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id18">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id18" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id19">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id19" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/123">#123</a> - Allow user to reset preferences for a sport</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/121">#121</a> - Add activity : snowshoes</p></li>
 | 
			
		||||
@@ -522,10 +543,10 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-5-1-2021-11-30">
 | 
			
		||||
<h2>Version 0.5.1 (2021/11/30)<a class="headerlink" href="#version-0-5-1-2021-11-30" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id18">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id18" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id19">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id19" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id20">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id20" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id21">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id21" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/116">#116</a> - Better UI for Speed and Elevation buttons in the graph of the Workout screen</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/115">#115</a> - Add option to download the GPX file of a Workout</p></li>
 | 
			
		||||
@@ -537,10 +558,10 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-5-0-2021-11-14">
 | 
			
		||||
<h2>Version 0.5.0 (2021/11/14)<a class="headerlink" href="#version-0-5-0-2021-11-14" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id20">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id20" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id21">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id21" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id22">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id22" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id23">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id23" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/99">#99</a> - Display workout with imperial units</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/91">#91</a> - Display elevation chart with min and max altitude of workout</p></li>
 | 
			
		||||
@@ -548,21 +569,21 @@
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/18">#18</a> - Better UI</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id22">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id22" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id24">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id24" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/95">#95</a> - Some workouts seem to be missing on statistics chart</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id23">
 | 
			
		||||
<h4>Misc<a class="headerlink" href="#id23" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id25">
 | 
			
		||||
<h4>Misc<a class="headerlink" href="#id25" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/104">#104</a> - Switch to AGPLv3 license</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id24">
 | 
			
		||||
<h3>Pull Requests<a class="headerlink" href="#id24" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id26">
 | 
			
		||||
<h3>Pull Requests<a class="headerlink" href="#id26" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/101">#101</a> - Docker updates for full files</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/100">#100</a> - Add client application in docker for development</p></li>
 | 
			
		||||
@@ -579,17 +600,17 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-4-9-2021-07-16">
 | 
			
		||||
<h2>Version 0.4.9 (2021/07/16)<a class="headerlink" href="#version-0-4-9-2021-07-16" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id25">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id25" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id26">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id26" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id27">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id27" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id28">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id28" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/83">#83</a> - allow using configured tile server to generate static maps<br /><strong>Note</strong>: to keep using the default tile server, set environment variable <code class="docutils literal notranslate"><span class="pre">DEFAULT_STATICMAP</span></code> to <code class="docutils literal notranslate"><span class="pre">True</span></code></p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/81">#81</a> - display remaining characters in textarea</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id27">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id27" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id29">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id29" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/82">#82</a> - a user can not modify his birth day</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/80">#80</a> - can not save notes with control characters</p></li>
 | 
			
		||||
@@ -600,10 +621,10 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-4-8-2021-07-03">
 | 
			
		||||
<h2>Version 0.4.8 (2021/07/03)<a class="headerlink" href="#version-0-4-8-2021-07-03" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id28">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id28" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id29">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id29" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id30">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id30" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id31">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id31" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/79">#79</a> - Fails to start after make rebuild</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -613,17 +634,17 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-4-7-2021-04-07">
 | 
			
		||||
<h2>Version 0.4.7 (2021/04/07)<a class="headerlink" href="#version-0-4-7-2021-04-07" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id30">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id30" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id31">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id31" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id32">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id32" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id33">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id33" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/75">#75</a> - Workouts on the same day are not displayed in right order</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id32">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id32" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id34">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id34" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p>Update Python and Javascript dependencies<br /><strong>IMPORTANT</strong>: Due to <a class="reference external" href="https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html#change-3687655465c25a39b968b4f5f6e9170b">SQLAlchemy update (1.4+)</a>, engine URLs starting with <code class="docutils literal notranslate"><span class="pre">postgres://</span></code> are no longer supported. Please update <code class="docutils literal notranslate"><span class="pre">DATABASE_URL</span></code> with <code class="docutils literal notranslate"><span class="pre">postgresql://</span></code>.</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -632,10 +653,10 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-4-6-2021-02-21">
 | 
			
		||||
<h2>Version 0.4.6 (2021/02/21)<a class="headerlink" href="#version-0-4-6-2021-02-21" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id33">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id33" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id34">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id34" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id35">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id35" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id36">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id36" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/72">#72</a> - Error message when file exceeding size is incorrect</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/71">#71</a> - max size or max number of files must be greater than 0</p></li>
 | 
			
		||||
@@ -647,10 +668,10 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-4-5-2021-02-17">
 | 
			
		||||
<h2>Version 0.4.5 (2021/02/17)<a class="headerlink" href="#version-0-4-5-2021-02-17" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id35">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id35" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id36">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id36" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id37">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id37" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id38">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id38" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/66">#66</a> - invalid gpx limit used when importing zip archive</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/64">#64</a> - Only 50 workouts per month shown in calendar</p></li>
 | 
			
		||||
@@ -661,17 +682,17 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-4-4-2021-01-31">
 | 
			
		||||
<h2>Version 0.4.4 (2021/01/31)<a class="headerlink" href="#version-0-4-4-2021-01-31" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id37">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id37" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id38">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id38" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id39">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id39" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id40">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id40" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/62">#62</a> - Error when sending reset password email</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id39">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id39" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id41">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id41" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p>Refactoring before introducing new features.</p></li>
 | 
			
		||||
<li><p>Add docker files for evaluation purposes.</p></li>
 | 
			
		||||
@@ -681,16 +702,16 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-4-3-2021-01-10">
 | 
			
		||||
<h2>Version 0.4.3 (2021/01/10)<a class="headerlink" href="#version-0-4-3-2021-01-10" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id40">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id40" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id41">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id41" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id42">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id42" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id43">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id43" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/58">#58</a> - Standardize terms used for workouts<br /><strong>Note:</strong> Database model, upload directory for workouts and API endpoints are also updated.</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id42">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id42" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id44">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id44" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/59">#59</a> - No message displayed on uploading image error</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -700,18 +721,18 @@
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-4-2-2021-01-03">
 | 
			
		||||
<h2>Version 0.4.2 (2021/01/03)<a class="headerlink" href="#version-0-4-2-2021-01-03" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id43">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id43" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id45">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id45" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<p>No new features in this release, only some refactorings before introducing
 | 
			
		||||
new features.</p>
 | 
			
		||||
</section>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-4-1-2020-12-31">
 | 
			
		||||
<h2>Version 0.4.1 (2020/12/31)<a class="headerlink" href="#version-0-4-1-2020-12-31" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id44">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id44" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id45">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id45" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id46">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id46" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id47">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id47" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/57">#57</a> - Use uuid for activities</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -727,10 +748,10 @@ new features.</p>
 | 
			
		||||
<li><p>It’s now possible to change the tile provider for maps. The default tile server is now <strong>OpenStreetMap</strong>’s standard tile layer (replacing <strong>ThunderForest Outdoors</strong>),
 | 
			
		||||
see <a class="reference external" href="https://samr1.github.io/FitTrackee/installation.html#map-tile-server">Map tile server in documentation</a>.</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
<section id="id46">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id46" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id47">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id47" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id48">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id48" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id49">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id49" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/54">#54</a> - Tile server can be changed</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/53">#53</a> - Simplify FitTrackee installation</p></li>
 | 
			
		||||
@@ -746,10 +767,10 @@ see <a class="reference external" href="https://samr1.github.io/FitTrackee/insta
 | 
			
		||||
<li><p>FitTrackee administration is now available (see <a class="reference external" href="https://samr1.github.io/FitTrackee/features.html#administration">documentation</a>)<br />⚠️ Warning: some application parameters move from environment variables to database (see <a class="reference external" href="https://samr1.github.io/FitTrackee/installation.html#environment-variables">installation</a>).</p></li>
 | 
			
		||||
<li><p>in order to send emails, Redis is now a mandatory dependency</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
<section id="id48">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id48" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id49">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id49" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id50">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id50" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id51">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id51" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/50">#50</a> - A user can reset his password</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/17">#17</a> - A user can delete his account</p></li>
 | 
			
		||||
@@ -761,8 +782,8 @@ see <a class="reference external" href="https://samr1.github.io/FitTrackee/insta
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-2-5-fix-and-improvements-2020-01-31">
 | 
			
		||||
<h2>Version 0.2.5 - Fix and improvements (2020/01/31)<a class="headerlink" href="#version-0-2-5-fix-and-improvements-2020-01-31" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id50">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id50" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id52">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id52" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<p>This version contains minor fix and improvements on client side:</p>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/commit/4c3fc343d51b9c27d3ebab71df648bcf7d7bae59">4c3fc34</a> - empty user data on logout</p></li>
 | 
			
		||||
@@ -775,10 +796,10 @@ add URL interceptors to simplify routes definition</p></li>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-2-4-minor-fix-2020-01-30">
 | 
			
		||||
<h2>Version 0.2.4 - Minor fix (2020/01/30)<a class="headerlink" href="#version-0-2-4-minor-fix-2020-01-30" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id51">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id51" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id52">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id52" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id53">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id53" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id54">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id54" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/47">#47</a> - timezone drop-down is not displayed correctly</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/46">#46</a> - calendar cannot display more than 5 or 6 activities on the same day</p></li>
 | 
			
		||||
@@ -789,17 +810,17 @@ add URL interceptors to simplify routes definition</p></li>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-2-3-fittrackee-available-in-french-2019-12-29">
 | 
			
		||||
<h2>Version 0.2.3 - FitTrackee available in French (2019/12/29)<a class="headerlink" href="#version-0-2-3-fittrackee-available-in-french-2019-12-29" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id53">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id53" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id54">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id54" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id55">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id55" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id56">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id56" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/43">#43</a> - Display weekend days with a different background color on calendar</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/40">#40</a> - Localize FitTrackee (i18n)</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id55">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id55" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id57">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id57" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/44">#44</a> - Cannot edit an activity that does not have a gpx file</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -809,10 +830,10 @@ add URL interceptors to simplify routes definition</p></li>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-2-2-statistics-fix-2019-09-23">
 | 
			
		||||
<h2>Version 0.2.2 - Statistics fix (2019/09/23)<a class="headerlink" href="#version-0-2-2-statistics-fix-2019-09-23" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id56">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id56" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id57">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id57" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id58">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id58" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id59">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id59" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/41">#41</a> - User statistics are incorrect</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -822,10 +843,10 @@ add URL interceptors to simplify routes definition</p></li>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-2-1-fix-and-improvements-2019-09-01">
 | 
			
		||||
<h2>Version 0.2.1 - Fix and improvements (2019/09/01)<a class="headerlink" href="#version-0-2-1-fix-and-improvements-2019-09-01" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id58">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id58" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id59">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id59" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id60">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id60" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id61">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id61" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/4">#4</a> - Show points on the map when mouse over the chart</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/14">#14</a> - Display segments informations</p></li>
 | 
			
		||||
@@ -836,15 +857,15 @@ add URL interceptors to simplify routes definition</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/37">#37</a> - Display map on activities list</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id60">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id60" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id62">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id62" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/34">#34</a> - Weather is not displayed anymore</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id61">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id61" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id63">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id63" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><strong><a class="reference external" href="https://poetry.eustace.io/">Poetry</a></strong> replaces <strong><a class="reference external" href="https://docs.pipenv.org">pipenv</a></strong> for Python packages management</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -853,17 +874,17 @@ add URL interceptors to simplify routes definition</p></li>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-2-0-statistics-2019-07-07">
 | 
			
		||||
<h2>Version 0.2.0 - Statistics (2019/07/07)<a class="headerlink" href="#version-0-2-0-statistics-2019-07-07" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id62">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id62" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id63">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id63" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id64">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id64" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id65">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id65" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/13">#13</a> - Detailed statistics</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id64">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id64" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id66">
 | 
			
		||||
<h3>Misc<a class="headerlink" href="#id66" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p>Update dependencies</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -872,17 +893,17 @@ add URL interceptors to simplify routes definition</p></li>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="version-0-1-1-fix-and-improvements-2019-02-07">
 | 
			
		||||
<h2>Version 0.1.1 - Fix and improvements (2019/02/07)<a class="headerlink" href="#version-0-1-1-fix-and-improvements-2019-02-07" title="Permalink to this headline">¶</a></h2>
 | 
			
		||||
<section id="id65">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id65" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id66">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id66" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id67">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id67" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id68">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id68" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/25">#25</a> - Display records on calendar</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/22">#22</a> - Add a total on current month statistics</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</section>
 | 
			
		||||
<section id="id67">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id67" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id69">
 | 
			
		||||
<h4>Bugs Fixed<a class="headerlink" href="#id69" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/31">#31</a> - Use moving duration for stats</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/29">#29</a> - Pause duration calculation with segments</p></li>
 | 
			
		||||
@@ -931,10 +952,10 @@ add URL interceptors to simplify routes definition</p></li>
 | 
			
		||||
<li><p>no administration for now</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
<p>➡️ more informations: see <a class="reference external" href="https://samr1.github.io/FitTrackee/">documentation</a>  and <a class="reference external" href="https://github.com/SamR1/FitTrackee/issues">current issues</a></p>
 | 
			
		||||
<section id="id68">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id68" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id69">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id69" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<section id="id70">
 | 
			
		||||
<h3>Issues Closed<a class="headerlink" href="#id70" title="Permalink to this headline">¶</a></h3>
 | 
			
		||||
<section id="id71">
 | 
			
		||||
<h4>New Features<a class="headerlink" href="#id71" title="Permalink to this headline">¶</a></h4>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/11">#11</a> - Timezone support</p></li>
 | 
			
		||||
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/10">#10</a> - Add a note to an activity</p></li>
 | 
			
		||||
@@ -962,7 +983,7 @@ add URL interceptors to simplify routes definition</p></li>
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Features — FitTrackee 0.6.1
 | 
			
		||||
    <title>Features — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -359,7 +359,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>Index — FitTrackee 0.6.1
 | 
			
		||||
    <title>Index — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -37,7 +37,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -179,7 +179,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>HTTP Routing Table — FitTrackee 0.6.1
 | 
			
		||||
    <title>HTTP Routing Table — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -44,7 +44,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -355,7 +355,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>FitTrackee — FitTrackee 0.6.1
 | 
			
		||||
    <title>FitTrackee — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -39,7 +39,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="#">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -173,7 +173,7 @@ Map</a>.</div>
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Installation — FitTrackee 0.6.1
 | 
			
		||||
    <title>Installation — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -632,11 +632,11 @@ $ make install-db
 | 
			
		||||
</div>
 | 
			
		||||
</div>
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p>Download the last release (for now, it is the release v0.6.1):</p></li>
 | 
			
		||||
<li><p>Download the last release (for now, it is the release v0.6.2):</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
 | 
			
		||||
$ tar -xzf v0.6.1.tar.gz
 | 
			
		||||
$ mv FitTrackee-0.6.1 FitTrackee
 | 
			
		||||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
 | 
			
		||||
$ tar -xzf v0.6.2.tar.gz
 | 
			
		||||
$ mv FitTrackee-0.6.2 FitTrackee
 | 
			
		||||
$ <span class="nb">cd</span> FitTrackee
 | 
			
		||||
</pre></div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -752,11 +752,11 @@ $ <span class="nb">source</span> .env
 | 
			
		||||
<ul class="simple">
 | 
			
		||||
<li><p>Stop the application</p></li>
 | 
			
		||||
<li><p>Change to the directory where FitTrackee directory is located</p></li>
 | 
			
		||||
<li><p>Download the last release (for now, it is the release v0.6.1) and overwrite existing files:</p></li>
 | 
			
		||||
<li><p>Download the last release (for now, it is the release v0.6.2) and overwrite existing files:</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
 | 
			
		||||
$ tar -xzf v0.6.1.tar.gz
 | 
			
		||||
$ cp -R FitTrackee-0.6.1/* FitTrackee/
 | 
			
		||||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
 | 
			
		||||
$ tar -xzf v0.6.2.tar.gz
 | 
			
		||||
$ cp -R FitTrackee-0.6.2/* FitTrackee/
 | 
			
		||||
$ <span class="nb">cd</span> FitTrackee
 | 
			
		||||
</pre></div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -984,7 +984,7 @@ $ make docker-build docker-run docker-init
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								docs/objects.inv
									
									
									
									
									
								
							
							
						
						@@ -4,7 +4,7 @@
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>Search — FitTrackee 0.6.1
 | 
			
		||||
    <title>Search — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -44,7 +44,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -149,7 +149,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Administrator — FitTrackee 0.6.1
 | 
			
		||||
    <title>Administrator — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="../index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -155,7 +155,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>Troubleshooting — FitTrackee 0.6.1
 | 
			
		||||
    <title>Troubleshooting — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="../index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -152,7 +152,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 | 
			
		||||
 | 
			
		||||
    <title>User — FitTrackee 0.6.1
 | 
			
		||||
    <title>User — FitTrackee 0.6.2
 | 
			
		||||
 documentation</title>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
        <a class="navbar-brand" href="../index.html">
 | 
			
		||||
          FitTrackee</a>
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.1
 | 
			
		||||
        <span class="navbar-text navbar-version pull-left"><b>0.6.2
 | 
			
		||||
</b></span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -143,7 +143,7 @@
 | 
			
		||||
    </p>
 | 
			
		||||
    <p>
 | 
			
		||||
        © Copyright 2018 - 2022, SamR1.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
 | 
			
		||||
      Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</footer>
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 538 KiB After Width: | Height: | Size: 539 KiB  | 
| 
		 Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 368 KiB  | 
| 
		 Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 202 KiB  | 
| 
		 Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB  | 
| 
		 Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB  | 
| 
		 Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 103 KiB  | 
@@ -369,13 +369,13 @@ Production environment
 | 
			
		||||
.. warning::
 | 
			
		||||
    | Note that FitTrackee is under heavy development, some features may be unstable.
 | 
			
		||||
 | 
			
		||||
-  Download the last release (for now, it is the release v0.6.1):
 | 
			
		||||
-  Download the last release (for now, it is the release v0.6.2):
 | 
			
		||||
 | 
			
		||||
.. code:: bash
 | 
			
		||||
 | 
			
		||||
   $ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
 | 
			
		||||
   $ tar -xzf v0.6.1.tar.gz
 | 
			
		||||
   $ mv FitTrackee-0.6.1 FitTrackee
 | 
			
		||||
   $ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
 | 
			
		||||
   $ tar -xzf v0.6.2.tar.gz
 | 
			
		||||
   $ mv FitTrackee-0.6.2 FitTrackee
 | 
			
		||||
   $ cd FitTrackee
 | 
			
		||||
 | 
			
		||||
-  Create **.env** from example and update it
 | 
			
		||||
@@ -493,13 +493,13 @@ Prod environment
 | 
			
		||||
 | 
			
		||||
- Change to the directory where FitTrackee directory is located
 | 
			
		||||
 | 
			
		||||
- Download the last release (for now, it is the release v0.6.1) and overwrite existing files:
 | 
			
		||||
- Download the last release (for now, it is the release v0.6.2) and overwrite existing files:
 | 
			
		||||
 | 
			
		||||
.. code:: bash
 | 
			
		||||
 | 
			
		||||
   $ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
 | 
			
		||||
   $ tar -xzf v0.6.1.tar.gz
 | 
			
		||||
   $ cp -R FitTrackee-0.6.1/* FitTrackee/
 | 
			
		||||
   $ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
 | 
			
		||||
   $ tar -xzf v0.6.2.tar.gz
 | 
			
		||||
   $ cp -R FitTrackee-0.6.2/* FitTrackee/
 | 
			
		||||
   $ cd FitTrackee
 | 
			
		||||
 | 
			
		||||
- Update **.env** if needed (see `Environment variables <installation.html#environment-variables>`__).
 | 
			
		||||
 
 | 
			
		||||
@@ -19,8 +19,9 @@ from flask_sqlalchemy import SQLAlchemy
 | 
			
		||||
from sqlalchemy.exc import ProgrammingError
 | 
			
		||||
 | 
			
		||||
from fittrackee.emails.email import EmailService
 | 
			
		||||
from fittrackee.request import CustomRequest
 | 
			
		||||
 | 
			
		||||
VERSION = __version__ = '0.6.1'
 | 
			
		||||
VERSION = __version__ = '0.6.2'
 | 
			
		||||
db = SQLAlchemy()
 | 
			
		||||
bcrypt = Bcrypt()
 | 
			
		||||
migrate = Migrate()
 | 
			
		||||
@@ -35,9 +36,17 @@ logging.basicConfig(
 | 
			
		||||
appLog = logging.getLogger('fittrackee')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomFlask(Flask):
 | 
			
		||||
    # add custom Request to handle user-agent parsing
 | 
			
		||||
    # (removed in Werkzeug 2.1)
 | 
			
		||||
    request_class = CustomRequest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_app() -> Flask:
 | 
			
		||||
    # instantiate the app
 | 
			
		||||
    app = Flask(__name__, static_folder='dist/static', template_folder='dist')
 | 
			
		||||
    app = CustomFlask(
 | 
			
		||||
        __name__, static_folder='dist/static', template_folder='dist'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # set config
 | 
			
		||||
    with app.app_context():
 | 
			
		||||
@@ -105,7 +114,7 @@ def create_app() -> Flask:
 | 
			
		||||
        appLog.setLevel(logging.DEBUG)
 | 
			
		||||
 | 
			
		||||
        # Enable CORS
 | 
			
		||||
        @app.after_request
 | 
			
		||||
        @app.after_request  # type: ignore
 | 
			
		||||
        def after_request(response: Response) -> Response:
 | 
			
		||||
            response.headers.add('Access-Control-Allow-Origin', '*')
 | 
			
		||||
            response.headers.add(
 | 
			
		||||
 
 | 
			
		||||
@@ -40,13 +40,14 @@ def get_application_config() -> Union[Dict, HttpResponse]:
 | 
			
		||||
 | 
			
		||||
      {
 | 
			
		||||
        "data": {
 | 
			
		||||
          "admin_contact": "admin@example.com",
 | 
			
		||||
          "gpx_limit_import": 10,
 | 
			
		||||
          "is_registration_enabled": false,
 | 
			
		||||
          "max_single_file_size": 1048576,
 | 
			
		||||
          "max_zip_file_size": 10485760,
 | 
			
		||||
          "max_users": 0,
 | 
			
		||||
          "max_zip_file_size": 10485760,
 | 
			
		||||
          "map_attribution": "© <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
 | 
			
		||||
          "version": "0.6.1"
 | 
			
		||||
          "version": "0.6.2"
 | 
			
		||||
        },
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
@@ -90,10 +91,12 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
 | 
			
		||||
        "data": {
 | 
			
		||||
          "admin_contact": "admin@example.com",
 | 
			
		||||
          "gpx_limit_import": 10,
 | 
			
		||||
          "is_registration_enabled": true,
 | 
			
		||||
          "is_registration_enabled": false,
 | 
			
		||||
          "max_single_file_size": 1048576,
 | 
			
		||||
          "max_users": 10,
 | 
			
		||||
          "max_zip_file_size": 10485760,
 | 
			
		||||
          "max_users": 10
 | 
			
		||||
          "map_attribution": "© <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
 | 
			
		||||
          "version": "0.6.2"
 | 
			
		||||
        },
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
@@ -102,8 +105,8 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    :<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
 | 
			
		||||
    :<json integer max_zip_file_size: max size of a zip archive
 | 
			
		||||
    :<json integer max_users: max users allowed to register on instance
 | 
			
		||||
    :<json integer max_zip_file_size: max size of a zip archive
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								fittrackee/dist/index.html
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1 +1 @@
 | 
			
		||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.c045258f.js"></script><script defer="defer" src="/static/js/app.5c12d3f9.js"></script><link href="/static/css/app.3729aa92.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
 | 
			
		||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.63e25135.js"></script><script defer="defer" src="/static/js/app.9fb29e8d.js"></script><link href="/static/css/app.3729aa92.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
 | 
			
		||||
							
								
								
									
										2
									
								
								fittrackee/dist/service-worker.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										2
									
								
								fittrackee/dist/service-worker.js.map
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										1
									
								
								fittrackee/dist/static/js/app.9fb29e8d.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										2
									
								
								fittrackee/dist/static/js/app.ea909ac0.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										1
									
								
								fittrackee/dist/static/js/chunk-vendors.63e25135.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										2
									
								
								fittrackee/dist/static/js/reset.30b1815a.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										24
									
								
								fittrackee/request.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
			
		||||
from typing import Optional, Tuple
 | 
			
		||||
 | 
			
		||||
from flask import Request
 | 
			
		||||
from ua_parser import user_agent_parser
 | 
			
		||||
from werkzeug.user_agent import UserAgent as IUserAgent
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserAgent(IUserAgent):
 | 
			
		||||
    def __init__(self, string: str):
 | 
			
		||||
        super().__init__(string)
 | 
			
		||||
        self.platform, self.browser = self._parse_user_agent(self.string)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _parse_user_agent(
 | 
			
		||||
        user_agent: str,
 | 
			
		||||
    ) -> Tuple[Optional[str], Optional[str]]:
 | 
			
		||||
        parsed_string = user_agent_parser.Parse(user_agent)
 | 
			
		||||
        platform = parsed_string.get('os', {}).get('family')
 | 
			
		||||
        browser = parsed_string.get('user_agent', {}).get('family')
 | 
			
		||||
        return platform, browser
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomRequest(Request):
 | 
			
		||||
    user_agent_class = UserAgent
 | 
			
		||||
@@ -4,17 +4,18 @@ from typing import Optional
 | 
			
		||||
import pytest
 | 
			
		||||
from flask import Flask
 | 
			
		||||
 | 
			
		||||
import fittrackee
 | 
			
		||||
from fittrackee.application.models import AppConfig
 | 
			
		||||
from fittrackee.users.models import User
 | 
			
		||||
 | 
			
		||||
from ..mixins import ApiTestCaseMixin
 | 
			
		||||
from ..utils import jsonify_dict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestGetConfig(ApiTestCaseMixin):
 | 
			
		||||
    def test_it_gets_application_config_for_unauthenticated_user(
 | 
			
		||||
        self, app: Flask
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        app_config = AppConfig.query.first()
 | 
			
		||||
        client = app.test_client()
 | 
			
		||||
 | 
			
		||||
        response = client.get('/api/config')
 | 
			
		||||
@@ -22,18 +23,7 @@ class TestGetConfig(ApiTestCaseMixin):
 | 
			
		||||
        data = json.loads(response.data.decode())
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert data['data']['admin_contact'] is None
 | 
			
		||||
        assert data['data']['gpx_limit_import'] == 10
 | 
			
		||||
        assert data['data']['is_registration_enabled'] is True
 | 
			
		||||
        assert data['data']['max_single_file_size'] == 1048576
 | 
			
		||||
        assert data['data']['max_zip_file_size'] == 10485760
 | 
			
		||||
        assert data['data']['max_users'] == 100
 | 
			
		||||
        assert data['data']['map_attribution'] == (
 | 
			
		||||
            '© <a href="http://www.openstreetmap.org/copyright" '
 | 
			
		||||
            'target="_blank" rel="noopener noreferrer">OpenStreetMap</a> '
 | 
			
		||||
            'contributors'
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['version'] == fittrackee.__version__
 | 
			
		||||
        assert data['data'] == jsonify_dict(app_config.serialize())
 | 
			
		||||
 | 
			
		||||
    def test_it_gets_application_config(
 | 
			
		||||
        self, app: Flask, user_1: User
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,51 @@
 | 
			
		||||
from flask import Flask
 | 
			
		||||
 | 
			
		||||
from fittrackee import VERSION
 | 
			
		||||
from fittrackee.application.models import AppConfig
 | 
			
		||||
from fittrackee.users.models import User
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestConfigModel:
 | 
			
		||||
    def test_application_config(self, app: Flask) -> None:
 | 
			
		||||
        app_config = AppConfig.query.first()
 | 
			
		||||
        assert 1 == app_config.id
 | 
			
		||||
        app_config.admin_contact = 'admin@example.com'
 | 
			
		||||
 | 
			
		||||
        assert app_config.is_registration_enabled is True
 | 
			
		||||
        assert (
 | 
			
		||||
            app_config.map_attribution
 | 
			
		||||
            == app.config['TILE_SERVER']['ATTRIBUTION']
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        serialized_app_config = app_config.serialize()
 | 
			
		||||
        assert serialized_app_config['gpx_limit_import'] == 10
 | 
			
		||||
        assert serialized_app_config['is_registration_enabled'] is True
 | 
			
		||||
        assert serialized_app_config['max_single_file_size'] == 1048576
 | 
			
		||||
        assert serialized_app_config['max_zip_file_size'] == 10485760
 | 
			
		||||
        assert serialized_app_config['max_users'] == 100
 | 
			
		||||
        assert serialized_app_config['map_attribution'] == (
 | 
			
		||||
            '© <a href="http://www.openstreetmap.org/copyright" '
 | 
			
		||||
            'target="_blank" rel="noopener noreferrer">OpenStreetMap</a> '
 | 
			
		||||
            'contributors'
 | 
			
		||||
        assert (
 | 
			
		||||
            serialized_app_config['admin_contact'] == app_config.admin_contact
 | 
			
		||||
        )
 | 
			
		||||
        assert 'admin_contact' in serialized_app_config
 | 
			
		||||
        assert (
 | 
			
		||||
            serialized_app_config['gpx_limit_import']
 | 
			
		||||
            == app_config.gpx_limit_import
 | 
			
		||||
        )
 | 
			
		||||
        assert serialized_app_config['is_registration_enabled'] is True
 | 
			
		||||
        assert (
 | 
			
		||||
            serialized_app_config['max_single_file_size']
 | 
			
		||||
            == app_config.max_single_file_size
 | 
			
		||||
        )
 | 
			
		||||
        assert (
 | 
			
		||||
            serialized_app_config['max_zip_file_size']
 | 
			
		||||
            == app_config.max_zip_file_size
 | 
			
		||||
        )
 | 
			
		||||
        assert serialized_app_config['max_users'] == app_config.max_users
 | 
			
		||||
        assert (
 | 
			
		||||
            serialized_app_config['map_attribution']
 | 
			
		||||
            == app_config.map_attribution
 | 
			
		||||
        )
 | 
			
		||||
        assert serialized_app_config['version'] == VERSION
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_registration_disabled_when_users_count_exceeds_limit(
 | 
			
		||||
        self, app: Flask, user_1: User, user_2: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        app_config = AppConfig.query.first()
 | 
			
		||||
        app_config.max_users = 2
 | 
			
		||||
        serialized_app_config = app_config.serialize()
 | 
			
		||||
 | 
			
		||||
        assert app_config.is_registration_enabled is False
 | 
			
		||||
        assert serialized_app_config['is_registration_enabled'] is False
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										81
									
								
								fittrackee/tests/fixtures/fixtures_workouts.py
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,6 +1,6 @@
 | 
			
		||||
import datetime
 | 
			
		||||
from io import BytesIO
 | 
			
		||||
from typing import Generator
 | 
			
		||||
from typing import Generator, List
 | 
			
		||||
from unittest.mock import Mock, patch
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
 | 
			
		||||
@@ -107,96 +107,105 @@ def workout_running_user_1() -> Workout:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture()
 | 
			
		||||
def seven_workouts_user_1() -> Workout:
 | 
			
		||||
    workout = Workout(
 | 
			
		||||
def seven_workouts_user_1() -> List[Workout]:
 | 
			
		||||
    workouts = []
 | 
			
		||||
    workout_1 = Workout(
 | 
			
		||||
        user_id=1,
 | 
			
		||||
        sport_id=1,
 | 
			
		||||
        workout_date=datetime.datetime.strptime('20/03/2017', '%d/%m/%Y'),
 | 
			
		||||
        distance=5,
 | 
			
		||||
        duration=datetime.timedelta(seconds=1024),
 | 
			
		||||
    )
 | 
			
		||||
    update_workout(workout)
 | 
			
		||||
    workout.ascent = 120
 | 
			
		||||
    workout.descent = 200
 | 
			
		||||
    db.session.add(workout)
 | 
			
		||||
    update_workout(workout_1)
 | 
			
		||||
    workout_1.ascent = 120
 | 
			
		||||
    workout_1.descent = 200
 | 
			
		||||
    db.session.add(workout_1)
 | 
			
		||||
    db.session.flush()
 | 
			
		||||
    workouts.append(workout_1)
 | 
			
		||||
 | 
			
		||||
    workout = Workout(
 | 
			
		||||
    workout_2 = Workout(
 | 
			
		||||
        user_id=1,
 | 
			
		||||
        sport_id=1,
 | 
			
		||||
        workout_date=datetime.datetime.strptime('01/06/2017', '%d/%m/%Y'),
 | 
			
		||||
        distance=10,
 | 
			
		||||
        duration=datetime.timedelta(seconds=3456),
 | 
			
		||||
    )
 | 
			
		||||
    update_workout(workout)
 | 
			
		||||
    workout.ascent = 100
 | 
			
		||||
    workout.descent = 80
 | 
			
		||||
    db.session.add(workout)
 | 
			
		||||
    update_workout(workout_2)
 | 
			
		||||
    workout_2.ascent = 100
 | 
			
		||||
    workout_2.descent = 80
 | 
			
		||||
    db.session.add(workout_2)
 | 
			
		||||
    db.session.flush()
 | 
			
		||||
    workouts.append(workout_2)
 | 
			
		||||
 | 
			
		||||
    workout = Workout(
 | 
			
		||||
    workout_3 = Workout(
 | 
			
		||||
        user_id=1,
 | 
			
		||||
        sport_id=1,
 | 
			
		||||
        workout_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'),
 | 
			
		||||
        distance=10,
 | 
			
		||||
        duration=datetime.timedelta(seconds=1024),
 | 
			
		||||
    )
 | 
			
		||||
    update_workout(workout)
 | 
			
		||||
    workout.ascent = 80
 | 
			
		||||
    workout.descent = 100
 | 
			
		||||
    db.session.add(workout)
 | 
			
		||||
    update_workout(workout_3)
 | 
			
		||||
    workout_3.ascent = 80
 | 
			
		||||
    workout_3.descent = 100
 | 
			
		||||
    db.session.add(workout_3)
 | 
			
		||||
    db.session.flush()
 | 
			
		||||
    workouts.append(workout_3)
 | 
			
		||||
 | 
			
		||||
    workout = Workout(
 | 
			
		||||
    workout_4 = Workout(
 | 
			
		||||
        user_id=1,
 | 
			
		||||
        sport_id=1,
 | 
			
		||||
        workout_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
 | 
			
		||||
        distance=1,
 | 
			
		||||
        duration=datetime.timedelta(seconds=600),
 | 
			
		||||
    )
 | 
			
		||||
    update_workout(workout)
 | 
			
		||||
    workout.ascent = 120
 | 
			
		||||
    workout.descent = 180
 | 
			
		||||
    db.session.add(workout)
 | 
			
		||||
    update_workout(workout_4)
 | 
			
		||||
    workout_4.ascent = 120
 | 
			
		||||
    workout_4.descent = 180
 | 
			
		||||
    db.session.add(workout_4)
 | 
			
		||||
    db.session.flush()
 | 
			
		||||
    workouts.append(workout_4)
 | 
			
		||||
 | 
			
		||||
    workout = Workout(
 | 
			
		||||
    workout_5 = Workout(
 | 
			
		||||
        user_id=1,
 | 
			
		||||
        sport_id=1,
 | 
			
		||||
        workout_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
 | 
			
		||||
        distance=10,
 | 
			
		||||
        duration=datetime.timedelta(seconds=1000),
 | 
			
		||||
    )
 | 
			
		||||
    update_workout(workout)
 | 
			
		||||
    workout.ascent = 100
 | 
			
		||||
    workout.descent = 200
 | 
			
		||||
    db.session.add(workout)
 | 
			
		||||
    update_workout(workout_5)
 | 
			
		||||
    workout_5.ascent = 100
 | 
			
		||||
    workout_5.descent = 200
 | 
			
		||||
    db.session.add(workout_5)
 | 
			
		||||
    db.session.flush()
 | 
			
		||||
    workouts.append(workout_5)
 | 
			
		||||
 | 
			
		||||
    workout = Workout(
 | 
			
		||||
    workout_6 = Workout(
 | 
			
		||||
        user_id=1,
 | 
			
		||||
        sport_id=1,
 | 
			
		||||
        workout_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'),
 | 
			
		||||
        distance=8,
 | 
			
		||||
        duration=datetime.timedelta(seconds=6000),
 | 
			
		||||
    )
 | 
			
		||||
    update_workout(workout)
 | 
			
		||||
    workout.ascent = 40
 | 
			
		||||
    workout.descent = 20
 | 
			
		||||
    db.session.add(workout)
 | 
			
		||||
    update_workout(workout_6)
 | 
			
		||||
    workout_6.ascent = 40
 | 
			
		||||
    workout_6.descent = 20
 | 
			
		||||
    db.session.add(workout_6)
 | 
			
		||||
    db.session.flush()
 | 
			
		||||
    workouts.append(workout_6)
 | 
			
		||||
 | 
			
		||||
    workout = Workout(
 | 
			
		||||
    workout_7 = Workout(
 | 
			
		||||
        user_id=1,
 | 
			
		||||
        sport_id=1,
 | 
			
		||||
        workout_date=datetime.datetime.strptime('09/05/2018', '%d/%m/%Y'),
 | 
			
		||||
        distance=10,
 | 
			
		||||
        duration=datetime.timedelta(seconds=3000),
 | 
			
		||||
    )
 | 
			
		||||
    update_workout(workout)
 | 
			
		||||
    db.session.add(workout)
 | 
			
		||||
    update_workout(workout_7)
 | 
			
		||||
    db.session.add(workout_7)
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
    return workout
 | 
			
		||||
    workouts.append(workout_7)
 | 
			
		||||
 | 
			
		||||
    return workouts
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture()
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ from uuid import uuid4
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from fittrackee.files import display_readable_file_size
 | 
			
		||||
from fittrackee.request import UserAgent
 | 
			
		||||
from fittrackee.utils import get_readable_duration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -42,3 +43,28 @@ class TestReadableDuration:
 | 
			
		||||
        readable_duration = get_readable_duration(30, locale)
 | 
			
		||||
 | 
			
		||||
        assert readable_duration == expected_duration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestParseUserAgent:
 | 
			
		||||
    string = (
 | 
			
		||||
        'Mozilla/5.0 (X11; Linux x86_64; rv:98.0) '
 | 
			
		||||
        'Gecko/20100101 Firefox/98.0'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_browser_name(self) -> None:
 | 
			
		||||
        user_agent = UserAgent(self.string)
 | 
			
		||||
        assert user_agent.browser == 'Firefox'
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_other_as_brother_name_when_empty_string_provided(
 | 
			
		||||
        self,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        user_agent = UserAgent('')
 | 
			
		||||
        assert user_agent.browser == 'Other'
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_operating_system(self) -> None:
 | 
			
		||||
        user_agent = UserAgent(self.string)
 | 
			
		||||
        assert user_agent.platform == 'Linux'
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_other_as_os_when_empty_string_provided(self) -> None:
 | 
			
		||||
        user_agent = UserAgent('')
 | 
			
		||||
        assert user_agent.platform == 'Other'
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,10 @@ from freezegun import freeze_time
 | 
			
		||||
 | 
			
		||||
from fittrackee.users.models import User, UserSportPreference
 | 
			
		||||
from fittrackee.users.utils.token import get_user_token
 | 
			
		||||
from fittrackee.workouts.models import Sport, Workout
 | 
			
		||||
from fittrackee.workouts.models import Sport
 | 
			
		||||
 | 
			
		||||
from ..mixins import ApiTestCaseMixin
 | 
			
		||||
from ..utils import jsonify_dict
 | 
			
		||||
 | 
			
		||||
USER_AGENT = (
 | 
			
		||||
    'Mozilla/5.0 (X11; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0'
 | 
			
		||||
@@ -284,8 +285,8 @@ class TestUserRegistration(ApiTestCaseMixin):
 | 
			
		||||
            {
 | 
			
		||||
                'username': username,
 | 
			
		||||
                'fittrackee_url': 'http://0.0.0.0:5000',
 | 
			
		||||
                'operating_system': 'linux',
 | 
			
		||||
                'browser_name': 'firefox',
 | 
			
		||||
                'operating_system': 'Linux',
 | 
			
		||||
                'browser_name': 'Firefox',
 | 
			
		||||
                'account_confirmation_url': (
 | 
			
		||||
                    'http://0.0.0.0:5000/account-confirmation'
 | 
			
		||||
                    f'?token={expected_token}'
 | 
			
		||||
@@ -453,9 +454,7 @@ class TestUserProfile(ApiTestCaseMixin):
 | 
			
		||||
 | 
			
		||||
        self.assert_401(response, 'invalid token, please log in again')
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_user_minimal_profile(
 | 
			
		||||
        self, app: Flask, user_1: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
    def test_it_returns_user(self, app: Flask, user_1: User) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
        )
 | 
			
		||||
@@ -468,92 +467,7 @@ class TestUserProfile(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        data = json.loads(response.data.decode())
 | 
			
		||||
        assert data['status'] == 'success'
 | 
			
		||||
        assert data['data'] is not None
 | 
			
		||||
        assert data['data']['username'] == 'test'
 | 
			
		||||
        assert data['data']['email'] == 'test@test.com'
 | 
			
		||||
        assert data['data']['created_at']
 | 
			
		||||
        assert not data['data']['admin']
 | 
			
		||||
        assert data['data']['timezone'] is None
 | 
			
		||||
        assert data['data']['weekm'] is False
 | 
			
		||||
        assert data['data']['imperial_units'] is False
 | 
			
		||||
        assert data['data']['language'] is None
 | 
			
		||||
        assert data['data']['nb_sports'] == 0
 | 
			
		||||
        assert data['data']['nb_workouts'] == 0
 | 
			
		||||
        assert data['data']['records'] == []
 | 
			
		||||
        assert data['data']['sports_list'] == []
 | 
			
		||||
        assert data['data']['total_distance'] == 0
 | 
			
		||||
        assert data['data']['total_duration'] == '0:00:00'
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_user_full_profile(
 | 
			
		||||
        self, app: Flask, user_1_full: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1_full.email
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        response = client.get(
 | 
			
		||||
            '/api/auth/profile',
 | 
			
		||||
            headers=dict(Authorization=f'Bearer {auth_token}'),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        data = json.loads(response.data.decode())
 | 
			
		||||
        assert data['status'] == 'success'
 | 
			
		||||
        assert data['data'] is not None
 | 
			
		||||
        assert data['data']['username'] == 'test'
 | 
			
		||||
        assert data['data']['email'] == 'test@test.com'
 | 
			
		||||
        assert data['data']['created_at']
 | 
			
		||||
        assert not data['data']['admin']
 | 
			
		||||
        assert data['data']['first_name'] == 'John'
 | 
			
		||||
        assert data['data']['last_name'] == 'Doe'
 | 
			
		||||
        assert data['data']['birth_date']
 | 
			
		||||
        assert data['data']['bio'] == 'just a random guy'
 | 
			
		||||
        assert data['data']['imperial_units'] is False
 | 
			
		||||
        assert data['data']['location'] == 'somewhere'
 | 
			
		||||
        assert data['data']['timezone'] == 'America/New_York'
 | 
			
		||||
        assert data['data']['weekm'] is False
 | 
			
		||||
        assert data['data']['language'] == 'en'
 | 
			
		||||
        assert data['data']['nb_sports'] == 0
 | 
			
		||||
        assert data['data']['nb_workouts'] == 0
 | 
			
		||||
        assert data['data']['records'] == []
 | 
			
		||||
        assert data['data']['sports_list'] == []
 | 
			
		||||
        assert data['data']['total_distance'] == 0
 | 
			
		||||
        assert data['data']['total_duration'] == '0:00:00'
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_user_profile_with_workouts(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        sport_2_running: Sport,
 | 
			
		||||
        workout_cycling_user_1: Workout,
 | 
			
		||||
        workout_running_user_1: Workout,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        response = client.get(
 | 
			
		||||
            '/api/auth/profile',
 | 
			
		||||
            headers=dict(Authorization=f'Bearer {auth_token}'),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        data = json.loads(response.data.decode())
 | 
			
		||||
        assert data['status'] == 'success'
 | 
			
		||||
        assert data['data'] is not None
 | 
			
		||||
        assert data['data']['username'] == 'test'
 | 
			
		||||
        assert data['data']['email'] == 'test@test.com'
 | 
			
		||||
        assert data['data']['created_at']
 | 
			
		||||
        assert not data['data']['admin']
 | 
			
		||||
        assert data['data']['timezone'] is None
 | 
			
		||||
        assert data['data']['imperial_units'] is False
 | 
			
		||||
        assert data['data']['nb_sports'] == 2
 | 
			
		||||
        assert data['data']['nb_workouts'] == 2
 | 
			
		||||
        assert len(data['data']['records']) == 8
 | 
			
		||||
        assert data['data']['sports_list'] == [1, 2]
 | 
			
		||||
        assert data['data']['total_distance'] == 22
 | 
			
		||||
        assert data['data']['total_duration'] == '2:40:00'
 | 
			
		||||
        assert data['data'] == jsonify_dict(user_1.serialize(user_1))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserProfileUpdate(ApiTestCaseMixin):
 | 
			
		||||
@@ -618,25 +532,7 @@ class TestUserProfileUpdate(ApiTestCaseMixin):
 | 
			
		||||
        data = json.loads(response.data.decode())
 | 
			
		||||
        assert data['status'] == 'success'
 | 
			
		||||
        assert data['message'] == 'user profile updated'
 | 
			
		||||
        assert data['data']['username'] == user_1.username
 | 
			
		||||
        assert data['data']['email'] == user_1.email
 | 
			
		||||
        assert not data['data']['admin']
 | 
			
		||||
        assert data['data']['created_at']
 | 
			
		||||
        assert data['data']['first_name'] == first_name
 | 
			
		||||
        assert data['data']['last_name'] == last_name
 | 
			
		||||
        assert data['data']['birth_date'] == 'Tue, 01 Jan 1980 00:00:00 GMT'
 | 
			
		||||
        assert data['data']['bio'] == bio
 | 
			
		||||
        assert data['data']['imperial_units'] is False
 | 
			
		||||
        assert data['data']['location'] == location
 | 
			
		||||
        assert data['data']['timezone'] is None
 | 
			
		||||
        assert data['data']['weekm'] is False
 | 
			
		||||
        assert data['data']['language'] is None
 | 
			
		||||
        assert data['data']['nb_sports'] == 0
 | 
			
		||||
        assert data['data']['nb_workouts'] == 0
 | 
			
		||||
        assert data['data']['records'] == []
 | 
			
		||||
        assert data['data']['sports_list'] == []
 | 
			
		||||
        assert data['data']['total_distance'] == 0
 | 
			
		||||
        assert data['data']['total_duration'] == '0:00:00'
 | 
			
		||||
        assert data['data'] == jsonify_dict(user_1.serialize(user_1))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserAccountUpdate(ApiTestCaseMixin):
 | 
			
		||||
@@ -911,8 +807,8 @@ class TestUserAccountUpdate(ApiTestCaseMixin):
 | 
			
		||||
            {
 | 
			
		||||
                'username': user_1.username,
 | 
			
		||||
                'fittrackee_url': 'http://0.0.0.0:5000',
 | 
			
		||||
                'operating_system': 'linux',
 | 
			
		||||
                'browser_name': 'firefox',
 | 
			
		||||
                'operating_system': 'Linux',
 | 
			
		||||
                'browser_name': 'Firefox',
 | 
			
		||||
                'new_email_address': new_email,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
@@ -953,8 +849,8 @@ class TestUserAccountUpdate(ApiTestCaseMixin):
 | 
			
		||||
            {
 | 
			
		||||
                'username': user_1.username,
 | 
			
		||||
                'fittrackee_url': 'http://0.0.0.0:5000',
 | 
			
		||||
                'operating_system': 'linux',
 | 
			
		||||
                'browser_name': 'firefox',
 | 
			
		||||
                'operating_system': 'Linux',
 | 
			
		||||
                'browser_name': 'Firefox',
 | 
			
		||||
                'email_confirmation_url': (
 | 
			
		||||
                    f'http://0.0.0.0:5000/email-update?token={expected_token}'
 | 
			
		||||
                ),
 | 
			
		||||
@@ -1113,8 +1009,8 @@ class TestUserAccountUpdate(ApiTestCaseMixin):
 | 
			
		||||
            {
 | 
			
		||||
                'username': user_1.username,
 | 
			
		||||
                'fittrackee_url': 'http://0.0.0.0:5000',
 | 
			
		||||
                'operating_system': 'linux',
 | 
			
		||||
                'browser_name': 'firefox',
 | 
			
		||||
                'operating_system': 'Linux',
 | 
			
		||||
                'browser_name': 'Firefox',
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@@ -1270,25 +1166,7 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
 | 
			
		||||
        data = json.loads(response.data.decode())
 | 
			
		||||
        assert data['status'] == 'success'
 | 
			
		||||
        assert data['message'] == 'user preferences updated'
 | 
			
		||||
        assert data['data']['username'] == user_1.username
 | 
			
		||||
        assert data['data']['email'] == user_1.email
 | 
			
		||||
        assert not data['data']['admin']
 | 
			
		||||
        assert data['data']['created_at']
 | 
			
		||||
        assert data['data']['first_name'] is None
 | 
			
		||||
        assert data['data']['last_name'] is None
 | 
			
		||||
        assert data['data']['birth_date'] is None
 | 
			
		||||
        assert data['data']['bio'] is None
 | 
			
		||||
        assert data['data']['imperial_units']
 | 
			
		||||
        assert data['data']['location'] is None
 | 
			
		||||
        assert data['data']['timezone'] == 'America/New_York'
 | 
			
		||||
        assert data['data']['weekm'] is True
 | 
			
		||||
        assert data['data']['language'] == 'fr'
 | 
			
		||||
        assert data['data']['nb_sports'] == 0
 | 
			
		||||
        assert data['data']['nb_workouts'] == 0
 | 
			
		||||
        assert data['data']['records'] == []
 | 
			
		||||
        assert data['data']['sports_list'] == []
 | 
			
		||||
        assert data['data']['total_distance'] == 0
 | 
			
		||||
        assert data['data']['total_duration'] == '0:00:00'
 | 
			
		||||
        assert data['data'] == jsonify_dict(user_1.serialize(user_1))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserSportPreferencesUpdate(ApiTestCaseMixin):
 | 
			
		||||
@@ -1812,8 +1690,8 @@ class TestPasswordResetRequest(ApiTestCaseMixin):
 | 
			
		||||
                    f'http://0.0.0.0:5000/password-reset?token={token}'
 | 
			
		||||
                ),
 | 
			
		||||
                'fittrackee_url': 'http://0.0.0.0:5000',
 | 
			
		||||
                'operating_system': 'linux',
 | 
			
		||||
                'browser_name': 'firefox',
 | 
			
		||||
                'operating_system': 'Linux',
 | 
			
		||||
                'browser_name': 'Firefox',
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@@ -2025,8 +1903,8 @@ class TestPasswordUpdate(ApiTestCaseMixin):
 | 
			
		||||
            {
 | 
			
		||||
                'username': user_1.username,
 | 
			
		||||
                'fittrackee_url': 'http://0.0.0.0:5000',
 | 
			
		||||
                'operating_system': 'linux',
 | 
			
		||||
                'browser_name': 'firefox',
 | 
			
		||||
                'operating_system': 'Linux',
 | 
			
		||||
                'browser_name': 'Firefox',
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@@ -2252,8 +2130,8 @@ class TestResendAccountConfirmationEmail(ApiTestCaseMixin):
 | 
			
		||||
            {
 | 
			
		||||
                'username': inactive_user.username,
 | 
			
		||||
                'fittrackee_url': 'http://0.0.0.0:5000',
 | 
			
		||||
                'operating_system': 'linux',
 | 
			
		||||
                'browser_name': 'firefox',
 | 
			
		||||
                'operating_system': 'Linux',
 | 
			
		||||
                'browser_name': 'Firefox',
 | 
			
		||||
                'account_confirmation_url': (
 | 
			
		||||
                    'http://0.0.0.0:5000/account-confirmation'
 | 
			
		||||
                    f'?token={expected_token}'
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ from fittrackee.utils import get_readable_duration
 | 
			
		||||
from fittrackee.workouts.models import Sport, Workout
 | 
			
		||||
 | 
			
		||||
from ..mixins import ApiTestCaseMixin
 | 
			
		||||
from ..utils import jsonify_dict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestGetUser(ApiTestCaseMixin):
 | 
			
		||||
@@ -28,6 +29,26 @@ class TestGetUser(ApiTestCaseMixin):
 | 
			
		||||
 | 
			
		||||
        self.assert_403(response)
 | 
			
		||||
 | 
			
		||||
    def test_user_can_access_his_profile(
 | 
			
		||||
        self, app: Flask, user_1: User, user_2: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        response = client.get(
 | 
			
		||||
            f'/api/users/{user_1.username}',
 | 
			
		||||
            content_type='application/json',
 | 
			
		||||
            headers=dict(Authorization=f'Bearer {auth_token}'),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        data = json.loads(response.data.decode())
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert data['status'] == 'success'
 | 
			
		||||
        assert len(data['data']['users']) == 1
 | 
			
		||||
        user = data['data']['users'][0]
 | 
			
		||||
        assert user['username'] == user_1.username
 | 
			
		||||
 | 
			
		||||
    def test_it_gets_inactive_user(
 | 
			
		||||
        self, app: Flask, user_1_admin: User, inactive_user: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
@@ -46,22 +67,7 @@ class TestGetUser(ApiTestCaseMixin):
 | 
			
		||||
        assert data['status'] == 'success'
 | 
			
		||||
        assert len(data['data']['users']) == 1
 | 
			
		||||
        user = data['data']['users'][0]
 | 
			
		||||
        assert user['username'] == inactive_user.username
 | 
			
		||||
        assert user['email'] == inactive_user.email
 | 
			
		||||
        assert user['created_at']
 | 
			
		||||
        assert not user['admin']
 | 
			
		||||
        assert not user['is_active']
 | 
			
		||||
        assert user['first_name'] is None
 | 
			
		||||
        assert user['last_name'] is None
 | 
			
		||||
        assert user['birth_date'] is None
 | 
			
		||||
        assert user['bio'] is None
 | 
			
		||||
        assert user['location'] is None
 | 
			
		||||
        assert user['nb_sports'] == 0
 | 
			
		||||
        assert user['nb_workouts'] == 0
 | 
			
		||||
        assert user['records'] == []
 | 
			
		||||
        assert user['sports_list'] == []
 | 
			
		||||
        assert user['total_distance'] == 0
 | 
			
		||||
        assert user['total_duration'] == '0:00:00'
 | 
			
		||||
        assert user == jsonify_dict(inactive_user.serialize(user_1_admin))
 | 
			
		||||
 | 
			
		||||
    def test_it_gets_single_user_without_workouts(
 | 
			
		||||
        self, app: Flask, user_1_admin: User, user_2: User
 | 
			
		||||
@@ -81,22 +87,7 @@ class TestGetUser(ApiTestCaseMixin):
 | 
			
		||||
        assert data['status'] == 'success'
 | 
			
		||||
        assert len(data['data']['users']) == 1
 | 
			
		||||
        user = data['data']['users'][0]
 | 
			
		||||
        assert user['username'] == user_2.username
 | 
			
		||||
        assert user['email'] == user_2.email
 | 
			
		||||
        assert user['created_at']
 | 
			
		||||
        assert not user['admin']
 | 
			
		||||
        assert user['is_active']
 | 
			
		||||
        assert user['first_name'] is None
 | 
			
		||||
        assert user['last_name'] is None
 | 
			
		||||
        assert user['birth_date'] is None
 | 
			
		||||
        assert user['bio'] is None
 | 
			
		||||
        assert user['location'] is None
 | 
			
		||||
        assert user['nb_sports'] == 0
 | 
			
		||||
        assert user['nb_workouts'] == 0
 | 
			
		||||
        assert user['records'] == []
 | 
			
		||||
        assert user['sports_list'] == []
 | 
			
		||||
        assert user['total_distance'] == 0
 | 
			
		||||
        assert user['total_duration'] == '0:00:00'
 | 
			
		||||
        assert user == jsonify_dict(user_2.serialize(user_1_admin))
 | 
			
		||||
 | 
			
		||||
    def test_it_gets_single_user_with_workouts(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -123,22 +114,7 @@ class TestGetUser(ApiTestCaseMixin):
 | 
			
		||||
        assert data['status'] == 'success'
 | 
			
		||||
        assert len(data['data']['users']) == 1
 | 
			
		||||
        user = data['data']['users'][0]
 | 
			
		||||
        assert user['username'] == user_1.username
 | 
			
		||||
        assert user['email'] == user_1.email
 | 
			
		||||
        assert user['created_at']
 | 
			
		||||
        assert not user['admin']
 | 
			
		||||
        assert user['is_active']
 | 
			
		||||
        assert user['first_name'] is None
 | 
			
		||||
        assert user['last_name'] is None
 | 
			
		||||
        assert user['birth_date'] is None
 | 
			
		||||
        assert user['bio'] is None
 | 
			
		||||
        assert user['location'] is None
 | 
			
		||||
        assert len(user['records']) == 8
 | 
			
		||||
        assert user['nb_sports'] == 2
 | 
			
		||||
        assert user['nb_workouts'] == 2
 | 
			
		||||
        assert user['sports_list'] == [1, 2]
 | 
			
		||||
        assert user['total_distance'] == 22
 | 
			
		||||
        assert user['total_duration'] == '2:40:00'
 | 
			
		||||
        assert user == jsonify_dict(user_1.serialize(user_2_admin))
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_error_if_user_does_not_exist(
 | 
			
		||||
        self, app: Flask, user_1_admin: User
 | 
			
		||||
@@ -187,106 +163,15 @@ class TestGetUsers(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['users']) == 3
 | 
			
		||||
        assert 'created_at' in data['data']['users'][0]
 | 
			
		||||
        assert 'created_at' in data['data']['users'][1]
 | 
			
		||||
        assert 'created_at' in data['data']['users'][2]
 | 
			
		||||
        assert 'admin' in data['data']['users'][0]['username']
 | 
			
		||||
        assert 'inactive' in data['data']['users'][1]['username']
 | 
			
		||||
        assert 'sam' in data['data']['users'][2]['username']
 | 
			
		||||
        assert 'admin@example.com' in data['data']['users'][0]['email']
 | 
			
		||||
        assert 'inactive@example.com' in data['data']['users'][1]['email']
 | 
			
		||||
        assert 'sam@test.com' in data['data']['users'][2]['email']
 | 
			
		||||
        assert data['data']['users'][0]['is_active']
 | 
			
		||||
        assert not data['data']['users'][1]['is_active']
 | 
			
		||||
        assert data['data']['users'][2]['is_active']
 | 
			
		||||
        assert data['data']['users'][0]['imperial_units'] is False
 | 
			
		||||
        assert data['data']['users'][0]['timezone'] is None
 | 
			
		||||
        assert data['data']['users'][0]['weekm'] is False
 | 
			
		||||
        assert data['data']['users'][0]['language'] is None
 | 
			
		||||
        assert data['data']['users'][0]['nb_sports'] == 0
 | 
			
		||||
        assert data['data']['users'][0]['nb_workouts'] == 0
 | 
			
		||||
        assert data['data']['users'][0]['records'] == []
 | 
			
		||||
        assert data['data']['users'][0]['sports_list'] == []
 | 
			
		||||
        assert data['data']['users'][0]['total_distance'] == 0
 | 
			
		||||
        assert data['data']['users'][0]['total_duration'] == '0:00:00'
 | 
			
		||||
        assert data['data']['users'][1]['nb_sports'] == 0
 | 
			
		||||
        assert data['data']['users'][1]['nb_workouts'] == 0
 | 
			
		||||
        assert data['data']['users'][1]['records'] == []
 | 
			
		||||
        assert data['data']['users'][1]['sports_list'] == []
 | 
			
		||||
        assert data['data']['users'][1]['total_distance'] == 0
 | 
			
		||||
        assert data['data']['users'][1]['total_duration'] == '0:00:00'
 | 
			
		||||
        assert data['data']['users'][2]['records'] == []
 | 
			
		||||
        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]['total_distance'] == 0
 | 
			
		||||
        assert data['data']['users'][2]['total_duration'] == '0:00:00'
 | 
			
		||||
        assert data['pagination'] == {
 | 
			
		||||
            'has_next': False,
 | 
			
		||||
            'has_prev': False,
 | 
			
		||||
            'page': 1,
 | 
			
		||||
            'pages': 1,
 | 
			
		||||
            'total': 3,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def test_it_gets_users_list_with_workouts(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1_admin: User,
 | 
			
		||||
        user_2: User,
 | 
			
		||||
        user_3: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        workout_cycling_user_1: Workout,
 | 
			
		||||
        sport_2_running: Sport,
 | 
			
		||||
        workout_running_user_1: Workout,
 | 
			
		||||
        workout_cycling_user_2: Workout,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1_admin.email
 | 
			
		||||
        assert data['data']['users'][0] == jsonify_dict(
 | 
			
		||||
            user_1_admin.serialize(user_1_admin)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        response = client.get(
 | 
			
		||||
            '/api/users',
 | 
			
		||||
            headers=dict(Authorization=f'Bearer {auth_token}'),
 | 
			
		||||
        assert data['data']['users'][1] == jsonify_dict(
 | 
			
		||||
            inactive_user.serialize(user_1_admin)
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['users'][2] == jsonify_dict(
 | 
			
		||||
            user_3.serialize(user_1_admin)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        data = json.loads(response.data.decode())
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['users']) == 3
 | 
			
		||||
        assert 'created_at' in data['data']['users'][0]
 | 
			
		||||
        assert 'created_at' in data['data']['users'][1]
 | 
			
		||||
        assert 'created_at' in data['data']['users'][2]
 | 
			
		||||
        assert 'admin' in data['data']['users'][0]['username']
 | 
			
		||||
        assert 'toto' in data['data']['users'][1]['username']
 | 
			
		||||
        assert 'sam' in data['data']['users'][2]['username']
 | 
			
		||||
        assert 'admin@example.com' in data['data']['users'][0]['email']
 | 
			
		||||
        assert 'toto@toto.com' in data['data']['users'][1]['email']
 | 
			
		||||
        assert 'sam@test.com' in data['data']['users'][2]['email']
 | 
			
		||||
        assert data['data']['users'][0]['is_active']
 | 
			
		||||
        assert data['data']['users'][1]['is_active']
 | 
			
		||||
        assert data['data']['users'][2]['is_active']
 | 
			
		||||
        assert data['data']['users'][0]['imperial_units'] is False
 | 
			
		||||
        assert data['data']['users'][0]['timezone'] is None
 | 
			
		||||
        assert data['data']['users'][0]['weekm'] is False
 | 
			
		||||
        assert data['data']['users'][0]['nb_sports'] == 2
 | 
			
		||||
        assert data['data']['users'][0]['nb_workouts'] == 2
 | 
			
		||||
        assert len(data['data']['users'][0]['records']) == 8
 | 
			
		||||
        assert data['data']['users'][0]['sports_list'] == [1, 2]
 | 
			
		||||
        assert data['data']['users'][0]['total_distance'] == 22.0
 | 
			
		||||
        assert data['data']['users'][0]['total_duration'] == '2:40:00'
 | 
			
		||||
        assert data['data']['users'][1]['nb_sports'] == 1
 | 
			
		||||
        assert data['data']['users'][1]['nb_workouts'] == 1
 | 
			
		||||
        assert len(data['data']['users'][1]['records']) == 4
 | 
			
		||||
        assert data['data']['users'][1]['sports_list'] == [1]
 | 
			
		||||
        assert data['data']['users'][1]['total_distance'] == 15
 | 
			
		||||
        assert data['data']['users'][1]['total_duration'] == '1:00:00'
 | 
			
		||||
        assert data['data']['users'][2]['nb_sports'] == 0
 | 
			
		||||
        assert data['data']['users'][2]['nb_workouts'] == 0
 | 
			
		||||
        assert len(data['data']['users'][2]['records']) == 0
 | 
			
		||||
        assert data['data']['users'][2]['sports_list'] == []
 | 
			
		||||
        assert data['data']['users'][2]['total_distance'] == 0
 | 
			
		||||
        assert data['data']['users'][2]['total_duration'] == '0:00:00'
 | 
			
		||||
        assert data['pagination'] == {
 | 
			
		||||
            'has_next': False,
 | 
			
		||||
            'has_prev': False,
 | 
			
		||||
 
 | 
			
		||||
@@ -9,77 +9,145 @@ from fittrackee.workouts.models import Sport, Workout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserModel:
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def assert_serialized_used(serialized_user: Dict) -> None:
 | 
			
		||||
        assert 'created_at' in serialized_user
 | 
			
		||||
        assert serialized_user['admin'] is False
 | 
			
		||||
        assert serialized_user['first_name'] is None
 | 
			
		||||
        assert serialized_user['is_active']
 | 
			
		||||
        assert serialized_user['last_name'] is None
 | 
			
		||||
        assert serialized_user['bio'] is None
 | 
			
		||||
        assert serialized_user['location'] is None
 | 
			
		||||
        assert serialized_user['birth_date'] is None
 | 
			
		||||
        assert serialized_user['picture'] is False
 | 
			
		||||
        assert serialized_user['nb_workouts'] == 0
 | 
			
		||||
 | 
			
		||||
    def test_user_model_as_auth_user(self, app: Flask, user_1: User) -> None:
 | 
			
		||||
    def test_it_returns_username_in_string_value(
 | 
			
		||||
        self, app: Flask, user_1: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        assert '<User \'test\'>' == str(user_1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserModelAssertMixin:
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def assert_user_account(serialized_user: Dict, user: User) -> None:
 | 
			
		||||
        assert serialized_user['admin'] == user.admin
 | 
			
		||||
        assert serialized_user['email_to_confirm'] == user.email_to_confirm
 | 
			
		||||
        assert serialized_user['is_active'] == user.is_active
 | 
			
		||||
        assert serialized_user['username'] == user.username
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def assert_user_profile(serialized_user: Dict, user: User) -> None:
 | 
			
		||||
        assert serialized_user['bio'] == user.bio
 | 
			
		||||
        assert serialized_user['birth_date'] == user.birth_date
 | 
			
		||||
        assert serialized_user['first_name'] == user.first_name
 | 
			
		||||
        assert serialized_user['last_name'] == user.last_name
 | 
			
		||||
        assert serialized_user['location'] == user.location
 | 
			
		||||
        assert serialized_user['picture'] is False
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def assert_workouts_keys_are_present(serialized_user: Dict) -> None:
 | 
			
		||||
        assert 'nb_sports' in serialized_user
 | 
			
		||||
        assert 'nb_workouts' in serialized_user
 | 
			
		||||
        assert 'records' in serialized_user
 | 
			
		||||
        assert 'sports_list' in serialized_user
 | 
			
		||||
        assert 'total_distance' in serialized_user
 | 
			
		||||
        assert 'total_duration' in serialized_user
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserSerializeAsAuthUser(UserModelAssertMixin):
 | 
			
		||||
    def test_it_returns_user_account_infos(
 | 
			
		||||
        self, app: Flask, user_1: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_1.serialize(user_1)
 | 
			
		||||
 | 
			
		||||
        self.assert_serialized_used(serialized_user)
 | 
			
		||||
        assert 'test' == serialized_user['username']
 | 
			
		||||
        assert 'test@test.com' == serialized_user['email']
 | 
			
		||||
        assert serialized_user['nb_sports'] == 0
 | 
			
		||||
        assert serialized_user['records'] == []
 | 
			
		||||
        assert serialized_user['sports_list'] == []
 | 
			
		||||
        assert serialized_user['total_distance'] == 0
 | 
			
		||||
        assert serialized_user['total_duration'] == '0:00:00'
 | 
			
		||||
        assert serialized_user['imperial_units'] is False
 | 
			
		||||
        assert serialized_user['language'] is None
 | 
			
		||||
        assert serialized_user['timezone'] is None
 | 
			
		||||
        assert serialized_user['weekm'] is False
 | 
			
		||||
        assert serialized_user['email_to_confirm'] is None
 | 
			
		||||
        assert 'confirmation_token' not in serialized_user
 | 
			
		||||
        self.assert_user_account(serialized_user, user_1)
 | 
			
		||||
 | 
			
		||||
    def test_user_model_as_admin(
 | 
			
		||||
    def test_it_returns_user_profile_infos(
 | 
			
		||||
        self, app: Flask, user_1: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_1.serialize(user_1)
 | 
			
		||||
 | 
			
		||||
        self.assert_user_profile(serialized_user, user_1)
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_user_preferences(
 | 
			
		||||
        self, app: Flask, user_1: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_1.serialize(user_1)
 | 
			
		||||
 | 
			
		||||
        assert serialized_user['imperial_units'] == user_1.imperial_units
 | 
			
		||||
        assert serialized_user['language'] == user_1.language
 | 
			
		||||
        assert serialized_user['timezone'] == user_1.timezone
 | 
			
		||||
        assert serialized_user['weekm'] == user_1.weekm
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_workouts_infos(self, app: Flask, user_1: User) -> None:
 | 
			
		||||
        serialized_user = user_1.serialize(user_1)
 | 
			
		||||
 | 
			
		||||
        self.assert_workouts_keys_are_present(serialized_user)
 | 
			
		||||
 | 
			
		||||
    def test_it_does_not_return_confirmation_token(
 | 
			
		||||
        self, app: Flask, user_1_admin: User, user_2: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_2.serialize(user_1_admin)
 | 
			
		||||
 | 
			
		||||
        assert 'confirmation_token' not in serialized_user
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserSerializeAsAdmin(UserModelAssertMixin):
 | 
			
		||||
    def test_it_returns_user_account_infos(
 | 
			
		||||
        self, app: Flask, user_1_admin: User, user_2: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_2.serialize(user_1_admin)
 | 
			
		||||
 | 
			
		||||
        self.assert_user_account(serialized_user, user_2)
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_user_profile_infos(
 | 
			
		||||
        self, app: Flask, user_1_admin: User, user_2: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_2.serialize(user_1_admin)
 | 
			
		||||
 | 
			
		||||
        self.assert_user_profile(serialized_user, user_1_admin)
 | 
			
		||||
 | 
			
		||||
    def test_it_does_return_user_preferences(
 | 
			
		||||
        self, app: Flask, user_1_admin: User, user_2: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_2.serialize(user_1_admin)
 | 
			
		||||
 | 
			
		||||
        self.assert_serialized_used(serialized_user)
 | 
			
		||||
        assert 'toto' == serialized_user['username']
 | 
			
		||||
        assert 'toto@toto.com' == serialized_user['email']
 | 
			
		||||
        assert serialized_user['nb_sports'] == 0
 | 
			
		||||
        assert serialized_user['records'] == []
 | 
			
		||||
        assert serialized_user['sports_list'] == []
 | 
			
		||||
        assert serialized_user['total_distance'] == 0
 | 
			
		||||
        assert serialized_user['total_duration'] == '0:00:00'
 | 
			
		||||
        assert serialized_user['email_to_confirm'] is None
 | 
			
		||||
        assert 'imperial_units' not in serialized_user
 | 
			
		||||
        assert 'language' not in serialized_user
 | 
			
		||||
        assert 'timezone' not in serialized_user
 | 
			
		||||
        assert 'weekm' not in serialized_user
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_workouts_infos(
 | 
			
		||||
        self, app: Flask, user_1_admin: User, user_2: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_2.serialize(user_1_admin)
 | 
			
		||||
 | 
			
		||||
        self.assert_workouts_keys_are_present(serialized_user)
 | 
			
		||||
 | 
			
		||||
    def test_it_does_not_return_confirmation_token(
 | 
			
		||||
        self, app: Flask, user_1_admin: User, user_2: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_2.serialize(user_1_admin)
 | 
			
		||||
 | 
			
		||||
        assert 'confirmation_token' not in serialized_user
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestInactiveUserSerialize(UserModelAssertMixin):
 | 
			
		||||
    def test_it_returns_is_active_to_false_for_inactive_user(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        inactive_user: User,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = inactive_user.serialize(inactive_user)
 | 
			
		||||
 | 
			
		||||
        assert serialized_user['is_active'] is False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserSerializeAsRegularUser(UserModelAssertMixin):
 | 
			
		||||
    def test_user_model_as_regular_user(
 | 
			
		||||
        self, app: Flask, user_1: User, user_2: User
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        with pytest.raises(UserNotFoundException):
 | 
			
		||||
            user_2.serialize(user_1)
 | 
			
		||||
 | 
			
		||||
    def test_encode_auth_token(self, app: Flask, user_1: User) -> None:
 | 
			
		||||
        auth_token = user_1.encode_auth_token(user_1.id)
 | 
			
		||||
        assert isinstance(auth_token, str)
 | 
			
		||||
 | 
			
		||||
    def test_encode_password_token(self, app: Flask, user_1: User) -> None:
 | 
			
		||||
        password_token = user_1.encode_password_reset_token(user_1.id)
 | 
			
		||||
        assert isinstance(password_token, str)
 | 
			
		||||
class TestUserRecords(UserModelAssertMixin):
 | 
			
		||||
    def test_it_returns_empty_list_when_no_workouts(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_1.serialize(user_1)
 | 
			
		||||
 | 
			
		||||
    def test_decode_auth_token(self, app: Flask, user_1: User) -> None:
 | 
			
		||||
        auth_token = user_1.encode_auth_token(user_1.id)
 | 
			
		||||
        assert isinstance(auth_token, str)
 | 
			
		||||
        assert User.decode_auth_token(auth_token) == user_1.id
 | 
			
		||||
        assert serialized_user['records'] == []
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_user_records(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -100,14 +168,79 @@ class TestUserModel:
 | 
			
		||||
        )
 | 
			
		||||
        assert serialized_user['records'][0]['workout_date']
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_is_active_to_false_fot_inactive_user(
 | 
			
		||||
 | 
			
		||||
class TestUserWorkouts(UserModelAssertMixin):
 | 
			
		||||
    def test_it_returns_infos_when_no_workouts(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        inactive_user: User,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = inactive_user.serialize(inactive_user)
 | 
			
		||||
        serialized_user = user_1.serialize(user_1)
 | 
			
		||||
 | 
			
		||||
        assert serialized_user['is_active'] is False
 | 
			
		||||
        assert serialized_user['nb_sports'] == 0
 | 
			
		||||
        assert serialized_user['nb_workouts'] == 0
 | 
			
		||||
        assert serialized_user['sports_list'] == []
 | 
			
		||||
        assert serialized_user['total_distance'] == 0
 | 
			
		||||
        assert serialized_user['total_duration'] == '0:00:00'
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_infos_when_only_one_workout_exists(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        workout_cycling_user_1: Workout,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_1.serialize(user_1)
 | 
			
		||||
 | 
			
		||||
        assert serialized_user['nb_sports'] == 1
 | 
			
		||||
        assert serialized_user['nb_workouts'] == 1
 | 
			
		||||
        assert serialized_user['sports_list'] == [sport_1_cycling.id]
 | 
			
		||||
        assert (
 | 
			
		||||
            serialized_user['total_distance']
 | 
			
		||||
            == workout_cycling_user_1.distance
 | 
			
		||||
        )
 | 
			
		||||
        assert serialized_user['total_duration'] == str(
 | 
			
		||||
            workout_cycling_user_1.duration
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_infos_when_several_sports(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        sport_2_running: Sport,
 | 
			
		||||
        workout_cycling_user_1: Workout,
 | 
			
		||||
        workout_running_user_1: Workout,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_user = user_1.serialize(user_1)
 | 
			
		||||
 | 
			
		||||
        assert serialized_user['nb_sports'] == 2
 | 
			
		||||
        assert serialized_user['nb_workouts'] == 2
 | 
			
		||||
        assert serialized_user['sports_list'] == [
 | 
			
		||||
            sport_1_cycling.id,
 | 
			
		||||
            sport_2_running.id,
 | 
			
		||||
        ]
 | 
			
		||||
        assert serialized_user['total_distance'] == (
 | 
			
		||||
            workout_cycling_user_1.distance + workout_running_user_1.distance
 | 
			
		||||
        )
 | 
			
		||||
        assert serialized_user['total_duration'] == str(
 | 
			
		||||
            workout_cycling_user_1.duration + workout_running_user_1.duration
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserModelToken:
 | 
			
		||||
    def test_encode_auth_token(self, app: Flask, user_1: User) -> None:
 | 
			
		||||
        auth_token = user_1.encode_auth_token(user_1.id)
 | 
			
		||||
        assert isinstance(auth_token, str)
 | 
			
		||||
 | 
			
		||||
    def test_encode_password_token(self, app: Flask, user_1: User) -> None:
 | 
			
		||||
        password_token = user_1.encode_password_reset_token(user_1.id)
 | 
			
		||||
        assert isinstance(password_token, str)
 | 
			
		||||
 | 
			
		||||
    def test_decode_auth_token(self, app: Flask, user_1: User) -> None:
 | 
			
		||||
        auth_token = user_1.encode_auth_token(user_1.id)
 | 
			
		||||
        assert isinstance(auth_token, str)
 | 
			
		||||
        assert User.decode_auth_token(auth_token) == user_1.id
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserSportModel:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
import random
 | 
			
		||||
import string
 | 
			
		||||
from typing import Optional
 | 
			
		||||
from json import loads
 | 
			
		||||
from typing import Dict, Optional
 | 
			
		||||
 | 
			
		||||
from flask import json as flask_json
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def random_string(
 | 
			
		||||
@@ -23,3 +26,7 @@ def random_string(
 | 
			
		||||
 | 
			
		||||
def random_email() -> str:
 | 
			
		||||
    return random_string(suffix='@example.com')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def jsonify_dict(data: Dict) -> Dict:
 | 
			
		||||
    return loads(flask_json.dumps(data))
 | 
			
		||||
 
 | 
			
		||||
@@ -14,26 +14,34 @@ class TestRecordModel:
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        workout_cycling_user_1: Workout,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        record_type = 'LD'
 | 
			
		||||
 | 
			
		||||
        record_ld = Record.query.filter_by(
 | 
			
		||||
            user_id=workout_cycling_user_1.user_id,
 | 
			
		||||
            sport_id=workout_cycling_user_1.sport_id,
 | 
			
		||||
            record_type='LD',
 | 
			
		||||
            record_type=record_type,
 | 
			
		||||
        ).first()
 | 
			
		||||
        assert 'test' == record_ld.user.username
 | 
			
		||||
        assert 1 == record_ld.sport_id
 | 
			
		||||
        assert 1 == record_ld.workout_id
 | 
			
		||||
        assert 'LD' == record_ld.record_type
 | 
			
		||||
        assert '2018-01-01 00:00:00' == str(record_ld.workout_date)
 | 
			
		||||
 | 
			
		||||
        assert record_ld.user.username == user_1.username
 | 
			
		||||
        assert record_ld.sport_id == sport_1_cycling.id
 | 
			
		||||
        assert record_ld.workout_id == workout_cycling_user_1.sport_id
 | 
			
		||||
        assert record_ld.record_type == record_type
 | 
			
		||||
        assert str(record_ld.workout_date) == str(
 | 
			
		||||
            workout_cycling_user_1.workout_date
 | 
			
		||||
        )
 | 
			
		||||
        assert record_ld.value == workout_cycling_user_1.duration
 | 
			
		||||
 | 
			
		||||
        assert '<Record Cycling - LD - 2018-01-01>' == str(record_ld)
 | 
			
		||||
 | 
			
		||||
        record_serialize = record_ld.serialize()
 | 
			
		||||
        assert 'id' in record_serialize
 | 
			
		||||
        assert 'user' in record_serialize
 | 
			
		||||
        assert 'sport_id' in record_serialize
 | 
			
		||||
        assert 'workout_id' in record_serialize
 | 
			
		||||
        assert 'record_type' in record_serialize
 | 
			
		||||
        assert 'workout_date' in record_serialize
 | 
			
		||||
        assert 'value' in record_serialize
 | 
			
		||||
 | 
			
		||||
        record_serialize['id'] = record_ld.id
 | 
			
		||||
        record_serialize['record_type'] = record_ld.record_type
 | 
			
		||||
        record_serialize['sport_id'] = record_ld.sport_id
 | 
			
		||||
        record_serialize['user'] = record_ld.user.username
 | 
			
		||||
        record_serialize['value'] = record_ld.value
 | 
			
		||||
        record_serialize['workout_id'] = record_ld.workout_id
 | 
			
		||||
        record_serialize['workout_date'] = record_ld.workout_date
 | 
			
		||||
 | 
			
		||||
    def test_record_model_with_none_value(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -48,12 +56,7 @@ class TestRecordModel:
 | 
			
		||||
            record_type='LD',
 | 
			
		||||
        ).first()
 | 
			
		||||
        record_ld.value = None
 | 
			
		||||
        assert 'test' == record_ld.user.username
 | 
			
		||||
        assert 1 == record_ld.sport_id
 | 
			
		||||
        assert 1 == record_ld.workout_id
 | 
			
		||||
        assert 'LD' == record_ld.record_type
 | 
			
		||||
        assert '2018-01-01 00:00:00' == str(record_ld.workout_date)
 | 
			
		||||
        assert '<Record Cycling - LD - 2018-01-01>' == str(record_ld)
 | 
			
		||||
 | 
			
		||||
        assert record_ld.value is None
 | 
			
		||||
 | 
			
		||||
        record_serialize = record_ld.serialize()
 | 
			
		||||
@@ -80,7 +83,7 @@ class TestRecordModel:
 | 
			
		||||
        assert record_serialize.get('value') == 10.0
 | 
			
		||||
        assert isinstance(record_serialize.get('value'), float)
 | 
			
		||||
 | 
			
		||||
    def test_add_farest_distance_records(
 | 
			
		||||
    def test_add_farthest_distance_records(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
 
 | 
			
		||||
@@ -7,41 +7,7 @@ from fittrackee.users.models import User, UserSportPreference
 | 
			
		||||
from fittrackee.workouts.models import Sport, Workout
 | 
			
		||||
 | 
			
		||||
from ..mixins import ApiTestCaseMixin
 | 
			
		||||
 | 
			
		||||
expected_sport_1_cycling_result = {
 | 
			
		||||
    'id': 1,
 | 
			
		||||
    'label': 'Cycling',
 | 
			
		||||
    'is_active': True,
 | 
			
		||||
    'is_active_for_user': True,
 | 
			
		||||
    'color': None,
 | 
			
		||||
    'stopped_speed_threshold': 1,
 | 
			
		||||
}
 | 
			
		||||
expected_sport_1_cycling_admin_result = expected_sport_1_cycling_result.copy()
 | 
			
		||||
expected_sport_1_cycling_admin_result['has_workouts'] = False
 | 
			
		||||
 | 
			
		||||
expected_sport_2_running_result = {
 | 
			
		||||
    'id': 2,
 | 
			
		||||
    'label': 'Running',
 | 
			
		||||
    'is_active': True,
 | 
			
		||||
    'is_active_for_user': True,
 | 
			
		||||
    'color': None,
 | 
			
		||||
    'stopped_speed_threshold': 0.1,
 | 
			
		||||
}
 | 
			
		||||
expected_sport_2_running_admin_result = expected_sport_2_running_result.copy()
 | 
			
		||||
expected_sport_2_running_admin_result['has_workouts'] = False
 | 
			
		||||
 | 
			
		||||
expected_sport_1_cycling_inactive_result = {
 | 
			
		||||
    'id': 1,
 | 
			
		||||
    'label': 'Cycling',
 | 
			
		||||
    'is_active': False,
 | 
			
		||||
    'is_active_for_user': False,
 | 
			
		||||
    'color': None,
 | 
			
		||||
    'stopped_speed_threshold': 1,
 | 
			
		||||
}
 | 
			
		||||
expected_sport_1_cycling_inactive_admin_result = (
 | 
			
		||||
    expected_sport_1_cycling_inactive_result.copy()
 | 
			
		||||
)
 | 
			
		||||
expected_sport_1_cycling_inactive_admin_result['has_workouts'] = False
 | 
			
		||||
from ..utils import jsonify_dict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestGetSports(ApiTestCaseMixin):
 | 
			
		||||
@@ -75,8 +41,12 @@ class TestGetSports(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['sports']) == 2
 | 
			
		||||
        assert data['data']['sports'][0] == expected_sport_1_cycling_result
 | 
			
		||||
        assert data['data']['sports'][1] == expected_sport_2_running_result
 | 
			
		||||
        assert data['data']['sports'][0] == jsonify_dict(
 | 
			
		||||
            sport_1_cycling.serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['sports'][1] == jsonify_dict(
 | 
			
		||||
            sport_2_running.serialize()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_it_gets_all_sports_with_inactive_one(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -98,11 +68,12 @@ class TestGetSports(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['sports']) == 2
 | 
			
		||||
        assert (
 | 
			
		||||
            data['data']['sports'][0]
 | 
			
		||||
            == expected_sport_1_cycling_inactive_result
 | 
			
		||||
        assert data['data']['sports'][0] == jsonify_dict(
 | 
			
		||||
            sport_1_cycling_inactive.serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['sports'][1] == jsonify_dict(
 | 
			
		||||
            sport_2_running.serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['sports'][1] == expected_sport_2_running_result
 | 
			
		||||
 | 
			
		||||
    def test_it_gets_all_sports_with_admin_rights(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -124,12 +95,11 @@ class TestGetSports(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['sports']) == 2
 | 
			
		||||
        assert (
 | 
			
		||||
            data['data']['sports'][0]
 | 
			
		||||
            == expected_sport_1_cycling_inactive_admin_result
 | 
			
		||||
        assert data['data']['sports'][0] == jsonify_dict(
 | 
			
		||||
            sport_1_cycling_inactive.serialize(is_admin=True)
 | 
			
		||||
        )
 | 
			
		||||
        assert (
 | 
			
		||||
            data['data']['sports'][1] == expected_sport_2_running_admin_result
 | 
			
		||||
        assert data['data']['sports'][1] == jsonify_dict(
 | 
			
		||||
            sport_2_running.serialize(is_admin=True)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_it_gets_sports_with_auth_user_preferences(
 | 
			
		||||
@@ -158,11 +128,14 @@ class TestGetSports(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['sports']) == 2
 | 
			
		||||
        assert data['data']['sports'][0]['color'] == '#000000'
 | 
			
		||||
        assert data['data']['sports'][0]['stopped_speed_threshold'] == 0.5
 | 
			
		||||
        assert data['data']['sports'][0]['is_active_for_user'] is False
 | 
			
		||||
        assert (
 | 
			
		||||
            data['data']['sports'][1] == expected_sport_2_running_admin_result
 | 
			
		||||
        assert data['data']['sports'][0] == jsonify_dict(
 | 
			
		||||
            sport_1_cycling.serialize(
 | 
			
		||||
                is_admin=True,
 | 
			
		||||
                sport_preferences=user_admin_sport_1_preference.serialize(),
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['sports'][1] == jsonify_dict(
 | 
			
		||||
            sport_2_running.serialize(is_admin=True)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -183,7 +156,9 @@ class TestGetSport(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['sports']) == 1
 | 
			
		||||
        assert data['data']['sports'][0] == expected_sport_1_cycling_result
 | 
			
		||||
        assert data['data']['sports'][0] == jsonify_dict(
 | 
			
		||||
            sport_1_cycling.serialize()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_it_gets_a_sport_with_preferences(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -205,7 +180,11 @@ class TestGetSport(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['sports']) == 1
 | 
			
		||||
        assert data['data']['sports'][0] == expected_sport_1_cycling_result
 | 
			
		||||
        assert data['data']['sports'][0] == jsonify_dict(
 | 
			
		||||
            sport_1_cycling.serialize(
 | 
			
		||||
                sport_preferences=user_sport_1_preference.serialize()
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_404_if_sport_does_not_exist(
 | 
			
		||||
        self, app: Flask, user_1: User
 | 
			
		||||
@@ -238,9 +217,8 @@ class TestGetSport(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['sports']) == 1
 | 
			
		||||
        assert (
 | 
			
		||||
            data['data']['sports'][0]
 | 
			
		||||
            == expected_sport_1_cycling_inactive_result
 | 
			
		||||
        assert data['data']['sports'][0] == jsonify_dict(
 | 
			
		||||
            sport_1_cycling_inactive.serialize()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_it_get_an_inactive_sport_with_admin_rights(
 | 
			
		||||
@@ -259,9 +237,8 @@ class TestGetSport(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['sports']) == 1
 | 
			
		||||
        assert (
 | 
			
		||||
            data['data']['sports'][0]
 | 
			
		||||
            == expected_sport_1_cycling_inactive_admin_result
 | 
			
		||||
        assert data['data']['sports'][0] == jsonify_dict(
 | 
			
		||||
            sport_1_cycling_inactive.serialize(is_admin=True)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -388,7 +365,6 @@ class TestUpdateSport(ApiTestCaseMixin):
 | 
			
		||||
        assert len(data['data']['sports']) == 1
 | 
			
		||||
        assert data['data']['sports'][0]['is_active'] is False
 | 
			
		||||
        assert data['data']['sports'][0]['is_active_for_user'] is False
 | 
			
		||||
        assert data['data']['sports'][0]['is_active_for_user'] is False
 | 
			
		||||
        assert data['data']['sports'][0]['has_workouts'] is False
 | 
			
		||||
 | 
			
		||||
    def test_it_enables_a_sport_with_preferences(
 | 
			
		||||
 
 | 
			
		||||
@@ -17,15 +17,17 @@ class TestSportModel:
 | 
			
		||||
        assert '<Sport \'Cycling\'>' == str(sport)
 | 
			
		||||
 | 
			
		||||
        serialized_sport = sport.serialize(is_admin=is_admin)
 | 
			
		||||
        assert 1 == serialized_sport['id']
 | 
			
		||||
        assert 'Cycling' == serialized_sport['label']
 | 
			
		||||
        assert serialized_sport['label'] == sport.label
 | 
			
		||||
        assert serialized_sport['id'] == sport.id
 | 
			
		||||
        assert serialized_sport['is_active'] is True
 | 
			
		||||
        assert serialized_sport['is_active_for_user'] is True
 | 
			
		||||
        assert serialized_sport['color'] is None
 | 
			
		||||
        assert serialized_sport['stopped_speed_threshold'] == 1
 | 
			
		||||
        return serialized_sport
 | 
			
		||||
 | 
			
		||||
    def test_sport_model(self, app: Flask, sport_1_cycling: Sport) -> None:
 | 
			
		||||
    def test_sport_model_without_workout(
 | 
			
		||||
        self, app: Flask, sport_1_cycling: Sport
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_sport = self.assert_sport_model(sport_1_cycling)
 | 
			
		||||
        assert 'has_workouts' not in serialized_sport
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import json
 | 
			
		||||
from typing import List
 | 
			
		||||
from unittest.mock import patch
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
 | 
			
		||||
@@ -8,6 +9,7 @@ from fittrackee.users.models import User
 | 
			
		||||
from fittrackee.workouts.models import Sport, Workout
 | 
			
		||||
 | 
			
		||||
from ..mixins import ApiTestCaseMixin
 | 
			
		||||
from ..utils import jsonify_dict
 | 
			
		||||
from .utils import get_random_short_id
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -45,25 +47,12 @@ class TestGetWorkouts(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['workouts']) == 2
 | 
			
		||||
        assert 'creation_date' in data['data']['workouts'][0]
 | 
			
		||||
        assert (
 | 
			
		||||
            'Sun, 01 Apr 2018 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][0]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][0] == jsonify_dict(
 | 
			
		||||
            workout_running_user_1.serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert 'test' == data['data']['workouts'][0]['user']
 | 
			
		||||
        assert 2 == data['data']['workouts'][0]['sport_id']
 | 
			
		||||
        assert 12.0 == data['data']['workouts'][0]['distance']
 | 
			
		||||
        assert '1:40:00' == data['data']['workouts'][0]['duration']
 | 
			
		||||
 | 
			
		||||
        assert 'creation_date' in data['data']['workouts'][1]
 | 
			
		||||
        assert (
 | 
			
		||||
            'Mon, 01 Jan 2018 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][1]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][1] == jsonify_dict(
 | 
			
		||||
            workout_cycling_user_1.serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert 'test' == data['data']['workouts'][1]['user']
 | 
			
		||||
        assert 1 == data['data']['workouts'][1]['sport_id']
 | 
			
		||||
        assert 10.0 == data['data']['workouts'][1]['distance']
 | 
			
		||||
        assert '1:00:00' == data['data']['workouts'][1]['duration']
 | 
			
		||||
        assert data['pagination'] == {
 | 
			
		||||
            'has_next': False,
 | 
			
		||||
            'has_prev': False,
 | 
			
		||||
@@ -119,7 +108,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -134,18 +123,22 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['workouts']) == 5
 | 
			
		||||
        assert 'creation_date' in data['data']['workouts'][0]
 | 
			
		||||
        assert (
 | 
			
		||||
            'Wed, 09 May 2018 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][0]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][0] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[6].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert '0:50:00' == data['data']['workouts'][0]['duration']
 | 
			
		||||
        assert 'creation_date' in data['data']['workouts'][4]
 | 
			
		||||
        assert (
 | 
			
		||||
            'Mon, 01 Jan 2018 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][4]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][1] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[5].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert '0:17:04' == data['data']['workouts'][4]['duration']
 | 
			
		||||
        assert data['data']['workouts'][2] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[3].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['workouts'][3] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[4].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['workouts'][4] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[2].serialize()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        assert data['pagination'] == {
 | 
			
		||||
            'has_next': True,
 | 
			
		||||
            'has_prev': False,
 | 
			
		||||
@@ -159,7 +152,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -174,18 +167,21 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['workouts']) == 5
 | 
			
		||||
        assert 'creation_date' in data['data']['workouts'][0]
 | 
			
		||||
        assert (
 | 
			
		||||
            'Wed, 09 May 2018 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][0]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][0] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[6].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert '0:50:00' == data['data']['workouts'][0]['duration']
 | 
			
		||||
        assert 'creation_date' in data['data']['workouts'][4]
 | 
			
		||||
        assert (
 | 
			
		||||
            'Mon, 01 Jan 2018 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][4]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][1] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[5].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['workouts'][2] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[3].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['workouts'][3] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[4].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['workouts'][4] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[2].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert '0:17:04' == data['data']['workouts'][4]['duration']
 | 
			
		||||
        assert data['pagination'] == {
 | 
			
		||||
            'has_next': True,
 | 
			
		||||
            'has_prev': False,
 | 
			
		||||
@@ -199,7 +195,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -214,18 +210,12 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['workouts']) == 2
 | 
			
		||||
        assert 'creation_date' in data['data']['workouts'][0]
 | 
			
		||||
        assert (
 | 
			
		||||
            'Thu, 01 Jun 2017 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][0]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][0] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[1].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert '0:57:36' == data['data']['workouts'][0]['duration']
 | 
			
		||||
        assert 'creation_date' in data['data']['workouts'][1]
 | 
			
		||||
        assert (
 | 
			
		||||
            'Mon, 20 Mar 2017 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][1]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][1] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[0].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert '0:17:04' == data['data']['workouts'][1]['duration']
 | 
			
		||||
        assert data['pagination'] == {
 | 
			
		||||
            'has_next': False,
 | 
			
		||||
            'has_prev': True,
 | 
			
		||||
@@ -239,7 +229,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -267,7 +257,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -286,7 +276,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -301,13 +291,11 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['workouts']) == 6
 | 
			
		||||
        assert (
 | 
			
		||||
            'Wed, 09 May 2018 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][0]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][0] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[6].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert (
 | 
			
		||||
            'Thu, 01 Jun 2017 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][5]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][5] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[1].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert data['pagination'] == {
 | 
			
		||||
            'has_next': True,
 | 
			
		||||
@@ -323,7 +311,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -338,13 +326,14 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['workouts']) == 3
 | 
			
		||||
        assert (
 | 
			
		||||
            'Wed, 09 May 2018 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][0]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][0] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[6].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert (
 | 
			
		||||
            'Fri, 23 Feb 2018 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][2]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][1] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[5].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert data['data']['workouts'][2] == jsonify_dict(
 | 
			
		||||
            seven_workouts_user_1[4].serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert data['pagination'] == {
 | 
			
		||||
            'has_next': True,
 | 
			
		||||
@@ -361,7 +350,7 @@ class TestGetWorkoutsWithOrder(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -397,7 +386,7 @@ class TestGetWorkoutsWithOrder(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -433,7 +422,7 @@ class TestGetWorkoutsWithOrder(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -471,7 +460,7 @@ class TestGetWorkoutsWithOrderBy(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -507,7 +496,7 @@ class TestGetWorkoutsWithOrderBy(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -537,7 +526,7 @@ class TestGetWorkoutsWithOrderBy(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -567,7 +556,7 @@ class TestGetWorkoutsWithOrderBy(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -599,7 +588,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -625,7 +614,6 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
 | 
			
		||||
            'Fri, 23 Feb 2018 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][1]['workout_date']
 | 
			
		||||
        )
 | 
			
		||||
        assert '0:10:00' == data['data']['workouts'][1]['duration']
 | 
			
		||||
        assert data['pagination'] == {
 | 
			
		||||
            'has_next': False,
 | 
			
		||||
            'has_prev': False,
 | 
			
		||||
@@ -639,7 +627,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -667,7 +655,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -704,7 +692,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -740,7 +728,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -776,7 +764,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -808,7 +796,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -876,7 +864,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
        sport_2_running: Sport,
 | 
			
		||||
        workout_running_user_1: Workout,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
@@ -912,7 +900,7 @@ class TestGetWorkoutsWithFiltersAndPagination(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -948,7 +936,7 @@ class TestGetWorkoutsWithFiltersAndPagination(ApiTestCaseMixin):
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        seven_workouts_user_1: Workout,
 | 
			
		||||
        seven_workouts_user_1: List[Workout],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        client, auth_token = self.get_test_client_and_auth_token(
 | 
			
		||||
            app, user_1.email
 | 
			
		||||
@@ -1001,15 +989,9 @@ class TestGetWorkout(ApiTestCaseMixin):
 | 
			
		||||
        assert response.status_code == 200
 | 
			
		||||
        assert 'success' in data['status']
 | 
			
		||||
        assert len(data['data']['workouts']) == 1
 | 
			
		||||
        assert 'creation_date' in data['data']['workouts'][0]
 | 
			
		||||
        assert (
 | 
			
		||||
            'Mon, 01 Jan 2018 00:00:00 GMT'
 | 
			
		||||
            == data['data']['workouts'][0]['workout_date']
 | 
			
		||||
        assert data['data']['workouts'][0] == jsonify_dict(
 | 
			
		||||
            workout_cycling_user_1.serialize()
 | 
			
		||||
        )
 | 
			
		||||
        assert 'test' == data['data']['workouts'][0]['user']
 | 
			
		||||
        assert 1 == data['data']['workouts'][0]['sport_id']
 | 
			
		||||
        assert 10.0 == data['data']['workouts'][0]['distance']
 | 
			
		||||
        assert '1:00:00' == data['data']['workouts'][0]['duration']
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_403_if_workout_belongs_to_a_different_user(
 | 
			
		||||
        self,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,17 @@
 | 
			
		||||
from uuid import UUID
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
 | 
			
		||||
from flask import Flask
 | 
			
		||||
 | 
			
		||||
from fittrackee import db
 | 
			
		||||
from fittrackee.users.models import User
 | 
			
		||||
from fittrackee.workouts.models import Sport, Workout
 | 
			
		||||
from fittrackee.workouts.utils.short_id import decode_short_id
 | 
			
		||||
from fittrackee.workouts.utils.short_id import encode_uuid
 | 
			
		||||
 | 
			
		||||
from ..utils import random_string
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestWorkoutModel:
 | 
			
		||||
    def test_workout_model(
 | 
			
		||||
    def test_sport_label_and_date_are_in_string_value(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
@@ -18,48 +20,109 @@ class TestWorkoutModel:
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        workout_cycling_user_1.title = 'Test'
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
    def test_short_id_returns_encoded_workout_uuid(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        workout_cycling_user_1: Workout,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        assert workout_cycling_user_1.short_id == encode_uuid(
 | 
			
		||||
            workout_cycling_user_1.uuid
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_serialize_for_workout_without_gpx(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        workout_cycling_user_1: Workout,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        workout = workout_cycling_user_1
 | 
			
		||||
 | 
			
		||||
        serialized_workout = workout.serialize()
 | 
			
		||||
        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['ave_speed'] == float(workout.ave_speed)
 | 
			
		||||
        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 'creation_date' in serialized_workout
 | 
			
		||||
        assert serialized_workout['descent'] is None
 | 
			
		||||
        assert serialized_workout['distance'] == float(workout.distance)
 | 
			
		||||
        assert serialized_workout['duration'] == str(workout.duration)
 | 
			
		||||
        assert serialized_workout['id'] == workout.short_id
 | 
			
		||||
        assert serialized_workout['map'] is None
 | 
			
		||||
        assert serialized_workout['weather_start'] is None
 | 
			
		||||
        assert serialized_workout['weather_end'] is None
 | 
			
		||||
        assert serialized_workout['max_alt'] is None
 | 
			
		||||
        assert serialized_workout['max_speed'] == float(workout.max_speed)
 | 
			
		||||
        assert serialized_workout['min_alt'] is None
 | 
			
		||||
        assert serialized_workout['modification_date'] is None
 | 
			
		||||
        assert serialized_workout['moving'] == str(workout.moving)
 | 
			
		||||
        assert serialized_workout['next_workout'] is None
 | 
			
		||||
        assert serialized_workout['notes'] is None
 | 
			
		||||
        assert serialized_workout['pauses'] is None
 | 
			
		||||
        assert serialized_workout['previous_workout'] is None
 | 
			
		||||
        assert serialized_workout['records'] == [
 | 
			
		||||
            record.serialize() for record in workout.records
 | 
			
		||||
        ]
 | 
			
		||||
        assert serialized_workout['segments'] == []
 | 
			
		||||
        assert serialized_workout['sport_id'] == workout.sport_id
 | 
			
		||||
        assert serialized_workout['title'] == workout.title
 | 
			
		||||
        assert serialized_workout['user'] == workout.user.username
 | 
			
		||||
        assert serialized_workout['weather_end'] is None
 | 
			
		||||
        assert serialized_workout['weather_start'] is None
 | 
			
		||||
        assert serialized_workout['with_gpx'] is False
 | 
			
		||||
        assert str(serialized_workout['workout_date']) == '2018-01-01 00:00:00'
 | 
			
		||||
 | 
			
		||||
    def test_serialize_for_workout_with_gpx(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        workout_cycling_user_1: Workout,
 | 
			
		||||
        workout_cycling_user_1_segment: Workout,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        workout = workout_cycling_user_1
 | 
			
		||||
        workout.bounds = [1, 2, 3, 4]
 | 
			
		||||
        workout.gpx = random_string()
 | 
			
		||||
        workout.map = random_string()
 | 
			
		||||
        workout.pauses = timedelta(minutes=15)
 | 
			
		||||
 | 
			
		||||
        serialized_workout = workout.serialize()
 | 
			
		||||
        assert serialized_workout['ascent'] is None
 | 
			
		||||
        assert serialized_workout['ave_speed'] == float(workout.ave_speed)
 | 
			
		||||
        assert serialized_workout['bounds'] == [
 | 
			
		||||
            float(bound) for bound in workout.bounds
 | 
			
		||||
        ]
 | 
			
		||||
        assert 'creation_date' in serialized_workout
 | 
			
		||||
        assert serialized_workout['descent'] is None
 | 
			
		||||
        assert serialized_workout['distance'] == float(workout.distance)
 | 
			
		||||
        assert serialized_workout['duration'] == str(workout.duration)
 | 
			
		||||
        assert serialized_workout['id'] == workout.short_id
 | 
			
		||||
        assert serialized_workout['map'] is None
 | 
			
		||||
        assert serialized_workout['max_alt'] is None
 | 
			
		||||
        assert serialized_workout['max_speed'] == float(workout.max_speed)
 | 
			
		||||
        assert serialized_workout['min_alt'] is None
 | 
			
		||||
        assert serialized_workout['modification_date'] is not None
 | 
			
		||||
        assert serialized_workout['moving'] == str(workout.moving)
 | 
			
		||||
        assert serialized_workout['next_workout'] is None
 | 
			
		||||
        assert serialized_workout['notes'] is None
 | 
			
		||||
        assert serialized_workout['pauses'] == str(workout.pauses)
 | 
			
		||||
        assert serialized_workout['previous_workout'] is None
 | 
			
		||||
        assert serialized_workout['records'] == [
 | 
			
		||||
            record.serialize() for record in workout.records
 | 
			
		||||
        ]
 | 
			
		||||
        assert serialized_workout['segments'] == [
 | 
			
		||||
            segment.serialize() for segment in workout.segments
 | 
			
		||||
        ]
 | 
			
		||||
        assert serialized_workout['sport_id'] == workout.sport_id
 | 
			
		||||
        assert serialized_workout['title'] == workout.title
 | 
			
		||||
        assert serialized_workout['user'] == workout.user.username
 | 
			
		||||
        assert serialized_workout['weather_end'] is None
 | 
			
		||||
        assert serialized_workout['weather_start'] is None
 | 
			
		||||
        assert serialized_workout['with_gpx'] is True
 | 
			
		||||
        assert str(serialized_workout['workout_date']) == '2018-01-01 00:00:00'
 | 
			
		||||
 | 
			
		||||
    def test_workout_segment_model(
 | 
			
		||||
        self,
 | 
			
		||||
@@ -74,3 +137,35 @@ class TestWorkoutModel:
 | 
			
		||||
            f'for workout \'{workout_cycling_user_1.short_id}\'>'
 | 
			
		||||
            == str(workout_cycling_user_1_segment)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_previous_workout(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        sport_2_running: Sport,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        workout_cycling_user_1: Workout,
 | 
			
		||||
        workout_running_user_1: Workout,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_workout = workout_running_user_1.serialize()
 | 
			
		||||
 | 
			
		||||
        assert (
 | 
			
		||||
            serialized_workout['previous_workout']
 | 
			
		||||
            == workout_cycling_user_1.short_id
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_it_returns_next_workout(
 | 
			
		||||
        self,
 | 
			
		||||
        app: Flask,
 | 
			
		||||
        sport_1_cycling: Sport,
 | 
			
		||||
        sport_2_running: Sport,
 | 
			
		||||
        user_1: User,
 | 
			
		||||
        workout_cycling_user_1: Workout,
 | 
			
		||||
        workout_running_user_1: Workout,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        serialized_workout = workout_cycling_user_1.serialize()
 | 
			
		||||
 | 
			
		||||
        assert (
 | 
			
		||||
            serialized_workout['next_workout']
 | 
			
		||||
            == workout_running_user_1.short_id
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -791,8 +791,9 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    :<json string timezone: user time zone
 | 
			
		||||
    :<json string weekm: does week start on Monday?
 | 
			
		||||
    :<json boolean weekm: does week start on Monday?
 | 
			
		||||
    :<json string language: language preferences
 | 
			
		||||
    :<json boolean imperial_units: display distance in imperial units
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -249,12 +249,13 @@ def get_users(auth_user: User) -> Dict:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@users_blueprint.route('/users/<user_name>', methods=['GET'])
 | 
			
		||||
@authenticate_as_admin
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_single_user(
 | 
			
		||||
    auth_user: User, user_name: str
 | 
			
		||||
) -> Union[Dict, HttpResponse]:
 | 
			
		||||
    """
 | 
			
		||||
    Get single user details. Only user with admin rights can get user details.
 | 
			
		||||
    Get single user details. Only user with admin rights can get other users
 | 
			
		||||
    details.
 | 
			
		||||
 | 
			
		||||
    It returns user preferences only for authenticated user.
 | 
			
		||||
 | 
			
		||||
@@ -353,6 +354,9 @@ def get_single_user(
 | 
			
		||||
    :statuscode 404:
 | 
			
		||||
        - user does not exist
 | 
			
		||||
    """
 | 
			
		||||
    if user_name != auth_user.username and not auth_user.admin:
 | 
			
		||||
        return ForbiddenErrorResponse()
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        user = User.query.filter_by(username=user_name).first()
 | 
			
		||||
        if user:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "fittrackee_client",
 | 
			
		||||
  "version": "0.6.1",
 | 
			
		||||
  "version": "0.6.2",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "serve": "vue-cli-service serve",
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
    "chartjs-plugin-datalabels": "^2.0.0",
 | 
			
		||||
    "core-js": "^3.21.1",
 | 
			
		||||
    "date-fns": "^2.28.0",
 | 
			
		||||
    "date-fns-tz": "^1.3.1",
 | 
			
		||||
    "date-fns-tz": "^1.3.3",
 | 
			
		||||
    "leaflet": "^1.7.1",
 | 
			
		||||
    "register-service-worker": "^1.7.1",
 | 
			
		||||
    "vue": "^3.0.0",
 | 
			
		||||
@@ -36,8 +36,8 @@
 | 
			
		||||
    "@intlify/vue-i18n-loader": "^4.0.1",
 | 
			
		||||
    "@types/chai": "^4.2.11",
 | 
			
		||||
    "@types/mocha": "^9.1.0",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.4.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.4.0",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.17.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.17.0",
 | 
			
		||||
    "@vue/cli-plugin-babel": "~5.0.1",
 | 
			
		||||
    "@vue/cli-plugin-eslint": "~5.0.1",
 | 
			
		||||
    "@vue/cli-plugin-pwa": "~5.0.1",
 | 
			
		||||
@@ -51,14 +51,14 @@
 | 
			
		||||
    "chai": "^4.3.6",
 | 
			
		||||
    "eslint": "^7.32.0",
 | 
			
		||||
    "eslint-config-prettier": "^8.5.0",
 | 
			
		||||
    "eslint-import-resolver-typescript": "^2.7.0",
 | 
			
		||||
    "eslint-import-resolver-typescript": "^2.7.1",
 | 
			
		||||
    "eslint-plugin-import": "^2.24.1",
 | 
			
		||||
    "eslint-plugin-prettier": "^4.0.0",
 | 
			
		||||
    "eslint-plugin-vue": "^8.5.0",
 | 
			
		||||
    "prettier": "^2.6.1",
 | 
			
		||||
    "sass": "^1.49.9",
 | 
			
		||||
    "prettier": "^2.6.2",
 | 
			
		||||
    "sass": "^1.49.11",
 | 
			
		||||
    "sass-loader": "^12.6.0",
 | 
			
		||||
    "typescript": "~4.5.5",
 | 
			
		||||
    "typescript": "^4.6.3",
 | 
			
		||||
    "vue-cli-plugin-i18n": "~2.3.1"
 | 
			
		||||
  },
 | 
			
		||||
  "eslintConfig": {
 | 
			
		||||
 
 | 
			
		||||
@@ -29,10 +29,10 @@
 | 
			
		||||
 | 
			
		||||
  import StatCard from '@/components/Common/StatCard.vue'
 | 
			
		||||
  import { TUnit } from '@/types/units'
 | 
			
		||||
  import { IUserProfile } from '@/types/user'
 | 
			
		||||
  import { IAuthUserProfile } from '@/types/user'
 | 
			
		||||
  import { convertDistance, units } from '@/utils/units'
 | 
			
		||||
  interface Props {
 | 
			
		||||
    user: IUserProfile
 | 
			
		||||
    user: IAuthUserProfile
 | 
			
		||||
  }
 | 
			
		||||
  const props = defineProps<Props>()
 | 
			
		||||
 | 
			
		||||
@@ -47,9 +47,11 @@
 | 
			
		||||
  const unitTo: TUnit = user.value.imperial_units
 | 
			
		||||
    ? units[defaultUnitFrom].defaultTarget
 | 
			
		||||
    : defaultUnitFrom
 | 
			
		||||
  const totalDistance = user.value.imperial_units
 | 
			
		||||
    ? convertDistance(user.value.total_distance, defaultUnitFrom, unitTo, 2)
 | 
			
		||||
    : parseFloat(user.value.total_distance.toFixed(2))
 | 
			
		||||
  const totalDistance: ComputedRef<number> = computed(() =>
 | 
			
		||||
    user.value.imperial_units
 | 
			
		||||
      ? convertDistance(user.value.total_distance, defaultUnitFrom, unitTo, 2)
 | 
			
		||||
      : parseFloat(user.value.total_distance.toFixed(2))
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  function get_duration(total_duration: ComputedRef<string>) {
 | 
			
		||||
    const duration = total_duration.value.match(/day/g)
 | 
			
		||||
 
 | 
			
		||||
@@ -263,7 +263,7 @@ const routes: Array<RouteRecordRaw> = [
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'users/:username',
 | 
			
		||||
        name: 'User',
 | 
			
		||||
        name: 'UserFromAdmin',
 | 
			
		||||
        component: () =>
 | 
			
		||||
          import(/* webpackChunkName: 'profile' */ '@/views/user/UserView.vue'),
 | 
			
		||||
        props: { fromAdmin: true },
 | 
			
		||||
 
 | 
			
		||||
@@ -1456,7 +1456,7 @@
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@types/node" "*"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/eslint-plugin@^5.0.0", "@typescript-eslint/eslint-plugin@^5.4.0":
 | 
			
		||||
"@typescript-eslint/eslint-plugin@^5.0.0":
 | 
			
		||||
  version "5.16.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.16.0.tgz#78f246dd8d1b528fc5bfca99a8a64d4023a3d86d"
 | 
			
		||||
  integrity sha512-SJoba1edXvQRMmNI505Uo4XmGbxCK9ARQpkvOd00anxzri9RNQk0DDCxD+LIl+jYhkzOJiOMMKYEHnHEODjdCw==
 | 
			
		||||
@@ -1471,7 +1471,22 @@
 | 
			
		||||
    semver "^7.3.5"
 | 
			
		||||
    tsutils "^3.21.0"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@^5.4.0":
 | 
			
		||||
"@typescript-eslint/eslint-plugin@^5.17.0":
 | 
			
		||||
  version "5.17.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.17.0.tgz#704eb4e75039000531255672bf1c85ee85cf1d67"
 | 
			
		||||
  integrity sha512-qVstvQilEd89HJk3qcbKt/zZrfBZ+9h2ynpAGlWjWiizA7m/MtLT9RoX6gjtpE500vfIg8jogAkDzdCxbsFASQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@typescript-eslint/scope-manager" "5.17.0"
 | 
			
		||||
    "@typescript-eslint/type-utils" "5.17.0"
 | 
			
		||||
    "@typescript-eslint/utils" "5.17.0"
 | 
			
		||||
    debug "^4.3.2"
 | 
			
		||||
    functional-red-black-tree "^1.0.1"
 | 
			
		||||
    ignore "^5.1.8"
 | 
			
		||||
    regexpp "^3.2.0"
 | 
			
		||||
    semver "^7.3.5"
 | 
			
		||||
    tsutils "^3.21.0"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/parser@^5.0.0":
 | 
			
		||||
  version "5.16.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.16.0.tgz#e4de1bde4b4dad5b6124d3da227347616ed55508"
 | 
			
		||||
  integrity sha512-fkDq86F0zl8FicnJtdXakFs4lnuebH6ZADDw6CYQv0UZeIjHvmEw87m9/29nk2Dv5Lmdp0zQ3zDQhiMWQf/GbA==
 | 
			
		||||
@@ -1481,6 +1496,16 @@
 | 
			
		||||
    "@typescript-eslint/typescript-estree" "5.16.0"
 | 
			
		||||
    debug "^4.3.2"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/parser@^5.17.0":
 | 
			
		||||
  version "5.17.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.17.0.tgz#7def77d5bcd8458d12d52909118cf3f0a45f89d5"
 | 
			
		||||
  integrity sha512-aRzW9Jg5Rlj2t2/crzhA2f23SIYFlF9mchGudyP0uiD6SenIxzKoLjwzHbafgHn39dNV/TV7xwQkLfFTZlJ4ig==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@typescript-eslint/scope-manager" "5.17.0"
 | 
			
		||||
    "@typescript-eslint/types" "5.17.0"
 | 
			
		||||
    "@typescript-eslint/typescript-estree" "5.17.0"
 | 
			
		||||
    debug "^4.3.2"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/scope-manager@5.16.0":
 | 
			
		||||
  version "5.16.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.16.0.tgz#7e7909d64bd0c4d8aef629cdc764b9d3e1d3a69a"
 | 
			
		||||
@@ -1489,6 +1514,14 @@
 | 
			
		||||
    "@typescript-eslint/types" "5.16.0"
 | 
			
		||||
    "@typescript-eslint/visitor-keys" "5.16.0"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/scope-manager@5.17.0":
 | 
			
		||||
  version "5.17.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.17.0.tgz#4cea7d0e0bc0e79eb60cad431c89120987c3f952"
 | 
			
		||||
  integrity sha512-062iCYQF/doQ9T2WWfJohQKKN1zmmXVfAcS3xaiialiw8ZUGy05Em6QVNYJGO34/sU1a7a+90U3dUNfqUDHr3w==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@typescript-eslint/types" "5.17.0"
 | 
			
		||||
    "@typescript-eslint/visitor-keys" "5.17.0"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/type-utils@5.16.0":
 | 
			
		||||
  version "5.16.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.16.0.tgz#b482bdde1d7d7c0c7080f7f2f67ea9580b9e0692"
 | 
			
		||||
@@ -1498,11 +1531,25 @@
 | 
			
		||||
    debug "^4.3.2"
 | 
			
		||||
    tsutils "^3.21.0"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/type-utils@5.17.0":
 | 
			
		||||
  version "5.17.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.17.0.tgz#1c4549d68c89877662224aabb29fbbebf5fc9672"
 | 
			
		||||
  integrity sha512-3hU0RynUIlEuqMJA7dragb0/75gZmwNwFf/QJokWzPehTZousP/MNifVSgjxNcDCkM5HI2K22TjQWUmmHUINSg==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@typescript-eslint/utils" "5.17.0"
 | 
			
		||||
    debug "^4.3.2"
 | 
			
		||||
    tsutils "^3.21.0"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/types@5.16.0":
 | 
			
		||||
  version "5.16.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.16.0.tgz#5827b011982950ed350f075eaecb7f47d3c643ee"
 | 
			
		||||
  integrity sha512-oUorOwLj/3/3p/HFwrp6m/J2VfbLC8gjW5X3awpQJ/bSG+YRGFS4dpsvtQ8T2VNveV+LflQHjlLvB6v0R87z4g==
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/types@5.17.0":
 | 
			
		||||
  version "5.17.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.17.0.tgz#861ec9e669ffa2aa9b873dd4d28d9b1ce26d216f"
 | 
			
		||||
  integrity sha512-AgQ4rWzmCxOZLioFEjlzOI3Ch8giDWx8aUDxyNw9iOeCvD3GEYAB7dxWGQy4T/rPVe8iPmu73jPHuaSqcjKvxw==
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/typescript-estree@5.16.0":
 | 
			
		||||
  version "5.16.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.16.0.tgz#32259459ec62f5feddca66adc695342f30101f61"
 | 
			
		||||
@@ -1516,6 +1563,19 @@
 | 
			
		||||
    semver "^7.3.5"
 | 
			
		||||
    tsutils "^3.21.0"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/typescript-estree@5.17.0":
 | 
			
		||||
  version "5.17.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.17.0.tgz#a7cba7dfc8f9cc2ac78c18584e684507df4f2488"
 | 
			
		||||
  integrity sha512-X1gtjEcmM7Je+qJRhq7ZAAaNXYhTgqMkR10euC4Si6PIjb+kwEQHSxGazXUQXFyqfEXdkGf6JijUu5R0uceQzg==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@typescript-eslint/types" "5.17.0"
 | 
			
		||||
    "@typescript-eslint/visitor-keys" "5.17.0"
 | 
			
		||||
    debug "^4.3.2"
 | 
			
		||||
    globby "^11.0.4"
 | 
			
		||||
    is-glob "^4.0.3"
 | 
			
		||||
    semver "^7.3.5"
 | 
			
		||||
    tsutils "^3.21.0"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/utils@5.16.0":
 | 
			
		||||
  version "5.16.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.16.0.tgz#42218b459d6d66418a4eb199a382bdc261650679"
 | 
			
		||||
@@ -1528,6 +1588,18 @@
 | 
			
		||||
    eslint-scope "^5.1.1"
 | 
			
		||||
    eslint-utils "^3.0.0"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/utils@5.17.0":
 | 
			
		||||
  version "5.17.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.17.0.tgz#549a9e1d491c6ccd3624bc3c1b098f5cfb45f306"
 | 
			
		||||
  integrity sha512-DVvndq1QoxQH+hFv+MUQHrrWZ7gQ5KcJzyjhzcqB1Y2Xes1UQQkTRPUfRpqhS8mhTWsSb2+iyvDW1Lef5DD7vA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@types/json-schema" "^7.0.9"
 | 
			
		||||
    "@typescript-eslint/scope-manager" "5.17.0"
 | 
			
		||||
    "@typescript-eslint/types" "5.17.0"
 | 
			
		||||
    "@typescript-eslint/typescript-estree" "5.17.0"
 | 
			
		||||
    eslint-scope "^5.1.1"
 | 
			
		||||
    eslint-utils "^3.0.0"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/visitor-keys@5.16.0":
 | 
			
		||||
  version "5.16.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.16.0.tgz#f27dc3b943e6317264c7492e390c6844cd4efbbb"
 | 
			
		||||
@@ -1536,6 +1608,14 @@
 | 
			
		||||
    "@typescript-eslint/types" "5.16.0"
 | 
			
		||||
    eslint-visitor-keys "^3.0.0"
 | 
			
		||||
 | 
			
		||||
"@typescript-eslint/visitor-keys@5.17.0":
 | 
			
		||||
  version "5.17.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.17.0.tgz#52daae45c61b0211b4c81b53a71841911e479128"
 | 
			
		||||
  integrity sha512-6K/zlc4OfCagUu7Am/BD5k8PSWQOgh34Nrv9Rxe2tBzlJ7uOeJ/h7ugCGDCeEZHT6k2CJBhbk9IsbkPI0uvUkA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@typescript-eslint/types" "5.17.0"
 | 
			
		||||
    eslint-visitor-keys "^3.0.0"
 | 
			
		||||
 | 
			
		||||
"@ungap/promise-all-settled@1.1.2":
 | 
			
		||||
  version "1.1.2"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
 | 
			
		||||
@@ -3198,10 +3278,10 @@ data-urls@^3.0.1:
 | 
			
		||||
    whatwg-mimetype "^3.0.0"
 | 
			
		||||
    whatwg-url "^10.0.0"
 | 
			
		||||
 | 
			
		||||
date-fns-tz@^1.3.1:
 | 
			
		||||
  version "1.3.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.1.tgz#88b6374e5a74cfb11ec2d2b120cfe0bc02eeb862"
 | 
			
		||||
  integrity sha512-Uy+wph6HcQ0IG8TWbVyXicgDmB1zdvb0CoIknZQaxiTun4uSfxLR+8gSTC2C3KCLq+0fEIuEtJ/ORDRIn6doQw==
 | 
			
		||||
date-fns-tz@^1.3.3:
 | 
			
		||||
  version "1.3.3"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.3.tgz#7884a4b3ed6cd95bfd81831d608e5ef8be500c86"
 | 
			
		||||
  integrity sha512-Gks46gwbSauBQnV3Oofluj1wTm8J0tM7sbSJ9P+cJq/ZnTCpMohTKmmO5Tn+jQ7dyn0+b8G7cY4O2DZ5P/LXcA==
 | 
			
		||||
 | 
			
		||||
date-fns@^2.28.0:
 | 
			
		||||
  version "2.28.0"
 | 
			
		||||
@@ -3646,10 +3726,10 @@ eslint-import-resolver-node@^0.3.6:
 | 
			
		||||
    debug "^3.2.7"
 | 
			
		||||
    resolve "^1.20.0"
 | 
			
		||||
 | 
			
		||||
eslint-import-resolver-typescript@^2.7.0:
 | 
			
		||||
  version "2.7.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.7.0.tgz#1f9d391b636dccdbaa4a3b1a87eb9a8237e23963"
 | 
			
		||||
  integrity sha512-MNHS3u5pebvROX4MjGP9coda589ZGfL1SqdxUV4kSrcclfDRWvNE2D+eljbnWVMvWDVRgT89nhscMHPKYGcObQ==
 | 
			
		||||
eslint-import-resolver-typescript@^2.7.1:
 | 
			
		||||
  version "2.7.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.7.1.tgz#a90a4a1c80da8d632df25994c4c5fdcdd02b8751"
 | 
			
		||||
  integrity sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    debug "^4.3.4"
 | 
			
		||||
    glob "^7.2.0"
 | 
			
		||||
@@ -6317,11 +6397,16 @@ prettier-linter-helpers@^1.0.0:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    fast-diff "^1.1.2"
 | 
			
		||||
 | 
			
		||||
"prettier@^1.18.2 || ^2.0.0", prettier@^2.6.1:
 | 
			
		||||
"prettier@^1.18.2 || ^2.0.0":
 | 
			
		||||
  version "2.6.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.1.tgz#d472797e0d7461605c1609808e27b80c0f9cfe17"
 | 
			
		||||
  integrity sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==
 | 
			
		||||
 | 
			
		||||
prettier@^2.6.2:
 | 
			
		||||
  version "2.6.2"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032"
 | 
			
		||||
  integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==
 | 
			
		||||
 | 
			
		||||
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
 | 
			
		||||
  version "5.6.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
 | 
			
		||||
@@ -6673,10 +6758,10 @@ sass-loader@^12.6.0:
 | 
			
		||||
    klona "^2.0.4"
 | 
			
		||||
    neo-async "^2.6.2"
 | 
			
		||||
 | 
			
		||||
sass@^1.49.9:
 | 
			
		||||
  version "1.49.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.9.tgz#b15a189ecb0ca9e24634bae5d1ebc191809712f9"
 | 
			
		||||
  integrity sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==
 | 
			
		||||
sass@^1.49.11:
 | 
			
		||||
  version "1.49.11"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.11.tgz#1ffeb77faeed8b806a2a1e021d7c9fd3fc322cb7"
 | 
			
		||||
  integrity sha512-wvS/geXgHUGs6A/4ud5BFIWKO1nKd7wYIGimDk4q4GFkJicILActpv9ueMT4eRGSsp1BdKHuw1WwAHXbhsJELQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    chokidar ">=3.0.0 <4.0.0"
 | 
			
		||||
    immutable "^4.0.0"
 | 
			
		||||
@@ -7471,10 +7556,10 @@ type-is@~1.6.18:
 | 
			
		||||
    media-typer "0.3.0"
 | 
			
		||||
    mime-types "~2.1.24"
 | 
			
		||||
 | 
			
		||||
typescript@~4.5.5:
 | 
			
		||||
  version "4.5.5"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
 | 
			
		||||
  integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
 | 
			
		||||
typescript@^4.6.3:
 | 
			
		||||
  version "4.6.3"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
 | 
			
		||||
  integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
 | 
			
		||||
 | 
			
		||||
unbox-primitive@^1.0.1:
 | 
			
		||||
  version "1.0.1"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										223
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						@@ -93,7 +93,7 @@ typecheck = ["mypy"]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "black"
 | 
			
		||||
version = "22.1.0"
 | 
			
		||||
version = "22.3.0"
 | 
			
		||||
description = "The uncompromising code formatter."
 | 
			
		||||
category = "dev"
 | 
			
		||||
optional = false
 | 
			
		||||
@@ -104,7 +104,7 @@ click = ">=8.0.0"
 | 
			
		||||
mypy-extensions = ">=0.4.3"
 | 
			
		||||
pathspec = ">=0.9.0"
 | 
			
		||||
platformdirs = ">=2"
 | 
			
		||||
tomli = ">=1.1.0"
 | 
			
		||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
 | 
			
		||||
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
 | 
			
		||||
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
 | 
			
		||||
 | 
			
		||||
@@ -146,11 +146,11 @@ unicode_backport = ["unicodedata2"]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "click"
 | 
			
		||||
version = "8.0.4"
 | 
			
		||||
version = "8.1.2"
 | 
			
		||||
description = "Composable command line interface toolkit"
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = ">=3.6"
 | 
			
		||||
python-versions = ">=3.7"
 | 
			
		||||
 | 
			
		||||
[package.dependencies]
 | 
			
		||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
 | 
			
		||||
@@ -232,7 +232,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "dramatiq"
 | 
			
		||||
version = "1.12.3"
 | 
			
		||||
version = "1.13.0"
 | 
			
		||||
description = "Background Processing for Python 3."
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
@@ -243,8 +243,8 @@ prometheus-client = ">=0.2"
 | 
			
		||||
redis = {version = ">=2.0,<5.0", optional = true, markers = "extra == \"redis\""}
 | 
			
		||||
 | 
			
		||||
[package.extras]
 | 
			
		||||
all = ["gevent (>=1.1)", "redis (>=2.0,<5.0)", "watchdog", "pika (>=1.0,<2.0)", "watchdog-gevent", "pylibmc (>=1.5,<2.0)"]
 | 
			
		||||
dev = ["gevent (>=1.1)", "redis (>=2.0,<5.0)", "watchdog", "pika (>=1.0,<2.0)", "watchdog-gevent", "pylibmc (>=1.5,<2.0)", "alabaster", "sphinx (<1.8)", "sphinxcontrib-napoleon", "flake8", "flake8-bugbear", "flake8-quotes", "isort", "bumpversion", "hiredis", "twine", "wheel", "pytest", "pytest-benchmark", "pytest-cov", "tox"]
 | 
			
		||||
all = ["redis (>=2.0,<5.0)", "pylibmc (>=1.5,<2.0)", "watchdog", "watchdog-gevent", "pika (>=1.0,<2.0)", "gevent (>=1.1)"]
 | 
			
		||||
dev = ["redis (>=2.0,<5.0)", "pylibmc (>=1.5,<2.0)", "watchdog", "watchdog-gevent", "pika (>=1.0,<2.0)", "gevent (>=1.1)", "alabaster", "sphinx (<1.8)", "sphinxcontrib-napoleon", "flake8", "flake8-bugbear", "flake8-quotes", "isort", "bumpversion", "hiredis", "twine", "wheel", "pytest", "pytest-benchmark", "pytest-cov", "tox"]
 | 
			
		||||
gevent = ["gevent (>=1.1)"]
 | 
			
		||||
memcached = ["pylibmc (>=1.5,<2.0)"]
 | 
			
		||||
rabbitmq = ["pika (>=1.0,<2.0)"]
 | 
			
		||||
@@ -267,14 +267,15 @@ pyflakes = ">=2.3.0,<2.4.0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "flask"
 | 
			
		||||
version = "2.0.3"
 | 
			
		||||
version = "2.1.1"
 | 
			
		||||
description = "A simple framework for building complex web applications."
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = ">=3.6"
 | 
			
		||||
python-versions = ">=3.7"
 | 
			
		||||
 | 
			
		||||
[package.dependencies]
 | 
			
		||||
click = ">=7.1.2"
 | 
			
		||||
click = ">=8.0"
 | 
			
		||||
importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
 | 
			
		||||
itsdangerous = ">=2.0"
 | 
			
		||||
Jinja2 = ">=3.0"
 | 
			
		||||
Werkzeug = ">=2.0"
 | 
			
		||||
@@ -285,14 +286,14 @@ dotenv = ["python-dotenv"]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "flask-bcrypt"
 | 
			
		||||
version = "0.7.1"
 | 
			
		||||
version = "1.0.0"
 | 
			
		||||
description = "Brcrypt hashing for Flask."
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = "*"
 | 
			
		||||
 | 
			
		||||
[package.dependencies]
 | 
			
		||||
bcrypt = "*"
 | 
			
		||||
bcrypt = ">=3.1.1"
 | 
			
		||||
Flask = "*"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -584,12 +585,16 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pillow"
 | 
			
		||||
version = "9.0.1"
 | 
			
		||||
version = "9.1.0"
 | 
			
		||||
description = "Python Imaging Library (Fork)"
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = ">=3.7"
 | 
			
		||||
 | 
			
		||||
[package.extras]
 | 
			
		||||
docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"]
 | 
			
		||||
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "platformdirs"
 | 
			
		||||
version = "2.5.1"
 | 
			
		||||
@@ -748,14 +753,14 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pytest-base-url"
 | 
			
		||||
version = "1.4.2"
 | 
			
		||||
version = "2.0.0"
 | 
			
		||||
description = "pytest plugin for URL based testing"
 | 
			
		||||
category = "dev"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = "*"
 | 
			
		||||
python-versions = ">=3.7,<4.0"
 | 
			
		||||
 | 
			
		||||
[package.dependencies]
 | 
			
		||||
pytest = ">=2.7.3"
 | 
			
		||||
pytest = ">=3.0.0,<8.0.0"
 | 
			
		||||
requests = ">=2.9"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -928,7 +933,7 @@ sphinx = ">=1.3.1"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "redis"
 | 
			
		||||
version = "4.2.0"
 | 
			
		||||
version = "4.2.1"
 | 
			
		||||
description = "Python client for Redis database and key-value store"
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
@@ -939,7 +944,7 @@ async-timeout = ">=4.0.2"
 | 
			
		||||
deprecated = ">=1.2.3"
 | 
			
		||||
importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""}
 | 
			
		||||
packaging = ">=20.4"
 | 
			
		||||
typing-extensions = "*"
 | 
			
		||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
 | 
			
		||||
 | 
			
		||||
[package.extras]
 | 
			
		||||
hiredis = ["hiredis (>=1.0.0)"]
 | 
			
		||||
@@ -1033,7 +1038,7 @@ python-versions = "*"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sphinx"
 | 
			
		||||
version = "4.4.0"
 | 
			
		||||
version = "4.5.0"
 | 
			
		||||
description = "Python documentation generator"
 | 
			
		||||
category = "dev"
 | 
			
		||||
optional = false
 | 
			
		||||
@@ -1269,7 +1274,7 @@ python-versions = ">=3.6"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "types-freezegun"
 | 
			
		||||
version = "1.1.7"
 | 
			
		||||
version = "1.1.8"
 | 
			
		||||
description = "Typing stubs for freezegun"
 | 
			
		||||
category = "dev"
 | 
			
		||||
optional = false
 | 
			
		||||
@@ -1285,7 +1290,7 @@ python-versions = "*"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "types-requests"
 | 
			
		||||
version = "2.27.15"
 | 
			
		||||
version = "2.27.16"
 | 
			
		||||
description = "Typing stubs for requests"
 | 
			
		||||
category = "dev"
 | 
			
		||||
optional = false
 | 
			
		||||
@@ -1310,6 +1315,14 @@ category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = ">=3.6"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ua-parser"
 | 
			
		||||
version = "0.10.0"
 | 
			
		||||
description = "Python port of Browserscope's user agent parser"
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = "*"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "urllib3"
 | 
			
		||||
version = "1.26.9"
 | 
			
		||||
@@ -1332,11 +1345,11 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "werkzeug"
 | 
			
		||||
version = "2.0.3"
 | 
			
		||||
version = "2.1.1"
 | 
			
		||||
description = "The comprehensive WSGI web application library."
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = ">=3.6"
 | 
			
		||||
python-versions = ">=3.7"
 | 
			
		||||
 | 
			
		||||
[package.extras]
 | 
			
		||||
watchdog = ["watchdog"]
 | 
			
		||||
@@ -1375,7 +1388,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
 | 
			
		||||
[metadata]
 | 
			
		||||
lock-version = "1.1"
 | 
			
		||||
python-versions = "^3.7"
 | 
			
		||||
content-hash = "545f5ef79ba67d5a2d2df007fb5ac67ca37630efd4ff04c3a15e181c73b4f466"
 | 
			
		||||
content-hash = "e130b306957d6577ecd21cc1a19daa81073d5bbe77ba9f6a60ff83d8af0a2f05"
 | 
			
		||||
 | 
			
		||||
[metadata.files]
 | 
			
		||||
alabaster = [
 | 
			
		||||
@@ -1416,29 +1429,29 @@ bcrypt = [
 | 
			
		||||
    {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"},
 | 
			
		||||
]
 | 
			
		||||
black = [
 | 
			
		||||
    {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
 | 
			
		||||
    {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
 | 
			
		||||
    {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"},
 | 
			
		||||
    {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"},
 | 
			
		||||
    {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"},
 | 
			
		||||
    {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"},
 | 
			
		||||
    {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"},
 | 
			
		||||
    {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"},
 | 
			
		||||
    {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"},
 | 
			
		||||
    {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"},
 | 
			
		||||
    {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"},
 | 
			
		||||
    {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"},
 | 
			
		||||
    {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"},
 | 
			
		||||
    {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"},
 | 
			
		||||
    {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"},
 | 
			
		||||
    {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"},
 | 
			
		||||
    {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"},
 | 
			
		||||
    {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"},
 | 
			
		||||
    {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"},
 | 
			
		||||
    {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"},
 | 
			
		||||
    {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"},
 | 
			
		||||
    {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
 | 
			
		||||
    {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
 | 
			
		||||
    {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"},
 | 
			
		||||
    {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"},
 | 
			
		||||
    {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"},
 | 
			
		||||
    {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"},
 | 
			
		||||
    {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"},
 | 
			
		||||
    {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"},
 | 
			
		||||
    {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"},
 | 
			
		||||
    {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"},
 | 
			
		||||
    {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"},
 | 
			
		||||
    {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"},
 | 
			
		||||
    {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"},
 | 
			
		||||
    {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"},
 | 
			
		||||
    {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"},
 | 
			
		||||
    {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"},
 | 
			
		||||
    {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"},
 | 
			
		||||
    {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"},
 | 
			
		||||
    {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"},
 | 
			
		||||
    {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"},
 | 
			
		||||
    {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"},
 | 
			
		||||
    {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"},
 | 
			
		||||
    {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"},
 | 
			
		||||
    {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"},
 | 
			
		||||
    {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"},
 | 
			
		||||
]
 | 
			
		||||
certifi = [
 | 
			
		||||
    {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
 | 
			
		||||
@@ -1501,8 +1514,8 @@ charset-normalizer = [
 | 
			
		||||
    {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
 | 
			
		||||
]
 | 
			
		||||
click = [
 | 
			
		||||
    {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
 | 
			
		||||
    {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
 | 
			
		||||
    {file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"},
 | 
			
		||||
    {file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"},
 | 
			
		||||
]
 | 
			
		||||
colorama = [
 | 
			
		||||
    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
 | 
			
		||||
@@ -1586,19 +1599,20 @@ docutils = [
 | 
			
		||||
    {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
 | 
			
		||||
]
 | 
			
		||||
dramatiq = [
 | 
			
		||||
    {file = "dramatiq-1.12.3-py3-none-any.whl", hash = "sha256:eccb0f54d44ebd9e2c79e00d67b808397589a1a621ba7c5fd58df5fb6204a0a8"},
 | 
			
		||||
    {file = "dramatiq-1.12.3.tar.gz", hash = "sha256:380bd77b6b19d642f417b642935049ff71ddf4b4e57d821e4f55b92541430f21"},
 | 
			
		||||
    {file = "dramatiq-1.13.0-py3-none-any.whl", hash = "sha256:8ef7509ca62bc45c3f1e3b1a0248e9f774337100e32ba1502cfcca15df79ad61"},
 | 
			
		||||
    {file = "dramatiq-1.13.0.tar.gz", hash = "sha256:b4fe0ca6b55b06bebf82cd14c88044fb267505a57d4aa47378194efa0cef5f47"},
 | 
			
		||||
]
 | 
			
		||||
flake8 = [
 | 
			
		||||
    {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
 | 
			
		||||
    {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
 | 
			
		||||
]
 | 
			
		||||
flask = [
 | 
			
		||||
    {file = "Flask-2.0.3-py3-none-any.whl", hash = "sha256:59da8a3170004800a2837844bfa84d49b022550616070f7cb1a659682b2e7c9f"},
 | 
			
		||||
    {file = "Flask-2.0.3.tar.gz", hash = "sha256:e1120c228ca2f553b470df4a5fa927ab66258467526069981b3eb0a91902687d"},
 | 
			
		||||
    {file = "Flask-2.1.1-py3-none-any.whl", hash = "sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264"},
 | 
			
		||||
    {file = "Flask-2.1.1.tar.gz", hash = "sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8"},
 | 
			
		||||
]
 | 
			
		||||
flask-bcrypt = [
 | 
			
		||||
    {file = "Flask-Bcrypt-0.7.1.tar.gz", hash = "sha256:d71c8585b2ee1c62024392ebdbc447438564e2c8c02b4e57b56a4cafd8d13c5f"},
 | 
			
		||||
    {file = "Flask-Bcrypt-1.0.0.tar.gz", hash = "sha256:e622fbd3b0bf63d516b8844fe3431fc30213592412b430036c3928a0c52dfb27"},
 | 
			
		||||
    {file = "Flask_Bcrypt-1.0.0-py3-none-any.whl", hash = "sha256:64a947e15ff06823c3843f4826d4548dfae038c7df8f98ea82755db62092ec4c"},
 | 
			
		||||
]
 | 
			
		||||
flask-dramatiq = [
 | 
			
		||||
    {file = "flask-dramatiq-0.6.0.tar.gz", hash = "sha256:63709e73d7c8d2e5d9bc554d1e859d91c5c5c9a4ebc9461752655bf1e0b87420"},
 | 
			
		||||
@@ -1806,41 +1820,44 @@ pathspec = [
 | 
			
		||||
    {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
 | 
			
		||||
]
 | 
			
		||||
pillow = [
 | 
			
		||||
    {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"},
 | 
			
		||||
    {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"},
 | 
			
		||||
    {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"},
 | 
			
		||||
    {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"},
 | 
			
		||||
    {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"},
 | 
			
		||||
    {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"},
 | 
			
		||||
    {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"},
 | 
			
		||||
    {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"},
 | 
			
		||||
    {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:af79d3fde1fc2e33561166d62e3b63f0cc3e47b5a3a2e5fea40d4917754734ea"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55dd1cf09a1fd7c7b78425967aacae9b0d70125f7d3ab973fadc7b5abc3de652"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66822d01e82506a19407d1afc104c3fcea3b81d5eb11485e593ad6b8492f995a"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5eaf3b42df2bcda61c53a742ee2c6e63f777d0e085bbc6b2ab7ed57deb13db7"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ce45deec9df310cbbee11104bae1a2a43308dd9c317f99235b6d3080ddd66e"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aea7ce61328e15943d7b9eaca87e81f7c62ff90f669116f857262e9da4057ba3"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp310-cp310-win32.whl", hash = "sha256:7a053bd4d65a3294b153bdd7724dce864a1d548416a5ef61f6d03bf149205160"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:97bda660702a856c2c9e12ec26fc6d187631ddfd896ff685814ab21ef0597033"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21dee8466b42912335151d24c1665fcf44dc2ee47e021d233a40c3ca5adae59c"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b6d4050b208c8ff886fd3db6690bf04f9a48749d78b41b7a5bf24c236ab0165"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5cfca31ab4c13552a0f354c87fbd7f162a4fafd25e6b521bba93a57fe6a3700a"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed742214068efa95e9844c2d9129e209ed63f61baa4d54dbf4cf8b5e2d30ccf2"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp37-cp37m-win32.whl", hash = "sha256:c9efef876c21788366ea1f50ecb39d5d6f65febe25ad1d4c0b8dff98843ac244"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:de344bcf6e2463bb25179d74d6e7989e375f906bcec8cb86edb8b12acbc7dfef"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:17869489de2fce6c36690a0c721bd3db176194af5f39249c1ac56d0bb0fcc512"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:25023a6209a4d7c42154073144608c9a71d3512b648a2f5d4465182cb93d3477"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8782189c796eff29dbb37dd87afa4ad4d40fc90b2742704f94812851b725964b"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:463acf531f5d0925ca55904fa668bb3461c3ef6bc779e1d6d8a488092bdee378"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f42364485bfdab19c1373b5cd62f7c5ab7cc052e19644862ec8f15bb8af289e"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3fddcdb619ba04491e8f771636583a7cc5a5051cd193ff1aa1ee8616d2a692c5"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp38-cp38-win32.whl", hash = "sha256:4fe29a070de394e449fd88ebe1624d1e2d7ddeed4c12e0b31624561b58948d9a"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:c24f718f9dd73bb2b31a6201e6db5ea4a61fdd1d1c200f43ee585fc6dcd21b34"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fb89397013cf302f282f0fc998bb7abf11d49dcff72c8ecb320f76ea6e2c5717"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c870193cce4b76713a2b29be5d8327c8ccbe0d4a49bc22968aa1e680930f5581"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69e5ddc609230d4408277af135c5b5c8fe7a54b2bdb8ad7c5100b86b3aab04c6"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35be4a9f65441d9982240e6966c1eaa1c654c4e5e931eaf580130409e31804d4"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82283af99c1c3a5ba1da44c67296d5aad19f11c535b551a5ae55328a317ce331"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a325ac71914c5c043fa50441b36606e64a10cd262de12f7a179620f579752ff8"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp39-cp39-win32.whl", hash = "sha256:a598d8830f6ef5501002ae85c7dbfcd9c27cc4efc02a1989369303ba85573e58"},
 | 
			
		||||
    {file = "Pillow-9.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c51cb9edac8a5abd069fd0758ac0a8bfe52c261ee0e330f363548aca6893595"},
 | 
			
		||||
    {file = "Pillow-9.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a336a4f74baf67e26f3acc4d61c913e378e931817cd1e2ef4dfb79d3e051b481"},
 | 
			
		||||
    {file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb1b89b11256b5b6cad5e7593f9061ac4624f7651f7a8eb4dfa37caa1dfaa4d0"},
 | 
			
		||||
    {file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:255c9d69754a4c90b0ee484967fc8818c7ff8311c6dddcc43a4340e10cd1636a"},
 | 
			
		||||
    {file = "Pillow-9.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5a3ecc026ea0e14d0ad7cd990ea7f48bfcb3eb4271034657dc9d06933c6629a7"},
 | 
			
		||||
    {file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5b0ff59785d93b3437c3703e3c64c178aabada51dea2a7f2c5eccf1bcf565a3"},
 | 
			
		||||
    {file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7110ec1701b0bf8df569a7592a196c9d07c764a0a74f65471ea56816f10e2c8"},
 | 
			
		||||
    {file = "Pillow-9.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458"},
 | 
			
		||||
    {file = "Pillow-9.1.0.tar.gz", hash = "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97"},
 | 
			
		||||
]
 | 
			
		||||
platformdirs = [
 | 
			
		||||
    {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"},
 | 
			
		||||
@@ -1954,8 +1971,8 @@ pytest = [
 | 
			
		||||
    {file = "pytest-7.1.1.tar.gz", hash = "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63"},
 | 
			
		||||
]
 | 
			
		||||
pytest-base-url = [
 | 
			
		||||
    {file = "pytest-base-url-1.4.2.tar.gz", hash = "sha256:7f1f32e08c2ee751e59e7f5880235b46e83496adc5cba5a01ca218c6fe81333d"},
 | 
			
		||||
    {file = "pytest_base_url-1.4.2-py2.py3-none-any.whl", hash = "sha256:8b6523a1a3af73c317bdae97b722dfb55a7336733d1ad411eb4a4931347ba77a"},
 | 
			
		||||
    {file = "pytest-base-url-2.0.0.tar.gz", hash = "sha256:e1e88a4fd221941572ccdcf3bf6c051392d2f8b6cef3e0bc7da95abec4b5346e"},
 | 
			
		||||
    {file = "pytest_base_url-2.0.0-py3-none-any.whl", hash = "sha256:ed36fd632c32af9f1c08f2c2835dcf42ca8fcd097d6ed44a09f253d365ad8297"},
 | 
			
		||||
]
 | 
			
		||||
pytest-black = [
 | 
			
		||||
    {file = "pytest-black-0.3.12.tar.gz", hash = "sha256:1d339b004f764d6cd0f06e690f6dd748df3d62e6fe1a692d6a5500ac2c5b75a5"},
 | 
			
		||||
@@ -2008,8 +2025,8 @@ recommonmark = [
 | 
			
		||||
    {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"},
 | 
			
		||||
]
 | 
			
		||||
redis = [
 | 
			
		||||
    {file = "redis-4.2.0-py3-none-any.whl", hash = "sha256:3cbe235cea80b9c9991b397567aa2d65eb4e6fb09787f61d227ae82eb4eb50b4"},
 | 
			
		||||
    {file = "redis-4.2.0.tar.gz", hash = "sha256:6758d01dec81af191b98a35cce3402675d115456584c39b500ab485a5e386bbb"},
 | 
			
		||||
    {file = "redis-4.2.1-py3-none-any.whl", hash = "sha256:69d05fac17bf3f43937afbb775c536eb516bd21355a4f17d59a966f4a531ce71"},
 | 
			
		||||
    {file = "redis-4.2.1.tar.gz", hash = "sha256:fe45513881229dbee610620b9e0817b1f48c47ba635870320fd44a712204bbdd"},
 | 
			
		||||
]
 | 
			
		||||
requests = [
 | 
			
		||||
    {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
 | 
			
		||||
@@ -2043,8 +2060,8 @@ sortedcontainers = [
 | 
			
		||||
    {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
 | 
			
		||||
]
 | 
			
		||||
sphinx = [
 | 
			
		||||
    {file = "Sphinx-4.4.0-py3-none-any.whl", hash = "sha256:5da895959511473857b6d0200f56865ed62c31e8f82dd338063b84ec022701fe"},
 | 
			
		||||
    {file = "Sphinx-4.4.0.tar.gz", hash = "sha256:6caad9786055cb1fa22b4a365c1775816b876f91966481765d7d50e9f0dd35cc"},
 | 
			
		||||
    {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"},
 | 
			
		||||
    {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"},
 | 
			
		||||
]
 | 
			
		||||
sphinx-bootstrap-theme = [
 | 
			
		||||
    {file = "sphinx-bootstrap-theme-0.8.1.tar.gz", hash = "sha256:683e3b735448dadd0149f76edecf95ff4bd9157787e9e77e0d048ca6f1d680df"},
 | 
			
		||||
@@ -2165,16 +2182,16 @@ typed-ast = [
 | 
			
		||||
    {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"},
 | 
			
		||||
]
 | 
			
		||||
types-freezegun = [
 | 
			
		||||
    {file = "types-freezegun-1.1.7.tar.gz", hash = "sha256:e9d1327e98c6caa8f65de0018ded274347e8e34198d59aa9a5c24ad9265d5e5e"},
 | 
			
		||||
    {file = "types_freezegun-1.1.7-py3-none-any.whl", hash = "sha256:efa02341ad35e4f24b3346d09b60332e967586a5bc495a56b1c9c927b4c788b6"},
 | 
			
		||||
    {file = "types-freezegun-1.1.8.tar.gz", hash = "sha256:f4bb08c54aa46816c77a1b5c7a2a54f5393c3ce58e7d4002e67f9081b411e921"},
 | 
			
		||||
    {file = "types_freezegun-1.1.8-py3-none-any.whl", hash = "sha256:59dd99a6d168bdb5bf884fda6daebd68c35bc86383df183cf182abc6dfcbb7b7"},
 | 
			
		||||
]
 | 
			
		||||
types-pytz = [
 | 
			
		||||
    {file = "types-pytz-2021.3.6.tar.gz", hash = "sha256:74547fd90d8d8ab4f1eedf3a344a7d186d97486973895f81221a712e1e2cd993"},
 | 
			
		||||
    {file = "types_pytz-2021.3.6-py3-none-any.whl", hash = "sha256:6805c72d51118923c5bf98633c39593d5b464d2ab49a803440e2d7ab6b8920df"},
 | 
			
		||||
]
 | 
			
		||||
types-requests = [
 | 
			
		||||
    {file = "types-requests-2.27.15.tar.gz", hash = "sha256:2d371183c535208d2cc8fe7473d9b49c344c7077eb70302eb708638fb86086a8"},
 | 
			
		||||
    {file = "types_requests-2.27.15-py3-none-any.whl", hash = "sha256:77d09182a68e447e9e8b0ffc21abf54618b96f07689dffbb6a41cf0356542969"},
 | 
			
		||||
    {file = "types-requests-2.27.16.tar.gz", hash = "sha256:c8010c18b291a7efb60b1452dbe12530bc25693dd657e70c62803fcdc4bffe9b"},
 | 
			
		||||
    {file = "types_requests-2.27.16-py3-none-any.whl", hash = "sha256:2437a5f4d16c0c8bd7539a8126d492b7aeb41e6cda670d76b286c7f83a658d42"},
 | 
			
		||||
]
 | 
			
		||||
types-urllib3 = [
 | 
			
		||||
    {file = "types-urllib3-1.26.11.tar.gz", hash = "sha256:24d64e441168851eb05f1d022de18ae31558f5649c8f1117e384c2e85e31315b"},
 | 
			
		||||
@@ -2184,13 +2201,17 @@ typing-extensions = [
 | 
			
		||||
    {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
 | 
			
		||||
    {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
 | 
			
		||||
]
 | 
			
		||||
ua-parser = [
 | 
			
		||||
    {file = "ua-parser-0.10.0.tar.gz", hash = "sha256:47b1782ed130d890018d983fac37c2a80799d9e0b9c532e734c67cf70f185033"},
 | 
			
		||||
    {file = "ua_parser-0.10.0-py2.py3-none-any.whl", hash = "sha256:46ab2e383c01dbd2ab284991b87d624a26a08f72da4d7d413f5bfab8b9036f8a"},
 | 
			
		||||
]
 | 
			
		||||
urllib3 = [
 | 
			
		||||
    {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
 | 
			
		||||
    {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
 | 
			
		||||
]
 | 
			
		||||
werkzeug = [
 | 
			
		||||
    {file = "Werkzeug-2.0.3-py3-none-any.whl", hash = "sha256:1421ebfc7648a39a5c58c601b154165d05cf47a3cd0ccb70857cbdacf6c8f2b8"},
 | 
			
		||||
    {file = "Werkzeug-2.0.3.tar.gz", hash = "sha256:b863f8ff057c522164b6067c9e28b041161b4be5ba4d0daceeaa50a163822d3c"},
 | 
			
		||||
    {file = "Werkzeug-2.1.1-py3-none-any.whl", hash = "sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6"},
 | 
			
		||||
    {file = "Werkzeug-2.1.1.tar.gz", hash = "sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74"},
 | 
			
		||||
]
 | 
			
		||||
wrapt = [
 | 
			
		||||
    {file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
[tool.poetry]
 | 
			
		||||
name = "fittrackee"
 | 
			
		||||
version = "0.6.1"
 | 
			
		||||
version = "0.6.2"
 | 
			
		||||
description = "Self-hosted outdoor workout/activity tracker"
 | 
			
		||||
authors = ["SamR1"]
 | 
			
		||||
license = "AGPL-3.0"
 | 
			
		||||
@@ -24,9 +24,9 @@ exclude = ["fittrackee/tests"]
 | 
			
		||||
 | 
			
		||||
[tool.poetry.dependencies]
 | 
			
		||||
python = "^3.7"
 | 
			
		||||
dramatiq = {version = "^1.12.3", extras = ["redis"]}
 | 
			
		||||
flask = "^2.0"
 | 
			
		||||
flask-bcrypt = "^0.7.1"
 | 
			
		||||
dramatiq = {version = "^1.13", extras = ["redis"]}
 | 
			
		||||
flask = "^2.1"
 | 
			
		||||
flask-bcrypt = "^1.0"
 | 
			
		||||
flask-dramatiq = "^0.6.0"
 | 
			
		||||
flask-migrate = "^3.1"
 | 
			
		||||
gpxpy = "=1.3.4"
 | 
			
		||||
@@ -40,9 +40,10 @@ shortuuid = "^1.0.8"
 | 
			
		||||
staticmap = "^0.5.4"
 | 
			
		||||
SQLAlchemy = "1.4.32"
 | 
			
		||||
pyOpenSSL = "^22.0"
 | 
			
		||||
ua-parser = "^0.10.0"
 | 
			
		||||
 | 
			
		||||
[tool.poetry.dev-dependencies]
 | 
			
		||||
black = "^22.1"
 | 
			
		||||
black = "^22.3"
 | 
			
		||||
freezegun = "^1.2"
 | 
			
		||||
mypy = "^0.942"
 | 
			
		||||
pytest = "^7.1"
 | 
			
		||||
@@ -58,7 +59,7 @@ sphinxcontrib-httpdomain = "^1.7"
 | 
			
		||||
types-pytz = "^2021.3"
 | 
			
		||||
types-requests = "^2.27"
 | 
			
		||||
types-freezegun = "^1.1"
 | 
			
		||||
Sphinx = "^4.4.0"
 | 
			
		||||
Sphinx = "^4.5"
 | 
			
		||||
 | 
			
		||||
[tool.poetry.scripts]
 | 
			
		||||
fittrackee = 'fittrackee.__main__:main'
 | 
			
		||||
 
 | 
			
		||||