99
1010import schemas
1111from auth0 .client import Auth0Client
12- from db .core import BaseModel
12+ from db .core import SoftDeleteModel
1313from db .types import (
1414 ApprovalStatusEnum ,
1515 GroupMembershipData ,
1919from schemas .user import SessionUser
2020
2121
22- class BiocommonsUser (BaseModel , table = True ):
22+ class BiocommonsUser (SoftDeleteModel , table = True ):
2323 __tablename__ = "biocommons_user"
2424 # Auth0 ID
2525 id : str = Field (primary_key = True )
@@ -50,13 +50,14 @@ def has_platform_membership(cls, user_id: str, platform_id: PlatformEnum, sessio
5050 """
5151 Check if a user has a membership for a specific platform.
5252 """
53- return session .exec (
54- select (PlatformMembership ).where (
53+ result = session .exec (
54+ select (PlatformMembership . id ).where (
5555 PlatformMembership .user_id == user_id ,
5656 PlatformMembership .platform_id == platform_id ,
5757 PlatformMembership .approval_status == ApprovalStatusEnum .APPROVED ,
5858 )
59- ).exists ()
59+ ).first ()
60+ return result is not None
6061
6162 @classmethod
6263 def create_from_auth0 (cls , auth0_id : str , auth0_client : Auth0Client ) -> Self :
@@ -87,6 +88,20 @@ def get_or_create(
8788 db_session .commit ()
8889 return user
8990
91+ def delete (self , session : Session , commit : bool = False ) -> "BiocommonsUser" :
92+ """
93+ Soft delete the user and cascade the soft delete to related memberships.
94+ """
95+ for membership in list (self .platform_memberships or []):
96+ if not membership .is_deleted :
97+ membership .delete (session , commit = False )
98+ for membership in list (self .group_memberships or []):
99+ if not membership .is_deleted :
100+ membership .delete (session , commit = False )
101+
102+ super ().delete (session , commit = commit )
103+ return self
104+
90105 def update_from_auth0 (self , auth0_id : str , auth0_client : Auth0Client ) -> Self :
91106 """
92107 Fetch user data from Auth0 and update this object with it.
@@ -133,12 +148,12 @@ def add_group_membership(
133148 return membership
134149
135150
136- class PlatformRoleLink (BaseModel , table = True ):
151+ class PlatformRoleLink (SoftDeleteModel , table = True ):
137152 platform_id : PlatformEnum = Field (primary_key = True , foreign_key = "platform.id" , sa_type = DbEnum (PlatformEnum , name = "PlatformEnum" ))
138153 role_id : str = Field (primary_key = True , foreign_key = "auth0role.id" )
139154
140155
141- class Platform (BaseModel , table = True ):
156+ class Platform (SoftDeleteModel , table = True ):
142157 id : PlatformEnum = Field (primary_key = True , unique = True , sa_type = DbEnum (PlatformEnum , name = "PlatformEnum" ))
143158 # Human-readable name for the platform
144159 name : str = Field (unique = True )
@@ -168,8 +183,17 @@ def get_approved_by_user_id(cls, user_id: str, session: Session) -> list[Self] |
168183 .where (PlatformMembership .approval_status == ApprovalStatusEnum .APPROVED )
169184 ).all ()
170185
186+ def delete (self , session : Session , commit : bool = False ) -> "Platform" :
187+ memberships = list (self .members or [])
188+ for membership in memberships :
189+ if not membership .is_deleted :
190+ membership .delete (session , commit = False )
191+
192+ super ().delete (session , commit = commit )
193+ return self
194+
171195
172- class PlatformMembership (BaseModel , table = True ):
196+ class PlatformMembership (SoftDeleteModel , table = True ):
173197 __table_args__ = (
174198 UniqueConstraint ("platform_id" , "user_id" , name = "platform_user_id_platform_id" ),
175199 )
@@ -224,6 +248,26 @@ def get_by_user_id_and_platform_id(cls, user_id: str, platform_id: PlatformEnum,
224248 )
225249 ).one_or_none ()
226250
251+ def delete (self , session : Session , commit : bool = False ) -> "PlatformMembership" :
252+ history_entries = session .exec (
253+ select (PlatformMembershipHistory )
254+ .where (
255+ PlatformMembershipHistory .user_id == self .user_id ,
256+ PlatformMembershipHistory .platform_id == self .platform_id ,
257+ )
258+ ).all ()
259+
260+ super ().delete (session , commit = False )
261+
262+ for history in history_entries :
263+ if not history .is_deleted :
264+ history .delete (session , commit = False )
265+
266+ if commit :
267+ session .commit ()
268+ session .expunge (self )
269+ return self
270+
227271 def save_history (self , session : Session ) -> "PlatformMembershipHistory" :
228272 # Make sure this object is in the session before accessing relationships
229273 if self not in session :
@@ -261,7 +305,7 @@ def get_data(self) -> PlatformMembershipData:
261305
262306
263307
264- class PlatformMembershipHistory (BaseModel , table = True ):
308+ class PlatformMembershipHistory (SoftDeleteModel , table = True ):
265309 id : uuid .UUID = Field (default_factory = uuid .uuid4 , primary_key = True )
266310 platform_id : PlatformEnum = Field (sa_type = DbEnum (PlatformEnum , name = "PlatformEnum" ))
267311 user_id : str = Field (foreign_key = "biocommons_user.id" )
@@ -288,7 +332,7 @@ class PlatformMembershipHistory(BaseModel, table=True):
288332 )
289333
290334
291- class GroupMembership (BaseModel , table = True ):
335+ class GroupMembership (SoftDeleteModel , table = True ):
292336 """
293337 Stores the current approval status for a user/group pairing.
294338 Note: only one row per user/group, the approval history
@@ -351,15 +395,36 @@ def get_by_user_id_and_group_id(cls, user_id: str, group_id: str, session: Sessi
351395 )
352396 ).one_or_none ()
353397
398+ def delete (self , session : Session , commit : bool = False ) -> "GroupMembership" :
399+ history_entries = session .exec (
400+ select (GroupMembershipHistory )
401+ .where (
402+ GroupMembershipHistory .user_id == self .user_id ,
403+ GroupMembershipHistory .group_id == self .group_id ,
404+ )
405+ ).all ()
406+
407+ super ().delete (session , commit = False )
408+
409+ for history in history_entries :
410+ if not history .is_deleted :
411+ history .delete (session , commit = False )
412+
413+ if commit :
414+ session .commit ()
415+ session .expunge (self )
416+ return self
417+
354418 @classmethod
355419 def has_group_membership (cls , user_id : str , group_id : str , session : Session ) -> bool :
356- return session .exec (
357- select (GroupMembership ).where (
420+ result = session .exec (
421+ select (GroupMembership . id ).where (
358422 GroupMembership .user_id == user_id ,
359423 GroupMembership .group_id == group_id ,
360424 GroupMembership .approval_status == ApprovalStatusEnum .APPROVED ,
361425 )
362- ).exists ()
426+ ).first ()
427+ return result is not None
363428
364429
365430
@@ -422,7 +487,7 @@ def get_data(self) -> GroupMembershipData:
422487
423488
424489
425- class GroupMembershipHistory (BaseModel , table = True ):
490+ class GroupMembershipHistory (SoftDeleteModel , table = True ):
426491 """
427492 Stores the full history of approval decisions for each user
428493 """
@@ -478,12 +543,12 @@ def get_by_user_id(cls, user_id: str, session: Session) -> list[Self] | None:
478543 ).all ()
479544
480545
481- class GroupRoleLink (BaseModel , table = True ):
546+ class GroupRoleLink (SoftDeleteModel , table = True ):
482547 group_id : str = Field (primary_key = True , foreign_key = "biocommonsgroup.group_id" )
483548 role_id : str = Field (primary_key = True , foreign_key = "auth0role.id" )
484549
485550
486- class Auth0Role (BaseModel , table = True ):
551+ class Auth0Role (SoftDeleteModel , table = True ):
487552 id : str = Field (primary_key = True , unique = True )
488553 name : str
489554 description : str = Field (default = "" )
@@ -529,7 +594,7 @@ def get_or_create_by_name(
529594 return role
530595
531596
532- class BiocommonsGroup (BaseModel , table = True ):
597+ class BiocommonsGroup (SoftDeleteModel , table = True ):
533598 # Name of the group / role name in Auth0, e.g. biocommons/group/tsi
534599 group_id : str = Field (primary_key = True , unique = True )
535600 # Human-readable name for the group
@@ -549,6 +614,15 @@ class BiocommonsGroup(BaseModel, table=True):
549614 def get_by_id (cls , group_id : str , session : Session ) -> Self | None :
550615 return session .get (BiocommonsGroup , group_id )
551616
617+ def delete (self , session : Session , commit : bool = False ) -> "BiocommonsGroup" :
618+ memberships = list (self .members or [])
619+ for membership in memberships :
620+ if not membership .is_deleted :
621+ membership .delete (session , commit = False )
622+
623+ super ().delete (session , commit = commit )
624+ return self
625+
552626 def get_admins (self , auth0_client : Auth0Client ) -> set [str ]:
553627 """
554628 Get all admin emails for this group from the Auth0 API, returning a set of emails.
0 commit comments