package lifecycle import ( "time" "git.apinb.com/bsm-sdk/core/utils" "git.apinb.com/ops/files/internal/models" "gorm.io/gorm" ) const ( deletionLeaseDuration = 5 * time.Minute DeleteOperationTimeout = 30 * time.Second ) type DeletionLease struct { Token string LeaseUntil time.Time } func ClaimDeletion(db *gorm.DB, id uint, allowedStatuses []models.FileStatus, now time.Time) (DeletionLease, bool, error) { query := db.Model(&models.FileObject{}). Where("id = ? AND status IN ?", id, allowedStatuses) return claimDeletion(query, now) } func ClaimExpiredPendingDeletion(db *gorm.DB, id uint, now time.Time) (DeletionLease, bool, error) { query := db.Model(&models.FileObject{}). Where("id = ? AND status = ? AND expires_at <= ?", id, models.FileStatusPending, now) return claimDeletion(query, now) } func ClaimDeletionRetry(db *gorm.DB, id uint, now time.Time) (DeletionLease, bool, error) { query := db.Model(&models.FileObject{}). Where("id = ? AND status = ?", id, models.FileStatusDeleting). Where("delete_lease_until IS NULL OR delete_lease_until <= ?", now) return claimDeletion(query, now) } func FinalizeDeletion(db *gorm.DB, id uint, token string) error { if token == "" { return gorm.ErrRecordNotFound } return db.Transaction(func(tx *gorm.DB) error { result := tx.Model(&models.FileObject{}). Where("id = ? AND status = ? AND delete_token = ?", id, models.FileStatusDeleting, token). Updates(map[string]any{ "status": models.FileStatusExpired, "delete_token": "", "delete_lease_until": nil, }) if result.Error != nil { return result.Error } if result.RowsAffected != 1 { return gorm.ErrRecordNotFound } result = tx.Where("id = ? AND status = ?", id, models.FileStatusExpired). Delete(&models.FileObject{}) if result.Error != nil { return result.Error } if result.RowsAffected != 1 { return gorm.ErrRecordNotFound } return nil }) } func claimDeletion(query *gorm.DB, now time.Time) (DeletionLease, bool, error) { lease := DeletionLease{ Token: utils.ULID(), LeaseUntil: now.Add(deletionLeaseDuration), } result := query.Updates(map[string]any{ "status": models.FileStatusDeleting, "delete_token": lease.Token, "delete_lease_until": lease.LeaseUntil, }) if result.Error != nil { return DeletionLease{}, false, result.Error } if result.RowsAffected != 1 { return DeletionLease{}, false, nil } return lease, true, nil }