From aa677ef572b7f5bff718928533ba850dcfb6967c Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 30 Nov 2025 04:34:10 +0100 Subject: [PATCH 1/7] Feat: add base class --- app/core/auth/endpoints_auth.py | 2 + app/core/auth/user_deleter_auth.py | 12 ++++++ app/core/core_endpoints/endpoints_core.py | 2 + app/core/core_endpoints/user_deleter_core.py | 12 ++++++ app/core/google_api/endpoints_google_api.py | 2 + .../google_api/user_deleter_google_api.py | 12 ++++++ app/core/groups/endpoints_groups.py | 2 + app/core/groups/user_deleter_groups.py | 12 ++++++ app/core/memberships/endpoints_memberships.py | 6 ++- .../memberships/user_deleter_memberships.py | 12 ++++++ .../notification/endpoints_notification.py | 2 + .../notification/user_deleter_notification.py | 12 ++++++ app/core/payment/endpoints_payment.py | 2 + app/core/payment/user_deleter_payment.py | 12 ++++++ app/core/schools/endpoints_schools.py | 2 + app/core/schools/user_deleter_schools.py | 12 ++++++ app/core/users/endpoints_users.py | 2 + app/core/users/user_deleter_users.py | 12 ++++++ app/modules/advert/endpoints_advert.py | 2 + app/modules/advert/user_deleter_advert.py | 12 ++++++ app/modules/amap/endpoints_amap.py | 2 + app/modules/amap/user_deleter_amap.py | 12 ++++++ app/modules/booking/endpoints_booking.py | 2 + app/modules/booking/user_deleter_booking.py | 12 ++++++ app/modules/calendar/endpoints_calendar.py | 2 + app/modules/calendar/user_deleter_calendar.py | 12 ++++++ app/modules/campaign/endpoints_campaign.py | 2 + app/modules/campaign/user_deleter_campaign.py | 12 ++++++ app/modules/cdr/endpoints_cdr.py | 2 + app/modules/cdr/user_deleter_cdr.py | 12 ++++++ .../endpoints_centralisation.py | 2 + .../user_deleter_centralisation.py | 12 ++++++ app/modules/cinema/endpoints_cinema.py | 2 + app/modules/cinema/user_deleter_cinema.py | 12 ++++++ .../flappybird/endpoints_flappybird.py | 2 + .../flappybird/user_deleter_flappybird.py | 12 ++++++ app/modules/home/endpoints_home.py | 2 + app/modules/home/user_deleter_home.py | 12 ++++++ app/modules/loan/endpoints_loan.py | 2 + app/modules/loan/user_deleter_loan.py | 12 ++++++ app/modules/ph/endpoints_ph.py | 2 + app/modules/ph/user_deleter_ph.py | 12 ++++++ app/modules/phonebook/endpoints_phonebook.py | 2 + .../phonebook/user_deleter_phonebook.py | 12 ++++++ app/modules/purchases/endpoints_purchases.py | 2 + .../purchases/user_deleter_purchases.py | 12 ++++++ app/modules/raffle/endpoints_raffle.py | 2 + app/modules/raffle/user_deleter_raffle.py | 12 ++++++ app/modules/raid/endpoints_raid.py | 2 + app/modules/raid/user_deleter_raid.py | 12 ++++++ .../endpoints_recommendation.py | 2 + .../user_deleter_recommendation.py | 12 ++++++ .../seed_library/endpoints_seed_library.py | 2 + .../seed_library/user_deleter_seed_library.py | 12 ++++++ app/types/module.py | 7 ++++ app/types/module_user_deleter.py | 40 +++++++++++++++++++ 56 files changed, 428 insertions(+), 1 deletion(-) create mode 100644 app/core/auth/user_deleter_auth.py create mode 100644 app/core/core_endpoints/user_deleter_core.py create mode 100644 app/core/google_api/user_deleter_google_api.py create mode 100644 app/core/groups/user_deleter_groups.py create mode 100644 app/core/memberships/user_deleter_memberships.py create mode 100644 app/core/notification/user_deleter_notification.py create mode 100644 app/core/payment/user_deleter_payment.py create mode 100644 app/core/schools/user_deleter_schools.py create mode 100644 app/core/users/user_deleter_users.py create mode 100644 app/modules/advert/user_deleter_advert.py create mode 100644 app/modules/amap/user_deleter_amap.py create mode 100644 app/modules/booking/user_deleter_booking.py create mode 100644 app/modules/calendar/user_deleter_calendar.py create mode 100644 app/modules/campaign/user_deleter_campaign.py create mode 100644 app/modules/cdr/user_deleter_cdr.py create mode 100644 app/modules/centralisation/user_deleter_centralisation.py create mode 100644 app/modules/cinema/user_deleter_cinema.py create mode 100644 app/modules/flappybird/user_deleter_flappybird.py create mode 100644 app/modules/home/user_deleter_home.py create mode 100644 app/modules/loan/user_deleter_loan.py create mode 100644 app/modules/ph/user_deleter_ph.py create mode 100644 app/modules/phonebook/user_deleter_phonebook.py create mode 100644 app/modules/purchases/user_deleter_purchases.py create mode 100644 app/modules/raffle/user_deleter_raffle.py create mode 100644 app/modules/raid/user_deleter_raid.py create mode 100644 app/modules/recommendation/user_deleter_recommendation.py create mode 100644 app/modules/seed_library/user_deleter_seed_library.py create mode 100644 app/types/module_user_deleter.py diff --git a/app/core/auth/endpoints_auth.py b/app/core/auth/endpoints_auth.py index 96f236c189..eca7fef9fa 100644 --- a/app/core/auth/endpoints_auth.py +++ b/app/core/auth/endpoints_auth.py @@ -21,6 +21,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.auth import cruds_auth, models_auth, schemas_auth +from app.core.auth.user_deleter_auth import user_deleter from app.core.users import cruds_users, models_users from app.core.utils.config import Settings from app.core.utils.security import ( @@ -51,6 +52,7 @@ tag="Auth", router=router, factory=None, + user_deleter=user_deleter, ) templates = Jinja2Templates(directory="assets/templates") diff --git a/app/core/auth/user_deleter_auth.py b/app/core/auth/user_deleter_auth.py new file mode 100644 index 0000000000..d528ee05d9 --- /dev/null +++ b/app/core/auth/user_deleter_auth.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class AuthUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = AuthUserDeleter() diff --git a/app/core/core_endpoints/endpoints_core.py b/app/core/core_endpoints/endpoints_core.py index d31b44a493..b858a47d0f 100644 --- a/app/core/core_endpoints/endpoints_core.py +++ b/app/core/core_endpoints/endpoints_core.py @@ -6,6 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.core_endpoints import cruds_core, models_core, schemas_core +from app.core.core_endpoints.user_deleter_core import user_deleter from app.core.groups.groups_type import AccountType, GroupType from app.core.users import models_users from app.core.utils.config import Settings @@ -26,6 +27,7 @@ tag="Core", router=router, factory=None, + user_deleter=user_deleter, ) diff --git a/app/core/core_endpoints/user_deleter_core.py b/app/core/core_endpoints/user_deleter_core.py new file mode 100644 index 0000000000..79aa571353 --- /dev/null +++ b/app/core/core_endpoints/user_deleter_core.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class CoreUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = CoreUserDeleter() diff --git a/app/core/google_api/endpoints_google_api.py b/app/core/google_api/endpoints_google_api.py index aaf2e79849..be0e722e56 100644 --- a/app/core/google_api/endpoints_google_api.py +++ b/app/core/google_api/endpoints_google_api.py @@ -4,6 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.google_api.google_api import GoogleAPI +from app.core.google_api.user_deleter_google_api import user_deleter from app.core.utils.config import Settings from app.dependencies import ( get_db, @@ -18,6 +19,7 @@ tag="GoogleAPI", router=router, factory=None, + user_deleter=user_deleter, ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/core/google_api/user_deleter_google_api.py b/app/core/google_api/user_deleter_google_api.py new file mode 100644 index 0000000000..59446c8fef --- /dev/null +++ b/app/core/google_api/user_deleter_google_api.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class GoogleAPIUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = GoogleAPIUserDeleter() diff --git a/app/core/groups/endpoints_groups.py b/app/core/groups/endpoints_groups.py index 587f7235dc..319ffd4cb1 100644 --- a/app/core/groups/endpoints_groups.py +++ b/app/core/groups/endpoints_groups.py @@ -13,6 +13,7 @@ from app.core.groups import cruds_groups, models_groups, schemas_groups from app.core.groups.factory_groups import CoreGroupsFactory from app.core.groups.groups_type import GroupType +from app.core.groups.user_deleter_groups import user_deleter from app.core.notification.utils_notification import get_topics_restricted_to_group_id from app.core.users import cruds_users from app.dependencies import ( @@ -32,6 +33,7 @@ tag="Groups", router=router, factory=CoreGroupsFactory(), + user_deleter=user_deleter, ) hyperion_security_logger = logging.getLogger("hyperion.security") diff --git a/app/core/groups/user_deleter_groups.py b/app/core/groups/user_deleter_groups.py new file mode 100644 index 0000000000..08f6ee82ed --- /dev/null +++ b/app/core/groups/user_deleter_groups.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class GroupsUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = GroupsUserDeleter() diff --git a/app/core/memberships/endpoints_memberships.py b/app/core/memberships/endpoints_memberships.py index fb530c4754..80937ed780 100644 --- a/app/core/memberships/endpoints_memberships.py +++ b/app/core/memberships/endpoints_memberships.py @@ -12,7 +12,10 @@ schemas_memberships, ) from app.core.memberships.factory_memberships import CoreMembershipsFactory -from app.core.memberships.utils_memberships import validate_user_new_membership +from app.core.memberships.user_deleter_memberships import user_deleter +from app.core.memberships.utils_memberships import ( + validate_user_new_membership, +) from app.core.users import cruds_users, models_users, schemas_users from app.dependencies import ( get_db, @@ -31,6 +34,7 @@ tag="Memberships", router=router, factory=CoreMembershipsFactory(), + user_deleter=user_deleter, ) diff --git a/app/core/memberships/user_deleter_memberships.py b/app/core/memberships/user_deleter_memberships.py new file mode 100644 index 0000000000..d96855cc4a --- /dev/null +++ b/app/core/memberships/user_deleter_memberships.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class MembershipsUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = MembershipsUserDeleter() diff --git a/app/core/notification/endpoints_notification.py b/app/core/notification/endpoints_notification.py index bc0e1ebc93..5b1bf68590 100644 --- a/app/core/notification/endpoints_notification.py +++ b/app/core/notification/endpoints_notification.py @@ -10,6 +10,7 @@ models_notification, schemas_notification, ) +from app.core.notification.user_deleter_notification import user_deleter from app.core.notification.utils_notification import get_user_notification_topics from app.core.users import models_users from app.dependencies import ( @@ -42,6 +43,7 @@ router=router, registred_topics=[notification_test_topic], factory=None, + user_deleter=user_deleter, ) diff --git a/app/core/notification/user_deleter_notification.py b/app/core/notification/user_deleter_notification.py new file mode 100644 index 0000000000..a3cb6131bf --- /dev/null +++ b/app/core/notification/user_deleter_notification.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class NotificationUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = NotificationUserDeleter() diff --git a/app/core/payment/endpoints_payment.py b/app/core/payment/endpoints_payment.py index b471523f6d..c12b47b392 100644 --- a/app/core/payment/endpoints_payment.py +++ b/app/core/payment/endpoints_payment.py @@ -13,6 +13,7 @@ from app.core.payment.types_payment import ( NotificationResultContent, ) +from app.core.payment.user_deleter_payment import user_deleter from app.dependencies import get_db from app.module import all_modules from app.types.module import CoreModule @@ -24,6 +25,7 @@ tag="Payments", router=router, factory=None, + user_deleter=user_deleter, ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/core/payment/user_deleter_payment.py b/app/core/payment/user_deleter_payment.py new file mode 100644 index 0000000000..a2befa31c3 --- /dev/null +++ b/app/core/payment/user_deleter_payment.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class PaymentUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = PaymentUserDeleter() diff --git a/app/core/schools/endpoints_schools.py b/app/core/schools/endpoints_schools.py index abb01febc1..c5400590f7 100644 --- a/app/core/schools/endpoints_schools.py +++ b/app/core/schools/endpoints_schools.py @@ -18,6 +18,7 @@ ) from app.core.schools.factory_schools import CoreSchoolsFactory from app.core.schools.schools_type import SchoolType +from app.core.schools.user_deleter_schools import user_deleter from app.core.users import cruds_users, schemas_users from app.dependencies import ( get_db, @@ -32,6 +33,7 @@ tag="Schools", router=router, factory=CoreSchoolsFactory(), + user_deleter=user_deleter, ) diff --git a/app/core/schools/user_deleter_schools.py b/app/core/schools/user_deleter_schools.py new file mode 100644 index 0000000000..7a81ad1780 --- /dev/null +++ b/app/core/schools/user_deleter_schools.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class SchoolsUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = SchoolsUserDeleter() diff --git a/app/core/users/endpoints_users.py b/app/core/users/endpoints_users.py index a3f7cdb39b..1831c3e69c 100644 --- a/app/core/users/endpoints_users.py +++ b/app/core/users/endpoints_users.py @@ -27,6 +27,7 @@ from app.core.users import cruds_users, models_users, schemas_users from app.core.users.factory_users import CoreUsersFactory from app.core.users.tools_users import get_account_type_and_school_id_from_email +from app.core.users.user_deleter_users import user_deleter from app.core.utils import security from app.core.utils.config import Settings from app.dependencies import ( @@ -60,6 +61,7 @@ tag="Users", router=router, factory=CoreUsersFactory(), + user_deleter=user_deleter, ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/core/users/user_deleter_users.py b/app/core/users/user_deleter_users.py new file mode 100644 index 0000000000..aec601f8bd --- /dev/null +++ b/app/core/users/user_deleter_users.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class UsersUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = UsersUserDeleter() diff --git a/app/modules/advert/endpoints_advert.py b/app/modules/advert/endpoints_advert.py index ba973aff1d..5661887333 100644 --- a/app/modules/advert/endpoints_advert.py +++ b/app/modules/advert/endpoints_advert.py @@ -23,6 +23,7 @@ schemas_advert, ) from app.modules.advert.factory_advert import AdvertFactory +from app.modules.advert.user_deleter_advert import user_deleter from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -40,6 +41,7 @@ tag="Advert", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=AdvertFactory(), + user_deleter=user_deleter, ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/advert/user_deleter_advert.py b/app/modules/advert/user_deleter_advert.py new file mode 100644 index 0000000000..dc69cad532 --- /dev/null +++ b/app/modules/advert/user_deleter_advert.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class AdvertUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = AdvertUserDeleter() diff --git a/app/modules/amap/endpoints_amap.py b/app/modules/amap/endpoints_amap.py index 62542cdd67..747adfc2a0 100644 --- a/app/modules/amap/endpoints_amap.py +++ b/app/modules/amap/endpoints_amap.py @@ -21,6 +21,7 @@ from app.modules.amap import cruds_amap, models_amap, schemas_amap from app.modules.amap.factory_amap import AmapFactory from app.modules.amap.types_amap import DeliveryStatusType +from app.modules.amap.user_deleter_amap import user_deleter from app.types.module import Module from app.utils.communication.notifications import NotificationTool from app.utils.redis import locker_get, locker_set @@ -41,6 +42,7 @@ default_allowed_account_types=[AccountType.student, AccountType.staff], registred_topics=[amap_topic], factory=AmapFactory(), + user_deleter=user_deleter, ) hyperion_amap_logger = logging.getLogger("hyperion.amap") diff --git a/app/modules/amap/user_deleter_amap.py b/app/modules/amap/user_deleter_amap.py new file mode 100644 index 0000000000..f85330700b --- /dev/null +++ b/app/modules/amap/user_deleter_amap.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class AmapUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = AmapUserDeleter() diff --git a/app/modules/booking/endpoints_booking.py b/app/modules/booking/endpoints_booking.py index bfe9f7072a..f19790edab 100644 --- a/app/modules/booking/endpoints_booking.py +++ b/app/modules/booking/endpoints_booking.py @@ -23,6 +23,7 @@ ) from app.modules.booking.factory_booking import BookingFactory from app.modules.booking.types_booking import Decision +from app.modules.booking.user_deleter_booking import user_deleter from app.types.module import Module from app.utils.communication.notifications import NotificationTool from app.utils.tools import is_group_id_valid, is_user_member_of_any_group @@ -32,6 +33,7 @@ tag="Booking", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=BookingFactory(), + user_deleter=user_deleter, ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/booking/user_deleter_booking.py b/app/modules/booking/user_deleter_booking.py new file mode 100644 index 0000000000..9ac589ddb3 --- /dev/null +++ b/app/modules/booking/user_deleter_booking.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class BookingUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = BookingUserDeleter() diff --git a/app/modules/calendar/endpoints_calendar.py b/app/modules/calendar/endpoints_calendar.py index 3b69bbee15..560e862457 100644 --- a/app/modules/calendar/endpoints_calendar.py +++ b/app/modules/calendar/endpoints_calendar.py @@ -15,6 +15,7 @@ ) from app.modules.calendar.factory_calendar import CalendarFactory from app.modules.calendar.types_calendar import Decision +from app.modules.calendar.user_deleter_calendar import user_deleter from app.types.module import Module from app.utils.tools import is_user_member_of_any_group @@ -23,6 +24,7 @@ tag="Calendar", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=CalendarFactory(), + user_deleter=user_deleter, ) ical_file_path = "data/ics/ae_calendar.ics" diff --git a/app/modules/calendar/user_deleter_calendar.py b/app/modules/calendar/user_deleter_calendar.py new file mode 100644 index 0000000000..cd775c6552 --- /dev/null +++ b/app/modules/calendar/user_deleter_calendar.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class CalendarUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = CalendarUserDeleter() diff --git a/app/modules/campaign/endpoints_campaign.py b/app/modules/campaign/endpoints_campaign.py index a638310927..51506d41bb 100644 --- a/app/modules/campaign/endpoints_campaign.py +++ b/app/modules/campaign/endpoints_campaign.py @@ -24,6 +24,7 @@ ) from app.modules.campaign.factory_campaign import CampaignFactory from app.modules.campaign.types_campaign import ListType, StatusType +from app.modules.campaign.user_deleter_campaign import user_deleter from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -38,6 +39,7 @@ tag="Campaign", default_allowed_groups_ids=[GroupType.AE], factory=CampaignFactory(), + user_deleter=user_deleter, ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/campaign/user_deleter_campaign.py b/app/modules/campaign/user_deleter_campaign.py new file mode 100644 index 0000000000..fdf4238ff6 --- /dev/null +++ b/app/modules/campaign/user_deleter_campaign.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class CampaignUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = CampaignUserDeleter() diff --git a/app/modules/cdr/endpoints_cdr.py b/app/modules/cdr/endpoints_cdr.py index 66cb82c025..19f2215a73 100644 --- a/app/modules/cdr/endpoints_cdr.py +++ b/app/modules/cdr/endpoints_cdr.py @@ -41,6 +41,7 @@ CdrStatus, DocumentSignatureType, ) +from app.modules.cdr.user_deleter_cdr import user_deleter from app.modules.cdr.utils_cdr import ( check_request_consistency, construct_dataframe_from_users_purchases, @@ -68,6 +69,7 @@ payment_callback=validate_payment, default_allowed_groups_ids=[GroupType.admin_cdr], factory=None, + user_deleter=user_deleter, ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/cdr/user_deleter_cdr.py b/app/modules/cdr/user_deleter_cdr.py new file mode 100644 index 0000000000..ee0a29b120 --- /dev/null +++ b/app/modules/cdr/user_deleter_cdr.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class CdrUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = CdrUserDeleter() diff --git a/app/modules/centralisation/endpoints_centralisation.py b/app/modules/centralisation/endpoints_centralisation.py index 62338ca6c4..e3f044aacd 100644 --- a/app/modules/centralisation/endpoints_centralisation.py +++ b/app/modules/centralisation/endpoints_centralisation.py @@ -1,4 +1,5 @@ from app.core.groups.groups_type import AccountType +from app.modules.centralisation.user_deleter_centralisation import user_deleter from app.types.module import Module module = Module( @@ -6,4 +7,5 @@ tag="Centralisation", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, + user_deleter=user_deleter, ) diff --git a/app/modules/centralisation/user_deleter_centralisation.py b/app/modules/centralisation/user_deleter_centralisation.py new file mode 100644 index 0000000000..d199d05887 --- /dev/null +++ b/app/modules/centralisation/user_deleter_centralisation.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class CentralisationUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = CentralisationUserDeleter() diff --git a/app/modules/cinema/endpoints_cinema.py b/app/modules/cinema/endpoints_cinema.py index 94dccf89c3..befcf8ba0a 100644 --- a/app/modules/cinema/endpoints_cinema.py +++ b/app/modules/cinema/endpoints_cinema.py @@ -21,6 +21,7 @@ ) from app.modules.cinema import cruds_cinema, schemas_cinema from app.modules.cinema.factory_cinema import CinemaFactory +from app.modules.cinema.user_deleter_cinema import user_deleter from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -48,6 +49,7 @@ default_allowed_account_types=[AccountType.student, AccountType.staff], registred_topics=[cinema_topic], factory=CinemaFactory(), + user_deleter=user_deleter, ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/cinema/user_deleter_cinema.py b/app/modules/cinema/user_deleter_cinema.py new file mode 100644 index 0000000000..eec267f91c --- /dev/null +++ b/app/modules/cinema/user_deleter_cinema.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class CinemaUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = CinemaUserDeleter() diff --git a/app/modules/flappybird/endpoints_flappybird.py b/app/modules/flappybird/endpoints_flappybird.py index 78a0a7eb59..d1945a1a8f 100644 --- a/app/modules/flappybird/endpoints_flappybird.py +++ b/app/modules/flappybird/endpoints_flappybird.py @@ -12,6 +12,7 @@ models_flappybird, schemas_flappybird, ) +from app.modules.flappybird.user_deleter_flappybird import user_deleter from app.types.module import Module module = Module( @@ -19,6 +20,7 @@ tag="Flappy Bird", default_allowed_account_types=[AccountType.student], factory=None, + user_deleter=user_deleter, ) diff --git a/app/modules/flappybird/user_deleter_flappybird.py b/app/modules/flappybird/user_deleter_flappybird.py new file mode 100644 index 0000000000..0d270e0407 --- /dev/null +++ b/app/modules/flappybird/user_deleter_flappybird.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class FlappybirdUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = FlappybirdUserDeleter() diff --git a/app/modules/home/endpoints_home.py b/app/modules/home/endpoints_home.py index ed46d295cb..13cbe2305d 100644 --- a/app/modules/home/endpoints_home.py +++ b/app/modules/home/endpoints_home.py @@ -1,4 +1,5 @@ from app.core.groups.groups_type import AccountType +from app.modules.home.user_deleter_home import user_deleter from app.types.module import Module module = Module( @@ -6,4 +7,5 @@ tag="Home", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, + user_deleter=user_deleter, ) diff --git a/app/modules/home/user_deleter_home.py b/app/modules/home/user_deleter_home.py new file mode 100644 index 0000000000..5239833711 --- /dev/null +++ b/app/modules/home/user_deleter_home.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class HomeUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = HomeUserDeleter() diff --git a/app/modules/loan/endpoints_loan.py b/app/modules/loan/endpoints_loan.py index 9874bd4fb9..a844765262 100644 --- a/app/modules/loan/endpoints_loan.py +++ b/app/modules/loan/endpoints_loan.py @@ -18,6 +18,7 @@ ) from app.modules.loan import cruds_loan, models_loan, schemas_loan from app.modules.loan.factory_loan import LoanFactory +from app.modules.loan.user_deleter_loan import user_deleter from app.types.module import Module from app.types.scheduler import Scheduler from app.utils.communication.notifications import NotificationTool @@ -36,6 +37,7 @@ tag="Loans", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=LoanFactory(), + user_deleter=user_deleter, ) diff --git a/app/modules/loan/user_deleter_loan.py b/app/modules/loan/user_deleter_loan.py new file mode 100644 index 0000000000..1ee2fc8b3c --- /dev/null +++ b/app/modules/loan/user_deleter_loan.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class LoanUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = LoanUserDeleter() diff --git a/app/modules/ph/endpoints_ph.py b/app/modules/ph/endpoints_ph.py index b23f87450f..71c27c01e5 100644 --- a/app/modules/ph/endpoints_ph.py +++ b/app/modules/ph/endpoints_ph.py @@ -17,6 +17,7 @@ is_user_in, ) from app.modules.ph import cruds_ph, models_ph, schemas_ph +from app.modules.ph.user_deleter_ph import user_deleter from app.types.content_type import ContentType from app.types.module import Module from app.types.scheduler import Scheduler @@ -43,6 +44,7 @@ default_allowed_account_types=[AccountType.student], registred_topics=[ph_topic], factory=None, + user_deleter=user_deleter, ) diff --git a/app/modules/ph/user_deleter_ph.py b/app/modules/ph/user_deleter_ph.py new file mode 100644 index 0000000000..5004b3cc31 --- /dev/null +++ b/app/modules/ph/user_deleter_ph.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class PHUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = PHUserDeleter() diff --git a/app/modules/phonebook/endpoints_phonebook.py b/app/modules/phonebook/endpoints_phonebook.py index f4406569ed..03432bbb8f 100644 --- a/app/modules/phonebook/endpoints_phonebook.py +++ b/app/modules/phonebook/endpoints_phonebook.py @@ -20,6 +20,7 @@ ) from app.modules.phonebook.factory_phonebook import PhonebookFactory from app.modules.phonebook.types_phonebook import RoleTags +from app.modules.phonebook.user_deleter_phonebook import user_deleter from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -34,6 +35,7 @@ tag="Phonebook", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=PhonebookFactory(), + user_deleter=user_deleter, ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/phonebook/user_deleter_phonebook.py b/app/modules/phonebook/user_deleter_phonebook.py new file mode 100644 index 0000000000..46e6fb62b4 --- /dev/null +++ b/app/modules/phonebook/user_deleter_phonebook.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class PhonebookUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = PhonebookUserDeleter() diff --git a/app/modules/purchases/endpoints_purchases.py b/app/modules/purchases/endpoints_purchases.py index 2891eb86dd..f4ab9813c5 100644 --- a/app/modules/purchases/endpoints_purchases.py +++ b/app/modules/purchases/endpoints_purchases.py @@ -1,4 +1,5 @@ from app.core.groups.groups_type import AccountType +from app.modules.purchases.user_deleter_purchases import user_deleter from app.types.module import Module module = Module( @@ -6,4 +7,5 @@ tag="Purchases", default_allowed_account_types=[AccountType.student, AccountType.external], factory=None, + user_deleter=user_deleter, ) diff --git a/app/modules/purchases/user_deleter_purchases.py b/app/modules/purchases/user_deleter_purchases.py new file mode 100644 index 0000000000..56e3d60d1d --- /dev/null +++ b/app/modules/purchases/user_deleter_purchases.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class PurchasesUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = PurchasesUserDeleter() diff --git a/app/modules/raffle/endpoints_raffle.py b/app/modules/raffle/endpoints_raffle.py index 500f106ae1..4a356d03f6 100644 --- a/app/modules/raffle/endpoints_raffle.py +++ b/app/modules/raffle/endpoints_raffle.py @@ -19,6 +19,7 @@ ) from app.modules.raffle import cruds_raffle, models_raffle, schemas_raffle from app.modules.raffle.types_raffle import RaffleStatusType +from app.modules.raffle.user_deleter_raffle import user_deleter from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -34,6 +35,7 @@ tag="Raffle", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, + user_deleter=user_deleter, ) hyperion_raffle_logger = logging.getLogger("hyperion.raffle") diff --git a/app/modules/raffle/user_deleter_raffle.py b/app/modules/raffle/user_deleter_raffle.py new file mode 100644 index 0000000000..a04c00f797 --- /dev/null +++ b/app/modules/raffle/user_deleter_raffle.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class RaffleUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = RaffleUserDeleter() diff --git a/app/modules/raid/endpoints_raid.py b/app/modules/raid/endpoints_raid.py index 9b866fd244..ceeb2218a4 100644 --- a/app/modules/raid/endpoints_raid.py +++ b/app/modules/raid/endpoints_raid.py @@ -19,6 +19,7 @@ ) from app.modules.raid import coredata_raid, cruds_raid, models_raid, schemas_raid from app.modules.raid.raid_type import DocumentType, DocumentValidation, Size +from app.modules.raid.user_deleter_raid import user_deleter from app.modules.raid.utils.utils_raid import ( calculate_raid_payment, get_all_security_files_zip, @@ -48,6 +49,7 @@ payment_callback=validate_payment, default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, + user_deleter=user_deleter, ) diff --git a/app/modules/raid/user_deleter_raid.py b/app/modules/raid/user_deleter_raid.py new file mode 100644 index 0000000000..e2b1e9ec07 --- /dev/null +++ b/app/modules/raid/user_deleter_raid.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class RaidUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = RaidUserDeleter() diff --git a/app/modules/recommendation/endpoints_recommendation.py b/app/modules/recommendation/endpoints_recommendation.py index ff8b96c447..21bb38d7df 100644 --- a/app/modules/recommendation/endpoints_recommendation.py +++ b/app/modules/recommendation/endpoints_recommendation.py @@ -18,6 +18,7 @@ schemas_recommendation, ) from app.modules.recommendation.factory_recommendation import RecommendationFactory +from app.modules.recommendation.user_deleter_recommendation import user_deleter from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -31,6 +32,7 @@ tag="Recommendation", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=RecommendationFactory(), + user_deleter=user_deleter, ) diff --git a/app/modules/recommendation/user_deleter_recommendation.py b/app/modules/recommendation/user_deleter_recommendation.py new file mode 100644 index 0000000000..e84d3737a1 --- /dev/null +++ b/app/modules/recommendation/user_deleter_recommendation.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class RecommendationUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = RecommendationUserDeleter() diff --git a/app/modules/seed_library/endpoints_seed_library.py b/app/modules/seed_library/endpoints_seed_library.py index d979ecf88e..c63f82acaf 100644 --- a/app/modules/seed_library/endpoints_seed_library.py +++ b/app/modules/seed_library/endpoints_seed_library.py @@ -19,6 +19,7 @@ ) from app.modules.seed_library.factory_seed_library import SeedLibraryFactory from app.modules.seed_library.types_seed_library import PlantState, SpeciesType +from app.modules.seed_library.user_deleter_seed_library import user_deleter from app.types.module import Module from app.utils import tools from app.utils.tools import is_user_member_of_any_group @@ -28,6 +29,7 @@ tag="seed_library", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=SeedLibraryFactory(), + user_deleter=user_deleter, ) diff --git a/app/modules/seed_library/user_deleter_seed_library.py b/app/modules/seed_library/user_deleter_seed_library.py new file mode 100644 index 0000000000..87aff02e53 --- /dev/null +++ b/app/modules/seed_library/user_deleter_seed_library.py @@ -0,0 +1,12 @@ +from app.types.module_user_deleter import ModuleUserDeleter + + +class SeedLibraryUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = SeedLibraryUserDeleter() diff --git a/app/types/module.py b/app/types/module.py index 185e3c60fa..262b711811 100644 --- a/app/types/module.py +++ b/app/types/module.py @@ -7,6 +7,7 @@ from app.core.notification.schemas_notification import Topic from app.core.payment import schemas_payment from app.types.factory import Factory +from app.types.module_user_deleter import ModuleUserDeleter class CoreModule: @@ -15,6 +16,7 @@ def __init__( root: str, tag: str, factory: Factory | None, + user_deleter: ModuleUserDeleter, router: APIRouter | None = None, payment_callback: Callable[ [schemas_payment.CheckoutPayment, AsyncSession], @@ -28,12 +30,14 @@ def __init__( :param root: the root of the module, used by Titan :param tag: the tag of the module, used by FastAPI :param factory: a factory to use to create fake data for the module (development purpose) + :param user_deleter: a ModuleUserDeleter to handle user deletion :param router: an optional custom APIRouter :param payment_callback: an optional method to call when a payment is notified by HelloAsso. A CheckoutPayment and the database will be provided during the call :param registred_topics: an optionnal list of Topics that should be registered by the module. Modules can also register topics dynamically. Once the Topic was registred, removing it from this list won't delete it """ self.root = root + self.user_deleter = user_deleter self.router = router or APIRouter(tags=[tag]) self.payment_callback: ( Callable[[schemas_payment.CheckoutPayment, AsyncSession], Awaitable[None]] @@ -49,6 +53,7 @@ def __init__( root: str, tag: str, factory: Factory | None, + user_deleter: ModuleUserDeleter, default_allowed_groups_ids: list[GroupType] | None = None, default_allowed_account_types: list[AccountType] | None = None, router: APIRouter | None = None, @@ -64,6 +69,7 @@ def __init__( :param root: the root of the module, used by Titan :param tag: the tag of the module, used by FastAPI :param factory: a factory to use to create fake data for the module (development purpose) + :param user_deleter: a ModuleUserDeleter to handle user deletion :param default_allowed_groups_ids: list of groups that should be able to see the module by default :param default_allowed_account_types: list of account_types that should be able to see the module by default :param router: an optional custom APIRouter @@ -72,6 +78,7 @@ def __init__( Once the Topic was registred, removing it from this list won't delete it """ self.root = root + self.user_deleter = user_deleter self.default_allowed_groups_ids = default_allowed_groups_ids self.default_allowed_account_types = default_allowed_account_types self.router = router or APIRouter(tags=[tag]) diff --git a/app/types/module_user_deleter.py b/app/types/module_user_deleter.py new file mode 100644 index 0000000000..91129fb979 --- /dev/null +++ b/app/types/module_user_deleter.py @@ -0,0 +1,40 @@ +from abc import ABC, abstractmethod + + +class ModuleUserDeleter(ABC): + """ + Abstract base class for user deletion functionality. + This class defines the interface for deleting users from the system. + Each module should implement this interface to provide its own user deletion logic. + """ + + @abstractmethod + def can_delete_user(self, user_id) -> bool: + """ + Check if the user can be deleted. + :param user_id: The ID of the user to check. + :return: True if the user can be deleted, False otherwise. + """ + + @abstractmethod + def delete_user(self, user_id) -> None: + """ + Delete the user from the system. + :param user_id: The ID of the user to delete. + """ + + +""" +from app.types.module_user_deleter import ModuleUserDeleter + + +class CoreUserDeleter(ModuleUserDeleter): + def can_delete_user(self, user_id) -> bool: + return True + + def delete_user(self, user_id) -> None: + pass + + +user_deleter = CoreUserDeleter() +""" From 969cae2b9682b503906c1bff2350a7b02fb97159 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 30 Nov 2025 04:38:12 +0100 Subject: [PATCH 2/7] Fix: change deleter usage --- app/core/auth/endpoints_auth.py | 4 ++-- app/core/core_endpoints/endpoints_core.py | 4 ++-- app/core/google_api/endpoints_google_api.py | 6 ++++-- app/core/groups/endpoints_groups.py | 4 ++-- app/core/memberships/endpoints_memberships.py | 8 ++++---- .../notification/endpoints_notification.py | 6 ++++-- app/core/payment/endpoints_payment.py | 8 +++----- app/core/schools/endpoints_schools.py | 4 ++-- app/core/users/endpoints_users.py | 4 ++-- app/modules/advert/endpoints_advert.py | 4 ++-- app/modules/amap/endpoints_amap.py | 4 ++-- app/modules/booking/endpoints_booking.py | 4 ++-- app/modules/calendar/endpoints_calendar.py | 4 ++-- app/modules/campaign/endpoints_campaign.py | 4 ++-- app/modules/cdr/endpoints_cdr.py | 4 ++-- .../endpoints_centralisation.py | 6 ++++-- app/modules/cinema/endpoints_cinema.py | 4 ++-- .../flappybird/endpoints_flappybird.py | 4 ++-- app/modules/home/endpoints_home.py | 4 ++-- app/modules/loan/endpoints_loan.py | 4 ++-- app/modules/ph/endpoints_ph.py | 4 ++-- app/modules/phonebook/endpoints_phonebook.py | 4 ++-- app/modules/purchases/endpoints_purchases.py | 6 ++++-- app/modules/raffle/endpoints_raffle.py | 4 ++-- app/modules/raid/endpoints_raid.py | 4 ++-- .../endpoints_recommendation.py | 6 ++++-- .../seed_library/endpoints_seed_library.py | 6 ++++-- app/types/module_user_deleter.py | 19 ++++++++++++++----- 28 files changed, 83 insertions(+), 64 deletions(-) diff --git a/app/core/auth/endpoints_auth.py b/app/core/auth/endpoints_auth.py index eca7fef9fa..d99e1b3728 100644 --- a/app/core/auth/endpoints_auth.py +++ b/app/core/auth/endpoints_auth.py @@ -21,7 +21,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.auth import cruds_auth, models_auth, schemas_auth -from app.core.auth.user_deleter_auth import user_deleter +from app.core.auth.user_deleter_auth import AuthUserDeleter from app.core.users import cruds_users, models_users from app.core.utils.config import Settings from app.core.utils.security import ( @@ -52,7 +52,7 @@ tag="Auth", router=router, factory=None, - user_deleter=user_deleter, + user_deleter=AuthUserDeleter(), ) templates = Jinja2Templates(directory="assets/templates") diff --git a/app/core/core_endpoints/endpoints_core.py b/app/core/core_endpoints/endpoints_core.py index b858a47d0f..3dbef10a24 100644 --- a/app/core/core_endpoints/endpoints_core.py +++ b/app/core/core_endpoints/endpoints_core.py @@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.core_endpoints import cruds_core, models_core, schemas_core -from app.core.core_endpoints.user_deleter_core import user_deleter +from app.core.core_endpoints.user_deleter_core import CoreUserDeleter from app.core.groups.groups_type import AccountType, GroupType from app.core.users import models_users from app.core.utils.config import Settings @@ -27,7 +27,7 @@ tag="Core", router=router, factory=None, - user_deleter=user_deleter, + user_deleter=CoreUserDeleter(), ) diff --git a/app/core/google_api/endpoints_google_api.py b/app/core/google_api/endpoints_google_api.py index be0e722e56..d4d2adf1f3 100644 --- a/app/core/google_api/endpoints_google_api.py +++ b/app/core/google_api/endpoints_google_api.py @@ -4,7 +4,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.google_api.google_api import GoogleAPI -from app.core.google_api.user_deleter_google_api import user_deleter +from app.core.google_api.user_deleter_google_api import ( + GoogleAPIUserDeleter, +) from app.core.utils.config import Settings from app.dependencies import ( get_db, @@ -19,7 +21,7 @@ tag="GoogleAPI", router=router, factory=None, - user_deleter=user_deleter, + user_deleter=GoogleAPIUserDeleter(), ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/core/groups/endpoints_groups.py b/app/core/groups/endpoints_groups.py index 319ffd4cb1..e1b07afd0e 100644 --- a/app/core/groups/endpoints_groups.py +++ b/app/core/groups/endpoints_groups.py @@ -13,7 +13,7 @@ from app.core.groups import cruds_groups, models_groups, schemas_groups from app.core.groups.factory_groups import CoreGroupsFactory from app.core.groups.groups_type import GroupType -from app.core.groups.user_deleter_groups import user_deleter +from app.core.groups.user_deleter_groups import GroupsUserDeleter from app.core.notification.utils_notification import get_topics_restricted_to_group_id from app.core.users import cruds_users from app.dependencies import ( @@ -33,7 +33,7 @@ tag="Groups", router=router, factory=CoreGroupsFactory(), - user_deleter=user_deleter, + user_deleter=GroupsUserDeleter(), ) hyperion_security_logger = logging.getLogger("hyperion.security") diff --git a/app/core/memberships/endpoints_memberships.py b/app/core/memberships/endpoints_memberships.py index 80937ed780..4550633e04 100644 --- a/app/core/memberships/endpoints_memberships.py +++ b/app/core/memberships/endpoints_memberships.py @@ -12,10 +12,10 @@ schemas_memberships, ) from app.core.memberships.factory_memberships import CoreMembershipsFactory -from app.core.memberships.user_deleter_memberships import user_deleter -from app.core.memberships.utils_memberships import ( - validate_user_new_membership, +from app.core.memberships.user_deleter_memberships import ( + MembershipsUserDeleter, ) +from app.core.memberships.utils_memberships import validate_user_new_membership from app.core.users import cruds_users, models_users, schemas_users from app.dependencies import ( get_db, @@ -34,7 +34,7 @@ tag="Memberships", router=router, factory=CoreMembershipsFactory(), - user_deleter=user_deleter, + user_deleter=MembershipsUserDeleter(), ) diff --git a/app/core/notification/endpoints_notification.py b/app/core/notification/endpoints_notification.py index 5b1bf68590..1e88dee7ec 100644 --- a/app/core/notification/endpoints_notification.py +++ b/app/core/notification/endpoints_notification.py @@ -10,7 +10,9 @@ models_notification, schemas_notification, ) -from app.core.notification.user_deleter_notification import user_deleter +from app.core.notification.user_deleter_notification import ( + NotificationUserDeleter, +) from app.core.notification.utils_notification import get_user_notification_topics from app.core.users import models_users from app.dependencies import ( @@ -43,7 +45,7 @@ router=router, registred_topics=[notification_test_topic], factory=None, - user_deleter=user_deleter, + user_deleter=NotificationUserDeleter(), ) diff --git a/app/core/payment/endpoints_payment.py b/app/core/payment/endpoints_payment.py index c12b47b392..36d93df055 100644 --- a/app/core/payment/endpoints_payment.py +++ b/app/core/payment/endpoints_payment.py @@ -10,10 +10,8 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.payment import cruds_payment, models_payment, schemas_payment -from app.core.payment.types_payment import ( - NotificationResultContent, -) -from app.core.payment.user_deleter_payment import user_deleter +from app.core.payment.types_payment import NotificationResultContent +from app.core.payment.user_deleter_payment import PaymentUserDeleter from app.dependencies import get_db from app.module import all_modules from app.types.module import CoreModule @@ -25,7 +23,7 @@ tag="Payments", router=router, factory=None, - user_deleter=user_deleter, + user_deleter=PaymentUserDeleter(), ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/core/schools/endpoints_schools.py b/app/core/schools/endpoints_schools.py index c5400590f7..ac8ce14df5 100644 --- a/app/core/schools/endpoints_schools.py +++ b/app/core/schools/endpoints_schools.py @@ -18,7 +18,7 @@ ) from app.core.schools.factory_schools import CoreSchoolsFactory from app.core.schools.schools_type import SchoolType -from app.core.schools.user_deleter_schools import user_deleter +from app.core.schools.user_deleter_schools import SchoolsUserDeleter from app.core.users import cruds_users, schemas_users from app.dependencies import ( get_db, @@ -33,7 +33,7 @@ tag="Schools", router=router, factory=CoreSchoolsFactory(), - user_deleter=user_deleter, + user_deleter=SchoolsUserDeleter(), ) diff --git a/app/core/users/endpoints_users.py b/app/core/users/endpoints_users.py index 1831c3e69c..4fa8cb0a4b 100644 --- a/app/core/users/endpoints_users.py +++ b/app/core/users/endpoints_users.py @@ -27,7 +27,7 @@ from app.core.users import cruds_users, models_users, schemas_users from app.core.users.factory_users import CoreUsersFactory from app.core.users.tools_users import get_account_type_and_school_id_from_email -from app.core.users.user_deleter_users import user_deleter +from app.core.users.user_deleter_users import UsersUserDeleter from app.core.utils import security from app.core.utils.config import Settings from app.dependencies import ( @@ -61,7 +61,7 @@ tag="Users", router=router, factory=CoreUsersFactory(), - user_deleter=user_deleter, + user_deleter=UsersUserDeleter(), ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/advert/endpoints_advert.py b/app/modules/advert/endpoints_advert.py index 5661887333..82267eb7b6 100644 --- a/app/modules/advert/endpoints_advert.py +++ b/app/modules/advert/endpoints_advert.py @@ -23,7 +23,7 @@ schemas_advert, ) from app.modules.advert.factory_advert import AdvertFactory -from app.modules.advert.user_deleter_advert import user_deleter +from app.modules.advert.user_deleter_advert import AdvertUserDeleter from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -41,7 +41,7 @@ tag="Advert", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=AdvertFactory(), - user_deleter=user_deleter, + user_deleter=AdvertUserDeleter(), ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/amap/endpoints_amap.py b/app/modules/amap/endpoints_amap.py index 747adfc2a0..288655324c 100644 --- a/app/modules/amap/endpoints_amap.py +++ b/app/modules/amap/endpoints_amap.py @@ -21,7 +21,7 @@ from app.modules.amap import cruds_amap, models_amap, schemas_amap from app.modules.amap.factory_amap import AmapFactory from app.modules.amap.types_amap import DeliveryStatusType -from app.modules.amap.user_deleter_amap import user_deleter +from app.modules.amap.user_deleter_amap import AmapUserDeleter from app.types.module import Module from app.utils.communication.notifications import NotificationTool from app.utils.redis import locker_get, locker_set @@ -42,7 +42,7 @@ default_allowed_account_types=[AccountType.student, AccountType.staff], registred_topics=[amap_topic], factory=AmapFactory(), - user_deleter=user_deleter, + user_deleter=AmapUserDeleter(), ) hyperion_amap_logger = logging.getLogger("hyperion.amap") diff --git a/app/modules/booking/endpoints_booking.py b/app/modules/booking/endpoints_booking.py index f19790edab..da6f5f65bb 100644 --- a/app/modules/booking/endpoints_booking.py +++ b/app/modules/booking/endpoints_booking.py @@ -23,7 +23,7 @@ ) from app.modules.booking.factory_booking import BookingFactory from app.modules.booking.types_booking import Decision -from app.modules.booking.user_deleter_booking import user_deleter +from app.modules.booking.user_deleter_booking import BookingUserDeleter from app.types.module import Module from app.utils.communication.notifications import NotificationTool from app.utils.tools import is_group_id_valid, is_user_member_of_any_group @@ -33,7 +33,7 @@ tag="Booking", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=BookingFactory(), - user_deleter=user_deleter, + user_deleter=BookingUserDeleter(), ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/calendar/endpoints_calendar.py b/app/modules/calendar/endpoints_calendar.py index 560e862457..ecc8d5a3c6 100644 --- a/app/modules/calendar/endpoints_calendar.py +++ b/app/modules/calendar/endpoints_calendar.py @@ -15,7 +15,7 @@ ) from app.modules.calendar.factory_calendar import CalendarFactory from app.modules.calendar.types_calendar import Decision -from app.modules.calendar.user_deleter_calendar import user_deleter +from app.modules.calendar.user_deleter_calendar import CalendarUserDeleter from app.types.module import Module from app.utils.tools import is_user_member_of_any_group @@ -24,7 +24,7 @@ tag="Calendar", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=CalendarFactory(), - user_deleter=user_deleter, + user_deleter=CalendarUserDeleter(), ) ical_file_path = "data/ics/ae_calendar.ics" diff --git a/app/modules/campaign/endpoints_campaign.py b/app/modules/campaign/endpoints_campaign.py index 51506d41bb..16006a0677 100644 --- a/app/modules/campaign/endpoints_campaign.py +++ b/app/modules/campaign/endpoints_campaign.py @@ -24,7 +24,7 @@ ) from app.modules.campaign.factory_campaign import CampaignFactory from app.modules.campaign.types_campaign import ListType, StatusType -from app.modules.campaign.user_deleter_campaign import user_deleter +from app.modules.campaign.user_deleter_campaign import CampaignUserDeleter from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -39,7 +39,7 @@ tag="Campaign", default_allowed_groups_ids=[GroupType.AE], factory=CampaignFactory(), - user_deleter=user_deleter, + user_deleter=CampaignUserDeleter(), ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/cdr/endpoints_cdr.py b/app/modules/cdr/endpoints_cdr.py index 19f2215a73..0b23400623 100644 --- a/app/modules/cdr/endpoints_cdr.py +++ b/app/modules/cdr/endpoints_cdr.py @@ -41,7 +41,7 @@ CdrStatus, DocumentSignatureType, ) -from app.modules.cdr.user_deleter_cdr import user_deleter +from app.modules.cdr.user_deleter_cdr import CdrUserDeleter from app.modules.cdr.utils_cdr import ( check_request_consistency, construct_dataframe_from_users_purchases, @@ -69,7 +69,7 @@ payment_callback=validate_payment, default_allowed_groups_ids=[GroupType.admin_cdr], factory=None, - user_deleter=user_deleter, + user_deleter=CdrUserDeleter(), ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/centralisation/endpoints_centralisation.py b/app/modules/centralisation/endpoints_centralisation.py index e3f044aacd..e809628835 100644 --- a/app/modules/centralisation/endpoints_centralisation.py +++ b/app/modules/centralisation/endpoints_centralisation.py @@ -1,5 +1,7 @@ from app.core.groups.groups_type import AccountType -from app.modules.centralisation.user_deleter_centralisation import user_deleter +from app.modules.centralisation.user_deleter_centralisation import ( + CentralisationUserDeleter, +) from app.types.module import Module module = Module( @@ -7,5 +9,5 @@ tag="Centralisation", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, - user_deleter=user_deleter, + user_deleter=CentralisationUserDeleter(), ) diff --git a/app/modules/cinema/endpoints_cinema.py b/app/modules/cinema/endpoints_cinema.py index befcf8ba0a..6711494881 100644 --- a/app/modules/cinema/endpoints_cinema.py +++ b/app/modules/cinema/endpoints_cinema.py @@ -21,7 +21,7 @@ ) from app.modules.cinema import cruds_cinema, schemas_cinema from app.modules.cinema.factory_cinema import CinemaFactory -from app.modules.cinema.user_deleter_cinema import user_deleter +from app.modules.cinema.user_deleter_cinema import CinemaUserDeleter from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -49,7 +49,7 @@ default_allowed_account_types=[AccountType.student, AccountType.staff], registred_topics=[cinema_topic], factory=CinemaFactory(), - user_deleter=user_deleter, + user_deleter=CinemaUserDeleter(), ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/flappybird/endpoints_flappybird.py b/app/modules/flappybird/endpoints_flappybird.py index d1945a1a8f..b1dace378a 100644 --- a/app/modules/flappybird/endpoints_flappybird.py +++ b/app/modules/flappybird/endpoints_flappybird.py @@ -12,7 +12,7 @@ models_flappybird, schemas_flappybird, ) -from app.modules.flappybird.user_deleter_flappybird import user_deleter +from app.modules.flappybird.user_deleter_flappybird import FlappybirdUserDeleter from app.types.module import Module module = Module( @@ -20,7 +20,7 @@ tag="Flappy Bird", default_allowed_account_types=[AccountType.student], factory=None, - user_deleter=user_deleter, + user_deleter=FlappybirdUserDeleter(), ) diff --git a/app/modules/home/endpoints_home.py b/app/modules/home/endpoints_home.py index 13cbe2305d..f2294f782c 100644 --- a/app/modules/home/endpoints_home.py +++ b/app/modules/home/endpoints_home.py @@ -1,5 +1,5 @@ from app.core.groups.groups_type import AccountType -from app.modules.home.user_deleter_home import user_deleter +from app.modules.home.user_deleter_home import HomeUserDeleter from app.types.module import Module module = Module( @@ -7,5 +7,5 @@ tag="Home", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, - user_deleter=user_deleter, + user_deleter=HomeUserDeleter(), ) diff --git a/app/modules/loan/endpoints_loan.py b/app/modules/loan/endpoints_loan.py index a844765262..2f8967a9a2 100644 --- a/app/modules/loan/endpoints_loan.py +++ b/app/modules/loan/endpoints_loan.py @@ -18,7 +18,7 @@ ) from app.modules.loan import cruds_loan, models_loan, schemas_loan from app.modules.loan.factory_loan import LoanFactory -from app.modules.loan.user_deleter_loan import user_deleter +from app.modules.loan.user_deleter_loan import LoanUserDeleter from app.types.module import Module from app.types.scheduler import Scheduler from app.utils.communication.notifications import NotificationTool @@ -37,7 +37,7 @@ tag="Loans", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=LoanFactory(), - user_deleter=user_deleter, + user_deleter=LoanUserDeleter(), ) diff --git a/app/modules/ph/endpoints_ph.py b/app/modules/ph/endpoints_ph.py index 71c27c01e5..5b7fe55603 100644 --- a/app/modules/ph/endpoints_ph.py +++ b/app/modules/ph/endpoints_ph.py @@ -17,7 +17,7 @@ is_user_in, ) from app.modules.ph import cruds_ph, models_ph, schemas_ph -from app.modules.ph.user_deleter_ph import user_deleter +from app.modules.ph.user_deleter_ph import PHUserDeleter from app.types.content_type import ContentType from app.types.module import Module from app.types.scheduler import Scheduler @@ -44,7 +44,7 @@ default_allowed_account_types=[AccountType.student], registred_topics=[ph_topic], factory=None, - user_deleter=user_deleter, + user_deleter=PHUserDeleter(), ) diff --git a/app/modules/phonebook/endpoints_phonebook.py b/app/modules/phonebook/endpoints_phonebook.py index 03432bbb8f..d0d6af07f4 100644 --- a/app/modules/phonebook/endpoints_phonebook.py +++ b/app/modules/phonebook/endpoints_phonebook.py @@ -20,7 +20,7 @@ ) from app.modules.phonebook.factory_phonebook import PhonebookFactory from app.modules.phonebook.types_phonebook import RoleTags -from app.modules.phonebook.user_deleter_phonebook import user_deleter +from app.modules.phonebook.user_deleter_phonebook import PhonebookUserDeleter from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -35,7 +35,7 @@ tag="Phonebook", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=PhonebookFactory(), - user_deleter=user_deleter, + user_deleter=PhonebookUserDeleter(), ) hyperion_error_logger = logging.getLogger("hyperion.error") diff --git a/app/modules/purchases/endpoints_purchases.py b/app/modules/purchases/endpoints_purchases.py index f4ab9813c5..f7f4fdefd8 100644 --- a/app/modules/purchases/endpoints_purchases.py +++ b/app/modules/purchases/endpoints_purchases.py @@ -1,5 +1,7 @@ from app.core.groups.groups_type import AccountType -from app.modules.purchases.user_deleter_purchases import user_deleter +from app.modules.purchases.user_deleter_purchases import ( + PurchasesUserDeleter, +) from app.types.module import Module module = Module( @@ -7,5 +9,5 @@ tag="Purchases", default_allowed_account_types=[AccountType.student, AccountType.external], factory=None, - user_deleter=user_deleter, + user_deleter=PurchasesUserDeleter(), ) diff --git a/app/modules/raffle/endpoints_raffle.py b/app/modules/raffle/endpoints_raffle.py index 4a356d03f6..42ce01b8da 100644 --- a/app/modules/raffle/endpoints_raffle.py +++ b/app/modules/raffle/endpoints_raffle.py @@ -19,7 +19,7 @@ ) from app.modules.raffle import cruds_raffle, models_raffle, schemas_raffle from app.modules.raffle.types_raffle import RaffleStatusType -from app.modules.raffle.user_deleter_raffle import user_deleter +from app.modules.raffle.user_deleter_raffle import RaffleUserDeleter from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -35,7 +35,7 @@ tag="Raffle", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, - user_deleter=user_deleter, + user_deleter=RaffleUserDeleter(), ) hyperion_raffle_logger = logging.getLogger("hyperion.raffle") diff --git a/app/modules/raid/endpoints_raid.py b/app/modules/raid/endpoints_raid.py index ceeb2218a4..9776c92476 100644 --- a/app/modules/raid/endpoints_raid.py +++ b/app/modules/raid/endpoints_raid.py @@ -19,7 +19,7 @@ ) from app.modules.raid import coredata_raid, cruds_raid, models_raid, schemas_raid from app.modules.raid.raid_type import DocumentType, DocumentValidation, Size -from app.modules.raid.user_deleter_raid import user_deleter +from app.modules.raid.user_deleter_raid import RaidUserDeleter from app.modules.raid.utils.utils_raid import ( calculate_raid_payment, get_all_security_files_zip, @@ -49,7 +49,7 @@ payment_callback=validate_payment, default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, - user_deleter=user_deleter, + user_deleter=RaidUserDeleter(), ) diff --git a/app/modules/recommendation/endpoints_recommendation.py b/app/modules/recommendation/endpoints_recommendation.py index 21bb38d7df..5ed668273f 100644 --- a/app/modules/recommendation/endpoints_recommendation.py +++ b/app/modules/recommendation/endpoints_recommendation.py @@ -18,7 +18,9 @@ schemas_recommendation, ) from app.modules.recommendation.factory_recommendation import RecommendationFactory -from app.modules.recommendation.user_deleter_recommendation import user_deleter +from app.modules.recommendation.user_deleter_recommendation import ( + RecommendationUserDeleter, +) from app.types import standard_responses from app.types.content_type import ContentType from app.types.module import Module @@ -32,7 +34,7 @@ tag="Recommendation", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=RecommendationFactory(), - user_deleter=user_deleter, + user_deleter=RecommendationUserDeleter(), ) diff --git a/app/modules/seed_library/endpoints_seed_library.py b/app/modules/seed_library/endpoints_seed_library.py index c63f82acaf..85f9cec071 100644 --- a/app/modules/seed_library/endpoints_seed_library.py +++ b/app/modules/seed_library/endpoints_seed_library.py @@ -19,7 +19,9 @@ ) from app.modules.seed_library.factory_seed_library import SeedLibraryFactory from app.modules.seed_library.types_seed_library import PlantState, SpeciesType -from app.modules.seed_library.user_deleter_seed_library import user_deleter +from app.modules.seed_library.user_deleter_seed_library import ( + SeedLibraryUserDeleter, +) from app.types.module import Module from app.utils import tools from app.utils.tools import is_user_member_of_any_group @@ -29,7 +31,7 @@ tag="seed_library", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=SeedLibraryFactory(), - user_deleter=user_deleter, + user_deleter=SeedLibraryUserDeleter(), ) diff --git a/app/types/module_user_deleter.py b/app/types/module_user_deleter.py index 91129fb979..e163d35345 100644 --- a/app/types/module_user_deleter.py +++ b/app/types/module_user_deleter.py @@ -1,4 +1,7 @@ from abc import ABC, abstractmethod +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession class ModuleUserDeleter(ABC): @@ -9,7 +12,11 @@ class ModuleUserDeleter(ABC): """ @abstractmethod - def can_delete_user(self, user_id) -> bool: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> Literal[True] | str: """ Check if the user can be deleted. :param user_id: The ID of the user to check. @@ -17,7 +24,7 @@ def can_delete_user(self, user_id) -> bool: """ @abstractmethod - def delete_user(self, user_id) -> None: + async def delete_user(self, user_id: str, db: AsyncSession) -> None: """ Delete the user from the system. :param user_id: The ID of the user to delete. @@ -26,15 +33,17 @@ def delete_user(self, user_id) -> None: """ from app.types.module_user_deleter import ModuleUserDeleter +from typing import Literal +from sqlalchemy.ext.asyncio import AsyncSession class CoreUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: + async def can_delete_user(self, user_id: str, db: AsyncSession) -> Literal[True] | str: return True - def delete_user(self, user_id) -> None: + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass -user_deleter = CoreUserDeleter() +() """ From 53e351f4ff13d276dc7323f2728abaaae229e448 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 30 Nov 2025 04:39:56 +0100 Subject: [PATCH 3/7] Feat: core deleters --- app/core/auth/cruds_auth.py | 28 ++++++ app/core/auth/user_deleter_auth.py | 25 +++-- app/core/core_endpoints/user_deleter_core.py | 15 ++- .../google_api/user_deleter_google_api.py | 15 ++- app/core/groups/cruds_groups.py | 12 +++ app/core/groups/user_deleter_groups.py | 21 +++-- .../memberships/user_deleter_memberships.py | 17 ++-- app/core/notification/cruds_notification.py | 24 +++++ .../notification/user_deleter_notification.py | 33 ++++++- app/core/payment/user_deleter_payment.py | 15 ++- app/core/schools/user_deleter_schools.py | 15 ++- app/core/users/cruds_users.py | 91 ++++++++++++++++++- app/core/users/models_users.py | 1 + app/core/users/user_deleter_users.py | 34 +++++-- 14 files changed, 292 insertions(+), 54 deletions(-) diff --git a/app/core/auth/cruds_auth.py b/app/core/auth/cruds_auth.py index 905e662187..f59a459c1c 100644 --- a/app/core/auth/cruds_auth.py +++ b/app/core/auth/cruds_auth.py @@ -121,3 +121,31 @@ async def revoke_refresh_token_by_user_id( .values(revoked_on=datetime.now(UTC)), ) await db.flush() + + +async def delete_refresh_token_by_user_id( + db: AsyncSession, + user_id: str, +) -> None: + """Delete a refresh token from database""" + + await db.execute( + delete(models_auth.RefreshToken).where( + models_auth.RefreshToken.user_id == user_id, + ), + ) + await db.flush() + + +async def delete_authorization_token_by_user_id( + db: AsyncSession, + user_id: str, +) -> None: + """Delete a refresh token from database""" + + await db.execute( + delete(models_auth.AuthorizationCode).where( + models_auth.AuthorizationCode.user_id == user_id, + ), + ) + await db.flush() diff --git a/app/core/auth/user_deleter_auth.py b/app/core/auth/user_deleter_auth.py index d528ee05d9..299a4a15b5 100644 --- a/app/core/auth/user_deleter_auth.py +++ b/app/core/auth/user_deleter_auth.py @@ -1,12 +1,25 @@ +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.auth import cruds_auth from app.types.module_user_deleter import ModuleUserDeleter class AuthUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> Literal[True] | str: return True - def delete_user(self, user_id) -> None: - pass - - -user_deleter = AuthUserDeleter() + async def delete_user(self, user_id: str, db: AsyncSession) -> None: + await cruds_auth.delete_authorization_token_by_user_id( + db=db, + user_id=user_id, + ) + await cruds_auth.delete_refresh_token_by_user_id( + db=db, + user_id=user_id, + ) diff --git a/app/core/core_endpoints/user_deleter_core.py b/app/core/core_endpoints/user_deleter_core.py index 79aa571353..0970ed3c19 100644 --- a/app/core/core_endpoints/user_deleter_core.py +++ b/app/core/core_endpoints/user_deleter_core.py @@ -1,12 +1,17 @@ +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class CoreUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> Literal[True] | str: return True - def delete_user(self, user_id) -> None: + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = CoreUserDeleter() diff --git a/app/core/google_api/user_deleter_google_api.py b/app/core/google_api/user_deleter_google_api.py index 59446c8fef..3a6d37ceb6 100644 --- a/app/core/google_api/user_deleter_google_api.py +++ b/app/core/google_api/user_deleter_google_api.py @@ -1,12 +1,17 @@ +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class GoogleAPIUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> Literal[True] | str: return True - def delete_user(self, user_id) -> None: + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = GoogleAPIUserDeleter() diff --git a/app/core/groups/cruds_groups.py b/app/core/groups/cruds_groups.py index 675f73e27d..ea6507d085 100644 --- a/app/core/groups/cruds_groups.py +++ b/app/core/groups/cruds_groups.py @@ -114,3 +114,15 @@ async def update_group( .values(**group_update.model_dump(exclude_none=True)), ) await db.flush() + + +async def delete_membership_by_user_id( + user_id: str, + db: AsyncSession, +): + await db.execute( + delete(models_groups.CoreMembership).where( + models_groups.CoreMembership.user_id == user_id, + ), + ) + await db.flush() diff --git a/app/core/groups/user_deleter_groups.py b/app/core/groups/user_deleter_groups.py index 08f6ee82ed..f3c05af94e 100644 --- a/app/core/groups/user_deleter_groups.py +++ b/app/core/groups/user_deleter_groups.py @@ -1,12 +1,21 @@ +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.groups import cruds_groups from app.types.module_user_deleter import ModuleUserDeleter class GroupsUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> Literal[True] | str: return True - def delete_user(self, user_id) -> None: - pass - - -user_deleter = GroupsUserDeleter() + async def delete_user(self, user_id: str, db: AsyncSession) -> None: + await cruds_groups.delete_membership_by_user_id( + user_id=user_id, + db=db, + ) diff --git a/app/core/memberships/user_deleter_memberships.py b/app/core/memberships/user_deleter_memberships.py index d96855cc4a..8cc964fc7e 100644 --- a/app/core/memberships/user_deleter_memberships.py +++ b/app/core/memberships/user_deleter_memberships.py @@ -1,12 +1,17 @@ +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class MembershipsUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> Literal[True] | str: return True - def delete_user(self, user_id) -> None: - pass - - -user_deleter = MembershipsUserDeleter() + async def delete_user(self, user_id: str, db: AsyncSession) -> None: + pass # We keep the memberships for stats and history purposes diff --git a/app/core/notification/cruds_notification.py b/app/core/notification/cruds_notification.py index c6503bd301..f8881a2730 100644 --- a/app/core/notification/cruds_notification.py +++ b/app/core/notification/cruds_notification.py @@ -272,3 +272,27 @@ async def get_usernames_by_firebase_tokens( .distinct(), ) return [f"{u.firstname} {u.name}" for u in list(result.scalars().all())] + + +async def delete_topic_membership_by_user_id( + user_id: str, + db: AsyncSession, +): + await db.execute( + delete(models_notification.TopicMembership).where( + models_notification.TopicMembership.user_id == user_id, + ), + ) + await db.flush() + + +async def delete_firebase_devices_by_user_id( + user_id: str, + db: AsyncSession, +): + await db.execute( + delete(models_notification.FirebaseDevice).where( + models_notification.FirebaseDevice.user_id == user_id, + ), + ) + await db.flush() diff --git a/app/core/notification/user_deleter_notification.py b/app/core/notification/user_deleter_notification.py index a3cb6131bf..d12ca05d07 100644 --- a/app/core/notification/user_deleter_notification.py +++ b/app/core/notification/user_deleter_notification.py @@ -1,12 +1,35 @@ +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.notification import cruds_notification from app.types.module_user_deleter import ModuleUserDeleter class NotificationUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> Literal[True] | str: return True - def delete_user(self, user_id) -> None: - pass - + async def delete_user(self, user_id: str, db: AsyncSession) -> None: + devices = await cruds_notification.get_firebase_devices_by_user_id( + db=db, + user_id=user_id, + ) + for device in devices: + await cruds_notification.delete_message_by_firebase_device_token( + db=db, + device_token=device.firebase_device_token, + ) + await cruds_notification.delete_firebase_devices_by_user_id( + db=db, + user_id=user_id, + ) -user_deleter = NotificationUserDeleter() + await cruds_notification.delete_topic_membership_by_user_id( + db=db, + user_id=user_id, + ) diff --git a/app/core/payment/user_deleter_payment.py b/app/core/payment/user_deleter_payment.py index a2befa31c3..016f63ab67 100644 --- a/app/core/payment/user_deleter_payment.py +++ b/app/core/payment/user_deleter_payment.py @@ -1,12 +1,17 @@ +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class PaymentUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> Literal[True] | str: return True - def delete_user(self, user_id) -> None: + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = PaymentUserDeleter() diff --git a/app/core/schools/user_deleter_schools.py b/app/core/schools/user_deleter_schools.py index 7a81ad1780..8fca53b7e1 100644 --- a/app/core/schools/user_deleter_schools.py +++ b/app/core/schools/user_deleter_schools.py @@ -1,12 +1,17 @@ +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class SchoolsUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> Literal[True] | str: return True - def delete_user(self, user_id) -> None: + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = SchoolsUserDeleter() diff --git a/app/core/users/cruds_users.py b/app/core/users/cruds_users.py index 5863711723..27f95e3724 100644 --- a/app/core/users/cruds_users.py +++ b/app/core/users/cruds_users.py @@ -3,7 +3,7 @@ from collections.abc import Sequence from uuid import UUID -from sqlalchemy import ForeignKey, and_, delete, not_, or_, select, update +from sqlalchemy import ForeignKey, and_, delete, func, not_, or_, select, update from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from sqlalchemy_utils import get_referencing_foreign_keys @@ -96,6 +96,7 @@ async def get_users( *excluded_account_type_condition, *excluded_group_condition, school_condition, + models_users.CoreUser.deactivated.is_(False), ), ), ) @@ -110,7 +111,10 @@ async def get_user_by_id( result = await db.execute( select(models_users.CoreUser) - .where(models_users.CoreUser.id == user_id) + .where( + models_users.CoreUser.id == user_id, + models_users.CoreUser.deactivated.is_(False), + ) .options( # The group relationship need to be loaded selectinload(models_users.CoreUser.groups), @@ -127,7 +131,10 @@ async def get_user_by_email( result = await db.execute( select(models_users.CoreUser) - .where(models_users.CoreUser.email == email) + .where( + models_users.CoreUser.email == email, + models_users.CoreUser.deactivated.is_(False), + ) .options( # The group relationship need to be loaded to be able # to check if the user is a member of a specific group @@ -144,7 +151,10 @@ async def update_user( ): await db.execute( update(models_users.CoreUser) - .where(models_users.CoreUser.id == user_id) + .where( + models_users.CoreUser.id == user_id, + models_users.CoreUser.deactivated.is_(False), + ) .values(**user_update.model_dump(exclude_none=True)), ) @@ -277,7 +287,10 @@ async def update_user_password_by_id( ): await db.execute( update(models_users.CoreUser) - .where(models_users.CoreUser.id == user_id) + .where( + models_users.CoreUser.id == user_id, + models_users.CoreUser.deactivated.is_(False), + ) .values(password_hash=new_password_hash), ) await db.flush() @@ -418,3 +431,71 @@ async def fusion_users( # Delete the user_deleted await delete_user(db, user_deleted_id) + + +async def count_deactivated_users(db: AsyncSession) -> int: + """Return the number of deactivated users in the database""" + + result = ( + await db.execute( + select(func.count()).where( + models_users.CoreUser.deactivated, + ), + ) + ).scalar() + return result or 0 + + +async def deactivate_user( + db: AsyncSession, + user_id: str, +): + """Deactivate a user in the database""" + count = await count_deactivated_users(db) + + await db.execute( + update(models_users.CoreUser) + .where(models_users.CoreUser.id == user_id) + .values( + deactivated=True, + email=f"deleted.user{count}@myecl.fr", + name="Deleted User", + firstname=str(count), + nickname=None, + floor=None, + phone=None, + promo=None, + birthday=None, + school_id=SchoolType.no_school.value, + account_type=AccountType.external, + ), + ) + await db.commit() + + +async def delete_email_migration_code_by_user_id( + db: AsyncSession, + user_id: str, +): + """Delete a user from database by id""" + + await db.execute( + delete(models_users.CoreUserEmailMigrationCode).where( + models_users.CoreUserEmailMigrationCode.user_id == user_id, + ), + ) + await db.commit() + + +async def delete_recover_request_by_user_id( + db: AsyncSession, + user_id: str, +): + """Delete a user from database by id""" + + await db.execute( + delete(models_users.CoreUserRecoverRequest).where( + models_users.CoreUserRecoverRequest.user_id == user_id, + ), + ) + await db.commit() diff --git a/app/core/users/models_users.py b/app/core/users/models_users.py index b3b4682323..c14e8d5fda 100644 --- a/app/core/users/models_users.py +++ b/app/core/users/models_users.py @@ -38,6 +38,7 @@ class CoreUser(Base): phone: Mapped[str | None] floor: Mapped[FloorsType | None] created_on: Mapped[datetime | None] + deactivated: Mapped[bool] = mapped_column(default=False) # We use list["CoreGroup"] with quotes as CoreGroup is only defined after this class # Defining CoreUser after CoreGroup would cause a similar issue diff --git a/app/core/users/user_deleter_users.py b/app/core/users/user_deleter_users.py index aec601f8bd..d3cfe69ee9 100644 --- a/app/core/users/user_deleter_users.py +++ b/app/core/users/user_deleter_users.py @@ -1,12 +1,34 @@ +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.users import cruds_users from app.types.module_user_deleter import ModuleUserDeleter +from app.utils.tools import delete_file_from_data class UsersUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> Literal[True] | str: return True - def delete_user(self, user_id) -> None: - pass - - -user_deleter = UsersUserDeleter() + async def delete_user(self, user_id: str, db: AsyncSession) -> None: + await cruds_users.delete_email_migration_code_by_user_id( + db=db, + user_id=user_id, + ) + await cruds_users.delete_recover_request_by_user_id( + db=db, + user_id=user_id, + ) + await cruds_users.deactivate_user( + db=db, + user_id=user_id, + ) + delete_file_from_data( + directory="profile-pictures", + filename=user_id, + ) From 851ddabbe1f5c8dfa5d57a4e55c1b4f8c54961c9 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 30 Nov 2025 04:39:57 +0100 Subject: [PATCH 4/7] Feat: improvment and added some module deleter --- app/core/auth/user_deleter_auth.py | 6 +-- app/core/core_endpoints/user_deleter_core.py | 6 +-- .../google_api/user_deleter_google_api.py | 6 +-- app/core/groups/user_deleter_groups.py | 6 +-- .../memberships/user_deleter_memberships.py | 6 +-- .../notification/user_deleter_notification.py | 6 +-- app/core/payment/user_deleter_payment.py | 6 +-- app/core/schools/user_deleter_schools.py | 6 +-- app/core/users/user_deleter_users.py | 6 +-- app/modules/advert/user_deleter_advert.py | 17 ++++---- app/modules/amap/user_deleter_amap.py | 40 ++++++++++++++++--- app/modules/booking/user_deleter_booking.py | 29 +++++++++++--- app/modules/calendar/user_deleter_calendar.py | 29 +++++++++++--- app/modules/campaign/cruds_campaign.py | 13 ++++++ app/modules/campaign/user_deleter_campaign.py | 24 +++++++---- app/modules/cdr/user_deleter_cdr.py | 17 ++++---- .../user_deleter_centralisation.py | 17 ++++---- app/modules/cinema/user_deleter_cinema.py | 17 ++++---- .../flappybird/user_deleter_flappybird.py | 17 ++++---- app/modules/home/user_deleter_home.py | 17 ++++---- app/modules/loan/user_deleter_loan.py | 17 ++++---- app/modules/ph/user_deleter_ph.py | 17 ++++---- .../phonebook/user_deleter_phonebook.py | 17 ++++---- .../purchases/user_deleter_purchases.py | 17 ++++---- app/modules/raffle/user_deleter_raffle.py | 17 ++++---- app/modules/raid/user_deleter_raid.py | 17 ++++---- .../user_deleter_recommendation.py | 17 ++++---- .../seed_library/user_deleter_seed_library.py | 17 ++++---- app/types/module_user_deleter.py | 7 ++-- 29 files changed, 271 insertions(+), 163 deletions(-) diff --git a/app/core/auth/user_deleter_auth.py b/app/core/auth/user_deleter_auth.py index 299a4a15b5..62cc20c77d 100644 --- a/app/core/auth/user_deleter_auth.py +++ b/app/core/auth/user_deleter_auth.py @@ -1,5 +1,3 @@ -from typing import Literal - from sqlalchemy.ext.asyncio import AsyncSession from app.core.auth import cruds_auth @@ -11,8 +9,8 @@ async def can_delete_user( self, user_id: str, db: AsyncSession, - ) -> Literal[True] | str: - return True + ) -> str: + return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: await cruds_auth.delete_authorization_token_by_user_id( diff --git a/app/core/core_endpoints/user_deleter_core.py b/app/core/core_endpoints/user_deleter_core.py index 0970ed3c19..6f7c226d1c 100644 --- a/app/core/core_endpoints/user_deleter_core.py +++ b/app/core/core_endpoints/user_deleter_core.py @@ -1,5 +1,3 @@ -from typing import Literal - from sqlalchemy.ext.asyncio import AsyncSession from app.types.module_user_deleter import ModuleUserDeleter @@ -10,8 +8,8 @@ async def can_delete_user( self, user_id: str, db: AsyncSession, - ) -> Literal[True] | str: - return True + ) -> str: + return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass diff --git a/app/core/google_api/user_deleter_google_api.py b/app/core/google_api/user_deleter_google_api.py index 3a6d37ceb6..934ce8c0d7 100644 --- a/app/core/google_api/user_deleter_google_api.py +++ b/app/core/google_api/user_deleter_google_api.py @@ -1,5 +1,3 @@ -from typing import Literal - from sqlalchemy.ext.asyncio import AsyncSession from app.types.module_user_deleter import ModuleUserDeleter @@ -10,8 +8,8 @@ async def can_delete_user( self, user_id: str, db: AsyncSession, - ) -> Literal[True] | str: - return True + ) -> str: + return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass diff --git a/app/core/groups/user_deleter_groups.py b/app/core/groups/user_deleter_groups.py index f3c05af94e..d83c1e557b 100644 --- a/app/core/groups/user_deleter_groups.py +++ b/app/core/groups/user_deleter_groups.py @@ -1,5 +1,3 @@ -from typing import Literal - from sqlalchemy.ext.asyncio import AsyncSession from app.core.groups import cruds_groups @@ -11,8 +9,8 @@ async def can_delete_user( self, user_id: str, db: AsyncSession, - ) -> Literal[True] | str: - return True + ) -> str: + return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: await cruds_groups.delete_membership_by_user_id( diff --git a/app/core/memberships/user_deleter_memberships.py b/app/core/memberships/user_deleter_memberships.py index 8cc964fc7e..b83391cc94 100644 --- a/app/core/memberships/user_deleter_memberships.py +++ b/app/core/memberships/user_deleter_memberships.py @@ -1,5 +1,3 @@ -from typing import Literal - from sqlalchemy.ext.asyncio import AsyncSession from app.types.module_user_deleter import ModuleUserDeleter @@ -10,8 +8,8 @@ async def can_delete_user( self, user_id: str, db: AsyncSession, - ) -> Literal[True] | str: - return True + ) -> str: + return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass # We keep the memberships for stats and history purposes diff --git a/app/core/notification/user_deleter_notification.py b/app/core/notification/user_deleter_notification.py index d12ca05d07..7e666e174a 100644 --- a/app/core/notification/user_deleter_notification.py +++ b/app/core/notification/user_deleter_notification.py @@ -1,5 +1,3 @@ -from typing import Literal - from sqlalchemy.ext.asyncio import AsyncSession from app.core.notification import cruds_notification @@ -11,8 +9,8 @@ async def can_delete_user( self, user_id: str, db: AsyncSession, - ) -> Literal[True] | str: - return True + ) -> str: + return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: devices = await cruds_notification.get_firebase_devices_by_user_id( diff --git a/app/core/payment/user_deleter_payment.py b/app/core/payment/user_deleter_payment.py index 016f63ab67..cfc29d3e3b 100644 --- a/app/core/payment/user_deleter_payment.py +++ b/app/core/payment/user_deleter_payment.py @@ -1,5 +1,3 @@ -from typing import Literal - from sqlalchemy.ext.asyncio import AsyncSession from app.types.module_user_deleter import ModuleUserDeleter @@ -10,8 +8,8 @@ async def can_delete_user( self, user_id: str, db: AsyncSession, - ) -> Literal[True] | str: - return True + ) -> str: + return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass diff --git a/app/core/schools/user_deleter_schools.py b/app/core/schools/user_deleter_schools.py index 8fca53b7e1..9c4f555005 100644 --- a/app/core/schools/user_deleter_schools.py +++ b/app/core/schools/user_deleter_schools.py @@ -1,5 +1,3 @@ -from typing import Literal - from sqlalchemy.ext.asyncio import AsyncSession from app.types.module_user_deleter import ModuleUserDeleter @@ -10,8 +8,8 @@ async def can_delete_user( self, user_id: str, db: AsyncSession, - ) -> Literal[True] | str: - return True + ) -> str: + return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass diff --git a/app/core/users/user_deleter_users.py b/app/core/users/user_deleter_users.py index d3cfe69ee9..d996b9728c 100644 --- a/app/core/users/user_deleter_users.py +++ b/app/core/users/user_deleter_users.py @@ -1,5 +1,3 @@ -from typing import Literal - from sqlalchemy.ext.asyncio import AsyncSession from app.core.users import cruds_users @@ -12,8 +10,8 @@ async def can_delete_user( self, user_id: str, db: AsyncSession, - ) -> Literal[True] | str: - return True + ) -> str: + return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: await cruds_users.delete_email_migration_code_by_user_id( diff --git a/app/modules/advert/user_deleter_advert.py b/app/modules/advert/user_deleter_advert.py index dc69cad532..563b24458b 100644 --- a/app/modules/advert/user_deleter_advert.py +++ b/app/modules/advert/user_deleter_advert.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class AdvertUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = AdvertUserDeleter() diff --git a/app/modules/amap/user_deleter_amap.py b/app/modules/amap/user_deleter_amap.py index f85330700b..7f1a433cff 100644 --- a/app/modules/amap/user_deleter_amap.py +++ b/app/modules/amap/user_deleter_amap.py @@ -1,12 +1,40 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.modules.amap import cruds_amap +from app.modules.amap.types_amap import DeliveryStatusType from app.types.module_user_deleter import ModuleUserDeleter class AmapUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + # Check if the user has any active orders or a negative balance + reasons = [] + user_cash = await cruds_amap.get_cash_by_id(user_id=user_id, db=db) + if user_cash is not None: + if user_cash.balance < 0: + reasons.append("User has negative balance") + orders = await cruds_amap.get_orders_of_user(user_id=user_id, db=db) + for order in orders: + delivery = await cruds_amap.get_delivery_by_id( + db=db, + delivery_id=order.delivery_id, + ) + if delivery is None: + continue + if delivery.status not in [ + DeliveryStatusType.delivered, + DeliveryStatusType.archived, + ]: + reasons.append( + f"User has order in delivery not delivered or archived: {order.delivery_id}", + ) + if reasons: + return "\n - ".join(reasons) + return "" - def delete_user(self, user_id) -> None: + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = AmapUserDeleter() diff --git a/app/modules/booking/user_deleter_booking.py b/app/modules/booking/user_deleter_booking.py index 9ac589ddb3..3b8749df90 100644 --- a/app/modules/booking/user_deleter_booking.py +++ b/app/modules/booking/user_deleter_booking.py @@ -1,12 +1,29 @@ +from datetime import UTC, datetime + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.modules.booking import cruds_booking from app.types.module_user_deleter import ModuleUserDeleter class BookingUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + user_bookings = await cruds_booking.get_applicant_bookings( + db=db, + applicant_id=user_id, + ) + reasons = [ + f"User has booking in future: {booking.id}" + for booking in user_bookings + if booking.end > datetime.now(tz=UTC) + ] + if reasons: + return "\n - ".join(reasons) + return "" - def delete_user(self, user_id) -> None: + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = BookingUserDeleter() diff --git a/app/modules/calendar/user_deleter_calendar.py b/app/modules/calendar/user_deleter_calendar.py index cd775c6552..c4838dafd0 100644 --- a/app/modules/calendar/user_deleter_calendar.py +++ b/app/modules/calendar/user_deleter_calendar.py @@ -1,12 +1,29 @@ +from datetime import UTC, datetime + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.modules.calendar import cruds_calendar from app.types.module_user_deleter import ModuleUserDeleter class CalendarUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + user_events = await cruds_calendar.get_applicant_events( + db=db, + applicant_id=user_id, + ) + reasons = [ + f"User has booking in future: {event.id}" + for event in user_events + if event.end > datetime.now(tz=UTC) + ] + if reasons: + return "\n - ".join(reasons) + return "" - def delete_user(self, user_id) -> None: + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = CalendarUserDeleter() diff --git a/app/modules/campaign/cruds_campaign.py b/app/modules/campaign/cruds_campaign.py index 4a97aab0a9..be1d791cc5 100644 --- a/app/modules/campaign/cruds_campaign.py +++ b/app/modules/campaign/cruds_campaign.py @@ -330,6 +330,19 @@ async def get_has_voted( return result.scalars().all() +async def delete_user_has_voted( + db: AsyncSession, + user_id: str, +) -> None: + """Delete all votes for a given user.""" + await db.execute( + delete(models_campaign.HasVoted).where( + models_campaign.HasVoted.user_id == user_id, + ), + ) + await db.commit() + + async def get_votes(db: AsyncSession) -> Sequence[models_campaign.Votes]: result = await db.execute(select(models_campaign.Votes)) return result.scalars().all() diff --git a/app/modules/campaign/user_deleter_campaign.py b/app/modules/campaign/user_deleter_campaign.py index fdf4238ff6..e21f102ad7 100644 --- a/app/modules/campaign/user_deleter_campaign.py +++ b/app/modules/campaign/user_deleter_campaign.py @@ -1,12 +1,22 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.modules.campaign import cruds_campaign, types_campaign from app.types.module_user_deleter import ModuleUserDeleter class CampaignUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: - pass - + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + status = await cruds_campaign.get_status(db=db) + if status != types_campaign.StatusType.published: + return " - User has voted in unpublished campaign, wait for publish" + return "" -user_deleter = CampaignUserDeleter() + async def delete_user(self, user_id: str, db: AsyncSession) -> None: + await cruds_campaign.delete_user_has_voted( + db=db, + user_id=user_id, + ) diff --git a/app/modules/cdr/user_deleter_cdr.py b/app/modules/cdr/user_deleter_cdr.py index ee0a29b120..c0d726703d 100644 --- a/app/modules/cdr/user_deleter_cdr.py +++ b/app/modules/cdr/user_deleter_cdr.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class CdrUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = CdrUserDeleter() diff --git a/app/modules/centralisation/user_deleter_centralisation.py b/app/modules/centralisation/user_deleter_centralisation.py index d199d05887..34d6f4cf6c 100644 --- a/app/modules/centralisation/user_deleter_centralisation.py +++ b/app/modules/centralisation/user_deleter_centralisation.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class CentralisationUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = CentralisationUserDeleter() diff --git a/app/modules/cinema/user_deleter_cinema.py b/app/modules/cinema/user_deleter_cinema.py index eec267f91c..6fdd6b529e 100644 --- a/app/modules/cinema/user_deleter_cinema.py +++ b/app/modules/cinema/user_deleter_cinema.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class CinemaUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = CinemaUserDeleter() diff --git a/app/modules/flappybird/user_deleter_flappybird.py b/app/modules/flappybird/user_deleter_flappybird.py index 0d270e0407..e102225ca3 100644 --- a/app/modules/flappybird/user_deleter_flappybird.py +++ b/app/modules/flappybird/user_deleter_flappybird.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class FlappybirdUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = FlappybirdUserDeleter() diff --git a/app/modules/home/user_deleter_home.py b/app/modules/home/user_deleter_home.py index 5239833711..d89454a643 100644 --- a/app/modules/home/user_deleter_home.py +++ b/app/modules/home/user_deleter_home.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class HomeUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = HomeUserDeleter() diff --git a/app/modules/loan/user_deleter_loan.py b/app/modules/loan/user_deleter_loan.py index 1ee2fc8b3c..74279394fe 100644 --- a/app/modules/loan/user_deleter_loan.py +++ b/app/modules/loan/user_deleter_loan.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class LoanUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = LoanUserDeleter() diff --git a/app/modules/ph/user_deleter_ph.py b/app/modules/ph/user_deleter_ph.py index 5004b3cc31..3bd4375dd9 100644 --- a/app/modules/ph/user_deleter_ph.py +++ b/app/modules/ph/user_deleter_ph.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class PHUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = PHUserDeleter() diff --git a/app/modules/phonebook/user_deleter_phonebook.py b/app/modules/phonebook/user_deleter_phonebook.py index 46e6fb62b4..bd07c905f4 100644 --- a/app/modules/phonebook/user_deleter_phonebook.py +++ b/app/modules/phonebook/user_deleter_phonebook.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class PhonebookUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = PhonebookUserDeleter() diff --git a/app/modules/purchases/user_deleter_purchases.py b/app/modules/purchases/user_deleter_purchases.py index 56e3d60d1d..4ebe4e0468 100644 --- a/app/modules/purchases/user_deleter_purchases.py +++ b/app/modules/purchases/user_deleter_purchases.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class PurchasesUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = PurchasesUserDeleter() diff --git a/app/modules/raffle/user_deleter_raffle.py b/app/modules/raffle/user_deleter_raffle.py index a04c00f797..6d163cd3f6 100644 --- a/app/modules/raffle/user_deleter_raffle.py +++ b/app/modules/raffle/user_deleter_raffle.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class RaffleUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = RaffleUserDeleter() diff --git a/app/modules/raid/user_deleter_raid.py b/app/modules/raid/user_deleter_raid.py index e2b1e9ec07..42195a9a97 100644 --- a/app/modules/raid/user_deleter_raid.py +++ b/app/modules/raid/user_deleter_raid.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class RaidUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = RaidUserDeleter() diff --git a/app/modules/recommendation/user_deleter_recommendation.py b/app/modules/recommendation/user_deleter_recommendation.py index e84d3737a1..85d5d09a12 100644 --- a/app/modules/recommendation/user_deleter_recommendation.py +++ b/app/modules/recommendation/user_deleter_recommendation.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class RecommendationUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = RecommendationUserDeleter() diff --git a/app/modules/seed_library/user_deleter_seed_library.py b/app/modules/seed_library/user_deleter_seed_library.py index 87aff02e53..748c1a55bf 100644 --- a/app/modules/seed_library/user_deleter_seed_library.py +++ b/app/modules/seed_library/user_deleter_seed_library.py @@ -1,12 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from app.types.module_user_deleter import ModuleUserDeleter class SeedLibraryUserDeleter(ModuleUserDeleter): - def can_delete_user(self, user_id) -> bool: - return True - - def delete_user(self, user_id) -> None: + async def can_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass - - -user_deleter = SeedLibraryUserDeleter() diff --git a/app/types/module_user_deleter.py b/app/types/module_user_deleter.py index e163d35345..45c957c988 100644 --- a/app/types/module_user_deleter.py +++ b/app/types/module_user_deleter.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from typing import Literal from sqlalchemy.ext.asyncio import AsyncSession @@ -16,7 +15,7 @@ async def can_delete_user( self, user_id: str, db: AsyncSession, - ) -> Literal[True] | str: + ) -> str: """ Check if the user can be deleted. :param user_id: The ID of the user to check. @@ -33,13 +32,13 @@ async def delete_user(self, user_id: str, db: AsyncSession) -> None: """ from app.types.module_user_deleter import ModuleUserDeleter -from typing import Literal + from sqlalchemy.ext.asyncio import AsyncSession class CoreUserDeleter(ModuleUserDeleter): async def can_delete_user(self, user_id: str, db: AsyncSession) -> Literal[True] | str: - return True + return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass From f7a44b215e5d7bbd4f685dadde2a2007c9cf7249 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 30 Nov 2025 04:39:58 +0100 Subject: [PATCH 5/7] feat: fix method name and add deleters --- app/core/auth/user_deleter_auth.py | 2 +- app/core/core_endpoints/user_deleter_core.py | 2 +- app/core/google_api/user_deleter_google_api.py | 2 +- app/core/groups/user_deleter_groups.py | 2 +- app/core/memberships/user_deleter_memberships.py | 2 +- .../notification/user_deleter_notification.py | 2 +- app/core/payment/user_deleter_payment.py | 2 +- app/core/schools/user_deleter_schools.py | 2 +- app/core/users/user_deleter_users.py | 2 +- app/modules/advert/user_deleter_advert.py | 2 +- app/modules/amap/user_deleter_amap.py | 2 +- app/modules/booking/user_deleter_booking.py | 2 +- app/modules/calendar/user_deleter_calendar.py | 2 +- app/modules/campaign/user_deleter_campaign.py | 2 +- app/modules/cdr/user_deleter_cdr.py | 14 ++++++++++++-- .../centralisation/user_deleter_centralisation.py | 2 +- app/modules/cinema/user_deleter_cinema.py | 2 +- app/modules/flappybird/cruds_flappybird.py | 12 ++++++++++++ app/modules/flappybird/user_deleter_flappybird.py | 15 +++++++++++++-- app/modules/home/user_deleter_home.py | 2 +- app/modules/loan/user_deleter_loan.py | 6 +++++- app/modules/ph/user_deleter_ph.py | 2 +- app/modules/phonebook/user_deleter_phonebook.py | 2 +- app/modules/purchases/user_deleter_purchases.py | 2 +- app/modules/raffle/user_deleter_raffle.py | 2 +- app/modules/raid/user_deleter_raid.py | 5 ++++- .../recommendation/user_deleter_recommendation.py | 2 +- .../seed_library/user_deleter_seed_library.py | 2 +- app/types/module_user_deleter.py | 2 +- 29 files changed, 70 insertions(+), 30 deletions(-) diff --git a/app/core/auth/user_deleter_auth.py b/app/core/auth/user_deleter_auth.py index 62cc20c77d..01b7f59f9c 100644 --- a/app/core/auth/user_deleter_auth.py +++ b/app/core/auth/user_deleter_auth.py @@ -5,7 +5,7 @@ class AuthUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/core/core_endpoints/user_deleter_core.py b/app/core/core_endpoints/user_deleter_core.py index 6f7c226d1c..5a279a270d 100644 --- a/app/core/core_endpoints/user_deleter_core.py +++ b/app/core/core_endpoints/user_deleter_core.py @@ -4,7 +4,7 @@ class CoreUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/core/google_api/user_deleter_google_api.py b/app/core/google_api/user_deleter_google_api.py index 934ce8c0d7..e32289f67a 100644 --- a/app/core/google_api/user_deleter_google_api.py +++ b/app/core/google_api/user_deleter_google_api.py @@ -4,7 +4,7 @@ class GoogleAPIUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/core/groups/user_deleter_groups.py b/app/core/groups/user_deleter_groups.py index d83c1e557b..17db017df2 100644 --- a/app/core/groups/user_deleter_groups.py +++ b/app/core/groups/user_deleter_groups.py @@ -5,7 +5,7 @@ class GroupsUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/core/memberships/user_deleter_memberships.py b/app/core/memberships/user_deleter_memberships.py index b83391cc94..e70136adb2 100644 --- a/app/core/memberships/user_deleter_memberships.py +++ b/app/core/memberships/user_deleter_memberships.py @@ -4,7 +4,7 @@ class MembershipsUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/core/notification/user_deleter_notification.py b/app/core/notification/user_deleter_notification.py index 7e666e174a..7fb08c7470 100644 --- a/app/core/notification/user_deleter_notification.py +++ b/app/core/notification/user_deleter_notification.py @@ -5,7 +5,7 @@ class NotificationUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/core/payment/user_deleter_payment.py b/app/core/payment/user_deleter_payment.py index cfc29d3e3b..4b159b8776 100644 --- a/app/core/payment/user_deleter_payment.py +++ b/app/core/payment/user_deleter_payment.py @@ -4,7 +4,7 @@ class PaymentUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/core/schools/user_deleter_schools.py b/app/core/schools/user_deleter_schools.py index 9c4f555005..f4bdeffd79 100644 --- a/app/core/schools/user_deleter_schools.py +++ b/app/core/schools/user_deleter_schools.py @@ -4,7 +4,7 @@ class SchoolsUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/core/users/user_deleter_users.py b/app/core/users/user_deleter_users.py index d996b9728c..2c49f942bd 100644 --- a/app/core/users/user_deleter_users.py +++ b/app/core/users/user_deleter_users.py @@ -6,7 +6,7 @@ class UsersUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/advert/user_deleter_advert.py b/app/modules/advert/user_deleter_advert.py index 563b24458b..b5e863a1ed 100644 --- a/app/modules/advert/user_deleter_advert.py +++ b/app/modules/advert/user_deleter_advert.py @@ -4,7 +4,7 @@ class AdvertUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/amap/user_deleter_amap.py b/app/modules/amap/user_deleter_amap.py index 7f1a433cff..bfb2ba9719 100644 --- a/app/modules/amap/user_deleter_amap.py +++ b/app/modules/amap/user_deleter_amap.py @@ -6,7 +6,7 @@ class AmapUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/booking/user_deleter_booking.py b/app/modules/booking/user_deleter_booking.py index 3b8749df90..52f13855b5 100644 --- a/app/modules/booking/user_deleter_booking.py +++ b/app/modules/booking/user_deleter_booking.py @@ -7,7 +7,7 @@ class BookingUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/calendar/user_deleter_calendar.py b/app/modules/calendar/user_deleter_calendar.py index c4838dafd0..78c560d5ce 100644 --- a/app/modules/calendar/user_deleter_calendar.py +++ b/app/modules/calendar/user_deleter_calendar.py @@ -7,7 +7,7 @@ class CalendarUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/campaign/user_deleter_campaign.py b/app/modules/campaign/user_deleter_campaign.py index e21f102ad7..168c16abb6 100644 --- a/app/modules/campaign/user_deleter_campaign.py +++ b/app/modules/campaign/user_deleter_campaign.py @@ -5,7 +5,7 @@ class CampaignUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/cdr/user_deleter_cdr.py b/app/modules/cdr/user_deleter_cdr.py index c0d726703d..ef25ee52fc 100644 --- a/app/modules/cdr/user_deleter_cdr.py +++ b/app/modules/cdr/user_deleter_cdr.py @@ -1,15 +1,25 @@ from sqlalchemy.ext.asyncio import AsyncSession +from app.modules.cdr.cruds_cdr import get_payments_by_user_id, get_purchases_by_user_id from app.types.module_user_deleter import ModuleUserDeleter class CdrUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, ) -> str: - return "" + reasons = "" + purchases = await get_purchases_by_user_id(db, user_id) + payments = await get_payments_by_user_id(db, user_id) + if any(not purchase.validated for purchase in purchases): + reasons += "\n - User has pending purchases" + if sum(payment.total for payment in payments) != sum( + purchase.quantity * purchase.product_variant.price for purchase in purchases + ): + reasons += "\n - User has uneven wallet balance" + return reasons async def delete_user(self, user_id: str, db: AsyncSession) -> None: pass diff --git a/app/modules/centralisation/user_deleter_centralisation.py b/app/modules/centralisation/user_deleter_centralisation.py index 34d6f4cf6c..947954fe9c 100644 --- a/app/modules/centralisation/user_deleter_centralisation.py +++ b/app/modules/centralisation/user_deleter_centralisation.py @@ -4,7 +4,7 @@ class CentralisationUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/cinema/user_deleter_cinema.py b/app/modules/cinema/user_deleter_cinema.py index 6fdd6b529e..54b665e7a5 100644 --- a/app/modules/cinema/user_deleter_cinema.py +++ b/app/modules/cinema/user_deleter_cinema.py @@ -4,7 +4,7 @@ class CinemaUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/flappybird/cruds_flappybird.py b/app/modules/flappybird/cruds_flappybird.py index 90ce8f209f..4c86753697 100644 --- a/app/modules/flappybird/cruds_flappybird.py +++ b/app/modules/flappybird/cruds_flappybird.py @@ -78,6 +78,18 @@ async def delete_flappybird_best_score( ) +async def delete_flappybird_score( + db: AsyncSession, + user_id: str, +): + """Remove a FlappyBirdScore in database""" + await db.execute( + delete(models_flappybird.FlappyBirdScore).where( + models_flappybird.FlappyBirdScore.user_id == user_id, + ), + ) + + async def update_flappybird_best_score( db: AsyncSession, user_id: str, diff --git a/app/modules/flappybird/user_deleter_flappybird.py b/app/modules/flappybird/user_deleter_flappybird.py index e102225ca3..3f9dc1b03a 100644 --- a/app/modules/flappybird/user_deleter_flappybird.py +++ b/app/modules/flappybird/user_deleter_flappybird.py @@ -1,10 +1,14 @@ from sqlalchemy.ext.asyncio import AsyncSession +from app.modules.flappybird.cruds_flappybird import ( + delete_flappybird_best_score, + delete_flappybird_score, +) from app.types.module_user_deleter import ModuleUserDeleter class FlappybirdUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, @@ -12,4 +16,11 @@ async def can_delete_user( return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: - pass + await delete_flappybird_best_score( + db=db, + user_id=user_id, + ) + await delete_flappybird_score( + db=db, + user_id=user_id, + ) diff --git a/app/modules/home/user_deleter_home.py b/app/modules/home/user_deleter_home.py index d89454a643..d02419613d 100644 --- a/app/modules/home/user_deleter_home.py +++ b/app/modules/home/user_deleter_home.py @@ -4,7 +4,7 @@ class HomeUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/loan/user_deleter_loan.py b/app/modules/loan/user_deleter_loan.py index 74279394fe..63343c0bdd 100644 --- a/app/modules/loan/user_deleter_loan.py +++ b/app/modules/loan/user_deleter_loan.py @@ -1,14 +1,18 @@ from sqlalchemy.ext.asyncio import AsyncSession +from app.modules.loan.cruds_loan import get_loans_by_borrower from app.types.module_user_deleter import ModuleUserDeleter class LoanUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, ) -> str: + loans = await get_loans_by_borrower(db, user_id) + if any(not loan.returned for loan in loans): + return "\n - User has pending loans" return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: diff --git a/app/modules/ph/user_deleter_ph.py b/app/modules/ph/user_deleter_ph.py index 3bd4375dd9..87670e7ef5 100644 --- a/app/modules/ph/user_deleter_ph.py +++ b/app/modules/ph/user_deleter_ph.py @@ -4,7 +4,7 @@ class PHUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/phonebook/user_deleter_phonebook.py b/app/modules/phonebook/user_deleter_phonebook.py index bd07c905f4..ca4e450ad9 100644 --- a/app/modules/phonebook/user_deleter_phonebook.py +++ b/app/modules/phonebook/user_deleter_phonebook.py @@ -4,7 +4,7 @@ class PhonebookUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/purchases/user_deleter_purchases.py b/app/modules/purchases/user_deleter_purchases.py index 4ebe4e0468..dfba9cbcf1 100644 --- a/app/modules/purchases/user_deleter_purchases.py +++ b/app/modules/purchases/user_deleter_purchases.py @@ -4,7 +4,7 @@ class PurchasesUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/raffle/user_deleter_raffle.py b/app/modules/raffle/user_deleter_raffle.py index 6d163cd3f6..094d986c91 100644 --- a/app/modules/raffle/user_deleter_raffle.py +++ b/app/modules/raffle/user_deleter_raffle.py @@ -4,7 +4,7 @@ class RaffleUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/raid/user_deleter_raid.py b/app/modules/raid/user_deleter_raid.py index 42195a9a97..a36fe12910 100644 --- a/app/modules/raid/user_deleter_raid.py +++ b/app/modules/raid/user_deleter_raid.py @@ -1,14 +1,17 @@ from sqlalchemy.ext.asyncio import AsyncSession +from app.modules.raid.cruds_raid import is_user_a_participant from app.types.module_user_deleter import ModuleUserDeleter class RaidUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, ) -> str: + if await is_user_a_participant(user_id, db) is not None: + return "\n - User is a participant in the current edition" return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: diff --git a/app/modules/recommendation/user_deleter_recommendation.py b/app/modules/recommendation/user_deleter_recommendation.py index 85d5d09a12..05a522059a 100644 --- a/app/modules/recommendation/user_deleter_recommendation.py +++ b/app/modules/recommendation/user_deleter_recommendation.py @@ -4,7 +4,7 @@ class RecommendationUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/modules/seed_library/user_deleter_seed_library.py b/app/modules/seed_library/user_deleter_seed_library.py index 748c1a55bf..731fbb1e52 100644 --- a/app/modules/seed_library/user_deleter_seed_library.py +++ b/app/modules/seed_library/user_deleter_seed_library.py @@ -4,7 +4,7 @@ class SeedLibraryUserDeleter(ModuleUserDeleter): - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, diff --git a/app/types/module_user_deleter.py b/app/types/module_user_deleter.py index 45c957c988..8d38ff5f4d 100644 --- a/app/types/module_user_deleter.py +++ b/app/types/module_user_deleter.py @@ -11,7 +11,7 @@ class ModuleUserDeleter(ABC): """ @abstractmethod - async def can_delete_user( + async def has_reason_not_to_delete_user( self, user_id: str, db: AsyncSession, From 0e43e6f9a59648ad2ca2e72db57a48c04d7037b3 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 30 Nov 2025 04:59:56 +0100 Subject: [PATCH 6/7] fix: rebase --- .../notification/user_deleter_notification.py | 10 ------- app/core/users/endpoints_users.py | 2 +- app/modules/calendar/user_deleter_calendar.py | 2 +- app/modules/campaign/user_deleter_campaign.py | 8 ++--- app/modules/cdr/user_deleter_cdr.py | 7 +++-- .../endpoints_centralassociation.py | 4 +++ .../user_deleter_centralassociation.py | 15 ++++++++++ app/modules/myeclpay/endpoints_myeclpay.py | 2 ++ app/modules/myeclpay/user_deleter_myeclpay.py | 15 ++++++++++ .../endpoints_sport_competition.py | 4 +++ .../user_deleter_sport_competition.py | 29 +++++++++++++++++++ 11 files changed, 80 insertions(+), 18 deletions(-) create mode 100644 app/modules/centralassociation/user_deleter_centralassociation.py create mode 100644 app/modules/myeclpay/user_deleter_myeclpay.py create mode 100644 app/modules/sport_competition/user_deleter_sport_competition.py diff --git a/app/core/notification/user_deleter_notification.py b/app/core/notification/user_deleter_notification.py index 7fb08c7470..ec3600ce8f 100644 --- a/app/core/notification/user_deleter_notification.py +++ b/app/core/notification/user_deleter_notification.py @@ -13,20 +13,10 @@ async def has_reason_not_to_delete_user( return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: - devices = await cruds_notification.get_firebase_devices_by_user_id( - db=db, - user_id=user_id, - ) - for device in devices: - await cruds_notification.delete_message_by_firebase_device_token( - db=db, - device_token=device.firebase_device_token, - ) await cruds_notification.delete_firebase_devices_by_user_id( db=db, user_id=user_id, ) - await cruds_notification.delete_topic_membership_by_user_id( db=db, user_id=user_id, diff --git a/app/core/users/endpoints_users.py b/app/core/users/endpoints_users.py index 4fa8cb0a4b..3f3795c9cb 100644 --- a/app/core/users/endpoints_users.py +++ b/app/core/users/endpoints_users.py @@ -313,7 +313,7 @@ async def create_user( # After adding the unconfirmed user to the database, we got an activation token that need to be send by email, # in order to make sure the email address is valid - account_type, school_id = await get_account_type_and_school_id_from_email( + account_type, _ = await get_account_type_and_school_id_from_email( email=email, db=db, ) diff --git a/app/modules/calendar/user_deleter_calendar.py b/app/modules/calendar/user_deleter_calendar.py index 78c560d5ce..dff88a6a7d 100644 --- a/app/modules/calendar/user_deleter_calendar.py +++ b/app/modules/calendar/user_deleter_calendar.py @@ -17,7 +17,7 @@ async def has_reason_not_to_delete_user( applicant_id=user_id, ) reasons = [ - f"User has booking in future: {event.id}" + f"User has events in future: {event.id}" for event in user_events if event.end > datetime.now(tz=UTC) ] diff --git a/app/modules/campaign/user_deleter_campaign.py b/app/modules/campaign/user_deleter_campaign.py index 168c16abb6..ce7f65b150 100644 --- a/app/modules/campaign/user_deleter_campaign.py +++ b/app/modules/campaign/user_deleter_campaign.py @@ -1,6 +1,6 @@ from sqlalchemy.ext.asyncio import AsyncSession -from app.modules.campaign import cruds_campaign, types_campaign +from app.modules.campaign import cruds_campaign from app.types.module_user_deleter import ModuleUserDeleter @@ -10,9 +10,9 @@ async def has_reason_not_to_delete_user( user_id: str, db: AsyncSession, ) -> str: - status = await cruds_campaign.get_status(db=db) - if status != types_campaign.StatusType.published: - return " - User has voted in unpublished campaign, wait for publish" + status = await cruds_campaign.get_has_voted(db=db, user_id=user_id) + if len(status) > 0: + return " - User has voted in active campaign, wait for reset" return "" async def delete_user(self, user_id: str, db: AsyncSession) -> None: diff --git a/app/modules/cdr/user_deleter_cdr.py b/app/modules/cdr/user_deleter_cdr.py index ef25ee52fc..9365056ab9 100644 --- a/app/modules/cdr/user_deleter_cdr.py +++ b/app/modules/cdr/user_deleter_cdr.py @@ -1,7 +1,9 @@ from sqlalchemy.ext.asyncio import AsyncSession +from app.modules.cdr.coredata_cdr import CdrYear from app.modules.cdr.cruds_cdr import get_payments_by_user_id, get_purchases_by_user_id from app.types.module_user_deleter import ModuleUserDeleter +from app.utils.tools import get_core_data class CdrUserDeleter(ModuleUserDeleter): @@ -11,8 +13,9 @@ async def has_reason_not_to_delete_user( db: AsyncSession, ) -> str: reasons = "" - purchases = await get_purchases_by_user_id(db, user_id) - payments = await get_payments_by_user_id(db, user_id) + year = await get_core_data(CdrYear, db) + purchases = await get_purchases_by_user_id(db, user_id, year.year) + payments = await get_payments_by_user_id(db, user_id, year.year) if any(not purchase.validated for purchase in purchases): reasons += "\n - User has pending purchases" if sum(payment.total for payment in payments) != sum( diff --git a/app/modules/centralassociation/endpoints_centralassociation.py b/app/modules/centralassociation/endpoints_centralassociation.py index c3471032ad..4d559d0bf7 100644 --- a/app/modules/centralassociation/endpoints_centralassociation.py +++ b/app/modules/centralassociation/endpoints_centralassociation.py @@ -1,4 +1,7 @@ from app.core.groups.groups_type import AccountType +from app.modules.centralassociation.user_deleter_centralassociation import ( + CentralassociationUserDeleter, +) from app.types.module import Module module = Module( @@ -6,4 +9,5 @@ tag="Centralassociation", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, + user_deleter=CentralassociationUserDeleter(), ) diff --git a/app/modules/centralassociation/user_deleter_centralassociation.py b/app/modules/centralassociation/user_deleter_centralassociation.py new file mode 100644 index 0000000000..2c6936764e --- /dev/null +++ b/app/modules/centralassociation/user_deleter_centralassociation.py @@ -0,0 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.types.module_user_deleter import ModuleUserDeleter + + +class CentralassociationUserDeleter(ModuleUserDeleter): + async def has_reason_not_to_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: + pass diff --git a/app/modules/myeclpay/endpoints_myeclpay.py b/app/modules/myeclpay/endpoints_myeclpay.py index 0b031e976e..47e1e60575 100644 --- a/app/modules/myeclpay/endpoints_myeclpay.py +++ b/app/modules/myeclpay/endpoints_myeclpay.py @@ -1,4 +1,5 @@ from app.core.groups.groups_type import AccountType +from app.modules.myeclpay.user_deleter_myeclpay import MyECLPayUserDeleter from app.types.module import Module module = Module( @@ -6,4 +7,5 @@ tag="MyECLPay", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, + user_deleter=MyECLPayUserDeleter(), ) diff --git a/app/modules/myeclpay/user_deleter_myeclpay.py b/app/modules/myeclpay/user_deleter_myeclpay.py new file mode 100644 index 0000000000..09ad5c5df9 --- /dev/null +++ b/app/modules/myeclpay/user_deleter_myeclpay.py @@ -0,0 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.types.module_user_deleter import ModuleUserDeleter + + +class MyECLPayUserDeleter(ModuleUserDeleter): + async def has_reason_not_to_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: + pass diff --git a/app/modules/sport_competition/endpoints_sport_competition.py b/app/modules/sport_competition/endpoints_sport_competition.py index 55dac2fcc2..08439a84a9 100644 --- a/app/modules/sport_competition/endpoints_sport_competition.py +++ b/app/modules/sport_competition/endpoints_sport_competition.py @@ -28,6 +28,9 @@ ExcelExportParams, ProductSchoolType, ) +from app.modules.sport_competition.user_deleter_sport_competition import ( + SportCompetitionUserDeleter, +) from app.modules.sport_competition.utils.data_exporter import ( construct_users_excel_with_parameters, ) @@ -57,6 +60,7 @@ default_allowed_account_types=get_account_types_except_externals(), payment_callback=validate_payment, factory=None, + user_deleter=SportCompetitionUserDeleter(), ) # region: Sport diff --git a/app/modules/sport_competition/user_deleter_sport_competition.py b/app/modules/sport_competition/user_deleter_sport_competition.py new file mode 100644 index 0000000000..7e68eafb06 --- /dev/null +++ b/app/modules/sport_competition/user_deleter_sport_competition.py @@ -0,0 +1,29 @@ +from datetime import UTC, datetime + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.modules.sport_competition import cruds_sport_competition +from app.types.module_user_deleter import ModuleUserDeleter + + +class SportCompetitionUserDeleter(ModuleUserDeleter): + async def has_reason_not_to_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + edition = await cruds_sport_competition.load_active_edition(db) + if edition is not None and edition.end_date >= datetime.now(tz=UTC): + competition_user = ( + await cruds_sport_competition.load_competition_user_by_id( + user_id, + edition.id, + db, + ) + ) + if competition_user is not None: + return " - User is registered in an active sport competition edition" + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: + pass From 258c3e72b95cdef043c30da18e4336f5968a1dfc Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 30 Nov 2025 05:52:06 +0100 Subject: [PATCH 7/7] fix: linting --- app/core/myeclpay/endpoints_myeclpay.py | 2 ++ app/core/myeclpay/user_deleter_myeclpay.py | 15 +++++++++++++++ tests/core/test_payment.py | 3 +++ 3 files changed, 20 insertions(+) create mode 100644 app/core/myeclpay/user_deleter_myeclpay.py diff --git a/app/core/myeclpay/endpoints_myeclpay.py b/app/core/myeclpay/endpoints_myeclpay.py index 0a9ad42e99..6ede9b3999 100644 --- a/app/core/myeclpay/endpoints_myeclpay.py +++ b/app/core/myeclpay/endpoints_myeclpay.py @@ -49,6 +49,7 @@ WalletDeviceStatus, WalletType, ) +from app.core.myeclpay.user_deleter_myeclpay import MyECLPayUserDeleter from app.core.myeclpay.utils_myeclpay import ( LATEST_TOS, QRCODE_EXPIRATION, @@ -98,6 +99,7 @@ router=router, payment_callback=validate_transfer_callback, factory=MyECLPayFactory(), + user_deleter=MyECLPayUserDeleter(), ) templates = Jinja2Templates(directory="assets/templates") diff --git a/app/core/myeclpay/user_deleter_myeclpay.py b/app/core/myeclpay/user_deleter_myeclpay.py new file mode 100644 index 0000000000..09ad5c5df9 --- /dev/null +++ b/app/core/myeclpay/user_deleter_myeclpay.py @@ -0,0 +1,15 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.types.module_user_deleter import ModuleUserDeleter + + +class MyECLPayUserDeleter(ModuleUserDeleter): + async def has_reason_not_to_delete_user( + self, + user_id: str, + db: AsyncSession, + ) -> str: + return "" + + async def delete_user(self, user_id: str, db: AsyncSession) -> None: + pass diff --git a/tests/core/test_payment.py b/tests/core/test_payment.py index b670855c0a..e842cfe8ba 100644 --- a/tests/core/test_payment.py +++ b/tests/core/test_payment.py @@ -20,6 +20,7 @@ from app.core.payment.types_payment import HelloAssoConfig, HelloAssoConfigName from app.core.schools import schemas_schools from app.core.users import schemas_users +from app.modules.home.user_deleter_home import HomeUserDeleter from app.types.module import Module from tests.commons import ( MockedPaymentTool, @@ -306,6 +307,7 @@ async def test_webhook_payment_callback( default_allowed_groups_ids=[], payment_callback=callback, factory=None, + user_deleter=HomeUserDeleter(), ) mocker.patch( "app.core.payment.endpoints_payment.all_modules", @@ -348,6 +350,7 @@ async def test_webhook_payment_callback_fail( default_allowed_groups_ids=[], payment_callback=callback, factory=None, + user_deleter=HomeUserDeleter(), ) mocker.patch( "app.core.payment.endpoints_payment.all_modules",