API - add endpoint to download export archive
This commit is contained in:
@ -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,
|
||||
)
|
||||
|
@ -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')
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user