API & Client - add pagination and filter on users lists
This commit is contained in:
		
							
								
								
									
										3
									
								
								.flake8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.flake8
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
[flake8]
 | 
			
		||||
per-file-ignores =
 | 
			
		||||
    fittrackee_api/fittrackee_api/activities/stats.py:E501
 | 
			
		||||
@@ -31,6 +31,8 @@ from .utils_gpx import (
 | 
			
		||||
 | 
			
		||||
activities_blueprint = Blueprint('activities', __name__)
 | 
			
		||||
 | 
			
		||||
ACTIVITIES_PER_PAGE = 5
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@activities_blueprint.route('/activities', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
@@ -152,7 +154,8 @@ def get_activities(auth_user_id):
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
 | 
			
		||||
    :query integer page: page if using pagination (default: 1)
 | 
			
		||||
    :query integer per_page: number of activities per page (default: 5)
 | 
			
		||||
    :query integer per_page: number of activities per page
 | 
			
		||||
                             (default: 5, max: 50)
 | 
			
		||||
    :query integer sport_id: sport id
 | 
			
		||||
    :query string from: start date (format: ``%Y-%m-%d``)
 | 
			
		||||
    :query string to: end date (format: ``%Y-%m-%d``)
 | 
			
		||||
@@ -200,7 +203,13 @@ def get_activities(auth_user_id):
 | 
			
		||||
        max_speed_to = params.get('max_speed_to')
 | 
			
		||||
        order = params.get('order')
 | 
			
		||||
        sport_id = params.get('sport_id')
 | 
			
		||||
        per_page = int(params.get('per_page')) if params.get('per_page') else 5
 | 
			
		||||
        per_page = (
 | 
			
		||||
            int(params.get('per_page'))
 | 
			
		||||
            if params.get('per_page')
 | 
			
		||||
            else ACTIVITIES_PER_PAGE
 | 
			
		||||
        )
 | 
			
		||||
        if per_page > 50:
 | 
			
		||||
            per_page = 50
 | 
			
		||||
        activities = (
 | 
			
		||||
            Activity.query.filter(
 | 
			
		||||
                Activity.user_id == auth_user_id,
 | 
			
		||||
 
 | 
			
		||||
@@ -158,7 +158,7 @@ def get_activities_by_time(auth_user_id, user_name):
 | 
			
		||||
 | 
			
		||||
    .. sourcecode:: http
 | 
			
		||||
 | 
			
		||||
      GET /api/stats/admin/by_time?from=2018-01-01&to=2018-06-30&time=week HTTP/1.1  # noqa
 | 
			
		||||
      GET /api/stats/admin/by_time?from=2018-01-01&to=2018-06-30&time=week HTTP/1.1
 | 
			
		||||
 | 
			
		||||
    **Example responses**:
 | 
			
		||||
 | 
			
		||||
@@ -346,7 +346,8 @@ def get_application_stats(auth_user_id):
 | 
			
		||||
        "data": {
 | 
			
		||||
          "activities": 3,
 | 
			
		||||
          "sports": 3,
 | 
			
		||||
          "users": 2
 | 
			
		||||
          "users": 2,
 | 
			
		||||
          "uploads_dir_size": 1000
 | 
			
		||||
        },
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -143,7 +143,7 @@ def update_application_config(auth_user_id):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@config_blueprint.route('/ping', methods=['GET'])
 | 
			
		||||
def ping_pong():
 | 
			
		||||
def health_check():
 | 
			
		||||
    """ health check endpoint
 | 
			
		||||
 | 
			
		||||
    **Example request**:
 | 
			
		||||
 
 | 
			
		||||
@@ -200,3 +200,13 @@ def test_update_config_no_config(app_no_config, user_1_admin):
 | 
			
		||||
    assert response.status_code == 500
 | 
			
		||||
    assert 'error' in data['status']
 | 
			
		||||
    assert 'Error on updating configuration.' in data['message']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_ping(app):
 | 
			
		||||
    """ => Ensure the /ping route behaves correctly."""
 | 
			
		||||
    client = app.test_client()
 | 
			
		||||
    response = client.get('/api/ping')
 | 
			
		||||
    data = json.loads(response.data.decode())
 | 
			
		||||
    assert response.status_code == 200
 | 
			
		||||
    assert 'pong' in data['message']
 | 
			
		||||
    assert 'success' in data['status']
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,11 @@
 | 
			
		||||
import json
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from io import BytesIO
 | 
			
		||||
from unittest.mock import patch
 | 
			
		||||
 | 
			
		||||
from fittrackee_api.users.models import User
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_ping(app):
 | 
			
		||||
    """ => Ensure the /ping route behaves correctly."""
 | 
			
		||||
    client = app.test_client()
 | 
			
		||||
    response = client.get('/api/ping')
 | 
			
		||||
    data = json.loads(response.data.decode())
 | 
			
		||||
    assert response.status_code == 200
 | 
			
		||||
    assert 'pong' in data['message']
 | 
			
		||||
    assert 'success' in data['status']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_single_user(app, user_1, user_2):
 | 
			
		||||
    """=> Get single user details"""
 | 
			
		||||
    client = app.test_client()
 | 
			
		||||
@@ -183,6 +175,13 @@ def test_users_list(app, user_1, user_2, user_3):
 | 
			
		||||
    assert data['data']['users'][2]['sports_list'] == []
 | 
			
		||||
    assert data['data']['users'][2]['total_distance'] == 0
 | 
			
		||||
    assert data['data']['users'][2]['total_duration'] == '0:00:00'
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_users_list_with_activities(
 | 
			
		||||
@@ -246,6 +245,666 @@ def test_users_list_with_activities(
 | 
			
		||||
    assert data['data']['users'][2]['sports_list'] == []
 | 
			
		||||
    assert data['data']['users'][2]['total_distance'] == 0
 | 
			
		||||
    assert data['data']['users'][2]['total_duration'] == '0:00:00'
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@patch('fittrackee_api.users.users.USER_PER_PAGE', 2)
 | 
			
		||||
def test_it_gets_first_page_on_users_list(
 | 
			
		||||
    app, user_1, user_2, user_3,
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?page=1',
 | 
			
		||||
        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']['users']) == 2
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': True,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 2,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@patch('fittrackee_api.users.users.USER_PER_PAGE', 2)
 | 
			
		||||
def test_it_gets_next_page_on_users_list(
 | 
			
		||||
    app, user_1, user_2, user_3,
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?page=2',
 | 
			
		||||
        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']['users']) == 1
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': True,
 | 
			
		||||
        'page': 2,
 | 
			
		||||
        'pages': 2,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_empty_next_page_on_users_list(
 | 
			
		||||
    app, user_1, user_2, user_3,
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?page=2',
 | 
			
		||||
        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']['users']) == 0
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': True,
 | 
			
		||||
        'page': 2,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_user_list_with_2_per_page(
 | 
			
		||||
    app, user_1, user_2, user_3,
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?per_page=2',
 | 
			
		||||
        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']['users']) == 2
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': True,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 2,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_next_page_on_user_list_with_2_per_page(
 | 
			
		||||
    app, user_1, user_2, user_3,
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?page=2&per_page=2',
 | 
			
		||||
        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']['users']) == 1
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': True,
 | 
			
		||||
        'page': 2,
 | 
			
		||||
        'pages': 2,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_username(app, user_1, user_2, user_3):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=username',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'sam' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 'test' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 'toto' in data['data']['users'][2]['username']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_username_ascending(
 | 
			
		||||
    app, user_1, user_2, user_3
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=username&order=asc',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'sam' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 'test' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 'toto' in data['data']['users'][2]['username']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_username_descending(
 | 
			
		||||
    app, user_1, user_2, user_3
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=username&order=desc',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'toto' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 'test' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 'sam' in data['data']['users'][2]['username']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_creation_date(
 | 
			
		||||
    app, user_2, user_3, user_1_admin
 | 
			
		||||
):
 | 
			
		||||
    user_2.created_at = datetime.utcnow() - timedelta(days=1)
 | 
			
		||||
    user_3.created_at = datetime.utcnow() - timedelta(hours=1)
 | 
			
		||||
    user_1_admin.created_at = datetime.utcnow()
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=created_at',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'toto' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 'sam' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 'admin' in data['data']['users'][2]['username']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_creation_date_ascending(
 | 
			
		||||
    app, user_2, user_3, user_1_admin
 | 
			
		||||
):
 | 
			
		||||
    user_2.created_at = datetime.utcnow() - timedelta(days=1)
 | 
			
		||||
    user_3.created_at = datetime.utcnow() - timedelta(hours=1)
 | 
			
		||||
    user_1_admin.created_at = datetime.utcnow()
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=created_at&order=asc',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'toto' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 'sam' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 'admin' in data['data']['users'][2]['username']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_creation_date_descending(
 | 
			
		||||
    app, user_2, user_3, user_1_admin
 | 
			
		||||
):
 | 
			
		||||
    user_2.created_at = datetime.utcnow() - timedelta(days=1)
 | 
			
		||||
    user_3.created_at = datetime.utcnow() - timedelta(hours=1)
 | 
			
		||||
    user_1_admin.created_at = datetime.utcnow()
 | 
			
		||||
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=created_at&order=desc',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'admin' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 'sam' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 'toto' in data['data']['users'][2]['username']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_admin_rights(
 | 
			
		||||
    app, user_2, user_1_admin, user_3
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=admin',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'toto' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 'sam' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 'admin' in data['data']['users'][2]['username']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_admin_rights_ascending(
 | 
			
		||||
    app, user_2, user_1_admin, user_3
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=admin&order=asc',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'toto' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 'sam' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 'admin' in data['data']['users'][2]['username']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_admin_rights_descending(
 | 
			
		||||
    app, user_2, user_3, 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.get(
 | 
			
		||||
        '/api/users?order_by=admin&order=desc',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'admin' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 'toto' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 'sam' in data['data']['users'][2]['username']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_activities_count(
 | 
			
		||||
    app, user_1, user_2, user_3, sport_1_cycling, activity_cycling_user_2,
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=activities_count',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'test' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 0 == data['data']['users'][0]['nb_activities']
 | 
			
		||||
    assert 'sam' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 0 == data['data']['users'][1]['nb_activities']
 | 
			
		||||
    assert 'toto' in data['data']['users'][2]['username']
 | 
			
		||||
    assert 1 == data['data']['users'][2]['nb_activities']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_activities_count_ascending(
 | 
			
		||||
    app, user_1, user_2, user_3, sport_1_cycling, activity_cycling_user_2,
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=activities_count&order=asc',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'test' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 0 == data['data']['users'][0]['nb_activities']
 | 
			
		||||
    assert 'sam' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 0 == data['data']['users'][1]['nb_activities']
 | 
			
		||||
    assert 'toto' in data['data']['users'][2]['username']
 | 
			
		||||
    assert 1 == data['data']['users'][2]['nb_activities']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_ordered_by_activities_count_descending(
 | 
			
		||||
    app, user_1, user_2, user_3, sport_1_cycling, activity_cycling_user_2,
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=activities_count&order=desc',
 | 
			
		||||
        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']['users']) == 3
 | 
			
		||||
    assert 'toto' in data['data']['users'][0]['username']
 | 
			
		||||
    assert 1 == data['data']['users'][0]['nb_activities']
 | 
			
		||||
    assert 'test' in data['data']['users'][1]['username']
 | 
			
		||||
    assert 0 == data['data']['users'][1]['nb_activities']
 | 
			
		||||
    assert 'sam' in data['data']['users'][2]['username']
 | 
			
		||||
    assert 0 == data['data']['users'][2]['nb_activities']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_gets_users_list_filtering_on_username(app, user_1, user_2, user_3):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?q=toto',
 | 
			
		||||
        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']['users']) == 1
 | 
			
		||||
    assert 'toto' in data['data']['users'][0]['username']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 1,
 | 
			
		||||
        'total': 1,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_returns_empty_users_list_filtering_on_username(
 | 
			
		||||
    app, user_1, user_2, user_3
 | 
			
		||||
):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?q=not_existing',
 | 
			
		||||
        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']['users']) == 0
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': False,
 | 
			
		||||
        'page': 1,
 | 
			
		||||
        'pages': 0,
 | 
			
		||||
        'total': 0,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_it_users_list_with_complex_query(app, user_1, user_2, user_3):
 | 
			
		||||
    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.get(
 | 
			
		||||
        '/api/users?order_by=username&order=desc&page=2&per_page=2',
 | 
			
		||||
        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']['users']) == 1
 | 
			
		||||
    assert 'sam' in data['data']['users'][0]['username']
 | 
			
		||||
    assert data['pagination'] == {
 | 
			
		||||
        'has_next': False,
 | 
			
		||||
        'has_prev': True,
 | 
			
		||||
        'page': 2,
 | 
			
		||||
        'pages': 2,
 | 
			
		||||
        'total': 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_encode_auth_token(app, user_1):
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@ import jwt
 | 
			
		||||
from fittrackee_api import bcrypt, db
 | 
			
		||||
from flask import current_app
 | 
			
		||||
from sqlalchemy import func
 | 
			
		||||
from sqlalchemy.ext.hybrid import hybrid_property
 | 
			
		||||
from sqlalchemy.sql.expression import select
 | 
			
		||||
 | 
			
		||||
from ..activities.models import Activity
 | 
			
		||||
 | 
			
		||||
@@ -88,13 +90,22 @@ class User(db.Model):
 | 
			
		||||
        except jwt.InvalidTokenError:
 | 
			
		||||
            return 'Invalid token. Please log in again.'
 | 
			
		||||
 | 
			
		||||
    @hybrid_property
 | 
			
		||||
    def activities_count(self):
 | 
			
		||||
        return Activity.query.filter(Activity.user_id == self.id).count()
 | 
			
		||||
 | 
			
		||||
    @activities_count.expression
 | 
			
		||||
    def activities_count(self):
 | 
			
		||||
        return (
 | 
			
		||||
            select([func.count(Activity.id)])
 | 
			
		||||
            .where(Activity.user_id == self.id)
 | 
			
		||||
            .label("activities_count")
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def serialize(self):
 | 
			
		||||
        nb_activity = Activity.query.filter(
 | 
			
		||||
            Activity.user_id == self.id
 | 
			
		||||
        ).count()
 | 
			
		||||
        sports = []
 | 
			
		||||
        total = (None, None)
 | 
			
		||||
        if nb_activity > 0:
 | 
			
		||||
        if self.activities_count > 0:
 | 
			
		||||
            sports = (
 | 
			
		||||
                db.session.query(Activity.sport_id)
 | 
			
		||||
                .filter(Activity.user_id == self.id)
 | 
			
		||||
@@ -123,7 +134,7 @@ class User(db.Model):
 | 
			
		||||
            'timezone': self.timezone,
 | 
			
		||||
            'weekm': self.weekm,
 | 
			
		||||
            'language': self.language,
 | 
			
		||||
            'nb_activities': nb_activity,
 | 
			
		||||
            'nb_activities': self.activities_count,
 | 
			
		||||
            'nb_sports': len(sports),
 | 
			
		||||
            'sports_list': [
 | 
			
		||||
                sport for sportslist in sports for sport in sportslist
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,8 @@ from .utils import authenticate, authenticate_as_admin
 | 
			
		||||
 | 
			
		||||
users_blueprint = Blueprint('users', __name__)
 | 
			
		||||
 | 
			
		||||
USER_PER_PAGE = 10
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@users_blueprint.route('/users', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
@@ -20,9 +22,18 @@ def get_users(auth_user_id):
 | 
			
		||||
 | 
			
		||||
    **Example request**:
 | 
			
		||||
 | 
			
		||||
    - without parameters
 | 
			
		||||
 | 
			
		||||
    .. sourcecode:: http
 | 
			
		||||
 | 
			
		||||
      GET /api/users HTTP/1.1
 | 
			
		||||
      GET /api/users/ HTTP/1.1
 | 
			
		||||
      Content-Type: application/json
 | 
			
		||||
 | 
			
		||||
    - with some query parameters
 | 
			
		||||
 | 
			
		||||
    .. sourcecode:: http
 | 
			
		||||
 | 
			
		||||
      GET /api/users?order_by=activities_count&par_page=5  HTTP/1.1
 | 
			
		||||
      Content-Type: application/json
 | 
			
		||||
 | 
			
		||||
    **Example response**:
 | 
			
		||||
@@ -84,6 +95,13 @@ def get_users(auth_user_id):
 | 
			
		||||
 | 
			
		||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
			
		||||
 | 
			
		||||
    :query integer page: page if using pagination (default: 1)
 | 
			
		||||
    :query integer per_page: number of users per page (default: 10, max: 50)
 | 
			
		||||
    :query string q: query on user name
 | 
			
		||||
    :query string order_by: sorting criteria (``username``, ``created_at``,
 | 
			
		||||
                            ``activities_count``, ``admin``)
 | 
			
		||||
    :query string order: sorting order (default: ``asc``)
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
 | 
			
		||||
    :statuscode 200: success
 | 
			
		||||
@@ -93,10 +111,61 @@ def get_users(auth_user_id):
 | 
			
		||||
        - Invalid token. Please log in again.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    users = User.query.all()
 | 
			
		||||
    params = request.args.copy()
 | 
			
		||||
    page = 1 if 'page' not in params.keys() else int(params.get('page'))
 | 
			
		||||
    per_page = (
 | 
			
		||||
        int(params.get('per_page'))
 | 
			
		||||
        if params.get('per_page')
 | 
			
		||||
        else USER_PER_PAGE
 | 
			
		||||
    )
 | 
			
		||||
    if per_page > 50:
 | 
			
		||||
        per_page = 50
 | 
			
		||||
    order_by = params.get('order_by')
 | 
			
		||||
    order = params.get('order', 'asc')
 | 
			
		||||
    query = params.get('q')
 | 
			
		||||
    users_pagination = (
 | 
			
		||||
        User.query.filter(
 | 
			
		||||
            User.username.like('%' + query + '%') if query else True,
 | 
			
		||||
        )
 | 
			
		||||
        .order_by(
 | 
			
		||||
            User.activities_count.asc()
 | 
			
		||||
            if order_by == 'activities_count' and order == 'asc'
 | 
			
		||||
            else True,
 | 
			
		||||
            User.activities_count.desc()
 | 
			
		||||
            if order_by == 'activities_count' and order == 'desc'
 | 
			
		||||
            else True,
 | 
			
		||||
            User.username.asc()
 | 
			
		||||
            if order_by == 'username' and order == 'asc'
 | 
			
		||||
            else True,
 | 
			
		||||
            User.username.desc()
 | 
			
		||||
            if order_by == 'username' and order == 'desc'
 | 
			
		||||
            else True,
 | 
			
		||||
            User.created_at.asc()
 | 
			
		||||
            if order_by == 'created_at' and order == 'asc'
 | 
			
		||||
            else True,
 | 
			
		||||
            User.created_at.desc()
 | 
			
		||||
            if order_by == 'created_at' and order == 'desc'
 | 
			
		||||
            else True,
 | 
			
		||||
            User.admin.asc()
 | 
			
		||||
            if order_by == 'admin' and order == 'asc'
 | 
			
		||||
            else True,
 | 
			
		||||
            User.admin.desc()
 | 
			
		||||
            if order_by == 'admin' and order == 'desc'
 | 
			
		||||
            else True,
 | 
			
		||||
        )
 | 
			
		||||
        .paginate(page, per_page, False)
 | 
			
		||||
    )
 | 
			
		||||
    users = users_pagination.items
 | 
			
		||||
    response_object = {
 | 
			
		||||
        'status': 'success',
 | 
			
		||||
        'data': {'users': [user.serialize() for user in users]},
 | 
			
		||||
        'pagination': {
 | 
			
		||||
            'has_next': users_pagination.has_next,
 | 
			
		||||
            'has_prev': users_pagination.has_prev,
 | 
			
		||||
            'page': users_pagination.page,
 | 
			
		||||
            'pages': users_pagination.pages,
 | 
			
		||||
            'total': users_pagination.total,
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
    return jsonify(response_object), 200
 | 
			
		||||
 | 
			
		||||
@@ -227,6 +296,7 @@ def get_picture(user_name):
 | 
			
		||||
def update_user(auth_user_id, user_name):
 | 
			
		||||
    """
 | 
			
		||||
    Update user to add admin rights
 | 
			
		||||
 | 
			
		||||
    Only user with admin rights can modify another user
 | 
			
		||||
 | 
			
		||||
    **Example request**:
 | 
			
		||||
@@ -321,12 +391,14 @@ def update_user(auth_user_id, user_name):
 | 
			
		||||
 | 
			
		||||
@users_blueprint.route('/users/<user_name>', methods=['DELETE'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def delete_activity(auth_user_id, user_name):
 | 
			
		||||
def delete_user(auth_user_id, user_name):
 | 
			
		||||
    """
 | 
			
		||||
    Delete a user account
 | 
			
		||||
    - a user can only delete his own account
 | 
			
		||||
    - an admin can delete all accounts except his account if he's the only
 | 
			
		||||
      one admin
 | 
			
		||||
 | 
			
		||||
    A user can only delete his own account
 | 
			
		||||
 | 
			
		||||
    An admin can delete all accounts except his account if he's the only
 | 
			
		||||
    one admin
 | 
			
		||||
 | 
			
		||||
    **Example request**:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,12 @@ export const setData = (target, data) => ({
 | 
			
		||||
  data,
 | 
			
		||||
  target,
 | 
			
		||||
})
 | 
			
		||||
export const setPaginatedData = (target, data, pagination) => ({
 | 
			
		||||
  type: 'SET_PAGINATED_DATA',
 | 
			
		||||
  data,
 | 
			
		||||
  pagination,
 | 
			
		||||
  target,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export const setError = message => ({
 | 
			
		||||
  type: 'SET_ERROR',
 | 
			
		||||
@@ -50,6 +56,9 @@ export const getOrUpdateData = (
 | 
			
		||||
    .then(ret => {
 | 
			
		||||
      if (ret.status === 'success') {
 | 
			
		||||
        if (canDispatch) {
 | 
			
		||||
          if (target === 'users' && action === 'getData') {
 | 
			
		||||
            return dispatch(setPaginatedData(target, ret.data, ret.pagination))
 | 
			
		||||
          }
 | 
			
		||||
          dispatch(setData(target, ret.data))
 | 
			
		||||
        } else if (action === 'updateData' && target === 'sports') {
 | 
			
		||||
          dispatch(updateSportsData(ret.data.sports[0]))
 | 
			
		||||
 
 | 
			
		||||
@@ -5,17 +5,69 @@ import { Helmet } from 'react-helmet'
 | 
			
		||||
import { Link } from 'react-router-dom'
 | 
			
		||||
 | 
			
		||||
import Message from '../Common/Message'
 | 
			
		||||
import Pagination from '../Common/Pagination'
 | 
			
		||||
import { history } from '../../index'
 | 
			
		||||
import { getOrUpdateData } from '../../actions'
 | 
			
		||||
import { apiUrl } from '../../utils'
 | 
			
		||||
import {
 | 
			
		||||
  apiUrl,
 | 
			
		||||
  formatUrl,
 | 
			
		||||
  sortOrders,
 | 
			
		||||
  translateValues,
 | 
			
		||||
  userFilters,
 | 
			
		||||
} from '../../utils'
 | 
			
		||||
 | 
			
		||||
class AdminUsers extends React.Component {
 | 
			
		||||
  constructor(props, context) {
 | 
			
		||||
    super(props, context)
 | 
			
		||||
    this.state = {
 | 
			
		||||
      page: null,
 | 
			
		||||
      per_page: null,
 | 
			
		||||
      order_by: 'created_at',
 | 
			
		||||
      order: 'asc',
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidMount() {
 | 
			
		||||
    this.props.loadUsers()
 | 
			
		||||
    this.initState()
 | 
			
		||||
    this.props.loadUsers(this.props.location.query)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidUpdate(prevProps) {
 | 
			
		||||
    if (prevProps.location.query !== this.props.location.query) {
 | 
			
		||||
      this.props.loadUsers(this.props.location.query)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  initState() {
 | 
			
		||||
    const { query } = this.props.location
 | 
			
		||||
    this.setState({
 | 
			
		||||
      page: query.page,
 | 
			
		||||
      per_page: query.per_page,
 | 
			
		||||
      order_by: query.order_by ? query.order_by : 'created_at',
 | 
			
		||||
      order: query.order ? query.order : 'asc',
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updatePage(key, value) {
 | 
			
		||||
    const query = Object.assign({}, this.state)
 | 
			
		||||
    query[key] = value
 | 
			
		||||
    this.setState(query)
 | 
			
		||||
    const url = formatUrl(this.props.location.pathname, query)
 | 
			
		||||
    history.push(url)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { message, t, updateUser, authUser, users } = this.props
 | 
			
		||||
    const {
 | 
			
		||||
      authUser,
 | 
			
		||||
      location,
 | 
			
		||||
      message,
 | 
			
		||||
      t,
 | 
			
		||||
      pagination,
 | 
			
		||||
      updateUser,
 | 
			
		||||
      users,
 | 
			
		||||
    } = this.props
 | 
			
		||||
    const translatedFilters = translateValues(t, userFilters)
 | 
			
		||||
    const translatedSortOrders = translateValues(t, sortOrders)
 | 
			
		||||
    return (
 | 
			
		||||
      <div>
 | 
			
		||||
        <Helmet>
 | 
			
		||||
@@ -24,89 +76,143 @@ class AdminUsers extends React.Component {
 | 
			
		||||
        {message && <Message message={message} t={t} />}
 | 
			
		||||
        <div className="container">
 | 
			
		||||
          <div className="row">
 | 
			
		||||
            <div className="col card">
 | 
			
		||||
              <div className="card-body">
 | 
			
		||||
                <table className="table table-borderless">
 | 
			
		||||
                  <thead>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                      <th>#</th>
 | 
			
		||||
                      <th>{t('user:Username')}</th>
 | 
			
		||||
                      <th>{t('user:Email')}</th>
 | 
			
		||||
                      <th>{t('user:Registration Date')}</th>
 | 
			
		||||
                      <th>{t('activities:Activities')}</th>
 | 
			
		||||
                      <th>{t('user:Admin')}</th>
 | 
			
		||||
                      <th>{t('administration:Actions')}</th>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                  </thead>
 | 
			
		||||
                  <tbody>
 | 
			
		||||
                    {users.map(user => (
 | 
			
		||||
                      <tr key={user.username}>
 | 
			
		||||
                        <td>
 | 
			
		||||
                          {user.picture === true && (
 | 
			
		||||
                            <img
 | 
			
		||||
                              alt="Avatar"
 | 
			
		||||
                              src={`${apiUrl}users/${
 | 
			
		||||
                                user.username
 | 
			
		||||
                              }/picture?${Date.now()}`}
 | 
			
		||||
                              className="img-fluid App-nav-profile-img"
 | 
			
		||||
                            />
 | 
			
		||||
                          )}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                          <Link to={`/users/${user.username}`}>
 | 
			
		||||
                            {user.username}
 | 
			
		||||
                          </Link>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>{user.email}</td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                          {format(
 | 
			
		||||
                            new Date(user.created_at),
 | 
			
		||||
                            'dd/MM/yyyy HH:mm'
 | 
			
		||||
                          )}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>{user.nb_activities}</td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                          {user.admin ? (
 | 
			
		||||
                            <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>
 | 
			
		||||
                          <input
 | 
			
		||||
                            type="submit"
 | 
			
		||||
                            className={`btn btn-${
 | 
			
		||||
                              user.admin ? 'dark' : 'primary'
 | 
			
		||||
                            } btn-sm`}
 | 
			
		||||
                            disabled={user.username === authUser.username}
 | 
			
		||||
                            value={
 | 
			
		||||
                              user.admin
 | 
			
		||||
                                ? t('administration:Remove admin rights')
 | 
			
		||||
                                : t('administration:Add admin rights')
 | 
			
		||||
                            }
 | 
			
		||||
                            onClick={() =>
 | 
			
		||||
                              updateUser(user.username, !user.admin)
 | 
			
		||||
                            }
 | 
			
		||||
                          />
 | 
			
		||||
                        </td>
 | 
			
		||||
            <div className="col">
 | 
			
		||||
              <div className="card">
 | 
			
		||||
                <div className="card-header">{t('administration:Users')}</div>
 | 
			
		||||
                <div className="card-body">
 | 
			
		||||
                  <div className="row user-filters">
 | 
			
		||||
                    <div className="col-lg-4 col-md-6 col-sm-12">
 | 
			
		||||
                      <label htmlFor="order_by">
 | 
			
		||||
                        {t('common:Sort by')}:{' '}
 | 
			
		||||
                        <select
 | 
			
		||||
                          id="order_by"
 | 
			
		||||
                          name="order_by"
 | 
			
		||||
                          value={this.state.order_by}
 | 
			
		||||
                          onChange={e =>
 | 
			
		||||
                            this.updatePage('order_by', e.target.value)
 | 
			
		||||
                          }
 | 
			
		||||
                        >
 | 
			
		||||
                          {translatedFilters.map(filter => (
 | 
			
		||||
                            <option key={filter.key} value={filter.key}>
 | 
			
		||||
                              {filter.label}
 | 
			
		||||
                            </option>
 | 
			
		||||
                          ))}
 | 
			
		||||
                        </select>{' '}
 | 
			
		||||
                      </label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div className="col-lg-4 col-md-6 col-sm-12">
 | 
			
		||||
                      <label htmlFor="sort">
 | 
			
		||||
                        {t('common:Sort')}:{' '}
 | 
			
		||||
                        <select
 | 
			
		||||
                          id="sort"
 | 
			
		||||
                          name="sort"
 | 
			
		||||
                          value={this.state.order}
 | 
			
		||||
                          onChange={e =>
 | 
			
		||||
                            this.updatePage('order', e.target.value)
 | 
			
		||||
                          }
 | 
			
		||||
                        >
 | 
			
		||||
                          {translatedSortOrders.map(sort => (
 | 
			
		||||
                            <option key={sort.key} value={sort.key}>
 | 
			
		||||
                              {sort.label}
 | 
			
		||||
                            </option>
 | 
			
		||||
                          ))}
 | 
			
		||||
                        </select>{' '}
 | 
			
		||||
                      </label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <table className="table table-borderless">
 | 
			
		||||
                    <thead>
 | 
			
		||||
                      <tr>
 | 
			
		||||
                        <th>#</th>
 | 
			
		||||
                        <th>{t('user:Username')}</th>
 | 
			
		||||
                        <th>{t('user:Email')}</th>
 | 
			
		||||
                        <th>{t('user:Registration Date')}</th>
 | 
			
		||||
                        <th>{t('activities:Activities')}</th>
 | 
			
		||||
                        <th>{t('user:Admin')}</th>
 | 
			
		||||
                        <th>{t('administration:Actions')}</th>
 | 
			
		||||
                      </tr>
 | 
			
		||||
                    ))}
 | 
			
		||||
                  </tbody>
 | 
			
		||||
                </table>
 | 
			
		||||
                <input
 | 
			
		||||
                  type="submit"
 | 
			
		||||
                  className="btn btn-secondary"
 | 
			
		||||
                  onClick={() => history.push('/admin/')}
 | 
			
		||||
                  value={t('common:Back')}
 | 
			
		||||
                />
 | 
			
		||||
                    </thead>
 | 
			
		||||
                    <tbody>
 | 
			
		||||
                      {users.map(user => (
 | 
			
		||||
                        <tr key={user.username}>
 | 
			
		||||
                          <td>
 | 
			
		||||
                            {user.picture === true ? (
 | 
			
		||||
                              <img
 | 
			
		||||
                                alt="Avatar"
 | 
			
		||||
                                src={`${apiUrl}users/${
 | 
			
		||||
                                  user.username
 | 
			
		||||
                                }/picture?${Date.now()}`}
 | 
			
		||||
                                className="img-fluid App-nav-profile-img"
 | 
			
		||||
                              />
 | 
			
		||||
                            ) : (
 | 
			
		||||
                              <i
 | 
			
		||||
                                className="fa fa-user-circle-o fa-2x no-picture"
 | 
			
		||||
                                aria-hidden="true"
 | 
			
		||||
                              />
 | 
			
		||||
                            )}
 | 
			
		||||
                          </td>
 | 
			
		||||
                          <td>
 | 
			
		||||
                            <Link to={`/users/${user.username}`}>
 | 
			
		||||
                              {user.username}
 | 
			
		||||
                            </Link>
 | 
			
		||||
                          </td>
 | 
			
		||||
                          <td>{user.email}</td>
 | 
			
		||||
                          <td>
 | 
			
		||||
                            {format(
 | 
			
		||||
                              new Date(user.created_at),
 | 
			
		||||
                              'dd/MM/yyyy HH:mm'
 | 
			
		||||
                            )}
 | 
			
		||||
                          </td>
 | 
			
		||||
                          <td>{user.nb_activities}</td>
 | 
			
		||||
                          <td>
 | 
			
		||||
                            {user.admin ? (
 | 
			
		||||
                              <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>
 | 
			
		||||
                            <input
 | 
			
		||||
                              type="submit"
 | 
			
		||||
                              className={`btn btn-${
 | 
			
		||||
                                user.admin ? 'dark' : 'primary'
 | 
			
		||||
                              } btn-sm`}
 | 
			
		||||
                              disabled={user.username === authUser.username}
 | 
			
		||||
                              value={
 | 
			
		||||
                                user.admin
 | 
			
		||||
                                  ? t('administration:Remove admin rights')
 | 
			
		||||
                                  : t('administration:Add admin rights')
 | 
			
		||||
                              }
 | 
			
		||||
                              onClick={() =>
 | 
			
		||||
                                updateUser(user.username, !user.admin)
 | 
			
		||||
                              }
 | 
			
		||||
                            />
 | 
			
		||||
                          </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </tbody>
 | 
			
		||||
                  </table>
 | 
			
		||||
                  <Pagination
 | 
			
		||||
                    pagination={pagination}
 | 
			
		||||
                    pathname={location.pathname}
 | 
			
		||||
                    query={this.state}
 | 
			
		||||
                    t={t}
 | 
			
		||||
                  />
 | 
			
		||||
                  <input
 | 
			
		||||
                    type="submit"
 | 
			
		||||
                    className="btn btn-secondary"
 | 
			
		||||
                    onClick={() => history.push('/admin/')}
 | 
			
		||||
                    value={t('common:Back')}
 | 
			
		||||
                  />
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
@@ -118,13 +224,15 @@ class AdminUsers extends React.Component {
 | 
			
		||||
 | 
			
		||||
export default connect(
 | 
			
		||||
  state => ({
 | 
			
		||||
    message: state.message,
 | 
			
		||||
    authUser: state.user,
 | 
			
		||||
    location: state.router.location,
 | 
			
		||||
    message: state.message,
 | 
			
		||||
    pagination: state.users.pagination,
 | 
			
		||||
    users: state.users.data,
 | 
			
		||||
  }),
 | 
			
		||||
  dispatch => ({
 | 
			
		||||
    loadUsers: () => {
 | 
			
		||||
      dispatch(getOrUpdateData('getData', 'users'))
 | 
			
		||||
    loadUsers: query => {
 | 
			
		||||
      dispatch(getOrUpdateData('getData', 'users', query))
 | 
			
		||||
    },
 | 
			
		||||
    updateUser: (userName, isAdmin) => {
 | 
			
		||||
      const data = { username: userName, admin: isAdmin }
 | 
			
		||||
 
 | 
			
		||||
@@ -36,8 +36,8 @@ body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.App-nav-profile-img {
 | 
			
		||||
  max-width: 35px;
 | 
			
		||||
  max-height: 35px;
 | 
			
		||||
  max-width: 32px;
 | 
			
		||||
  max-height: 32px;
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -427,6 +427,10 @@ label {
 | 
			
		||||
  padding-right: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.no-picture {
 | 
			
		||||
  color: #40578a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.page-title {
 | 
			
		||||
  font-size: 2em;
 | 
			
		||||
  margin: 1em;
 | 
			
		||||
@@ -518,6 +522,11 @@ label {
 | 
			
		||||
  color: black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-filters {
 | 
			
		||||
  font-size: 0.9em;
 | 
			
		||||
  margin-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.weather-img {
 | 
			
		||||
  max-width: 35px;
 | 
			
		||||
  max-height: 35px;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										72
									
								
								fittrackee_client/src/components/Common/Pagination.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								fittrackee_client/src/components/Common/Pagination.jsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { Link } from 'react-router-dom'
 | 
			
		||||
 | 
			
		||||
import { formatUrl, rangePagination } from '../../utils'
 | 
			
		||||
 | 
			
		||||
export default class Pagination extends React.PureComponent {
 | 
			
		||||
  getUrl(value) {
 | 
			
		||||
    const { query, pathname } = this.props
 | 
			
		||||
    const newQuery = Object.assign({}, query)
 | 
			
		||||
    let page = query.page ? +query.page : 1
 | 
			
		||||
    switch (value) {
 | 
			
		||||
      case 'prev':
 | 
			
		||||
        page -= 1
 | 
			
		||||
        break
 | 
			
		||||
      case 'next':
 | 
			
		||||
        page += 1
 | 
			
		||||
        break
 | 
			
		||||
      default:
 | 
			
		||||
        page = +value
 | 
			
		||||
    }
 | 
			
		||||
    newQuery.page = page
 | 
			
		||||
    return formatUrl(pathname, newQuery)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { pagination, t } = this.props
 | 
			
		||||
    return (
 | 
			
		||||
      <>
 | 
			
		||||
        {pagination && Object.keys(pagination).length > 0 && (
 | 
			
		||||
          <nav aria-label="Page navigation example">
 | 
			
		||||
            <ul className="pagination justify-content-center">
 | 
			
		||||
              <li
 | 
			
		||||
                className={`page-item ${pagination.has_prev ? '' : 'disabled'}`}
 | 
			
		||||
              >
 | 
			
		||||
                <Link
 | 
			
		||||
                  className="page-link"
 | 
			
		||||
                  to={this.getUrl('prev')}
 | 
			
		||||
                  aria-disabled={!pagination.has_prev}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('common:Previous')}
 | 
			
		||||
                </Link>
 | 
			
		||||
              </li>
 | 
			
		||||
              {rangePagination(pagination.pages).map(page => (
 | 
			
		||||
                <li
 | 
			
		||||
                  key={page}
 | 
			
		||||
                  className={`page-item ${
 | 
			
		||||
                    page === pagination.page ? 'active' : ''
 | 
			
		||||
                  }`}
 | 
			
		||||
                >
 | 
			
		||||
                  <Link className="page-link" to={this.getUrl(page)}>
 | 
			
		||||
                    {page}
 | 
			
		||||
                  </Link>
 | 
			
		||||
                </li>
 | 
			
		||||
              ))}
 | 
			
		||||
              <li
 | 
			
		||||
                className={`page-item ${pagination.has_next ? '' : 'disabled'}`}
 | 
			
		||||
              >
 | 
			
		||||
                <Link
 | 
			
		||||
                  className="page-link"
 | 
			
		||||
                  to={this.getUrl('next')}
 | 
			
		||||
                  aria-disabled={!pagination.has_next}
 | 
			
		||||
                >
 | 
			
		||||
                  {t('common:Next')}
 | 
			
		||||
                </Link>
 | 
			
		||||
              </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
          </nav>
 | 
			
		||||
        )}
 | 
			
		||||
      </>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +1,8 @@
 | 
			
		||||
import { createApiRequest } from '../utils'
 | 
			
		||||
import { createApiRequest, formatUrl } from '../utils'
 | 
			
		||||
 | 
			
		||||
export default class FitTrackeeApi {
 | 
			
		||||
  static getData(target, data = {}) {
 | 
			
		||||
    let url = target
 | 
			
		||||
    if (data.id || (target === 'users' && data.username)) {
 | 
			
		||||
      url = `${url}/${data.username ? data.username : data.id}`
 | 
			
		||||
    } else if (Object.keys(data).length > 0) {
 | 
			
		||||
      url += '?'
 | 
			
		||||
      Object.keys(data).map(key => (url += `&${key}=${data[key]}`))
 | 
			
		||||
    }
 | 
			
		||||
    const url = formatUrl(target, data)
 | 
			
		||||
    const params = {
 | 
			
		||||
      url: url,
 | 
			
		||||
      method: 'GET',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,37 @@
 | 
			
		||||
{
 | 
			
		||||
  "activities count": "activities count",
 | 
			
		||||
  "Add workout": "Add workout",
 | 
			
		||||
  "admin rights": "admin rights",
 | 
			
		||||
  "ascending": "ascending",
 | 
			
		||||
  "Back": "Back",
 | 
			
		||||
  "Cancel": "Cancel",
 | 
			
		||||
  "Confirmation": "Confirmation",
 | 
			
		||||
  "Dashboard": "Dashboard",
 | 
			
		||||
  "descending": "descending",
 | 
			
		||||
  "Edit": "Edit",
 | 
			
		||||
  "day": "day",
 | 
			
		||||
  "days": "days",
 | 
			
		||||
  "Login": "Login",
 | 
			
		||||
  "Logout": "Logout",
 | 
			
		||||
  "Next": "Next",
 | 
			
		||||
  "No": "No",
 | 
			
		||||
  "no": "no",
 | 
			
		||||
  "No records.": "No records.",
 | 
			
		||||
  "No workouts.": "No workouts.",
 | 
			
		||||
  "Page not found": "Page not found",
 | 
			
		||||
  "Previous": "Prev",
 | 
			
		||||
  "Register": "Register",
 | 
			
		||||
  "Statistics": "Statistics",
 | 
			
		||||
  "registration date": "registration date",
 | 
			
		||||
  "Sort": "Sort",
 | 
			
		||||
  "Sort by": "Sort by",
 | 
			
		||||
  "Sport": "Sport",
 | 
			
		||||
  "sport": "sport",
 | 
			
		||||
  "Sports": "Sports",
 | 
			
		||||
  "sports": "sports",
 | 
			
		||||
  "Statistics": "Statistics",
 | 
			
		||||
  "Submit": "Submit",
 | 
			
		||||
  "to": "to",
 | 
			
		||||
  "user name": "user name",
 | 
			
		||||
  "Workout": "Workout",
 | 
			
		||||
  "Workouts": "Workouts",
 | 
			
		||||
  "workout": "workout",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,37 @@
 | 
			
		||||
{
 | 
			
		||||
  "activities count": "nombre d'activités",
 | 
			
		||||
  "Add workout": "Ajouter une activité",
 | 
			
		||||
  "admin rights": "droits d'admin",
 | 
			
		||||
  "ascending": "ascendant",
 | 
			
		||||
  "Back": "Revenir à la page précédente",
 | 
			
		||||
  "Cancel": "Annuler",
 | 
			
		||||
  "Confirmation": "Confirmation",
 | 
			
		||||
  "Dashboard": "Tableau de Bord",
 | 
			
		||||
  "descending": "descendant",
 | 
			
		||||
  "Edit": "Modifier",
 | 
			
		||||
  "day": "jour",
 | 
			
		||||
  "days": "jours",
 | 
			
		||||
  "Login": "Se connecter",
 | 
			
		||||
  "Logout": "Se déconnecter",
 | 
			
		||||
  "Next": "Page suivante",
 | 
			
		||||
  "No": "Non",
 | 
			
		||||
  "no": "non",
 | 
			
		||||
  "No records.": "Pas de records.",
 | 
			
		||||
  "No workouts.": "Pas d'activités.",
 | 
			
		||||
  "Page not found": "Page introuvable",
 | 
			
		||||
  "Previous": "Page précédente",
 | 
			
		||||
  "Register": "S'inscrire",
 | 
			
		||||
  "Statistics": "Statistiques",
 | 
			
		||||
  "registration date": "date d'inscription",
 | 
			
		||||
  "Sort": "Tri",
 | 
			
		||||
  "Sort by": "Trier par",
 | 
			
		||||
  "Sport": "Sport",
 | 
			
		||||
  "sport": "sport",
 | 
			
		||||
  "Sports": "Sports",
 | 
			
		||||
  "sports": "sports",
 | 
			
		||||
  "Statistics": "Statistiques",
 | 
			
		||||
  "Submit": "Valider",
 | 
			
		||||
  "to": "à",
 | 
			
		||||
  "user name": "utilisateur",
 | 
			
		||||
  "Workout": "Activité",
 | 
			
		||||
  "Workouts": "Activités",
 | 
			
		||||
  "workout": "activité",
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,13 @@ const handleDataAndError = (state, type, action) => {
 | 
			
		||||
      data: action.data[action.target],
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (action.type === 'SET_PAGINATED_DATA') {
 | 
			
		||||
    return {
 | 
			
		||||
      ...state,
 | 
			
		||||
      data: action.data[action.target],
 | 
			
		||||
      pagination: action.pagination,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return state
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,18 @@ export const thunderforestApiKey = `${
 | 
			
		||||
  process.env.REACT_APP_THUNDERFOREST_API_KEY
 | 
			
		||||
}`
 | 
			
		||||
 | 
			
		||||
export const userFilters = [
 | 
			
		||||
  { key: 'activities_count', label: 'activities count' },
 | 
			
		||||
  { key: 'admin', label: 'admin rights' },
 | 
			
		||||
  { key: 'created_at', label: 'registration date' },
 | 
			
		||||
  { key: 'username', label: 'user name' },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export const sortOrders = [
 | 
			
		||||
  { key: 'asc', label: 'ascending' },
 | 
			
		||||
  { key: 'desc', label: 'descending' },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export const isLoggedIn = () => !!window.localStorage.authToken
 | 
			
		||||
 | 
			
		||||
export const generateIds = arr => {
 | 
			
		||||
@@ -81,3 +93,35 @@ export const getDateWithTZ = (date, tz) => {
 | 
			
		||||
 | 
			
		||||
export const capitalize = target =>
 | 
			
		||||
  target.charAt(0).toUpperCase() + target.slice(1)
 | 
			
		||||
 | 
			
		||||
export const rangePagination = pages =>
 | 
			
		||||
  Array.from({ length: pages }, (_, i) => i + 1)
 | 
			
		||||
 | 
			
		||||
const sortValues = (a, b) => {
 | 
			
		||||
  const valueALabel = a.label.toLowerCase()
 | 
			
		||||
  const valueBLabel = b.label.toLowerCase()
 | 
			
		||||
  return valueALabel > valueBLabel ? 1 : valueALabel < valueBLabel ? -1 : 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const translateValues = (t, values, key = 'common') =>
 | 
			
		||||
  values
 | 
			
		||||
    .map(value => ({
 | 
			
		||||
      ...value,
 | 
			
		||||
      label: t(`${key}:${value.label}`),
 | 
			
		||||
    }))
 | 
			
		||||
    .sort(sortValues)
 | 
			
		||||
 | 
			
		||||
export const formatUrl = (pathname, query) => {
 | 
			
		||||
  let url = pathname
 | 
			
		||||
  if (query.id || (pathname === 'users' && query.username)) {
 | 
			
		||||
    url = `${url}/${query.username ? query.username : query.id}`
 | 
			
		||||
  } else if (Object.keys(query).length > 0) {
 | 
			
		||||
    url += '?'
 | 
			
		||||
    Object.keys(query)
 | 
			
		||||
      .filter(key => query[key])
 | 
			
		||||
      .map(
 | 
			
		||||
        (key, index) => (url += `${index === 0 ? '' : '&'}${key}=${query[key]}`)
 | 
			
		||||
      )
 | 
			
		||||
  }
 | 
			
		||||
  return url
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user