API & Client - user can delete his account - fix #17
This commit is contained in:
		@@ -117,6 +117,15 @@ def user_2():
 | 
				
			|||||||
    return user
 | 
					    return user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture()
 | 
				
			||||||
 | 
					def user_2_admin():
 | 
				
			||||||
 | 
					    user = User(username='toto', email='toto@toto.com', password='87654321')
 | 
				
			||||||
 | 
					    user.admin = True
 | 
				
			||||||
 | 
					    db.session.add(user)
 | 
				
			||||||
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					    return user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture()
 | 
					@pytest.fixture()
 | 
				
			||||||
def user_3():
 | 
					def user_3():
 | 
				
			||||||
    user = User(username='sam', email='sam@test.com', password='12345678')
 | 
					    user = User(username='sam', email='sam@test.com', password='12345678')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import json
 | 
					import json
 | 
				
			||||||
 | 
					from io import BytesIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from fittrackee_api.users.models import User
 | 
					from fittrackee_api.users.models import User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -409,3 +410,186 @@ def test_it_returns_error_if_user_can_not_change_admin_rights(
 | 
				
			|||||||
    assert response.status_code == 403
 | 
					    assert response.status_code == 403
 | 
				
			||||||
    assert 'error' in data['status']
 | 
					    assert 'error' in data['status']
 | 
				
			||||||
    assert 'You do not have permissions.' in data['message']
 | 
					    assert 'You do not have permissions.' in data['message']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_user_can_delete_its_own_account(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.delete(
 | 
				
			||||||
 | 
					        '/api/users/test',
 | 
				
			||||||
 | 
					        headers=dict(
 | 
				
			||||||
 | 
					            Authorization='Bearer '
 | 
				
			||||||
 | 
					            + json.loads(resp_login.data.decode())['auth_token']
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert response.status_code == 204
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_user_with_activity_can_delete_its_own_account(
 | 
				
			||||||
 | 
					    app, user_1, sport_1_cycling, gpx_file
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    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',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    client.post(
 | 
				
			||||||
 | 
					        '/api/activities',
 | 
				
			||||||
 | 
					        data=dict(
 | 
				
			||||||
 | 
					            file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
 | 
				
			||||||
 | 
					            data='{"sport_id": 1}',
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        headers=dict(
 | 
				
			||||||
 | 
					            content_type='multipart/form-data',
 | 
				
			||||||
 | 
					            Authorization='Bearer '
 | 
				
			||||||
 | 
					            + json.loads(resp_login.data.decode())['auth_token'],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    response = client.delete(
 | 
				
			||||||
 | 
					        '/api/users/test',
 | 
				
			||||||
 | 
					        headers=dict(
 | 
				
			||||||
 | 
					            Authorization='Bearer '
 | 
				
			||||||
 | 
					            + json.loads(resp_login.data.decode())['auth_token']
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert response.status_code == 204
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_user_with_picture_can_delete_its_own_account(
 | 
				
			||||||
 | 
					    app, user_1, sport_1_cycling, gpx_file
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    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',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    client.post(
 | 
				
			||||||
 | 
					        '/api/auth/picture',
 | 
				
			||||||
 | 
					        data=dict(file=(BytesIO(b'avatar'), 'avatar.png')),
 | 
				
			||||||
 | 
					        headers=dict(
 | 
				
			||||||
 | 
					            content_type='multipart/form-data',
 | 
				
			||||||
 | 
					            authorization='Bearer '
 | 
				
			||||||
 | 
					            + json.loads(resp_login.data.decode())['auth_token'],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    response = client.delete(
 | 
				
			||||||
 | 
					        '/api/users/test',
 | 
				
			||||||
 | 
					        headers=dict(
 | 
				
			||||||
 | 
					            Authorization='Bearer '
 | 
				
			||||||
 | 
					            + json.loads(resp_login.data.decode())['auth_token']
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert response.status_code == 204
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_user_can_not_delete_another_user_account(app, user_1, 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.delete(
 | 
				
			||||||
 | 
					        '/api/users/toto',
 | 
				
			||||||
 | 
					        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_it_returns_error_when_deleting_non_existing_user(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.delete(
 | 
				
			||||||
 | 
					        '/api/users/not_existing',
 | 
				
			||||||
 | 
					        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 'User does not exist.' in data['message']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_admin_can_delete_another_user_account(app, user_1_admin, user_2):
 | 
				
			||||||
 | 
					    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/users/toto',
 | 
				
			||||||
 | 
					        headers=dict(
 | 
				
			||||||
 | 
					            Authorization='Bearer '
 | 
				
			||||||
 | 
					            + json.loads(resp_login.data.decode())['auth_token']
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert response.status_code == 204
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_admin_can_delete_its_own_account(app, user_1_admin, user_2_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/users/admin',
 | 
				
			||||||
 | 
					        headers=dict(
 | 
				
			||||||
 | 
					            Authorization='Bearer '
 | 
				
			||||||
 | 
					            + json.loads(resp_login.data.decode())['auth_token']
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert response.status_code == 204
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_admin_can_not_delete_its_own_account_if_no_other_admin(
 | 
				
			||||||
 | 
					    app, user_1_admin, user_2
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    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/users/admin',
 | 
				
			||||||
 | 
					        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 can not delete your account, no other user has admin rights.'
 | 
				
			||||||
 | 
					        in data['message']
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,12 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from fittrackee_api import appLog, db
 | 
					from fittrackee_api import appLog, db
 | 
				
			||||||
from flask import Blueprint, jsonify, request, send_file
 | 
					from flask import Blueprint, jsonify, request, send_file
 | 
				
			||||||
from sqlalchemy import exc
 | 
					from sqlalchemy import exc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ..activities.utils_files import get_absolute_file_path
 | 
					from ..activities.utils_files import get_absolute_file_path
 | 
				
			||||||
from .models import User
 | 
					from .models import Activity, User
 | 
				
			||||||
from .utils import authenticate, authenticate_as_admin
 | 
					from .utils import authenticate, authenticate_as_admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
users_blueprint = Blueprint('users', __name__)
 | 
					users_blueprint = Blueprint('users', __name__)
 | 
				
			||||||
@@ -270,7 +273,7 @@ def update_user(auth_user_id, user_name):
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
					    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
				
			||||||
    :param integer user_name: user name
 | 
					    :param string user_name: user name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :<json boolean admin: does the user have administrator rights
 | 
					    :<json boolean admin: does the user have administrator rights
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -316,6 +319,111 @@ def update_user(auth_user_id, user_name):
 | 
				
			|||||||
    return jsonify(response_object), code
 | 
					    return jsonify(response_object), code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@users_blueprint.route('/users/<user_name>', methods=['DELETE'])
 | 
				
			||||||
 | 
					@authenticate
 | 
				
			||||||
 | 
					def delete_activity(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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    **Example request**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. sourcecode:: http
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      DELETE /api/users/john_doe HTTP/1.1
 | 
				
			||||||
 | 
					      Content-Type: application/json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    **Example response**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. sourcecode:: http
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      HTTP/1.1 204 NO CONTENT
 | 
				
			||||||
 | 
					      Content-Type: application/json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param integer auth_user_id: authenticate user id (from JSON Web Token)
 | 
				
			||||||
 | 
					    :param string user_name: user name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :statuscode 204: user account deleted
 | 
				
			||||||
 | 
					    :statuscode 401:
 | 
				
			||||||
 | 
					        - Provide a valid auth token.
 | 
				
			||||||
 | 
					        - Signature expired. Please log in again.
 | 
				
			||||||
 | 
					        - Invalid token. Please log in again.
 | 
				
			||||||
 | 
					    :statuscode 403:
 | 
				
			||||||
 | 
					        - You do not have permissions.
 | 
				
			||||||
 | 
					        - You can not delete your account, no other user has admin rights.
 | 
				
			||||||
 | 
					    :statuscode 404:
 | 
				
			||||||
 | 
					        - User does not exist.
 | 
				
			||||||
 | 
					    :statuscode 500: Error. Please try again or contact the administrator.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        auth_user = User.query.filter_by(id=auth_user_id).first()
 | 
				
			||||||
 | 
					        user = User.query.filter_by(username=user_name).first()
 | 
				
			||||||
 | 
					        if user:
 | 
				
			||||||
 | 
					            if user.id != auth_user_id and not auth_user.admin:
 | 
				
			||||||
 | 
					                response_object = {
 | 
				
			||||||
 | 
					                    'status': 'error',
 | 
				
			||||||
 | 
					                    'message': 'You do not have permissions.',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return response_object, 403
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                user.admin is True
 | 
				
			||||||
 | 
					                and User.query.filter_by(admin=True).count() == 1
 | 
				
			||||||
 | 
					            ):
 | 
				
			||||||
 | 
					                response_object = {
 | 
				
			||||||
 | 
					                    'status': 'error',
 | 
				
			||||||
 | 
					                    'message': (
 | 
				
			||||||
 | 
					                        'You can not delete your account, '
 | 
				
			||||||
 | 
					                        'no other user has admin rights.'
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return response_object, 403
 | 
				
			||||||
 | 
					            for activity in Activity.query.filter_by(user_id=user.id).all():
 | 
				
			||||||
 | 
					                db.session.delete(activity)
 | 
				
			||||||
 | 
					                db.session.flush()
 | 
				
			||||||
 | 
					            user_picture = user.picture
 | 
				
			||||||
 | 
					            db.session.delete(user)
 | 
				
			||||||
 | 
					            db.session.commit()
 | 
				
			||||||
 | 
					            if user_picture:
 | 
				
			||||||
 | 
					                picture_path = get_absolute_file_path(user.picture)
 | 
				
			||||||
 | 
					                if os.path.isfile(picture_path):
 | 
				
			||||||
 | 
					                    os.remove(picture_path)
 | 
				
			||||||
 | 
					            shutil.rmtree(
 | 
				
			||||||
 | 
					                get_absolute_file_path(f'activities/{user.id}'),
 | 
				
			||||||
 | 
					                ignore_errors=True,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            shutil.rmtree(
 | 
				
			||||||
 | 
					                get_absolute_file_path(f'pictures/{user.id}'),
 | 
				
			||||||
 | 
					                ignore_errors=True,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            response_object = {'status': 'no content'}
 | 
				
			||||||
 | 
					            code = 204
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            response_object = {
 | 
				
			||||||
 | 
					                'status': 'not found',
 | 
				
			||||||
 | 
					                'message': 'User does not exist.',
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            code = 404
 | 
				
			||||||
 | 
					    except (
 | 
				
			||||||
 | 
					        exc.IntegrityError,
 | 
				
			||||||
 | 
					        exc.OperationalError,
 | 
				
			||||||
 | 
					        ValueError,
 | 
				
			||||||
 | 
					        OSError,
 | 
				
			||||||
 | 
					    ) 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@users_blueprint.route('/ping', methods=['GET'])
 | 
					@users_blueprint.route('/ping', methods=['GET'])
 | 
				
			||||||
def ping_pong():
 | 
					def ping_pong():
 | 
				
			||||||
    """ health check endpoint
 | 
					    """ health check endpoint
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import FitTrackeeGenericApi from '../fitTrackeeApi'
 | 
				
			|||||||
import FitTrackeeApi from '../fitTrackeeApi/auth'
 | 
					import FitTrackeeApi from '../fitTrackeeApi/auth'
 | 
				
			||||||
import { history } from '../index'
 | 
					import { history } from '../index'
 | 
				
			||||||
import { generateIds } from '../utils'
 | 
					import { generateIds } from '../utils'
 | 
				
			||||||
import { getOrUpdateData, updateLanguage } from './index'
 | 
					import { getOrUpdateData, setError, updateLanguage } from './index'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const AuthError = message => ({ type: 'AUTH_ERROR', message })
 | 
					const AuthError = message => ({ type: 'AUTH_ERROR', message })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -133,3 +133,15 @@ export const deletePicture = () => dispatch =>
 | 
				
			|||||||
    .catch(error => {
 | 
					    .catch(error => {
 | 
				
			||||||
      throw error
 | 
					      throw error
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const deleteUser = username => dispatch =>
 | 
				
			||||||
 | 
					  FitTrackeeGenericApi.deleteData('users', username)
 | 
				
			||||||
 | 
					    .then(ret => {
 | 
				
			||||||
 | 
					      if (ret.status === 204) {
 | 
				
			||||||
 | 
					        dispatch(logout())
 | 
				
			||||||
 | 
					        history.push('/')
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        ret.json().then(r => dispatch(setError(`${r.message}`)))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(error => dispatch(setError(`user|${error}`)))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -91,7 +91,7 @@ class ActivityDisplay extends React.Component {
 | 
				
			|||||||
          <div className="container">
 | 
					          <div className="container">
 | 
				
			||||||
            {displayModal && (
 | 
					            {displayModal && (
 | 
				
			||||||
              <CustomModal
 | 
					              <CustomModal
 | 
				
			||||||
                title={t('activities:Confirmation')}
 | 
					                title={t('common:Confirmation')}
 | 
				
			||||||
                text={t(
 | 
					                text={t(
 | 
				
			||||||
                  'activities:Are you sure you want to delete this activity?'
 | 
					                  'activities:Are you sure you want to delete this activity?'
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,15 +6,17 @@ import { connect } from 'react-redux'
 | 
				
			|||||||
import TimezonePicker from 'react-timezone'
 | 
					import TimezonePicker from 'react-timezone'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Message from '../Common/Message'
 | 
					import Message from '../Common/Message'
 | 
				
			||||||
import { handleProfileFormSubmit } from '../../actions/user'
 | 
					import { deleteUser, handleProfileFormSubmit } from '../../actions/user'
 | 
				
			||||||
import { history } from '../../index'
 | 
					import { history } from '../../index'
 | 
				
			||||||
import { languages } from '../NavBar/LanguageDropdown'
 | 
					import { languages } from '../NavBar/LanguageDropdown'
 | 
				
			||||||
 | 
					import CustomModal from '../Common/CustomModal'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProfileEdit extends React.Component {
 | 
					class ProfileEdit extends React.Component {
 | 
				
			||||||
  constructor(props, context) {
 | 
					  constructor(props, context) {
 | 
				
			||||||
    super(props, context)
 | 
					    super(props, context)
 | 
				
			||||||
    this.state = {
 | 
					    this.state = {
 | 
				
			||||||
      formData: {},
 | 
					      formData: {},
 | 
				
			||||||
 | 
					      displayModal: false,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,9 +53,22 @@ class ProfileEdit extends React.Component {
 | 
				
			|||||||
    this.setState(formData)
 | 
					    this.setState(formData)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  displayModal(value) {
 | 
				
			||||||
 | 
					    this.setState(prevState => ({
 | 
				
			||||||
 | 
					      ...prevState,
 | 
				
			||||||
 | 
					      displayModal: value,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render() {
 | 
					  render() {
 | 
				
			||||||
    const { onHandleProfileFormSubmit, message, t, user } = this.props
 | 
					    const {
 | 
				
			||||||
    const { formData } = this.state
 | 
					      message,
 | 
				
			||||||
 | 
					      onDeleteUser,
 | 
				
			||||||
 | 
					      onHandleProfileFormSubmit,
 | 
				
			||||||
 | 
					      t,
 | 
				
			||||||
 | 
					      user,
 | 
				
			||||||
 | 
					    } = this.props
 | 
				
			||||||
 | 
					    const { displayModal, formData } = this.state
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
        <Helmet>
 | 
					        <Helmet>
 | 
				
			||||||
@@ -62,6 +77,20 @@ class ProfileEdit extends React.Component {
 | 
				
			|||||||
        <Message message={message} t={t} />
 | 
					        <Message message={message} t={t} />
 | 
				
			||||||
        {formData.isAuthenticated && (
 | 
					        {formData.isAuthenticated && (
 | 
				
			||||||
          <div className="container">
 | 
					          <div className="container">
 | 
				
			||||||
 | 
					            {displayModal && (
 | 
				
			||||||
 | 
					              <CustomModal
 | 
				
			||||||
 | 
					                title={t('common:Confirmation')}
 | 
				
			||||||
 | 
					                text={t(
 | 
				
			||||||
 | 
					                  'user:Are you sure you want to delete your account? ' +
 | 
				
			||||||
 | 
					                    'All data will be deleted, this cannot be undone.'
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					                confirm={() => {
 | 
				
			||||||
 | 
					                  onDeleteUser(user.username)
 | 
				
			||||||
 | 
					                  this.displayModal(false)
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                close={() => this.displayModal(false)}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
            <h1 className="page-title">{t('user:Profile Edition')}</h1>
 | 
					            <h1 className="page-title">{t('user:Profile Edition')}</h1>
 | 
				
			||||||
            <div className="row">
 | 
					            <div className="row">
 | 
				
			||||||
              <div className="col-md-2" />
 | 
					              <div className="col-md-2" />
 | 
				
			||||||
@@ -242,6 +271,11 @@ class ProfileEdit extends React.Component {
 | 
				
			|||||||
                            className="btn btn-primary btn-lg btn-block"
 | 
					                            className="btn btn-primary btn-lg btn-block"
 | 
				
			||||||
                            value={t('common:Submit')}
 | 
					                            value={t('common:Submit')}
 | 
				
			||||||
                          />
 | 
					                          />
 | 
				
			||||||
 | 
					                          <input
 | 
				
			||||||
 | 
					                            className="btn btn-danger btn-lg btn-block"
 | 
				
			||||||
 | 
					                            onClick={() => this.displayModal(true)}
 | 
				
			||||||
 | 
					                            defaultValue={t('user:Delete my account')}
 | 
				
			||||||
 | 
					                          />
 | 
				
			||||||
                          <input
 | 
					                          <input
 | 
				
			||||||
                            type="submit"
 | 
					                            type="submit"
 | 
				
			||||||
                            className="btn btn-secondary btn-lg btn-block"
 | 
					                            className="btn btn-secondary btn-lg btn-block"
 | 
				
			||||||
@@ -271,6 +305,9 @@ export default withTranslation()(
 | 
				
			|||||||
      user: state.user,
 | 
					      user: state.user,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    dispatch => ({
 | 
					    dispatch => ({
 | 
				
			||||||
 | 
					      onDeleteUser: username => {
 | 
				
			||||||
 | 
					        dispatch(deleteUser(username))
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      onHandleProfileFormSubmit: formData => {
 | 
					      onHandleProfileFormSubmit: formData => {
 | 
				
			||||||
        dispatch(handleProfileFormSubmit(formData))
 | 
					        dispatch(handleProfileFormSubmit(formData))
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,6 @@
 | 
				
			|||||||
  "Ascent": "Ascent",
 | 
					  "Ascent": "Ascent",
 | 
				
			||||||
  "Average speed": "Average speed",
 | 
					  "Average speed": "Average speed",
 | 
				
			||||||
  "Chart": "Chart",
 | 
					  "Chart": "Chart",
 | 
				
			||||||
  "Confirmation": "Confirmation",
 | 
					 | 
				
			||||||
  "data from gpx, without any cleaning": "data from gpx, without any cleaning",
 | 
					  "data from gpx, without any cleaning": "data from gpx, without any cleaning",
 | 
				
			||||||
  "Date": "Date",
 | 
					  "Date": "Date",
 | 
				
			||||||
  "Delete activity": "Delete activity",
 | 
					  "Delete activity": "Delete activity",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
  "Add workout": "Add workout",
 | 
					  "Add workout": "Add workout",
 | 
				
			||||||
  "Back": "Back",
 | 
					  "Back": "Back",
 | 
				
			||||||
  "Cancel": "Cancel",
 | 
					  "Cancel": "Cancel",
 | 
				
			||||||
 | 
					  "Confirmation": "Confirmation",
 | 
				
			||||||
  "Dashboard": "Dashboard",
 | 
					  "Dashboard": "Dashboard",
 | 
				
			||||||
  "Edit": "Edit",
 | 
					  "Edit": "Edit",
 | 
				
			||||||
  "day": "day",
 | 
					  "day": "day",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,5 +30,6 @@
 | 
				
			|||||||
  "statistics": "statistiques",
 | 
					  "statistics": "statistiques",
 | 
				
			||||||
  "User does not exist.": "User does not exist.",
 | 
					  "User does not exist.": "User does not exist.",
 | 
				
			||||||
  "Valid email must be provided.\n": "Valid email must be provided.",
 | 
					  "Valid email must be provided.\n": "Valid email must be provided.",
 | 
				
			||||||
 | 
					  "You can not delete your account, no other user has admin rights.": "You can not delete your account, no other user has admin rights.",
 | 
				
			||||||
  "You do not have permissions.": "You do not have permissions."
 | 
					  "You do not have permissions.": "You do not have permissions."
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "Admin": "Admin",
 | 
					  "Admin": "Admin",
 | 
				
			||||||
 | 
					  "Are you sure you want to delete your account? All data will be deleted, this cannot be undone.": "Are you sure you want to delete your account? All data will be deleted, this cannot be undone.",
 | 
				
			||||||
  "Bio": "Bio",
 | 
					  "Bio": "Bio",
 | 
				
			||||||
  "Birth Date": "Birth Date",
 | 
					  "Birth Date": "Birth Date",
 | 
				
			||||||
 | 
					  "Delete my account": "Delete my account",
 | 
				
			||||||
  "Delete picture": "Delete picture",
 | 
					  "Delete picture": "Delete picture",
 | 
				
			||||||
  "Edit Profile": "Edit Profile",
 | 
					  "Edit Profile": "Edit Profile",
 | 
				
			||||||
  "Email": "Email",
 | 
					  "Email": "Email",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,6 @@
 | 
				
			|||||||
  "Ascent": "Dénivelé positif",
 | 
					  "Ascent": "Dénivelé positif",
 | 
				
			||||||
  "Average speed": "Vitesse moyenne",
 | 
					  "Average speed": "Vitesse moyenne",
 | 
				
			||||||
  "Chart": "Analyse",
 | 
					  "Chart": "Analyse",
 | 
				
			||||||
  "Confirmation": "Confirmation",
 | 
					 | 
				
			||||||
  "data from gpx, without any cleaning": "données issues du fichier gpx, sans correction",
 | 
					  "data from gpx, without any cleaning": "données issues du fichier gpx, sans correction",
 | 
				
			||||||
  "Date": "Date",
 | 
					  "Date": "Date",
 | 
				
			||||||
  "Delete activity": "Supprimer l'activité",
 | 
					  "Delete activity": "Supprimer l'activité",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
  "Add workout": "Ajouter une activité",
 | 
					  "Add workout": "Ajouter une activité",
 | 
				
			||||||
  "Back": "Revenir à la page précédente",
 | 
					  "Back": "Revenir à la page précédente",
 | 
				
			||||||
  "Cancel": "Annuler",
 | 
					  "Cancel": "Annuler",
 | 
				
			||||||
 | 
					  "Confirmation": "Confirmation",
 | 
				
			||||||
  "Dashboard": "Tableau de Bord",
 | 
					  "Dashboard": "Tableau de Bord",
 | 
				
			||||||
  "Edit": "Modifier",
 | 
					  "Edit": "Modifier",
 | 
				
			||||||
  "day": "jour",
 | 
					  "day": "jour",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,5 +30,6 @@
 | 
				
			|||||||
  "statistics": "statistics",
 | 
					  "statistics": "statistics",
 | 
				
			||||||
  "User does not exist.": "L'utilisateur n'existe pas.",
 | 
					  "User does not exist.": "L'utilisateur n'existe pas.",
 | 
				
			||||||
  "Valid email must be provided.\n": "L'email fourni n'est pas valide.",
 | 
					  "Valid email must be provided.\n": "L'email fourni n'est pas valide.",
 | 
				
			||||||
 | 
					  "You can not delete your account, no other user has admin rights.": "Vous ne pouvez pas supprimer votre compte, aucun autre utilisateur n'a des droits d'administration.",
 | 
				
			||||||
  "You do not have permissions.": "Vous n'avez pas les permissions nécessaires."
 | 
					  "You do not have permissions.": "Vous n'avez pas les permissions nécessaires."
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "Admin": "Admin",
 | 
					  "Admin": "Admin",
 | 
				
			||||||
 | 
					  "Are you sure you want to delete your account? All data will be deleted, this cannot be undone.": "Etes-vous sûr de vouloir supprimer votre compte ? Toutes les données seront définitivement effacés.",
 | 
				
			||||||
  "Bio": "Bio",
 | 
					  "Bio": "Bio",
 | 
				
			||||||
  "Birth Date": "Date de naissance",
 | 
					  "Birth Date": "Date de naissance",
 | 
				
			||||||
 | 
					  "Delete my account": "Supprimer mon compte",
 | 
				
			||||||
  "Delete picture": "Supprimer l'image",
 | 
					  "Delete picture": "Supprimer l'image",
 | 
				
			||||||
  "Edit Profile": "Editer le profil",
 | 
					  "Edit Profile": "Editer le profil",
 | 
				
			||||||
  "Email": "Email",
 | 
					  "Email": "Email",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user