API - add endpoint to download export archive

This commit is contained in:
Sam
2023-03-01 21:00:53 +01:00
parent 90c85f921c
commit 073c677b92
7 changed files with 207 additions and 9 deletions

View File

@ -5,7 +5,13 @@ import secrets
from typing import Dict, Optional, Tuple, Union
import jwt
from flask import Blueprint, current_app, request
from flask import (
Blueprint,
Response,
current_app,
request,
send_from_directory,
)
from sqlalchemy import exc, func
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.utils import secure_filename
@ -21,6 +27,7 @@ from fittrackee.emails.tasks import (
from fittrackee.files import get_absolute_file_path
from fittrackee.oauth2.server import require_auth
from fittrackee.responses import (
DataNotFoundErrorResponse,
ForbiddenErrorResponse,
HttpResponse,
InvalidPayloadErrorResponse,
@ -1818,3 +1825,58 @@ def get_user_data_export(auth_user: User) -> Union[Dict, HttpResponse]:
"status": "success",
"request": export_request.serialize() if export_request else None,
}
@auth_blueprint.route(
'/auth/profile/export/<string:file_name>', methods=['GET']
)
@require_auth()
def download_data_export(
auth_user: User, file_name: str
) -> Union[Response, HttpResponse]:
"""
Download a data export archive
**Example request**:
.. sourcecode:: http
GET /auth/profile/export/download/archive_rgjsR3fHr5Yp.zip HTTP/1.1
Content-Type: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/x-gzip
:param string file_name: filename
: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
:statuscode 404: file not found
"""
export_request = UserDataExport.query.filter_by(
user_id=auth_user.id
).first()
if (
not export_request
or not export_request.completed
or export_request.file_name != file_name
):
return DataNotFoundErrorResponse(
data_type="archive", message="file not found"
)
return send_from_directory(
f"{current_app.config['UPLOAD_FOLDER']}/exports/{auth_user.id}",
export_request.file_name,
mimetype='application/zip',
as_attachment=True,
)

View File

@ -1,14 +1,20 @@
import os
from datetime import datetime
from typing import Dict, Optional, Union
from typing import Any, Dict, Optional, Union
import jwt
from flask import current_app
from sqlalchemy import func
from sqlalchemy.engine.base import Connection
from sqlalchemy.event import listens_for
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm.mapper import Mapper
from sqlalchemy.orm.session import Session
from sqlalchemy.sql.expression import select
from fittrackee import bcrypt, db
from fittrackee import appLog, bcrypt, db
from fittrackee.files import get_absolute_file_path
from fittrackee.workouts.models import Workout
from .exceptions import UserNotFoundException
@ -284,7 +290,7 @@ class UserDataExport(BaseModel):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(
db.Integer,
db.ForeignKey('users.id'),
db.ForeignKey('users.id', ondelete='CASCADE'),
index=True,
unique=True,
)
@ -319,3 +325,19 @@ class UserDataExport(BaseModel):
"file_name": self.file_name if status == "successful" else None,
"file_size": self.file_size if status == "successful" else None,
}
@listens_for(UserDataExport, 'after_delete')
def on_users_data_export_delete(
mapper: Mapper, connection: Connection, old_record: 'UserDataExport'
) -> None:
@listens_for(db.Session, 'after_flush', once=True)
def receive_after_flush(session: Session, context: Any) -> None:
if old_record.file_name:
try:
file_path = (
f"exports/{old_record.user_id}/{old_record.file_name}"
)
os.remove(get_absolute_file_path(file_path))
except OSError:
appLog.error('archive found when deleting export request')

View File

@ -26,7 +26,7 @@ from fittrackee.workouts.models import Record, Workout, WorkoutSegment
from .auth import get_language
from .exceptions import InvalidEmailException, UserNotFoundException
from .models import User, UserSportPreference
from .models import User, UserDataExport, UserSportPreference
from .utils.admin import UserManagerService
users_blueprint = Blueprint('users', __name__)
@ -667,6 +667,9 @@ def delete_user(
WorkoutSegment.workout_id == Workout.id, Workout.user_id == user.id
).delete(synchronize_session=False)
db.session.query(Workout).filter(Workout.user_id == user.id).delete()
db.session.query(UserDataExport).filter(
UserDataExport.user_id == user.id
).delete()
db.session.flush()
user_picture = user.picture
db.session.delete(user)
@ -675,6 +678,10 @@ def delete_user(
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'exports/{user.id}'),
ignore_errors=True,
)
shutil.rmtree(
get_absolute_file_path(f'workouts/{user.id}'),
ignore_errors=True,