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:
2026-03-09 09:34:10 +02:00
parent a55fafd3a9
commit ae410ca4da
26 changed files with 2501 additions and 35 deletions

View 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");
}
}