API - add enpoints to request user data export and get export status
This commit is contained in:
parent
8d8bb2efb9
commit
e4fc8849be
@ -9,7 +9,12 @@ from flask import Flask
|
||||
from freezegun import freeze_time
|
||||
|
||||
from fittrackee import db
|
||||
from fittrackee.users.models import BlacklistedToken, User, UserSportPreference
|
||||
from fittrackee.users.models import (
|
||||
BlacklistedToken,
|
||||
User,
|
||||
UserDataExport,
|
||||
UserSportPreference,
|
||||
)
|
||||
from fittrackee.users.utils.token import get_user_token
|
||||
from fittrackee.workouts.models import Sport
|
||||
|
||||
@ -2822,3 +2827,154 @@ class TestUserPrivacyPolicyUpdate(ApiTestCaseMixin):
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
class TestPostUserDataExportRequest(ApiTestCaseMixin):
|
||||
def test_it_returns_data_export_info_when_no_ongoing_request_exists_for_user( # noqa
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
) -> None:
|
||||
db.session.add(UserDataExport(user_id=user_2.id))
|
||||
db.session.commit()
|
||||
client, auth_token = self.get_test_client_and_auth_token(
|
||||
app, user_1.email
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
'/api/auth/profile/export/request',
|
||||
content_type='application/json',
|
||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data.decode())
|
||||
data_export_request = UserDataExport.query.filter_by(
|
||||
user_id=user_1.id
|
||||
).first()
|
||||
assert data["status"] == "success"
|
||||
assert data["request"] == jsonify_dict(data_export_request.serialize())
|
||||
|
||||
def test_it_returns_error_if_ongoing_request_exist(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
) -> None:
|
||||
db.session.add(UserDataExport(user_id=user_1.id))
|
||||
db.session.commit()
|
||||
client, auth_token = self.get_test_client_and_auth_token(
|
||||
app, user_1.email
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
'/api/auth/profile/export/request',
|
||||
content_type='application/json',
|
||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||
)
|
||||
|
||||
self.assert_400(response, "ongoing request exists")
|
||||
|
||||
def test_it_returns_error_if_existing_request_is_completed_for_less_than_24_hours( # noqa
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
) -> None:
|
||||
completed_export_request = UserDataExport(user_id=user_1.id)
|
||||
db.session.add(completed_export_request)
|
||||
completed_export_request.completed = True
|
||||
db.session.commit()
|
||||
client, auth_token = self.get_test_client_and_auth_token(
|
||||
app, user_1.email
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
'/api/auth/profile/export/request',
|
||||
content_type='application/json',
|
||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||
)
|
||||
|
||||
self.assert_400(response, "completed request already exists")
|
||||
|
||||
def test_it_returns_new_request_if_existing_request_is_completed_for_more_than_more_24_hours( # noqa
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
) -> None:
|
||||
completed_export_request = UserDataExport(
|
||||
user_id=user_1.id,
|
||||
created_at=datetime.utcnow() - timedelta(hours=24),
|
||||
)
|
||||
db.session.add(completed_export_request)
|
||||
completed_export_request.completed = True
|
||||
db.session.commit()
|
||||
client, auth_token = self.get_test_client_and_auth_token(
|
||||
app, user_1.email
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
'/api/auth/profile/export/request',
|
||||
content_type='application/json',
|
||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data.decode())
|
||||
data_export_request = UserDataExport.query.filter_by(
|
||||
user_id=user_1.id
|
||||
).first()
|
||||
assert data_export_request.id != completed_export_request.id
|
||||
assert data["status"] == "success"
|
||||
assert data["request"] == jsonify_dict(data_export_request.serialize())
|
||||
|
||||
|
||||
class TestGetUserDataExportRequest(ApiTestCaseMixin):
|
||||
def test_it_returns_none_if_no_request(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
) -> None:
|
||||
client, auth_token = self.get_test_client_and_auth_token(
|
||||
app, user_1.email
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
'/api/auth/profile/export',
|
||||
content_type='application/json',
|
||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data.decode())
|
||||
assert data["status"] == "success"
|
||||
assert data["request"] is None
|
||||
|
||||
def test_it_returns_existing_request(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
) -> None:
|
||||
completed_export_request = UserDataExport(
|
||||
user_id=user_1.id,
|
||||
created_at=datetime.utcnow() - timedelta(hours=24),
|
||||
)
|
||||
db.session.add(completed_export_request)
|
||||
db.session.commit()
|
||||
client, auth_token = self.get_test_client_and_auth_token(
|
||||
app, user_1.email
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
'/api/auth/profile/export',
|
||||
content_type='application/json',
|
||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data.decode())
|
||||
assert data["status"] == "success"
|
||||
assert data["request"] == jsonify_dict(
|
||||
completed_export_request.serialize()
|
||||
)
|
||||
|
@ -33,7 +33,7 @@ from fittrackee.responses import (
|
||||
from fittrackee.utils import get_readable_duration
|
||||
from fittrackee.workouts.models import Sport
|
||||
|
||||
from .models import BlacklistedToken, User, UserSportPreference
|
||||
from .models import BlacklistedToken, User, UserDataExport, UserSportPreference
|
||||
from .utils.controls import check_password, is_valid_email, register_controls
|
||||
from .utils.token import decode_user_token
|
||||
|
||||
@ -1684,3 +1684,132 @@ def accept_privacy_policy(auth_user: User) -> Union[Dict, HttpResponse]:
|
||||
return InvalidPayloadErrorResponse()
|
||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||
return handle_error_and_return_response(e, db=db)
|
||||
|
||||
|
||||
@auth_blueprint.route('/auth/profile/export/request', methods=['POST'])
|
||||
@require_auth()
|
||||
def request_user_data_export(auth_user: User) -> Union[Dict, HttpResponse]:
|
||||
"""
|
||||
Request a data export for authenticated user.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /auth/profile/export/request HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": "success",
|
||||
"request": {
|
||||
"created_at": "Wed, 01 Mar 2023 12:31:17 GMT",
|
||||
"status": "in_progress",
|
||||
"file_name": null,
|
||||
"file_size": null
|
||||
}
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth 2.0 Bearer Token
|
||||
|
||||
:statuscode 200: success
|
||||
:statuscode 400:
|
||||
- ongoing request exists
|
||||
- completed request already exists
|
||||
:statuscode 401:
|
||||
- provide a valid auth token
|
||||
- signature expired, please log in again
|
||||
- invalid token, please log in again
|
||||
:statuscode 500: internal server error
|
||||
"""
|
||||
existing_export_request = UserDataExport.query.filter_by(
|
||||
user_id=auth_user.id
|
||||
).first()
|
||||
if existing_export_request:
|
||||
if not existing_export_request.completed:
|
||||
return InvalidPayloadErrorResponse("ongoing request exists")
|
||||
|
||||
if (
|
||||
existing_export_request.created_at
|
||||
> datetime.datetime.utcnow() - datetime.timedelta(hours=24)
|
||||
):
|
||||
return InvalidPayloadErrorResponse(
|
||||
"completed request already exists"
|
||||
)
|
||||
|
||||
try:
|
||||
if existing_export_request:
|
||||
db.session.delete(existing_export_request)
|
||||
db.session.flush()
|
||||
export_request = UserDataExport(user_id=auth_user.id)
|
||||
db.session.add(export_request)
|
||||
db.session.commit()
|
||||
return {"status": "success", "request": export_request.serialize()}
|
||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||
return handle_error_and_return_response(e, db=db)
|
||||
|
||||
|
||||
@auth_blueprint.route('/auth/profile/export', methods=['GET'])
|
||||
@require_auth()
|
||||
def get_user_data_export(auth_user: User) -> Union[Dict, HttpResponse]:
|
||||
"""
|
||||
Get a data export info for authenticated user if a request exists.
|
||||
It returns:
|
||||
- export creation date
|
||||
- export status ("in_progress", "successful" and "errored")
|
||||
- file name and size (in bytes) when export is successful
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /auth/profile/export HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
- if a request exists:
|
||||
|
||||
{
|
||||
"status": "success",
|
||||
"request": {
|
||||
"created_at": "Wed, 01 Mar 2023 12:31:17 GMT",
|
||||
"status": "successful",
|
||||
"file_name": "archive_rgjsR3fHt295ywNQr5Yp.zip",
|
||||
"file_size": 924
|
||||
}
|
||||
}
|
||||
|
||||
- if no request:
|
||||
|
||||
{
|
||||
"status": "success",
|
||||
"request": null
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth 2.0 Bearer Token
|
||||
|
||||
:statuscode 200: success
|
||||
:statuscode 401:
|
||||
- provide a valid auth token
|
||||
- signature expired, please log in again
|
||||
- invalid token, please log in again
|
||||
"""
|
||||
export_request = UserDataExport.query.filter_by(
|
||||
user_id=auth_user.id
|
||||
).first()
|
||||
return {
|
||||
"status": "success",
|
||||
"request": export_request.serialize() if export_request else None,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user