From 8d8bb2efb96d5ac38293f41a13f8528f27e59f72 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 1 Mar 2023 12:16:32 +0100 Subject: [PATCH] API - store export request in database --- .../30_374a670efe23_add_privacy_policy.py | 19 +++++++ fittrackee/tests/users/test_users_model.py | 50 ++++++++++++++++++- fittrackee/users/models.py | 43 ++++++++++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/fittrackee/migrations/versions/30_374a670efe23_add_privacy_policy.py b/fittrackee/migrations/versions/30_374a670efe23_add_privacy_policy.py index ee595744..d2fc07a5 100644 --- a/fittrackee/migrations/versions/30_374a670efe23_add_privacy_policy.py +++ b/fittrackee/migrations/versions/30_374a670efe23_add_privacy_policy.py @@ -29,11 +29,30 @@ def upgrade(): existing_type=sa.VARCHAR(length=50), nullable=True) + op.create_table('users_data_export', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.Column('completed', sa.Boolean(), nullable=False), + sa.Column('file_name', sa.String(length=100), nullable=True), + sa.Column('file_size', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('users_data_export', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_users_data_export_user_id'), ['user_id'], unique=True) + # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('users_data_export', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_users_data_export_user_id')) + + op.drop_table('users_data_export') + with op.batch_alter_table('users', schema=None) as batch_op: batch_op.alter_column('date_format', existing_type=sa.VARCHAR(length=50), diff --git a/fittrackee/tests/users/test_users_model.py b/fittrackee/tests/users/test_users_model.py index a2fe1a34..b1d3e3ab 100644 --- a/fittrackee/tests/users/test_users_model.py +++ b/fittrackee/tests/users/test_users_model.py @@ -6,9 +6,14 @@ from flask import Flask from freezegun import freeze_time from fittrackee import db -from fittrackee.tests.utils import random_string +from fittrackee.tests.utils import random_int, random_string from fittrackee.users.exceptions import UserNotFoundException -from fittrackee.users.models import BlacklistedToken, User, UserSportPreference +from fittrackee.users.models import ( + BlacklistedToken, + User, + UserDataExport, + UserSportPreference, +) from fittrackee.workouts.models import Sport, Workout @@ -381,3 +386,44 @@ class TestUserSportModel: assert serialized_user_sport['color'] is None assert serialized_user_sport['is_active'] assert serialized_user_sport['stopped_speed_threshold'] == 1 + + +class TestUserDataExportSerializer: + def test_it_returns_ongoing_export(self, app: Flask, user_1: User) -> None: + created_at = datetime.utcnow() + data_export = UserDataExport(user_id=user_1.id, created_at=created_at) + + serialized_data_export = data_export.serialize() + + assert serialized_data_export["created_at"] == created_at + assert serialized_data_export["status"] == "in_progress" + assert serialized_data_export["file_name"] is None + assert serialized_data_export["file_size"] is None + + def test_it_returns_successful_export( + self, app: Flask, user_1: User + ) -> None: + created_at = datetime.utcnow() + data_export = UserDataExport(user_id=user_1.id, created_at=created_at) + data_export.completed = True + data_export.file_name = random_string() + data_export.file_size = random_int() + + serialized_data_export = data_export.serialize() + + assert serialized_data_export["created_at"] == created_at + assert serialized_data_export["status"] == "successful" + assert serialized_data_export["file_name"] == data_export.file_name + assert serialized_data_export["file_size"] == data_export.file_size + + def test_it_returns_errored_export(self, app: Flask, user_1: User) -> None: + created_at = datetime.utcnow() + data_export = UserDataExport(user_id=user_1.id, created_at=created_at) + data_export.completed = True + + serialized_data_export = data_export.serialize() + + assert serialized_data_export["created_at"] == created_at + assert serialized_data_export["status"] == "errored" + assert serialized_data_export["file_name"] is None + assert serialized_data_export["file_size"] is None diff --git a/fittrackee/users/models.py b/fittrackee/users/models.py index 1a536dde..7b6d3c09 100644 --- a/fittrackee/users/models.py +++ b/fittrackee/users/models.py @@ -276,3 +276,46 @@ class BlacklistedToken(BaseModel): @classmethod def check(cls, auth_token: str) -> bool: return cls.query.filter_by(token=str(auth_token)).first() is not None + + +class UserDataExport(BaseModel): + __tablename__ = 'users_data_export' + + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + user_id = db.Column( + db.Integer, + db.ForeignKey('users.id'), + index=True, + unique=True, + ) + created_at = db.Column( + db.DateTime, nullable=False, default=datetime.utcnow + ) + updated_at = db.Column( + db.DateTime, nullable=True, onupdate=datetime.utcnow + ) + completed = db.Column(db.Boolean, nullable=False, default=False) + file_name = db.Column(db.String(100), nullable=True) + file_size = db.Column(db.Integer, nullable=True) + + def __init__( + self, + user_id: int, + created_at: Optional[datetime] = None, + ): + self.user_id = user_id + self.created_at = ( + datetime.utcnow() if created_at is None else created_at + ) + + def serialize(self) -> Dict: + if self.completed: + status = "successful" if self.file_name else "errored" + else: + status = "in_progress" + return { + "created_at": self.created_at, + "status": status, + "file_name": self.file_name if status == "successful" else None, + "file_size": self.file_size if status == "successful" else None, + }