- Add PaymentProviderSeeder with initial provider data (Stripe, Lemon Squeezy, Polar, OxaPay, Crypto, Activation Key) - Create migration to disable JSON constraints and change configuration column from JSON to TEXT - Update PaymentProvider model cast from 'array' to 'encrypted:array' for secure configuration storage
463 lines
16 KiB
PHP
463 lines
16 KiB
PHP
<?php
|
|
|
|
namespace Database\Seeders;
|
|
|
|
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
|
use Illuminate\Database\Seeder;
|
|
|
|
class DatabaseSeeder extends Seeder
|
|
{
|
|
/**
|
|
* Available seeders with descriptions.
|
|
*/
|
|
private array $availableSeeders = [];
|
|
|
|
public function __construct()
|
|
{
|
|
$this->discoverSeeders();
|
|
}
|
|
|
|
/**
|
|
* Automatically discover all seeders in the database/seeders directory.
|
|
*/
|
|
private function discoverSeeders(): void
|
|
{
|
|
$seederPath = database_path('seeders');
|
|
$seederFiles = glob($seederPath.'/*Seeder.php');
|
|
|
|
foreach ($seederFiles as $file) {
|
|
$className = basename($file, '.php');
|
|
$fullClassName = "Database\\Seeders\\{$className}";
|
|
|
|
// Skip DatabaseSeeder itself to avoid recursion
|
|
if ($className === 'DatabaseSeeder') {
|
|
continue;
|
|
}
|
|
|
|
// Check if class exists and is instantiable
|
|
if (class_exists($fullClassName) && is_subclass_of($fullClassName, Seeder::class)) {
|
|
$this->availableSeeders[$className] = [
|
|
'class' => $fullClassName,
|
|
'description' => $this->generateSeederDescription($className),
|
|
'default' => $this->getDefaultSelection($className),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate description for seeder based on its name.
|
|
*/
|
|
private function generateSeederDescription(string $className): string
|
|
{
|
|
// Common patterns for seeder descriptions
|
|
$descriptions = [
|
|
'AdminSeeder' => 'Create admin user with interactive password prompt',
|
|
'MetaSeeder' => 'Seed meta tags and SEO data',
|
|
'NewSettingsSeeder' => 'Seed Filament settings (website, IMAP, configuration)',
|
|
'UserSeeder' => 'Seed sample users',
|
|
'PlanSeeder' => 'Seed subscription plans',
|
|
'CategorySeeder' => 'Seed categories',
|
|
'PostSeeder' => 'Seed blog posts',
|
|
'ProductSeeder' => 'Seed products',
|
|
'OrderSeeder' => 'Seed sample orders',
|
|
'PermissionSeeder' => 'Seed permissions and roles',
|
|
'CountrySeeder' => 'Seed countries data',
|
|
'LanguageSeeder' => 'Seed languages data',
|
|
'CurrencySeeder' => 'Seed currencies data',
|
|
'PaymentSeeder' => 'Seed payment methods and data',
|
|
'PaymentProviderSeeder' => 'Seed payment providers (Stripe, Lemon Squeezy, Polar, etc.)',
|
|
'EmailSeeder' => 'Seed email templates',
|
|
'NotificationSeeder' => 'Seed notification templates',
|
|
'SettingsSeeder' => 'Seed application settings',
|
|
'TestSeeder' => 'Seed test data',
|
|
'DemoSeeder' => 'Seed demo data',
|
|
'SampleSeeder' => 'Seed sample data',
|
|
];
|
|
|
|
if (isset($descriptions[$className])) {
|
|
return $descriptions[$className];
|
|
}
|
|
|
|
// Generate description based on class name pattern
|
|
$name = strtolower(str_replace('Seeder', '', $className));
|
|
|
|
// Simple pluralization
|
|
if (str_ends_with($name, 'y')) {
|
|
$name = substr($name, 0, -1).'ies';
|
|
} elseif (! str_ends_with($name, 's')) {
|
|
$name .= 's';
|
|
}
|
|
|
|
return "Seed {$name}";
|
|
}
|
|
|
|
/**
|
|
* Determine if a seeder should be selected by default.
|
|
*/
|
|
private function getDefaultSelection(string $className): bool
|
|
{
|
|
// Core/essential seeders that should run by default
|
|
$essentialSeeders = [
|
|
'AdminSeeder',
|
|
'NewSettingsSeeder',
|
|
'SettingsSeeder',
|
|
'MetaSeeder',
|
|
];
|
|
|
|
return in_array($className, $essentialSeeders);
|
|
}
|
|
|
|
/**
|
|
* Seed the application's database.
|
|
*/
|
|
public function run(): void
|
|
{
|
|
$this->command->info('🌱 Welcome to Interactive Database Seeder!');
|
|
$this->command->info('');
|
|
$this->command->info('Use arrow keys to navigate, spacebar to toggle selection, Enter to run:');
|
|
$this->command->info('');
|
|
|
|
$selectedSeeders = $this->runInteractiveSelection();
|
|
|
|
if (empty($selectedSeeders)) {
|
|
$this->command->warn('❌ No seeders selected. Exiting...');
|
|
|
|
return;
|
|
}
|
|
|
|
$this->command->info('');
|
|
$this->command->info('🚀 Running '.count($selectedSeeders).' selected seeders:');
|
|
|
|
// Run selected seeders
|
|
foreach ($selectedSeeders as $seederName) {
|
|
$seederInfo = $this->availableSeeders[$seederName];
|
|
$this->command->info(" 📦 Running {$seederName}...");
|
|
|
|
try {
|
|
$this->call($seederInfo['class']);
|
|
$this->command->info(" ✅ {$seederName} completed successfully");
|
|
} catch (\Exception $e) {
|
|
$this->command->error(" ❌ {$seederName} failed: {$e->getMessage()}");
|
|
}
|
|
}
|
|
|
|
$this->command->info('');
|
|
$this->command->info('🎉 Database seeding completed!');
|
|
}
|
|
|
|
/**
|
|
* Run interactive checkbox selection.
|
|
*/
|
|
private function runInteractiveSelection(): array
|
|
{
|
|
// Initialize selection with defaults
|
|
$selection = [];
|
|
foreach ($this->availableSeeders as $name => $info) {
|
|
$selection[$name] = $info['default'];
|
|
}
|
|
|
|
$seederNames = array_keys($this->availableSeeders);
|
|
$currentIndex = 0;
|
|
|
|
// Check if we're on Windows or have limited terminal capabilities
|
|
if ($this->isWindows() || ! $this->supportsInteractiveTerminal()) {
|
|
return $this->runFallbackSelection($seederNames, $selection);
|
|
}
|
|
|
|
// Set up terminal for interactive input (Unix-like systems)
|
|
if (function_exists('readline_callback_handler_install')) {
|
|
readline_callback_handler_install('', function () {});
|
|
}
|
|
|
|
// Set terminal to raw mode for character input
|
|
shell_exec('stty -icanon -echo');
|
|
|
|
try {
|
|
while (true) {
|
|
// Clear screen and redraw
|
|
$this->clearScreen();
|
|
$this->displayCheckboxInterface($seederNames, $currentIndex, $selection);
|
|
|
|
// Read single character
|
|
$read = [STDIN];
|
|
$write = [];
|
|
$except = [];
|
|
$n = stream_select($read, $write, $except, null);
|
|
|
|
if ($n && in_array(STDIN, $read)) {
|
|
$char = fgetc(STDIN);
|
|
|
|
switch ($char) {
|
|
case "\033": // Escape sequence (arrow keys)
|
|
$this->handleArrowKey($currentIndex, $seederNames, $currentIndex);
|
|
break;
|
|
|
|
case ' ': // Spacebar - toggle selection
|
|
$currentSeeder = $seederNames[$currentIndex];
|
|
$selection[$currentSeeder] = ! $selection[$currentSeeder];
|
|
break;
|
|
|
|
case "\n": // Enter - confirm selection
|
|
case "\r":
|
|
$this->restoreTerminal();
|
|
if (function_exists('readline_callback_handler_remove')) {
|
|
readline_callback_handler_remove();
|
|
}
|
|
|
|
return array_keys(array_filter($selection));
|
|
|
|
case 'a': // Select all
|
|
case 'A':
|
|
foreach ($selection as $key => $value) {
|
|
$selection[$key] = true;
|
|
}
|
|
break;
|
|
|
|
case 'n': // Select none
|
|
case 'N':
|
|
foreach ($selection as $key => $value) {
|
|
$selection[$key] = false;
|
|
}
|
|
break;
|
|
|
|
case 'q': // Quit
|
|
case 'Q':
|
|
case "\x03": // Ctrl+C
|
|
$this->restoreTerminal();
|
|
if (function_exists('readline_callback_handler_remove')) {
|
|
readline_callback_handler_remove();
|
|
}
|
|
|
|
return [];
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
$this->restoreTerminal();
|
|
if (function_exists('readline_callback_handler_remove')) {
|
|
readline_callback_handler_remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle arrow key sequences.
|
|
*/
|
|
private function handleArrowKey(int &$currentIndex, array $seederNames, int &$newIndex): void
|
|
{
|
|
// Read the next two characters of the escape sequence
|
|
$char1 = fgetc(STDIN);
|
|
$char2 = fgetc(STDIN);
|
|
|
|
if ($char1 === '[') {
|
|
switch ($char2) {
|
|
case 'A': // Up arrow
|
|
$currentIndex = max(0, $currentIndex - 1);
|
|
break;
|
|
case 'B': // Down arrow
|
|
$currentIndex = min(count($seederNames) - 1, $currentIndex + 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
$newIndex = $currentIndex;
|
|
}
|
|
|
|
/**
|
|
* Display the checkbox interface.
|
|
*/
|
|
private function displayCheckboxInterface(array $seederNames, int $currentIndex, array $selection): void
|
|
{
|
|
$this->command->info('┌─────────────────────────────────────────────────────────────┐');
|
|
$this->command->info('│ Select Seeders │');
|
|
$this->command->info('│ │');
|
|
$this->command->info('│ ↑↓ : Navigate Space : Toggle A : All N : None │');
|
|
$this->command->info('│ Enter : Run Q : Quit │');
|
|
$this->command->info('└─────────────────────────────────────────────────────────────┘');
|
|
$this->command->info('');
|
|
|
|
foreach ($seederNames as $index => $seederName) {
|
|
$info = $this->availableSeeders[$seederName];
|
|
$isSelected = $selection[$seederName];
|
|
$isCurrent = $index === $currentIndex;
|
|
|
|
$checkbox = $isSelected ? '✅' : '⭕';
|
|
$marker = $isCurrent ? '►' : ' ';
|
|
$description = $info['description'];
|
|
|
|
// Truncate description if too long
|
|
if (strlen($description) > 50) {
|
|
$description = substr($description, 0, 47).'...';
|
|
}
|
|
|
|
$line = sprintf(' %s %s %-20s %s', $marker, $checkbox, $seederName, $description);
|
|
|
|
if ($isCurrent) {
|
|
$this->command->line($line);
|
|
} else {
|
|
$this->command->info($line);
|
|
}
|
|
}
|
|
|
|
$this->command->info('');
|
|
$selectedCount = count(array_filter($selection));
|
|
$totalCount = count($seederNames);
|
|
$this->command->info("Selected: {$selectedCount}/{$totalCount} seeders");
|
|
}
|
|
|
|
/**
|
|
* Clear the terminal screen.
|
|
*/
|
|
private function clearScreen(): void
|
|
{
|
|
// ANSI escape code to clear screen and move cursor to top-left
|
|
echo "\033[2J\033[H";
|
|
}
|
|
|
|
/**
|
|
* Check if running on Windows.
|
|
*/
|
|
private function isWindows(): bool
|
|
{
|
|
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
|
|
}
|
|
|
|
/**
|
|
* Check if terminal supports interactive features.
|
|
*/
|
|
private function supportsInteractiveTerminal(): bool
|
|
{
|
|
return function_exists('shell_exec') && shell_exec('which stty') !== null;
|
|
}
|
|
|
|
/**
|
|
* Restore terminal settings.
|
|
*/
|
|
private function restoreTerminal(): void
|
|
{
|
|
if (! $this->isWindows() && $this->supportsInteractiveTerminal()) {
|
|
shell_exec('stty icanon echo');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fallback selection method for Windows or limited terminals.
|
|
*/
|
|
private function runFallbackSelection(array $seederNames, array $selection): array
|
|
{
|
|
$this->command->info('🌱 Available Seeders (Windows/Basic Mode):');
|
|
$this->command->info('');
|
|
|
|
foreach ($seederNames as $index => $seederName) {
|
|
$info = $this->availableSeeders[$seederName];
|
|
$isSelected = $selection[$seederName];
|
|
$status = $isSelected ? '✅' : '⭕';
|
|
$description = $info['description'];
|
|
|
|
if (strlen($description) > 50) {
|
|
$description = substr($description, 0, 47).'...';
|
|
}
|
|
|
|
$this->command->line(sprintf(' [%d] %s %s - %s', $index + 1, $status, $seederName, $description));
|
|
}
|
|
|
|
$this->command->info('');
|
|
$this->command->info('Options:');
|
|
$this->command->line(' • Enter numbers (e.g., "1 3 5") to toggle selection');
|
|
$this->command->line(' • Type "all" to select all');
|
|
$this->command->line(' • Type "none" to deselect all');
|
|
$this->command->line(' • Type "list" to show current selection');
|
|
$this->command->line(' • Press Enter to run selected seeders');
|
|
$this->command->line(' • Type "exit" to cancel');
|
|
$this->command->info('');
|
|
|
|
while (true) {
|
|
$input = trim($this->command->ask('Enter your choice (or press Enter to run)'));
|
|
|
|
if (empty($input)) {
|
|
break; // Run with current selection
|
|
}
|
|
|
|
$input = strtolower($input);
|
|
|
|
switch ($input) {
|
|
case 'exit':
|
|
case 'quit':
|
|
$this->command->warn('👋 Exiting seeder...');
|
|
|
|
return [];
|
|
|
|
case 'all':
|
|
foreach ($selection as $key => $value) {
|
|
$selection[$key] = true;
|
|
}
|
|
$this->displayCurrentSelection($selection);
|
|
break;
|
|
|
|
case 'none':
|
|
foreach ($selection as $key => $value) {
|
|
$selection[$key] = false;
|
|
}
|
|
$this->displayCurrentSelection($selection);
|
|
break;
|
|
|
|
case 'list':
|
|
$this->displayCurrentSelection($selection);
|
|
break;
|
|
|
|
default:
|
|
// Toggle by numbers
|
|
$numbers = preg_split('/\s+/', $input);
|
|
$toggled = false;
|
|
|
|
foreach ($numbers as $num) {
|
|
if (is_numeric($num)) {
|
|
$index = (int) $num - 1;
|
|
if (isset($seederNames[$index])) {
|
|
$seederName = $seederNames[$index];
|
|
$selection[$seederName] = ! $selection[$seederName];
|
|
$status = $selection[$seederName] ? 'selected' : 'deselected';
|
|
$this->command->line(" ✓ {$seederName} {$status}");
|
|
$toggled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! $toggled) {
|
|
$this->command->warn(" ⚠️ Invalid input: {$input}");
|
|
$this->command->line(' Try numbers (1, 2, 3) or commands: all, none, list, exit');
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return array_keys(array_filter($selection));
|
|
}
|
|
|
|
/**
|
|
* Display current selection for fallback mode.
|
|
*/
|
|
private function displayCurrentSelection(array $selection): void
|
|
{
|
|
$selected = array_keys(array_filter($selection));
|
|
if (empty($selected)) {
|
|
$this->command->line(' ⭕ No seeders selected');
|
|
} else {
|
|
$this->command->line(' ✅ Selected: '.implode(', ', $selected));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display current selection.
|
|
*/
|
|
private function displaySelection(array $selected): void
|
|
{
|
|
if (empty($selected)) {
|
|
$this->command->line(' ⭕ No seeders selected');
|
|
} else {
|
|
$this->command->line(' ✅ Currently selected: '.implode(', ', $selected));
|
|
}
|
|
}
|
|
}
|