diff --git a/CHANGELOG.md b/CHANGELOG.md index a15edc489..22f26d2d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,4 +78,5 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - New endpoint for adding a message of the day to the database ([#1136](https://github.com/ScilifelabDataCentre/dds_web/pull/1136)) - Patch: Custom error for PI email validation ([#1146](https://github.com/ScilifelabDataCentre/dds_web/pull/1146)) - New Data Delivery System logo ([#1148](https://github.com/ScilifelabDataCentre/dds_web/pull/1148)) +- Cronjob: Scheduled task for deleting unanswered invites after a week ([#1147](https://github.com/ScilifelabDataCentre/dds_web/pull/1147)) - Checkbox in registration form and policy to agree to ([#1151](https://github.com/ScilifelabDataCentre/dds_web/pull/1151)) diff --git a/dds_web/database/models.py b/dds_web/database/models.py index 7f7a136e4..54fc1d590 100644 --- a/dds_web/database/models.py +++ b/dds_web/database/models.py @@ -726,6 +726,7 @@ class Invite(db.Model): nonce = db.Column(db.LargeBinary(12), default=None) public_key = db.Column(db.LargeBinary(300), default=None) private_key = db.Column(db.LargeBinary(300), default=None) + created_at = db.Column(db.DateTime(), nullable=False, default=dds_web.utils.current_time()) @property def projects(self): diff --git a/dds_web/scheduled_tasks.py b/dds_web/scheduled_tasks.py index 123e65a95..70c717868 100644 --- a/dds_web/scheduled_tasks.py +++ b/dds_web/scheduled_tasks.py @@ -1,3 +1,5 @@ +from datetime import datetime, timedelta + import flask_apscheduler import flask @@ -167,3 +169,40 @@ def set_expired_to_archived(): ) for proj in errors[unit].keys(): scheduler.app.logger.error(f"Error for project '{proj}': {errors[unit][proj]} ") + + +@scheduler.task("cron", id="delete_invite", hour=0, minute=1, misfire_grace_time=3600) +def delete_invite(): + """Delete invite older than a week""" + + scheduler.app.logger.debug("Task: Checking for invites to delete.") + + from sqlalchemy.exc import OperationalError, SQLAlchemyError + from dds_web import db + from dds_web.database import models + from dds_web.errors import DatabaseError + from dds_web.utils import current_time + + with scheduler.app.app_context(): + expiration: datetime.datetime = current_time() + errors: Dict = {} + + try: + invites: list = db.session.query(models.Invite).all() + for invite in invites: + if (invite.created_at + timedelta(weeks=1)) < expiration: + try: + db.session.delete(invite) + db.session.commit() + scheduler.app.logger.debug("Invite deleted.") + except (OperationalError, SQLAlchemyError) as err: + errors[invite] = str(err) + scheduler.app.logger.exception(err) + db.session.rollback() + continue + except (OperationalError, SQLAlchemyError) as err: + scheduler.app.logger.exception(err) + raise + + for invite, error in errors.items(): + scheduler.app.logger.error(f"{invite} not deleted: {error}") diff --git a/migrations/versions/1fbd604872e9_add_column_created_at.py b/migrations/versions/1fbd604872e9_add_column_created_at.py new file mode 100644 index 000000000..eaaa5b672 --- /dev/null +++ b/migrations/versions/1fbd604872e9_add_column_created_at.py @@ -0,0 +1,28 @@ +"""add_column_created_at + +Revision ID: 1fbd604872e9 +Revises: 19b877061c98 +Create Date: 2022-04-08 14:32:26.800385 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +from dds_web.utils import current_time + +# revision identifiers, used by Alembic. +revision = "1fbd604872e9" +down_revision = "19b877061c98" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + "invites", sa.Column("created_at", sa.DateTime(), nullable=False, default=current_time()) + ) + + +def downgrade(): + op.drop_column("invites", "created_at") diff --git a/tests/conftest.py b/tests/conftest.py index b5c3e0a4c..da80e8fa3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -319,7 +319,11 @@ def demo_data(): ), ) - invites = [Invite(**{"email": "existing_invite_email@mailtrap.io", "role": "Researcher"})] + invites = [ + Invite(**{"email": "existing_invite_email@mailtrap.io", "role": "Researcher"}), + Invite(**{"email": "existing_expired_invite_email@mailtrap.io", "role": "Researcher"}), + ] + invites[1].created_at = dds_web.utils.current_time() - datetime.timedelta(weeks=1) return (units, users, projects, invites, files_and_versions) @@ -420,6 +424,7 @@ def add_data_to_db(): units[0].projects.extend(projects) units[0].users.extend([users[2], users[3], users[4]]) units[0].invites.append(invites[0]) + units[0].invites.append(invites[1]) units[1].users.extend([users[8], users[9]]) diff --git a/tests/test_scheduled_tasks.py b/tests/test_scheduled_tasks.py new file mode 100644 index 000000000..31abaf573 --- /dev/null +++ b/tests/test_scheduled_tasks.py @@ -0,0 +1,14 @@ +import flask + +import pytest + +from dds_web import db +from dds_web.database import models + +from dds_web.scheduled_tasks import delete_invite + + +def test_delete_invite(client: flask.testing.FlaskClient) -> None: + assert len(db.session.query(models.Invite).all()) == 2 + delete_invite() + assert len(db.session.query(models.Invite).all()) == 1