API - limit access to users for now
- only auth user can access his preference - only admin can visualize users
This commit is contained in:
parent
6c42b9ffbd
commit
ac6aceadfd
@ -14,7 +14,7 @@ from ..api_test_case import ApiTestCaseMixin
|
|||||||
|
|
||||||
|
|
||||||
class TestGetUser(ApiTestCaseMixin):
|
class TestGetUser(ApiTestCaseMixin):
|
||||||
def test_it_gets_single_user_without_workouts(
|
def test_it_returns_error_if_user_has_no_admin_rights(
|
||||||
self, app: Flask, user_1: User, user_2: User
|
self, app: Flask, user_1: User, user_2: User
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
@ -27,6 +27,21 @@ class TestGetUser(ApiTestCaseMixin):
|
|||||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.assert_403(response)
|
||||||
|
|
||||||
|
def test_it_gets_single_user_without_workouts(
|
||||||
|
self, app: Flask, user_1_admin: User, user_2: User
|
||||||
|
) -> None:
|
||||||
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
|
app, user_1_admin.email
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get(
|
||||||
|
f'/api/users/{user_2.username}',
|
||||||
|
content_type='application/json',
|
||||||
|
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||||
|
)
|
||||||
|
|
||||||
data = json.loads(response.data.decode())
|
data = json.loads(response.data.decode())
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert data['status'] == 'success'
|
assert data['status'] == 'success'
|
||||||
@ -40,11 +55,7 @@ class TestGetUser(ApiTestCaseMixin):
|
|||||||
assert user['last_name'] is None
|
assert user['last_name'] is None
|
||||||
assert user['birth_date'] is None
|
assert user['birth_date'] is None
|
||||||
assert user['bio'] is None
|
assert user['bio'] is None
|
||||||
assert user['imperial_units'] is False
|
|
||||||
assert user['location'] is None
|
assert user['location'] is None
|
||||||
assert user['timezone'] is None
|
|
||||||
assert user['weekm'] is False
|
|
||||||
assert user['language'] is None
|
|
||||||
assert user['nb_sports'] == 0
|
assert user['nb_sports'] == 0
|
||||||
assert user['nb_workouts'] == 0
|
assert user['nb_workouts'] == 0
|
||||||
assert user['records'] == []
|
assert user['records'] == []
|
||||||
@ -56,13 +67,14 @@ class TestGetUser(ApiTestCaseMixin):
|
|||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
user_1: User,
|
user_1: User,
|
||||||
|
user_2_admin: User,
|
||||||
sport_1_cycling: Sport,
|
sport_1_cycling: Sport,
|
||||||
sport_2_running: Sport,
|
sport_2_running: Sport,
|
||||||
workout_cycling_user_1: Workout,
|
workout_cycling_user_1: Workout,
|
||||||
workout_running_user_1: Workout,
|
workout_running_user_1: Workout,
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_2_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -84,11 +96,7 @@ class TestGetUser(ApiTestCaseMixin):
|
|||||||
assert user['last_name'] is None
|
assert user['last_name'] is None
|
||||||
assert user['birth_date'] is None
|
assert user['birth_date'] is None
|
||||||
assert user['bio'] is None
|
assert user['bio'] is None
|
||||||
assert user['imperial_units'] is False
|
|
||||||
assert user['location'] is None
|
assert user['location'] is None
|
||||||
assert user['timezone'] is None
|
|
||||||
assert user['weekm'] is False
|
|
||||||
assert user['language'] is None
|
|
||||||
assert len(user['records']) == 8
|
assert len(user['records']) == 8
|
||||||
assert user['nb_sports'] == 2
|
assert user['nb_sports'] == 2
|
||||||
assert user['nb_workouts'] == 2
|
assert user['nb_workouts'] == 2
|
||||||
@ -97,10 +105,10 @@ class TestGetUser(ApiTestCaseMixin):
|
|||||||
assert user['total_duration'] == '2:40:00'
|
assert user['total_duration'] == '2:40:00'
|
||||||
|
|
||||||
def test_it_returns_error_if_user_does_not_exist(
|
def test_it_returns_error_if_user_does_not_exist(
|
||||||
self, app: Flask, user_1: User
|
self, app: Flask, user_1_admin: User
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -113,8 +121,8 @@ class TestGetUser(ApiTestCaseMixin):
|
|||||||
|
|
||||||
|
|
||||||
class TestGetUsers(ApiTestCaseMixin):
|
class TestGetUsers(ApiTestCaseMixin):
|
||||||
def test_it_get_users_list(
|
def test_it_returns_error_if_user_has_no_admin_rights(
|
||||||
self, app: Flask, user_1: User, user_2: User, user_3: User
|
self, app: Flask, user_1: User, user_2: User
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1.email
|
||||||
@ -125,6 +133,20 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.assert_403(response)
|
||||||
|
|
||||||
|
def test_it_get_users_list(
|
||||||
|
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
|
||||||
|
) -> None:
|
||||||
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
|
app, user_1_admin.email
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get(
|
||||||
|
'/api/users',
|
||||||
|
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||||
|
)
|
||||||
|
|
||||||
data = json.loads(response.data.decode())
|
data = json.loads(response.data.decode())
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'success' in data['status']
|
assert 'success' in data['status']
|
||||||
@ -132,10 +154,10 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
assert 'created_at' in data['data']['users'][0]
|
assert 'created_at' in data['data']['users'][0]
|
||||||
assert 'created_at' in data['data']['users'][1]
|
assert 'created_at' in data['data']['users'][1]
|
||||||
assert 'created_at' in data['data']['users'][2]
|
assert 'created_at' in data['data']['users'][2]
|
||||||
assert 'test' in data['data']['users'][0]['username']
|
assert 'admin' in data['data']['users'][0]['username']
|
||||||
assert 'toto' in data['data']['users'][1]['username']
|
assert 'toto' in data['data']['users'][1]['username']
|
||||||
assert 'sam' in data['data']['users'][2]['username']
|
assert 'sam' in data['data']['users'][2]['username']
|
||||||
assert 'test@test.com' in data['data']['users'][0]['email']
|
assert 'admin@example.com' in data['data']['users'][0]['email']
|
||||||
assert 'toto@toto.com' in data['data']['users'][1]['email']
|
assert 'toto@toto.com' in data['data']['users'][1]['email']
|
||||||
assert 'sam@test.com' in data['data']['users'][2]['email']
|
assert 'sam@test.com' in data['data']['users'][2]['email']
|
||||||
assert data['data']['users'][0]['imperial_units'] is False
|
assert data['data']['users'][0]['imperial_units'] is False
|
||||||
@ -148,20 +170,12 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
assert data['data']['users'][0]['sports_list'] == []
|
assert data['data']['users'][0]['sports_list'] == []
|
||||||
assert data['data']['users'][0]['total_distance'] == 0
|
assert data['data']['users'][0]['total_distance'] == 0
|
||||||
assert data['data']['users'][0]['total_duration'] == '0:00:00'
|
assert data['data']['users'][0]['total_duration'] == '0:00:00'
|
||||||
assert data['data']['users'][1]['imperial_units'] is False
|
|
||||||
assert data['data']['users'][1]['timezone'] is None
|
|
||||||
assert data['data']['users'][1]['weekm'] is False
|
|
||||||
assert data['data']['users'][1]['language'] is None
|
|
||||||
assert data['data']['users'][1]['nb_sports'] == 0
|
assert data['data']['users'][1]['nb_sports'] == 0
|
||||||
assert data['data']['users'][1]['nb_workouts'] == 0
|
assert data['data']['users'][1]['nb_workouts'] == 0
|
||||||
assert data['data']['users'][1]['records'] == []
|
assert data['data']['users'][1]['records'] == []
|
||||||
assert data['data']['users'][1]['sports_list'] == []
|
assert data['data']['users'][1]['sports_list'] == []
|
||||||
assert data['data']['users'][1]['total_distance'] == 0
|
assert data['data']['users'][1]['total_distance'] == 0
|
||||||
assert data['data']['users'][1]['total_duration'] == '0:00:00'
|
assert data['data']['users'][1]['total_duration'] == '0:00:00'
|
||||||
assert data['data']['users'][2]['imperial_units'] is False
|
|
||||||
assert data['data']['users'][2]['timezone'] is None
|
|
||||||
assert data['data']['users'][2]['weekm'] is True
|
|
||||||
assert data['data']['users'][2]['language'] is None
|
|
||||||
assert data['data']['users'][2]['records'] == []
|
assert data['data']['users'][2]['records'] == []
|
||||||
assert data['data']['users'][2]['nb_sports'] == 0
|
assert data['data']['users'][2]['nb_sports'] == 0
|
||||||
assert data['data']['users'][2]['nb_workouts'] == 0
|
assert data['data']['users'][2]['nb_workouts'] == 0
|
||||||
@ -179,7 +193,7 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
def test_it_gets_users_list_with_workouts(
|
def test_it_gets_users_list_with_workouts(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
user_1: User,
|
user_1_admin: User,
|
||||||
user_2: User,
|
user_2: User,
|
||||||
user_3: User,
|
user_3: User,
|
||||||
sport_1_cycling: Sport,
|
sport_1_cycling: Sport,
|
||||||
@ -189,7 +203,7 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
workout_cycling_user_2: Workout,
|
workout_cycling_user_2: Workout,
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -204,10 +218,10 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
assert 'created_at' in data['data']['users'][0]
|
assert 'created_at' in data['data']['users'][0]
|
||||||
assert 'created_at' in data['data']['users'][1]
|
assert 'created_at' in data['data']['users'][1]
|
||||||
assert 'created_at' in data['data']['users'][2]
|
assert 'created_at' in data['data']['users'][2]
|
||||||
assert 'test' in data['data']['users'][0]['username']
|
assert 'admin' in data['data']['users'][0]['username']
|
||||||
assert 'toto' in data['data']['users'][1]['username']
|
assert 'toto' in data['data']['users'][1]['username']
|
||||||
assert 'sam' in data['data']['users'][2]['username']
|
assert 'sam' in data['data']['users'][2]['username']
|
||||||
assert 'test@test.com' in data['data']['users'][0]['email']
|
assert 'admin@example.com' in data['data']['users'][0]['email']
|
||||||
assert 'toto@toto.com' in data['data']['users'][1]['email']
|
assert 'toto@toto.com' in data['data']['users'][1]['email']
|
||||||
assert 'sam@test.com' in data['data']['users'][2]['email']
|
assert 'sam@test.com' in data['data']['users'][2]['email']
|
||||||
assert data['data']['users'][0]['imperial_units'] is False
|
assert data['data']['users'][0]['imperial_units'] is False
|
||||||
@ -219,18 +233,12 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
assert data['data']['users'][0]['sports_list'] == [1, 2]
|
assert data['data']['users'][0]['sports_list'] == [1, 2]
|
||||||
assert data['data']['users'][0]['total_distance'] == 22.0
|
assert data['data']['users'][0]['total_distance'] == 22.0
|
||||||
assert data['data']['users'][0]['total_duration'] == '2:40:00'
|
assert data['data']['users'][0]['total_duration'] == '2:40:00'
|
||||||
assert data['data']['users'][1]['imperial_units'] is False
|
|
||||||
assert data['data']['users'][1]['timezone'] is None
|
|
||||||
assert data['data']['users'][1]['weekm'] is False
|
|
||||||
assert data['data']['users'][1]['nb_sports'] == 1
|
assert data['data']['users'][1]['nb_sports'] == 1
|
||||||
assert data['data']['users'][1]['nb_workouts'] == 1
|
assert data['data']['users'][1]['nb_workouts'] == 1
|
||||||
assert len(data['data']['users'][1]['records']) == 4
|
assert len(data['data']['users'][1]['records']) == 4
|
||||||
assert data['data']['users'][1]['sports_list'] == [1]
|
assert data['data']['users'][1]['sports_list'] == [1]
|
||||||
assert data['data']['users'][1]['total_distance'] == 15
|
assert data['data']['users'][1]['total_distance'] == 15
|
||||||
assert data['data']['users'][1]['total_duration'] == '1:00:00'
|
assert data['data']['users'][1]['total_duration'] == '1:00:00'
|
||||||
assert data['data']['users'][2]['imperial_units'] is False
|
|
||||||
assert data['data']['users'][2]['timezone'] is None
|
|
||||||
assert data['data']['users'][2]['weekm'] is True
|
|
||||||
assert data['data']['users'][2]['nb_sports'] == 0
|
assert data['data']['users'][2]['nb_sports'] == 0
|
||||||
assert data['data']['users'][2]['nb_workouts'] == 0
|
assert data['data']['users'][2]['nb_workouts'] == 0
|
||||||
assert len(data['data']['users'][2]['records']) == 0
|
assert len(data['data']['users'][2]['records']) == 0
|
||||||
@ -249,12 +257,12 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
def test_it_gets_first_page_on_users_list(
|
def test_it_gets_first_page_on_users_list(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
user_1: User,
|
user_1_admin: User,
|
||||||
user_2: User,
|
user_2: User,
|
||||||
user_3: User,
|
user_3: User,
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -278,12 +286,12 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
def test_it_gets_next_page_on_users_list(
|
def test_it_gets_next_page_on_users_list(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
user_1: User,
|
user_1_admin: User,
|
||||||
user_2: User,
|
user_2: User,
|
||||||
user_3: User,
|
user_3: User,
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -306,12 +314,12 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
def test_it_gets_empty_next_page_on_users_list(
|
def test_it_gets_empty_next_page_on_users_list(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
user_1: User,
|
user_1_admin: User,
|
||||||
user_2: User,
|
user_2: User,
|
||||||
user_3: User,
|
user_3: User,
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -334,12 +342,12 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
def test_it_gets_user_list_with_2_per_page(
|
def test_it_gets_user_list_with_2_per_page(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
user_1: User,
|
user_1_admin: User,
|
||||||
user_2: User,
|
user_2: User,
|
||||||
user_3: User,
|
user_3: User,
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -362,12 +370,12 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
def test_it_gets_next_page_on_user_list_with_2_per_page(
|
def test_it_gets_next_page_on_user_list_with_2_per_page(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
user_1: User,
|
user_1_admin: User,
|
||||||
user_2: User,
|
user_2: User,
|
||||||
user_3: User,
|
user_3: User,
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -388,10 +396,10 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def test_it_gets_users_list_ordered_by_username(
|
def test_it_gets_users_list_ordered_by_username(
|
||||||
self, app: Flask, user_1: User, user_2: User, user_3: User
|
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -403,8 +411,8 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'success' in data['status']
|
assert 'success' in data['status']
|
||||||
assert len(data['data']['users']) == 3
|
assert len(data['data']['users']) == 3
|
||||||
assert 'sam' in data['data']['users'][0]['username']
|
assert 'admin' in data['data']['users'][0]['username']
|
||||||
assert 'test' in data['data']['users'][1]['username']
|
assert 'sam' in data['data']['users'][1]['username']
|
||||||
assert 'toto' in data['data']['users'][2]['username']
|
assert 'toto' in data['data']['users'][2]['username']
|
||||||
assert data['pagination'] == {
|
assert data['pagination'] == {
|
||||||
'has_next': False,
|
'has_next': False,
|
||||||
@ -415,10 +423,10 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def test_it_gets_users_list_ordered_by_username_ascending(
|
def test_it_gets_users_list_ordered_by_username_ascending(
|
||||||
self, app: Flask, user_1: User, user_2: User, user_3: User
|
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -430,8 +438,8 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'success' in data['status']
|
assert 'success' in data['status']
|
||||||
assert len(data['data']['users']) == 3
|
assert len(data['data']['users']) == 3
|
||||||
assert 'sam' in data['data']['users'][0]['username']
|
assert 'admin' in data['data']['users'][0]['username']
|
||||||
assert 'test' in data['data']['users'][1]['username']
|
assert 'sam' in data['data']['users'][1]['username']
|
||||||
assert 'toto' in data['data']['users'][2]['username']
|
assert 'toto' in data['data']['users'][2]['username']
|
||||||
assert data['pagination'] == {
|
assert data['pagination'] == {
|
||||||
'has_next': False,
|
'has_next': False,
|
||||||
@ -442,10 +450,10 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def test_it_gets_users_list_ordered_by_username_descending(
|
def test_it_gets_users_list_ordered_by_username_descending(
|
||||||
self, app: Flask, user_1: User, user_2: User, user_3: User
|
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -458,8 +466,8 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
assert 'success' in data['status']
|
assert 'success' in data['status']
|
||||||
assert len(data['data']['users']) == 3
|
assert len(data['data']['users']) == 3
|
||||||
assert 'toto' in data['data']['users'][0]['username']
|
assert 'toto' in data['data']['users'][0]['username']
|
||||||
assert 'test' in data['data']['users'][1]['username']
|
assert 'sam' in data['data']['users'][1]['username']
|
||||||
assert 'sam' in data['data']['users'][2]['username']
|
assert 'admin' in data['data']['users'][2]['username']
|
||||||
assert data['pagination'] == {
|
assert data['pagination'] == {
|
||||||
'has_next': False,
|
'has_next': False,
|
||||||
'has_prev': False,
|
'has_prev': False,
|
||||||
@ -642,14 +650,14 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
def test_it_gets_users_list_ordered_by_workouts_count(
|
def test_it_gets_users_list_ordered_by_workouts_count(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
user_1: User,
|
user_1_admin: User,
|
||||||
user_2: User,
|
user_2: User,
|
||||||
user_3: User,
|
user_3: User,
|
||||||
sport_1_cycling: Sport,
|
sport_1_cycling: Sport,
|
||||||
workout_cycling_user_2: Workout,
|
workout_cycling_user_2: Workout,
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -661,7 +669,7 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'success' in data['status']
|
assert 'success' in data['status']
|
||||||
assert len(data['data']['users']) == 3
|
assert len(data['data']['users']) == 3
|
||||||
assert 'test' in data['data']['users'][0]['username']
|
assert 'admin' in data['data']['users'][0]['username']
|
||||||
assert 0 == data['data']['users'][0]['nb_workouts']
|
assert 0 == data['data']['users'][0]['nb_workouts']
|
||||||
assert 'sam' in data['data']['users'][1]['username']
|
assert 'sam' in data['data']['users'][1]['username']
|
||||||
assert 0 == data['data']['users'][1]['nb_workouts']
|
assert 0 == data['data']['users'][1]['nb_workouts']
|
||||||
@ -678,14 +686,14 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
def test_it_gets_users_list_ordered_by_workouts_count_ascending(
|
def test_it_gets_users_list_ordered_by_workouts_count_ascending(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
user_1: User,
|
user_1_admin: User,
|
||||||
user_2: User,
|
user_2: User,
|
||||||
user_3: User,
|
user_3: User,
|
||||||
sport_1_cycling: Sport,
|
sport_1_cycling: Sport,
|
||||||
workout_cycling_user_2: Workout,
|
workout_cycling_user_2: Workout,
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -697,7 +705,7 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'success' in data['status']
|
assert 'success' in data['status']
|
||||||
assert len(data['data']['users']) == 3
|
assert len(data['data']['users']) == 3
|
||||||
assert 'test' in data['data']['users'][0]['username']
|
assert 'admin' in data['data']['users'][0]['username']
|
||||||
assert 0 == data['data']['users'][0]['nb_workouts']
|
assert 0 == data['data']['users'][0]['nb_workouts']
|
||||||
assert 'sam' in data['data']['users'][1]['username']
|
assert 'sam' in data['data']['users'][1]['username']
|
||||||
assert 0 == data['data']['users'][1]['nb_workouts']
|
assert 0 == data['data']['users'][1]['nb_workouts']
|
||||||
@ -714,14 +722,14 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
def test_it_gets_users_list_ordered_by_workouts_count_descending(
|
def test_it_gets_users_list_ordered_by_workouts_count_descending(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
user_1: User,
|
user_1_admin: User,
|
||||||
user_2: User,
|
user_2: User,
|
||||||
user_3: User,
|
user_3: User,
|
||||||
sport_1_cycling: Sport,
|
sport_1_cycling: Sport,
|
||||||
workout_cycling_user_2: Workout,
|
workout_cycling_user_2: Workout,
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -735,7 +743,7 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
assert len(data['data']['users']) == 3
|
assert len(data['data']['users']) == 3
|
||||||
assert 'toto' in data['data']['users'][0]['username']
|
assert 'toto' in data['data']['users'][0]['username']
|
||||||
assert 1 == data['data']['users'][0]['nb_workouts']
|
assert 1 == data['data']['users'][0]['nb_workouts']
|
||||||
assert 'test' in data['data']['users'][1]['username']
|
assert 'admin' in data['data']['users'][1]['username']
|
||||||
assert 0 == data['data']['users'][1]['nb_workouts']
|
assert 0 == data['data']['users'][1]['nb_workouts']
|
||||||
assert 'sam' in data['data']['users'][2]['username']
|
assert 'sam' in data['data']['users'][2]['username']
|
||||||
assert 0 == data['data']['users'][2]['nb_workouts']
|
assert 0 == data['data']['users'][2]['nb_workouts']
|
||||||
@ -748,10 +756,10 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def test_it_gets_users_list_filtering_on_username(
|
def test_it_gets_users_list_filtering_on_username(
|
||||||
self, app: Flask, user_1: User, user_2: User, user_3: User
|
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -773,10 +781,10 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def test_it_returns_empty_users_list_filtering_on_username(
|
def test_it_returns_empty_users_list_filtering_on_username(
|
||||||
self, app: Flask, user_1: User, user_2: User, user_3: User
|
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -797,10 +805,10 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def test_it_users_list_with_complex_query(
|
def test_it_users_list_with_complex_query(
|
||||||
self, app: Flask, user_1: User, user_2: User, user_3: User
|
self, app: Flask, user_1_admin: User, user_2: User, user_3: User
|
||||||
) -> None:
|
) -> None:
|
||||||
client, auth_token = self.get_test_client_and_auth_token(
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
app, user_1.email
|
app, user_1_admin.email
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
@ -812,7 +820,7 @@ class TestGetUsers(ApiTestCaseMixin):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'success' in data['status']
|
assert 'success' in data['status']
|
||||||
assert len(data['data']['users']) == 1
|
assert len(data['data']['users']) == 1
|
||||||
assert 'sam' in data['data']['users'][0]['username']
|
assert 'admin' in data['data']['users'][0]['username']
|
||||||
assert data['pagination'] == {
|
assert data['pagination'] == {
|
||||||
'has_next': False,
|
'has_next': False,
|
||||||
'has_prev': True,
|
'has_prev': True,
|
||||||
|
@ -1,33 +1,71 @@
|
|||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import pytest
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
|
from fittrackee.users.exceptions import UserNotFoundException
|
||||||
from fittrackee.users.models import User, UserSportPreference
|
from fittrackee.users.models import User, UserSportPreference
|
||||||
from fittrackee.workouts.models import Sport, Workout
|
from fittrackee.workouts.models import Sport, Workout
|
||||||
|
|
||||||
|
|
||||||
class TestUserModel:
|
class TestUserModel:
|
||||||
def test_user_model(self, app: Flask, user_1: User) -> None:
|
@staticmethod
|
||||||
assert '<User \'test\'>' == str(user_1)
|
def assert_serialized_used(serialized_user: Dict) -> None:
|
||||||
|
|
||||||
serialized_user = user_1.serialize()
|
|
||||||
assert 'test' == serialized_user['username']
|
|
||||||
assert 'created_at' in serialized_user
|
assert 'created_at' in serialized_user
|
||||||
assert serialized_user['admin'] is False
|
assert serialized_user['admin'] is False
|
||||||
assert serialized_user['first_name'] is None
|
assert serialized_user['first_name'] is None
|
||||||
assert serialized_user['last_name'] is None
|
assert serialized_user['last_name'] is None
|
||||||
assert serialized_user['imperial_units'] is False
|
|
||||||
assert serialized_user['bio'] is None
|
assert serialized_user['bio'] is None
|
||||||
assert serialized_user['location'] is None
|
assert serialized_user['location'] is None
|
||||||
assert serialized_user['birth_date'] is None
|
assert serialized_user['birth_date'] is None
|
||||||
assert serialized_user['picture'] is False
|
assert serialized_user['picture'] is False
|
||||||
assert serialized_user['timezone'] is None
|
|
||||||
assert serialized_user['weekm'] is False
|
|
||||||
assert serialized_user['language'] is None
|
|
||||||
assert serialized_user['nb_sports'] == 0
|
|
||||||
assert serialized_user['nb_workouts'] == 0
|
assert serialized_user['nb_workouts'] == 0
|
||||||
|
|
||||||
|
def test_user_model_as_auth_user(self, app: Flask, user_1: User) -> None:
|
||||||
|
assert '<User \'test\'>' == str(user_1)
|
||||||
|
|
||||||
|
serialized_user = user_1.serialize(user_1)
|
||||||
|
|
||||||
|
self.assert_serialized_used(serialized_user)
|
||||||
|
assert 'test' == serialized_user['username']
|
||||||
|
assert 'test@test.com' == serialized_user['email']
|
||||||
|
assert serialized_user['nb_sports'] == 0
|
||||||
assert serialized_user['records'] == []
|
assert serialized_user['records'] == []
|
||||||
assert serialized_user['sports_list'] == []
|
assert serialized_user['sports_list'] == []
|
||||||
assert serialized_user['total_distance'] == 0
|
assert serialized_user['total_distance'] == 0
|
||||||
assert serialized_user['total_duration'] == '0:00:00'
|
assert serialized_user['total_duration'] == '0:00:00'
|
||||||
|
assert serialized_user['imperial_units'] is False
|
||||||
|
assert serialized_user['language'] is None
|
||||||
|
assert serialized_user['timezone'] is None
|
||||||
|
assert serialized_user['weekm'] is False
|
||||||
|
assert serialized_user['email_to_confirm'] is None
|
||||||
|
assert 'confirmation_token' not in serialized_user
|
||||||
|
|
||||||
|
def test_user_model_as_admin(
|
||||||
|
self, app: Flask, user_1_admin: User, user_2: User
|
||||||
|
) -> None:
|
||||||
|
serialized_user = user_2.serialize(user_1_admin)
|
||||||
|
|
||||||
|
self.assert_serialized_used(serialized_user)
|
||||||
|
assert 'toto' == serialized_user['username']
|
||||||
|
assert 'toto@toto.com' == serialized_user['email']
|
||||||
|
assert serialized_user['nb_sports'] == 0
|
||||||
|
assert serialized_user['records'] == []
|
||||||
|
assert serialized_user['sports_list'] == []
|
||||||
|
assert serialized_user['total_distance'] == 0
|
||||||
|
assert serialized_user['total_duration'] == '0:00:00'
|
||||||
|
assert serialized_user['email_to_confirm'] is None
|
||||||
|
assert 'imperial_units' not in serialized_user
|
||||||
|
assert 'language' not in serialized_user
|
||||||
|
assert 'timezone' not in serialized_user
|
||||||
|
assert 'weekm' not in serialized_user
|
||||||
|
assert 'confirmation_token' not in serialized_user
|
||||||
|
|
||||||
|
def test_user_model_as_regular_user(
|
||||||
|
self, app: Flask, user_1: User, user_2: User
|
||||||
|
) -> None:
|
||||||
|
with pytest.raises(UserNotFoundException):
|
||||||
|
user_2.serialize(user_1)
|
||||||
|
|
||||||
def test_encode_auth_token(self, app: Flask, user_1: User) -> None:
|
def test_encode_auth_token(self, app: Flask, user_1: User) -> None:
|
||||||
auth_token = user_1.encode_auth_token(user_1.id)
|
auth_token = user_1.encode_auth_token(user_1.id)
|
||||||
@ -49,7 +87,7 @@ class TestUserModel:
|
|||||||
sport_1_cycling: Sport,
|
sport_1_cycling: Sport,
|
||||||
workout_cycling_user_1: Workout,
|
workout_cycling_user_1: Workout,
|
||||||
) -> None:
|
) -> None:
|
||||||
serialized_user = user_1.serialize()
|
serialized_user = user_1.serialize(user_1)
|
||||||
assert len(serialized_user['records']) == 4
|
assert len(serialized_user['records']) == 4
|
||||||
assert serialized_user['records'][0]['record_type'] == 'AS'
|
assert serialized_user['records'][0]['record_type'] == 'AS'
|
||||||
assert serialized_user['records'][0]['sport_id'] == sport_1_cycling.id
|
assert serialized_user['records'][0]['sport_id'] == sport_1_cycling.id
|
||||||
|
@ -386,7 +386,7 @@ def get_authenticated_user_profile(
|
|||||||
- invalid token, please log in again
|
- invalid token, please log in again
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return {'status': 'success', 'data': auth_user.serialize()}
|
return {'status': 'success', 'data': auth_user.serialize(auth_user)}
|
||||||
|
|
||||||
|
|
||||||
@auth_blueprint.route('/auth/profile/edit', methods=['POST'])
|
@auth_blueprint.route('/auth/profile/edit', methods=['POST'])
|
||||||
@ -540,7 +540,7 @@ def edit_user(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
return {
|
return {
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'message': 'user profile updated',
|
'message': 'user profile updated',
|
||||||
'data': auth_user.serialize(),
|
'data': auth_user.serialize(auth_user),
|
||||||
}
|
}
|
||||||
|
|
||||||
# handler errors
|
# handler errors
|
||||||
@ -734,7 +734,7 @@ def update_user_account(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
return {
|
return {
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'message': 'user account updated',
|
'message': 'user account updated',
|
||||||
'data': auth_user.serialize(),
|
'data': auth_user.serialize(auth_user),
|
||||||
}
|
}
|
||||||
|
|
||||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||||
@ -872,7 +872,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
return {
|
return {
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'message': 'user preferences updated',
|
'message': 'user preferences updated',
|
||||||
'data': auth_user.serialize(),
|
'data': auth_user.serialize(auth_user),
|
||||||
}
|
}
|
||||||
|
|
||||||
# handler errors
|
# handler errors
|
||||||
|
@ -11,6 +11,8 @@ from sqlalchemy.sql.expression import select
|
|||||||
from fittrackee import bcrypt, db
|
from fittrackee import bcrypt, db
|
||||||
from fittrackee.workouts.models import Workout
|
from fittrackee.workouts.models import Workout
|
||||||
|
|
||||||
|
from .exceptions import UserNotFoundException
|
||||||
|
from .roles import UserRole
|
||||||
from .utils.token import decode_user_token, get_user_token
|
from .utils.token import decode_user_token, get_user_token
|
||||||
|
|
||||||
BaseModel: DeclarativeMeta = db.Model
|
BaseModel: DeclarativeMeta = db.Model
|
||||||
@ -109,7 +111,18 @@ class User(BaseModel):
|
|||||||
.label('workouts_count')
|
.label('workouts_count')
|
||||||
)
|
)
|
||||||
|
|
||||||
def serialize(self) -> Dict:
|
def serialize(self, current_user: 'User') -> Dict:
|
||||||
|
role = (
|
||||||
|
UserRole.AUTH_USER
|
||||||
|
if current_user.id == self.id
|
||||||
|
else UserRole.ADMIN
|
||||||
|
if current_user.admin
|
||||||
|
else UserRole.USER
|
||||||
|
)
|
||||||
|
|
||||||
|
if role == UserRole.USER:
|
||||||
|
raise UserNotFoundException()
|
||||||
|
|
||||||
sports = []
|
sports = []
|
||||||
total = (0, '0:00:00')
|
total = (0, '0:00:00')
|
||||||
if self.workouts_count > 0: # type: ignore
|
if self.workouts_count > 0: # type: ignore
|
||||||
@ -127,30 +140,40 @@ class User(BaseModel):
|
|||||||
.filter(Workout.user_id == self.id)
|
.filter(Workout.user_id == self.id)
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
return {
|
|
||||||
'username': self.username,
|
serialized_user = {
|
||||||
'email': self.email,
|
|
||||||
'created_at': self.created_at,
|
|
||||||
'admin': self.admin,
|
'admin': self.admin,
|
||||||
|
'bio': self.bio,
|
||||||
|
'birth_date': self.birth_date,
|
||||||
|
'created_at': self.created_at,
|
||||||
|
'email': self.email,
|
||||||
|
'email_to_confirm': self.email_to_confirm,
|
||||||
'first_name': self.first_name,
|
'first_name': self.first_name,
|
||||||
'last_name': self.last_name,
|
'last_name': self.last_name,
|
||||||
'bio': self.bio,
|
|
||||||
'location': self.location,
|
'location': self.location,
|
||||||
'birth_date': self.birth_date,
|
|
||||||
'picture': self.picture is not None,
|
|
||||||
'timezone': self.timezone,
|
|
||||||
'weekm': self.weekm,
|
|
||||||
'language': self.language,
|
|
||||||
'nb_sports': len(sports),
|
'nb_sports': len(sports),
|
||||||
'nb_workouts': self.workouts_count,
|
'nb_workouts': self.workouts_count,
|
||||||
|
'picture': self.picture is not None,
|
||||||
'records': [record.serialize() for record in self.records],
|
'records': [record.serialize() for record in self.records],
|
||||||
'sports_list': [
|
'sports_list': [
|
||||||
sport for sportslist in sports for sport in sportslist
|
sport for sportslist in sports for sport in sportslist
|
||||||
],
|
],
|
||||||
'total_distance': float(total[0]),
|
'total_distance': float(total[0]),
|
||||||
'total_duration': str(total[1]),
|
'total_duration': str(total[1]),
|
||||||
'imperial_units': self.imperial_units,
|
'username': self.username,
|
||||||
}
|
}
|
||||||
|
if role == UserRole.AUTH_USER:
|
||||||
|
serialized_user = {
|
||||||
|
**serialized_user,
|
||||||
|
**{
|
||||||
|
'imperial_units': self.imperial_units,
|
||||||
|
'language': self.language,
|
||||||
|
'timezone': self.timezone,
|
||||||
|
'weekm': self.weekm,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialized_user
|
||||||
|
|
||||||
|
|
||||||
class UserSportPreference(BaseModel):
|
class UserSportPreference(BaseModel):
|
||||||
|
7
fittrackee/users/roles.py
Normal file
7
fittrackee/users/roles.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class UserRole(Enum):
|
||||||
|
ADMIN = 'admin'
|
||||||
|
AUTH_USER = 'auth_user'
|
||||||
|
USER = 'user'
|
@ -50,7 +50,7 @@ def set_admin(username: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@users_blueprint.route('/users', methods=['GET'])
|
@users_blueprint.route('/users', methods=['GET'])
|
||||||
@authenticate
|
@authenticate_as_admin
|
||||||
def get_users(auth_user: User) -> Dict:
|
def get_users(auth_user: User) -> Dict:
|
||||||
"""
|
"""
|
||||||
Get all users
|
Get all users
|
||||||
@ -227,7 +227,7 @@ def get_users(auth_user: User) -> Dict:
|
|||||||
users = users_pagination.items
|
users = users_pagination.items
|
||||||
return {
|
return {
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'data': {'users': [user.serialize() for user in users]},
|
'data': {'users': [user.serialize(auth_user) for user in users]},
|
||||||
'pagination': {
|
'pagination': {
|
||||||
'has_next': users_pagination.has_next,
|
'has_next': users_pagination.has_next,
|
||||||
'has_prev': users_pagination.has_prev,
|
'has_prev': users_pagination.has_prev,
|
||||||
@ -239,7 +239,7 @@ def get_users(auth_user: User) -> Dict:
|
|||||||
|
|
||||||
|
|
||||||
@users_blueprint.route('/users/<user_name>', methods=['GET'])
|
@users_blueprint.route('/users/<user_name>', methods=['GET'])
|
||||||
@authenticate
|
@authenticate_as_admin
|
||||||
def get_single_user(
|
def get_single_user(
|
||||||
auth_user: User, user_name: str
|
auth_user: User, user_name: str
|
||||||
) -> Union[Dict, HttpResponse]:
|
) -> Union[Dict, HttpResponse]:
|
||||||
@ -345,7 +345,7 @@ def get_single_user(
|
|||||||
if user:
|
if user:
|
||||||
return {
|
return {
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'data': {'users': [user.serialize()]},
|
'data': {'users': [user.serialize(auth_user)]},
|
||||||
}
|
}
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
@ -581,7 +581,7 @@ def update_user(auth_user: User, user_name: str) -> Union[Dict, HttpResponse]:
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'data': {'users': [user.serialize()]},
|
'data': {'users': [user.serialize(auth_user)]},
|
||||||
}
|
}
|
||||||
except exc.StatementError as e:
|
except exc.StatementError as e:
|
||||||
return handle_error_and_return_response(e, db=db)
|
return handle_error_and_return_response(e, db=db)
|
||||||
|
Loading…
Reference in New Issue
Block a user