API & Client - sport administration & refactor - #15
This commit is contained in:
parent
a10128f13e
commit
1f8de2eccc
@ -4,4 +4,5 @@ Sports
|
|||||||
.. autoflask:: fittrackee_api:create_app()
|
.. autoflask:: fittrackee_api:create_app()
|
||||||
:endpoints:
|
:endpoints:
|
||||||
sports.get_sports,
|
sports.get_sports,
|
||||||
sports.get_sport
|
sports.get_sport,
|
||||||
|
sports.update_sport
|
||||||
|
@ -142,39 +142,45 @@
|
|||||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||||
<span class="nt">"sports"</span><span class="p">:</span> <span class="p">[</span>
|
<span class="nt">"sports"</span><span class="p">:</span> <span class="p">[</span>
|
||||||
<span class="p">{</span>
|
<span class="p">{</span>
|
||||||
<span class="nt">"_can_be_deleted"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
<span class="nt">"_can_be_disabled"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/cycling-sport.png"</span><span class="p">,</span>
|
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/cycling-sport.png"</span><span class="p">,</span>
|
||||||
|
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Cycling (Sport)"</span>
|
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Cycling (Sport)"</span>
|
||||||
<span class="p">},</span>
|
<span class="p">},</span>
|
||||||
<span class="p">{</span>
|
<span class="p">{</span>
|
||||||
<span class="nt">"_can_be_deleted"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
<span class="nt">"_can_be_disabled"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
||||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/cycling-transport.png"</span><span class="p">,</span>
|
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/cycling-transport.png"</span><span class="p">,</span>
|
||||||
|
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Cycling (Transport)"</span>
|
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Cycling (Transport)"</span>
|
||||||
<span class="p">},</span>
|
<span class="p">},</span>
|
||||||
<span class="p">{</span>
|
<span class="p">{</span>
|
||||||
<span class="nt">"_can_be_deleted"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
<span class="nt">"_can_be_disabled"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/hiking.png"</span><span class="p">,</span>
|
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/hiking.png"</span><span class="p">,</span>
|
||||||
|
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Hiking"</span>
|
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Hiking"</span>
|
||||||
<span class="p">},</span>
|
<span class="p">},</span>
|
||||||
<span class="p">{</span>
|
<span class="p">{</span>
|
||||||
<span class="nt">"_can_be_deleted"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
<span class="nt">"_can_be_disabled"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
|
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
|
||||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/mountain-biking.png"</span><span class="p">,</span>
|
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/mountain-biking.png"</span><span class="p">,</span>
|
||||||
|
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Mountain Biking"</span>
|
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Mountain Biking"</span>
|
||||||
<span class="p">},</span>
|
<span class="p">},</span>
|
||||||
<span class="p">{</span>
|
<span class="p">{</span>
|
||||||
<span class="nt">"_can_be_deleted"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
<span class="nt">"_can_be_disabled"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
|
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
|
||||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/running.png"</span><span class="p">,</span>
|
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/running.png"</span><span class="p">,</span>
|
||||||
|
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Running"</span>
|
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Running"</span>
|
||||||
<span class="p">},</span>
|
<span class="p">},</span>
|
||||||
<span class="p">{</span>
|
<span class="p">{</span>
|
||||||
<span class="nt">"_can_be_deleted"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
<span class="nt">"_can_be_disabled"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/walking.png"</span><span class="p">,</span>
|
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/walking.png"</span><span class="p">,</span>
|
||||||
|
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Walking"</span>
|
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Walking"</span>
|
||||||
<span class="p">}</span>
|
<span class="p">}</span>
|
||||||
<span class="p">]</span>
|
<span class="p">]</span>
|
||||||
@ -228,9 +234,10 @@
|
|||||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||||
<span class="nt">"sports"</span><span class="p">:</span> <span class="p">[</span>
|
<span class="nt">"sports"</span><span class="p">:</span> <span class="p">[</span>
|
||||||
<span class="p">{</span>
|
<span class="p">{</span>
|
||||||
<span class="nt">"_can_be_deleted"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
<span class="nt">"_can_be_disabled"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/cycling-sport.png"</span><span class="p">,</span>
|
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/cycling-sport.png"</span><span class="p">,</span>
|
||||||
|
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Cycling (Sport)"</span>
|
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Cycling (Sport)"</span>
|
||||||
<span class="p">}</span>
|
<span class="p">}</span>
|
||||||
<span class="p">]</span>
|
<span class="p">]</span>
|
||||||
@ -280,6 +287,86 @@
|
|||||||
</dl>
|
</dl>
|
||||||
</dd></dl>
|
</dd></dl>
|
||||||
|
|
||||||
|
<dl class="patch">
|
||||||
|
<dt id="patch--api-sports-(int-sport_id)">
|
||||||
|
<code class="sig-name descname">PATCH </code><code class="sig-name descname">/api/sports/</code><span class="sig-paren">(</span><em class="property">int: </em><em class="sig-param">sport_id</em><span class="sig-paren">)</span><a class="headerlink" href="#patch--api-sports-(int-sport_id)" title="Permalink to this definition">¶</a></dt>
|
||||||
|
<dd><p>Update a sport</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/sports/1</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||||
|
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||||
|
</pre></div>
|
||||||
|
</div>
|
||||||
|
<p><strong>Example response</strong>:</p>
|
||||||
|
<ul class="simple">
|
||||||
|
<li><p>success</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="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||||
|
<span class="nt">"sports"</span><span class="p">:</span> <span class="p">[</span>
|
||||||
|
<span class="p">{</span>
|
||||||
|
<span class="nt">"_can_be_disabled"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||||
|
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||||
|
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/cycling-sport.png"</span><span class="p">,</span>
|
||||||
|
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||||
|
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Cycling (Sport)"</span>
|
||||||
|
<span class="p">}</span>
|
||||||
|
<span class="p">]</span>
|
||||||
|
<span class="p">},</span>
|
||||||
|
<span class="nt">"status"</span><span class="p">:</span> <span class="s2">"success"</span>
|
||||||
|
<span class="p">}</span>
|
||||||
|
</pre></div>
|
||||||
|
</div>
|
||||||
|
<ul class="simple">
|
||||||
|
<li><p>sport not found</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">404</span> <span class="ne">NOT FOUND</span>
|
||||||
|
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||||
|
|
||||||
|
<span class="p">{</span>
|
||||||
|
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||||
|
<span class="nt">"sports"</span><span class="p">:</span> <span class="p">[]</span>
|
||||||
|
<span class="p">},</span>
|
||||||
|
<span class="nt">"status"</span><span class="p">:</span> <span class="s2">"not found"</span>
|
||||||
|
<span class="p">}</span>
|
||||||
|
</pre></div>
|
||||||
|
</div>
|
||||||
|
<dl class="field-list simple">
|
||||||
|
<dt class="field-odd">Parameters</dt>
|
||||||
|
<dd class="field-odd"><ul class="simple">
|
||||||
|
<li><p><strong>auth_user_id</strong> (<em>integer</em>) – authenticate user id (from JSON Web Token)</p></li>
|
||||||
|
<li><p><strong>sport_id</strong> (<em>integer</em>) – sport id</p></li>
|
||||||
|
</ul>
|
||||||
|
</dd>
|
||||||
|
<dt class="field-even">Request JSON Object</dt>
|
||||||
|
<dd class="field-even"><ul class="simple">
|
||||||
|
<li><p><strong>is_active</strong> (<em>string</em>) – sport active status</p></li>
|
||||||
|
</ul>
|
||||||
|
</dd>
|
||||||
|
<dt class="field-odd">Request Headers</dt>
|
||||||
|
<dd class="field-odd"><ul class="simple">
|
||||||
|
<li><p><a class="reference external" href="https://tools.ietf.org/html/rfc7235#section-4.2">Authorization</a> – 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><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> – sport updated</p></li>
|
||||||
|
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> – invalid payload</p></li>
|
||||||
|
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> – <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>
|
||||||
|
</ul>
|
||||||
|
</p></li>
|
||||||
|
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – sport not found</p></li>
|
||||||
|
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> – </p></li>
|
||||||
|
</ul>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</dd></dl>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -259,6 +259,11 @@
|
|||||||
<td>
|
<td>
|
||||||
<a href="api/activities.html#patch--api-activities-(int-activity_id)"><code class="xref">PATCH /api/activities/(int:activity_id)</code></a></td><td>
|
<a href="api/activities.html#patch--api-activities-(int-activity_id)"><code class="xref">PATCH /api/activities/(int:activity_id)</code></a></td><td>
|
||||||
<em></em></td></tr>
|
<em></em></td></tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>
|
||||||
|
<a href="api/sports.html#patch--api-sports-(int-sport_id)"><code class="xref">PATCH /api/sports/(int:sport_id)</code></a></td><td>
|
||||||
|
<em></em></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
BIN
docs/objects.inv
BIN
docs/objects.inv
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -4,4 +4,5 @@ Sports
|
|||||||
.. autoflask:: fittrackee_api:create_app()
|
.. autoflask:: fittrackee_api:create_app()
|
||||||
:endpoints:
|
:endpoints:
|
||||||
sports.get_sports,
|
sports.get_sports,
|
||||||
sports.get_sport
|
sports.get_sport,
|
||||||
|
sports.update_sport
|
||||||
|
@ -60,7 +60,7 @@ class Sport(db.Model):
|
|||||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
label = db.Column(db.String(50), unique=True, nullable=False)
|
label = db.Column(db.String(50), unique=True, nullable=False)
|
||||||
img = db.Column(db.String(255), unique=True, nullable=True)
|
img = db.Column(db.String(255), unique=True, nullable=True)
|
||||||
is_default = db.Column(db.Boolean, default=False, nullable=False)
|
is_active = db.Column(db.Boolean, default=True, nullable=False)
|
||||||
activities = db.relationship(
|
activities = db.relationship(
|
||||||
'Activity', lazy=True, backref=db.backref('sports', lazy='joined')
|
'Activity', lazy=True, backref=db.backref('sports', lazy='joined')
|
||||||
)
|
)
|
||||||
@ -79,8 +79,10 @@ class Sport(db.Model):
|
|||||||
'id': self.id,
|
'id': self.id,
|
||||||
'label': self.label,
|
'label': self.label,
|
||||||
'img': self.img,
|
'img': self.img,
|
||||||
'_can_be_deleted': len(self.activities) == 0
|
'is_active': self.is_active,
|
||||||
and not self.is_default,
|
'_can_be_disabled': not (
|
||||||
|
len(self.activities) > 0 and self.is_active
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,39 +32,45 @@ def get_sports(auth_user_id):
|
|||||||
"data": {
|
"data": {
|
||||||
"sports": [
|
"sports": [
|
||||||
{
|
{
|
||||||
"_can_be_deleted": false,
|
"_can_be_disabled": false,
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"img": "/img/sports/cycling-sport.png",
|
"img": "/img/sports/cycling-sport.png",
|
||||||
|
"is_active": true,
|
||||||
"label": "Cycling (Sport)"
|
"label": "Cycling (Sport)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_can_be_deleted": false,
|
"_can_be_disabled": false,
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"img": "/img/sports/cycling-transport.png",
|
"img": "/img/sports/cycling-transport.png",
|
||||||
|
"is_active": true,
|
||||||
"label": "Cycling (Transport)"
|
"label": "Cycling (Transport)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_can_be_deleted": false,
|
"_can_be_disabled": false,
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"img": "/img/sports/hiking.png",
|
"img": "/img/sports/hiking.png",
|
||||||
|
"is_active": true,
|
||||||
"label": "Hiking"
|
"label": "Hiking"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_can_be_deleted": false,
|
"_can_be_disabled": false,
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"img": "/img/sports/mountain-biking.png",
|
"img": "/img/sports/mountain-biking.png",
|
||||||
|
"is_active": true,
|
||||||
"label": "Mountain Biking"
|
"label": "Mountain Biking"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_can_be_deleted": false,
|
"_can_be_disabled": false,
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"img": "/img/sports/running.png",
|
"img": "/img/sports/running.png",
|
||||||
|
"is_active": true,
|
||||||
"label": "Running"
|
"label": "Running"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_can_be_deleted": false,
|
"_can_be_disabled": false,
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"img": "/img/sports/walking.png",
|
"img": "/img/sports/walking.png",
|
||||||
|
"is_active": true,
|
||||||
"label": "Walking"
|
"label": "Walking"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -117,9 +123,10 @@ def get_sport(auth_user_id, sport_id):
|
|||||||
"data": {
|
"data": {
|
||||||
"sports": [
|
"sports": [
|
||||||
{
|
{
|
||||||
"_can_be_deleted": false,
|
"_can_be_disabled": false,
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"img": "/img/sports/cycling-sport.png",
|
"img": "/img/sports/cycling-sport.png",
|
||||||
|
"is_active": true,
|
||||||
"label": "Cycling (Sport)"
|
"label": "Cycling (Sport)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -168,64 +175,101 @@ def get_sport(auth_user_id, sport_id):
|
|||||||
return jsonify(response_object), code
|
return jsonify(response_object), code
|
||||||
|
|
||||||
|
|
||||||
# no administration - no documentation for now
|
|
||||||
|
|
||||||
|
|
||||||
@sports_blueprint.route('/sports', methods=['POST'])
|
|
||||||
@authenticate_as_admin
|
|
||||||
def post_sport(auth_user_id):
|
|
||||||
"""Post a sport"""
|
|
||||||
sport_data = request.get_json()
|
|
||||||
if not sport_data or sport_data.get('label') is None:
|
|
||||||
response_object = {'status': 'error', 'message': 'Invalid payload.'}
|
|
||||||
return jsonify(response_object), 400
|
|
||||||
|
|
||||||
try:
|
|
||||||
new_sport = Sport(label=sport_data.get('label'))
|
|
||||||
db.session.add(new_sport)
|
|
||||||
db.session.commit()
|
|
||||||
response_object = {
|
|
||||||
'status': 'created',
|
|
||||||
'data': {'sports': [new_sport.serialize()]},
|
|
||||||
}
|
|
||||||
code = 201
|
|
||||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
|
||||||
db.session.rollback()
|
|
||||||
appLog.error(e)
|
|
||||||
response_object = {
|
|
||||||
'status': 'error',
|
|
||||||
'message': 'Error. Please try again or contact the administrator.',
|
|
||||||
}
|
|
||||||
code = 500
|
|
||||||
return jsonify(response_object), code
|
|
||||||
|
|
||||||
|
|
||||||
@sports_blueprint.route('/sports/<int:sport_id>', methods=['PATCH'])
|
@sports_blueprint.route('/sports/<int:sport_id>', methods=['PATCH'])
|
||||||
@authenticate_as_admin
|
@authenticate_as_admin
|
||||||
def update_sport(auth_user_id, sport_id):
|
def update_sport(auth_user_id, sport_id):
|
||||||
"""Update a sport"""
|
"""Update a sport
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
PATCH /api/sports/1 HTTP/1.1
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
- success
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"sports": [
|
||||||
|
{
|
||||||
|
"_can_be_disabled": false,
|
||||||
|
"id": 1,
|
||||||
|
"img": "/img/sports/cycling-sport.png",
|
||||||
|
"is_active": false,
|
||||||
|
"label": "Cycling (Sport)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status": "success"
|
||||||
|
}
|
||||||
|
|
||||||
|
- sport not found
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 404 NOT FOUND
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"sports": []
|
||||||
|
},
|
||||||
|
"status": "not found"
|
||||||
|
}
|
||||||
|
|
||||||
|
:param integer auth_user_id: authenticate user id (from JSON Web Token)
|
||||||
|
:param integer sport_id: sport id
|
||||||
|
|
||||||
|
:<json string is_active: sport active status
|
||||||
|
|
||||||
|
:reqheader Authorization: OAuth 2.0 Bearer Token
|
||||||
|
|
||||||
|
:statuscode 200: sport updated
|
||||||
|
:statuscode 400: invalid payload
|
||||||
|
:statuscode 401:
|
||||||
|
- Provide a valid auth token.
|
||||||
|
- Signature expired. Please log in again.
|
||||||
|
- Invalid token. Please log in again.
|
||||||
|
:statuscode 404: sport not found
|
||||||
|
:statuscode 500:
|
||||||
|
|
||||||
|
"""
|
||||||
sport_data = request.get_json()
|
sport_data = request.get_json()
|
||||||
if not sport_data or sport_data.get('label') is None:
|
if not sport_data or sport_data.get('is_active') is None:
|
||||||
response_object = {'status': 'error', 'message': 'Invalid payload.'}
|
response_object = {'status': 'error', 'message': 'Invalid payload.'}
|
||||||
return jsonify(response_object), 400
|
return jsonify(response_object), 400
|
||||||
|
|
||||||
sports_list = []
|
|
||||||
try:
|
try:
|
||||||
sport = Sport.query.filter_by(id=sport_id).first()
|
sport = Sport.query.filter_by(id=sport_id).first()
|
||||||
if sport:
|
if sport:
|
||||||
sport.label = sport_data.get('label')
|
if not (
|
||||||
|
not sport_data.get('is_active')
|
||||||
|
and sport.is_active
|
||||||
|
and len(sport.activities) > 0
|
||||||
|
):
|
||||||
|
sport.is_active = sport_data.get('is_active')
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
sports_list.append({'id': sport.id, 'label': sport.label})
|
|
||||||
response_object = {
|
response_object = {
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'data': {'sports': sports_list},
|
'data': {'sports': [sport.serialize()]},
|
||||||
}
|
}
|
||||||
code = 200
|
code = 200
|
||||||
else:
|
else:
|
||||||
response_object = {
|
response_object = {
|
||||||
'status': 'not found',
|
'status': 'fail',
|
||||||
'data': {'sports': sports_list},
|
'message': 'Sport can not be disabled, activities exist.',
|
||||||
}
|
}
|
||||||
|
code = 400
|
||||||
|
else:
|
||||||
|
response_object = {'status': 'not found', 'data': {'sports': []}}
|
||||||
code = 404
|
code = 404
|
||||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
@ -236,36 +280,3 @@ def update_sport(auth_user_id, sport_id):
|
|||||||
}
|
}
|
||||||
code = 500
|
code = 500
|
||||||
return jsonify(response_object), code
|
return jsonify(response_object), code
|
||||||
|
|
||||||
|
|
||||||
@sports_blueprint.route('/sports/<int:sport_id>', methods=['DELETE'])
|
|
||||||
@authenticate_as_admin
|
|
||||||
def delete_sport(auth_user_id, sport_id):
|
|
||||||
"""Delete a sport"""
|
|
||||||
try:
|
|
||||||
sport = Sport.query.filter_by(id=sport_id).first()
|
|
||||||
if sport:
|
|
||||||
db.session.delete(sport)
|
|
||||||
db.session.commit()
|
|
||||||
response_object = {'status': 'no content'}
|
|
||||||
code = 204
|
|
||||||
else:
|
|
||||||
response_object = {'status': 'not found', 'data': {'sports': []}}
|
|
||||||
code = 404
|
|
||||||
except exc.IntegrityError as e:
|
|
||||||
db.session.rollback()
|
|
||||||
appLog.error(e)
|
|
||||||
response_object = {
|
|
||||||
'status': 'error',
|
|
||||||
'message': 'Error. Associated activities exist.',
|
|
||||||
}
|
|
||||||
code = 500
|
|
||||||
except (exc.OperationalError, ValueError) as e:
|
|
||||||
db.session.rollback()
|
|
||||||
appLog.error(e)
|
|
||||||
response_object = {
|
|
||||||
'status': 'error',
|
|
||||||
'message': 'Error. Please try again or contact the administrator.',
|
|
||||||
}
|
|
||||||
code = 500
|
|
||||||
return jsonify(response_object), code
|
|
||||||
|
@ -4,14 +4,16 @@ expected_sport_1_cycling_result = {
|
|||||||
'id': 1,
|
'id': 1,
|
||||||
'label': 'Cycling',
|
'label': 'Cycling',
|
||||||
'img': None,
|
'img': None,
|
||||||
'_can_be_deleted': True,
|
'is_active': True,
|
||||||
|
'_can_be_disabled': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_sport_2_running_result = {
|
expected_sport_2_running_result = {
|
||||||
'id': 2,
|
'id': 2,
|
||||||
'label': 'Running',
|
'label': 'Running',
|
||||||
'img': None,
|
'img': None,
|
||||||
'_can_be_deleted': True,
|
'is_active': True,
|
||||||
|
'_can_be_disabled': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -83,78 +85,6 @@ def test_get_a_sport_invalid(app, user_1):
|
|||||||
assert len(data['data']['sports']) == 0
|
assert len(data['data']['sports']) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_add_a_sport(app, user_1_admin):
|
|
||||||
client = app.test_client()
|
|
||||||
resp_login = client.post(
|
|
||||||
'/api/auth/login',
|
|
||||||
data=json.dumps(dict(email='admin@example.com', password='12345678')),
|
|
||||||
content_type='application/json',
|
|
||||||
)
|
|
||||||
response = client.post(
|
|
||||||
'/api/sports',
|
|
||||||
content_type='application/json',
|
|
||||||
data=json.dumps(dict(label='Cycling')),
|
|
||||||
headers=dict(
|
|
||||||
Authorization='Bearer '
|
|
||||||
+ json.loads(resp_login.data.decode())['auth_token']
|
|
||||||
),
|
|
||||||
)
|
|
||||||
data = json.loads(response.data.decode())
|
|
||||||
|
|
||||||
assert response.status_code == 201
|
|
||||||
assert 'created' in data['status']
|
|
||||||
|
|
||||||
assert len(data['data']['sports']) == 1
|
|
||||||
assert data['data']['sports'][0] == expected_sport_1_cycling_result
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_a_sport_not_admin(app, user_1):
|
|
||||||
client = app.test_client()
|
|
||||||
resp_login = client.post(
|
|
||||||
'/api/auth/login',
|
|
||||||
data=json.dumps(dict(email='test@test.com', password='12345678')),
|
|
||||||
content_type='application/json',
|
|
||||||
)
|
|
||||||
response = client.post(
|
|
||||||
'/api/sports',
|
|
||||||
content_type='application/json',
|
|
||||||
data=json.dumps(dict(label='surfing')),
|
|
||||||
headers=dict(
|
|
||||||
Authorization='Bearer '
|
|
||||||
+ json.loads(resp_login.data.decode())['auth_token']
|
|
||||||
),
|
|
||||||
)
|
|
||||||
data = json.loads(response.data.decode())
|
|
||||||
|
|
||||||
assert response.status_code == 403
|
|
||||||
assert 'created' not in data['status']
|
|
||||||
assert 'error' in data['status']
|
|
||||||
assert 'You do not have permissions.' in data['message']
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_a_sport_invalid_payload(app, user_1_admin):
|
|
||||||
client = app.test_client()
|
|
||||||
resp_login = client.post(
|
|
||||||
'/api/auth/login',
|
|
||||||
data=json.dumps(dict(email='admin@example.com', password='12345678')),
|
|
||||||
content_type='application/json',
|
|
||||||
)
|
|
||||||
response = client.post(
|
|
||||||
'/api/sports',
|
|
||||||
content_type='application/json',
|
|
||||||
data=json.dumps(dict()),
|
|
||||||
headers=dict(
|
|
||||||
Authorization='Bearer '
|
|
||||||
+ json.loads(resp_login.data.decode())['auth_token']
|
|
||||||
),
|
|
||||||
)
|
|
||||||
data = json.loads(response.data.decode())
|
|
||||||
|
|
||||||
assert response.status_code == 400
|
|
||||||
assert 'error' in data['status']
|
|
||||||
assert 'Invalid payload.' in data['message']
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_a_sport(app, user_1_admin, sport_1_cycling):
|
def test_update_a_sport(app, user_1_admin, sport_1_cycling):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
resp_login = client.post(
|
resp_login = client.post(
|
||||||
@ -165,7 +95,7 @@ def test_update_a_sport(app, user_1_admin, sport_1_cycling):
|
|||||||
response = client.patch(
|
response = client.patch(
|
||||||
'/api/sports/1',
|
'/api/sports/1',
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
data=json.dumps(dict(label='cycling updated')),
|
data=json.dumps(dict(is_active=False)),
|
||||||
headers=dict(
|
headers=dict(
|
||||||
Authorization='Bearer '
|
Authorization='Bearer '
|
||||||
+ json.loads(resp_login.data.decode())['auth_token']
|
+ json.loads(resp_login.data.decode())['auth_token']
|
||||||
@ -177,7 +107,49 @@ def test_update_a_sport(app, user_1_admin, sport_1_cycling):
|
|||||||
assert 'success' in data['status']
|
assert 'success' in data['status']
|
||||||
|
|
||||||
assert len(data['data']['sports']) == 1
|
assert len(data['data']['sports']) == 1
|
||||||
assert 'cycling updated' in data['data']['sports'][0]['label']
|
assert data['data']['sports'][0]['is_active'] is False
|
||||||
|
|
||||||
|
response = client.patch(
|
||||||
|
'/api/sports/1',
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(dict(is_active=True)),
|
||||||
|
headers=dict(
|
||||||
|
Authorization='Bearer '
|
||||||
|
+ json.loads(resp_login.data.decode())['auth_token']
|
||||||
|
),
|
||||||
|
)
|
||||||
|
data = json.loads(response.data.decode())
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert 'success' in data['status']
|
||||||
|
|
||||||
|
assert len(data['data']['sports']) == 1
|
||||||
|
assert data['data']['sports'][0]['is_active'] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_disable_a_sport_with_activities(
|
||||||
|
app, user_1_admin, sport_1_cycling, activity_cycling_user_1
|
||||||
|
):
|
||||||
|
client = app.test_client()
|
||||||
|
resp_login = client.post(
|
||||||
|
'/api/auth/login',
|
||||||
|
data=json.dumps(dict(email='admin@example.com', password='12345678')),
|
||||||
|
content_type='application/json',
|
||||||
|
)
|
||||||
|
response = client.patch(
|
||||||
|
'/api/sports/1',
|
||||||
|
content_type='application/json',
|
||||||
|
data=json.dumps(dict(is_active=False)),
|
||||||
|
headers=dict(
|
||||||
|
Authorization='Bearer '
|
||||||
|
+ json.loads(resp_login.data.decode())['auth_token']
|
||||||
|
),
|
||||||
|
)
|
||||||
|
data = json.loads(response.data.decode())
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert 'fail' in data['status']
|
||||||
|
assert 'Sport can not be disabled, activities exist.' in data['message']
|
||||||
|
|
||||||
|
|
||||||
def test_update_a_sport_not_admin(app, user_1, sport_1_cycling):
|
def test_update_a_sport_not_admin(app, user_1, sport_1_cycling):
|
||||||
@ -190,7 +162,7 @@ def test_update_a_sport_not_admin(app, user_1, sport_1_cycling):
|
|||||||
response = client.patch(
|
response = client.patch(
|
||||||
'/api/sports/1',
|
'/api/sports/1',
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
data=json.dumps(dict(label='cycling updated')),
|
data=json.dumps(dict(is_active=False)),
|
||||||
headers=dict(
|
headers=dict(
|
||||||
Authorization='Bearer '
|
Authorization='Bearer '
|
||||||
+ json.loads(resp_login.data.decode())['auth_token']
|
+ json.loads(resp_login.data.decode())['auth_token']
|
||||||
@ -237,7 +209,7 @@ def test_update_a_sport_invalid_id(app, user_1_admin):
|
|||||||
response = client.patch(
|
response = client.patch(
|
||||||
'/api/sports/1',
|
'/api/sports/1',
|
||||||
content_type='application/json',
|
content_type='application/json',
|
||||||
data=json.dumps(dict(label='cycling updated')),
|
data=json.dumps(dict(is_active=False)),
|
||||||
headers=dict(
|
headers=dict(
|
||||||
Authorization='Bearer '
|
Authorization='Bearer '
|
||||||
+ json.loads(resp_login.data.decode())['auth_token']
|
+ json.loads(resp_login.data.decode())['auth_token']
|
||||||
@ -248,91 +220,3 @@ def test_update_a_sport_invalid_id(app, user_1_admin):
|
|||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
assert 'not found' in data['status']
|
assert 'not found' in data['status']
|
||||||
assert len(data['data']['sports']) == 0
|
assert len(data['data']['sports']) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_delete_a_sport(app, user_1_admin, sport_1_cycling):
|
|
||||||
client = app.test_client()
|
|
||||||
resp_login = client.post(
|
|
||||||
'/api/auth/login',
|
|
||||||
data=json.dumps(dict(email='admin@example.com', password='12345678')),
|
|
||||||
content_type='application/json',
|
|
||||||
)
|
|
||||||
response = client.delete(
|
|
||||||
'/api/sports/1',
|
|
||||||
content_type='application/json',
|
|
||||||
headers=dict(
|
|
||||||
Authorization='Bearer '
|
|
||||||
+ json.loads(resp_login.data.decode())['auth_token']
|
|
||||||
),
|
|
||||||
)
|
|
||||||
assert response.status_code == 204
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_a_sport_not_admin(app, user_1, sport_1_cycling):
|
|
||||||
client = app.test_client()
|
|
||||||
resp_login = client.post(
|
|
||||||
'/api/auth/login',
|
|
||||||
data=json.dumps(dict(email='test@test.com', password='12345678')),
|
|
||||||
content_type='application/json',
|
|
||||||
)
|
|
||||||
response = client.delete(
|
|
||||||
'/api/sports/1',
|
|
||||||
content_type='application/json',
|
|
||||||
headers=dict(
|
|
||||||
Authorization='Bearer '
|
|
||||||
+ json.loads(resp_login.data.decode())['auth_token']
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
data = json.loads(response.data.decode())
|
|
||||||
assert response.status_code == 403
|
|
||||||
assert 'error' in data['status']
|
|
||||||
assert 'You do not have permissions.' in data['message']
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_a_sport_invalid_id(app, user_1_admin):
|
|
||||||
client = app.test_client()
|
|
||||||
resp_login = client.post(
|
|
||||||
'/api/auth/login',
|
|
||||||
data=json.dumps(dict(email='admin@example.com', password='12345678')),
|
|
||||||
content_type='application/json',
|
|
||||||
)
|
|
||||||
response = client.delete(
|
|
||||||
'/api/sports/1',
|
|
||||||
content_type='application/json',
|
|
||||||
data=json.dumps(dict()),
|
|
||||||
headers=dict(
|
|
||||||
Authorization='Bearer '
|
|
||||||
+ json.loads(resp_login.data.decode())['auth_token']
|
|
||||||
),
|
|
||||||
)
|
|
||||||
data = json.loads(response.data.decode())
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
|
||||||
assert 'not found' in data['status']
|
|
||||||
assert len(data['data']['sports']) == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_a_sport_with_an_activity(
|
|
||||||
app, user_1_admin, sport_1_cycling, activity_cycling_user_1
|
|
||||||
):
|
|
||||||
client = app.test_client()
|
|
||||||
resp_login = client.post(
|
|
||||||
'/api/auth/login',
|
|
||||||
data=json.dumps(dict(email='admin@example.com', password='12345678')),
|
|
||||||
content_type='application/json',
|
|
||||||
)
|
|
||||||
response = client.delete(
|
|
||||||
'/api/sports/1',
|
|
||||||
content_type='application/json',
|
|
||||||
headers=dict(
|
|
||||||
Authorization='Bearer '
|
|
||||||
+ json.loads(resp_login.data.decode())['auth_token']
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
data = json.loads(response.data.decode())
|
|
||||||
|
|
||||||
assert response.status_code == 500
|
|
||||||
assert 'error' in data['status']
|
|
||||||
assert 'Error. Associated activities exist.' in data['message']
|
|
||||||
|
@ -6,7 +6,8 @@ def test_sport_model(app, sport_1_cycling):
|
|||||||
serialized_sport = sport_1_cycling.serialize()
|
serialized_sport = sport_1_cycling.serialize()
|
||||||
assert 1 == serialized_sport['id']
|
assert 1 == serialized_sport['id']
|
||||||
assert 'Cycling' == serialized_sport['label']
|
assert 'Cycling' == serialized_sport['label']
|
||||||
assert serialized_sport['_can_be_deleted'] is True
|
assert serialized_sport['is_active'] is True
|
||||||
|
assert serialized_sport['_can_be_disabled'] is True
|
||||||
|
|
||||||
|
|
||||||
def test_sport_model_with_activity(
|
def test_sport_model_with_activity(
|
||||||
@ -19,4 +20,5 @@ def test_sport_model_with_activity(
|
|||||||
serialized_sport = sport_1_cycling.serialize()
|
serialized_sport = sport_1_cycling.serialize()
|
||||||
assert 1 == serialized_sport['id']
|
assert 1 == serialized_sport['id']
|
||||||
assert 'Cycling' == serialized_sport['label']
|
assert 'Cycling' == serialized_sport['label']
|
||||||
assert serialized_sport['_can_be_deleted'] is False
|
assert serialized_sport['is_active'] is True
|
||||||
|
assert serialized_sport['_can_be_disabled'] is False
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
"""replace 'is_default' with 'is_active' in 'Sports' table
|
||||||
|
|
||||||
|
Revision ID: 1345afe3b11d
|
||||||
|
Revises: f69f1e413bde
|
||||||
|
Create Date: 2019-09-22 17:57:00.595775
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '1345afe3b11d'
|
||||||
|
down_revision = 'f69f1e413bde'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('sports',
|
||||||
|
sa.Column('is_active',
|
||||||
|
sa.Boolean(create_constraint=50),
|
||||||
|
nullable=True))
|
||||||
|
op.execute("UPDATE sports SET is_active = true")
|
||||||
|
op.alter_column('sports', 'is_active', nullable=False)
|
||||||
|
op.drop_column('sports', 'is_default')
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.add_column('sports',
|
||||||
|
sa.Column('is_default',
|
||||||
|
sa.Boolean(create_constraint=50),
|
||||||
|
nullable=True))
|
||||||
|
op.execute("UPDATE sports SET is_default = true")
|
||||||
|
op.alter_column('sports', 'is_default', nullable=False)
|
||||||
|
op.drop_column('sports', 'is_active')
|
@ -24,14 +24,29 @@ export const setLoading = loading => ({
|
|||||||
loading,
|
loading,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getOrUpdateData = (action, target, data) => dispatch => {
|
export const updateSportsData = data => ({
|
||||||
|
type: 'UPDATE_SPORT_DATA',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getOrUpdateData = (
|
||||||
|
action,
|
||||||
|
target,
|
||||||
|
data,
|
||||||
|
canDispatch = true
|
||||||
|
) => dispatch => {
|
||||||
if (data && data.id && isNaN(data.id)) {
|
if (data && data.id && isNaN(data.id)) {
|
||||||
return dispatch(setError(`${target}|Incorrect id`))
|
return dispatch(setError(`${target}|Incorrect id`))
|
||||||
}
|
}
|
||||||
|
dispatch(setError(''))
|
||||||
return FitTrackeeApi[action](target, data)
|
return FitTrackeeApi[action](target, data)
|
||||||
.then(ret => {
|
.then(ret => {
|
||||||
if (ret.status === 'success') {
|
if (ret.status === 'success') {
|
||||||
|
if (canDispatch) {
|
||||||
dispatch(setData(target, ret.data))
|
dispatch(setData(target, ret.data))
|
||||||
|
} else if (action === 'updateData' && target === 'sports') {
|
||||||
|
dispatch(updateSportsData(ret.data.sports[0]))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
dispatch(setError(`${target}|${ret.message || ret.status}`))
|
dispatch(setError(`${target}|${ret.message || ret.status}`))
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ class ActivityDisplay extends React.Component {
|
|||||||
} = this.props
|
} = this.props
|
||||||
const { coordinates, displayModal } = this.state
|
const { coordinates, displayModal } = this.state
|
||||||
const [activity] = activities
|
const [activity] = activities
|
||||||
const title = activity ? activity.title : 'Activity'
|
const title = activity ? activity.title : t('activities:Activity')
|
||||||
const [sport] = activity
|
const [sport] = activity
|
||||||
? sports.filter(s => s.id === activity.sport_id)
|
? sports.filter(s => s.id === activity.sport_id)
|
||||||
: []
|
: []
|
||||||
|
@ -5,7 +5,8 @@ import { capitalize } from '../../utils/index'
|
|||||||
|
|
||||||
const menuItems = ['application', 'sports', 'users']
|
const menuItems = ['application', 'sports', 'users']
|
||||||
|
|
||||||
export default function AdminMenu() {
|
export default function AdminMenu(props) {
|
||||||
|
const { t } = props
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ul className="admin-items">
|
<ul className="admin-items">
|
||||||
@ -16,7 +17,7 @@ export default function AdminMenu() {
|
|||||||
pathname: `/admin/${item}`,
|
pathname: `/admin/${item}`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{capitalize(item)}
|
{t(`administration:${capitalize(item)}`)}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
|
|
||||||
import { getOrUpdateData } from '../../../actions'
|
|
||||||
import AdminDetail from '../generic/AdminDetail'
|
|
||||||
|
|
||||||
class AdminSports extends React.Component {
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.loadSport(this.props.match.params.sportId)
|
|
||||||
}
|
|
||||||
componentWillUnmount() {
|
|
||||||
// reload all Sports
|
|
||||||
this.props.loadSport(null)
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
const { sports } = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<AdminDetail results={sports} target="sports" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
sports: state.sports.data,
|
|
||||||
user: state.user,
|
|
||||||
}),
|
|
||||||
dispatch => ({
|
|
||||||
loadSport: sportId => {
|
|
||||||
dispatch(getOrUpdateData('getData', 'sports', { id: sportId }))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)(AdminSports)
|
|
@ -1,42 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { Route, Switch } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { getOrUpdateData } from '../../../actions'
|
|
||||||
import AdminPage from '../generic/AdminPage'
|
|
||||||
import AdminSport from './AdminSport'
|
|
||||||
import AdminSportsAdd from './AdminSportsAdd'
|
|
||||||
import NotFound from '../../Others/NotFound'
|
|
||||||
|
|
||||||
class AdminSports extends React.Component {
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.loadSports()
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
const { sports } = this.props
|
|
||||||
return (
|
|
||||||
<Switch>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path="/admin/sports"
|
|
||||||
render={() => <AdminPage data={sports} target="sports" />}
|
|
||||||
/>
|
|
||||||
<Route exact path="/admin/sports/add" component={AdminSportsAdd} />
|
|
||||||
<Route exact path="/admin/sports/:sportId" component={AdminSport} />
|
|
||||||
<Route component={NotFound} />
|
|
||||||
</Switch>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
sports: state.sports,
|
|
||||||
user: state.user,
|
|
||||||
}),
|
|
||||||
dispatch => ({
|
|
||||||
loadSports: () => {
|
|
||||||
dispatch(getOrUpdateData('getData', 'sports'))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)(AdminSports)
|
|
@ -1,75 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Helmet } from 'react-helmet'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
|
|
||||||
import { addData } from '../../../actions/index'
|
|
||||||
import { history } from '../../../index'
|
|
||||||
|
|
||||||
class AdminSportsAdd extends React.Component {
|
|
||||||
componentDidMount() {}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { message, onAddSport } = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Helmet>
|
|
||||||
<title>FitTrackee - Admin - Add Sport</title>
|
|
||||||
</Helmet>
|
|
||||||
<h1 className="page-title">Administration - Sport</h1>
|
|
||||||
{message && <code>{message}</code>}
|
|
||||||
|
|
||||||
<div className="container">
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-md-2" />
|
|
||||||
<div className="col-md-8">
|
|
||||||
<div className="card">
|
|
||||||
<div className="card-header">Add a sport</div>
|
|
||||||
<div className="card-body">
|
|
||||||
<form onSubmit={event => event.preventDefault()}>
|
|
||||||
<div className="form-group">
|
|
||||||
<label>
|
|
||||||
Label:
|
|
||||||
<input
|
|
||||||
name="label"
|
|
||||||
className="form-control input-lg"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-primary btn-lg btn-block"
|
|
||||||
onClick={event => onAddSport(event)}
|
|
||||||
value="Submit"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-secondary btn-lg btn-block"
|
|
||||||
onClick={() => history.push('/admin/sports')}
|
|
||||||
value="Cancel"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-2" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
message: state.message,
|
|
||||||
user: state.user,
|
|
||||||
}),
|
|
||||||
dispatch => ({
|
|
||||||
onAddSport: e => {
|
|
||||||
const data = { label: e.target.form.label.value }
|
|
||||||
dispatch(addData('sports', data))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)(AdminSportsAdd)
|
|
121
fittrackee_client/src/components/Admin/Sports/index.jsx
Normal file
121
fittrackee_client/src/components/Admin/Sports/index.jsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { Helmet } from 'react-helmet'
|
||||||
|
|
||||||
|
import Message from '../../Common/Message'
|
||||||
|
import { getOrUpdateData } from '../../../actions'
|
||||||
|
import { history } from '../../../index'
|
||||||
|
|
||||||
|
class AdminSports extends React.Component {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.loadSports()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { message, sports, t, updateSport } = this.props
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Helmet>
|
||||||
|
<title>FitTrackee - {t('administration:Administration')}</title>
|
||||||
|
</Helmet>
|
||||||
|
{message && <Message message={message} t={t} />}
|
||||||
|
<div className="container">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col card">
|
||||||
|
<div className="card-body">
|
||||||
|
{sports.length > 0 && (
|
||||||
|
<table className="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{t('administration:id')}</th>
|
||||||
|
<th>{t('administration:Image')}</th>
|
||||||
|
<th>{t('administration:Label')}</th>
|
||||||
|
<th>{t('administration:Active')}</th>
|
||||||
|
<th>{t('administration:Actions')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{sports.map(sport => (
|
||||||
|
<tr key={sport.id}>
|
||||||
|
<th scope="row">{sport.id}</th>
|
||||||
|
<td>
|
||||||
|
<img
|
||||||
|
className="admin-img"
|
||||||
|
src={sport.img ? sport.img : '/img/photo.png'}
|
||||||
|
alt="sport logo"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{t(`sports:${sport.label}`)}</td>
|
||||||
|
<td>
|
||||||
|
{sport.is_active ? (
|
||||||
|
<i
|
||||||
|
className="fa fa-check-square-o custom-fa"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<i
|
||||||
|
className="fa fa-square-o custom-fa"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{sport._can_be_disabled ? (
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
className={`btn btn-${
|
||||||
|
sport.is_active ? 'dark' : 'primary'
|
||||||
|
} btn-sm`}
|
||||||
|
value={
|
||||||
|
sport.is_active
|
||||||
|
? t('administration:Disable')
|
||||||
|
: t('administration:Enable')
|
||||||
|
}
|
||||||
|
onClick={() =>
|
||||||
|
updateSport(sport.id, !sport.is_active)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span className="admin-message">
|
||||||
|
{t('administration:activities exist')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-secondary btn-lg btn-block"
|
||||||
|
onClick={() => history.push('/admin/')}
|
||||||
|
value={t('administration:Back')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
state => ({
|
||||||
|
message: state.message,
|
||||||
|
sports: state.sports.data,
|
||||||
|
user: state.user,
|
||||||
|
}),
|
||||||
|
dispatch => ({
|
||||||
|
loadSports: () => {
|
||||||
|
dispatch(getOrUpdateData('getData', 'sports'))
|
||||||
|
},
|
||||||
|
updateSport: (sportId, isActive) => {
|
||||||
|
const data = { id: sportId, is_active: isActive }
|
||||||
|
dispatch(getOrUpdateData('updateData', 'sports', data, false))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)(AdminSports)
|
@ -1,141 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Helmet } from 'react-helmet'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
|
|
||||||
import { deleteData, getOrUpdateData } from '../../../actions/index'
|
|
||||||
import { history } from '../../../index'
|
|
||||||
|
|
||||||
class AdminDetail extends React.Component {
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context)
|
|
||||||
this.state = {
|
|
||||||
isInEdition: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { message, onDataUpdate, onDataDelete, results, target } = this.props
|
|
||||||
const { isInEdition } = this.state
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Helmet>
|
|
||||||
<title>FitTrackee - Admin</title>
|
|
||||||
</Helmet>
|
|
||||||
{message ? (
|
|
||||||
<code>{message}</code>
|
|
||||||
) : (
|
|
||||||
results.length === 1 && (
|
|
||||||
<div className="container">
|
|
||||||
<div className="row">
|
|
||||||
<div className="col card">
|
|
||||||
<div className="card-body">
|
|
||||||
<form onSubmit={event => event.preventDefault()}>
|
|
||||||
{Object.keys(results[0])
|
|
||||||
.filter(key => key.charAt(0) !== '_')
|
|
||||||
.map(key => (
|
|
||||||
<div className="form-group" key={key}>
|
|
||||||
<label>
|
|
||||||
{key}:
|
|
||||||
{key === 'img' ? (
|
|
||||||
<img
|
|
||||||
src={
|
|
||||||
results[0][key]
|
|
||||||
? results[0][key]
|
|
||||||
: '/img/photo.png'
|
|
||||||
}
|
|
||||||
alt="property"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<input
|
|
||||||
className="form-control input-lg"
|
|
||||||
name={key}
|
|
||||||
readOnly={key === 'id' || !isInEdition}
|
|
||||||
defaultValue={results[0][key]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{isInEdition ? (
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-primary btn-lg btn-block"
|
|
||||||
onClick={event => {
|
|
||||||
onDataUpdate(event, target)
|
|
||||||
this.setState({ isInEdition: false })
|
|
||||||
}}
|
|
||||||
value="Submit"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-secondary btn-lg btn-block"
|
|
||||||
onClick={event => {
|
|
||||||
event.target.form.reset()
|
|
||||||
this.setState({ isInEdition: false })
|
|
||||||
}}
|
|
||||||
value="Cancel"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-primary btn-lg btn-block"
|
|
||||||
onClick={() => this.setState({ isInEdition: true })}
|
|
||||||
value="Edit"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-danger btn-lg btn-block"
|
|
||||||
disabled={!results[0]._can_be_deleted}
|
|
||||||
onClick={event => onDataDelete(event, target)}
|
|
||||||
title={
|
|
||||||
results[0]._can_be_deleted
|
|
||||||
? ''
|
|
||||||
: "Can't be deleted, associated data exist"
|
|
||||||
}
|
|
||||||
value="Delete"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-secondary btn-lg btn-block"
|
|
||||||
onClick={() => history.push(`/admin/${target}`)}
|
|
||||||
value="Back to the list"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
message: state.message,
|
|
||||||
}),
|
|
||||||
dispatch => ({
|
|
||||||
onDataDelete: (e, target) => {
|
|
||||||
const id = e.target.form.id.value
|
|
||||||
dispatch(deleteData(target, id))
|
|
||||||
},
|
|
||||||
onDataUpdate: (e, target) => {
|
|
||||||
const data = [].slice
|
|
||||||
.call(e.target.form.elements)
|
|
||||||
.reduce(function(map, obj) {
|
|
||||||
if (obj.name) {
|
|
||||||
map[obj.name] = obj.value
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
}, {})
|
|
||||||
dispatch(getOrUpdateData('updateData', target, data))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)(AdminDetail)
|
|
@ -1,94 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Helmet } from 'react-helmet'
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { history } from '../../../index'
|
|
||||||
|
|
||||||
export default function AdminPage(props) {
|
|
||||||
const { data, target } = props
|
|
||||||
const { error } = data
|
|
||||||
const results = data.data
|
|
||||||
const tbKeys = []
|
|
||||||
if (results.length > 0) {
|
|
||||||
Object.keys(results[0])
|
|
||||||
.filter(key => key.charAt(0) !== '_')
|
|
||||||
.map(key => tbKeys.push(key))
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Helmet>
|
|
||||||
<title>FitTrackee - Admin</title>
|
|
||||||
</Helmet>
|
|
||||||
{error ? (
|
|
||||||
<code>{error}</code>
|
|
||||||
) : (
|
|
||||||
<div className="container">
|
|
||||||
<div className="row">
|
|
||||||
<div className="col card">
|
|
||||||
<div className="card-body">
|
|
||||||
<table className="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
{tbKeys.map(tbKey => (
|
|
||||||
<th key={tbKey} scope="col">
|
|
||||||
{tbKey}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{results.map((result, idx) => (
|
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
|
||||||
<tr key={idx}>
|
|
||||||
{Object.keys(result)
|
|
||||||
.filter(key => key.charAt(0) !== '_')
|
|
||||||
.map(key => {
|
|
||||||
if (key === 'id') {
|
|
||||||
return (
|
|
||||||
<th key={key} scope="row">
|
|
||||||
<Link to={`/admin/${target}/${result[key]}`}>
|
|
||||||
{result[key]}
|
|
||||||
</Link>
|
|
||||||
</th>
|
|
||||||
)
|
|
||||||
} else if (key === 'img') {
|
|
||||||
return (
|
|
||||||
<td key={key}>
|
|
||||||
<img
|
|
||||||
className="admin-img"
|
|
||||||
src={
|
|
||||||
result[key]
|
|
||||||
? result[key]
|
|
||||||
: '/img/photo.png'
|
|
||||||
}
|
|
||||||
alt="logo"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return <td key={key}>{result[key]}</td>
|
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-primary btn-lg btn-block"
|
|
||||||
onClick={() => history.push(`/admin/${target}/add`)}
|
|
||||||
value="Add a new item"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-secondary btn-lg btn-block"
|
|
||||||
onClick={() => history.push('/admin/')}
|
|
||||||
value="Back"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,21 +1,22 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
|
import { withTranslation } from 'react-i18next'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { Link, Redirect, Route, Switch } from 'react-router-dom'
|
import { Link, Redirect, Route, Switch } from 'react-router-dom'
|
||||||
|
|
||||||
import AdminDashboard from './AdminDashboard'
|
import AdminDashboard from './AdminDashboard'
|
||||||
import AdminMenu from './AdminMenu'
|
import AdminMenu from './AdminMenu'
|
||||||
import AdminSports from './Sports/AdminSports'
|
import AdminSports from './Sports'
|
||||||
import AccessDenied from './../Others/AccessDenied'
|
import AccessDenied from './../Others/AccessDenied'
|
||||||
import NotFound from './../Others/NotFound'
|
import NotFound from './../Others/NotFound'
|
||||||
import { isLoggedIn } from '../../utils'
|
import { isLoggedIn } from '../../utils'
|
||||||
|
|
||||||
function Admin(props) {
|
function Admin(props) {
|
||||||
const { user } = props
|
const { t, user } = props
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>FitTrackee - Admin</title>
|
<title>FitTrackee - {t('administration:Administration')}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="container dashboard">
|
<div className="container dashboard">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@ -27,11 +28,11 @@ function Admin(props) {
|
|||||||
pathname: '/admin/',
|
pathname: '/admin/',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Administration
|
{t('administration:Administration')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<AdminMenu />
|
<AdminMenu t={t} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -39,8 +40,16 @@ function Admin(props) {
|
|||||||
{isLoggedIn() ? (
|
{isLoggedIn() ? (
|
||||||
user.admin ? (
|
user.admin ? (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/admin" component={AdminDashboard} />
|
<Route
|
||||||
<Route path="/admin/sports" component={AdminSports} />
|
exact
|
||||||
|
path="/admin"
|
||||||
|
render={() => <AdminDashboard t={t} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path="/admin/sports"
|
||||||
|
render={() => <AdminSports t={t} />}
|
||||||
|
/>
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
) : (
|
) : (
|
||||||
@ -56,6 +65,8 @@ function Admin(props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(state => ({
|
export default withTranslation()(
|
||||||
|
connect(state => ({
|
||||||
user: state.user,
|
user: state.user,
|
||||||
}))(Admin)
|
}))(Admin)
|
||||||
|
)
|
||||||
|
@ -170,6 +170,12 @@ label {
|
|||||||
list-style-type: square;
|
list-style-type: square;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-message {
|
||||||
|
color: #7c7c7d;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import LanguageDetector from 'i18next-browser-languagedetector'
|
|||||||
import XHR from 'i18next-xhr-backend'
|
import XHR from 'i18next-xhr-backend'
|
||||||
|
|
||||||
import EnActivitiesTranslations from './locales/en/activities.json'
|
import EnActivitiesTranslations from './locales/en/activities.json'
|
||||||
|
import EnAdministrationTranslations from './locales/en/administration.json'
|
||||||
import EnCommonTranslations from './locales/en/common.json'
|
import EnCommonTranslations from './locales/en/common.json'
|
||||||
import EnDashboardTranslations from './locales/en/dashboard.json'
|
import EnDashboardTranslations from './locales/en/dashboard.json'
|
||||||
import EnMessagesTranslations from './locales/en/messages.json'
|
import EnMessagesTranslations from './locales/en/messages.json'
|
||||||
@ -10,6 +11,7 @@ import EnSportsTranslations from './locales/en/sports.json'
|
|||||||
import EnStatisticsTranslations from './locales/en/statistics.json'
|
import EnStatisticsTranslations from './locales/en/statistics.json'
|
||||||
import EnUserTranslations from './locales/en/user.json'
|
import EnUserTranslations from './locales/en/user.json'
|
||||||
import FrActivitiesTranslations from './locales/fr/activities.json'
|
import FrActivitiesTranslations from './locales/fr/activities.json'
|
||||||
|
import FrAdministrationTranslations from './locales/fr/administration.json'
|
||||||
import FrCommonTranslations from './locales/fr/common.json'
|
import FrCommonTranslations from './locales/fr/common.json'
|
||||||
import FrDashboardTranslations from './locales/fr/dashboard.json'
|
import FrDashboardTranslations from './locales/fr/dashboard.json'
|
||||||
import FrMessagesTranslations from './locales/fr/messages.json'
|
import FrMessagesTranslations from './locales/fr/messages.json'
|
||||||
@ -31,6 +33,7 @@ i18n
|
|||||||
resources: {
|
resources: {
|
||||||
en: {
|
en: {
|
||||||
activities: EnActivitiesTranslations,
|
activities: EnActivitiesTranslations,
|
||||||
|
administration: EnAdministrationTranslations,
|
||||||
common: EnCommonTranslations,
|
common: EnCommonTranslations,
|
||||||
dashboard: EnDashboardTranslations,
|
dashboard: EnDashboardTranslations,
|
||||||
messages: EnMessagesTranslations,
|
messages: EnMessagesTranslations,
|
||||||
@ -40,6 +43,7 @@ i18n
|
|||||||
},
|
},
|
||||||
fr: {
|
fr: {
|
||||||
activities: FrActivitiesTranslations,
|
activities: FrActivitiesTranslations,
|
||||||
|
administration: FrAdministrationTranslations,
|
||||||
common: FrCommonTranslations,
|
common: FrCommonTranslations,
|
||||||
dashboard: FrDashboardTranslations,
|
dashboard: FrDashboardTranslations,
|
||||||
messages: FrMessagesTranslations,
|
messages: FrMessagesTranslations,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"Activity": "Activity",
|
||||||
"Activity Date": "Activity Date",
|
"Activity Date": "Activity Date",
|
||||||
"Add a workout": "Add a workout",
|
"Add a workout": "Add a workout",
|
||||||
"Are you sure you want to delete this activity?": "Are you sure you want to delete this activity?",
|
"Are you sure you want to delete this activity?": "Are you sure you want to delete this activity?",
|
||||||
|
15
fittrackee_client/src/locales/en/administration.json
Normal file
15
fittrackee_client/src/locales/en/administration.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"Actions": "Actions",
|
||||||
|
"Active": "Active",
|
||||||
|
"activities exist": "activities exist",
|
||||||
|
"Administration": "Administration",
|
||||||
|
"Application": "Application",
|
||||||
|
"Back": "Back",
|
||||||
|
"Disable": "Disable",
|
||||||
|
"Enable": "Enable",
|
||||||
|
"id": "id",
|
||||||
|
"Image": "Image",
|
||||||
|
"Label": "Label",
|
||||||
|
"Sports": "Sports",
|
||||||
|
"Users": "Users"
|
||||||
|
}
|
@ -23,6 +23,7 @@
|
|||||||
"records": "records",
|
"records": "records",
|
||||||
"Signature expired. Please log in again.": "Signature expired. Please log in again.",
|
"Signature expired. Please log in again.": "Signature expired. Please log in again.",
|
||||||
"Sorry. That user already exists.": "Sorry. That user already exists.",
|
"Sorry. That user already exists.": "Sorry. That user already exists.",
|
||||||
|
"Sport can not be disabled, activities exist." : "Sport can not be disabled, activities exist.",
|
||||||
"Sport does not exist.": "Sport does not exist.",
|
"Sport does not exist.": "Sport does not exist.",
|
||||||
"sports": "sports",
|
"sports": "sports",
|
||||||
"statistics": "statistiques",
|
"statistics": "statistiques",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"Activity": "Activité",
|
||||||
"Activity Date": "Date de l'activité",
|
"Activity Date": "Date de l'activité",
|
||||||
"Add a workout": "Ajouter une activité",
|
"Add a workout": "Ajouter une activité",
|
||||||
"Are you sure you want to delete this activity?": "Etes-vous sûr de vouloir supprimer cette activité ?",
|
"Are you sure you want to delete this activity?": "Etes-vous sûr de vouloir supprimer cette activité ?",
|
||||||
|
15
fittrackee_client/src/locales/fr/administration.json
Normal file
15
fittrackee_client/src/locales/fr/administration.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"Actions": "Actions",
|
||||||
|
"Active": "Active",
|
||||||
|
"Administration": "Administration",
|
||||||
|
"activities exist": "des activités existent",
|
||||||
|
"Application": "Application",
|
||||||
|
"Back": "Retour",
|
||||||
|
"Disable": "désactiver",
|
||||||
|
"Enable": "activer",
|
||||||
|
"id": "id",
|
||||||
|
"Image": "Image",
|
||||||
|
"Label": "Label",
|
||||||
|
"Sports": "Sports",
|
||||||
|
"Users": "Utilisateurs"
|
||||||
|
}
|
@ -23,6 +23,7 @@
|
|||||||
"records": "records",
|
"records": "records",
|
||||||
"Signature expired. Please log in again.": "Signature expirée. Merci de vous reconnecter.",
|
"Signature expired. Please log in again.": "Signature expirée. Merci de vous reconnecter.",
|
||||||
"Sorry. That user already exists.": "Désolé. Cet utilisateur existe déjà.",
|
"Sorry. That user already exists.": "Désolé. Cet utilisateur existe déjà.",
|
||||||
|
"Sport can not be disabled, activities exist." : "Le sport ne peut être désactivé, des activitées existent",
|
||||||
"Sport does not exist.": "Le sport n'existe pas.",
|
"Sport does not exist.": "Le sport n'existe pas.",
|
||||||
"sports": "sports",
|
"sports": "sports",
|
||||||
"statistics": "statistics",
|
"statistics": "statistics",
|
||||||
|
@ -105,8 +105,20 @@ const messages = (state = initial.messages, action) => {
|
|||||||
const records = (state = initial.records, action) =>
|
const records = (state = initial.records, action) =>
|
||||||
handleDataAndError(state, 'records', action)
|
handleDataAndError(state, 'records', action)
|
||||||
|
|
||||||
const sports = (state = initial.sports, action) =>
|
const sports = (state = initial.sports, action) => {
|
||||||
handleDataAndError(state, 'sports', action)
|
if (action.type === 'UPDATE_SPORT_DATA') {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
data: state.data.map(sport => {
|
||||||
|
if (sport.id === action.data.id) {
|
||||||
|
sport.is_active = action.data.is_active
|
||||||
|
}
|
||||||
|
return sport
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handleDataAndError(state, 'sports', action)
|
||||||
|
}
|
||||||
|
|
||||||
const user = (state = initial.user, action) => {
|
const user = (state = initial.user, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
Loading…
Reference in New Issue
Block a user