@@ -28,10 +28,11 @@ import (
2828
2929var (
3030 // Error defines the pendingdelete chore errors class.
31- Error = errs .Class ("pendingdelete" )
32- mon = monkit .Package ()
33- userDataTask = "user-pending-deletion"
34- projectDataTask = "project-pending-deletion"
31+ Error = errs .Class ("pendingdelete" )
32+ mon = monkit .Package ()
33+ frozenDataTask = "frozen-user-deletion"
34+ projectDataTask = "project-pending-deletion"
35+ pendingDeleteUserDataTask = "user-pending-deletion"
3536)
3637
3738// Config contains configuration for pending deletion project cleanup.
@@ -42,6 +43,7 @@ type Config struct {
4243 DeleteConcurrency int `help:"how many delete workers to run at a time" default:"1"`
4344
4445 Project DeleteTypeConfig
46+ User DeleteTypeConfig
4547 ViolationFreeze DeleteTypeConfig
4648 BillingFreeze DeleteTypeConfig
4749 TrialFreeze DeleteTypeConfig
@@ -114,10 +116,19 @@ func (chore *Chore) Run(ctx context.Context) (err error) {
114116
115117 if len (chore .enabledFrozenDeleteTypes ()) != 0 {
116118 group .Go (func () error {
117- return chore .runDeleteUserData (ctx )
119+ return chore .runDeleteFrozenUsers (ctx )
118120 })
119121 }
120122
123+ if chore .config .User .Enabled {
124+ group .Go (func () error {
125+ return chore .runDeletePendingDeletionUsers (ctx )
126+ })
127+ } else {
128+ chore .log .Info ("skipping deleting pending deletion users because it is disabled in config" ,
129+ zap .String ("task" , pendingDeleteUserDataTask ))
130+ }
131+
121132 return group .Wait ()
122133 })
123134}
@@ -172,7 +183,7 @@ func (chore *Chore) runDeleteProjects(ctx context.Context) (err error) {
172183 }
173184
174185 if project .Status == nil || * project .Status != console .ProjectPendingDeletion {
175- chore .log .Error ("project not marked pending deletion, skipping" ,
186+ chore .log .Info ("project not marked pending deletion, skipping" ,
176187 zap .String ("task" , projectDataTask ),
177188 zap .String ("projectID" , p .ProjectID .String ()),
178189 zap .String ("userID" , p .OwnerID .String ()),
@@ -208,6 +219,115 @@ func (chore *Chore) runDeleteProjects(ctx context.Context) (err error) {
208219 return Error .Wrap (errGrp .Err ())
209220}
210221
222+ func (chore * Chore ) runDeletePendingDeletionUsers (ctx context.Context ) (err error ) {
223+ defer mon .Task ()(& ctx )(& err )
224+
225+ chore .log .Info ("deleting pending deletion users" , zap .String ("task" , pendingDeleteUserDataTask ))
226+
227+ mu := new (sync.Mutex )
228+ var errGrp errs.Group
229+
230+ addErr := func (err error ) {
231+ mu .Lock ()
232+ errGrp .Add (err )
233+ mu .Unlock ()
234+ }
235+
236+ errorLog := func (msg string , err2 error , args ... zap.Field ) {
237+ chore .log .Error (msg ,
238+ zap .String ("task" , pendingDeleteUserDataTask ),
239+ zap .Error (err2 ),
240+ )
241+ }
242+
243+ var skippedUsers , deletedUsers , deletedProjects atomic.Int64
244+ hasNext := true
245+ for hasNext {
246+ idsPage , err := chore .store .Users ().ListPendingDeletionBefore (
247+ ctx ,
248+ chore .config .ListLimit , chore .nowFn ().Add (- chore .config .User .BufferTime ),
249+ )
250+ if err != nil {
251+ chore .log .Error ("failed to get users for deletion" ,
252+ zap .String ("task" , pendingDeleteUserDataTask ), zap .Error (err ))
253+ return err
254+ }
255+ hasNext = idsPage .HasNext
256+
257+ if ! hasNext && len (idsPage .IDs ) == 0 {
258+ break
259+ }
260+
261+ limiter := sync2 .NewLimiter (chore .config .DeleteConcurrency )
262+
263+ for _ , userID := range idsPage .IDs {
264+ limiter .Go (ctx , func () {
265+ // confirm user still marked pending deletion
266+ user , err := chore .store .Users ().Get (ctx , userID )
267+ if err != nil {
268+ chore .log .Error ("failed to get user for deletion" ,
269+ zap .String ("task" , pendingDeleteUserDataTask ),
270+ zap .String ("userID" , userID .String ()),
271+ zap .Error (err ),
272+ )
273+ addErr (err )
274+ return
275+ }
276+
277+ if user .Status != console .PendingDeletion {
278+ chore .log .Info ("user not marked pending deletion, skipping" ,
279+ zap .String ("task" , pendingDeleteUserDataTask ),
280+ zap .String ("userID" , userID .String ()),
281+ )
282+ skippedUsers .Add (1 )
283+ return
284+ }
285+
286+ projects , err := chore .store .Projects ().GetActiveByUserID (ctx , userID )
287+ if err != nil {
288+ errorLog ("failed to get projects for deletion" , err , zap .String ("userID" , userID .String ()))
289+ addErr (err )
290+ return
291+ }
292+
293+ for _ , project := range projects {
294+ err := chore .deleteData (ctx , project .ID , userID , pendingDeleteUserDataTask )
295+ if err != nil {
296+ addErr (err )
297+ return
298+ }
299+
300+ err = chore .disableProject (ctx , project .ID , project .PublicID , userID , pendingDeleteUserDataTask )
301+ if err != nil {
302+ addErr (err )
303+ return
304+ }
305+
306+ deletedProjects .Add (1 )
307+ }
308+
309+ err = chore .deactivateUser (ctx , userID , nil , pendingDeleteUserDataTask )
310+ if err != nil {
311+ addErr (err )
312+ return
313+ }
314+ deletedUsers .Add (1 )
315+ })
316+ }
317+
318+ limiter .Wait ()
319+ }
320+
321+ chore .log .Info ("finished deleting users" ,
322+ zap .String ("task" , pendingDeleteUserDataTask ),
323+ zap .Int64 ("skipped_users" , skippedUsers .Load ()),
324+ zap .Int64 ("deleted_users" , deletedUsers .Load ()),
325+ zap .Int64 ("deleted_projects" , deletedProjects .Load ()),
326+ )
327+
328+ return Error .Wrap (errGrp .Err ())
329+ }
330+
211331func (chore * Chore ) enabledFrozenDeleteTypes () []console.EventTypeAndTime {
212332 var eventTypes []console.EventTypeAndTime
213333 if chore .config .ViolationFreeze .Enabled {
@@ -230,18 +350,18 @@ func (chore *Chore) enabledFrozenDeleteTypes() []console.EventTypeAndTime {
230350 }
231351 if len (eventTypes ) == 0 {
232352 chore .log .Info ("no freeze event types are enabled, skipping unpaid data deletion" ,
233- zap .String ("task" , userDataTask ),
353+ zap .String ("task" , frozenDataTask ),
234354 )
235355 return nil
236356 }
237357
238358 return eventTypes
239359}
240360
241- func (chore * Chore ) runDeleteUserData (ctx context.Context ) (err error ) {
361+ func (chore * Chore ) runDeleteFrozenUsers (ctx context.Context ) (err error ) {
242362 defer mon .Task ()(& ctx )(& err )
243363
244- chore .log .Info ("deleting pending deletion users and data" , zap .String ("task" , userDataTask ))
364+ chore .log .Info ("deleting pending deletion users and data" , zap .String ("task" , frozenDataTask ))
245365
246366 mu := new (sync.Mutex )
247367 var errGrp errs.Group
@@ -254,7 +374,7 @@ func (chore *Chore) runDeleteUserData(ctx context.Context) (err error) {
254374
255375 errorLog := func (msg string , err2 error , args ... zap.Field ) {
256376 chore .log .Error (msg ,
257- zap .String ("task" , userDataTask ),
377+ zap .String ("task" , frozenDataTask ),
258378 zap .Error (err2 ),
259379 )
260380 }
@@ -290,9 +410,9 @@ func (chore *Chore) runDeleteUserData(ctx context.Context) (err error) {
290410 }
291411
292412 if user .Status != console .PendingDeletion {
293- chore .log .Error ("user not marked pending deletion, skipping" ,
413+ chore .log .Info ("user not marked pending deletion, skipping" ,
294414 zap .String ("userID" , event .UserID .String ()),
295- zap .String ("task" , userDataTask ),
415+ zap .String ("task" , frozenDataTask ),
296416 )
297417 skippedUsers .Add (1 )
298418 return
@@ -306,13 +426,13 @@ func (chore *Chore) runDeleteUserData(ctx context.Context) (err error) {
306426 }
307427
308428 for _ , project := range projects {
309- err := chore .deleteData (ctx , project .ID , event .UserID , userDataTask )
429+ err := chore .deleteData (ctx , project .ID , event .UserID , frozenDataTask )
310430 if err != nil {
311431 addErr (err )
312432 return
313433 }
314434
315- err = chore .disableProject (ctx , project .ID , project .PublicID , event .UserID , userDataTask )
435+ err = chore .disableProject (ctx , project .ID , project .PublicID , event .UserID , frozenDataTask )
316436 if err != nil {
317437 addErr (err )
318438 return
@@ -321,47 +441,20 @@ func (chore *Chore) runDeleteUserData(ctx context.Context) (err error) {
321441 deletedProjects .Add (1 )
322442 }
323443
324- // this is ideally part of chore.deactivateUser, but we cannot use a db object in
325- // a transaction, which accounts.CreditCards().RemoveAll does internally.
326- err = chore .accounts .CreditCards ().RemoveAll (ctx , event .UserID )
327- if err != nil {
328- chore .log .Error ("failed to remove user credit cards" ,
329- zap .String ("userID" , event .UserID .String ()),
330- zap .Error (err ),
331- )
332- addErr (err )
333- return
334- }
335-
336- err = chore .store .WithTx (ctx , func (ctx context.Context , tx console.DBTx ) error {
337- err = chore .deactivateUser (ctx , tx , event )
338- if err != nil {
339- return err
340- }
341-
342- // remove the freeze event so this user is not retrieved by GetEscalatedEventsOlderThanToDelete
343- err = tx .AccountFreezeEvents ().DeleteByUserIDAndEvent (ctx , event .UserID , event .Type )
344- if err != nil {
345- chore .log .Error ("failed to remove freeze event" , zap .String ("userID" , event .UserID .String ()), zap .Error (err ))
346- return err
347- }
348-
349- deletedUsers .Add (1 )
350-
351- return nil
352- })
444+ err = chore .deactivateUser (ctx , event .UserID , & event .Type , frozenDataTask )
353445 if err != nil {
354446 addErr (err )
355447 return
356448 }
449+ deletedUsers .Add (1 )
357450 })
358451 }
359452
360453 limiter .Wait ()
361454 }
362455
363456 chore .log .Info ("finished deleting pending deletion users and data" ,
364- zap .String ("task" , userDataTask ),
457+ zap .String ("task" , frozenDataTask ),
365458 zap .Int64 ("skipped_users" , skippedUsers .Load ()),
366459 zap .Int64 ("deleted_users" , deletedUsers .Load ()),
367460 zap .Int64 ("deleted_projects" , deletedProjects .Load ()),
@@ -485,44 +578,66 @@ func (chore *Chore) disableProject(ctx context.Context, projectID, projectPublic
485578 })
486579}
487580
488- func (chore * Chore ) deactivateUser (ctx context.Context , tx console. DBTx , event console.EventWithUser ) (err error ) {
489- _ , err = tx . WebappSessions ().DeleteAllByUserID (ctx , event . UserID )
581+ func (chore * Chore ) deactivateUser (ctx context.Context , userID uuid. UUID , freezeEventType * console.AccountFreezeEventType , task string ) (err error ) {
582+ err = chore . accounts . CreditCards ().RemoveAll (ctx , userID )
490583 if err != nil {
491- chore .log .Error ("failed to remove webapp sessions for user " ,
492- zap .String ("task" , userDataTask ),
493- zap .String ("userID" , event . UserID .String ()),
584+ chore .log .Error ("failed to remove user credit cards " ,
585+ zap .String ("task" , task ),
586+ zap .String ("userID" , userID .String ()),
494587 zap .Error (err ),
495588 )
496589 return err
497590 }
498591
499- deactivatedEmail := fmt .
Sprintf (
"deactivated+%[email protected] " ,
event .
UserID .
String ())
500- status := console .Deleted
592+ return chore .store .WithTx (ctx , func (ctx context.Context , tx console.DBTx ) error {
593+ _ , err = tx .WebappSessions ().DeleteAllByUserID (ctx , userID )
594+ if err != nil {
595+ chore .log .Error ("failed to remove webapp sessions for user" ,
596+ zap .String ("task" , task ),
597+ zap .String ("userID" , userID .String ()),
598+ zap .Error (err ),
599+ )
600+ return err
601+ }
501602
502- err = tx .Users ().Update (ctx , event .UserID , console.UpdateUserRequest {
503- FullName : new (string ),
504- ShortName : new (* string ),
505- Email : & deactivatedEmail ,
506- Status : & status ,
507- ExternalID : new (* string ),
508- EmailChangeVerificationStep : new (int ),
509- })
510- if err != nil {
511- chore .log .Error ("failed to update user status to Deleted" ,
512- zap .String ("task" , userDataTask ),
513- zap .String ("userID" , event .UserID .String ()),
514- zap .Error (err ),
515- )
516- return err
517- }
603+ deactivatedEmail := fmt .
Sprintf (
"deactivated+%[email protected] " ,
userID .
String ())
604+ status := console .Deleted
605+ err = tx .Users ().Update (ctx , userID , console.UpdateUserRequest {
606+ FullName : new (string ),
607+ ShortName : new (* string ),
608+ Email : & deactivatedEmail ,
609+ Status : & status ,
610+ ExternalID : new (* string ),
611+ EmailChangeVerificationStep : new (int ),
612+ })
613+ if err != nil {
614+ chore .log .Error ("failed to update user status to Deleted" ,
615+ zap .String ("task" , task ),
616+ zap .String ("userID" , userID .String ()),
617+ zap .Error (err ),
618+ )
619+ return err
620+ }
518621
519- chore .log .Info (
520- "user deactivated" ,
521- zap .String ("task" , userDataTask ),
522- zap .String ("userID" , event .UserID .String ()),
523- )
622+ if freezeEventType != nil {
623+ err = tx .AccountFreezeEvents ().DeleteByUserIDAndEvent (ctx , userID , * freezeEventType )
624+ if err != nil {
625+ chore .log .Error ("failed to remove freeze event" ,
626+ zap .String ("task" , task ),
627+ zap .String ("userID" , userID .String ()),
628+ zap .Error (err ))
629+ return err
630+ }
631+ }
524632
525- return nil
633+ chore .log .Info (
634+ "user deactivated" ,
635+ zap .String ("task" , task ),
636+ zap .String ("userID" , userID .String ()),
637+ )
638+
639+ return nil
640+ })
526641}
527642
528643// Close stops chore.
0 commit comments