1140 lines
34 KiB
Markdown
1140 lines
34 KiB
Markdown
# Profile Auto-Delete System Reference
|
|
|
|
This document provides comprehensive documentation on the profile auto-delete system, including configuration, commands, workflows, and testing procedures.
|
|
|
|
## Table of Contents
|
|
1. [Overview](#overview)
|
|
2. [System Architecture](#system-architecture)
|
|
3. [Configuration Structure](#configuration-structure)
|
|
4. [Artisan Commands](#artisan-commands)
|
|
5. [Workflow Process](#workflow-process)
|
|
6. [Scheduled Execution](#scheduled-execution)
|
|
7. [Testing](#testing)
|
|
8. [Production Configuration](#production-configuration)
|
|
9. [Troubleshooting](#troubleshooting)
|
|
10. [Related Systems](#related-systems)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The profile auto-delete system automatically manages inactive profiles through a two-step process:
|
|
|
|
1. **Mark Inactive**: Profiles that haven't logged in for a configured period are automatically marked as inactive
|
|
2. **Process Inactive**: Inactive profiles receive progressive warning emails before final deletion
|
|
|
|
### Purpose
|
|
- Maintain platform health by removing abandoned profiles
|
|
- Protect user privacy by deleting old personal data
|
|
- Send warning emails giving users a chance to prevent deletion
|
|
- Comply with data retention policies and GDPR requirements
|
|
- Keep the active user base current and engaged
|
|
|
|
### Key Features
|
|
- **Two-step process**: Separate inactive marking from deletion
|
|
- **Progressive warnings**: 3 escalating warning emails before deletion
|
|
- **Grace period**: Deleted profiles can be restored within configurable timeframe (default: 30 days)
|
|
- **Configurable thresholds**: All timeframes configurable per platform
|
|
- **Balance protection**: Prevents deletion of profiles with negative balances
|
|
- **Transaction anonymization**: Past transactions preserved but anonymized
|
|
- **Balance handling**: Multiple options for account balance disposal
|
|
- **Automatic scheduling**: Runs daily via Laravel scheduler
|
|
- **Comprehensive logging**: All actions logged for audit trail
|
|
|
|
---
|
|
|
|
## System Architecture
|
|
|
|
### Two-Step Process
|
|
|
|
#### Step 1: Mark Inactive (Daily at 01:30)
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ profiles:mark-inactive │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ • Finds profiles where last_login_at > days_not_logged_in │
|
|
│ • Sets inactive_at = now() │
|
|
│ • Profile becomes hidden/labeled (per config) │
|
|
│ • Logs action to mark-inactive-profiles.log │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
#### Step 2: Process Inactive (Daily at 02:00)
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ profiles:process-inactive │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ • Finds profiles where inactive_at is set │
|
|
│ • Calculates days since inactive_at │
|
|
│ • Sends appropriate warning based on thresholds: │
|
|
│ - Warning 1: X days after inactive │
|
|
│ - Warning 2: Y days after inactive │
|
|
│ - Final Warning: Z days after inactive │
|
|
│ - Delete: N days after inactive │
|
|
│ • Logs all actions to inactive-profiles.log │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Database Schema
|
|
|
|
**Users/Organizations Table:**
|
|
- `last_login_at` (timestamp, nullable) - Last successful login
|
|
- `inactive_at` (timestamp, nullable) - When profile was marked inactive
|
|
- `deleted_at` (timestamp, nullable) - Soft delete timestamp
|
|
|
|
**Field States:**
|
|
- `inactive_at = NULL` → Profile is active
|
|
- `inactive_at = [past date]` → Profile is inactive, awaiting deletion
|
|
- `deleted_at = [date]` → Profile is soft-deleted
|
|
|
|
---
|
|
|
|
## Configuration Structure
|
|
|
|
### Location
|
|
Configuration is defined in:
|
|
- `config/timebank-default.php` (lines 1405-1420+)
|
|
- `config/timebank_cc.php` (lines 1405-1420+)
|
|
- Active config loaded via `TIMEBANK_CONFIG` environment variable
|
|
|
|
### Complete Configuration
|
|
|
|
```php
|
|
// Step 1: Mark profiles as inactive
|
|
'profile_inactive' => [
|
|
'days_not_logged_in' => 350, // Profile marked inactive after this many days
|
|
're-activate_at_login' => true, // Auto-reactivate when user logs in
|
|
'messenger_hidden' => true, // Hide from messenger search
|
|
'profile_search_hidden' => true, // Hide from main search
|
|
'profile_hidden' => true, // Hide profile page from non-admins
|
|
'profile_labeled' => true, // Show "Inactive" label
|
|
],
|
|
|
|
// Step 2: Process inactive profiles (send warnings and delete)
|
|
'delete_profile' => [
|
|
// NOTE: These are days AFTER the profile is marked inactive
|
|
// Total time = days_not_logged_in + days_after_inactive
|
|
'days_after_inactive' => [
|
|
'warning_1' => 7, // First warning
|
|
'warning_2' => 10, // Second warning
|
|
'warning_final' => 14, // Final warning
|
|
'run_delete' => 15, // Delete profile
|
|
],
|
|
'account_balances' => [
|
|
// Handle account balances on deletion (elsif logic):
|
|
'donate_balances_to_organization_account_specified' => true,
|
|
'transfer_balances_to_bank_client' => false,
|
|
'transfer_balances_to_account_id' => null,
|
|
'transfer_balances_to_debit_account' => true,
|
|
],
|
|
],
|
|
```
|
|
|
|
### Configuration Timeline Example
|
|
|
|
With the configuration above:
|
|
- **Day 0**: User last logs in
|
|
- **Day 350**: Profile marked inactive (`profiles:mark-inactive`)
|
|
- **Day 357**: First warning email sent (350 + 7)
|
|
- **Day 360**: Second warning email sent (350 + 10)
|
|
- **Day 364**: Final warning email sent (350 + 14)
|
|
- **Day 365**: Profile deleted (350 + 15)
|
|
|
|
**Total: 1 year from last login to deletion**
|
|
|
|
---
|
|
|
|
## Artisan Commands
|
|
|
|
### 1. Mark Inactive Profiles
|
|
|
|
**Command:** `php artisan profiles:mark-inactive`
|
|
|
|
**Purpose:** Automatically mark profiles as inactive based on login inactivity.
|
|
|
|
**What It Does:**
|
|
1. Finds all User and Organization profiles where:
|
|
- `last_login_at` is older than configured `days_not_logged_in`
|
|
- `inactive_at` is NULL (not already marked inactive)
|
|
2. Sets `inactive_at` to current timestamp
|
|
3. Logs action to `storage/logs/mark-inactive-profiles.log`
|
|
|
|
**Usage:**
|
|
```bash
|
|
# Mark all eligible profiles as inactive
|
|
php artisan profiles:mark-inactive
|
|
```
|
|
|
|
**Output Example:**
|
|
```
|
|
Checking profiles for inactivity...
|
|
[User] Marked inactive: John Doe (1095 days)
|
|
[User] Marked inactive: Jane Smith (1200 days)
|
|
[Organization] Marked inactive: Community Center (800 days)
|
|
Processing complete: 3 profiles marked as inactive
|
|
```
|
|
|
|
**Log File:** `storage/logs/mark-inactive-profiles.log`
|
|
```
|
|
[2025-11-14 01:30:15] === Starting profile inactivity check ===
|
|
[2025-11-14 01:30:16] [User] Marked INACTIVE: John Doe (ID: 123) - Not logged in for 1095 days (last login: 2022-01-01)
|
|
[2025-11-14 01:30:17] [User] Marked INACTIVE: Jane Smith (ID: 456) - Not logged in for 1200 days (last login: 2021-09-15)
|
|
[2025-11-14 01:30:18] === Completed: 3 profiles marked inactive ===
|
|
```
|
|
|
|
**Schedule:** Runs daily at 01:30 (via `app/Console/Kernel.php`)
|
|
|
|
---
|
|
|
|
### 2. Process Inactive Profiles
|
|
|
|
**Command:** `php artisan profiles:process-inactive`
|
|
|
|
**Purpose:** Send warning emails and delete profiles based on inactivity duration.
|
|
|
|
**What It Does:**
|
|
1. Finds all User and Organization profiles where:
|
|
- `inactive_at` is set (marked as inactive)
|
|
- `deleted_at` is NULL (not already deleted)
|
|
2. Calculates days since `inactive_at`
|
|
3. Determines action based on thresholds:
|
|
- Send Warning 1 email
|
|
- Send Warning 2 email
|
|
- Send Final Warning email
|
|
- Delete profile
|
|
4. Emails are queued (not sent immediately)
|
|
5. Logs all actions to `storage/logs/inactive-profiles.log`
|
|
|
|
**Usage:**
|
|
```bash
|
|
# Process all inactive profiles
|
|
php artisan profiles:process-inactive
|
|
```
|
|
|
|
**Output Example:**
|
|
```
|
|
Processing inactive profiles...
|
|
[User] warning_1: John Doe (7 days remaining)
|
|
[User] warning_2: Jane Smith (5 days remaining)
|
|
[Organization] final: Community Center (1 day remaining)
|
|
[User] Deleted: Old User
|
|
Processing complete: 3 warnings sent, 1 profiles deleted
|
|
```
|
|
|
|
**Log File:** `storage/logs/inactive-profiles.log`
|
|
```
|
|
[2025-11-14 02:00:15] === Starting inactive profile processing ===
|
|
[2025-11-14 02:00:16] [User] warning_1 sent to John Doe (ID: 123) - Inactive for 7.5 days, 7.5 days remaining
|
|
[2025-11-14 02:00:17] [User] warning_2 sent to Jane Smith (ID: 456) - Inactive for 10.2 days, 4.8 days remaining
|
|
[2025-11-14 02:00:18] [Organization] final sent to Community Center (ID: 789) - Inactive for 14.1 days, 0.9 days remaining
|
|
[2025-11-14 02:00:19] [User] DELETED Old User (ID: 321) - Inactive for 15.5 days - Confirmation email sent to olduser@example.com
|
|
[2025-11-14 02:00:20] === Completed: 3 warnings, 1 deletions ===
|
|
```
|
|
|
|
**Schedule:** Runs daily at 02:00 (via `app/Console/Kernel.php`)
|
|
|
|
**Important Notes:**
|
|
- Emails are queued, not sent immediately (requires queue worker)
|
|
- Profiles with negative balances are skipped and logged
|
|
- Each profile receives only ONE email per day (based on current threshold)
|
|
- Soft-deleted profiles are automatically excluded
|
|
- Deletion confirmation emails are sent automatically after successful deletion
|
|
|
|
---
|
|
|
|
## Workflow Process
|
|
|
|
### Full Lifecycle Example
|
|
|
|
#### Timeline: Production Configuration
|
|
|
|
```
|
|
Day 0 (Jan 1, 2024):
|
|
├─ User logs in for last time
|
|
└─ last_login_at = 2024-01-01 10:00:00
|
|
|
|
Day 350 (Dec 16, 2024) at 01:30:
|
|
├─ profiles:mark-inactive runs
|
|
├─ Finds user (350 days since last login)
|
|
├─ Sets inactive_at = 2024-12-16 01:30:00
|
|
├─ Profile becomes hidden/labeled
|
|
└─ Log: "Marked INACTIVE: User (350 days)"
|
|
|
|
Day 350 (Dec 16, 2024) at 02:00:
|
|
├─ profiles:process-inactive runs
|
|
├─ User inactive for 0 days (just marked)
|
|
└─ No action (threshold not reached)
|
|
|
|
Day 357 (Dec 23, 2024) at 02:00:
|
|
├─ profiles:process-inactive runs
|
|
├─ User inactive for 7 days
|
|
├─ Threshold: warning_1 (7 days)
|
|
├─ Queues Warning 1 email
|
|
└─ Log: "warning_1 sent, 8 days remaining"
|
|
|
|
Email Content (Warning 1):
|
|
├─ Subject: "Warning: Your profile will be deleted soon"
|
|
├─ Header: "Inactive profile warning"
|
|
├─ Message: "Your profile has been inactive for 357 days..."
|
|
├─ Shows: Account balances, time remaining
|
|
└─ Button: "Activate profile"
|
|
|
|
Day 360 (Dec 26, 2024) at 02:00:
|
|
├─ profiles:process-inactive runs
|
|
├─ User inactive for 10 days
|
|
├─ Threshold: warning_2 (10 days)
|
|
├─ Queues Warning 2 email
|
|
└─ Log: "warning_2 sent, 5 days remaining"
|
|
|
|
Day 364 (Dec 30, 2024) at 02:00:
|
|
├─ profiles:process-inactive runs
|
|
├─ User inactive for 14 days
|
|
├─ Threshold: warning_final (14 days)
|
|
├─ Queues Final Warning email
|
|
└─ Log: "final sent, 1 day remaining"
|
|
|
|
Day 365 (Dec 31, 2024) at 02:00:
|
|
├─ profiles:process-inactive runs
|
|
├─ User inactive for 15 days
|
|
├─ Threshold: run_delete (15 days)
|
|
├─ Checks for negative balances → None
|
|
├─ Executes DeleteUser action (soft delete)
|
|
├─ Sets deleted_at = 2024-12-31 02:00:00
|
|
├─ Balance handling preferences stored in comment field
|
|
├─ Profile enters grace period (30 days by default)
|
|
├─ User can restore profile during grace period
|
|
├─ Deletion confirmation email sent
|
|
└─ Log: "DELETED User (365 days total)"
|
|
|
|
Day 395 (Jan 30, 2025) at 02:30:
|
|
├─ profiles:permanently-delete-expired runs
|
|
├─ Grace period expired (30 days since deletion)
|
|
├─ Executes permanent deletion
|
|
├─ Personal data anonymized
|
|
├─ Transactions anonymized
|
|
├─ Account balances handled per stored preferences
|
|
└─ Profile cannot be restored after this point
|
|
```
|
|
|
|
### User Reactivation Scenarios
|
|
|
|
#### Scenario 1: User Logs In During Warning Period
|
|
```
|
|
Day 357: Warning 1 sent
|
|
Day 358: User logs in
|
|
├─ Auto-reactivation enabled (re-activate_at_login = true)
|
|
├─ Sets inactive_at = NULL
|
|
├─ Profile becomes active again
|
|
├─ User sees "Welcome back!" message
|
|
└─ Process stops (profile no longer inactive)
|
|
```
|
|
|
|
#### Scenario 2: User Clicks "Activate Profile" Button
|
|
```
|
|
Day 359: User clicks button in warning email
|
|
├─ Redirected to login page with intended route
|
|
├─ User logs in successfully
|
|
├─ Auto-reactivation triggers
|
|
├─ Sets inactive_at = NULL
|
|
└─ Profile active, deletion prevented
|
|
```
|
|
|
|
#### Scenario 3: User Does Nothing
|
|
```
|
|
Day 357: Warning 1 sent → User ignores
|
|
Day 360: Warning 2 sent → User ignores
|
|
Day 364: Final Warning → User ignores
|
|
Day 365: Profile soft-deleted → Grace period begins (30 days)
|
|
Day 395: Grace period expires → Profile permanently deleted
|
|
```
|
|
|
|
#### Scenario 4: Profile Restoration During Grace Period
|
|
```
|
|
Day 365: Profile soft-deleted (automatic deletion after inactivity)
|
|
├─ Deletion confirmation email sent
|
|
├─ Profile enters 30-day grace period
|
|
└─ Profile can be restored by admin
|
|
|
|
Day 370: Admin restores profile (5 days after deletion)
|
|
├─ Command: php artisan profiles:restore username
|
|
├─ Sets deleted_at = NULL
|
|
├─ Clears stored balance handling preferences
|
|
├─ Profile becomes active again
|
|
└─ User can log in normally
|
|
```
|
|
|
|
---
|
|
|
|
## Scheduled Execution
|
|
|
|
### Scheduler Configuration
|
|
|
|
**Location:** `app/Console/Kernel.php` (lines 51-63)
|
|
|
|
```php
|
|
// Mark profiles as inactive when they haven't logged in for configured days
|
|
$schedule->command('profiles:mark-inactive')
|
|
->daily()
|
|
->at('01:30')
|
|
->withoutOverlapping()
|
|
->appendOutputTo(storage_path('logs/mark-inactive-profiles.log'));
|
|
|
|
// Process inactive profiles daily (send warnings and soft-delete profiles)
|
|
$schedule->command('profiles:process-inactive')
|
|
->daily()
|
|
->at('02:00')
|
|
->withoutOverlapping()
|
|
->appendOutputTo(storage_path('logs/inactive-profiles.log'));
|
|
|
|
// Permanently delete profiles after grace period expires
|
|
$schedule->command('profiles:permanently-delete-expired')
|
|
->daily()
|
|
->at('02:30')
|
|
->withoutOverlapping()
|
|
->appendOutputTo(storage_path('logs/permanent-deletions.log'));
|
|
```
|
|
|
|
### Execution Times
|
|
|
|
- **01:30**: Mark inactive profiles (Step 1)
|
|
- **02:00**: Process inactive profiles (Step 2 - soft delete)
|
|
- **02:30**: Permanently delete expired profiles (after grace period)
|
|
|
|
30-minute gap ensures profiles marked at 01:30 won't be immediately processed.
|
|
|
|
### Enabling the Scheduler
|
|
|
|
**Crontab Entry:**
|
|
```bash
|
|
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
|
|
```
|
|
|
|
**Verify Schedule:**
|
|
```bash
|
|
# List all scheduled tasks
|
|
php artisan schedule:list
|
|
|
|
# Test scheduler without waiting
|
|
php artisan schedule:test
|
|
```
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Testing Configuration
|
|
|
|
For rapid testing, use minute-based values:
|
|
|
|
```php
|
|
'profile_inactive' => [
|
|
'days_not_logged_in' => 0.000694, // ~1 minute
|
|
],
|
|
|
|
'delete_profile' => [
|
|
'days_after_inactive' => [
|
|
'warning_1' => 0.001389, // ~2 minutes after marked inactive
|
|
'warning_2' => 0.00208, // ~3 minutes after marked inactive
|
|
'warning_final' => 0.00278, // ~4 minutes after marked inactive
|
|
'run_delete' => 0.00347, // ~5 minutes after marked inactive
|
|
],
|
|
],
|
|
```
|
|
|
|
**Conversion Formula:** `days = minutes / 1440`
|
|
|
|
### Test Script: All 3 Warnings
|
|
|
|
**File:** `test-all-warnings.sh`
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
|
|
USER_ID=${1:-102}
|
|
|
|
echo "=== Testing All 3 Warning Emails for User ID: $USER_ID ==="
|
|
|
|
# Warning 1: Set inactive_at to 2 minutes ago
|
|
echo "=== Test 1: Warning 1 (inactive for 2 minutes) ==="
|
|
php artisan tinker --execute="
|
|
\$user = App\Models\User::find($USER_ID);
|
|
\$user->inactive_at = now()->subMinutes(2);
|
|
\$user->save();
|
|
exit;
|
|
"
|
|
php artisan profiles:process-inactive
|
|
sleep 2
|
|
|
|
# Warning 2: Set inactive_at to 3 minutes ago
|
|
echo "=== Test 2: Warning 2 (inactive for 3 minutes) ==="
|
|
php artisan tinker --execute="
|
|
\$user = App\Models\User::find($USER_ID);
|
|
\$user->inactive_at = now()->subMinutes(3);
|
|
\$user->save();
|
|
exit;
|
|
"
|
|
php artisan profiles:process-inactive
|
|
sleep 2
|
|
|
|
# Warning Final: Set inactive_at to 4 minutes ago
|
|
echo "=== Test 3: Warning Final (inactive for 4 minutes) ==="
|
|
php artisan tinker --execute="
|
|
\$user = App\Models\User::find($USER_ID);
|
|
\$user->inactive_at = now()->subMinutes(4);
|
|
\$user->save();
|
|
exit;
|
|
"
|
|
php artisan profiles:process-inactive
|
|
|
|
echo "=== All 3 warning emails sent! ==="
|
|
php artisan queue:work --stop-when-empty --timeout=30
|
|
```
|
|
|
|
**Usage:**
|
|
```bash
|
|
chmod +x test-all-warnings.sh
|
|
./test-all-warnings.sh 102
|
|
```
|
|
|
|
### Manual Testing Steps
|
|
|
|
#### Test 1: Mark Inactive
|
|
```bash
|
|
# Setup test user
|
|
php artisan tinker --execute="
|
|
\$user = App\Models\User::find(102);
|
|
\$user->last_login_at = now()->subMinutes(2);
|
|
\$user->inactive_at = null;
|
|
\$user->save();
|
|
echo 'User setup: last login 2 minutes ago';
|
|
exit;
|
|
"
|
|
|
|
# Run mark-inactive command
|
|
php artisan profiles:mark-inactive
|
|
|
|
# Verify user is now inactive
|
|
php artisan tinker --execute="
|
|
\$user = App\Models\User::find(102);
|
|
echo 'inactive_at: ' . \$user->inactive_at . PHP_EOL;
|
|
echo 'Is active: ' . (\$user->isActive() ? 'Yes' : 'No');
|
|
exit;
|
|
"
|
|
```
|
|
|
|
#### Test 2: Warning Emails
|
|
```bash
|
|
# Setup user for Warning 1
|
|
php artisan tinker --execute="
|
|
\$user = App\Models\User::find(102);
|
|
\$user->inactive_at = now()->subMinutes(2);
|
|
\$user->save();
|
|
exit;
|
|
"
|
|
|
|
# Process inactive profiles
|
|
php artisan profiles:process-inactive
|
|
|
|
# Process email queue
|
|
php artisan queue:work --stop-when-empty
|
|
|
|
# Check Mailpit for email
|
|
```
|
|
|
|
#### Test 3: Deletion
|
|
```bash
|
|
# Setup user for deletion
|
|
php artisan tinker --execute="
|
|
\$user = App\Models\User::find(102);
|
|
\$user->inactive_at = now()->subMinutes(6);
|
|
\$user->save();
|
|
exit;
|
|
"
|
|
|
|
# Process inactive profiles (will delete)
|
|
php artisan profiles:process-inactive
|
|
|
|
# Verify deletion
|
|
php artisan tinker --execute="
|
|
\$user = App\Models\User::withTrashed()->find(102);
|
|
echo 'deleted_at: ' . \$user->deleted_at . PHP_EOL;
|
|
exit;
|
|
"
|
|
```
|
|
|
|
### Verification Checklist
|
|
|
|
- [ ] Command logs to correct file
|
|
- [ ] Profiles marked inactive at right threshold
|
|
- [ ] Warning 1 email received
|
|
- [ ] Warning 2 email received
|
|
- [ ] Final warning email received
|
|
- [ ] Profile deleted at correct threshold
|
|
- [ ] Profile with negative balance skipped
|
|
- [ ] Already deleted profiles not reprocessed
|
|
- [ ] Email queue processed successfully
|
|
- [ ] Transactions anonymized after deletion
|
|
|
|
---
|
|
|
|
## Production Configuration
|
|
|
|
### Recommended Production Settings
|
|
|
|
```php
|
|
'profile_inactive' => [
|
|
'days_not_logged_in' => 350, // ~11.5 months
|
|
're-activate_at_login' => true,
|
|
'messenger_hidden' => true,
|
|
'profile_search_hidden' => true,
|
|
'profile_hidden' => true,
|
|
'profile_labeled' => true,
|
|
],
|
|
|
|
'delete_profile' => [
|
|
'days_after_inactive' => [
|
|
'warning_1' => 7, // 357 days total (~11.75 months)
|
|
'warning_2' => 10, // 360 days total (~11.83 months)
|
|
'warning_final' => 14, // 364 days total (~12 months)
|
|
'run_delete' => 15, // 365 days total (1 year)
|
|
],
|
|
],
|
|
```
|
|
|
|
**Total Timeline:** 1 year from last login to deletion
|
|
|
|
### Alternative: Strict 3-Year Retention
|
|
|
|
```php
|
|
'profile_inactive' => [
|
|
'days_not_logged_in' => 1080, // ~3 years
|
|
],
|
|
|
|
'delete_profile' => [
|
|
'days_after_inactive' => [
|
|
'warning_1' => 7, // 1087 days total
|
|
'warning_2' => 10, // 1090 days total
|
|
'warning_final' => 14, // 1094 days total
|
|
'run_delete' => 15, // 1095 days total (3 years)
|
|
],
|
|
],
|
|
```
|
|
|
|
### Balance Handling Options
|
|
|
|
**Option 1: Donate to Organization (Recommended)**
|
|
```php
|
|
'account_balances' => [
|
|
'donate_balances_to_organization_account_specified' => true,
|
|
'transfer_balances_to_bank_client' => false,
|
|
'transfer_balances_to_account_id' => null,
|
|
'transfer_balances_to_debit_account' => true,
|
|
],
|
|
```
|
|
Users can select an organization to receive their balance during deletion flow.
|
|
|
|
**Option 2: Remove Currency (Debit Account)**
|
|
```php
|
|
'account_balances' => [
|
|
'donate_balances_to_organization_account_specified' => false,
|
|
'transfer_balances_to_bank_client' => false,
|
|
'transfer_balances_to_account_id' => null,
|
|
'transfer_balances_to_debit_account' => true, // Removes from circulation
|
|
],
|
|
```
|
|
Balance transferred to debit account (removes dead currency).
|
|
|
|
**Option 3: Transfer to Specific Account**
|
|
```php
|
|
'account_balances' => [
|
|
'donate_balances_to_organization_account_specified' => false,
|
|
'transfer_balances_to_bank_client' => false,
|
|
'transfer_balances_to_account_id' => 123, // Specific account ID
|
|
'transfer_balances_to_debit_account' => false,
|
|
],
|
|
```
|
|
All balances go to a designated account (e.g., community fund).
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: Commands Not Running Automatically
|
|
|
|
**Symptoms:**
|
|
- Profiles not being marked inactive
|
|
- No warning emails sent
|
|
- Logs not updating
|
|
|
|
**Diagnosis:**
|
|
```bash
|
|
# Check if scheduler is configured
|
|
crontab -l | grep artisan
|
|
|
|
# Verify schedule:run works
|
|
php artisan schedule:run
|
|
|
|
# List scheduled tasks
|
|
php artisan schedule:list
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Add to crontab
|
|
crontab -e
|
|
|
|
# Add this line:
|
|
* * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1
|
|
|
|
# Verify
|
|
php artisan schedule:test
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Emails Not Sending
|
|
|
|
**Symptoms:**
|
|
- Log shows "warning sent" but no email received
|
|
- Queue contains jobs but they're not processing
|
|
|
|
**Diagnosis:**
|
|
```bash
|
|
# Check queue status
|
|
php artisan queue:failed
|
|
|
|
# Check mail configuration
|
|
php artisan tinker --execute="dd(config('mail'));"
|
|
|
|
# Verify queue worker is running
|
|
ps aux | grep "queue:work"
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Start queue worker
|
|
php artisan queue:work
|
|
|
|
# Or use supervisor for persistent queue worker
|
|
# See: Laravel Queue documentation
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Already Deleted Profiles Being Processed
|
|
|
|
**Symptoms:**
|
|
- Log shows deletions for profiles that were already deleted
|
|
- "DELETED" entries for same profile multiple times
|
|
|
|
**Fixed In:** Version with `whereNull('deleted_at')` filter (app/Console/Commands/ProcessInactiveProfiles.php:50, 61)
|
|
|
|
**Verification:**
|
|
```bash
|
|
# Check ProcessInactiveProfiles.php contains:
|
|
grep "whereNull('deleted_at')" app/Console/Commands/ProcessInactiveProfiles.php
|
|
|
|
# Should see:
|
|
# ->whereNull('deleted_at') // Exclude already deleted profiles
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Profiles with Negative Balances Not Skipped
|
|
|
|
**Symptoms:**
|
|
- Error when trying to delete profile with negative balance
|
|
- Deletion process crashes
|
|
|
|
**Expected Behavior:**
|
|
- Profiles with negative balances should be skipped
|
|
- Log entry: "SKIPPED deletion - Has negative balance"
|
|
|
|
**Verification:**
|
|
```bash
|
|
# Check logs for skip messages
|
|
grep "SKIPPED" storage/logs/inactive-profiles.log
|
|
|
|
# Manually test
|
|
php artisan tinker --execute="
|
|
\$user = App\Models\User::find(102);
|
|
\$accounts = \$user->accounts()->active()->notRemoved()->get();
|
|
foreach (\$accounts as \$account) {
|
|
echo \$account->name . ': ' . \$account->balance . PHP_EOL;
|
|
}
|
|
exit;
|
|
"
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Wrong Warning Level Sent
|
|
|
|
**Symptoms:**
|
|
- User receives Warning 2 but should get Warning 1
|
|
- Only one warning email instead of three
|
|
|
|
**Understanding:**
|
|
The system sends ONE warning per day based on CURRENT inactive duration:
|
|
- If user is 7 days inactive → Warning 1
|
|
- If user is 10 days inactive → Warning 2
|
|
- If user is 14 days inactive → Final Warning
|
|
|
|
This is by design. Users naturally progress through warnings as time passes.
|
|
|
|
**Not a Bug:** Each profile gets the appropriate warning for their current state, not all warnings at once.
|
|
|
|
---
|
|
|
|
### Issue: Configuration Not Taking Effect
|
|
|
|
**Symptoms:**
|
|
- Changed config values but behavior unchanged
|
|
- Still using old thresholds
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Clear config cache
|
|
php artisan config:clear
|
|
|
|
# Rebuild cache
|
|
php artisan config:cache
|
|
|
|
# Verify config loaded
|
|
php artisan tinker --execute="
|
|
echo 'days_not_logged_in: ' . timebank_config('profile_inactive.days_not_logged_in') . PHP_EOL;
|
|
echo 'warning_1: ' . timebank_config('delete_profile.days_after_inactive.warning_1') . PHP_EOL;
|
|
exit;
|
|
"
|
|
```
|
|
|
|
---
|
|
|
|
## Related Systems
|
|
|
|
### 1. Profile Inactive System
|
|
|
|
**Documentation:** [PROFILE_INACTIVE_CONFIG.md](PROFILE_INACTIVE_CONFIG.md)
|
|
|
|
The auto-delete system builds on the profile inactive system:
|
|
- Uses same `inactive_at` field
|
|
- Respects same visibility/hiding rules
|
|
- Triggered by same `isActive()` method
|
|
|
|
**Integration:**
|
|
- `MarkInactiveProfiles` sets `inactive_at`
|
|
- Profile becomes hidden (per inactive config)
|
|
- `ProcessInactiveProfiles` processes these inactive profiles
|
|
- Progressive warnings sent before deletion
|
|
|
|
### 2. User Deletion Action
|
|
|
|
**Location:** `app/Actions/Jetstream/DeleteUser.php`
|
|
|
|
The auto-delete system uses the existing `DeleteUser` action:
|
|
```php
|
|
$deleteUser = new DeleteUser();
|
|
$deleteUser->delete($profile, 'delete', null);
|
|
```
|
|
|
|
**What It Does:**
|
|
- Anonymizes profile data
|
|
- Preserves transactions (anonymized)
|
|
- Handles account balances (per config)
|
|
- Soft deletes profile
|
|
- **Note:** Does NOT send deletion email (handled by ProcessInactiveProfiles command)
|
|
|
|
### 3. Email System
|
|
|
|
#### Warning Emails
|
|
|
|
**Mailable Classes:**
|
|
- `app/Mail/InactiveProfileWarning1Mail.php`
|
|
- `app/Mail/InactiveProfileWarning2Mail.php`
|
|
- `app/Mail/InactiveProfileWarningFinalMail.php`
|
|
|
|
**Email Templates:**
|
|
- `resources/views/emails/inactive-profiles/{locale}/warning-1.blade.php`
|
|
- `resources/views/emails/inactive-profiles/{locale}/warning-2.blade.php`
|
|
- `resources/views/emails/inactive-profiles/{locale}/warning-final.blade.php`
|
|
|
|
**Languages Supported:** en, nl, de, es, fr
|
|
|
|
#### Deletion Confirmation Email
|
|
|
|
**Mailable Class:**
|
|
- `app/Mail/UserDeletedMail.php`
|
|
|
|
**Email Template:**
|
|
- `resources/views/emails/administration/{locale}/user-deleted.blade.php`
|
|
|
|
**Sent by:** `ProcessInactiveProfiles` command (app/Console/Commands/ProcessInactiveProfiles.php:194)
|
|
|
|
**When:** Immediately after successful profile deletion
|
|
|
|
**Content:**
|
|
- Account details (username, full name, email)
|
|
- Deletion timestamp
|
|
- Balance handling information (total balance, donation details if applicable)
|
|
- Confirmation that data has been permanently deleted and anonymized
|
|
- Note that action is irreversible
|
|
|
|
**Variables:**
|
|
```php
|
|
[
|
|
'time' => '18 November 2025, 22:26',
|
|
'deletedUser' => (object)[
|
|
'name' => 'ProfileName',
|
|
'full_name' => 'Full Profile Name',
|
|
'lang_preference' => 'en',
|
|
],
|
|
'mail' => 'user@example.com',
|
|
'balanceHandlingOption' => 'delete',
|
|
'totalBalance' => 120, // in minutes
|
|
'donationAccountId' => null,
|
|
'donationAccountName' => null,
|
|
'donationOrganizationName' => null,
|
|
]
|
|
```
|
|
|
|
**Languages Supported:** en, nl, de, es, fr
|
|
|
|
### 4. Queue System
|
|
|
|
All emails (warnings and deletion confirmations) are queued for asynchronous delivery:
|
|
- Uses Laravel queue system
|
|
- Requires queue worker running
|
|
- Prevents command timeout for large batches
|
|
- Emails are sent in the user's preferred language (`lang_preference`)
|
|
|
|
**Queue Configuration:** `config/queue.php`
|
|
|
|
**Email Types Queued:**
|
|
1. Warning emails (InactiveProfileWarning1Mail, InactiveProfileWarning2Mail, InactiveProfileWarningFinalMail)
|
|
2. Deletion confirmation emails (UserDeletedMail)
|
|
|
|
### 5. Profile Deletion Grace Period
|
|
|
|
**Documentation:** [PROFILE_DELETION_GRACE_PERIOD.md](PROFILE_DELETION_GRACE_PERIOD.md)
|
|
|
|
When profiles are deleted (either automatically or manually), they enter a grace period before permanent deletion:
|
|
|
|
**How It Works:**
|
|
- Profile is soft-deleted (`deleted_at` timestamp set)
|
|
- Balance handling preferences stored in `comment` field as JSON
|
|
- Profile remains in grace period for configurable days (default: 30)
|
|
- Profile can be restored during grace period using `php artisan profiles:restore`
|
|
- After grace period expires, `profiles:permanently-delete-expired` command:
|
|
- Anonymizes profile data permanently
|
|
- Handles account balances per stored preferences
|
|
- Makes restoration impossible
|
|
|
|
**Configuration:**
|
|
```php
|
|
'delete_profile' => [
|
|
'grace_period_days' => 30, // Days before permanent deletion
|
|
],
|
|
```
|
|
|
|
**Restoration Command:**
|
|
```bash
|
|
# List all restorable profiles
|
|
php artisan profiles:restore --list
|
|
|
|
# Restore specific profile
|
|
php artisan profiles:restore username
|
|
```
|
|
|
|
**Integration with Auto-Delete:**
|
|
- Auto-deleted profiles enter grace period automatically
|
|
- Deletion confirmation email mentions grace period
|
|
- Admin can restore accidentally deleted profiles
|
|
- Permanent deletion happens 30 days after initial deletion
|
|
|
|
---
|
|
|
|
## Monitoring
|
|
|
|
### Log Files
|
|
|
|
**Mark Inactive Log:** `storage/logs/mark-inactive-profiles.log`
|
|
```bash
|
|
# View recent marks
|
|
tail -n 50 storage/logs/mark-inactive-profiles.log
|
|
|
|
# Count marked today
|
|
grep "$(date +%Y-%m-%d)" storage/logs/mark-inactive-profiles.log | wc -l
|
|
```
|
|
|
|
**Process Inactive Log:** `storage/logs/inactive-profiles.log`
|
|
```bash
|
|
# View recent activity
|
|
tail -n 100 storage/logs/inactive-profiles.log
|
|
|
|
# Count deletions today
|
|
grep "DELETED" storage/logs/inactive-profiles.log | grep "$(date +%Y-%m-%d)" | wc -l
|
|
|
|
# Count warnings sent
|
|
grep "warning_1 sent" storage/logs/inactive-profiles.log | grep "$(date +%Y-%m-%d)" | wc -l
|
|
```
|
|
|
|
### Metrics to Track
|
|
|
|
**Daily:**
|
|
- Number of profiles marked inactive
|
|
- Number of warnings sent (by level)
|
|
- Number of profiles deleted
|
|
- Number of profiles skipped (negative balance)
|
|
|
|
**Weekly:**
|
|
- Total inactive profiles
|
|
- Inactive profiles by stage (warning 1, 2, final, pending deletion)
|
|
- Average time to deletion
|
|
- Reactivation rate (users who logged back in)
|
|
|
|
**Monthly:**
|
|
- Data retention compliance
|
|
- Account balance disposal (by method)
|
|
- User notification effectiveness
|
|
- Error rates
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Communication Strategy
|
|
|
|
**Before Implementation:**
|
|
- Announce new auto-delete policy to all users
|
|
- Update Terms of Service
|
|
- Add FAQ explaining the process
|
|
- Give 60-day grace period before first deletions
|
|
|
|
**During Operation:**
|
|
- Clear warning emails with specific dates
|
|
- Multiple warnings with escalating urgency
|
|
- Easy reactivation process (just login)
|
|
- Contact information for questions
|
|
|
|
### 2. Data Protection
|
|
|
|
**GDPR Compliance:**
|
|
- Auto-delete supports "right to be forgotten"
|
|
- Users can delete account anytime (no waiting)
|
|
- Clear retention policy stated upfront
|
|
- Audit trail via log files
|
|
|
|
**Balance Protection:**
|
|
- Never delete profiles with negative balances
|
|
- Allow balance donation before deletion
|
|
- Clear communication about balance disposal
|
|
- Option to withdraw balance before inactivity
|
|
|
|
### 3. Testing Strategy
|
|
|
|
**Before Production:**
|
|
- [ ] Test full cycle with minute-based config
|
|
- [ ] Verify all 3 warning emails send correctly
|
|
- [ ] Test deletion with various balance states
|
|
- [ ] Confirm transactions anonymize properly
|
|
- [ ] Verify scheduler runs correctly
|
|
- [ ] Test queue worker processes emails
|
|
- [ ] Check logs for errors
|
|
- [ ] Test reactivation flow
|
|
|
|
**In Production:**
|
|
- [ ] Monitor logs daily for first month
|
|
- [ ] Review deletion counts weekly
|
|
- [ ] Track reactivation rates
|
|
- [ ] Collect user feedback on warnings
|
|
- [ ] Adjust timing if needed
|
|
|
|
### 4. Gradual Rollout
|
|
|
|
**Phase 1: Warnings Only (Month 1)**
|
|
```php
|
|
'days_after_inactive' => [
|
|
'warning_1' => 7,
|
|
'warning_2' => 10,
|
|
'warning_final' => 14,
|
|
'run_delete' => 99999, // Never delete (testing)
|
|
],
|
|
```
|
|
Send warnings but don't delete. Monitor feedback.
|
|
|
|
**Phase 2: Long Timeline (Month 2-3)**
|
|
```php
|
|
'days_after_inactive' => [
|
|
'warning_1' => 30,
|
|
'warning_2' => 45,
|
|
'warning_final' => 60,
|
|
'run_delete' => 90, // 3 months from marked inactive
|
|
],
|
|
```
|
|
Very generous timeline while users adjust.
|
|
|
|
**Phase 3: Normal Timeline (Month 4+)**
|
|
```php
|
|
'days_after_inactive' => [
|
|
'warning_1' => 7,
|
|
'warning_2' => 10,
|
|
'warning_final' => 14,
|
|
'run_delete' => 15,
|
|
],
|
|
```
|
|
Standard production timeline.
|
|
|
|
---
|
|
|
|
## See Also
|
|
|
|
- [PROFILE_DELETION_GRACE_PERIOD.md](PROFILE_DELETION_GRACE_PERIOD.md) - Grace period and profile restoration
|
|
- [PROFILE_INACTIVE_CONFIG.md](PROFILE_INACTIVE_CONFIG.md) - Profile inactive system documentation
|
|
- [EMAIL-TESTING-GUIDE.md](../EMAIL-TESTING-GUIDE.md) - Email testing procedures
|
|
- `app/Console/Commands/MarkInactiveProfiles.php` - Mark inactive command
|
|
- `app/Console/Commands/ProcessInactiveProfiles.php` - Process inactive command
|
|
- `app/Console/Commands/PermanentlyDeleteExpiredProfiles.php` - Permanent deletion command
|
|
- `app/Console/Commands/RestoreDeletedProfile.php` - Profile restoration command
|
|
- `app/Actions/Jetstream/DeleteUser.php` - User deletion action
|
|
- `app/Actions/Jetstream/RestoreProfile.php` - Profile restoration action
|
|
- `app/Mail/UserDeletedMail.php` - Deletion confirmation email mailable
|
|
- `app/Http/Livewire/Profile/DeleteUserForm.php` - Manual deletion form (uses same email)
|
|
- `resources/views/emails/administration/{locale}/user-deleted.blade.php` - Deletion email template
|
|
- `config/timebank-default.php` - Full configuration reference
|
|
|
|
---
|
|
|
|
## Changelog
|
|
|
|
### 2025-12-19 - Grace Period Implementation
|
|
- Added 30-day grace period for deleted profiles
|
|
- Profiles now soft-deleted first, permanently deleted after grace period
|
|
- Created `profiles:permanently-delete-expired` scheduled command
|
|
- Created `profiles:restore` command for profile restoration
|
|
- Balance handling deferred until permanent deletion
|
|
- Updated deletion emails to mention grace period
|
|
- Added comprehensive grace period documentation
|
|
|
|
### 2025-11-18 - Deletion Confirmation Emails
|
|
- Added automatic deletion confirmation emails
|
|
- Integrated `UserDeletedMail` into `ProcessInactiveProfiles` command
|
|
- Email sent immediately after successful profile deletion
|
|
- Updated log messages to include "Confirmation email sent to {email}"
|
|
- All deletion emails queued for asynchronous delivery
|
|
|
|
### 2025-11-14 - Initial Implementation
|
|
- Created two-step auto-delete system
|
|
- Implemented `MarkInactiveProfiles` command
|
|
- Refactored `ProcessInactiveProfiles` to use `inactive_at`
|
|
- Added scheduler configuration
|
|
- Created comprehensive documentation
|
|
- Fixed soft-delete exclusion bug
|
|
- Added production and testing configurations
|
|
|
|
---
|
|
|
|
**Last Updated:** 2025-12-19
|
|
**Maintainer:** Development Team
|
|
**Status:** PRODUCTION READY
|
|
**Commands:** `profiles:mark-inactive`, `profiles:process-inactive`, `profiles:permanently-delete-expired`, `profiles:restore`
|