Merge pull request #165 from SamR1/user-account-update

Improve user registration and account update
This commit is contained in:
Sam 2022-03-26 20:50:42 +01:00 committed by GitHub
commit b1160f73ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
200 changed files with 7889 additions and 1986 deletions

View File

@ -68,6 +68,14 @@ jobs:
--health-retries 5 --health-retries 5
selenium: selenium:
image: selenium/standalone-firefox image: selenium/standalone-firefox
mailhog:
image: mailhog/mailhog:latest
redis:
image: redis:latest
env:
APP_SETTINGS: fittrackee.config.End2EndTestingConfig
EMAIL_URL: "smtp://mailhog:1025"
REDIS_URL: "redis://redis:6379"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Poetry and Dependencies - name: Install Poetry and Dependencies
@ -83,4 +91,5 @@ jobs:
setsid nohup flask run --with-threads -h 0.0.0.0 -p 5000 >> nohup.out 2>&1 & setsid nohup flask run --with-threads -h 0.0.0.0 -p 5000 >> nohup.out 2>&1 &
export TEST_APP_URL=http://$(hostname --ip-address):5000 export TEST_APP_URL=http://$(hostname --ip-address):5000
sleep 5 sleep 5
nohup flask worker --processes=1 >> nohup.out 2>&1 &
pytest e2e --driver Remote --capability browserName firefox --host selenium --port 4444 pytest e2e --driver Remote --capability browserName firefox --host selenium --port 4444

View File

@ -4,8 +4,9 @@ Authentication
.. autoflask:: fittrackee:create_app() .. autoflask:: fittrackee:create_app()
:endpoints: :endpoints:
auth.register_user, auth.register_user,
auth.confirm_account,
auth.resend_account_confirmation_email,
auth.login_user, auth.login_user,
auth.logout_user,
auth.get_authenticated_user_profile, auth.get_authenticated_user_profile,
auth.edit_user, auth.edit_user,
auth.edit_user_preferences, auth.edit_user_preferences,
@ -14,4 +15,6 @@ Authentication
auth.edit_picture, auth.edit_picture,
auth.del_picture, auth.del_picture,
auth.request_password_reset, auth.request_password_reset,
auth.update_password auth.update_user_account,
auth.update_password,
auth.update_email

View File

@ -44,9 +44,13 @@ Workouts
- average speed (**new in 0.5.1**) - average speed (**new in 0.5.1**)
- User records by sports: - User records by sports:
- average speed - average speed
- farest distance - farthest distance
- longest duration - longest duration
- maximum speed - maximum speed
.. note::
Records may differ from records displayed by the application that originally generated the gpx files.
- Workouts list and filter. Only sports with workouts are displayed in sport dropdown. - Workouts list and filter. Only sports with workouts are displayed in sport dropdown.
.. note:: .. note::
@ -58,6 +62,7 @@ Account & preferences
- A user can create, update and deleted his account - A user can create, update and deleted his account
- A user can set language, timezone and first day of week. - A user can set language, timezone and first day of week.
- A user can reset his password (*new in 0.3.0*) - A user can reset his password (*new in 0.3.0*)
- A user can change his email address (*new in 0.6.0*)
- A user can choose between metric system and imperial system for distance, elevation and speed display (*new in 0.5.0*) - A user can choose between metric system and imperial system for distance, elevation and speed display (*new in 0.5.0*)
- A user can set sport preferences (*new in 0.5.0*): - A user can set sport preferences (*new in 0.5.0*):
- change sport color (used for sport image and charts) - change sport color (used for sport image and charts)
@ -82,6 +87,7 @@ Administration
- maximum size of uploaded files - maximum size of uploaded files
- maximum size of zip archive - maximum size of zip archive
- maximum number of files in the zip archive. If an archive contains more files, only the configured number of files is processed, without raising errors. - maximum number of files in the zip archive. If an archive contains more files, only the configured number of files is processed, without raising errors.
- administrator email for contact (*new in 0.6.0*)
.. warning:: .. warning::
Updating server configuration may be necessary to handle large files (like `nginx <https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size>`_ for instance). Updating server configuration may be necessary to handle large files (like `nginx <https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size>`_ for instance).
@ -89,8 +95,12 @@ Administration
- **Users** - **Users**
- display users list and details - display and filter users list
- edit a user to add/remove administration rights - edit a user to:
- add/remove administration rights
- activate his account (*new in 0.6.0*)
- update his email (in case his account is locked) (*new in 0.6.0*)
- reset his password (in case his account is locked) (*new in 0.6.0*)
- delete a user - delete a user
- **Sports** - **Sports**

View File

@ -298,6 +298,8 @@ For instance, copy and update ``.env`` file from ``.env.example`` and source the
$ fittrackee_set_admin <username> $ fittrackee_set_admin <username>
.. note::
If the user account is inactive, it activates it.
From sources From sources
^^^^^^^^^^^^ ^^^^^^^^^^^^

View File

@ -130,7 +130,9 @@
<dl class="http post"> <dl class="http post">
<dt class="sig sig-object http" id="post--api-auth-register"> <dt class="sig sig-object http" id="post--api-auth-register">
<span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/auth/register</span></span><a class="headerlink" href="#post--api-auth-register" title="Permalink to this definition"></a></dt> <span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/auth/register</span></span><a class="headerlink" href="#post--api-auth-register" title="Permalink to this definition"></a></dt>
<dd><p>register a user</p> <dd><p>register a user and send confirmation email.</p>
<p>The newly created account is inactive. The user must confirm his email
to activate it.</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/auth/register</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/auth/register</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -138,14 +140,12 @@
</div> </div>
<p><strong>Example responses</strong>:</p> <p><strong>Example responses</strong>:</p>
<ul class="simple"> <ul class="simple">
<li><p>successful registration</p></li> <li><p>success</p></li>
</ul> </ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">201</span> <span class="ne">CREATED</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">SUCCESS</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
<span class="p">{</span><span class="w"></span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;auth_token&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;JSON Web Token&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;successfully registered&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;success&quot;</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;success&quot;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span> <span class="p">}</span><span class="w"></span>
</pre></div> </pre></div>
@ -165,23 +165,24 @@
<dl class="field-list simple"> <dl class="field-list simple">
<dt class="field-odd">Request JSON Object</dt> <dt class="field-odd">Request JSON Object</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>username</strong> (<em>string</em>) user name (3 to 12 characters required)</p></li> <li><p><strong>username</strong> (<em>string</em>) username (3 to 30 characters required)</p></li>
<li><p><strong>email</strong> (<em>string</em>) user email</p></li> <li><p><strong>email</strong> (<em>string</em>) user email</p></li>
<li><p><strong>password</strong> (<em>string</em>) password (8 characters required)</p></li> <li><p><strong>password</strong> (<em>string</em>) password (8 characters required)</p></li>
<li><p><strong>password_conf</strong> (<em>string</em>) password confirmation</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-even">Status Codes</dt> <dt class="field-even">Status Codes</dt>
<dd class="field-even"><ul class="simple"> <dd class="field-even"><ul class="simple">
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2">201 Created</a></span> successfully registered</p></li> <li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> success</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> <ul> <li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> <ul>
<li><p>invalid payload</p></li> <li><p>invalid payload</p></li>
<li><p>sorry, that user already exists</p></li> <li><p>sorry, that username is already taken</p></li>
<li><dl class="simple"> <li><dl class="simple">
<dt>Errors:</dt><dd><ul> <dt>Errors:</dt><dd><ul>
<li><p>username: 3 to 12 characters required</p></li> <li><p>username: 3 to 30 characters required</p></li>
<li><p>username:
only alphanumeric characters and the underscore
character “_” allowed</p></li>
<li><p>email: valid email must be provided</p></li> <li><p>email: valid email must be provided</p></li>
<li><p>password: password and password confirmation dont match</p></li>
<li><p>password: 8 characters required</p></li> <li><p>password: 8 characters required</p></li>
</ul> </ul>
</dd> </dd>
@ -196,10 +197,82 @@
</dl> </dl>
</dd></dl> </dd></dl>
<dl class="http post">
<dt class="sig sig-object http" id="post--api-auth-account-confirm">
<span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/auth/account/confirm</span></span><a class="headerlink" href="#post--api-auth-account-confirm" title="Permalink to this definition"></a></dt>
<dd><p>activate user account after registration</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/auth/account/confirm</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div>
</div>
<p><strong>Example response</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;auth_token&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;JSON Web Token&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;account confirmation successful&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;success&quot;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<dl class="field-list simple">
<dt class="field-odd">Request JSON Object</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>token</strong> (<em>string</em>) confirmation token</p></li>
</ul>
</dd>
<dt class="field-even">Status Codes</dt>
<dd class="field-even"><ul class="simple">
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> account confirmation successful</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> invalid payload</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a></span> error, please try again or contact the administrator</p></li>
</ul>
</dd>
</dl>
</dd></dl>
<dl class="http post">
<dt class="sig sig-object http" id="post--api-auth-account-resend-confirmation">
<span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/auth/account/resend-confirmation</span></span><a class="headerlink" href="#post--api-auth-account-resend-confirmation" title="Permalink to this definition"></a></dt>
<dd><p>resend email with instructions to confirm account</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/auth/account/resend-confirmation</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div>
</div>
<p><strong>Example response</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;confirmation email resent&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;success&quot;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<dl class="field-list simple">
<dt class="field-odd">Request JSON Object</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>email</strong> (<em>string</em>) user email</p></li>
</ul>
</dd>
<dt class="field-even">Status Codes</dt>
<dd class="field-even"><ul class="simple">
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> confirmation email resent</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> invalid payload</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a></span> error, please try again or contact the administrator</p></li>
</ul>
</dd>
</dl>
</dd></dl>
<dl class="http post"> <dl class="http post">
<dt class="sig sig-object http" id="post--api-auth-login"> <dt class="sig sig-object http" id="post--api-auth-login">
<span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/auth/login</span></span><a class="headerlink" href="#post--api-auth-login" title="Permalink to this definition"></a></dt> <span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/auth/login</span></span><a class="headerlink" href="#post--api-auth-login" title="Permalink to this definition"></a></dt>
<dd><p>user login</p> <dd><p>user login</p>
<p>Only user with an active account can log in.</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/auth/login</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/auth/login</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -222,7 +295,7 @@
<ul class="simple"> <ul class="simple">
<li><p>error on login</p></li> <li><p>error on login</p></li>
</ul> </ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">404</span> <span class="ne">NOT FOUND</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">401</span> <span class="ne">UNAUTHORIZED</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
<span class="p">{</span><span class="w"></span> <span class="p">{</span><span class="w"></span>
@ -235,7 +308,7 @@
<dt class="field-odd">Request JSON Object</dt> <dt class="field-odd">Request JSON Object</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>email</strong> (<em>string</em>) user email</p></li> <li><p><strong>email</strong> (<em>string</em>) user email</p></li>
<li><p><strong>password_conf</strong> (<em>string</em>) password confirmation</p></li> <li><p><strong>password</strong> (<em>string</em>) password</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-even">Status Codes</dt> <dt class="field-even">Status Codes</dt>
@ -249,59 +322,10 @@
</dl> </dl>
</dd></dl> </dd></dl>
<dl class="http get">
<dt class="sig sig-object http" id="get--api-auth-logout">
<span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/auth/logout</span></span><a class="headerlink" href="#get--api-auth-logout" title="Permalink to this definition"></a></dt>
<dd><p>user logout</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/auth/logout</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div>
</div>
<p><strong>Example responses</strong>:</p>
<ul class="simple">
<li><p>successful logout</p></li>
</ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;successfully logged out&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;success&quot;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<ul class="simple">
<li><p>error on login</p></li>
</ul>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">401</span> <span class="ne">UNAUTHORIZED</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;provide a valid auth token&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;error&quot;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<dl class="field-list simple">
<dt class="field-odd">Request Headers</dt>
<dd class="field-odd"><ul class="simple">
<li><p><span><a class="reference external" href="https://tools.ietf.org/html/rfc7235#section-4.2">Authorization</a></span> OAuth 2.0 Bearer Token</p></li>
</ul>
</dd>
<dt class="field-even">Status Codes</dt>
<dd class="field-even"><ul class="simple">
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> successfully logged out</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> provide a valid auth token</p></li>
</ul>
</dd>
</dl>
</dd></dl>
<dl class="http get"> <dl class="http get">
<dt class="sig sig-object http" id="get--api-auth-profile"> <dt class="sig sig-object http" id="get--api-auth-profile">
<span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/auth/profile</span></span><a class="headerlink" href="#get--api-auth-profile" title="Permalink to this definition"></a></dt> <span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/auth/profile</span></span><a class="headerlink" href="#get--api-auth-profile" title="Permalink to this definition"></a></dt>
<dd><p>get authenticated user info</p> <dd><p>get authenticated user info (profile, account, preferences)</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/auth/profile</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/auth/profile</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -320,6 +344,7 @@
<span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam@example.com&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam@example.com&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;imperial_units&quot;</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">&quot;imperial_units&quot;</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">&quot;is_active&quot;</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">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
@ -402,7 +427,7 @@
<dl class="http post"> <dl class="http post">
<dt class="sig sig-object http" id="post--api-auth-profile-edit"> <dt class="sig sig-object http" id="post--api-auth-profile-edit">
<span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/auth/profile/edit</span></span><a class="headerlink" href="#post--api-auth-profile-edit" title="Permalink to this definition"></a></dt> <span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/auth/profile/edit</span></span><a class="headerlink" href="#post--api-auth-profile-edit" title="Permalink to this definition"></a></dt>
<dd><p>edit authenticated user</p> <dd><p>edit authenticated user profile</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/auth/profile/edit</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/auth/profile/edit</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -421,6 +446,7 @@
<span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam@example.com&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam@example.com&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;imperial_units&quot;</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">&quot;imperial_units&quot;</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">&quot;is_active&quot;</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">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
@ -489,8 +515,6 @@
<li><p><strong>location</strong> (<em>string</em>) user location</p></li> <li><p><strong>location</strong> (<em>string</em>) user location</p></li>
<li><p><strong>bio</strong> (<em>string</em>) user biography</p></li> <li><p><strong>bio</strong> (<em>string</em>) user biography</p></li>
<li><p><strong>birth_date</strong> (<em>string</em>) user birth date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span></code>)</p></li> <li><p><strong>birth_date</strong> (<em>string</em>) user birth date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span></code>)</p></li>
<li><p><strong>password</strong> (<em>string</em>) user password</p></li>
<li><p><strong>password_conf</strong> (<em>string</em>) user password confirmation</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-even">Request Headers</dt> <dt class="field-even">Request Headers</dt>
@ -503,7 +527,6 @@
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> user profile updated</p></li> <li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> user profile updated</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> <ul> <li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> <ul>
<li><p>invalid payload</p></li> <li><p>invalid payload</p></li>
<li><p>password: password and password confirmation dont match</p></li>
</ul> </ul>
</p></li> </p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> <ul> <li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> <ul>
@ -540,6 +563,7 @@
<span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam@example.com&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam@example.com&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;imperial_units&quot;</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">&quot;imperial_units&quot;</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">&quot;is_active&quot;</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">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
@ -862,10 +886,140 @@
</dl> </dl>
</dd></dl> </dd></dl>
<dl class="http patch">
<dt class="sig sig-object http" id="patch--api-auth-profile-edit-account">
<span class="sig-name descname"><span class="pre">PATCH</span> </span><span class="sig-name descname"><span class="pre">/api/auth/profile/edit/account</span></span><a class="headerlink" href="#patch--api-auth-profile-edit-account" title="Permalink to this definition"></a></dt>
<dd><p>update authenticated user email and password</p>
<p>It sends emails:</p>
<ul class="simple">
<li><p>Password change</p></li>
<li><p>Email change:</p>
<ul>
<li><p>one to the current address to inform user</p></li>
<li><p>another one to the new address to confirm it.</p></li>
</ul>
</li>
</ul>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">PATCH</span> <span class="nn">/api/auth/profile/edit/account</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div>
</div>
<p><strong>Example response</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;data&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;admin&quot;</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">&quot;bio&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;birth_date&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;created_at&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Sun, 14 Jul 2019 14:09:58 GMT&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam@example.com&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;imperial_units&quot;</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">&quot;is_active&quot;</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">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;nb_sports&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;nb_workouts&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;picture&quot;</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">&quot;records&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">9</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;record_type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;AS&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;sport_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;user&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;value&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">18</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;workout_date&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;workout_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;id&quot;</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">&quot;record_type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;FD&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;sport_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;user&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;value&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">18</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;workout_date&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;workout_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;record_type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;LD&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;sport_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;user&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;value&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;1:01:00&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;workout_date&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;workout_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;record_type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;MS&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;sport_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;user&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;value&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">18</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;workout_date&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Sun, 07 Jul 2019 08:00:00 GMT&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;workout_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;hvYBqYBRa7wwXpaStWR4V2&quot;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;sports_list&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">6</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;timezone&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Europe/Paris&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;total_distance&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">67.895</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;total_duration&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;6:50:27&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;username&quot;</span><span class="p">:</span><span class="w"> </span><span class="nt">&quot;sam&quot;</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;weekm&quot;</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="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;user account updated&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;success&quot;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<dl class="field-list simple">
<dt class="field-odd">Request JSON Object</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>email</strong> (<em>string</em>) user email</p></li>
<li><p><strong>password</strong> (<em>string</em>) user current password</p></li>
<li><p><strong>new_password</strong> (<em>string</em>) user new password</p></li>
</ul>
</dd>
<dt class="field-even">Request Headers</dt>
<dd class="field-even"><ul class="simple">
<li><p><span><a class="reference external" href="https://tools.ietf.org/html/rfc7235#section-4.2">Authorization</a></span> OAuth 2.0 Bearer Token</p></li>
</ul>
</dd>
<dt class="field-odd">Status Codes</dt>
<dd class="field-odd"><ul class="simple">
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> user account updated</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> <ul>
<li><p>invalid payload</p></li>
<li><p>email is missing</p></li>
<li><p>current password is missing</p></li>
<li><p>email: valid email must be provided</p></li>
<li><p>password: 8 characters required</p></li>
</ul>
</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> <ul>
<li><p>provide a valid auth token</p></li>
<li><p>signature expired, please log in again</p></li>
<li><p>invalid token, please log in again</p></li>
<li><p>invalid credentials</p></li>
</ul>
</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a></span> error, please try again or contact the administrator</p></li>
</ul>
</dd>
</dl>
</dd></dl>
<dl class="http post"> <dl class="http post">
<dt class="sig sig-object http" id="post--api-auth-password-update"> <dt class="sig sig-object http" id="post--api-auth-password-update">
<span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/auth/password/update</span></span><a class="headerlink" href="#post--api-auth-password-update" title="Permalink to this definition"></a></dt> <span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/auth/password/update</span></span><a class="headerlink" href="#post--api-auth-password-update" title="Permalink to this definition"></a></dt>
<dd><p>update user password</p> <dd><p>update user password after password reset request</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/auth/password/update</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/auth/password/update</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -885,7 +1039,6 @@
<dt class="field-odd">Request JSON Object</dt> <dt class="field-odd">Request JSON Object</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>password</strong> (<em>string</em>) password (8 characters required)</p></li> <li><p><strong>password</strong> (<em>string</em>) password (8 characters required)</p></li>
<li><p><strong>password_conf</strong> (<em>string</em>) password confirmation</p></li>
<li><p><strong>token</strong> (<em>string</em>) password reset token</p></li> <li><p><strong>token</strong> (<em>string</em>) password reset token</p></li>
</ul> </ul>
</dd> </dd>
@ -900,6 +1053,41 @@
</dl> </dl>
</dd></dl> </dd></dl>
<dl class="http post">
<dt class="sig sig-object http" id="post--api-auth-email-update">
<span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/auth/email/update</span></span><a class="headerlink" href="#post--api-auth-email-update" title="Permalink to this definition"></a></dt>
<dd><p>update user email after confirmation</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/auth/email/update</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div>
</div>
<p><strong>Example response</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;email updated&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;success&quot;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<dl class="field-list simple">
<dt class="field-odd">Request JSON Object</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>token</strong> (<em>string</em>) password reset token</p></li>
</ul>
</dd>
<dt class="field-even">Status Codes</dt>
<dd class="field-even"><ul class="simple">
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> email updated</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> invalid payload</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a></span> error, please try again or contact the administrator</p></li>
</ul>
</dd>
</dl>
</dd></dl>
</section> </section>

View File

@ -180,6 +180,7 @@
<span class="p">{</span><span class="w"></span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;data&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;data&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;admin_contact&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin@example.com&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;gpx_limit_import&quot;</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">&quot;gpx_limit_import&quot;</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">&quot;is_registration_enabled&quot;</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">&quot;is_registration_enabled&quot;</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">&quot;max_single_file_size&quot;</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">&quot;max_single_file_size&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1048576</span><span class="p">,</span><span class="w"></span>
@ -193,6 +194,7 @@
<dl class="field-list simple"> <dl class="field-list simple">
<dt class="field-odd">Request JSON Object</dt> <dt class="field-odd">Request JSON Object</dt>
<dd class="field-odd"><ul class="simple"> <dd class="field-odd"><ul class="simple">
<li><p><strong>admin_contact</strong> (<em>string</em>) email to contact the administrator</p></li>
<li><p><strong>gpx_limit_import</strong> (<em>integer</em>) max number of files in zip archive</p></li> <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>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_single_file_size</strong> (<em>integer</em>) max size of a single file</p></li>
@ -213,6 +215,7 @@
<li><p>provide a valid auth token</p></li> <li><p>provide a valid auth token</p></li>
<li><p>signature expired, please log in again</p></li> <li><p>signature expired, please log in again</p></li>
<li><p>invalid token, please log in again</p></li> <li><p>invalid token, please log in again</p></li>
<li><p>valid email must be provided for admin contact</p></li>
</ul> </ul>
</p></li> </p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4">403 Forbidden</a></span> you do not have permissions</p></li> <li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4">403 Forbidden</a></span> you do not have permissions</p></li>

View File

@ -130,7 +130,9 @@
<dl class="http get"> <dl class="http get">
<dt class="sig sig-object http" id="get--api-users"> <dt class="sig sig-object http" id="get--api-users">
<span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/users</span></span><a class="headerlink" href="#get--api-users" title="Permalink to this definition"></a></dt> <span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/users</span></span><a class="headerlink" href="#get--api-users" title="Permalink to this definition"></a></dt>
<dd><p>Get all users</p> <dd><p>Get all users (regardless their account status), if authenticated user
has admin rights</p>
<p>It returns user preferences only for authenticated user.</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<ul class="simple"> <ul class="simple">
<li><p>without parameters</p></li> <li><p>without parameters</p></li>
@ -160,6 +162,7 @@
<span class="w"> </span><span class="nt">&quot;created_at&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Sun, 14 Jul 2019 14:09:58 GMT&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;created_at&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Sun, 14 Jul 2019 14:09:58 GMT&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin@example.com&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin@example.com&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;is_admin&quot;</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">&quot;imperial_units&quot;</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">&quot;imperial_units&quot;</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">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
@ -213,7 +216,8 @@
<span class="w"> </span><span class="nt">&quot;timezone&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Europe/Paris&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;timezone&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Europe/Paris&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;total_distance&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">67.895</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;total_distance&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">67.895</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;total_duration&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;6:50:27&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;total_duration&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;6:50:27&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;username&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin&quot;</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;username&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;weekm&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span> <span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;admin&quot;</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">&quot;admin&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
@ -222,6 +226,7 @@
<span class="w"> </span><span class="nt">&quot;created_at&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Sat, 20 Jul 2019 11:27:03 GMT&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;created_at&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Sat, 20 Jul 2019 11:27:03 GMT&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam@example.com&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sam@example.com&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;is_admin&quot;</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">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;fr&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;fr&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
@ -248,7 +253,7 @@
<li><p><strong>per_page</strong> (<em>integer</em>) number of users per page (default: 10, max: 50)</p></li> <li><p><strong>per_page</strong> (<em>integer</em>) number of users per page (default: 10, max: 50)</p></li>
<li><p><strong>q</strong> (<em>string</em>) query on user name</p></li> <li><p><strong>q</strong> (<em>string</em>) query on user name</p></li>
<li><p><strong>order_by</strong> (<em>string</em>) sorting criteria (<code class="docutils literal notranslate"><span class="pre">username</span></code>, <code class="docutils literal notranslate"><span class="pre">created_at</span></code>, <li><p><strong>order_by</strong> (<em>string</em>) sorting criteria (<code class="docutils literal notranslate"><span class="pre">username</span></code>, <code class="docutils literal notranslate"><span class="pre">created_at</span></code>,
<code class="docutils literal notranslate"><span class="pre">workouts_count</span></code>, <code class="docutils literal notranslate"><span class="pre">admin</span></code>)</p></li> <code class="docutils literal notranslate"><span class="pre">workouts_count</span></code>, <code class="docutils literal notranslate"><span class="pre">admin</span></code>, <code class="docutils literal notranslate"><span class="pre">is_active</span></code>)</p></li>
<li><p><strong>order</strong> (<em>string</em>) sorting order (default: <code class="docutils literal notranslate"><span class="pre">asc</span></code>)</p></li> <li><p><strong>order</strong> (<em>string</em>) sorting order (default: <code class="docutils literal notranslate"><span class="pre">asc</span></code>)</p></li>
</ul> </ul>
</dd> </dd>
@ -274,7 +279,8 @@
<dl class="http get"> <dl class="http get">
<dt class="sig sig-object http" id="get--api-users-(user_name)"> <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> <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</p> <dd><p>Get single user details. Only user with admin rights can get user details.</p>
<p>It returns user preferences only for authenticated user.</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/users/admin</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/users/admin</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -294,6 +300,7 @@
<span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin@example.com&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin@example.com&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;imperial_units&quot;</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">&quot;imperial_units&quot;</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">&quot;is_admin&quot;</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">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
@ -418,10 +425,16 @@
<dl class="http patch"> <dl class="http patch">
<dt class="sig sig-object http" id="patch--api-users-(user_name)"> <dt class="sig sig-object http" id="patch--api-users-(user_name)">
<span class="sig-name descname"><span class="pre">PATCH</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="#patch--api-users-(user_name)" title="Permalink to this definition"></a></dt> <span class="sig-name descname"><span class="pre">PATCH</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="#patch--api-users-(user_name)" title="Permalink to this definition"></a></dt>
<dd><p>Update user to add admin rights</p> <dd><p>Update user account</p>
<ul class="simple">
<li><p>add/remove admin rights (regardless user account status)</p></li>
<li><p>reset password (and send email to update user password)</p></li>
<li><p>update user email (and send email to update user password)</p></li>
<li><p>activate account for an inactive user</p></li>
</ul>
<p>Only user with admin rights can modify another user</p> <p>Only user with admin rights can modify another user</p>
<p><strong>Example request</strong>:</p> <p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">PATCH</span> <span class="nn">api/users/&lt;user_name&gt;</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">PATCH</span> <span class="nn">/api/users/&lt;user_name&gt;</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> <span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
</pre></div> </pre></div>
</div> </div>
@ -439,6 +452,7 @@
<span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin@example.com&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;email&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin@example.com&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;first_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;imperial_units&quot;</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">&quot;imperial_units&quot;</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">&quot;is_active&quot;</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">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;language&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;last_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span> <span class="w"> </span><span class="nt">&quot;location&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
@ -506,7 +520,10 @@
</dd> </dd>
<dt class="field-even">Request JSON Object</dt> <dt class="field-even">Request JSON Object</dt>
<dd class="field-even"><ul class="simple"> <dd class="field-even"><ul class="simple">
<li><p><strong>activate</strong> (<em>boolean</em>) activate user account</p></li>
<li><p><strong>admin</strong> (<em>boolean</em>) does the user have administrator rights</p></li> <li><p><strong>admin</strong> (<em>boolean</em>) does the user have administrator rights</p></li>
<li><p><strong>new_email</strong> (<em>boolean</em>) new user email</p></li>
<li><p><strong>reset_password</strong> (<em>boolean</em>) reset user password</p></li>
</ul> </ul>
</dd> </dd>
<dt class="field-odd">Request Headers</dt> <dt class="field-odd">Request Headers</dt>
@ -517,6 +534,12 @@
<dt class="field-even">Status Codes</dt> <dt class="field-even">Status Codes</dt>
<dd class="field-even"><ul class="simple"> <dd class="field-even"><ul class="simple">
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> success</p></li> <li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a></span> success</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a></span> <ul>
<li><p>invalid payload</p></li>
<li><p>valid email must be provided</p></li>
<li><p>new email must be different than curent email</p></li>
</ul>
</p></li>
<li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> <ul> <li><p><span><a class="reference external" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a></span> <ul>
<li><p>provide a valid auth token</p></li> <li><p>provide a valid auth token</p></li>
<li><p>signature expired, please log in again</p></li> <li><p>signature expired, please log in again</p></li>

View File

@ -209,13 +209,19 @@
<li><dl class="simple"> <li><dl class="simple">
<dt>User records by sports:</dt><dd><ul class="simple"> <dt>User records by sports:</dt><dd><ul class="simple">
<li><p>average speed</p></li> <li><p>average speed</p></li>
<li><p>farest distance</p></li> <li><p>farthest distance</p></li>
<li><p>longest duration</p></li> <li><p>longest duration</p></li>
<li><p>maximum speed</p></li> <li><p>maximum speed</p></li>
</ul> </ul>
</dd> </dd>
</dl> </dl>
</li> </li>
</ul>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Records may differ from records displayed by the application that originally generated the gpx files.</p>
</div>
<ul class="simple">
<li><p>Workouts list and filter. Only sports with workouts are displayed in sport dropdown.</p></li> <li><p>Workouts list and filter. Only sports with workouts are displayed in sport dropdown.</p></li>
</ul> </ul>
<div class="admonition note"> <div class="admonition note">
@ -229,6 +235,7 @@
<li><p>A user can create, update and deleted his account</p></li> <li><p>A user can create, update and deleted his account</p></li>
<li><p>A user can set language, timezone and first day of week.</p></li> <li><p>A user can set language, timezone and first day of week.</p></li>
<li><p>A user can reset his password (<em>new in 0.3.0</em>)</p></li> <li><p>A user can reset his password (<em>new in 0.3.0</em>)</p></li>
<li><p>A user can change his email address (<em>new in 0.6.0</em>)</p></li>
<li><p>A user can choose between metric system and imperial system for distance, elevation and speed display (<em>new in 0.5.0</em>)</p></li> <li><p>A user can choose between metric system and imperial system for distance, elevation and speed display (<em>new in 0.5.0</em>)</p></li>
<li><dl class="simple"> <li><dl class="simple">
<dt>A user can set sport preferences (<em>new in 0.5.0</em>):</dt><dd><ul> <dt>A user can set sport preferences (<em>new in 0.5.0</em>):</dt><dd><ul>
@ -260,6 +267,7 @@
<li><p>maximum size of uploaded files</p></li> <li><p>maximum size of uploaded files</p></li>
<li><p>maximum size of zip archive</p></li> <li><p>maximum size of zip archive</p></li>
<li><p>maximum number of files in the zip archive. If an archive contains more files, only the configured number of files is processed, without raising errors.</p></li> <li><p>maximum number of files in the zip archive. If an archive contains more files, only the configured number of files is processed, without raising errors.</p></li>
<li><p>administrator email for contact (<em>new in 0.6.0</em>)</p></li>
</ul> </ul>
<div class="admonition warning"> <div class="admonition warning">
<p class="admonition-title">Warning</p> <p class="admonition-title">Warning</p>
@ -268,8 +276,17 @@
</li> </li>
<li><p><strong>Users</strong></p> <li><p><strong>Users</strong></p>
<ul class="simple"> <ul class="simple">
<li><p>display users list and details</p></li> <li><p>display and filter users list</p></li>
<li><p>edit a user to add/remove administration rights</p></li> <li><dl class="simple">
<dt>edit a user to:</dt><dd><ul>
<li><p>add/remove administration rights</p></li>
<li><p>activate his account (<em>new in 0.6.0</em>)</p></li>
<li><p>update his email (in case his account is locked) (<em>new in 0.6.0</em>)</p></li>
<li><p>reset his password (in case his account is locked) (<em>new in 0.6.0</em>)</p></li>
</ul>
</dd>
</dl>
</li>
<li><p>delete a user</p></li> <li><p>delete a user</p></li>
</ul> </ul>
</li> </li>

View File

@ -125,11 +125,6 @@
<tr class="pcap"><td></td><td>&#160;</td><td></td></tr> <tr class="pcap"><td></td><td>&#160;</td><td></td></tr>
<tr class="cap" id="cap-/api"><td></td><td> <tr class="cap" id="cap-/api"><td></td><td>
<strong>/api</strong></td><td></td></tr> <strong>/api</strong></td><td></td></tr>
<tr>
<td></td>
<td>
<a href="api/auth.html#get--api-auth-logout"><code class="xref">GET /api/auth/logout</code></a></td><td>
<em></em></td></tr>
<tr> <tr>
<td></td> <td></td>
<td> <td>
@ -235,6 +230,21 @@
<td> <td>
<a href="api/workouts.html#get--api-workouts-map_tile-(s)-(z)-(x)-(y).png"><code class="xref">GET /api/workouts/map_tile/(s)/(z)/(x)/(y).png</code></a></td><td> <a href="api/workouts.html#get--api-workouts-map_tile-(s)-(z)-(x)-(y).png"><code class="xref">GET /api/workouts/map_tile/(s)/(z)/(x)/(y).png</code></a></td><td>
<em></em></td></tr> <em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/auth.html#post--api-auth-account-confirm"><code class="xref">POST /api/auth/account/confirm</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/auth.html#post--api-auth-account-resend-confirmation"><code class="xref">POST /api/auth/account/resend-confirmation</code></a></td><td>
<em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/auth.html#post--api-auth-email-update"><code class="xref">POST /api/auth/email/update</code></a></td><td>
<em></em></td></tr>
<tr> <tr>
<td></td> <td></td>
<td> <td>
@ -305,6 +315,11 @@
<td> <td>
<a href="api/workouts.html#delete--api-workouts-(string-workout_short_id)"><code class="xref">DELETE /api/workouts/(string:workout_short_id)</code></a></td><td> <a href="api/workouts.html#delete--api-workouts-(string-workout_short_id)"><code class="xref">DELETE /api/workouts/(string:workout_short_id)</code></a></td><td>
<em></em></td></tr> <em></em></td></tr>
<tr>
<td></td>
<td>
<a href="api/auth.html#patch--api-auth-profile-edit-account"><code class="xref">PATCH /api/auth/profile/edit/account</code></a></td><td>
<em></em></td></tr>
<tr> <tr>
<td></td> <td></td>
<td> <td>

View File

@ -557,6 +557,10 @@ $ <span class="nb">source</span> .env
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ fittrackee_set_admin &lt;username&gt; <div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ fittrackee_set_admin &lt;username&gt;
</pre></div> </pre></div>
</div> </div>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>If the user account is inactive, it activates it.</p>
</div>
</section> </section>
<section id="from-sources"> <section id="from-sources">
<h3>From sources<a class="headerlink" href="#from-sources" title="Permalink to this headline"></a></h3> <h3>From sources<a class="headerlink" href="#from-sources" title="Permalink to this headline"></a></h3>

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,9 @@ Authentication
.. autoflask:: fittrackee:create_app() .. autoflask:: fittrackee:create_app()
:endpoints: :endpoints:
auth.register_user, auth.register_user,
auth.confirm_account,
auth.resend_account_confirmation_email,
auth.login_user, auth.login_user,
auth.logout_user,
auth.get_authenticated_user_profile, auth.get_authenticated_user_profile,
auth.edit_user, auth.edit_user,
auth.edit_user_preferences, auth.edit_user_preferences,
@ -14,4 +15,6 @@ Authentication
auth.edit_picture, auth.edit_picture,
auth.del_picture, auth.del_picture,
auth.request_password_reset, auth.request_password_reset,
auth.update_password auth.update_user_account,
auth.update_password,
auth.update_email

View File

@ -44,9 +44,13 @@ Workouts
- average speed (**new in 0.5.1**) - average speed (**new in 0.5.1**)
- User records by sports: - User records by sports:
- average speed - average speed
- farest distance - farthest distance
- longest duration - longest duration
- maximum speed - maximum speed
.. note::
Records may differ from records displayed by the application that originally generated the gpx files.
- Workouts list and filter. Only sports with workouts are displayed in sport dropdown. - Workouts list and filter. Only sports with workouts are displayed in sport dropdown.
.. note:: .. note::
@ -58,6 +62,7 @@ Account & preferences
- A user can create, update and deleted his account - A user can create, update and deleted his account
- A user can set language, timezone and first day of week. - A user can set language, timezone and first day of week.
- A user can reset his password (*new in 0.3.0*) - A user can reset his password (*new in 0.3.0*)
- A user can change his email address (*new in 0.6.0*)
- A user can choose between metric system and imperial system for distance, elevation and speed display (*new in 0.5.0*) - A user can choose between metric system and imperial system for distance, elevation and speed display (*new in 0.5.0*)
- A user can set sport preferences (*new in 0.5.0*): - A user can set sport preferences (*new in 0.5.0*):
- change sport color (used for sport image and charts) - change sport color (used for sport image and charts)
@ -82,6 +87,7 @@ Administration
- maximum size of uploaded files - maximum size of uploaded files
- maximum size of zip archive - maximum size of zip archive
- maximum number of files in the zip archive. If an archive contains more files, only the configured number of files is processed, without raising errors. - maximum number of files in the zip archive. If an archive contains more files, only the configured number of files is processed, without raising errors.
- administrator email for contact (*new in 0.6.0*)
.. warning:: .. warning::
Updating server configuration may be necessary to handle large files (like `nginx <https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size>`_ for instance). Updating server configuration may be necessary to handle large files (like `nginx <https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size>`_ for instance).
@ -89,8 +95,12 @@ Administration
- **Users** - **Users**
- display users list and details - display and filter users list
- edit a user to add/remove administration rights - edit a user to:
- add/remove administration rights
- activate his account (*new in 0.6.0*)
- update his email (in case his account is locked) (*new in 0.6.0*)
- reset his password (in case his account is locked) (*new in 0.6.0*)
- delete a user - delete a user
- **Sports** - **Sports**

View File

@ -298,6 +298,8 @@ For instance, copy and update ``.env`` file from ``.env.example`` and source the
$ fittrackee_set_admin <username> $ fittrackee_set_admin <username>
.. note::
If the user account is inactive, it activates it.
From sources From sources
^^^^^^^^^^^^ ^^^^^^^^^^^^

View File

@ -1,9 +1,4 @@
from .utils import ( from .utils import TEST_URL, login_valid_user, register_valid_user_and_logout
TEST_URL,
assert_navbar,
login_valid_user,
register_valid_user_and_logout,
)
URL = f'{TEST_URL}/login' URL = f'{TEST_URL}/login'
@ -34,10 +29,20 @@ class TestLogin:
assert 'Register' in links[0].text assert 'Register' in links[0].text
assert links[1].tag_name == 'a' assert links[1].tag_name == 'a'
assert 'Forgot password?' in links[1].text assert 'Forgot password?' in links[1].text
assert links[2].tag_name == 'a'
assert "Didn't received instructions?" in links[2].text
def test_user_can_log_in(self, selenium): def test_user_can_log_in(self, selenium):
user = register_valid_user_and_logout(selenium) user = register_valid_user_and_logout(selenium)
login_valid_user(selenium, user) login_valid_user(selenium, user)
assert_navbar(selenium, user) nav = selenium.find_element_by_id('nav').text
assert 'Register' not in nav
assert 'Login' not in nav
assert 'Dashboard' in nav
assert 'Workouts' in nav
assert 'Statistics' in nav
assert 'Add a workout' in nav
assert user['username'] in nav
assert 'Logout' in nav

View File

@ -1,13 +1,15 @@
from .utils import TEST_URL, register_valid_user from .utils import register_valid_user
URL = f'{TEST_URL}/profile'
class TestProfile: class TestProfile:
def test_it_displays_user_profile(self, selenium): def test_it_displays_user_profile(self, selenium):
user = register_valid_user(selenium) user = register_valid_user(selenium)
selenium.get(URL) app_menu = selenium.find_element_by_class_name('nav-items-user-menu')
profile_link = app_menu.find_elements_by_class_name('nav-item')[1]
profile_link.click()
selenium.implicitly_wait(1)
user_header = selenium.find_element_by_class_name('user-header') user_header = selenium.find_element_by_class_name('user-header')
assert user['username'] in user_header.text assert user['username'] in user_header.text
assert '0\nworkouts' in user_header.text assert '0\nworkouts' in user_header.text

View File

@ -1,9 +1,7 @@
from .utils import ( from .utils import (
TEST_URL, TEST_URL,
assert_navbar,
random_string, random_string,
register, register,
register_valid_user,
register_valid_user_and_logout, register_valid_user_and_logout,
) )
@ -23,21 +21,40 @@ class TestRegistration:
assert inputs[1].get_attribute('type') == 'email' assert inputs[1].get_attribute('type') == 'email'
assert inputs[2].get_attribute('id') == 'password' assert inputs[2].get_attribute('id') == 'password'
assert inputs[2].get_attribute('type') == 'password' assert inputs[2].get_attribute('type') == 'password'
assert inputs[3].get_attribute('id') == 'confirm-password'
assert inputs[3].get_attribute('type') == 'password' form_infos = selenium.find_elements_by_class_name('form-info')
assert len(form_infos) == 3
assert form_infos[0].text == (
'3 to 30 characters required, only alphanumeric characters and '
'the underscore character "_" allowed.'
)
assert form_infos[1].text == 'Enter a valid email address.'
assert form_infos[2].text == 'At least 8 characters required.'
button = selenium.find_element_by_tag_name('button') button = selenium.find_element_by_tag_name('button')
assert button.get_attribute('type') == 'submit' assert button.get_attribute('type') == 'submit'
assert 'Register' in button.text assert 'Register' in button.text
link = selenium.find_element_by_class_name('links') links = selenium.find_elements_by_class_name('links')
assert link.tag_name == 'a' assert links[0].tag_name == 'a'
assert 'Login' in link.text assert 'Login' in links[0].text
assert links[1].tag_name == 'a'
assert "Didn't received instructions?" in links[1].text
def test_user_can_register(self, selenium): def test_user_can_register(self, selenium):
user = register_valid_user(selenium) user = {
'username': random_string(),
'email': f'{random_string()}@example.com',
'password': 'p@ssw0rd',
}
assert_navbar(selenium, user) register(selenium, user)
message = selenium.find_element_by_class_name('success-message').text
assert (
'A link to activate your account has been '
'emailed to the address provided.'
) in message
def test_user_can_not_register_with_invalid_email(self, selenium): def test_user_can_not_register_with_invalid_email(self, selenium):
user_name = random_string() user_name = random_string()
@ -45,7 +62,6 @@ class TestRegistration:
'username': user_name, 'username': user_name,
'email': user_name, 'email': user_name,
'password': 'p@ssw0rd', 'password': 'p@ssw0rd',
'password_conf': 'p@ssw0rd',
} }
register(selenium, user_infos) register(selenium, user_infos)
@ -65,29 +81,19 @@ class TestRegistration:
assert selenium.current_url == URL assert selenium.current_url == URL
errors = selenium.find_element_by_class_name('error-message').text errors = selenium.find_element_by_class_name('error-message').text
assert 'Sorry, that user already exists.' in errors assert 'Sorry, that username is already taken.' in errors
def test_user_can_not_register_if_email_is_already_taken(self, selenium): def test_user_does_not_return_error_if_email_is_already_taken(
self, selenium
):
user = register_valid_user_and_logout(selenium) user = register_valid_user_and_logout(selenium)
user['username'] = random_string() user['username'] = random_string()
register(selenium, user) register(selenium, user)
assert selenium.current_url == URL assert selenium.current_url == f'{TEST_URL}/login'
errors = selenium.find_element_by_class_name('error-message').text message = selenium.find_element_by_class_name('success-message').text
assert 'Sorry, that user already exists.' in errors assert (
'A link to activate your account has been '
def test_it_displays_error_if_passwords_do_not_match(self, selenium): 'emailed to the address provided.'
user_name = random_string() ) in message
user_infos = {
'username': user_name,
'email': f'{user_name}@example.com',
'password': 'p@ssw0rd',
'password_conf': 'password',
}
register(selenium, user_infos)
assert selenium.current_url == URL
errors = selenium.find_element_by_class_name('error-message').text
assert 'password and password confirmation don\'t match' in errors

View File

@ -1,18 +1,25 @@
import os import os
import random import random
import re
import string import string
import time
import requests
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from urllib3.util import parse_url
TEST_APP_URL = os.getenv('TEST_APP_URL') TEST_APP_URL = os.getenv('TEST_APP_URL')
TEST_CLIENT_URL = os.getenv('TEST_CLIENT_URL') TEST_CLIENT_URL = os.getenv('TEST_CLIENT_URL')
E2E_ARGS = os.getenv('E2E_ARGS') E2E_ARGS = os.getenv('E2E_ARGS')
TEST_URL = TEST_CLIENT_URL if E2E_ARGS == 'client' else TEST_APP_URL TEST_URL = TEST_CLIENT_URL if E2E_ARGS == 'client' else TEST_APP_URL
EMAIL_URL = os.getenv('EMAIL_URL', 'smtp://none:none@0.0.0.0:1025')
parsed_email_url = parse_url(EMAIL_URL)
EMAIL_API_URL = f'http://{parsed_email_url.host}:8025'
def random_string(length=8): def random_string(length=8):
return ''.join(random.choice(string.ascii_letters) for x in range(length)) return ''.join(random.choice(string.ascii_letters) for _ in range(length))
def register(selenium, user): def register(selenium, user):
@ -24,8 +31,6 @@ def register(selenium, user):
email.send_keys(user.get('email')) email.send_keys(user.get('email'))
password = selenium.find_element_by_id('password') password = selenium.find_element_by_id('password')
password.send_keys(user.get('password')) password.send_keys(user.get('password'))
password_conf = selenium.find_element_by_id('confirm-password')
password_conf.send_keys(user.get('password_conf'))
submit_button = selenium.find_element_by_tag_name('button') submit_button = selenium.find_element_by_tag_name('button')
submit_button.click() submit_button.click()
@ -47,44 +52,36 @@ def register_valid_user(selenium):
'username': user_name, 'username': user_name,
'email': f'{user_name}@example.com', 'email': f'{user_name}@example.com',
'password': 'p@ssw0rd', 'password': 'p@ssw0rd',
'password_conf': 'p@ssw0rd',
} }
register(selenium, user) register(selenium, user)
WebDriverWait(selenium, 15).until(EC.url_changes(f"{TEST_URL}/register")) WebDriverWait(selenium, 30).until(EC.url_changes(f"{TEST_URL}/register"))
confirm_account(selenium, user)
return user return user
def register_valid_user_and_logout(selenium): def register_valid_user_and_logout(selenium):
user_name = random_string() user = register_valid_user(selenium)
user = {
'username': user_name,
'email': f'{user_name}@example.com',
'password': 'p@ssw0rd',
'password_conf': 'p@ssw0rd',
}
register(selenium, user)
WebDriverWait(selenium, 15).until(EC.url_changes(f"{TEST_URL}/register"))
user_menu = selenium.find_element_by_class_name('nav-items-user-menu') user_menu = selenium.find_element_by_class_name('nav-items-user-menu')
logout_link = user_menu.find_elements_by_class_name('nav-item')[2] logout_link = user_menu.find_elements_by_class_name('nav-item')[2]
logout_link.click() logout_link.click()
selenium.implicitly_wait(1)
return user return user
def confirm_account(selenium, user):
time.sleep(1)
response = requests.get(
f"{EMAIL_API_URL}/api/v2/search?kind=to&query={user['email']}"
)
response.raise_for_status()
results = response.json()
message = results['items'][0]['Content']['Body']
link = re.search(r'Verify your email: (.+?)\r\n', message).groups()[0]
link = link.replace('http://0.0.0.0:5000', TEST_URL)
selenium.get(link)
WebDriverWait(selenium, 15).until(EC.url_changes(link))
def login_valid_user(selenium, user): def login_valid_user(selenium, user):
login(selenium, user) login(selenium, user)
WebDriverWait(selenium, 10).until(EC.url_changes(f"{TEST_URL}/login")) WebDriverWait(selenium, 10).until(EC.url_changes(f"{TEST_URL}/login"))
return user return user
def assert_navbar(selenium, user):
nav = selenium.find_element_by_id('nav').text
assert 'Register' not in nav
assert 'Login' not in nav
assert 'Dashboard' in nav
assert 'Workouts' in nav
assert 'Statistics' in nav
assert 'Add a workout' in nav
assert user['username'] in nav
assert 'Logout' in nav

View File

@ -1,5 +1,6 @@
import logging import logging
import os import os
import re
import shutil import shutil
from importlib import import_module, reload from importlib import import_module, reload
from typing import Any from typing import Any
@ -15,6 +16,7 @@ from flask_bcrypt import Bcrypt
from flask_dramatiq import Dramatiq from flask_dramatiq import Dramatiq
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import ProgrammingError
from fittrackee.emails.email import EmailService from fittrackee.emails.email import EmailService
@ -66,9 +68,16 @@ def create_app() -> Flask:
with app.app_context(): with app.app_context():
# Note: check if "app_config" table exist to avoid errors when # Note: check if "app_config" table exist to avoid errors when
# dropping tables on dev environments # dropping tables on dev environments
try:
if db.engine.dialect.has_table(db.engine.connect(), 'app_config'): if db.engine.dialect.has_table(db.engine.connect(), 'app_config'):
db_app_config = get_or_init_config() db_app_config = get_or_init_config()
update_app_config_from_database(app, db_app_config) update_app_config_from_database(app, db_app_config)
except ProgrammingError as e:
# avoid error on AppConfig migration
if re.match(
r'psycopg2.errors.UndefinedColumn(.*)app_config.', str(e)
):
pass
from .application.app_config import config_blueprint # noqa from .application.app_config import config_blueprint # noqa
from .users.auth import auth_blueprint # noqa from .users.auth import auth_blueprint # noqa

View File

@ -11,6 +11,7 @@ from fittrackee.responses import (
) )
from fittrackee.users.decorators import authenticate_as_admin from fittrackee.users.decorators import authenticate_as_admin
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.users.utils.controls import is_valid_email
from .models import AppConfig from .models import AppConfig
from .utils import update_app_config_from_database, verify_app_config from .utils import update_app_config_from_database, verify_app_config
@ -87,6 +88,7 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
{ {
"data": { "data": {
"admin_contact": "admin@example.com",
"gpx_limit_import": 10, "gpx_limit_import": 10,
"is_registration_enabled": true, "is_registration_enabled": true,
"max_single_file_size": 1048576, "max_single_file_size": 1048576,
@ -96,6 +98,7 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
"status": "success" "status": "success"
} }
:<json string admin_contact: email to contact the administrator
:<json integer gpx_limit_import: max number of files in zip archive :<json integer gpx_limit_import: max number of files in zip archive
:<json boolean is_registration_enabled: is registration enabled ? :<json boolean is_registration_enabled: is registration enabled ?
:<json integer max_single_file_size: max size of a single file :<json integer max_single_file_size: max size of a single file
@ -110,6 +113,7 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
- provide a valid auth token - provide a valid auth token
- signature expired, please log in again - signature expired, please log in again
- invalid token, please log in again - invalid token, please log in again
- valid email must be provided for admin contact
:statuscode 403: you do not have permissions :statuscode 403: you do not have permissions
:statuscode 500: error when updating configuration :statuscode 500: error when updating configuration
""" """
@ -118,6 +122,9 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
return InvalidPayloadErrorResponse() return InvalidPayloadErrorResponse()
ret = verify_app_config(config_data) ret = verify_app_config(config_data)
admin_contact = config_data.get('admin_contact')
if admin_contact and not is_valid_email(admin_contact):
ret.append('valid email must be provided for admin contact')
if ret: if ret:
return InvalidPayloadErrorResponse(message=ret) return InvalidPayloadErrorResponse(message=ret)
@ -133,6 +140,8 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
config.max_zip_file_size = config_data.get('max_zip_file_size') config.max_zip_file_size = config_data.get('max_zip_file_size')
if 'max_users' in config_data: if 'max_users' in config_data:
config.max_users = config_data.get('max_users') config.max_users = config_data.get('max_users')
if 'admin_contact' in config_data:
config.admin_contact = admin_contact if admin_contact else None
if config.max_zip_file_size < config.max_single_file_size: if config.max_zip_file_size < config.max_single_file_size:
return InvalidPayloadErrorResponse( return InvalidPayloadErrorResponse(

View File

@ -23,6 +23,7 @@ class AppConfig(BaseModel):
db.Integer, default=1048576, nullable=False db.Integer, default=1048576, nullable=False
) )
max_zip_file_size = db.Column(db.Integer, default=10485760, nullable=False) max_zip_file_size = db.Column(db.Integer, default=10485760, nullable=False)
admin_contact = db.Column(db.String(255), nullable=True)
@property @property
def is_registration_enabled(self) -> bool: def is_registration_enabled(self) -> bool:
@ -43,6 +44,7 @@ class AppConfig(BaseModel):
def serialize(self) -> Dict: def serialize(self) -> Dict:
return { return {
'admin_contact': self.admin_contact,
'gpx_limit_import': self.gpx_limit_import, 'gpx_limit_import': self.gpx_limit_import,
'is_registration_enabled': self.is_registration_enabled, 'is_registration_enabled': self.is_registration_enabled,
'max_single_file_size': self.max_single_file_size, 'max_single_file_size': self.max_single_file_size,

View File

@ -12,7 +12,6 @@ else:
class BaseConfig: class BaseConfig:
"""Base configuration"""
DEBUG = False DEBUG = False
TESTING = False TESTING = False
@ -49,8 +48,6 @@ class BaseConfig:
class DevelopmentConfig(BaseConfig): class DevelopmentConfig(BaseConfig):
"""Development configuration"""
DEBUG = True DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
SECRET_KEY = 'development key' SECRET_KEY = 'development key'
@ -59,8 +56,6 @@ class DevelopmentConfig(BaseConfig):
class TestingConfig(BaseConfig): class TestingConfig(BaseConfig):
"""Testing configuration"""
DEBUG = True DEBUG = True
TESTING = True TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL') SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL')
@ -74,9 +69,11 @@ class TestingConfig(BaseConfig):
SENDER_EMAIL = 'fittrackee@example.com' SENDER_EMAIL = 'fittrackee@example.com'
class ProductionConfig(BaseConfig): class End2EndTestingConfig(TestingConfig):
"""Production configuration""" DRAMATIQ_BROKER_URL = os.getenv('REDIS_URL', 'redis://')
class ProductionConfig(BaseConfig):
DEBUG = False DEBUG = False
# https://docs.sqlalchemy.org/en/13/core/pooling.html#using-connection-pools-with-multiprocessing-or-os-fork # noqa # https://docs.sqlalchemy.org/en/13/core/pooling.html#using-connection-pools-with-multiprocessing-or-os-fork # noqa
SQLALCHEMY_ENGINE_OPTIONS = ( SQLALCHEMY_ENGINE_OPTIONS = (

View File

@ -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.f76e2e3d.js"></script><script defer="defer" src="/static/js/app.3c006379.js"></script><link href="/static/css/app.28b1f60f.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.1308e452.js"></script><script defer="defer" src="/static/js/app.b8a9753b.js"></script><link href="/static/css/app.b6e7921a.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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
#account-confirmation[data-v-785df978]{display:flex;flex-direction:column;align-items:center}#account-confirmation svg[data-v-785df978]{stroke:none;fill-rule:nonzero;fill:var(--app-color);filter:var(--svg-filter);width:100px}#account-confirmation .error-message[data-v-785df978]{font-size:1.1em;text-align:center;display:flex;flex-direction:column}@media screen and (max-width:1000px){#account-confirmation .error-message[data-v-785df978]{font-size:1em}}#email-update[data-v-8c2ec9ce]{display:flex;flex-direction:column;align-items:center}#email-update svg[data-v-8c2ec9ce]{stroke:none;fill-rule:nonzero;fill:var(--app-color);filter:var(--svg-filter);width:100px}#email-update .error-message[data-v-8c2ec9ce]{font-size:1.1em;text-align:center;display:flex;flex-direction:column}@media screen and (max-width:1000px){#email-update .error-message[data-v-8c2ec9ce]{font-size:1em}}#profile[data-v-05463732],#profile[data-v-05463732] .profile-form{display:flex;flex-direction:column}#profile[data-v-05463732] .profile-form hr{border-color:var(--card-border-color);border-width:1px 0 0 0}#profile[data-v-05463732] .profile-form .form-items{display:flex;flex-direction:column}#profile[data-v-05463732] .profile-form .form-items input{margin:5px 0}#profile[data-v-05463732] .profile-form .form-items select{height:35px;padding:5px 0}#profile[data-v-05463732] .profile-form .form-items ::v-deep(.custom-textarea) textarea{padding:5px}#profile[data-v-05463732] .profile-form .form-items .form-item{display:flex;flex-direction:column;padding:10px}#profile[data-v-05463732] .profile-form .form-items .birth-date{height:20px}#profile[data-v-05463732] .profile-form .form-buttons{display:flex;margin-top:10px;padding:10px 0;gap:10px}#user[data-v-af7007f4]{margin:auto;width:700px}@media screen and (max-width:1000px){#user[data-v-af7007f4]{width:100%;margin:0 auto 50px auto}}

View File

@ -1 +0,0 @@
#profile[data-v-163d82f7],#profile[data-v-163d82f7] .profile-form{display:flex;flex-direction:column}#profile[data-v-163d82f7] .profile-form hr{border-color:var(--card-border-color);border-width:1px 0 0 0}#profile[data-v-163d82f7] .profile-form .form-items{display:flex;flex-direction:column}#profile[data-v-163d82f7] .profile-form .form-items input{margin:5px 0}#profile[data-v-163d82f7] .profile-form .form-items select{height:35px;padding:5px 0}#profile[data-v-163d82f7] .profile-form .form-items ::v-deep(.custom-textarea) textarea{padding:5px}#profile[data-v-163d82f7] .profile-form .form-items .form-item{display:flex;flex-direction:column;padding:10px}#profile[data-v-163d82f7] .profile-form .form-items .birth-date{height:20px}#profile[data-v-163d82f7] .profile-form .form-buttons{display:flex;margin-top:10px;padding:10px 0;gap:10px}#user[data-v-10e7b479]{margin:auto;width:700px}@media screen and (max-width:1000px){#user[data-v-10e7b479]{width:100%;margin:0 auto 50px auto}}

View File

@ -1 +0,0 @@
#password-action-done[data-v-18334f6d]{display:flex;flex-direction:column;align-items:center}#password-action-done svg[data-v-18334f6d]{stroke:none;fill-rule:nonzero;fill:var(--app-color);filter:var(--svg-filter);width:100px}#password-action-done .password-message[data-v-18334f6d]{font-size:1.1em;text-align:center}@media screen and (max-width:1000px){#password-action-done .password-message[data-v-18334f6d]{font-size:1em}}#password-reset-request[data-v-68377e44] .card .card-content #user-form{width:100%}#password-reset[data-v-f5e39b60]{display:flex}#password-reset .container[data-v-f5e39b60]{display:flex;justify-content:center;width:50%}@media screen and (max-width:700px){#password-reset .container[data-v-f5e39b60]{width:100%;margin:0 auto 50px auto}}

View File

@ -0,0 +1 @@
#account-confirmation-email[data-v-66aca424]{display:flex;flex-direction:column}#account-confirmation-email .email-sent[data-v-66aca424]{display:flex;flex-direction:column;align-items:center}#account-confirmation-email .email-sent svg[data-v-66aca424]{stroke:none;fill-rule:nonzero;fill:var(--app-color);filter:var(--svg-filter);width:100px}#account-confirmation-email .email-sent .email-sent-message[data-v-66aca424]{font-size:1.1em;text-align:center}@media screen and (max-width:1000px){#account-confirmation-email .email-sent .email-sent-message[data-v-66aca424]{font-size:1em}}#account-confirmation-email[data-v-66aca424] .card .card-content #user-auth-form{margin-top:0}#account-confirmation-email[data-v-66aca424] .card .card-content #user-auth-form #user-form{width:100%}#account-confirmation[data-v-35aad344]{display:flex}#account-confirmation .container[data-v-35aad344]{display:flex;justify-content:center;width:50%}@media screen and (max-width:700px){#account-confirmation .container[data-v-35aad344]{width:100%}}#password-action-done[data-v-eac78356]{display:flex;flex-direction:column;align-items:center}#password-action-done svg[data-v-eac78356]{stroke:none;fill-rule:nonzero;fill:var(--app-color);filter:var(--svg-filter);width:100px}#password-action-done .password-message[data-v-eac78356]{font-size:1.1em;text-align:center}@media screen and (max-width:1000px){#password-action-done .password-message[data-v-eac78356]{font-size:1em}}#password-reset-request[data-v-68377e44] .card .card-content #user-form{width:100%}#password-reset[data-v-a1cc55c4]{display:flex}#password-reset .container[data-v-a1cc55c4]{display:flex;justify-content:center;width:50%}@media screen and (max-width:700px){#password-reset .container[data-v-a1cc55c4]{width:100%}}

View File

@ -1 +1 @@
.chart-menu[data-v-af15954c]{display:flex}.chart-menu .chart-arrow[data-v-af15954c],.chart-menu .time-frames[data-v-af15954c]{flex-grow:1;text-align:center}.chart-menu .chart-arrow[data-v-af15954c]{cursor:pointer}.sports-menu{display:flex;flex-wrap:wrap;padding:10px}.sports-menu label{display:flex;align-items:center;font-size:.9em;font-weight:400;min-width:120px;padding:10px}@media screen and (max-width:1000px){.sports-menu label{min-width:100px}}@media screen and (max-width:500px){.sports-menu label{min-width:20px}.sports-menu label .sport-label{display:none}}.sports-menu .sport-img{padding:3px;width:20px;height:20px}#user-statistics.stats-disabled[data-v-7d54529b]{opacity:.3;pointer-events:none}#user-statistics[data-v-7d54529b] .chart-radio{justify-content:space-around;padding:30px 10px 10px 10px}#statistics[data-v-0d93da6e]{display:flex;width:100%}#statistics .container[data-v-0d93da6e]{display:flex;flex-direction:column;width:100%} .chart-menu[data-v-af15954c]{display:flex}.chart-menu .chart-arrow[data-v-af15954c],.chart-menu .time-frames[data-v-af15954c]{flex-grow:1;text-align:center}.chart-menu .chart-arrow[data-v-af15954c]{cursor:pointer}.sports-menu{display:flex;flex-wrap:wrap;padding:10px}.sports-menu label{display:flex;align-items:center;font-size:.9em;font-weight:400;min-width:120px;padding:10px}@media screen and (max-width:1000px){.sports-menu label{min-width:100px}}@media screen and (max-width:500px){.sports-menu label{min-width:20px}.sports-menu label .sport-label{display:none}}.sports-menu .sport-img{padding:3px;width:20px;height:20px}#user-statistics.stats-disabled[data-v-7d54529b]{opacity:.3;pointer-events:none}#user-statistics[data-v-7d54529b] .chart-radio{justify-content:space-around;padding:30px 10px 10px 10px}#statistics[data-v-2e341d4e]{display:flex;width:100%}#statistics .container[data-v-2e341d4e]{display:flex;flex-direction:column;width:100%}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[845],{4264:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});var n=r(6252),a=r(2262),s=r(3577),u=r(2119),o=r(7167),c=r(8602),i=r(9917);const l={key:0,id:"account-confirmation",class:"center-card with-margin"},E={class:"error-message"};var d=(0,n.aZ)({setup(e){const t=(0,u.yj)(),r=(0,u.tv)(),d=(0,i.o)(),S=(0,n.Fl)((()=>d.getters[c.SY.GETTERS.ERROR_MESSAGES])),_=(0,n.Fl)((()=>t.query.token));function m(){_.value?d.dispatch(c.YN.ACTIONS.CONFIRM_ACCOUNT,{token:_.value}):r.push("/")}return(0,n.wF)((()=>m())),(0,n.Ah)((()=>d.commit(c.SY.MUTATIONS.EMPTY_ERROR_MESSAGES))),(e,t)=>{const r=(0,n.up)("router-link");return(0,a.SU)(S)?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(o.Z),(0,n._)("p",E,[(0,n._)("span",null,(0,s.zw)(e.$t("error.SOMETHING_WRONG"))+".",1),(0,n.Wm)(r,{class:"links",to:"/account-confirmation/resend"},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.$t("buttons.ACCOUNT-CONFIRMATION-RESEND"))+"? ",1)])),_:1})])])):(0,n.kq)("",!0)}}}),S=r(3744);const _=(0,S.Z)(d,[["__scopeId","data-v-785df978"]]);var m=_},8160:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});var n=r(6252),a=r(2262),s=r(3577),u=r(2119),o=r(7167),c=r(8602),i=r(9917);const l={key:0,id:"email-update",class:"center-card with-margin"},E={class:"error-message"};var d=(0,n.aZ)({setup(e){const t=(0,u.yj)(),r=(0,u.tv)(),d=(0,i.o)(),S=(0,n.Fl)((()=>d.getters[c.YN.GETTERS.AUTH_USER_PROFILE])),_=(0,n.Fl)((()=>d.getters[c.YN.GETTERS.IS_AUTHENTICATED])),m=(0,n.Fl)((()=>d.getters[c.SY.GETTERS.ERROR_MESSAGES])),R=(0,n.Fl)((()=>t.query.token));function T(){R.value?d.dispatch(c.YN.ACTIONS.CONFIRM_EMAIL,{token:R.value,refreshUser:_.value}):r.push("/")}return(0,n.wF)((()=>T())),(0,n.Ah)((()=>d.commit(c.SY.MUTATIONS.EMPTY_ERROR_MESSAGES))),(0,n.YP)((()=>m.value),(e=>{S.value.username&&e&&r.push("/")})),(e,t)=>{const r=(0,n.up)("router-link"),u=(0,n.up)("i18n-t");return(0,a.SU)(m)&&!(0,a.SU)(S).username?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(o.Z),(0,n._)("p",E,[(0,n._)("span",null,(0,s.zw)(e.$t("error.SOMETHING_WRONG"))+".",1),(0,n._)("span",null,[(0,n.Wm)(u,{keypath:"user.PROFILE.ERRORED_EMAIL_UPDATE"},{default:(0,n.w5)((()=>[(0,n.Wm)(r,{to:"/login"},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.$t("user.LOG_IN")),1)])),_:1})])),_:1})])])])):(0,n.kq)("",!0)}}}),S=r(3744);const _=(0,S.Z)(d,[["__scopeId","data-v-8c2ec9ce"]]);var m=_},6266:function(e,t,r){r.r(t),r.d(t,{default:function(){return S}});var n=r(6252),a=r(2262),s=r(8602),u=r(9917);const o=e=>((0,n.dD)("data-v-05463732"),e=e(),(0,n.Cn)(),e),c={key:0,id:"profile",class:"container view"},i=o((()=>(0,n._)("div",{id:"bottom"},null,-1)));var l=(0,n.aZ)({setup(e){const t=(0,u.o)(),r=(0,n.Fl)((()=>t.getters[s.YN.GETTERS.AUTH_USER_PROFILE]));return(e,t)=>{const s=(0,n.up)("router-view");return(0,a.SU)(r).username?((0,n.wg)(),(0,n.iD)("div",c,[(0,n.Wm)(s,{user:(0,a.SU)(r)},null,8,["user"]),i])):(0,n.kq)("",!0)}}}),E=r(3744);const d=(0,E.Z)(l,[["__scopeId","data-v-05463732"]]);var S=d},9453:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});var n=r(6252),a=r(2262),s=r(2119),u=r(2179),o=r(4317),c=r(8602),i=r(9917);const l={key:0,id:"user",class:"view"},E={class:"box"};var d=(0,n.aZ)({props:{fromAdmin:{type:Boolean}},setup(e){const t=e,{fromAdmin:r}=(0,a.BK)(t),d=(0,s.yj)(),S=(0,i.o)(),_=(0,n.Fl)((()=>S.getters[c.RT.GETTERS.USER]));return(0,n.wF)((()=>{d.params.username&&"string"===typeof d.params.username&&S.dispatch(c.RT.ACTIONS.GET_USER,d.params.username)})),(0,n.Jd)((()=>{S.dispatch(c.RT.ACTIONS.EMPTY_USER)})),(e,t)=>(0,a.SU)(_).username?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(u.Z,{user:(0,a.SU)(_)},null,8,["user"]),(0,n._)("div",E,[(0,n.Wm)(o.Z,{user:(0,a.SU)(_),"from-admin":(0,a.SU)(r)},null,8,["user","from-admin"])])])):(0,n.kq)("",!0)}}),S=r(3744);const _=(0,S.Z)(d,[["__scopeId","data-v-af7007f4"]]);var m=_}}]);
//# sourceMappingURL=profile.2bd853ba.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,2 +0,0 @@
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[845],{2023:function(e,r,t){t.r(r),t.d(r,{default:function(){return f}});var s=t(6252),a=t(2262),n=t(8602),u=t(9917);const i=e=>((0,s.dD)("data-v-163d82f7"),e=e(),(0,s.Cn)(),e),c={key:0,id:"profile",class:"container view"},d=i((()=>(0,s._)("div",{id:"bottom"},null,-1)));var o=(0,s.aZ)({setup(e){const r=(0,u.o)(),t=(0,s.Fl)((()=>r.getters[n.YN.GETTERS.AUTH_USER_PROFILE]));return(e,r)=>{const n=(0,s.up)("router-view");return(0,a.SU)(t).username?((0,s.wg)(),(0,s.iD)("div",c,[(0,s.Wm)(n,{user:(0,a.SU)(t)},null,8,["user"]),d])):(0,s.kq)("",!0)}}}),l=t(3744);const v=(0,l.Z)(o,[["__scopeId","data-v-163d82f7"]]);var f=v},8368:function(e,r,t){t.r(r),t.d(r,{default:function(){return m}});var s=t(6252),a=t(2262),n=t(2119),u=t(5160),i=t(2165),c=t(8602),d=t(9917);const o={key:0,id:"user",class:"view"},l={class:"box"};var v=(0,s.aZ)({setup(e){const r=(0,n.yj)(),t=(0,d.o)(),v=(0,s.Fl)((()=>t.getters[c.RT.GETTERS.USER]));return(0,s.wF)((()=>{r.params.username&&"string"===typeof r.params.username&&t.dispatch(c.RT.ACTIONS.GET_USER,r.params.username)})),(0,s.Jd)((()=>{t.dispatch(c.RT.ACTIONS.EMPTY_USER)})),(e,r)=>(0,a.SU)(v).username?((0,s.wg)(),(0,s.iD)("div",o,[(0,s.Wm)(u.Z,{user:(0,a.SU)(v)},null,8,["user"]),(0,s._)("div",l,[(0,s.Wm)(i.Z,{user:(0,a.SU)(v),"from-admin":!0},null,8,["user"])])])):(0,s.kq)("",!0)}}),f=t(3744);const p=(0,f.Z)(v,[["__scopeId","data-v-10e7b479"]]);var m=p}}]);
//# sourceMappingURL=profile.97ac14b7.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{2319:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});var r=t(6252),a=t(2262),l=t(3577),o=t(3324),c=t(9472);const n={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,r.aZ)({emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){let t=(0,a.iH)("month");const o=["week","month","year"];function c(e){t.value=e,s("timeFrameUpdate",e)}return(e,v)=>((0,r.wg)(),(0,r.iD)("div",n,[(0,r._)("div",i,[(0,r._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:v[0]||(v[0]=e=>s("arrowClick",!0))})]),(0,r._)("div",u,[(0,r._)("div",d,[((0,r.wg)(),(0,r.iD)(r.HY,null,(0,r.Ko)(o,(s=>(0,r._)("div",{class:"time-frame custom-checkbox",key:s},[(0,r._)("label",null,[(0,r._)("input",{type:"radio",id:s,name:s,checked:(0,a.SU)(t)===s,onInput:e=>c(s)},null,40,p),(0,r._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,r._)("div",m,[(0,r._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:v[1]||(v[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const S=(0,k.Z)(v,[["__scopeId","data-v-af15954c"]]);var w=S,f=t(631);const _={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,r.aZ)({props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:c}=(0,o.QT)(),n=(0,r.f3)("sportColors"),{selectedSportIds:i}=(0,a.BK)(t),u=(0,r.Fl)((()=>(0,f.xH)(t.userSports,c)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,r.up)("SportImage");return(0,r.wg)(),(0,r.iD)("div",_,[((0,r.wg)(!0),(0,r.iD)(r.HY,null,(0,r.Ko)((0,a.SU)(u),(e=>((0,r.wg)(),(0,r.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,a.SU)(n)[e.label]})},[(0,r._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,a.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,r.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,r._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,r.aZ)({props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:n}=(0,a.BK)(s);let i=(0,a.iH)("month");const u=(0,a.iH)(v(i.value)),d=(0,r.Fl)((()=>(0,f.xH)(s.sports,t))),p=(0,a.iH)(S(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function S(e){return e.map((e=>e.id))}function _(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,r.YP)((()=>s.sports),(e=>{p.value=S(e)})),(e,s)=>(0,a.SU)(d)?((0,r.wg)(),(0,r.iD)("div",y,[(0,r.Wm)(w,{onTimeFrameUpdate:m,onArrowClick:k}),(0,r.Wm)(c.Z,{sports:(0,a.SU)(l),user:(0,a.SU)(n),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,r.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,a.SU)(l),onSelectedSportIdsUpdate:_},null,8,["selected-sport-ids","user-sports"])])):(0,r.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-7d54529b"]]);var Z=F,D=t(5630),H=t(8602),x=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,r.aZ)({setup(e){const s=(0,x.o)(),t=(0,r.Fl)((()=>s.getters[H.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,r.Fl)((()=>s.getters[H.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const c=(0,r.up)("Card");return(0,r.wg)(),(0,r.iD)("div",E,[(0,a.SU)(t).username?((0,r.wg)(),(0,r.iD)("div",R,[(0,r.Wm)(c,null,{title:(0,r.w5)((()=>[(0,r.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,r.w5)((()=>[(0,r.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,a.SU)(t).nb_workouts}),user:(0,a.SU)(t),sports:(0,a.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,a.SU)(t).nb_workouts?((0,r.wg)(),(0,r.j4)(D.Z,{key:0})):(0,r.kq)("",!0)])):(0,r.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-0d93da6e"]]);var A=P}}]); "use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{7749:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});var r=t(6252),a=t(2262),l=t(3577),o=t(3324),c=t(7402);const n={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,r.aZ)({emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){let t=(0,a.iH)("month");const o=["week","month","year"];function c(e){t.value=e,s("timeFrameUpdate",e)}return(e,v)=>((0,r.wg)(),(0,r.iD)("div",n,[(0,r._)("div",i,[(0,r._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:v[0]||(v[0]=e=>s("arrowClick",!0))})]),(0,r._)("div",u,[(0,r._)("div",d,[((0,r.wg)(),(0,r.iD)(r.HY,null,(0,r.Ko)(o,(s=>(0,r._)("div",{class:"time-frame custom-checkbox",key:s},[(0,r._)("label",null,[(0,r._)("input",{type:"radio",id:s,name:s,checked:(0,a.SU)(t)===s,onInput:e=>c(s)},null,40,p),(0,r._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,r._)("div",m,[(0,r._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:v[1]||(v[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const S=(0,k.Z)(v,[["__scopeId","data-v-af15954c"]]);var w=S,f=t(631);const _={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,r.aZ)({props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:c}=(0,o.QT)(),n=(0,r.f3)("sportColors"),{selectedSportIds:i}=(0,a.BK)(t),u=(0,r.Fl)((()=>(0,f.xH)(t.userSports,c)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,r.up)("SportImage");return(0,r.wg)(),(0,r.iD)("div",_,[((0,r.wg)(!0),(0,r.iD)(r.HY,null,(0,r.Ko)((0,a.SU)(u),(e=>((0,r.wg)(),(0,r.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,a.SU)(n)[e.label]})},[(0,r._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,a.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,r.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,r._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,r.aZ)({props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:n}=(0,a.BK)(s);let i=(0,a.iH)("month");const u=(0,a.iH)(v(i.value)),d=(0,r.Fl)((()=>(0,f.xH)(s.sports,t))),p=(0,a.iH)(S(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function S(e){return e.map((e=>e.id))}function _(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,r.YP)((()=>s.sports),(e=>{p.value=S(e)})),(e,s)=>(0,a.SU)(d)?((0,r.wg)(),(0,r.iD)("div",y,[(0,r.Wm)(w,{onTimeFrameUpdate:m,onArrowClick:k}),(0,r.Wm)(c.Z,{sports:(0,a.SU)(l),user:(0,a.SU)(n),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,r.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,a.SU)(l),onSelectedSportIdsUpdate:_},null,8,["selected-sport-ids","user-sports"])])):(0,r.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-7d54529b"]]);var Z=F,D=t(5630),H=t(8602),x=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,r.aZ)({setup(e){const s=(0,x.o)(),t=(0,r.Fl)((()=>s.getters[H.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,r.Fl)((()=>s.getters[H.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const c=(0,r.up)("Card");return(0,r.wg)(),(0,r.iD)("div",E,[(0,a.SU)(t).username?((0,r.wg)(),(0,r.iD)("div",R,[(0,r.Wm)(c,null,{title:(0,r.w5)((()=>[(0,r.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,r.w5)((()=>[(0,r.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,a.SU)(t).nb_workouts}),user:(0,a.SU)(t),sports:(0,a.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,a.SU)(t).nb_workouts?((0,r.wg)(),(0,r.j4)(D.Z,{key:0})):(0,r.kq)("",!0)])):(0,r.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-2e341d4e"]]);var A=P}}]);
//# sourceMappingURL=statistics.221180ef.js.map //# sourceMappingURL=statistics.31ff9ae2.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,3 +11,43 @@ def reset_password_email(user: Dict, email_data: Dict) -> None:
recipient=user['email'], recipient=user['email'],
data=email_data, data=email_data,
) )
@dramatiq.actor(queue_name='fittrackee_emails')
def email_updated_to_current_address(user: Dict, email_data: Dict) -> None:
email_service.send(
template='email_update_to_current_email',
lang=user['language'],
recipient=user['email'],
data=email_data,
)
@dramatiq.actor(queue_name='fittrackee_emails')
def email_updated_to_new_address(user: Dict, email_data: Dict) -> None:
email_service.send(
template='email_update_to_new_email',
lang=user['language'],
recipient=user['email'],
data=email_data,
)
@dramatiq.actor(queue_name='fittrackee_emails')
def password_change_email(user: Dict, email_data: Dict) -> None:
email_service.send(
template='password_change',
lang=user['language'],
recipient=user['email'],
data=email_data,
)
@dramatiq.actor(queue_name='fittrackee_emails')
def account_confirmation_email(user: Dict, email_data: Dict) -> None:
email_service.send(
template='account_confirmation',
lang=user['language'],
recipient=user['email'],
data=email_data,
)

View File

@ -0,0 +1,32 @@
{% extends "layout.html" %}
{% block title %}Confirm your account{% endblock %}
{% block preheader %}Use this link to confirm your account.{% endblock %}
{% block content %}<h1>Hi {{username}},</h1>
<p>You have created an account on FitTrackee account. Use the link below to confirm your address email.</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{account_confirmation_url}}" class="f-fallback button button--green" target="_blank">Verify your email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
{% if operating_system and browser_name %}For security, this request was received from a {{operating_system}} device using {{browser_name}}.
{% endif %}If this account creation wasn't initiated by you, please ignore this email.
</p>
<p>Thanks,
<br>The FitTrackee Team</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">{{account_confirmation_url}}</p>
</td>
</tr>
</table>{% endblock %}

View File

@ -0,0 +1,12 @@
Hi {{username}},
You have created an account on FitTrackee account. Use the link below to confirm your address email.
Verify your email: {{ account_confirmation_url }}
{% if operating_system and browser_name %}For security, this request was received from a {{operating_system}} device using {{browser_name}}.
{% endif %}If this account creation wasn't initiated by you, please ignore this email.
Thanks,
The FitTrackee Team
{{fittrackee_url}}

View File

@ -0,0 +1 @@
FitTrackee - Confirm your account

View File

@ -0,0 +1,34 @@
{% extends "layout.html" %}
{% block title %}Confirmer votre inscription{% endblock %}
{% block preheader %}Utiliser ce lien pour confirmer votre inscription.{% endblock %}
{% block content %}<h1>Bonjour {{username}},</h1>
<p>Vous avez créé un compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour confirmer votre adresse email.
</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{account_confirmation_url}}" class="f-fallback button button--green" target="_blank">Vérifier l'adresse email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
{% if operating_system and browser_name %}Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}.
{% endif %}Si vous n'êtes pas à l'origine de la création de ce compte, vous pouvez ignorer cet e-mail.
</p>
<p>Merci,
<br>L'équipe FitTrackee</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">Si vous avez des problèmes avec le bouton, vous pouvez copier et coller le lien suivant dans votre navigateur</p>
<p class="f-fallback sub">{{account_confirmation_url}}</p>
</td>
</tr>
</table>{% endblock %}

View File

@ -0,0 +1,13 @@
Bonjour {{username}},
Vous avez créé un compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour confirmer votre adresse email.
Vérifier l'adresse email : {{ account_confirmation_url }}
{% if operating_system and browser_name %}Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}.
{% endif %}Si vous n'êtes pas à l'origine de la création de ce compte, vous pouvez ignorer cet e-mail.
Merci,
L'équipe FitTrackee
{{fittrackee_url}}

View File

@ -0,0 +1 @@
FitTrackee - Confirmer votre inscription

View File

@ -0,0 +1,26 @@
{% extends "layout.html" %}
{% block title %}Email changed{% endblock %}
{% block preheader %}Your email is being updated.{% endblock %}
{% block content %}<h1>Hi {{username}},</h1>
<p>You recently requested to change your email address for your FitTrackee account to: </p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
{{new_email_address}}
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
For security, this request was received from a {{operating_system}} device using {{browser_name}}.
If this email change wasn't initiated by you, please change your password immediately or contact your administrator if your account is locked.
</p>
<p>Thanks,
<br>
The FitTrackee Team
</p>{% endblock %}

View File

@ -0,0 +1,10 @@
Hi {{username}},
You recently requested to change your email address for your FitTrackee account to: {{ new_email_address }}
For security, this request was received from a {{operating_system}} device using {{browser_name}}.
If this email change wasn't initiated by you, please change your password immediately or contact your administrator if your account is locked.
Thanks,
The FitTrackee Team
{{fittrackee_url}}

View File

@ -0,0 +1 @@
FitTrackee - Email changed

View File

@ -0,0 +1,26 @@
{% extends "layout.html" %}
{% block title %}Adresse email modifiée{% endblock %}
{% block preheader %}Votre adresse email est en cours de mise à jour.{% endblock %}
{% block content %}<h1>Bonjour {{username}},</h1>
<p>Vous avez récemment demandé la modification de l'adresse email associée à votre compte sur FitTrackee vers :</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
{{ new_email_address }}
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}.
Si vous n'êtes pas à l'origine de cette modification, veuillez changer votre mot de passe immédiatement ou contacter l'administrateur si votre compte est bloqué.
</p>
<p>Merci,
<br>
L'équipe FitTrackee
</p>{% endblock %}

View File

@ -0,0 +1,10 @@
Bonjour {{username}},
Vous avez récemment demandé la modification de l'adresse email associée à votre compte sur FitTrackee vers : {{ new_email_address }}
Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}.
Si vous n'êtes pas à l'origine de cette modification, veuillez changer votre mot de passe immédiatement ou contacter l'administrateur si votre compte est bloqué.
Merci,
L'équipe FitTrackee
{{fittrackee_url}}

View File

@ -0,0 +1 @@
FitTrackee - Adresse email modifiée

View File

@ -0,0 +1,32 @@
{% extends "layout.html" %}
{% block title %}Confirm email change{% endblock %}
{% block preheader %}Use this link to confirm email change.{% endblock %}
{% block content %}<h1>Hi {{username}},</h1>
<p>You recently requested to change your email address for your FitTrackee account. Use the button below to confirm this address.</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{email_confirmation_url}}" class="f-fallback button button--green" target="_blank">Verify your email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
{% if operating_system and browser_name %}For security, this request was received from a {{operating_system}} device using {{browser_name}}.
{% endif %}If this email change wasn't initiated by you, please ignore this email.
</p>
<p>Thanks,
<br>The FitTrackee Team</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">{{email_confirmation_url}}</p>
</td>
</tr>
</table>{% endblock %}

View File

@ -0,0 +1,12 @@
Hi {{username}},
You recently requested to change your email address for your FitTrackee account. Use the link below to confirm this address.
Verify your email: {{ email_confirmation_url }}
{% if operating_system and browser_name %}For security, this request was received from a {{operating_system}} device using {{browser_name}}.
{% endif %}If this email change wasn't initiated by you, please ignore this email.
Thanks,
The FitTrackee Team
{{fittrackee_url}}

View File

@ -0,0 +1 @@
FitTrackee - Confirm email change

View File

@ -0,0 +1,34 @@
{% extends "layout.html" %}
{% block title %}Confirmer le changement d'adresse email{% endblock %}
{% block preheader %}Utiliser ce lien pour confirmer cette adresse email.{% endblock %}
{% block content %}<h1>Bonjour {{username}},</h1>
<p>Vous avez récemment demandé la modification de l'adresse email associée à votre compte sur FitTrackee.
Cliquez sur le bouton ci-dessous pour confirmer cette adresse email.
</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{email_confirmation_url}}" class="f-fallback button button--green" target="_blank">Vérifier l'adresse email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
{% if operating_system and browser_name %}Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}.
{% endif %}Si vous n'êtes pas à l'origine de cette modification, vous pouvez ignorer cet e-mail.
</p>
<p>Merci,
<br>L'équipe FitTrackee</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">Si vous avez des problèmes avec le bouton, vous pouvez copier et coller le lien suivant dans votre navigateur</p>
<p class="f-fallback sub">{{email_confirmation_url}}</p>
</td>
</tr>
</table>{% endblock %}

View File

@ -0,0 +1,13 @@
Bonjour {{username}},
Vous avez récemment demandé la modification de l'adresse email associée à votre compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour confirmer cette adresse email.
Vérifier l'adresse email : {{ email_confirmation_url }}
{% if operating_system and browser_name %}Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}.
{% endif %}Si vous n'êtes pas à l'origine de cette modification, vous pouvez ignorer cet e-mail.
Merci,
L'équipe FitTrackee
{{fittrackee_url}}

View File

@ -0,0 +1 @@
FitTrackee - Confirmer le changement d'adresse email

View File

@ -0,0 +1,238 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="x-apple-disable-message-reformatting" content=""/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>FitTrackee - {% block title %}{% endblock %}</title>
<style type="text/css" rel="stylesheet" media="all">
body {
background-color: #F4F4F7;
color: #51545E;
width: 100% !important;
height: 100%;
margin: 0;
-webkit-text-size-adjust: none;
}
a {
color: #3869D4;
}
a img {
border: none;
}
td {
word-break: break-word;
}
.preheader {
display: none !important;
visibility: hidden;
mso-hide: all;
font-size: 1px;
line-height: 1px;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
}
body,
td,
th {
font-family: Helvetica, Arial, sans-serif;
}
h1 {
margin-top: 0;
color: #333333;
font-size: 22px;
font-weight: bold;
text-align: left;
}
td,
th {
font-size: 16px;
}
p {
color: #51545E;
margin: .4em 0 1.1875em;
font-size: 16px;
line-height: 1.625;
}
p.sub {
color: #6B6E76;
font-size: 13px;
}
.button {
background-color: #3869D4;
border-top: 10px solid #3869D4;
border-right: 18px solid #3869D4;
border-bottom: 10px solid #3869D4;
border-left: 18px solid #3869D4;
display: inline-block;
color: #FFF;
text-decoration: none;
border-radius: 3px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
-webkit-text-size-adjust: none;
box-sizing: border-box;
}
.button--green {
background-color: #22BC66;
border-top: 10px solid #22BC66;
border-right: 18px solid #22BC66;
border-bottom: 10px solid #22BC66;
border-left: 18px solid #22BC66;
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
text-align: center !important;
}
}
.email-wrapper {
width: 100%;
margin: 0;
padding: 0;
background-color: #F4F4F7;
}
.email-content {
width: 100%;
margin: 0;
padding: 0;
}
.email-masthead {
padding: 25px 0;
text-align: center;
}
.email-masthead-name {
font-size: 16px;
font-weight: bold;
color: #A8AAAF;
text-decoration: none;
text-shadow: 0 1px 0 white;
}
.email-body {
width: 100%;
margin: 0;
padding: 0;
background-color: #FFFFFF;
}
.email-body-inner {
width: 570px;
margin: 0 auto;
padding: 0;
background-color: #FFFFFF;
}
.body-action {
width: 100%;
margin: 30px auto;
padding: 0;
text-align: center;
}
.body-sub {
margin-top: 25px;
padding-top: 25px;
border-top: 1px solid #EAEAEC;
}
.content-cell {
padding: 35px;
}
@media only screen and (max-width: 600px) {
.email-body-inner,
.email-footer {
width: 100% !important;
}
}
@media (prefers-color-scheme: dark) {
body,
.email-body,
.email-body-inner,
.email-content,
.email-wrapper,
.email-masthead,
.email-footer {
background-color: #333333 !important;
color: #FFF !important;
}
p,
h1 {
color: #FFF !important;
}
.email-masthead-name {
text-shadow: none !important;
}
}
</style>
<!--[if mso]>
<style type="text/css">
.f-fallback {
font-family: Arial, sans-serif;
}
</style>
<![endif]-->
</head>
<body>
<span class="preheader">{% block preheader %}{% endblock %}</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="{{fittrackee_url}}" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
{% block content %}{% endblock %}
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,13 @@
{% extends "layout.html" %}
{% block title %}Password changed{% endblock %}
{% block preheader %}Your password has been changed.{% endblock %}
{% block content %}<h1>Hi {{username}},</h1>
<p>The password for your FitTrackee account has been changed.</p>
<p>
{% if operating_system and browser_name %}For security, this request was received from a {{operating_system}} device using {{browser_name}}.
{% endif %}If this password change wasn't initiated by you, please change your password immediately or contact your administrator if your account is locked.
</p>
<p>Thanks,
<br>
The FitTrackee Team
</p>{% endblock %}

View File

@ -0,0 +1,10 @@
Hi {{username}},
The password for your FitTrackee account has been changed.
{% if operating_system and browser_name %}For security, this request was received from a {{operating_system}} device using {{browser_name}}.
{% endif %}If this password change wasn't initiated by you, please change your password immediately or contact your administrator if your account is locked.
Thanks,
The FitTrackee Team
{{fittrackee_url}}

View File

@ -0,0 +1 @@
FitTrackee - Password changed

View File

@ -0,0 +1,13 @@
{% extends "layout.html" %}
{% block title %}FitTrackee - Mot de passe modifié{% endblock %}
{% block preheader %}Votre mot de passe a été modifié.{% endblock %}
{% block content %}<h1>Bonjour {{username}},</h1>
<p>Le mot de passe de votre compte FitTrackee a été modifié.</p>
<p>
{% if operating_system and browser_name %}Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}.
{% endif %}Si vous n'êtes pas à l'origine de cette modification, veuillez changer votre mot de passe immédiatement ou contacter l'administrateur si votre compte est bloqué.
</p>
<p>Merci,
<br>
L'équipe FitTrackee
</p>{% endblock %}

View File

@ -0,0 +1,10 @@
Bonjour {{username}},
Le mot de passe de votre compte FitTrackee a été modifié.
{% if operating_system and browser_name %}Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}.
{% endif %}Si vous n'êtes pas à l'origine de cette modification, veuillez changer votre mot de passe immédiatement ou contacter l'administrateur si votre compte est bloqué.
Merci,
L'équipe FitTrackee
{{fittrackee_url}}

View File

@ -0,0 +1 @@
FitTrackee - Mot de passe modifié

View File

@ -1,218 +1,7 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> {% extends "layout.html" %}
<html lang="en"> {% block title %}Password reset request{% endblock %}
<head> {% block preheader %}Use this link to reset your password. The link is only valid for {{ expiration_delay }}.{% endblock %}
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> {% block content %}<h1>Hi {{username}},</h1>
<meta name="x-apple-disable-message-reformatting" content=""/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Fittrackee - Password reset request</title>
<style type="text/css" rel="stylesheet" media="all">
body {
background-color: #F4F4F7;
color: #51545E;
width: 100% !important;
height: 100%;
margin: 0;
-webkit-text-size-adjust: none;
}
a {
color: #3869D4;
}
a img {
border: none;
}
td {
word-break: break-word;
}
.preheader {
display: none !important;
visibility: hidden;
mso-hide: all;
font-size: 1px;
line-height: 1px;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
}
body,
td,
th {
font-family: Helvetica, Arial, sans-serif;
}
h1 {
margin-top: 0;
color: #333333;
font-size: 22px;
font-weight: bold;
text-align: left;
}
td,
th {
font-size: 16px;
}
p {
color: #51545E;
margin: .4em 0 1.1875em;
font-size: 16px;
line-height: 1.625;
}
p.sub {
color: #6B6E76;
font-size: 13px;
}
.button {
background-color: #3869D4;
border-top: 10px solid #3869D4;
border-right: 18px solid #3869D4;
border-bottom: 10px solid #3869D4;
border-left: 18px solid #3869D4;
display: inline-block;
color: #FFF;
text-decoration: none;
border-radius: 3px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
-webkit-text-size-adjust: none;
box-sizing: border-box;
}
.button--green {
background-color: #22BC66;
border-top: 10px solid #22BC66;
border-right: 18px solid #22BC66;
border-bottom: 10px solid #22BC66;
border-left: 18px solid #22BC66;
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
text-align: center !important;
}
}
.email-wrapper {
width: 100%;
margin: 0;
padding: 0;
background-color: #F4F4F7;
}
.email-content {
width: 100%;
margin: 0;
padding: 0;
}
.email-masthead {
padding: 25px 0;
text-align: center;
}
.email-masthead-name {
font-size: 16px;
font-weight: bold;
color: #A8AAAF;
text-decoration: none;
text-shadow: 0 1px 0 white;
}
.email-body {
width: 100%;
margin: 0;
padding: 0;
background-color: #FFFFFF;
}
.email-body-inner {
width: 570px;
margin: 0 auto;
padding: 0;
background-color: #FFFFFF;
}
.body-action {
width: 100%;
margin: 30px auto;
padding: 0;
text-align: center;
}
.body-sub {
margin-top: 25px;
padding-top: 25px;
border-top: 1px solid #EAEAEC;
}
.content-cell {
padding: 35px;
}
@media only screen and (max-width: 600px) {
.email-body-inner,
.email-footer {
width: 100% !important;
}
}
@media (prefers-color-scheme: dark) {
body,
.email-body,
.email-body-inner,
.email-content,
.email-wrapper,
.email-masthead,
.email-footer {
background-color: #333333 !important;
color: #FFF !important;
}
p,
h1 {
color: #FFF !important;
}
.email-masthead-name {
text-shadow: none !important;
}
}
</style>
<!--[if mso]>
<style type="text/css">
.f-fallback {
font-family: Arial, sans-serif;
}
</style>
<![endif]-->
</head>
<body>
<span class="preheader">Use this link to reset your password. The link is only valid for {{ expiration_delay }}.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="https://example.com" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Hi {{username}},</h1>
<p>You recently requested to reset your password for your account. Use the button below to reset it. <p>You recently requested to reset your password for your account. Use the button below to reset it.
<strong>This password reset link is only valid for {{ expiration_delay }}.</strong> <strong>This password reset link is only valid for {{ expiration_delay }}.</strong>
</p> </p>
@ -230,8 +19,8 @@
</tr> </tr>
</table> </table>
<p> <p>
For security, this request was received from a {{operating_system}} device using {{browser_name}}. {% if operating_system and browser_name %}For security, this request was received from a {{operating_system}} device using {{browser_name}}.
If you did not request a password reset, please ignore this email. {% endif %}If you did not request a password reset, please ignore this email.
</p> </p>
<p>Thanks, <p>Thanks,
<br>The FitTrackee Team</p> <br>The FitTrackee Team</p>
@ -242,27 +31,4 @@
<p class="f-fallback sub">{{password_reset_url}}</p> <p class="f-fallback sub">{{password_reset_url}}</p>
</td> </td>
</tr> </tr>
</table> </table>{% endblock %}
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -1,10 +1,12 @@
Hi {{username}}, Hi {{username}},
You recently requested to reset your password for your FitTrackee account. Use the button below to reset it. This password reset link is only valid for {{ expiration_delay }}. You recently requested to reset your password for your FitTrackee account. Use the link below to reset it. This password reset link is only valid for {{ expiration_delay }}.
Reset your password ( {{ password_reset_url }} ) Reset your password: {{ password_reset_url }}
For security, this request was received from a {{operating_system}} device using {{browser_name}}. If you did not request a password reset, please ignore this email. {% if operating_system and browser_name %}For security, this request was received from a {{operating_system}} device using {{browser_name}}.
{% endif %}If you did not request a password reset, please ignore this email.
Thanks, Thanks,
The FitTrackee Team The FitTrackee Team
{{fittrackee_url}}

View File

@ -1,219 +1,8 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> {% extends "layout.html" %}
<html lang="en"> {% block title %}Réinitialiser le mot de passe{% endblock %}
<head> {% block preheader %}Utiliser ce lien pour réinitialiser le mot de passe. Ce lien n'est valide que pendant {{ expiration_delay }}.{% endblock %}
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> {% block content %}<h1>Bonjour {{username}},</h1>
<meta name="x-apple-disable-message-reformatting" content=""/> <p>Vous avez récemment demandé la réinitialisation du mot de passe de votre compte sur FitTrackee.
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>FitTrackee - Réinitialiser le mot de passe</title>
<style type="text/css" rel="stylesheet" media="all">
body {
background-color: #F4F4F7;
color: #51545E;
width: 100% !important;
height: 100%;
margin: 0;
-webkit-text-size-adjust: none;
}
a {
color: #3869D4;
}
a img {
border: none;
}
td {
word-break: break-word;
}
.preheader {
display: none !important;
visibility: hidden;
mso-hide: all;
font-size: 1px;
line-height: 1px;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
}
body,
td,
th {
font-family: Helvetica, Arial, sans-serif;
}
h1 {
margin-top: 0;
color: #333333;
font-size: 22px;
font-weight: bold;
text-align: left;
}
td,
th {
font-size: 16px;
}
p {
color: #51545E;
margin: .4em 0 1.1875em;
font-size: 16px;
line-height: 1.625;
}
p.sub {
color: #6B6E76;
font-size: 13px;
}
.button {
background-color: #3869D4;
border-top: 10px solid #3869D4;
border-right: 18px solid #3869D4;
border-bottom: 10px solid #3869D4;
border-left: 18px solid #3869D4;
display: inline-block;
color: #FFF;
text-decoration: none;
border-radius: 3px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
-webkit-text-size-adjust: none;
box-sizing: border-box;
}
.button--green {
background-color: #22BC66;
border-top: 10px solid #22BC66;
border-right: 18px solid #22BC66;
border-bottom: 10px solid #22BC66;
border-left: 18px solid #22BC66;
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
text-align: center !important;
}
}
.email-wrapper {
width: 100%;
margin: 0;
padding: 0;
background-color: #F4F4F7;
}
.email-content {
width: 100%;
margin: 0;
padding: 0;
}
.email-masthead {
padding: 25px 0;
text-align: center;
}
.email-masthead-name {
font-size: 16px;
font-weight: bold;
color: #A8AAAF;
text-decoration: none;
text-shadow: 0 1px 0 white;
}
.email-body {
width: 100%;
margin: 0;
padding: 0;
background-color: #FFFFFF;
}
.email-body-inner {
width: 570px;
margin: 0 auto;
padding: 0;
background-color: #FFFFFF;
}
.body-action {
width: 100%;
margin: 30px auto;
padding: 0;
text-align: center;
}
.body-sub {
margin-top: 25px;
padding-top: 25px;
border-top: 1px solid #EAEAEC;
}
.content-cell {
padding: 35px;
}
@media only screen and (max-width: 600px) {
.email-body-inner,
.email-footer {
width: 100% !important;
}
}
@media (prefers-color-scheme: dark) {
body,
.email-body,
.email-body-inner,
.email-content,
.email-wrapper,
.email-masthead,
.email-footer {
background-color: #333333 !important;
color: #FFF !important;
}
p,
h1 {
color: #FFF !important;
}
.email-masthead-name {
text-shadow: none !important;
}
}
</style>
<!--[if mso]>
<style type="text/css">
.f-fallback {
font-family: Arial, sans-serif;
}
</style>
<![endif]-->
</head>
<body>
<span class="preheader">Utiliser ce lien pour réinitialiser le mot de passe. Ce lien n'est valide que pendant {{ expiration_delay }}.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="https://example.com" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Bonjour {{username}},</h1>
<p>Vous avez récemment demander la réinitilisation du mot de passe de votre compte sur FitTrackee.
Cliquez sur le bouton ci-dessous pour le réinitialiser. Cliquez sur le bouton ci-dessous pour le réinitialiser.
<strong>Cette réinitialisation n'est valide que pendant {{ expiration_delay }}.</strong> <strong>Cette réinitialisation n'est valide que pendant {{ expiration_delay }}.</strong>
</p> </p>
@ -231,8 +20,8 @@
</tr> </tr>
</table> </table>
<p> <p>
Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}. {% if operating_system and browser_name %}Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}.
Si vous n'avez pas demandé de réinitalisation, vous pouvez ignorer cet e-mail. {% endif %}Si vous n'avez pas demandé de réinitialisation, vous pouvez ignorer cet e-mail.
</p> </p>
<p>Merci, <p>Merci,
<br>L'équipe FitTrackee</p> <br>L'équipe FitTrackee</p>
@ -243,27 +32,4 @@
<p class="f-fallback sub">{{password_reset_url}}</p> <p class="f-fallback sub">{{password_reset_url}}</p>
</td> </td>
</tr> </tr>
</table> </table>{% endblock %}
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -1,12 +1,13 @@
Bonjour {{username}}, Bonjour {{username}},
Vous avez récemment demander la réinitilisation du mot de passe de votre compte sur FitTrackee. Vous avez récemment demandé la réinitialisation du mot de passe de votre compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour le réinitialiser. Ce lien n'est valide que pendant {{ expiration_delay }}. Cliquez sur le lien ci-dessous pour le réinitialiser. Ce lien n'est valide que pendant {{ expiration_delay }}.
Réinitialiser le mot de passe: ( {{ password_reset_url }} ) Réinitialiser le mot de passe : {{ password_reset_url }}
Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}. {% if operating_system and browser_name %}Pour vérification, cette demande a été reçue à partir d'un appareil sous {{operating_system}}, utilisant le navigateur {{browser_name}}.
Si vous n'avez pas demandé de réinitalisation, vous pouvez ignorer cet e-mail. {% endif %}Si vous n'avez pas demandé de réinitialisation, vous pouvez ignorer cet e-mail.
Merci, Merci,
L'équipe FitTrackee L'équipe FitTrackee
{{fittrackee_url}}

View File

@ -0,0 +1,59 @@
"""update User and AppConfig tables
Revision ID: 5e3a3a31c432
Revises: e30007d681cb
Create Date: 2022-02-23 11:05:24.223304
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '5e3a3a31c432'
down_revision = 'e30007d681cb'
branch_labels = None
depends_on = None
def upgrade():
op.alter_column(
'users', 'username', existing_type=sa.String(length=20),
type_=sa.String(length=255), existing_nullable=False
)
op.alter_column(
'users', 'email', existing_type=sa.String(length=120),
type_=sa.String(length=255), existing_nullable=False
)
op.add_column(
'users',
sa.Column('is_active', sa.Boolean(), default=False, nullable=True))
op.execute("UPDATE users SET is_active = true")
op.alter_column('users', 'is_active', nullable=False)
op.add_column(
'users',
sa.Column('email_to_confirm', sa.String(length=255), nullable=True))
op.add_column(
'users',
sa.Column('confirmation_token', sa.String(length=255), nullable=True))
op.add_column(
'app_config',
sa.Column('admin_contact', sa.String(length=255), nullable=True)
)
def downgrade():
op.drop_column('app_config', 'admin_contact')
op.drop_column('users', 'confirmation_token')
op.drop_column('users', 'email_to_confirm')
op.drop_column('users', 'is_active')
op.alter_column(
'users', 'email', existing_type=sa.String(length=255),
type_=sa.String(length=120), existing_nullable=False
)
op.alter_column(
'users', 'username', existing_type=sa.String(length=255),
type_=sa.String(length=20), existing_nullable=False
)

View File

@ -1,14 +1,40 @@
import json import json
from typing import Optional
import pytest
from flask import Flask from flask import Flask
import fittrackee import fittrackee
from fittrackee.application.models import AppConfig
from fittrackee.users.models import User from fittrackee.users.models import User
from ..api_test_case import ApiTestCaseMixin from ..mixins import ApiTestCaseMixin
class TestGetConfig(ApiTestCaseMixin): class TestGetConfig(ApiTestCaseMixin):
def test_it_gets_application_config_for_unauthenticated_user(
self, app: Flask
) -> None:
client = app.test_client()
response = client.get('/api/config')
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'] == (
'&copy; <a href="http://www.openstreetmap.org/copyright" '
'target="_blank" rel="noopener noreferrer">OpenStreetMap</a> '
'contributors'
)
assert data['data']['version'] == fittrackee.__version__
def test_it_gets_application_config( def test_it_gets_application_config(
self, app: Flask, user_1: User self, app: Flask, user_1: User
) -> None: ) -> None:
@ -24,17 +50,6 @@ class TestGetConfig(ApiTestCaseMixin):
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert data['data']['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'] == (
'&copy; <a href="http://www.openstreetmap.org/copyright" '
'target="_blank" rel="noopener noreferrer">OpenStreetMap</a> '
'contributors'
)
assert data['data']['version'] == fittrackee.__version__
def test_it_returns_error_if_application_has_no_config( def test_it_returns_error_if_application_has_no_config(
self, app_no_config: Flask, user_1_admin: User self, app_no_config: Flask, user_1_admin: User
@ -96,12 +111,14 @@ class TestUpdateConfig(ApiTestCaseMixin):
client, auth_token = self.get_test_client_and_auth_token( client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email app, user_1_admin.email
) )
admin_email = self.random_email()
response = client.patch( response = client.patch(
'/api/config', '/api/config',
content_type='application/json', content_type='application/json',
data=json.dumps( data=json.dumps(
dict( dict(
admin_contact=admin_email,
gpx_limit_import=20, gpx_limit_import=20,
max_single_file_size=10000, max_single_file_size=10000,
max_zip_file_size=25000, max_zip_file_size=25000,
@ -111,9 +128,10 @@ class TestUpdateConfig(ApiTestCaseMixin):
headers=dict(Authorization=f'Bearer {auth_token}'), headers=dict(Authorization=f'Bearer {auth_token}'),
) )
data = json.loads(response.data.decode())
assert response.status_code == 200 assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status'] assert 'success' in data['status']
assert data['data']['admin_contact'] == admin_email
assert data['data']['gpx_limit_import'] == 20 assert data['data']['gpx_limit_import'] == 20
assert data['data']['is_registration_enabled'] is True assert data['data']['is_registration_enabled'] is True
assert data['data']['max_single_file_size'] == 10000 assert data['data']['max_single_file_size'] == 10000
@ -262,3 +280,57 @@ class TestUpdateConfig(ApiTestCaseMixin):
self.assert_400( self.assert_400(
response, 'Max. files in a zip archive must be greater than 0' response, 'Max. files in a zip archive must be greater than 0'
) )
def test_it_raises_error_if_admin_contact_is_invalid(
self, app: Flask, user_1_admin: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
response = client.patch(
'/api/config',
content_type='application/json',
data=json.dumps(
dict(
admin_contact=self.random_string(),
)
),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_400(
response, 'valid email must be provided for admin contact'
)
@pytest.mark.parametrize(
'input_description,input_email', [('input string', ''), ('None', None)]
)
def test_it_empties_error_if_admin_contact_is_an_empty(
self,
app: Flask,
user_1_admin: User,
input_description: str,
input_email: Optional[str],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)
app_config = AppConfig.query.first()
app_config.admin_contact = self.random_email()
response = client.patch(
'/api/config',
content_type='application/json',
data=json.dumps(
dict(
admin_contact=input_email,
)
),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert data['data']['admin_contact'] is None

View File

@ -19,3 +19,4 @@ class TestConfigModel:
'target="_blank" rel="noopener noreferrer">OpenStreetMap</a> ' 'target="_blank" rel="noopener noreferrer">OpenStreetMap</a> '
'contributors' 'contributors'
) )
assert 'admin_contact' in serialized_app_config

View File

@ -10,6 +10,7 @@ os.environ['DATABASE_URL'] = os.environ['DATABASE_TEST_URL']
pytest_plugins = [ pytest_plugins = [
'fittrackee.tests.fixtures.fixtures_app', 'fittrackee.tests.fixtures.fixtures_app',
'fittrackee.tests.fixtures.fixtures_emails',
'fittrackee.tests.fixtures.fixtures_workouts', 'fittrackee.tests.fixtures.fixtures_workouts',
'fittrackee.tests.fixtures.fixtures_users', 'fittrackee.tests.fixtures.fixtures_users',
] ]

View File

@ -0,0 +1,174 @@
# flake8: noqa
expected_en_text_body = """Hi test,
You have created an account on FitTrackee account. Use the link below to confirm your address email.
Verify your email: http://localhost/account-confirmation?token=xxx
For security, this request was received from a Linux device using Firefox.
If this account creation wasn't initiated by you, please ignore this email.
Thanks,
The FitTrackee Team
http://localhost"""
expected_fr_text_body = """Bonjour test,
Vous avez créé un compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour confirmer votre adresse email.
Vérifier l'adresse email : http://localhost/account-confirmation?token=xxx
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
Si vous n'êtes pas à l'origine de la création de ce compte, vous pouvez ignorer cet e-mail.
Merci,
L'équipe FitTrackee
http://localhost"""
expected_en_html_body = """ <body>
<span class="preheader">Use this link to confirm your account.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Hi test,</h1>
<p>You have created an account on FitTrackee account. Use the link below to confirm your address email.</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="http://localhost/account-confirmation?token=xxx" class="f-fallback button button--green" target="_blank">Verify your email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
For security, this request was received from a Linux device using Firefox.
If this account creation wasn't initiated by you, please ignore this email.
</p>
<p>Thanks,
<br>The FitTrackee Team</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">http://localhost/account-confirmation?token=xxx</p>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""
expected_fr_html_body = """ <body>
<span class="preheader">Utiliser ce lien pour confirmer votre inscription.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Bonjour test,</h1>
<p>Vous avez créé un compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour confirmer votre adresse email.
</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="http://localhost/account-confirmation?token=xxx" class="f-fallback button button--green" target="_blank">Vérifier l'adresse email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
Si vous n'êtes pas à l'origine de la création de ce compte, vous pouvez ignorer cet e-mail.
</p>
<p>Merci,
<br>L'équipe FitTrackee</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">Si vous avez des problèmes avec le bouton, vous pouvez copier et coller le lien suivant dans votre navigateur</p>
<p class="f-fallback sub">http://localhost/account-confirmation?token=xxx</p>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""

View File

@ -0,0 +1,155 @@
# flake8: noqa
expected_en_text_body = """Hi test,
You recently requested to change your email address for your FitTrackee account to: new.email@example.com
For security, this request was received from a Linux device using Firefox.
If this email change wasn't initiated by you, please change your password immediately or contact your administrator if your account is locked.
Thanks,
The FitTrackee Team
http://localhost"""
expected_fr_text_body = """Bonjour test,
Vous avez récemment demandé la modification de l'adresse email associée à votre compte sur FitTrackee vers : new.email@example.com
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
Si vous n'êtes pas à l'origine de cette modification, veuillez changer votre mot de passe immédiatement ou contacter l'administrateur si votre compte est bloqué.
Merci,
L'équipe FitTrackee
http://localhost"""
expected_en_html_body = """ <body>
<span class="preheader">Your email is being updated.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Hi test,</h1>
<p>You recently requested to change your email address for your FitTrackee account to: </p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
new.email@example.com
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
For security, this request was received from a Linux device using Firefox.
If this email change wasn't initiated by you, please change your password immediately or contact your administrator if your account is locked.
</p>
<p>Thanks,
<br>
The FitTrackee Team
</p>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""
expected_fr_html_body = """ <body>
<span class="preheader">Votre adresse email est en cours de mise à jour.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Bonjour test,</h1>
<p>Vous avez récemment demandé la modification de l'adresse email associée à votre compte sur FitTrackee vers :</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
new.email@example.com
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
Si vous n'êtes pas à l'origine de cette modification, veuillez changer votre mot de passe immédiatement ou contacter l'administrateur si votre compte est bloqué.
</p>
<p>Merci,
<br>
L'équipe FitTrackee
</p>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""

View File

@ -0,0 +1,343 @@
# flake8: noqa
expected_en_text_body = """Hi test,
You recently requested to change your email address for your FitTrackee account. Use the link below to confirm this address.
Verify your email: http://localhost/email-update?token=xxx
For security, this request was received from a Linux device using Firefox.
If this email change wasn't initiated by you, please ignore this email.
Thanks,
The FitTrackee Team
http://localhost"""
expected_en_text_body_without_security = """Hi test,
You recently requested to change your email address for your FitTrackee account. Use the link below to confirm this address.
Verify your email: http://localhost/email-update?token=xxx
If this email change wasn't initiated by you, please ignore this email.
Thanks,
The FitTrackee Team
http://localhost"""
expected_fr_text_body = """Bonjour test,
Vous avez récemment demandé la modification de l'adresse email associée à votre compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour confirmer cette adresse email.
Vérifier l'adresse email : http://localhost/email-update?token=xxx
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
Si vous n'êtes pas à l'origine de cette modification, vous pouvez ignorer cet e-mail.
Merci,
L'équipe FitTrackee
http://localhost"""
expected_fr_text_body_without_security = """Bonjour test,
Vous avez récemment demandé la modification de l'adresse email associée à votre compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour confirmer cette adresse email.
Vérifier l'adresse email : http://localhost/email-update?token=xxx
Si vous n'êtes pas à l'origine de cette modification, vous pouvez ignorer cet e-mail.
Merci,
L'équipe FitTrackee
http://localhost"""
expected_en_html_body = """ <body>
<span class="preheader">Use this link to confirm email change.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Hi test,</h1>
<p>You recently requested to change your email address for your FitTrackee account. Use the button below to confirm this address.</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="http://localhost/email-update?token=xxx" class="f-fallback button button--green" target="_blank">Verify your email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
For security, this request was received from a Linux device using Firefox.
If this email change wasn't initiated by you, please ignore this email.
</p>
<p>Thanks,
<br>The FitTrackee Team</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">http://localhost/email-update?token=xxx</p>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""
expected_en_html_body_without_security = """ <body>
<span class="preheader">Use this link to confirm email change.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Hi test,</h1>
<p>You recently requested to change your email address for your FitTrackee account. Use the button below to confirm this address.</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="http://localhost/email-update?token=xxx" class="f-fallback button button--green" target="_blank">Verify your email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
If this email change wasn't initiated by you, please ignore this email.
</p>
<p>Thanks,
<br>The FitTrackee Team</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">http://localhost/email-update?token=xxx</p>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""
expected_fr_html_body = """ <body>
<span class="preheader">Utiliser ce lien pour confirmer cette adresse email.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Bonjour test,</h1>
<p>Vous avez récemment demandé la modification de l'adresse email associée à votre compte sur FitTrackee.
Cliquez sur le bouton ci-dessous pour confirmer cette adresse email.
</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="http://localhost/email-update?token=xxx" class="f-fallback button button--green" target="_blank">Vérifier l'adresse email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
Si vous n'êtes pas à l'origine de cette modification, vous pouvez ignorer cet e-mail.
</p>
<p>Merci,
<br>L'équipe FitTrackee</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">Si vous avez des problèmes avec le bouton, vous pouvez copier et coller le lien suivant dans votre navigateur</p>
<p class="f-fallback sub">http://localhost/email-update?token=xxx</p>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""
expected_fr_html_body_without_security = """ <body>
<span class="preheader">Utiliser ce lien pour confirmer cette adresse email.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Bonjour test,</h1>
<p>Vous avez récemment demandé la modification de l'adresse email associée à votre compte sur FitTrackee.
Cliquez sur le bouton ci-dessous pour confirmer cette adresse email.
</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="http://localhost/email-update?token=xxx" class="f-fallback button button--green" target="_blank">Vérifier l'adresse email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
Si vous n'êtes pas à l'origine de cette modification, vous pouvez ignorer cet e-mail.
</p>
<p>Merci,
<br>L'équipe FitTrackee</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">Si vous avez des problèmes avec le bouton, vous pouvez copier et coller le lien suivant dans votre navigateur</p>
<p class="f-fallback sub">http://localhost/email-update?token=xxx</p>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""

View File

@ -0,0 +1,253 @@
# flake8: noqa
expected_en_text_body = """Hi test,
The password for your FitTrackee account has been changed.
For security, this request was received from a Linux device using Firefox.
If this password change wasn't initiated by you, please change your password immediately or contact your administrator if your account is locked.
Thanks,
The FitTrackee Team
http://localhost"""
expected_en_text_body_without_security = """Hi test,
The password for your FitTrackee account has been changed.
If this password change wasn't initiated by you, please change your password immediately or contact your administrator if your account is locked.
Thanks,
The FitTrackee Team
http://localhost"""
expected_fr_text_body = """Bonjour test,
Le mot de passe de votre compte FitTrackee a été modifié.
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
Si vous n'êtes pas à l'origine de cette modification, veuillez changer votre mot de passe immédiatement ou contacter l'administrateur si votre compte est bloqué.
Merci,
L'équipe FitTrackee
http://localhost"""
expected_fr_text_body_without_security = """Bonjour test,
Le mot de passe de votre compte FitTrackee a été modifié.
Si vous n'êtes pas à l'origine de cette modification, veuillez changer votre mot de passe immédiatement ou contacter l'administrateur si votre compte est bloqué.
Merci,
L'équipe FitTrackee
http://localhost"""
expected_en_html_body = """ <body>
<span class="preheader">Your password has been changed.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Hi test,</h1>
<p>The password for your FitTrackee account has been changed.</p>
<p>
For security, this request was received from a Linux device using Firefox.
If this password change wasn't initiated by you, please change your password immediately or contact your administrator if your account is locked.
</p>
<p>Thanks,
<br>
The FitTrackee Team
</p>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""
expected_en_html_body_without_security = """ <body>
<span class="preheader">Your password has been changed.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Hi test,</h1>
<p>The password for your FitTrackee account has been changed.</p>
<p>
If this password change wasn't initiated by you, please change your password immediately or contact your administrator if your account is locked.
</p>
<p>Thanks,
<br>
The FitTrackee Team
</p>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""
expected_fr_html_body = """ <body>
<span class="preheader">Votre mot de passe a été modifié.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Bonjour test,</h1>
<p>Le mot de passe de votre compte FitTrackee a été modifié.</p>
<p>
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
Si vous n'êtes pas à l'origine de cette modification, veuillez changer votre mot de passe immédiatement ou contacter l'administrateur si votre compte est bloqué.
</p>
<p>Merci,
<br>
L'équipe FitTrackee
</p>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""
expected_fr_html_body_without_security = """ <body>
<span class="preheader">Votre mot de passe a été modifié.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Bonjour test,</h1>
<p>Le mot de passe de votre compte FitTrackee a été modifié.</p>
<p>
Si vous n'êtes pas à l'origine de cette modification, veuillez changer votre mot de passe immédiatement ou contacter l'administrateur si votre compte est bloqué.
</p>
<p>Merci,
<br>
L'équipe FitTrackee
</p>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""

View File

@ -2,27 +2,55 @@
expected_en_text_body = """Hi test, expected_en_text_body = """Hi test,
You recently requested to reset your password for your FitTrackee account. Use the button below to reset it. This password reset link is only valid for 3 seconds. You recently requested to reset your password for your FitTrackee account. Use the link below to reset it. This password reset link is only valid for 3 seconds.
Reset your password ( http://localhost/password-reset?token=xxx ) Reset your password: http://localhost/password-reset?token=xxx
For security, this request was received from a Linux device using Firefox. If you did not request a password reset, please ignore this email. For security, this request was received from a Linux device using Firefox.
If you did not request a password reset, please ignore this email.
Thanks, Thanks,
The FitTrackee Team""" The FitTrackee Team
http://localhost"""
expected_en_text_body_without_security = """Hi test,
You recently requested to reset your password for your FitTrackee account. Use the link below to reset it. This password reset link is only valid for 3 seconds.
Reset your password: http://localhost/password-reset?token=xxx
If you did not request a password reset, please ignore this email.
Thanks,
The FitTrackee Team
http://localhost"""
expected_fr_text_body = """Bonjour test, expected_fr_text_body = """Bonjour test,
Vous avez récemment demander la réinitilisation du mot de passe de votre compte sur FitTrackee. Vous avez récemment demandé la réinitialisation du mot de passe de votre compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour le réinitialiser. Ce lien n'est valide que pendant 3 secondes. Cliquez sur le lien ci-dessous pour le réinitialiser. Ce lien n'est valide que pendant 3 secondes.
Réinitialiser le mot de passe: ( http://localhost/password-reset?token=xxx ) Réinitialiser le mot de passe : http://localhost/password-reset?token=xxx
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox. Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
Si vous n'avez pas demandé de réinitalisation, vous pouvez ignorer cet e-mail. Si vous n'avez pas demandé de réinitialisation, vous pouvez ignorer cet e-mail.
Merci, Merci,
L'équipe FitTrackee""" L'équipe FitTrackee
http://localhost"""
expected_fr_text_body_without_security = """Bonjour test,
Vous avez récemment demandé la réinitialisation du mot de passe de votre compte sur FitTrackee.
Cliquez sur le lien ci-dessous pour le réinitialiser. Ce lien n'est valide que pendant 3 secondes.
Réinitialiser le mot de passe : http://localhost/password-reset?token=xxx
Si vous n'avez pas demandé de réinitialisation, vous pouvez ignorer cet e-mail.
Merci,
L'équipe FitTrackee
http://localhost"""
expected_en_html_body = """ <body> expected_en_html_body = """ <body>
<span class="preheader">Use this link to reset your password. The link is only valid for 3 seconds.</span> <span class="preheader">Use this link to reset your password. The link is only valid for 3 seconds.</span>
@ -32,7 +60,7 @@ expected_en_html_body = """ <body>
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation"> <table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr> <tr>
<td class="email-masthead"> <td class="email-masthead">
<a href="https://example.com" class="f-fallback email-masthead-name"> <a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee FitTrackee
</a> </a>
</td> </td>
@ -98,6 +126,79 @@ expected_en_html_body = """ <body>
</body> </body>
</html>""" </html>"""
expected_en_html_body_without_security = """ <body>
<span class="preheader">Use this link to reset your password. The link is only valid for 3 seconds.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Hi test,</h1>
<p>You recently requested to reset your password for your account. Use the button below to reset it.
<strong>This password reset link is only valid for 3 seconds.</strong>
</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="http://localhost/password-reset?token=xxx" class="f-fallback button button--green" target="_blank">Reset your password</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
If you did not request a password reset, please ignore this email.
</p>
<p>Thanks,
<br>The FitTrackee Team</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">http://localhost/password-reset?token=xxx</p>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""
expected_fr_html_body = """ <body> expected_fr_html_body = """ <body>
<span class="preheader">Utiliser ce lien pour réinitialiser le mot de passe. Ce lien n'est valide que pendant 3 secondes.</span> <span class="preheader">Utiliser ce lien pour réinitialiser le mot de passe. Ce lien n'est valide que pendant 3 secondes.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation"> <table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
@ -106,7 +207,7 @@ expected_fr_html_body = """ <body>
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation"> <table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr> <tr>
<td class="email-masthead"> <td class="email-masthead">
<a href="https://example.com" class="f-fallback email-masthead-name"> <a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee FitTrackee
</a> </a>
</td> </td>
@ -118,7 +219,7 @@ expected_fr_html_body = """ <body>
<td class="content-cell"> <td class="content-cell">
<div class="f-fallback"> <div class="f-fallback">
<h1>Bonjour test,</h1> <h1>Bonjour test,</h1>
<p>Vous avez récemment demander la réinitilisation du mot de passe de votre compte sur FitTrackee. <p>Vous avez récemment demandé la réinitialisation du mot de passe de votre compte sur FitTrackee.
Cliquez sur le bouton ci-dessous pour le réinitialiser. Cliquez sur le bouton ci-dessous pour le réinitialiser.
<strong>Cette réinitialisation n'est valide que pendant 3 secondes.</strong> <strong>Cette réinitialisation n'est valide que pendant 3 secondes.</strong>
</p> </p>
@ -137,7 +238,82 @@ expected_fr_html_body = """ <body>
</table> </table>
<p> <p>
Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox. Pour vérification, cette demande a été reçue à partir d'un appareil sous Linux, utilisant le navigateur Firefox.
Si vous n'avez pas demandé de réinitalisation, vous pouvez ignorer cet e-mail. Si vous n'avez pas demandé de réinitialisation, vous pouvez ignorer cet e-mail.
</p>
<p>Merci,
<br>L'équipe FitTrackee</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">Si vous avez des problèmes avec le bouton, vous pouvez copier et coller le lien suivant dans votre navigateur</p>
<p class="f-fallback sub">http://localhost/password-reset?token=xxx</p>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">&copy; FitTrackee.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""
expected_fr_html_body_without_security = """ <body>
<span class="preheader">Utiliser ce lien pour réinitialiser le mot de passe. Ce lien n'est valide que pendant 3 secondes.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="http://localhost" class="f-fallback email-masthead-name">
FitTrackee
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<table class="email-body-inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Bonjour test,</h1>
<p>Vous avez récemment demandé la réinitialisation du mot de passe de votre compte sur FitTrackee.
Cliquez sur le bouton ci-dessous pour le réinitialiser.
<strong>Cette réinitialisation n'est valide que pendant 3 secondes.</strong>
</p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="http://localhost/password-reset?token=xxx" class="f-fallback button button--green" target="_blank">Réinitialiser le mot de passe</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>
Si vous n'avez pas demandé de réinitialisation, vous pouvez ignorer cet e-mail.
</p> </p>
<p>Merci, <p>Merci,
<br>L'équipe FitTrackee</p> <br>L'équipe FitTrackee</p>

View File

@ -0,0 +1,77 @@
import pytest
from flask import Flask
from fittrackee.emails.email import EmailTemplate
from .template_results.email_account_confirmation import (
expected_en_html_body,
expected_en_text_body,
expected_fr_html_body,
expected_fr_text_body,
)
class TestEmailTemplateForAccountConfirmation:
EMAIL_DATA = {
'username': 'test',
'account_confirmation_url': (
'http://localhost/account-confirmation?token=xxx'
),
'operating_system': 'Linux',
'browser_name': 'Firefox',
'fittrackee_url': 'http://localhost',
}
@pytest.mark.parametrize(
'lang, expected_subject',
[
('en', 'FitTrackee - Confirm your account'),
('fr', 'FitTrackee - Confirmer votre inscription'),
],
)
def test_it_gets_subject(
self, app: Flask, lang: str, expected_subject: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
subject = email_template.get_content(
'account_confirmation', lang, 'subject.txt', {}
)
assert subject == expected_subject
@pytest.mark.parametrize(
'lang, expected_text_body',
[
('en', expected_en_text_body),
('fr', expected_fr_text_body),
],
)
def test_it_gets_text_body(
self, app: Flask, lang: str, expected_text_body: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'account_confirmation', lang, 'body.txt', self.EMAIL_DATA
)
assert text_body == expected_text_body
def test_it_gets_en_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'account_confirmation', 'en', 'body.html', self.EMAIL_DATA
)
assert expected_en_html_body in text_body
def test_it_gets_fr_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'account_confirmation', 'fr', 'body.html', self.EMAIL_DATA
)
assert expected_fr_html_body in text_body

View File

@ -7,7 +7,7 @@ from fittrackee import email_service
from fittrackee.emails.email import EmailMessage from fittrackee.emails.email import EmailMessage
from fittrackee.emails.exceptions import InvalidEmailUrlScheme from fittrackee.emails.exceptions import InvalidEmailUrlScheme
from ..api_test_case import CallArgsMixin from ..mixins import CallArgsMixin
from .template_results.password_reset_request import expected_en_text_body from .template_results.password_reset_request import expected_en_text_body
@ -97,6 +97,7 @@ class TestEmailServiceSend(CallArgsMixin):
'password_reset_url': 'http://localhost/password-reset?token=xxx', 'password_reset_url': 'http://localhost/password-reset?token=xxx',
'operating_system': 'Linux', 'operating_system': 'Linux',
'browser_name': 'Firefox', 'browser_name': 'Firefox',
'fittrackee_url': 'http://localhost',
} }
def assert_smtp(self, smtp: Mock) -> None: def assert_smtp(self, smtp: Mock) -> None:

View File

@ -0,0 +1,218 @@
import pytest
from flask import Flask
from fittrackee.emails.email import EmailTemplate
from .template_results.email_update_to_current_email import (
expected_en_html_body as expected_en_current_email_html_body,
expected_en_text_body as expected_en_current_email_text_body,
expected_fr_html_body as expected_fr_current_email_html_body,
expected_fr_text_body as expected_fr_current_email_text_body,
)
# fmt: off
from .template_results.email_update_to_new_email import ( # isort:skip
expected_en_html_body as expected_en_new_email_html_body,
expected_en_html_body_without_security as
expected_en_new_email_html_body_without_security,
expected_en_text_body as expected_en_new_email_text_body,
expected_en_text_body_without_security as
expected_en_new_email_text_body_without_security,
expected_fr_html_body as expected_fr_new_email_html_body,
expected_fr_html_body_without_security as
expected_fr_new_email_html_body_without_security,
expected_fr_text_body as expected_fr_new_email_text_body,
expected_fr_text_body_without_security as
expected_fr_new_email_text_body_without_security,
)
# fmt: off
class TestEmailTemplateForEmailUpdateToCurrentEmail:
EMAIL_DATA = {
'username': 'test',
'new_email_address': 'new.email@example.com',
'operating_system': 'Linux',
'browser_name': 'Firefox',
'fittrackee_url': 'http://localhost',
}
@pytest.mark.parametrize(
'lang, expected_subject',
[
('en', 'FitTrackee - Email changed'),
('fr', 'FitTrackee - Adresse email modifiée'),
],
)
def test_it_gets_subject(
self, app: Flask, lang: str, expected_subject: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
subject = email_template.get_content(
'email_update_to_current_email', lang, 'subject.txt', {}
)
assert subject == expected_subject
@pytest.mark.parametrize(
'lang, expected_text_body',
[
('en', expected_en_current_email_text_body),
('fr', expected_fr_current_email_text_body),
],
)
def test_it_gets_text_body(
self, app: Flask, lang: str, expected_text_body: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'email_update_to_current_email', lang, 'body.txt', self.EMAIL_DATA
)
assert text_body == expected_text_body
def test_it_gets_en_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'email_update_to_current_email', 'en', 'body.html', self.EMAIL_DATA
)
assert expected_en_current_email_html_body in text_body
def test_it_gets_fr_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'email_update_to_current_email', 'fr', 'body.html', self.EMAIL_DATA
)
assert expected_fr_current_email_html_body in text_body
class TestEmailTemplateForEmailUpdateToNewEmail:
EMAIL_DATA = {
'username': 'test',
'email_confirmation_url': 'http://localhost/email-update?token=xxx',
'operating_system': 'Linux',
'browser_name': 'Firefox',
'fittrackee_url': 'http://localhost',
}
@pytest.mark.parametrize(
'lang, expected_subject',
[
('en', 'FitTrackee - Confirm email change'),
('fr', "FitTrackee - Confirmer le changement d'adresse email"),
],
)
def test_it_gets_subject(
self, app: Flask, lang: str, expected_subject: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
subject = email_template.get_content(
'email_update_to_new_email', lang, 'subject.txt', {}
)
assert subject == expected_subject
@pytest.mark.parametrize(
'lang, expected_text_body',
[
('en', expected_en_new_email_text_body),
('fr', expected_fr_new_email_text_body),
],
)
def test_it_gets_text_body(
self, app: Flask, lang: str, expected_text_body: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'email_update_to_new_email', lang, 'body.txt', self.EMAIL_DATA
)
assert text_body == expected_text_body
def test_it_gets_en_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'email_update_to_new_email', 'en', 'body.html', self.EMAIL_DATA
)
assert expected_en_new_email_html_body in text_body
def test_it_gets_fr_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'email_update_to_new_email', 'fr', 'body.html', self.EMAIL_DATA
)
assert expected_fr_new_email_html_body in text_body
class TestEmailTemplateForEmailUpdateToNewEmailWithoutSecurityInfos:
EMAIL_DATA = {
'username': 'test',
'email_confirmation_url': 'http://localhost/email-update?token=xxx',
'fittrackee_url': 'http://localhost',
}
@pytest.mark.parametrize(
'lang, expected_subject',
[
('en', 'FitTrackee - Confirm email change'),
('fr', "FitTrackee - Confirmer le changement d'adresse email"),
],
)
def test_it_gets_subject(
self, app: Flask, lang: str, expected_subject: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
subject = email_template.get_content(
'email_update_to_new_email', lang, 'subject.txt', {}
)
assert subject == expected_subject
@pytest.mark.parametrize(
'lang, expected_text_body',
[
('en', expected_en_new_email_text_body_without_security),
('fr', expected_fr_new_email_text_body_without_security),
],
)
def test_it_gets_text_body(
self, app: Flask, lang: str, expected_text_body: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'email_update_to_new_email', lang, 'body.txt', self.EMAIL_DATA
)
assert text_body == expected_text_body
def test_it_gets_en_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'email_update_to_new_email', 'en', 'body.html', self.EMAIL_DATA
)
assert expected_en_new_email_html_body_without_security in text_body
def test_it_gets_fr_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'email_update_to_new_email', 'fr', 'body.html', self.EMAIL_DATA
)
assert expected_fr_new_email_html_body_without_security in text_body

View File

@ -0,0 +1,139 @@
import pytest
from flask import Flask
from fittrackee.emails.email import EmailTemplate
from .template_results.password_change import (
expected_en_html_body,
expected_en_html_body_without_security,
expected_en_text_body,
expected_en_text_body_without_security,
expected_fr_html_body,
expected_fr_html_body_without_security,
expected_fr_text_body,
expected_fr_text_body_without_security,
)
class TestEmailTemplateForPasswordChange:
EMAIL_DATA = {
'username': 'test',
'operating_system': 'Linux',
'browser_name': 'Firefox',
'fittrackee_url': 'http://localhost',
}
@pytest.mark.parametrize(
'lang, expected_subject',
[
('en', 'FitTrackee - Password changed'),
('fr', 'FitTrackee - Mot de passe modifié'),
],
)
def test_it_gets_subject(
self, app: Flask, lang: str, expected_subject: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
subject = email_template.get_content(
'password_change', lang, 'subject.txt', {}
)
assert subject == expected_subject
@pytest.mark.parametrize(
'lang, expected_text_body',
[
('en', expected_en_text_body),
('fr', expected_fr_text_body),
],
)
def test_it_gets_text_body(
self, app: Flask, lang: str, expected_text_body: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'password_change', lang, 'body.txt', self.EMAIL_DATA
)
assert text_body == expected_text_body
def test_it_gets_en_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'password_change', 'en', 'body.html', self.EMAIL_DATA
)
assert expected_en_html_body in text_body
def test_it_gets_fr_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'password_change', 'fr', 'body.html', self.EMAIL_DATA
)
assert expected_fr_html_body in text_body
class TestEmailTemplateForPasswordChangeWithSecurityInfos:
EMAIL_DATA = {
'username': 'test',
'fittrackee_url': 'http://localhost',
}
@pytest.mark.parametrize(
'lang, expected_subject',
[
('en', 'FitTrackee - Password changed'),
('fr', 'FitTrackee - Mot de passe modifié'),
],
)
def test_it_gets_subject(
self, app: Flask, lang: str, expected_subject: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
subject = email_template.get_content(
'password_change', lang, 'subject.txt', {}
)
assert subject == expected_subject
@pytest.mark.parametrize(
'lang, expected_text_body',
[
('en', expected_en_text_body_without_security),
('fr', expected_fr_text_body_without_security),
],
)
def test_it_gets_text_body(
self, app: Flask, lang: str, expected_text_body: str
) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'password_change', lang, 'body.txt', self.EMAIL_DATA
)
assert text_body == expected_text_body
def test_it_gets_en_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'password_change', 'en', 'body.html', self.EMAIL_DATA
)
assert expected_en_html_body_without_security in text_body
def test_it_gets_fr_html_body(self, app: Flask) -> None:
email_template = EmailTemplate(app.config['TEMPLATES_FOLDER'])
text_body = email_template.get_content(
'password_change', 'fr', 'body.html', self.EMAIL_DATA
)
assert expected_fr_html_body_without_security in text_body

Some files were not shown because too many files have changed in this diff Show More