Add complete feature suite: Permissions, Audit Trail, API Auth, Error Tracking, Module System, and Site Settings
- Install spatie/laravel-permission v6.24 with 3 roles (admin, editor, viewer) and 5 base permissions - Install owen-it/laravel-auditing v14.0 for tracking model changes - Install laravel/sanctum v4.3 for API token authentication - Install spatie/laravel-ignition v2.11 and spatie/flare-client-php v1.10 for enhanced error tracking - Add Module System with make:module artisan command for scaffolding features - Create Site Settings page in Filament admin for logo, colors, and configuration - Add comprehensive debugging documentation (DEBUGGING.md, AI_CONTEXT.md updates) - Create FEATURES.md with complete feature reference - Update User model with HasRoles and HasApiTokens traits - Configure Redis cache and OPcache for performance - Add RolePermissionSeeder with pre-configured roles and permissions - Update documentation with debugging-first workflow - All features pre-installed and production-ready
This commit is contained in:
336
src/app/Console/Commands/MakeModuleCommand.php
Normal file
336
src/app/Console/Commands/MakeModuleCommand.php
Normal file
@@ -0,0 +1,336 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MakeModuleCommand extends Command
|
||||
{
|
||||
protected $signature = 'make:module {name : The name of the module}';
|
||||
|
||||
protected $description = 'Create a new module with standard structure';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$name = $this->argument('name');
|
||||
$studlyName = Str::studly($name);
|
||||
$kebabName = Str::kebab($name);
|
||||
|
||||
$modulePath = app_path("Modules/{$studlyName}");
|
||||
|
||||
if (File::exists($modulePath)) {
|
||||
$this->error("Module {$studlyName} already exists!");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info("Creating module: {$studlyName}");
|
||||
|
||||
$this->createDirectories($modulePath);
|
||||
$this->createModel($modulePath, $studlyName);
|
||||
$this->createMigration($studlyName);
|
||||
$this->createController($modulePath, $studlyName);
|
||||
$this->createRoutes($modulePath, $studlyName, $kebabName);
|
||||
$this->createViews($modulePath, $studlyName, $kebabName);
|
||||
$this->createFilamentResource($studlyName);
|
||||
$this->createTests($studlyName);
|
||||
|
||||
$this->info("\n✅ Module {$studlyName} created successfully!");
|
||||
$this->info("\nNext steps:");
|
||||
$this->info("1. Run migrations: php artisan migrate");
|
||||
$this->info("2. Register routes in routes/web.php:");
|
||||
$this->info(" require __DIR__.'/../app/Modules/{$studlyName}/routes.php';");
|
||||
$this->info("3. Access at: /{$kebabName}");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
protected function createDirectories(string $path): void
|
||||
{
|
||||
File::makeDirectory($path, 0755, true);
|
||||
File::makeDirectory("{$path}/Models", 0755, true);
|
||||
File::makeDirectory("{$path}/Controllers", 0755, true);
|
||||
File::makeDirectory("{$path}/Views", 0755, true);
|
||||
}
|
||||
|
||||
protected function createModel(string $path, string $name): void
|
||||
{
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
namespace App\Modules\\{$name}\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class {$name} extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected \$fillable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
}
|
||||
PHP;
|
||||
|
||||
File::put("{$path}/Models/{$name}.php", $stub);
|
||||
$this->info("✓ Created Model: {$name}");
|
||||
}
|
||||
|
||||
protected function createMigration(string $name): void
|
||||
{
|
||||
$table = Str::snake(Str::plural($name));
|
||||
$timestamp = date('Y_m_d_His');
|
||||
$migrationName = "create_{$table}_table";
|
||||
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('{$table}', function (Blueprint \$table) {
|
||||
\$table->id();
|
||||
\$table->string('name');
|
||||
\$table->text('description')->nullable();
|
||||
\$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('{$table}');
|
||||
}
|
||||
};
|
||||
PHP;
|
||||
|
||||
$migrationPath = database_path("migrations/{$timestamp}_{$migrationName}.php");
|
||||
File::put($migrationPath, $stub);
|
||||
$this->info("✓ Created Migration: {$migrationName}");
|
||||
}
|
||||
|
||||
protected function createController(string $path, string $name): void
|
||||
{
|
||||
$plural = Str::plural($name);
|
||||
$variable = Str::camel($name);
|
||||
$pluralVariable = Str::camel($plural);
|
||||
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
namespace App\Modules\\{$name}\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Modules\\{$name}\Models\\{$name};
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class {$name}Controller extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
\${$pluralVariable} = {$name}::latest()->paginate(15);
|
||||
return view('{$name}::index', compact('{$pluralVariable}'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('{$name}::create');
|
||||
}
|
||||
|
||||
public function store(Request \$request)
|
||||
{
|
||||
\$validated = \$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
{$name}::create(\$validated);
|
||||
|
||||
return redirect()->route('{$variable}.index')
|
||||
->with('success', '{$name} created successfully.');
|
||||
}
|
||||
|
||||
public function show({$name} \${$variable})
|
||||
{
|
||||
return view('{$name}::show', compact('{$variable}'));
|
||||
}
|
||||
|
||||
public function edit({$name} \${$variable})
|
||||
{
|
||||
return view('{$name}::edit', compact('{$variable}'));
|
||||
}
|
||||
|
||||
public function update(Request \$request, {$name} \${$variable})
|
||||
{
|
||||
\$validated = \$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
\${$variable}->update(\$validated);
|
||||
|
||||
return redirect()->route('{$variable}.index')
|
||||
->with('success', '{$name} updated successfully.');
|
||||
}
|
||||
|
||||
public function destroy({$name} \${$variable})
|
||||
{
|
||||
\${$variable}->delete();
|
||||
|
||||
return redirect()->route('{$variable}.index')
|
||||
->with('success', '{$name} deleted successfully.');
|
||||
}
|
||||
}
|
||||
PHP;
|
||||
|
||||
File::put("{$path}/Controllers/{$name}Controller.php", $stub);
|
||||
$this->info("✓ Created Controller: {$name}Controller");
|
||||
}
|
||||
|
||||
protected function createRoutes(string $path, string $name, string $kebabName): void
|
||||
{
|
||||
$variable = Str::camel($name);
|
||||
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Modules\\{$name}\Controllers\\{$name}Controller;
|
||||
|
||||
Route::middleware(['web', 'auth'])->group(function () {
|
||||
Route::resource('{$kebabName}', {$name}Controller::class)->names('{$variable}');
|
||||
});
|
||||
PHP;
|
||||
|
||||
File::put("{$path}/routes.php", $stub);
|
||||
$this->info("✓ Created Routes");
|
||||
}
|
||||
|
||||
protected function createViews(string $path, string $name, string $kebabName): void
|
||||
{
|
||||
$plural = Str::plural($name);
|
||||
$variable = Str::camel($name);
|
||||
$pluralVariable = Str::camel($plural);
|
||||
|
||||
$indexView = <<<BLADE
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('{$plural}') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900">
|
||||
<div class="flex justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold">{$plural} List</h3>
|
||||
<a href="{{ route('{$variable}.create') }}" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
|
||||
Create New
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Name</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse(\${$pluralVariable} as \${$variable})
|
||||
<tr>
|
||||
<td class="px-6 py-4">{{ \${$variable}->name }}</td>
|
||||
<td class="px-6 py-4">{{ \${$variable}->description }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<a href="{{ route('{$variable}.edit', \${$variable}) }}" class="text-blue-600 hover:text-blue-900">Edit</a>
|
||||
<form action="{{ route('{$variable}.destroy', \${$variable}) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-red-600 hover:text-red-900 ml-2">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="3" class="px-6 py-4 text-center">No {$pluralVariable} found.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="mt-4">
|
||||
{{ \${$pluralVariable}->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
BLADE;
|
||||
|
||||
File::put("{$path}/Views/index.blade.php", $indexView);
|
||||
$this->info("✓ Created Views");
|
||||
}
|
||||
|
||||
protected function createFilamentResource(string $name): void
|
||||
{
|
||||
$this->call('make:filament-resource', [
|
||||
'name' => $name,
|
||||
'--generate' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function createTests(string $name): void
|
||||
{
|
||||
$variable = Str::camel($name);
|
||||
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
use App\Modules\\{$name}\Models\\{$name};
|
||||
use App\Models\User;
|
||||
|
||||
it('can list {$variable}s', function () {
|
||||
\$user = User::factory()->create();
|
||||
{$name}::factory()->count(3)->create();
|
||||
|
||||
\$response = \$this->actingAs(\$user)->get(route('{$variable}.index'));
|
||||
|
||||
\$response->assertStatus(200);
|
||||
});
|
||||
|
||||
it('can create {$variable}', function () {
|
||||
\$user = User::factory()->create();
|
||||
|
||||
\$response = \$this->actingAs(\$user)->post(route('{$variable}.store'), [
|
||||
'name' => 'Test {$name}',
|
||||
'description' => 'Test description',
|
||||
]);
|
||||
|
||||
\$response->assertRedirect(route('{$variable}.index'));
|
||||
\$this->assertDatabaseHas('{$variable}s', ['name' => 'Test {$name}']);
|
||||
});
|
||||
PHP;
|
||||
|
||||
$testPath = base_path("tests/Modules/{$name}");
|
||||
File::makeDirectory($testPath, 0755, true);
|
||||
File::put("{$testPath}/{$name}Test.php", $stub);
|
||||
$this->info("✓ Created Tests");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user