Files
thrillwiki_laravel/app/Console/Commands/MakeThrillWikiCrud.php
pacnpal cc33781245 feat: Implement rides management with CRUD functionality
- Added rides index view with search and filter options.
- Created rides show view to display ride details.
- Implemented API routes for rides.
- Developed authentication routes for user registration, login, and email verification.
- Created tests for authentication, email verification, password reset, and user profile management.
- Added feature tests for rides and operators, including creation, updating, deletion, and searching.
- Implemented soft deletes and caching for rides and operators.
- Enhanced manufacturer and operator model tests for various functionalities.
2025-06-19 22:34:10 -04:00

1163 lines
39 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Illuminate\Filesystem\Filesystem;
class MakeThrillWikiCrud extends Command
{
/**
* The name and signature of the console command.
*/
protected $signature = 'make:thrillwiki-crud {name : The name of the CRUD entity}
{--model= : Override the model name}
{--controller= : Override the controller name}
{--migration : Generate migration file}
{--api : Generate API routes and resources}
{--with-tests : Generate test files}
{--force : Overwrite existing files}';
/**
* The console command description.
*/
protected $description = 'Generate a complete ThrillWiki CRUD with controller, model, views, routes, and tests';
/**
* Filesystem instance
*/
protected Filesystem $files;
/**
* Create a new command instance.
*/
public function __construct(Filesystem $files)
{
parent::__construct();
$this->files = $files;
}
/**
* Execute the console command.
*/
public function handle(): int
{
$name = $this->argument('name');
$this->info("🚀 Generating ThrillWiki CRUD for: {$name}");
// Prepare naming conventions
$names = $this->prepareNames($name);
try {
// Generate files
$this->generateModel($names);
$this->generateController($names);
$this->generateFormRequest($names);
$this->generateViews($names);
$this->generateRoutes($names);
if ($this->option('migration')) {
$this->generateMigration($names);
}
if ($this->option('api')) {
$this->generateApiResources($names);
}
if ($this->option('with-tests')) {
$this->generateTests($names);
}
$this->displaySuccessMessage($names);
} catch (\Exception $e) {
$this->error("❌ Error generating CRUD: " . $e->getMessage());
return self::FAILURE;
}
return self::SUCCESS;
}
/**
* Prepare all naming variations
*/
protected function prepareNames(string $name): array
{
$studly = Str::studly($name);
$snake = Str::snake($name);
$kebab = Str::kebab($name);
$plural = Str::plural($snake);
$pluralStudly = Str::studly($plural);
return [
'name' => $name,
'studly' => $studly,
'snake' => $snake,
'kebab' => $kebab,
'plural' => $plural,
'pluralStudly' => $pluralStudly,
'camel' => Str::camel($name),
'pluralCamel' => Str::camel($plural),
'title' => Str::title(str_replace('_', ' ', $snake)),
'pluralTitle' => Str::title(str_replace('_', ' ', $plural)),
'model' => $this->option('model') ?: $studly,
'controller' => $this->option('controller') ?: $studly . 'Controller',
'table' => $plural,
];
}
/**
* Generate the Eloquent model
*/
protected function generateModel(array $names): void
{
$path = app_path("Models/{$names['model']}.php");
if ($this->files->exists($path) && !$this->option('force')) {
$this->warn("⚠️ Model already exists: {$path}");
return;
}
$stub = $this->getModelStub();
$content = $this->replaceStubVariables($stub, $names);
$this->files->put($path, $content);
$this->info("✅ Model created: app/Models/{$names['model']}.php");
}
/**
* Generate the controller
*/
protected function generateController(array $names): void
{
$path = app_path("Http/Controllers/{$names['controller']}.php");
if ($this->files->exists($path) && !$this->option('force')) {
$this->warn("⚠️ Controller already exists: {$path}");
return;
}
$stub = $this->getControllerStub();
$content = $this->replaceStubVariables($stub, $names);
$this->files->put($path, $content);
$this->info("✅ Controller created: app/Http/Controllers/{$names['controller']}.php");
}
/**
* Generate form request validation
*/
protected function generateFormRequest(array $names): void
{
$requestName = $names['studly'] . 'Request';
$path = app_path("Http/Requests/{$requestName}.php");
if ($this->files->exists($path) && !$this->option('force')) {
$this->warn("⚠️ Form Request already exists: {$path}");
return;
}
if (!$this->files->isDirectory(dirname($path))) {
$this->files->makeDirectory(dirname($path), 0755, true);
}
$stub = $this->getFormRequestStub();
$content = $this->replaceStubVariables($stub, array_merge($names, ['requestName' => $requestName]));
$this->files->put($path, $content);
$this->info("✅ Form Request created: app/Http/Requests/{$requestName}.php");
}
/**
* Generate Blade views
*/
protected function generateViews(array $names): void
{
$viewPath = resource_path("views/{$names['plural']}");
if (!$this->files->isDirectory($viewPath)) {
$this->files->makeDirectory($viewPath, 0755, true);
}
$views = ['index', 'show', 'create', 'edit'];
foreach ($views as $view) {
$path = "{$viewPath}/{$view}.blade.php";
if ($this->files->exists($path) && !$this->option('force')) {
$this->warn("⚠️ View already exists: {$path}");
continue;
}
$stub = $this->getViewStub($view);
$content = $this->replaceStubVariables($stub, $names);
$this->files->put($path, $content);
$this->info("✅ View created: resources/views/{$names['plural']}/{$view}.blade.php");
}
}
/**
* Generate routes
*/
protected function generateRoutes(array $names): void
{
$webRoutesPath = base_path('routes/web.php');
$routeDefinition = $this->getRouteDefinition($names);
// Check if route already exists
$webRoutes = $this->files->get($webRoutesPath);
if (strpos($webRoutes, "Route::resource('{$names['plural']}'") !== false) {
$this->warn("⚠️ Routes may already exist for {$names['plural']}");
return;
}
// Append route to web.php
$this->files->append($webRoutesPath, "\n" . $routeDefinition);
$this->info("✅ Routes added to routes/web.php");
}
/**
* Generate migration
*/
protected function generateMigration(array $names): void
{
$migrationName = "create_{$names['table']}_table";
$timestamp = date('Y_m_d_His');
$path = database_path("migrations/{$timestamp}_{$migrationName}.php");
$stub = $this->getMigrationStub();
$content = $this->replaceStubVariables($stub, $names);
$this->files->put($path, $content);
$this->info("✅ Migration created: database/migrations/{$timestamp}_{$migrationName}.php");
}
/**
* Generate API resources
*/
protected function generateApiResources(array $names): void
{
// API Controller
$apiPath = app_path("Http/Controllers/Api/{$names['controller']}.php");
if (!$this->files->isDirectory(dirname($apiPath))) {
$this->files->makeDirectory(dirname($apiPath), 0755, true);
}
$stub = $this->getApiControllerStub();
$content = $this->replaceStubVariables($stub, $names);
$this->files->put($apiPath, $content);
$this->info("✅ API Controller created: app/Http/Controllers/Api/{$names['controller']}.php");
// API Resource
$resourceName = $names['studly'] . 'Resource';
$resourcePath = app_path("Http/Resources/{$resourceName}.php");
if (!$this->files->isDirectory(dirname($resourcePath))) {
$this->files->makeDirectory(dirname($resourcePath), 0755, true);
}
$stub = $this->getApiResourceStub();
$content = $this->replaceStubVariables($stub, array_merge($names, ['resourceName' => $resourceName]));
$this->files->put($resourcePath, $content);
$this->info("✅ API Resource created: app/Http/Resources/{$resourceName}.php");
// API Routes
$apiRoutesPath = base_path('routes/api.php');
$apiRouteDefinition = $this->getApiRouteDefinition($names);
$this->files->append($apiRoutesPath, "\n" . $apiRouteDefinition);
$this->info("✅ API Routes added to routes/api.php");
}
/**
* Generate test files
*/
protected function generateTests(array $names): void
{
$testName = $names['controller'] . 'Test';
$testPath = base_path("tests/Feature/{$testName}.php");
if ($this->files->exists($testPath) && !$this->option('force')) {
$this->warn("⚠️ Test already exists: {$testPath}");
return;
}
$stub = $this->getTestStub();
$content = $this->replaceStubVariables($stub, array_merge($names, ['testName' => $testName]));
$this->files->put($testPath, $content);
$this->info("✅ Test created: tests/Feature/{$testName}.php");
}
/**
* Get model stub content
*/
protected function getModelStub(): string
{
return '<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class {{ studly }} extends Model
{
use HasFactory, SoftDeletes;
/**
* The table associated with the model.
*/
protected $table = \'{{ table }}\';
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
\'name\',
\'description\',
\'is_active\',
];
/**
* The attributes that should be cast.
*/
protected $casts = [
\'is_active\' => \'boolean\',
\'created_at\' => \'datetime\',
\'updated_at\' => \'datetime\',
\'deleted_at\' => \'datetime\',
];
/**
* Scope for active records
*/
public function scopeActive($query)
{
return $query->where(\'is_active\', true);
}
/**
* Get cache key for this model
*/
public function getCacheKey(string $suffix = \'\'): string
{
return \'thrillwiki.{{ snake }}.\'.$this->id.($suffix ? \'.\'.$suffix : \'\');
}
}';
}
/**
* Get controller stub content
*/
protected function getControllerStub(): string
{
return '<?php
namespace App\Http\Controllers;
use App\Models\{{ studly }};
use App\Http\Requests\{{ studly }}Request;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;
class {{ controller }} extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request): View
{
$query = {{ studly }}::query();
// Search functionality
if ($request->filled(\'search\')) {
$search = $request->get(\'search\');
$query->where(function ($q) use ($search) {
$q->where(\'name\', \'ILIKE\', "%{$search}%")
->orWhere(\'description\', \'ILIKE\', "%{$search}%");
});
}
// Filter by status
if ($request->filled(\'status\')) {
$query->where(\'is_active\', $request->get(\'status\') === \'active\');
}
${{ pluralCamel }} = $query->latest()->paginate(15)->withQueryString();
return view(\'{{ plural }}.index\', compact(\'{{ pluralCamel }}\'));
}
/**
* Show the form for creating a new resource.
*/
public function create(): View
{
return view(\'{{ plural }}.create\');
}
/**
* Store a newly created resource in storage.
*/
public function store({{ studly }}Request $request): RedirectResponse
{
${{ camel }} = {{ studly }}::create($request->validated());
return redirect()
->route(\'{{ plural }}.show\', ${{ camel }})
->with(\'success\', \'{{ title }} created successfully!\');
}
/**
* Display the specified resource.
*/
public function show({{ studly }} ${{ camel }}): View
{
return view(\'{{ plural }}.show\', compact(\'{{ camel }}\'));
}
/**
* Show the form for editing the specified resource.
*/
public function edit({{ studly }} ${{ camel }}): View
{
return view(\'{{ plural }}.edit\', compact(\'{{ camel }}\'));
}
/**
* Update the specified resource in storage.
*/
public function update({{ studly }}Request $request, {{ studly }} ${{ camel }}): RedirectResponse
{
${{ camel }}->update($request->validated());
return redirect()
->route(\'{{ plural }}.show\', ${{ camel }})
->with(\'success\', \'{{ title }} updated successfully!\');
}
/**
* Remove the specified resource from storage.
*/
public function destroy({{ studly }} ${{ camel }}): RedirectResponse
{
${{ camel }}->delete();
return redirect()
->route(\'{{ plural }}.index\')
->with(\'success\', \'{{ title }} deleted successfully!\');
}
}';
}
/**
* Get form request stub content
*/
protected function getFormRequestStub(): string
{
return '<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class {{ requestName }} extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; // Add authorization logic as needed
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
$rules = [
\'name\' => [\'required\', \'string\', \'max:255\'],
\'description\' => [\'nullable\', \'string\'],
\'is_active\' => [\'boolean\'],
];
// For updates, make name unique except for current record
if ($this->route(\'{{ camel }}\')) {
$rules[\'name\'][] = \'unique:{{ table }},name,\' . $this->route(\'{{ camel }}\')->id;
} else {
$rules[\'name\'][] = \'unique:{{ table }},name\';
}
return $rules;
}
/**
* Get custom messages for validator errors.
*/
public function messages(): array
{
return [
\'name.required\' => \'The {{ snake }} name is required.\',
\'name.unique\' => \'A {{ snake }} with this name already exists.\',
];
}
}';
}
/**
* Get view stub content
*/
protected function getViewStub(string $view): string
{
$stubs = [
'index' => '@extends(\'layouts.app\')
@section(\'content\')
<div class="container mx-auto px-4 py-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">{{ pluralTitle }}</h1>
<a href="{{ route(\'{{ plural }}.create\') }}" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
Add New {{ title }}
</a>
</div>
<!-- Search and Filters -->
<form method="GET" class="mb-6">
<div class="flex gap-4">
<input type="text" name="search" value="{{ request(\'search\') }}"
placeholder="Search {{ plural }}..."
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white">
<select name="status" class="px-4 py-2 border border-gray-300 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white">
<option value="">All Status</option>
<option value="active" {{ request(\'status\') === \'active\' ? \'selected\' : \'\' }}>Active</option>
<option value="inactive" {{ request(\'status\') === \'inactive\' ? \'selected\' : \'\' }}>Inactive</option>
</select>
<button type="submit" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg">
Search
</button>
</div>
</form>
<!-- Results -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
@forelse(${{ pluralCamel }} as ${{ camel }})
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<div class="flex justify-between items-start">
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
<a href="{{ route(\'{{ plural }}.show\', ${{ camel }}) }}" class="hover:text-blue-600">
{{ ${{ camel }}->name }}
</a>
</h3>
@if(${{ camel }}->description)
<p class="text-gray-600 dark:text-gray-400 mt-2">{{ ${{ camel }}->description }}</p>
@endif
<div class="flex items-center mt-2 space-x-4">
<span class="text-sm text-gray-500">
{{ ${{ camel }}->created_at->format(\'M j, Y\') }}
</span>
<span class="px-2 py-1 text-xs rounded-full {{ ${{ camel }}->is_active ? \'bg-green-100 text-green-800\' : \'bg-red-100 text-red-800\' }}">
{{ ${{ camel }}->is_active ? \'Active\' : \'Inactive\' }}
</span>
</div>
</div>
<div class="flex space-x-2">
<a href="{{ route(\'{{ plural }}.edit\', ${{ camel }}) }}" class="text-blue-600 hover:text-blue-800">Edit</a>
<form action="{{ route(\'{{ plural }}.destroy\', ${{ camel }}) }}" method="POST" class="inline">
@csrf
@method(\'DELETE\')
<button type="submit" class="text-red-600 hover:text-red-800"
onclick="return confirm(\'Are you sure?\')">Delete</button>
</form>
</div>
</div>
</div>
@empty
<div class="p-6 text-center text-gray-500 dark:text-gray-400">
No {{ plural }} found.
</div>
@endforelse
</div>
<!-- Pagination -->
@if(${{ pluralCamel }}->hasPages())
<div class="mt-6">
{{ ${{ pluralCamel }}->links() }}
</div>
@endif
</div>
@endsection',
'show' => '@extends(\'layouts.app\')
@section(\'content\')
<div class="container mx-auto px-4 py-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">{{ ${{ camel }}->name }}</h1>
<div class="flex space-x-2">
<a href="{{ route(\'{{ plural }}.edit\', ${{ camel }}) }}" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
Edit
</a>
<a href="{{ route(\'{{ plural }}.index\') }}" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg">
Back to List
</a>
</div>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Details</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Name</label>
<p class="text-gray-900 dark:text-white">{{ ${{ camel }}->name }}</p>
</div>
@if(${{ camel }}->description)
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Description</label>
<p class="text-gray-900 dark:text-white">{{ ${{ camel }}->description }}</p>
</div>
@endif
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Status</label>
<span class="px-2 py-1 text-xs rounded-full {{ ${{ camel }}->is_active ? \'bg-green-100 text-green-800\' : \'bg-red-100 text-red-800\' }}">
{{ ${{ camel }}->is_active ? \'Active\' : \'Inactive\' }}
</span>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Metadata</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Created</label>
<p class="text-gray-900 dark:text-white">{{ ${{ camel }}->created_at->format(\'F j, Y \at g:i A\') }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Last Updated</label>
<p class="text-gray-900 dark:text-white">{{ ${{ camel }}->updated_at->format(\'F j, Y \at g:i A\') }}</p>
</div>
</div>
</div>
</div>
<div class="flex justify-end space-x-2 mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
<form action="{{ route(\'{{ plural }}.destroy\', ${{ camel }}) }}" method="POST" class="inline">
@csrf
@method(\'DELETE\')
<button type="submit" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg"
onclick="return confirm(\'Are you sure you want to delete this {{ snake }}?\')">
Delete {{ title }}
</button>
</form>
</div>
</div>
</div>
@endsection',
'create' => '@extends(\'layouts.app\')
@section(\'content\')
<div class="container mx-auto px-4 py-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Create New {{ title }}</h1>
<a href="{{ route(\'{{ plural }}.index\') }}" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg">
Back to List
</a>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<form action="{{ route(\'{{ plural }}.store\') }}" method="POST">
@csrf
<div class="space-y-6">
<div>
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Name *
</label>
<input type="text" id="name" name="name" value="{{ old(\'name\') }}" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white">
@error(\'name\')
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Description
</label>
<textarea id="description" name="description" rows="4"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white">{{ old(\'description\') }}</textarea>
@error(\'description\')
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<div>
<label class="flex items-center">
<input type="checkbox" name="is_active" value="1" {{ old(\'is_active\', true) ? \'checked\' : \'\' }}
class="rounded border-gray-300 text-blue-600 shadow-sm focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">Active</span>
</label>
@error(\'is_active\')
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
</div>
<div class="flex justify-end space-x-2 mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
<a href="{{ route(\'{{ plural }}.index\') }}" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg">
Cancel
</a>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
Create {{ title }}
</button>
</div>
</form>
</div>
</div>
@endsection',
'edit' => '@extends(\'layouts.app\')
@section(\'content\')
<div class="container mx-auto px-4 py-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Edit {{ title }}</h1>
<div class="flex space-x-2">
<a href="{{ route(\'{{ plural }}.show\', ${{ camel }}) }}" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg">
View
</a>
<a href="{{ route(\'{{ plural }}.index\') }}" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg">
Back to List
</a>
</div>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<form action="{{ route(\'{{ plural }}.update\', ${{ camel }}) }}" method="POST">
@csrf
@method(\'PUT\')
<div class="space-y-6">
<div>
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Name *
</label>
<input type="text" id="name" name="name" value="{{ old(\'name\', ${{ camel }}->name) }}" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white">
@error(\'name\')
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Description
</label>
<textarea id="description" name="description" rows="4"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white">{{ old(\'description\', ${{ camel }}->description) }}</textarea>
@error(\'description\')
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<div>
<label class="flex items-center">
<input type="checkbox" name="is_active" value="1" {{ old(\'is_active\', ${{ camel }}->is_active) ? \'checked\' : \'\' }}
class="rounded border-gray-300 text-blue-600 shadow-sm focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">Active</span>
</label>
@error(\'is_active\')
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
</div>
<div class="flex justify-end space-x-2 mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
<a href="{{ route(\'{{ plural }}.show\', ${{ camel }}) }}" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg">
Cancel
</a>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
Update {{ title }}
</button>
</div>
</form>
</div>
</div>
@endsection',
];
return $stubs[$view] ?? '';
}
/**
* Get migration stub content
*/
protected function getMigrationStub(): string
{
return '<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create(\'{{ table }}\', function (Blueprint $table) {
$table->id();
$table->string(\'name\');
$table->text(\'description\')->nullable();
$table->boolean(\'is_active\')->default(true);
$table->timestamps();
$table->softDeletes();
$table->index([\'name\']);
$table->index([\'is_active\']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists(\'{{ table }}\');
}
};';
}
/**
* Get API controller stub content
*/
protected function getApiControllerStub(): string
{
return '<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\{{ studly }};
use App\Http\Requests\{{ studly }}Request;
use App\Http\Resources\{{ studly }}Resource;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class {{ controller }} extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request): JsonResponse
{
$query = {{ studly }}::query();
// Search functionality
if ($request->filled(\'search\')) {
$search = $request->get(\'search\');
$query->where(function ($q) use ($search) {
$q->where(\'name\', \'ILIKE\', "%{$search}%")
->orWhere(\'description\', \'ILIKE\', "%{$search}%");
});
}
// Filter by status
if ($request->filled(\'status\')) {
$query->where(\'is_active\', $request->get(\'status\') === \'active\');
}
${{ pluralCamel }} = $query->latest()->paginate(15);
return response()->json([
\'data\' => {{ studly }}Resource::collection(${{ pluralCamel }}),
\'meta\' => [
\'current_page\' => ${{ pluralCamel }}->currentPage(),
\'last_page\' => ${{ pluralCamel }}->lastPage(),
\'per_page\' => ${{ pluralCamel }}->perPage(),
\'total\' => ${{ pluralCamel }}->total(),
]
]);
}
/**
* Store a newly created resource in storage.
*/
public function store({{ studly }}Request $request): JsonResponse
{
${{ camel }} = {{ studly }}::create($request->validated());
return response()->json([
\'message\' => \'{{ title }} created successfully\',
\'data\' => new {{ studly }}Resource(${{ camel }})
], 201);
}
/**
* Display the specified resource.
*/
public function show({{ studly }} ${{ camel }}): JsonResponse
{
return response()->json([
\'data\' => new {{ studly }}Resource(${{ camel }})
]);
}
/**
* Update the specified resource in storage.
*/
public function update({{ studly }}Request $request, {{ studly }} ${{ camel }}): JsonResponse
{
${{ camel }}->update($request->validated());
return response()->json([
\'message\' => \'{{ title }} updated successfully\',
\'data\' => new {{ studly }}Resource(${{ camel }})
]);
}
/**
* Remove the specified resource from storage.
*/
public function destroy({{ studly }} ${{ camel }}): JsonResponse
{
${{ camel }}->delete();
return response()->json([
\'message\' => \'{{ title }} deleted successfully\'
]);
}
}';
}
/**
* Get API resource stub content
*/
protected function getApiResourceStub(): string
{
return '<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class {{ resourceName }} extends JsonResource
{
/**
* Transform the resource into an array.
*/
public function toArray(Request $request): array
{
return [
\'id\' => $this->id,
\'name\' => $this->name,
\'description\' => $this->description,
\'is_active\' => $this->is_active,
\'created_at\' => $this->created_at,
\'updated_at\' => $this->updated_at,
];
}
}';
}
/**
* Get test stub content
*/
protected function getTestStub(): string
{
return '<?php
namespace Tests\Feature;
use App\Models\{{ studly }};
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class {{ testName }} extends TestCase
{
use RefreshDatabase;
protected User $user;
protected function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create();
}
/** @test */
public function it_can_display_{{ plural }}_index(): void
{
{{ studly }}::factory()->count(3)->create();
$response = $this->actingAs($this->user)->get(route(\'{{ plural }}.index\'));
$response->assertStatus(200)
->assertSee(\'{{ pluralTitle }}\');
}
/** @test */
public function it_can_create_a_{{ snake }}(): void
{
${{ camel }}Data = [
\'name\' => \'Test {{ title }}\',
\'description\' => \'Test description\',
\'is_active\' => true,
];
$response = $this->actingAs($this->user)->post(route(\'{{ plural }}.store\'), ${{ camel }}Data);
$response->assertRedirect();
$this->assertDatabaseHas(\'{{ table }}\', ${{ camel }}Data);
}
/** @test */
public function it_can_show_a_{{ snake }}(): void
{
${{ camel }} = {{ studly }}::factory()->create();
$response = $this->actingAs($this->user)->get(route(\'{{ plural }}.show\', ${{ camel }}));
$response->assertStatus(200)
->assertSee(${{ camel }}->name);
}
/** @test */
public function it_can_update_a_{{ snake }}(): void
{
${{ camel }} = {{ studly }}::factory()->create();
$updateData = [
\'name\' => \'Updated {{ title }}\',
\'description\' => \'Updated description\',
\'is_active\' => false,
];
$response = $this->actingAs($this->user)->put(route(\'{{ plural }}.update\', ${{ camel }}), $updateData);
$response->assertRedirect();
$this->assertDatabaseHas(\'{{ table }}\', $updateData);
}
/** @test */
public function it_can_delete_a_{{ snake }}(): void
{
${{ camel }} = {{ studly }}::factory()->create();
$response = $this->actingAs($this->user)->delete(route(\'{{ plural }}.destroy\', ${{ camel }}));
$response->assertRedirect();
$this->assertSoftDeleted(\'{{ table }}\', [\'id\' => ${{ camel }}->id]);
}
/** @test */
public function it_validates_required_fields(): void
{
$response = $this->actingAs($this->user)->post(route(\'{{ plural }}.store\'), []);
$response->assertSessionHasErrors([\'name\']);
}
/** @test */
public function it_can_search_{{ plural }}(): void
{
${{ camel }}1 = {{ studly }}::factory()->create([\'name\' => \'Searchable {{ title }}\']);
${{ camel }}2 = {{ studly }}::factory()->create([\'name\' => \'Other {{ title }}\']);
$response = $this->actingAs($this->user)->get(route(\'{{ plural }}.index\', [\'search\' => \'Searchable\']));
$response->assertStatus(200)
->assertSee(${{ camel }}1->name)
->assertDontSee(${{ camel }}2->name);
}
}';
}
/**
* Replace stub variables with actual values
*/
protected function replaceStubVariables(string $stub, array $names): string
{
foreach ($names as $key => $value) {
$stub = str_replace("{{ {$key} }}", $value, $stub);
}
return $stub;
}
/**
* Get route definition
*/
protected function getRouteDefinition(array $names): string
{
return "// {$names['pluralTitle']} CRUD routes
Route::resource('{$names['plural']}', App\Http\Controllers\\{$names['controller']}::class);";
}
/**
* Get API route definition
*/
protected function getApiRouteDefinition(array $names): string
{
return "// {$names['pluralTitle']} API routes
Route::apiResource('{$names['plural']}', App\Http\Controllers\Api\\{$names['controller']}::class);";
}
/**
* Display success message
*/
protected function displaySuccessMessage(array $names): void
{
$this->info('');
$this->info('🎉 ThrillWiki CRUD \'' . $names['studly'] . '\' created successfully!');
$this->info('');
$this->info('📁 Files Generated:');
$this->info(' • app/Models/' . $names['model'] . '.php');
$this->info(' • app/Http/Controllers/' . $names['controller'] . '.php');
$this->info(' • app/Http/Requests/' . $names['studly'] . 'Request.php');
$this->info(' • resources/views/' . $names['plural'] . '/ (index, show, create, edit)');
$this->info(' • routes/web.php (resource routes added)');
if ($this->option('migration')) {
$this->info(' • database/migrations/[timestamp]_create_' . $names['table'] . '_table.php');
}
if ($this->option('api')) {
$this->info(' • app/Http/Controllers/Api/' . $names['controller'] . '.php');
$this->info(' • app/Http/Resources/' . $names['studly'] . 'Resource.php');
$this->info(' • routes/api.php (API routes added)');
}
if ($this->option('with-tests')) {
$this->info(' • tests/Feature/' . $names['controller'] . 'Test.php');
}
$this->info('');
$this->info('🚀 Next Steps:');
$this->info(' 1. Run migration: php artisan migrate');
$this->info(' 2. Create factory: php artisan make:factory ' . $names['studly'] . 'Factory');
$this->info(' 3. Update navigation to include new routes');
$this->info(' 4. Customize fields and validation as needed');
}
}