description = 'Anonymize IP addresses older than ' . timebank_config('ip_retention.retention_days') . ' days for GDPR compliance'; } public function handle() { $retentionDays = timebank_config('ip_retention.retention_days', 180); $cutoffDate = now()->subDays($retentionDays); $isDryRun = $this->option('dry-run'); $this->info('Starting IP address cleanup...'); $this->info('Retention period: ' . $retentionDays . ' days'); $this->info('Cutoff date: ' . $cutoffDate->toDateTimeString()); if ($isDryRun) { $this->warn('DRY RUN MODE - No changes will be made'); } $totalAnonymized = 0; // Cleanup profile tables (users, organizations, banks, admins) $profileModels = [ 'users' => User::class, 'organizations' => Organization::class, 'banks' => Bank::class, 'admins' => Admin::class, ]; foreach ($profileModels as $tableName => $modelClass) { $count = $this->cleanupProfileTable($tableName, $modelClass, $cutoffDate, $isDryRun); $totalAnonymized += $count; } // Cleanup activity log IP addresses $activityLogCount = $this->cleanupActivityLog($cutoffDate, $isDryRun); $totalAnonymized += $activityLogCount; $action = $isDryRun ? 'Would anonymize' : 'Anonymized'; $this->info("✓ {$action} {$totalAnonymized} IP address records in total."); // Log the cleanup action if (!$isDryRun) { Log::info('IP address cleanup completed', [ 'retention_days' => $retentionDays, 'cutoff_date' => $cutoffDate->toDateTimeString(), 'total_anonymized' => $totalAnonymized, ]); } return 0; } /** * Cleanup IP addresses from profile tables * * @param string $tableName * @param string $modelClass * @param \Carbon\Carbon $cutoffDate * @param bool $isDryRun * @return int Number of records anonymized */ protected function cleanupProfileTable(string $tableName, string $modelClass, $cutoffDate, bool $isDryRun): int { $this->line("\nProcessing {$tableName} table..."); // Find profiles with last_login_ip that should be anonymized $query = $modelClass::whereNotNull('last_login_ip') ->where('last_login_ip', '!=', '') ->where(function ($q) use ($cutoffDate) { // Anonymize if last_login_at is older than cutoff date $q->where('last_login_at', '<', $cutoffDate) // Or if last_login_at is null (should not happen, but handle it) ->orWhereNull('last_login_at'); }); $count = $query->count(); if ($count === 0) { $this->line(" No IP addresses to anonymize in {$tableName}"); return 0; } if ($isDryRun) { $this->warn(" Would anonymize {$count} IP addresses in {$tableName}"); // Show some examples in dry run $examples = $query->take(3)->get(['id', 'name', 'last_login_ip', 'last_login_at']); if ($examples->isNotEmpty()) { $this->line(" Examples:"); foreach ($examples as $example) { $loginDate = 'never'; if ($example->last_login_at) { $loginDate = is_string($example->last_login_at) ? $example->last_login_at : $example->last_login_at->toDateString(); } $this->line(" - ID {$example->id} ({$example->name}): {$example->last_login_ip} (last login: {$loginDate})"); } } } else { // Anonymize by setting to null $updated = $query->update(['last_login_ip' => null]); $this->info(" ✓ Anonymized {$updated} IP addresses in {$tableName}"); } return $count; } /** * Cleanup IP addresses from activity log * * @param \Carbon\Carbon $cutoffDate * @param bool $isDryRun * @return int Number of records anonymized */ protected function cleanupActivityLog($cutoffDate, bool $isDryRun): int { $this->line("\nProcessing activity_log table..."); // Find activity logs with IP addresses older than cutoff date $query = Activity::whereNotNull('properties->ip') ->where('created_at', '<', $cutoffDate); $count = $query->count(); if ($count === 0) { $this->line(" No IP addresses to anonymize in activity_log"); return 0; } if ($isDryRun) { $this->warn(" Would anonymize {$count} IP addresses in activity_log"); // Show some examples in dry run $examples = $query->take(3)->get(['id', 'log_name', 'properties', 'created_at']); if ($examples->isNotEmpty()) { $this->line(" Examples:"); foreach ($examples as $example) { $ip = $example->properties['ip'] ?? 'N/A'; $this->line(" - ID {$example->id} ({$example->log_name}): {$ip} (date: {$example->created_at->toDateString()})"); } } } else { // Anonymize by removing IP from properties JSON $activities = $query->get(); $updated = 0; foreach ($activities as $activity) { $properties = $activity->properties; if (isset($properties['ip'])) { unset($properties['ip']); $activity->properties = $properties; $activity->save(); $updated++; } } $this->info(" ✓ Anonymized {$updated} IP addresses in activity_log"); } return $count; } }