Skip to content

Commit e149759

Browse files
ivan-g-scntDamirkhon
authored andcommitted
M2-3148 Outdated data is excluded from the report and is deleted
1 parent 6e14751 commit e149759

5 files changed

Lines changed: 108 additions & 2 deletions

File tree

src/apps/answers/crud/answers.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import uuid
44
from typing import Collection
55

6-
from pydantic import parse_obj_as
6+
from pydantic import PositiveInt, parse_obj_as
77
from sqlalchemy import (
88
Text,
99
and_,
@@ -37,10 +37,11 @@
3737
UserAnswerItemData,
3838
Version,
3939
)
40-
from apps.answers.errors import AnswerNotFoundError
40+
from apps.answers.errors import AnswerNotFoundError, AnswerRetentionType
4141
from apps.applets.db.schemas import AppletHistorySchema
4242
from apps.shared.filtering import Comparisons, FilterField, Filtering
4343
from apps.shared.paging import paging
44+
from apps.workspaces.domain.constants import DataRetention
4445
from infrastructure.database.crud import BaseCRUD
4546

4647

@@ -206,6 +207,8 @@ async def get_applet_answers(
206207
)
207208
.where(
208209
AnswerSchema.applet_id == applet_id,
210+
AnswerSchema.soft_exists(),
211+
AnswerItemSchema.soft_exists(),
209212
*filter_clauses,
210213
)
211214
)
@@ -567,6 +570,53 @@ async def get_applet_user_answer_items(
567570

568571
return parse_obj_as(list[UserAnswerItemData], db_result.all())
569572

573+
async def removing_outdated_answers(
574+
self,
575+
applet_id: uuid.UUID,
576+
retention_period: PositiveInt,
577+
retention_type: DataRetention,
578+
):
579+
hours_in_day = 24
580+
hours_in_week = hours_in_day * 7
581+
hours_in_month = hours_in_day * 30
582+
hours_in_year = hours_in_day * 365
583+
584+
if retention_type == DataRetention.DAYS:
585+
retention_time = datetime.timedelta(
586+
hours=retention_period * hours_in_day
587+
)
588+
elif retention_type == DataRetention.WEEKS:
589+
retention_time = datetime.timedelta(
590+
hours=retention_period * hours_in_week
591+
)
592+
elif retention_type == DataRetention.MONTHS:
593+
retention_time = datetime.timedelta(
594+
hours=retention_period * hours_in_month
595+
)
596+
elif retention_type == DataRetention.YEARS:
597+
retention_time = datetime.timedelta(
598+
hours=retention_period * hours_in_year
599+
)
600+
else:
601+
raise AnswerRetentionType()
602+
border_datetime = datetime.datetime.utcnow() - retention_time
603+
604+
query = update(AnswerSchema)
605+
query = query.where(AnswerSchema.applet_id == applet_id)
606+
query = query.where(AnswerSchema.created_at < border_datetime)
607+
query = query.where(AnswerSchema.soft_exists())
608+
query = query.values(is_deleted=True)
609+
query = query.returning(column("id"))
610+
deleted_answer_ids: list[uuid.UUID] = [
611+
x[0] for x in await self._execute(query)
612+
]
613+
614+
query = update(AnswerItemSchema)
615+
query = query.where(AnswerItemSchema.answer_id.in_(deleted_answer_ids))
616+
query = query.where(AnswerSchema.soft_exists())
617+
query = query.values(is_deleted=True)
618+
await self._execute(query)
619+
570620
async def update_encrypted_fields(
571621
self, user_public_key: str, data: list[AnswerItemDataEncrypted]
572622
):

src/apps/answers/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ class AnswerNoteAccessDeniedError(AccessDeniedError):
2727
message = _("Note access denied.")
2828

2929

30+
class AnswerRetentionType(ValidationError):
31+
message = _("Incorrect answer retention type.")
32+
33+
3034
class UserDoesNotHavePermissionError(AccessDeniedError):
3135
message = _("User does not have permission.")
3236

src/apps/answers/tasks.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55

66
import sentry_sdk
77
from fastapi import UploadFile
8+
from sqlalchemy.engine import Result
89
from sqlalchemy.ext.asyncio import AsyncSession
910

11+
from apps.answers.crud.answers import AnswersCRUD
1012
from apps.answers.deps.preprocess_arbitrary import get_arbitrary_info
1113
from apps.answers.domain import ReportServerResponse
14+
from apps.applets.crud import AppletsCRUD
1215
from apps.mailing.domain import MessageSchema
1316
from apps.mailing.services import MailingService
17+
from apps.workspaces.domain.constants import DataRetention
1418
from broker import broker
1519
from infrastructure.database import session_manager
1620

@@ -75,3 +79,29 @@ async def create_report(
7579
finally:
7680
if not isinstance(session_maker, AsyncSession):
7781
await session_maker.remove()
82+
83+
84+
@broker.task(
85+
task_name="apps.answers.tasks:removing_outdated_answers",
86+
schedule=[{"cron": "*/30 * * * *"}],
87+
)
88+
async def removing_outdated_answers():
89+
session_maker = session_manager.get_session()
90+
try:
91+
async with session_maker() as session:
92+
applets_data: Result = await AppletsCRUD(
93+
session
94+
).get_every_non_indefinitely_applet_retentions()
95+
for applet_data in applets_data:
96+
applet_id, retention_period, retention_type = applet_data
97+
retention_type = DataRetention(retention_type)
98+
await AnswersCRUD(session).removing_outdated_answers(
99+
applet_id, retention_period, retention_type
100+
)
101+
await session.commit()
102+
except Exception as e:
103+
traceback.print_exception(e)
104+
sentry_sdk.capture_exception(e)
105+
finally:
106+
if not isinstance(session_maker, AsyncSession):
107+
await session_maker.remove()

src/apps/applets/crud/applets.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from apps.users import UserSchema
3838
from apps.users.db.schemas import UserDeviceSchema
3939
from apps.workspaces.db.schemas import UserAppletAccessSchema
40+
from apps.workspaces.domain.constants import DataRetention
4041
from infrastructure.database.crud import BaseCRUD
4142

4243
__all__ = ["AppletsCRUD"]
@@ -953,6 +954,22 @@ async def get_workspace_applets_flat_list_count(
953954
db_result = await self._execute(select(func.count(query.c.id)))
954955
return db_result.scalars().first() or 0
955956

957+
async def get_every_non_indefinitely_applet_retentions(self) -> Result:
958+
"""returned Result[Row[uuid.UUID, int, str]]
959+
Result[Row[applet_id, applet_retention_period, applet_retention_type]]
960+
"""
961+
query: Query = select(
962+
AppletSchema.id.label("id"),
963+
AppletSchema.retention_period.label("retention_period"),
964+
AppletSchema.retention_type.label("retention_type"),
965+
)
966+
query = query.where(
967+
AppletSchema.retention_type != DataRetention.INDEFINITELY
968+
)
969+
result: Result = await self._execute(query)
970+
971+
return result
972+
956973
async def clear_report_settings(self, applet_id: uuid.UUID):
957974
query: Query = update(AppletSchema)
958975
query = query.where(AppletSchema.id == applet_id)

src/apps/applets/service/applet.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
)
5555
from apps.themes.service import ThemeService
5656
from apps.users.services.user import UserService
57+
from apps.workspaces.domain.constants import DataRetention
5758
from apps.workspaces.errors import AppletEncryptionUpdateDenied
5859
from apps.workspaces.service.user_applet_access import UserAppletAccessService
5960
from config import settings
@@ -825,6 +826,10 @@ async def set_data_retention(
825826
await AppletsCRUD(self.session).set_data_retention(
826827
applet_id, data_retention
827828
)
829+
if data_retention.retention != DataRetention.INDEFINITELY:
830+
await AnswersCRUD(self.session).removing_outdated_answers(
831+
applet_id, data_retention.period or 1, data_retention.retention
832+
)
828833

829834
async def get_full_applet(self, applet_id: uuid.UUID) -> AppletFull:
830835
schema = await AppletsCRUD(self.session).get_by_id(applet_id)

0 commit comments

Comments
 (0)