639 lines
21 KiB
Markdown
639 lines
21 KiB
Markdown
# Profile Deletion Grace Period
|
|
|
|
This document describes the grace period system for profile deletions, which allows profiles to be restored within a configurable timeframe after deletion.
|
|
|
|
## Overview
|
|
|
|
When a profile is deleted (either manually by the user or automatically due to inactivity), the system implements a two-phase deletion process:
|
|
|
|
1. **Soft Deletion**: Profile is marked as deleted but data remains intact
|
|
2. **Permanent Deletion**: After the grace period expires, profile data is permanently anonymized and balances are handled
|
|
|
|
This gives users a safety window to restore their profiles if deleted accidentally or if they change their mind.
|
|
|
|
## Configuration
|
|
|
|
The grace period is configured per platform in the timebank configuration files:
|
|
|
|
**Location:** `config/timebank-*.php`
|
|
|
|
```php
|
|
'delete_profile' => [
|
|
'grace_period_days' => 30, // Number of days before permanent deletion
|
|
// ... other deletion settings
|
|
],
|
|
```
|
|
|
|
**Default Value:** 30 days
|
|
|
|
**Recommendation:** Keep at least 14 days to give users adequate time to restore their profiles.
|
|
|
|
## How It Works
|
|
|
|
### Manual Deletion (User-Initiated)
|
|
|
|
1. User initiates deletion from profile settings
|
|
2. Profile is soft-deleted:
|
|
- `deleted_at` timestamp is set to current time
|
|
- Balance handling preferences are stored in **Redis cache** (key: `balance_handling_{ModelClass}_{ID}`)
|
|
- Cache TTL: grace period + 7 days buffer
|
|
- `comment` field remains **empty** for manual deletions
|
|
- User is logged out and redirected to goodbye page
|
|
3. Confirmation email is sent with grace period information
|
|
4. Profile can be restored during grace period
|
|
5. After grace period expires, scheduled command permanently deletes the profile
|
|
|
|
### Auto-Deletion (Inactivity-Based)
|
|
|
|
1. System detects inactive profile (based on `inactive_at` timestamp)
|
|
2. Warning emails are sent before deletion
|
|
3. Profile is soft-deleted if user doesn't respond
|
|
4. Profile is soft-deleted:
|
|
- `deleted_at` timestamp is set to current time
|
|
- Balance handling preferences are stored in **Redis cache**
|
|
- `comment` field is set to: **"Profile automatically deleted after X days of inactivity."** (translated)
|
|
5. Email notification sent about automatic deletion
|
|
6. Grace period begins, allowing restoration
|
|
7. After grace period expires, permanent deletion occurs
|
|
|
|
### Permanent Deletion Process
|
|
|
|
After the grace period expires:
|
|
|
|
1. **Balance handling preferences are retrieved from Redis cache**:
|
|
- Cache key: `balance_handling_{ModelClass}_{ID}`
|
|
- If cache found: Execute user's choice (donate or destroy currency)
|
|
- **FALLBACK**: If cache lost, **destroy currency** (transfer to debit account)
|
|
- Fallback triggers a warning log for monitoring
|
|
|
|
2. Balance handling is executed:
|
|
- If donated: transfers to selected organization account
|
|
- If deleted (or fallback): balances transferred to debit account (currency removed from circulation)
|
|
|
|
3. Profile data is anonymized:
|
|
- Email: `removed-{id}@remove.ed`
|
|
- Name: `Removed {profiletype} {id}`
|
|
- Password: randomized
|
|
- Personal data: cleared
|
|
- Comment: cleared
|
|
|
|
4. Profile photo deleted
|
|
|
|
5. Related data cleaned up (stars, bookmarks, etc.)
|
|
|
|
6. **Cache is cleared** after successful completion
|
|
|
|
## Artisan Commands
|
|
|
|
### Restore Deleted Profile
|
|
|
|
Restore a profile that was deleted within the grace period.
|
|
|
|
#### Command Signature
|
|
|
|
```bash
|
|
php artisan profiles:restore {username?} {--list} {--type=}
|
|
```
|
|
|
|
#### Parameters
|
|
|
|
- `{username?}` - Optional. Username of the profile to restore
|
|
- `--list` - Display all profiles eligible for restoration
|
|
- `--type=` - Filter by profile type (user, organization, bank, admin)
|
|
|
|
#### Usage Examples
|
|
|
|
**List all restorable profiles:**
|
|
```bash
|
|
php artisan profiles:restore --list
|
|
```
|
|
|
|
**List restorable profiles by type:**
|
|
```bash
|
|
php artisan profiles:restore --list --type=user
|
|
php artisan profiles:restore --list --type=organization
|
|
```
|
|
|
|
**Restore specific profile:**
|
|
```bash
|
|
php artisan profiles:restore john_doe
|
|
```
|
|
|
|
**Interactive mode (prompts for username):**
|
|
```bash
|
|
php artisan profiles:restore
|
|
```
|
|
|
|
#### Output Example
|
|
|
|
```
|
|
Available profiles for restoration:
|
|
|
|
Users:
|
|
┌──────────┬───────────────┬──────────────┬─────────────┐
|
|
│ Username │ Email │ Deleted │ Time Left │
|
|
├──────────┼───────────────┼──────────────┼─────────────┤
|
|
│ john_doe │ john@test.com │ 2 days ago │ 28 days │
|
|
│ jane_smith│ jane@test.com │ 5 hours ago │ 29 days │
|
|
└──────────┴───────────────┴──────────────┴─────────────┘
|
|
|
|
Organizations:
|
|
┌──────────┬───────────────┬──────────────┬─────────────┐
|
|
│ Username │ Email │ Deleted │ Time Left │
|
|
├──────────┼───────────────┼──────────────┼─────────────┤
|
|
│ org_test │ org@test.com │ 1 day ago │ 29 days │
|
|
└──────────┴───────────────┴──────────────┴─────────────┘
|
|
|
|
Total: 3 profile(s) can be restored
|
|
```
|
|
|
|
#### Restoration Process
|
|
|
|
The command will:
|
|
|
|
1. Verify the profile exists and is within grace period
|
|
2. Check that the profile hasn't been anonymized
|
|
3. Clear the `deleted_at` timestamp
|
|
4. Clear stored balance handling preferences from `comment` field
|
|
5. Log the restoration action
|
|
6. Display success message with profile details
|
|
|
|
#### Error Handling
|
|
|
|
The command handles various error scenarios:
|
|
|
|
- **Profile not found**: "Profile 'username' not found"
|
|
- **Already active**: "Profile 'username' is not deleted"
|
|
- **Grace period expired**: "Profile 'username' cannot be restored (grace period expired)"
|
|
- **Already anonymized**: "Profile 'username' has already been permanently deleted"
|
|
|
|
### Permanent Deletion Command (Scheduled)
|
|
|
|
This command runs automatically via Laravel scheduler to permanently delete expired profiles.
|
|
|
|
#### Command Signature
|
|
|
|
```bash
|
|
php artisan profiles:permanently-delete-expired
|
|
```
|
|
|
|
#### Schedule Configuration
|
|
|
|
**Location:** `app/Console/Kernel.php`
|
|
|
|
```php
|
|
$schedule->command('profiles:permanently-delete-expired')
|
|
->daily()
|
|
->at('02:30')
|
|
->withoutOverlapping()
|
|
->appendOutputTo(storage_path('logs/permanent-deletions.log'));
|
|
```
|
|
|
|
**Default Schedule:** Daily at 02:30 AM
|
|
|
|
#### What It Does
|
|
|
|
1. Finds all profiles where:
|
|
- `deleted_at` is set
|
|
- `deleted_at` + grace_period_days < now
|
|
- Email is not already anonymized (not deleted_user_*)
|
|
2. For each expired profile:
|
|
- Retrieves balance handling preferences from `comment` field
|
|
- Calls `DeleteUser::permanentlyDelete()` method
|
|
- Handles balances according to stored preferences
|
|
- Anonymizes profile data
|
|
3. Logs all actions to `storage/logs/permanent-deletions.log`
|
|
|
|
#### Manual Execution
|
|
|
|
You can manually trigger permanent deletion of expired profiles:
|
|
|
|
```bash
|
|
php artisan profiles:permanently-delete-expired
|
|
```
|
|
|
|
**Warning:** This command is destructive and cannot be undone. Only run manually for testing or if you need to immediately process expired profiles.
|
|
|
|
## Email Notifications
|
|
|
|
### User-Deleted Email
|
|
|
|
Sent immediately when a profile is deleted.
|
|
|
|
**Template:** `resources/views/emails/administration/user-deleted.blade.php`
|
|
|
|
**Contains:**
|
|
- Account details (username, email, deletion time)
|
|
- Balance handling information (if applicable)
|
|
- Grace period notification
|
|
- Data handling information
|
|
|
|
**Key Message:**
|
|
> "This action will become permanent after {days} days."
|
|
|
|
### Auto-Deleted Email
|
|
|
|
Sent when a profile is automatically deleted due to inactivity.
|
|
|
|
**Template:** Same as user-deleted, with additional fields:
|
|
- `autoDeleted: true`
|
|
- Deletion reason
|
|
- Days of inactivity
|
|
|
|
## Technical Implementation
|
|
|
|
### Data Storage During Grace Period
|
|
|
|
**Profile Models** (User, Organization, Bank, Admin):
|
|
|
|
- `deleted_at` (datetime): Timestamp when profile was soft-deleted
|
|
- `comment` (text):
|
|
- **Auto-deletion**: Human-readable message (e.g., "Profile automatically deleted after 120 days of inactivity.")
|
|
- **Manual deletion**: Empty (no comment)
|
|
|
|
**Balance Handling Cache Storage (Redis):**
|
|
|
|
**Why Cache?**
|
|
- Balance handling is stored in cache (not database) to keep the `comment` field human-readable for administrators
|
|
- User's balance handling choice (donate vs delete) needs to persist through the grace period
|
|
- Balances are handled during permanent deletion (after grace period expires), not during soft delete
|
|
- Cache automatically expires after grace period + buffer, self-cleaning the data
|
|
- Transactions table provides permanent audit trail of actual balance transfers
|
|
|
|
**Cache Details:**
|
|
- **Key**: `balance_handling_{ModelClass}_{ProfileID}`
|
|
- **TTL**: Grace period days + 7 days buffer
|
|
- **Structure**:
|
|
```php
|
|
[
|
|
'option' => 'donate|delete',
|
|
'donation_account_id' => 123, // null if option is 'delete'
|
|
'stored_at' => '2025-12-19 10:30:00'
|
|
]
|
|
```
|
|
|
|
**Cache Fallback:**
|
|
- If cache is lost during permanent deletion, the system defaults to **destroying currency** (transfer to debit account)
|
|
- A warning is logged: `Balance handling cache lost for profile deletion`
|
|
- This ensures no orphaned balances remain in the system
|
|
- Transaction table will show the fallback action for audit purposes
|
|
|
|
### Translation Keys
|
|
|
|
**Auto-deletion comment** (`resources/lang/*.json`):
|
|
- English: `"Profile automatically deleted after :days days of inactivity."`
|
|
- Dutch: `"Profiel automatisch verwijderd na :days dagen inactiviteit."`
|
|
- French: `"Profil automatiquement supprimé après :days jours d'inactivité."`
|
|
- Spanish: `"Perfil eliminado automáticamente después de :days días de inactividad."`
|
|
- German: `"Profil automatisch nach :days Tagen Inaktivität gelöscht."`
|
|
|
|
### Key Classes and Files
|
|
|
|
#### Actions
|
|
|
|
**`app/Actions/Jetstream/DeleteUser.php`**
|
|
- `delete()`: Soft deletes profile, stores balance preferences in Redis cache
|
|
- `permanentlyDelete()`: Retrieves balance preferences from cache, handles balances, anonymizes profile, clears cache
|
|
|
|
**`app/Actions/Jetstream/RestoreProfile.php`**
|
|
- `restore()`: Restores a soft-deleted profile within grace period
|
|
|
|
#### Commands
|
|
|
|
**`app/Console/Commands/RestoreDeletedProfile.php`**
|
|
- Interactive CLI for restoring profiles
|
|
- Lists eligible profiles
|
|
- Handles restoration process
|
|
|
|
**`app/Console/Commands/PermanentlyDeleteExpiredProfiles.php`**
|
|
- Scheduled task for permanent deletion
|
|
- Processes expired profiles
|
|
- Logs all actions
|
|
|
|
#### Livewire Components
|
|
|
|
**`app/Http/Livewire/Profile/DeleteUserForm.php`**
|
|
- `deleteUser()`: Handles user-initiated deletion
|
|
- Stores balance handling options
|
|
- Manages logout and session
|
|
- Redirects to goodbye page
|
|
|
|
### Views
|
|
|
|
**`resources/views/goodbye-deleted-user.blade.php`**
|
|
- Displayed after manual deletion
|
|
- Shows deletion confirmation and grace period info
|
|
|
|
### Routes
|
|
|
|
**Route:** `/goodbye-deleted-user` (Named: `goodbye-deleted-user`)
|
|
- Displays goodbye page after deletion
|
|
- Requires `session('result')` data
|
|
|
|
## Testing
|
|
|
|
### Test Manual Deletion
|
|
|
|
1. Set grace period to 1 day in config for testing:
|
|
```php
|
|
'grace_period_days' => 1,
|
|
```
|
|
|
|
2. Delete a test profile from profile settings
|
|
|
|
3. Verify:
|
|
- Goodbye page displays
|
|
- Email received with grace period info
|
|
- Profile listed in `php artisan profiles:restore --list`
|
|
|
|
4. Restore the profile:
|
|
```bash
|
|
php artisan profiles:restore test_user
|
|
```
|
|
|
|
5. Verify profile is active and can log in
|
|
|
|
### Test Automatic Deletion
|
|
|
|
1. Run the permanent deletion command after grace period:
|
|
```bash
|
|
php artisan profiles:permanently-delete-expired
|
|
```
|
|
|
|
2. Verify:
|
|
- Profile is permanently deleted
|
|
- Data is anonymized
|
|
- Balances handled according to preferences
|
|
- Logged in `storage/logs/permanent-deletions.log`
|
|
|
|
### Test Edge Cases
|
|
|
|
**Grace period expired:**
|
|
```bash
|
|
php artisan profiles:restore expired_user
|
|
# Should show: "Profile cannot be restored (grace period expired)"
|
|
```
|
|
|
|
**Already anonymized:**
|
|
```bash
|
|
php artisan profiles:restore permanently_deleted_user
|
|
# Should show: "Profile has already been permanently deleted"
|
|
```
|
|
|
|
**Non-existent profile:**
|
|
```bash
|
|
php artisan profiles:restore nonexistent
|
|
# Should show: "Profile 'nonexistent' not found"
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### For Administrators
|
|
|
|
1. **Monitor grace period activity:**
|
|
- Check `storage/logs/permanent-deletions.log` regularly
|
|
- Review restoration requests
|
|
- Adjust grace period based on user feedback
|
|
|
|
2. **Backup before major changes:**
|
|
- Database backup before reducing grace period
|
|
- Test restoration process in staging first
|
|
|
|
3. **User communication:**
|
|
- Clearly communicate grace period in deletion UI
|
|
- Include grace period in Terms of Service
|
|
- Send reminder emails before permanent deletion (optional enhancement)
|
|
|
|
### For Developers
|
|
|
|
1. **Never bypass grace period:**
|
|
- Always use `DeleteUser@delete()` for deletions
|
|
- Never directly set `deleted_at` without storing balance preferences
|
|
- Use `DeleteUser@permanentlyDelete()` only after grace period
|
|
|
|
2. **Preserve balance preferences:**
|
|
- Always store in `comment` field as JSON
|
|
- Include `stored_at` timestamp
|
|
- Validate before permanent deletion
|
|
|
|
3. **Testing:**
|
|
- Use short grace period (1 day) in development
|
|
- Test both restoration and permanent deletion
|
|
- Verify email notifications
|
|
|
|
4. **Cache management:**
|
|
- Clear account balance cache when restoring
|
|
- Invalidate session data appropriately
|
|
- Refresh Elasticsearch indices if needed
|
|
|
|
## Troubleshooting
|
|
|
|
### Profile shows as deleted but can't be restored
|
|
|
|
**Possible causes:**
|
|
1. Grace period already expired
|
|
2. Profile already permanently deleted (email starts with `deleted_user_`)
|
|
3. Database inconsistency
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Check profile status in database
|
|
php artisan tinker
|
|
>>> $user = App\Models\User::withTrashed()->where('name', 'username')->first();
|
|
>>> $user->deleted_at
|
|
>>> $user->email
|
|
>>> Carbon\Carbon::parse($user->deleted_at)->addDays(config('timebank_cc.delete_profile.grace_period_days'))
|
|
```
|
|
|
|
### Balance handling preferences lost
|
|
|
|
**Possible causes:**
|
|
1. `comment` field was cleared manually
|
|
2. Profile updated after deletion
|
|
|
|
**Solution:**
|
|
- Check database for `comment` field content
|
|
- If lost, you'll need to manually decide balance handling during restoration
|
|
|
|
### Permanent deletion not running
|
|
|
|
**Possible causes:**
|
|
1. Laravel scheduler not configured
|
|
2. Command has errors
|
|
3. No expired profiles
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Check scheduler is running
|
|
php artisan schedule:list
|
|
|
|
# Run manually to see errors
|
|
php artisan profiles:permanently-delete-expired
|
|
|
|
# Check logs
|
|
tail -f storage/logs/laravel.log
|
|
tail -f storage/logs/permanent-deletions.log
|
|
```
|
|
|
|
### Restored profile can't log in
|
|
|
|
**Possible causes:**
|
|
1. Cache not cleared
|
|
2. Session data corrupted
|
|
3. Password was changed during soft deletion
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Clear all caches
|
|
php artisan cache:clear
|
|
php artisan view:clear
|
|
php artisan config:clear
|
|
|
|
# Reset password if needed
|
|
php artisan tinker
|
|
>>> $user = App\Models\User::where('name', 'username')->first();
|
|
>>> $user->password = Hash::make('newpassword');
|
|
>>> $user->save();
|
|
```
|
|
|
|
## Future Enhancements
|
|
|
|
Potential improvements to the grace period system:
|
|
|
|
1. **Reminder emails:**
|
|
- Send email 7 days before permanent deletion
|
|
- Send final warning 24 hours before deletion
|
|
|
|
2. **Self-service restoration:**
|
|
- Allow users to restore their own profiles via email link
|
|
- Magic link authentication for deleted accounts
|
|
|
|
3. **Extended grace for specific cases:**
|
|
- Longer grace period for profiles with high balance
|
|
- Longer grace for organization/bank profiles
|
|
|
|
4. **Audit trail:**
|
|
- Track who restored profiles and when
|
|
- Log reason for restoration
|
|
- Activity log entries for all grace period actions
|
|
|
|
5. **Dashboard for admins:**
|
|
- View all profiles in grace period
|
|
- Bulk restore operations
|
|
- Analytics on deletion patterns
|
|
|
|
### WireChat Message Handling
|
|
|
|
All WireChat messages are eventually deleted to prevent orphaned data. When a profile is permanently deleted, their sent messages are released for cleanup.
|
|
|
|
#### Configuration
|
|
|
|
**Location:** `config/timebank-*.php`
|
|
|
|
```php
|
|
'wirechat' => [
|
|
'disappearing_messages' => [
|
|
'allow_users_to_keep' => true, // Allow marking messages as "kept"
|
|
'duration' => 30, // Days before regular messages deleted
|
|
'kept_messages_duration' => 90, // Days before kept messages deleted
|
|
'cleanup_schedule' => 'everyFiveMinutes',
|
|
],
|
|
'profile_deletion' => [
|
|
'release_kept_messages' => true, // MUST be true to prevent orphans
|
|
],
|
|
],
|
|
```
|
|
|
|
**Note:** Disappearing messages are ALWAYS enabled. There is no 'enabled' flag - messages are always cleaned up to prevent orphaned data from deleted profiles.
|
|
|
|
#### Behavior
|
|
|
|
**All messages are eventually deleted:**
|
|
- Regular messages: Deleted after `duration` days (default: 30 days)
|
|
- Kept messages: Deleted after `kept_messages_duration` days (default: 90 days)
|
|
- Messages from deleted profiles: Released and then deleted after `duration` days
|
|
|
|
**When profile is permanently deleted:**
|
|
1. Profile enters grace period (default: 30 days)
|
|
2. Grace period expires → permanent deletion runs
|
|
3. All kept messages sent by that profile: `kept_at = null` (released)
|
|
4. Messages become regular messages, deleted after `duration` days
|
|
5. This prevents orphaned messages with invalid `sendable_id`
|
|
|
|
#### Timeline Example
|
|
|
|
**With default settings:**
|
|
- `duration = 30` days
|
|
- `kept_messages_duration = 90` days
|
|
- `release_kept_messages = true`
|
|
|
|
```
|
|
Scenario 1: Normal message lifecycle
|
|
Day 0: User sends message
|
|
Day 30: Message automatically deleted by DeleteExpiredWireChatMessagesJob
|
|
|
|
Scenario 2: Kept message lifecycle
|
|
Day 0: User sends message, recipient marks as "kept"
|
|
Day 90: Kept message automatically deleted by cleanup job
|
|
|
|
Scenario 3: Sender deletes profile
|
|
Day 0: User sends message, recipient marks as "kept"
|
|
Day 365: User deletes profile → grace period starts
|
|
Day 395: Grace period expires → permanent deletion
|
|
→ kept_at set to null (message "released")
|
|
→ Message now subject to normal 30-day duration
|
|
Day 425: Message deleted by cleanup job (30 days after release)
|
|
```
|
|
|
|
#### Why This Prevents Orphaned Messages
|
|
|
|
**Without `release_kept_messages`:**
|
|
- Deleted profile has `sendable_id = 123`, `sendable_type = 'App\Models\User'`
|
|
- Profile anonymized: name becomes "Removed user abc123ef"
|
|
- Kept messages still reference old `sendable_id` pointing to anonymized profile
|
|
- Messages orphaned forever with no way to trace original sender
|
|
- Database accumulates orphaned data indefinitely
|
|
|
|
**With `release_kept_messages = true`:**
|
|
- Kept messages released when profile permanently deleted
|
|
- Normal cleanup job deletes them after `duration` days
|
|
- No orphaned data remains in database
|
|
- Clean data retention without privacy concerns
|
|
- Conversation history maintained for recipients until cleanup
|
|
|
|
#### Configuration Notes
|
|
|
|
- `release_kept_messages` should ALWAYS be `true` (default and recommended)
|
|
- Setting it to `false` will cause orphaned messages to accumulate
|
|
- All durations specified in DAYS (converted to seconds internally)
|
|
- Cleanup job runs based on `cleanup_schedule` (default: every 5 minutes)
|
|
- User control is hardcoded to `false` (users cannot change message duration)
|
|
- Disappearing messages are hardcoded to enabled (always active)
|
|
|
|
## Related Documentation
|
|
|
|
- **Profile Auto-Delete:** `references/PROFILE_AUTO_DELETE.md` - Automatic deletion based on inactivity
|
|
- **Profile Inactive Config:** `references/PROFILE_INACTIVE_CONFIG.md` - Configuration for inactive profile detection
|
|
- **Email Testing:** `references/EMAIL-TESTING-GUIDE.md` - Testing email notifications
|
|
|
|
## Changelog
|
|
|
|
### Version 1.1 (2025-12-19)
|
|
- **BREAKING CHANGE**: Balance handling data moved from `comment` field to Redis cache
|
|
- Added cache fallback: automatically destroys currency if cache is lost
|
|
- Comment field now human-readable:
|
|
- Auto-deletion: Shows translated message "Profile automatically deleted after X days of inactivity"
|
|
- Manual deletion: Empty (no comment)
|
|
- Added translation keys for auto-deletion comments in 5 languages (en, nl, fr, de, es)
|
|
- Enhanced logging: Warning logged when cache fallback is triggered
|
|
- Cache automatically expires after grace period + 7 days buffer
|
|
- Transaction table provides complete audit trail of balance handling
|
|
|
|
### Version 1.0 (2025-12-19)
|
|
- Initial implementation of grace period system
|
|
- Two-phase deletion process (soft delete + permanent delete)
|
|
- Restore command with interactive and list modes
|
|
- Scheduled permanent deletion command
|
|
- Email notifications with grace period information
|
|
- Balance handling deferred until permanent deletion
|