coolify/app/Console/Commands/Cloud/ExportUsers.php
Andras Bacsai 2719d66042 feat: add ClickHouse backups and cloud ops tools
Enable scheduled ClickHouse backups across the job, API, and UI, and
guard unsupported database types via isBackupSolutionAvailable().
Convert Stripe subscription sync from a job to an action with clearer
discrepancy resolution, and add cloud:export-users plus
cloud:cleanup-unverified-users with tests.
2026-07-16 11:57:52 +02:00

127 lines
3.9 KiB
PHP

<?php
namespace App\Console\Commands\Cloud;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use RuntimeException;
use Throwable;
class ExportUsers extends Command
{
protected $signature = 'cloud:export-users';
protected $description = 'Export subscribed and unsubscribed verified Coolify Cloud users to separate CSV files';
public function handle(): int
{
if (! isCloud()) {
$this->error('This command can only be run on Coolify Cloud.');
return self::FAILURE;
}
$backups = Storage::disk('backups');
$backups->delete('cloud-users.csv');
$subscribedPath = $backups->path('cloud-users-subscribed.csv');
$unsubscribedPath = $backups->path('cloud-users-unsubscribed.csv');
$subscribedOutput = fopen($subscribedPath, 'wb');
if ($subscribedOutput === false) {
$this->error("Unable to open {$subscribedPath} for writing.");
return self::FAILURE;
}
$unsubscribedOutput = fopen($unsubscribedPath, 'wb');
if ($unsubscribedOutput === false) {
fclose($subscribedOutput);
$this->error("Unable to open {$unsubscribedPath} for writing.");
return self::FAILURE;
}
$subscribedCount = 0;
$unsubscribedCount = 0;
try {
$header = [
'email',
'first_name',
'last_name',
'lifetime_value_currency',
'lifetime_value_amount',
'utm_campaign',
'utm_source',
'utm_medium',
'utm_content',
'utm_term',
'phone',
];
$this->writeCsvRow($subscribedOutput, $header);
$this->writeCsvRow($unsubscribedOutput, $header);
foreach (User::query()
->select(['id', 'email', 'name'])
->where('id', '!=', 0)
->whereNotNull('email_verified_at')
->withExists([
'teams as is_subscribed' => fn ($query) => $query
->whereRelation('subscription', 'stripe_invoice_paid', true),
])
->lazyById(500) as $user) {
$nameParts = preg_split('/\s+/u', trim((string) $user->name), 2) ?: [];
[$firstName, $lastName] = array_pad($nameParts, 2, '');
$row = [
$user->email,
$firstName,
$lastName,
'',
'',
'',
'',
'',
'',
'',
'',
];
if ($user->is_subscribed) {
$this->writeCsvRow($subscribedOutput, $row);
$subscribedCount++;
} else {
$this->writeCsvRow($unsubscribedOutput, $row);
$unsubscribedCount++;
}
}
} catch (Throwable $exception) {
$this->error("Unable to export users: {$exception->getMessage()}");
return self::FAILURE;
} finally {
fclose($subscribedOutput);
fclose($unsubscribedOutput);
}
$this->info("Exported {$subscribedCount} subscribed verified users to {$subscribedPath}");
$this->info("Exported {$unsubscribedCount} unsubscribed verified users to {$unsubscribedPath}");
return self::SUCCESS;
}
/**
* @param resource $output
* @param array<int, mixed> $fields
*/
private function writeCsvRow($output, array $fields): void
{
if (fputcsv($output, $fields, ',', '"', '') === false) {
throw new RuntimeException('Unable to write the CSV file.');
}
}
}