1007 lines
30 KiB
Bash
1007 lines
30 KiB
Bash
#!/bin/bash
|
|
|
|
# Laravel Base Setup Script
|
|
# Run this after post-install.sh to configure auth, API, and base structure
|
|
# Usage: ./scripts/laravel-setup.sh
|
|
|
|
set -e
|
|
|
|
echo "=========================================="
|
|
echo "Laravel Base Setup"
|
|
echo "=========================================="
|
|
|
|
# Check if we're in a Laravel project
|
|
if [ ! -f "artisan" ]; then
|
|
echo "Error: Not in a Laravel project directory"
|
|
echo "Run this from your Laravel project root (src/)"
|
|
exit 1
|
|
fi
|
|
|
|
# ============================================
|
|
# AUTH SCAFFOLDING (Blade-focused, no JS frameworks)
|
|
# ============================================
|
|
echo ""
|
|
echo "============================================"
|
|
echo "AUTHENTICATION SETUP (Blade-based)"
|
|
echo "============================================"
|
|
echo ""
|
|
echo "Choose authentication scaffolding:"
|
|
echo ""
|
|
echo "1) Laravel Breeze + Blade (Recommended)"
|
|
echo " - Simple, server-side rendered"
|
|
echo " - Login, register, password reset, email verification"
|
|
echo " - Tailwind CSS styling"
|
|
echo ""
|
|
echo "2) Laravel Breeze + Livewire"
|
|
echo " - Reactive components without writing JavaScript"
|
|
echo " - Same features as Blade with dynamic updates"
|
|
echo ""
|
|
echo "3) Laravel Breeze API only"
|
|
echo " - Headless API authentication"
|
|
echo " - For when you build your own Blade views"
|
|
echo ""
|
|
echo "4) Laravel Jetstream + Livewire (Full-featured)"
|
|
echo " - Profile management, 2FA, API tokens"
|
|
echo " - Optional teams feature"
|
|
echo " - Best for SaaS applications"
|
|
echo ""
|
|
echo "5) None (Manual setup later)"
|
|
echo ""
|
|
read -p "Enter choice [1-5]: " AUTH_CHOICE
|
|
|
|
case $AUTH_CHOICE in
|
|
1)
|
|
echo ""
|
|
echo "Installing Breeze with Blade..."
|
|
composer require laravel/breeze --dev
|
|
php artisan breeze:install blade
|
|
php artisan migrate
|
|
npm install && npm run build
|
|
echo "Breeze (Blade) installed successfully!"
|
|
;;
|
|
2)
|
|
echo ""
|
|
echo "Installing Breeze with Livewire..."
|
|
composer require laravel/breeze --dev
|
|
php artisan breeze:install livewire
|
|
php artisan migrate
|
|
npm install && npm run build
|
|
echo "Breeze (Livewire) installed successfully!"
|
|
;;
|
|
3)
|
|
echo ""
|
|
echo "Installing Breeze API only..."
|
|
composer require laravel/breeze --dev
|
|
php artisan breeze:install api
|
|
php artisan migrate
|
|
echo "Breeze (API) installed successfully!"
|
|
echo "Build your own Blade views for the frontend."
|
|
;;
|
|
4)
|
|
echo ""
|
|
read -p "Enable Teams feature? [y/N]: " ENABLE_TEAMS
|
|
|
|
composer require laravel/jetstream
|
|
|
|
TEAMS_FLAG=""
|
|
if [[ "$ENABLE_TEAMS" =~ ^[Yy]$ ]]; then
|
|
TEAMS_FLAG="--teams"
|
|
fi
|
|
|
|
php artisan jetstream:install livewire $TEAMS_FLAG
|
|
php artisan migrate
|
|
npm install && npm run build
|
|
echo "Jetstream (Livewire) installed successfully!"
|
|
;;
|
|
5)
|
|
echo "Skipping auth scaffolding."
|
|
;;
|
|
esac
|
|
|
|
# ============================================
|
|
# TESTING (PEST)
|
|
# ============================================
|
|
echo ""
|
|
echo "============================================"
|
|
echo "TESTING FRAMEWORK (Pest)"
|
|
echo "============================================"
|
|
echo ""
|
|
echo "Pest is a testing framework with elegant syntax:"
|
|
echo " - Clean, readable test syntax"
|
|
echo " - Built on top of PHPUnit"
|
|
echo " - Great for unit & feature tests"
|
|
echo ""
|
|
read -p "Install Pest testing framework? [Y/n]: " INSTALL_PEST
|
|
|
|
if [[ ! "$INSTALL_PEST" =~ ^[Nn]$ ]]; then
|
|
echo ""
|
|
echo "Installing Pest..."
|
|
composer require pestphp/pest --dev --with-all-dependencies
|
|
composer require pestphp/pest-plugin-laravel --dev
|
|
|
|
# Initialize Pest
|
|
php artisan pest:install
|
|
|
|
# Configure phpunit.xml for SQLite in-memory
|
|
if [ -f "phpunit.xml" ]; then
|
|
# Add SQLite in-memory for testing
|
|
sed -i 's|<!-- <env name="DB_CONNECTION" value="sqlite"/> -->|<env name="DB_CONNECTION" value="sqlite"/>|' phpunit.xml
|
|
sed -i 's|<!-- <env name="DB_DATABASE" value=":memory:"/> -->|<env name="DB_DATABASE" value=":memory:"/>|' phpunit.xml
|
|
fi
|
|
|
|
# Create tests directory structure
|
|
mkdir -p tests/Feature
|
|
mkdir -p tests/Unit
|
|
mkdir -p tests/Modules
|
|
|
|
# Create Pest.php helper with useful traits
|
|
cat > tests/Pest.php << 'PEST'
|
|
<?php
|
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Test Case
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
uses(Tests\TestCase::class)->in('Feature', 'Modules');
|
|
uses(Tests\TestCase::class)->in('Unit');
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Database Refresh
|
|
|--------------------------------------------------------------------------
|
|
| Uses LazilyRefreshDatabase for faster tests - only migrates when needed.
|
|
*/
|
|
|
|
uses(LazilyRefreshDatabase::class)->in('Feature', 'Modules');
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Expectations
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
expect()->extend('toBeOne', function () {
|
|
return $this->toBe(1);
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Functions
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
function createUser(array $attributes = []): \App\Models\User
|
|
{
|
|
return \App\Models\User::factory()->create($attributes);
|
|
}
|
|
|
|
function createAdmin(array $attributes = []): \App\Models\User
|
|
{
|
|
$user = createUser($attributes);
|
|
$user->assignRole('admin');
|
|
return $user;
|
|
}
|
|
PEST
|
|
|
|
# Create example feature test
|
|
cat > tests/Feature/ExampleTest.php << 'TEST'
|
|
<?php
|
|
|
|
it('returns a successful response from homepage', function () {
|
|
$response = $this->get('/');
|
|
|
|
$response->assertStatus(200);
|
|
});
|
|
|
|
it('redirects guests from admin to login', function () {
|
|
$response = $this->get('/admin');
|
|
|
|
$response->assertRedirect('/admin/login');
|
|
});
|
|
TEST
|
|
|
|
# Create example unit test
|
|
cat > tests/Unit/ExampleTest.php << 'TEST'
|
|
<?php
|
|
|
|
it('can perform basic assertions', function () {
|
|
expect(true)->toBeTrue();
|
|
expect(1 + 1)->toBe(2);
|
|
expect(['a', 'b', 'c'])->toContain('b');
|
|
});
|
|
|
|
it('can use Laravel helpers', function () {
|
|
expect(config('app.name'))->toBeString();
|
|
expect(app())->toBeInstanceOf(\Illuminate\Foundation\Application::class);
|
|
});
|
|
TEST
|
|
|
|
# Create module test helper
|
|
cat > tests/Modules/.gitkeep << 'GITKEEP'
|
|
# Module tests go here
|
|
# Create subdirectories per module, e.g., tests/Modules/Inventory/
|
|
GITKEEP
|
|
|
|
# Create test helper for modules
|
|
cat > tests/TestHelpers.php << 'HELPER'
|
|
<?php
|
|
|
|
namespace Tests;
|
|
|
|
trait TestHelpers
|
|
{
|
|
/**
|
|
* Create a user with specific permissions.
|
|
*/
|
|
protected function userWithPermissions(array $permissions): \App\Models\User
|
|
{
|
|
$user = \App\Models\User::factory()->create();
|
|
|
|
foreach ($permissions as $permission) {
|
|
$user->givePermissionTo($permission);
|
|
}
|
|
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* Create a user with module access.
|
|
*/
|
|
protected function userWithModuleAccess(string $moduleSlug): \App\Models\User
|
|
{
|
|
return $this->userWithPermissions([
|
|
"{$moduleSlug}.view",
|
|
"{$moduleSlug}.create",
|
|
"{$moduleSlug}.edit",
|
|
"{$moduleSlug}.delete",
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Assert model was audited.
|
|
*/
|
|
protected function assertAudited($model, string $event = 'created'): void
|
|
{
|
|
$this->assertDatabaseHas('audits', [
|
|
'auditable_type' => get_class($model),
|
|
'auditable_id' => $model->id,
|
|
'event' => $event,
|
|
]);
|
|
}
|
|
}
|
|
HELPER
|
|
|
|
echo ""
|
|
echo "Pest installed!"
|
|
echo "Run tests with: php artisan test"
|
|
echo "Or: ./vendor/bin/pest"
|
|
fi
|
|
|
|
# ============================================
|
|
# FILAMENT ADMIN PANEL
|
|
# ============================================
|
|
echo ""
|
|
echo "============================================"
|
|
echo "ADMIN PANEL (Filament)"
|
|
echo "============================================"
|
|
echo ""
|
|
echo "Filament provides a full-featured admin panel with:"
|
|
echo " - User management (list, create, edit, delete)"
|
|
echo " - Role & permission management"
|
|
echo " - Dashboard widgets"
|
|
echo " - Form & table builders"
|
|
echo ""
|
|
read -p "Install Filament Admin Panel? [Y/n]: " INSTALL_FILAMENT
|
|
|
|
if [[ ! "$INSTALL_FILAMENT" =~ ^[Nn]$ ]]; then
|
|
echo ""
|
|
echo "Installing Filament..."
|
|
composer require filament/filament:"^3.2" -W
|
|
|
|
php artisan filament:install --panels
|
|
|
|
echo ""
|
|
echo "Creating admin user..."
|
|
php artisan make:filament-user
|
|
|
|
# Create UserResource for user management
|
|
echo ""
|
|
echo "Creating User management resource..."
|
|
php artisan make:filament-resource User --generate
|
|
|
|
echo ""
|
|
echo "Filament installed successfully!"
|
|
echo "Admin panel available at: /admin"
|
|
|
|
# Install Site Settings
|
|
echo ""
|
|
echo "============================================"
|
|
echo "SITE SETTINGS (Appearance)"
|
|
echo "============================================"
|
|
echo ""
|
|
echo "Site settings allow you to manage:"
|
|
echo " - Logo and favicon"
|
|
echo " - Color scheme"
|
|
echo " - Site name"
|
|
echo ""
|
|
read -p "Install site settings? [Y/n]: " INSTALL_SETTINGS
|
|
|
|
if [[ ! "$INSTALL_SETTINGS" =~ ^[Nn]$ ]]; then
|
|
echo ""
|
|
echo "Installing spatie/laravel-settings..."
|
|
composer require spatie/laravel-settings
|
|
composer require filament/spatie-laravel-settings-plugin:"^3.2"
|
|
|
|
php artisan vendor:publish --provider="Spatie\LaravelSettings\LaravelSettingsServiceProvider" --tag="migrations"
|
|
php artisan migrate
|
|
|
|
# Create Settings directory
|
|
mkdir -p app/Settings
|
|
|
|
# Create SiteSettings class
|
|
cat > app/Settings/SiteSettings.php << 'SETTINGS'
|
|
<?php
|
|
|
|
namespace App\Settings;
|
|
|
|
use Spatie\LaravelSettings\Settings;
|
|
|
|
class SiteSettings extends Settings
|
|
{
|
|
public string $site_name;
|
|
public ?string $logo;
|
|
public ?string $favicon;
|
|
public string $primary_color;
|
|
public string $secondary_color;
|
|
public bool $dark_mode;
|
|
public ?string $footer_text;
|
|
|
|
public static function group(): string
|
|
{
|
|
return 'site';
|
|
}
|
|
}
|
|
SETTINGS
|
|
|
|
# Create settings migration
|
|
cat > database/settings/$(date +%Y_%m_%d_%H%M%S)_create_site_settings.php << 'MIGRATION'
|
|
<?php
|
|
|
|
use Spatie\LaravelSettings\Migrations\SettingsMigration;
|
|
|
|
return new class extends SettingsMigration
|
|
{
|
|
public function up(): void
|
|
{
|
|
$this->migrator->add('site.site_name', config('app.name', 'Laravel'));
|
|
$this->migrator->add('site.logo', null);
|
|
$this->migrator->add('site.favicon', null);
|
|
$this->migrator->add('site.primary_color', '#3b82f6');
|
|
$this->migrator->add('site.secondary_color', '#64748b');
|
|
$this->migrator->add('site.dark_mode', false);
|
|
$this->migrator->add('site.footer_text', null);
|
|
}
|
|
};
|
|
MIGRATION
|
|
|
|
# Run settings migration
|
|
php artisan migrate
|
|
|
|
# Create Filament Settings Page
|
|
mkdir -p app/Filament/Pages
|
|
cat > app/Filament/Pages/ManageSiteSettings.php << 'PAGE'
|
|
<?php
|
|
|
|
namespace App\Filament\Pages;
|
|
|
|
use App\Settings\SiteSettings;
|
|
use Filament\Forms;
|
|
use Filament\Forms\Form;
|
|
use Filament\Pages\SettingsPage;
|
|
|
|
class ManageSiteSettings extends SettingsPage
|
|
{
|
|
protected static ?string $navigationIcon = 'heroicon-o-cog-6-tooth';
|
|
|
|
protected static ?string $navigationGroup = 'Settings';
|
|
|
|
protected static ?int $navigationSort = 100;
|
|
|
|
protected static string $settings = SiteSettings::class;
|
|
|
|
protected static ?string $title = 'Site Settings';
|
|
|
|
protected static ?string $navigationLabel = 'Appearance';
|
|
|
|
public function form(Form $form): Form
|
|
{
|
|
return $form
|
|
->schema([
|
|
Forms\Components\Section::make('General')
|
|
->description('Basic site information')
|
|
->schema([
|
|
Forms\Components\TextInput::make('site_name')
|
|
->label('Site Name')
|
|
->required()
|
|
->maxLength(255),
|
|
Forms\Components\FileUpload::make('logo')
|
|
->label('Logo')
|
|
->image()
|
|
->directory('site')
|
|
->visibility('public')
|
|
->imageResizeMode('contain')
|
|
->imageCropAspectRatio('16:9')
|
|
->imageResizeTargetWidth('400')
|
|
->helperText('Recommended: 400x100px or similar aspect ratio'),
|
|
Forms\Components\FileUpload::make('favicon')
|
|
->label('Favicon')
|
|
->image()
|
|
->directory('site')
|
|
->visibility('public')
|
|
->imageResizeTargetWidth('32')
|
|
->imageResizeTargetHeight('32')
|
|
->helperText('Will be resized to 32x32px'),
|
|
]),
|
|
Forms\Components\Section::make('Appearance')
|
|
->description('Customize the look and feel')
|
|
->schema([
|
|
Forms\Components\ColorPicker::make('primary_color')
|
|
->label('Primary Color')
|
|
->required(),
|
|
Forms\Components\ColorPicker::make('secondary_color')
|
|
->label('Secondary Color')
|
|
->required(),
|
|
Forms\Components\Toggle::make('dark_mode')
|
|
->label('Enable Dark Mode')
|
|
->helperText('Allow users to switch to dark mode'),
|
|
]),
|
|
Forms\Components\Section::make('Footer')
|
|
->schema([
|
|
Forms\Components\Textarea::make('footer_text')
|
|
->label('Footer Text')
|
|
->rows(2)
|
|
->placeholder('© 2024 Your Company. All rights reserved.'),
|
|
]),
|
|
]);
|
|
}
|
|
}
|
|
PAGE
|
|
|
|
# Create helper function
|
|
cat > app/Helpers/site.php << 'HELPER'
|
|
<?php
|
|
|
|
use App\Settings\SiteSettings;
|
|
|
|
if (!function_exists('site_settings')) {
|
|
function site_settings(?string $key = null, mixed $default = null): mixed
|
|
{
|
|
$settings = app(SiteSettings::class);
|
|
|
|
if ($key === null) {
|
|
return $settings;
|
|
}
|
|
|
|
return $settings->{$key} ?? $default;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('site_logo')) {
|
|
function site_logo(): ?string
|
|
{
|
|
$logo = site_settings('logo');
|
|
return $logo ? asset('storage/' . $logo) : null;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('site_favicon')) {
|
|
function site_favicon(): ?string
|
|
{
|
|
$favicon = site_settings('favicon');
|
|
return $favicon ? asset('storage/' . $favicon) : null;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('site_name')) {
|
|
function site_name(): string
|
|
{
|
|
return site_settings('site_name', config('app.name'));
|
|
}
|
|
}
|
|
|
|
if (!function_exists('primary_color')) {
|
|
function primary_color(): string
|
|
{
|
|
return site_settings('primary_color', '#3b82f6');
|
|
}
|
|
}
|
|
|
|
if (!function_exists('secondary_color')) {
|
|
function secondary_color(): string
|
|
{
|
|
return site_settings('secondary_color', '#64748b');
|
|
}
|
|
}
|
|
HELPER
|
|
|
|
# Register helper in composer.json autoload
|
|
php -r "
|
|
\$composer = json_decode(file_get_contents('composer.json'), true);
|
|
\$composer['autoload']['files'] = \$composer['autoload']['files'] ?? [];
|
|
if (!in_array('app/Helpers/site.php', \$composer['autoload']['files'])) {
|
|
\$composer['autoload']['files'][] = 'app/Helpers/site.php';
|
|
}
|
|
file_put_contents('composer.json', json_encode(\$composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
"
|
|
composer dump-autoload
|
|
|
|
# Create Blade component
|
|
mkdir -p app/View/Components
|
|
cat > app/View/Components/SiteHead.php << 'COMPONENT'
|
|
<?php
|
|
|
|
namespace App\View\Components;
|
|
|
|
use Closure;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\View\Component;
|
|
|
|
class SiteHead extends Component
|
|
{
|
|
public function __construct(
|
|
public ?string $title = null
|
|
) {}
|
|
|
|
public function render(): View|Closure|string
|
|
{
|
|
return view('components.site-head');
|
|
}
|
|
}
|
|
COMPONENT
|
|
|
|
mkdir -p resources/views/components
|
|
cat > resources/views/components/site-head.blade.php << 'BLADE'
|
|
{{-- Site Head Component - Include in your <head> --}}
|
|
<title>{{ $title ? $title . ' - ' . site_name() : site_name() }}</title>
|
|
|
|
@if(site_favicon())
|
|
<link rel="icon" href="{{ site_favicon() }}" type="image/x-icon">
|
|
@endif
|
|
|
|
<style>
|
|
:root {
|
|
--primary-color: {{ primary_color() }};
|
|
--secondary-color: {{ secondary_color() }};
|
|
}
|
|
|
|
.text-primary { color: var(--primary-color); }
|
|
.bg-primary { background-color: var(--primary-color); }
|
|
.border-primary { border-color: var(--primary-color); }
|
|
|
|
.text-secondary { color: var(--secondary-color); }
|
|
.bg-secondary { background-color: var(--secondary-color); }
|
|
.border-secondary { border-color: var(--secondary-color); }
|
|
|
|
/* Override Tailwind primary colors */
|
|
.btn-primary {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
}
|
|
.btn-primary:hover {
|
|
filter: brightness(0.9);
|
|
}
|
|
</style>
|
|
BLADE
|
|
|
|
echo ""
|
|
echo "Site settings installed!"
|
|
echo "Access at: /admin → Settings → Appearance"
|
|
fi
|
|
|
|
# Install Audit Trail
|
|
echo ""
|
|
echo "============================================"
|
|
echo "AUDIT TRAIL"
|
|
echo "============================================"
|
|
echo ""
|
|
echo "Audit trail tracks all changes to your data:"
|
|
echo " - Who made the change"
|
|
echo " - What was changed (old → new values)"
|
|
echo " - When it happened"
|
|
echo " - Which module/model"
|
|
echo ""
|
|
read -p "Install audit trail system? [Y/n]: " INSTALL_AUDIT
|
|
|
|
if [[ ! "$INSTALL_AUDIT" =~ ^[Nn]$ ]]; then
|
|
echo ""
|
|
echo "Installing owen-it/laravel-auditing..."
|
|
composer require owen-it/laravel-auditing
|
|
|
|
php artisan vendor:publish --provider="OwenIt\Auditing\AuditingServiceProvider" --tag="config"
|
|
php artisan vendor:publish --provider="OwenIt\Auditing\AuditingServiceProvider" --tag="migrations"
|
|
php artisan migrate
|
|
|
|
# Install Filament Auditing plugin
|
|
echo ""
|
|
echo "Installing Filament audit trail UI..."
|
|
composer require tapp/filament-auditing:"^3.0"
|
|
php artisan vendor:publish --tag="filament-auditing-config"
|
|
|
|
# Create base Auditable trait for modules
|
|
mkdir -p app/Traits
|
|
cat > app/Traits/ModuleAuditable.php << 'TRAIT'
|
|
<?php
|
|
|
|
namespace App\Traits;
|
|
|
|
use OwenIt\Auditing\Contracts\Auditable;
|
|
|
|
trait ModuleAuditable
|
|
{
|
|
use \OwenIt\Auditing\Auditable;
|
|
|
|
/**
|
|
* Get the module name for this model.
|
|
* Override in your model to set custom module name.
|
|
*/
|
|
public function getModuleName(): string
|
|
{
|
|
// Extract module name from namespace
|
|
$namespace = get_class($this);
|
|
if (preg_match('/Modules\\\\([^\\\\]+)/', $namespace, $matches)) {
|
|
return $matches[1];
|
|
}
|
|
return 'Core';
|
|
}
|
|
|
|
/**
|
|
* Generate tags for the audit.
|
|
*/
|
|
public function generateTags(): array
|
|
{
|
|
return [
|
|
'module:' . $this->getModuleName(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Attributes to include in the audit.
|
|
* Override in your model to customize.
|
|
*/
|
|
// protected $auditInclude = [];
|
|
|
|
/**
|
|
* Attributes to exclude from the audit.
|
|
* Override in your model to customize.
|
|
*/
|
|
// protected $auditExclude = [];
|
|
|
|
/**
|
|
* Audit strategy: 'all', 'include', 'exclude'
|
|
* Set via module config or model property.
|
|
*/
|
|
public function getAuditStrategy(): string
|
|
{
|
|
$moduleName = $this->getModuleName();
|
|
$configKey = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $moduleName));
|
|
|
|
return config("{$configKey}.audit.strategy", 'all');
|
|
}
|
|
|
|
/**
|
|
* Check if auditing is enabled for this model.
|
|
*/
|
|
public function isAuditingEnabled(): bool
|
|
{
|
|
$moduleName = $this->getModuleName();
|
|
$configKey = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $moduleName));
|
|
|
|
return config("{$configKey}.audit.enabled", true);
|
|
}
|
|
}
|
|
TRAIT
|
|
|
|
echo ""
|
|
echo "Audit trail installed!"
|
|
echo "Add 'use ModuleAuditable;' to models you want to audit."
|
|
fi
|
|
fi
|
|
|
|
# ============================================
|
|
# API SETUP (SANCTUM)
|
|
# ============================================
|
|
echo ""
|
|
echo "============================================"
|
|
echo "API SETUP"
|
|
echo "============================================"
|
|
echo ""
|
|
read -p "Configure Laravel Sanctum for API authentication? [Y/n]: " SETUP_SANCTUM
|
|
|
|
if [[ ! "$SETUP_SANCTUM" =~ ^[Nn]$ ]]; then
|
|
# Sanctum is included in Laravel 11+ by default
|
|
echo "Publishing Sanctum configuration..."
|
|
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
|
|
|
|
# Add Sanctum middleware to API
|
|
echo "Sanctum configured!"
|
|
echo ""
|
|
echo "API endpoints will use Sanctum for authentication."
|
|
echo "Tokens can be created via: \$user->createToken('token-name')"
|
|
fi
|
|
|
|
# ============================================
|
|
# STORAGE LINK
|
|
# ============================================
|
|
echo ""
|
|
echo "Creating storage symlink..."
|
|
php artisan storage:link
|
|
|
|
# ============================================
|
|
# BASE MIDDLEWARE
|
|
# ============================================
|
|
echo ""
|
|
echo "============================================"
|
|
echo "ADDITIONAL SETUP"
|
|
echo "============================================"
|
|
|
|
# Force HTTPS in production
|
|
read -p "Add ForceHttps middleware for production? [Y/n]: " ADD_HTTPS
|
|
if [[ ! "$ADD_HTTPS" =~ ^[Nn]$ ]]; then
|
|
mkdir -p app/Http/Middleware
|
|
cat > app/Http/Middleware/ForceHttps.php << 'MIDDLEWARE'
|
|
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class ForceHttps
|
|
{
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
if (app()->environment('production') && !$request->secure()) {
|
|
return redirect()->secure($request->getRequestUri());
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
}
|
|
MIDDLEWARE
|
|
echo "ForceHttps middleware created at app/Http/Middleware/ForceHttps.php"
|
|
echo "Register in bootstrap/app.php or routes to enable."
|
|
fi
|
|
|
|
# Security Headers Middleware
|
|
read -p "Add SecurityHeaders middleware? [Y/n]: " ADD_SECURITY
|
|
if [[ ! "$ADD_SECURITY" =~ ^[Nn]$ ]]; then
|
|
cat > app/Http/Middleware/SecurityHeaders.php << 'MIDDLEWARE'
|
|
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class SecurityHeaders
|
|
{
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
$response = $next($request);
|
|
|
|
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
|
|
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
|
$response->headers->set('X-XSS-Protection', '1; mode=block');
|
|
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
$response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
|
|
|
|
if (app()->environment('production')) {
|
|
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
}
|
|
MIDDLEWARE
|
|
echo "SecurityHeaders middleware created at app/Http/Middleware/SecurityHeaders.php"
|
|
fi
|
|
|
|
# ============================================
|
|
# MODULE SYSTEM SETUP
|
|
# ============================================
|
|
echo ""
|
|
echo "============================================"
|
|
echo "MODULE SYSTEM"
|
|
echo "============================================"
|
|
echo ""
|
|
echo "The modular architecture allows you to organize features"
|
|
echo "into self-contained modules with their own admin panels."
|
|
echo ""
|
|
read -p "Install module system (spatie/laravel-permission + make:module command)? [Y/n]: " INSTALL_MODULES
|
|
|
|
if [[ ! "$INSTALL_MODULES" =~ ^[Nn]$ ]]; then
|
|
echo ""
|
|
echo "Installing Spatie Permission for role-based access..."
|
|
composer require spatie/laravel-permission
|
|
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
|
|
php artisan migrate
|
|
|
|
echo ""
|
|
echo "Setting up module system..."
|
|
|
|
# Create Modules directory
|
|
mkdir -p app/Modules
|
|
|
|
# Copy ModuleServiceProvider
|
|
if [ -f "../src/app/Providers/ModuleServiceProvider.php.stub" ]; then
|
|
cp ../src/app/Providers/ModuleServiceProvider.php.stub app/Providers/ModuleServiceProvider.php
|
|
else
|
|
# Create inline if stub not found
|
|
cat > app/Providers/ModuleServiceProvider.php << 'PROVIDER'
|
|
<?php
|
|
|
|
namespace App\Providers;
|
|
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\ServiceProvider;
|
|
|
|
class ModuleServiceProvider extends ServiceProvider
|
|
{
|
|
public function register(): void
|
|
{
|
|
$modulesPath = app_path('Modules');
|
|
if (!File::isDirectory($modulesPath)) return;
|
|
|
|
foreach (File::directories($modulesPath) as $modulePath) {
|
|
$moduleName = basename($modulePath);
|
|
$providerClass = "App\\Modules\\{$moduleName}\\{$moduleName}ServiceProvider";
|
|
if (class_exists($providerClass)) {
|
|
$this->app->register($providerClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function boot(): void
|
|
{
|
|
$modulesPath = app_path('Modules');
|
|
if (!File::isDirectory($modulesPath)) return;
|
|
|
|
foreach (File::directories($modulesPath) as $modulePath) {
|
|
$moduleName = basename($modulePath);
|
|
|
|
// Load routes
|
|
$webRoutes = "{$modulePath}/Routes/web.php";
|
|
$apiRoutes = "{$modulePath}/Routes/api.php";
|
|
if (File::exists($webRoutes)) $this->loadRoutesFrom($webRoutes);
|
|
if (File::exists($apiRoutes)) $this->loadRoutesFrom($apiRoutes);
|
|
|
|
// Load views
|
|
$viewsPath = "{$modulePath}/Resources/views";
|
|
if (File::isDirectory($viewsPath)) {
|
|
$slug = strtolower(preg_replace('/(?<!^)[A-Z]/', '-$0', $moduleName));
|
|
$this->loadViewsFrom($viewsPath, $slug);
|
|
}
|
|
|
|
// Load migrations
|
|
$migrationsPath = "{$modulePath}/Database/Migrations";
|
|
if (File::isDirectory($migrationsPath)) {
|
|
$this->loadMigrationsFrom($migrationsPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
PROVIDER
|
|
fi
|
|
|
|
# Copy MakeModule command
|
|
mkdir -p app/Console/Commands
|
|
if [ -f "../src/app/Console/Commands/MakeModule.php.stub" ]; then
|
|
cp ../src/app/Console/Commands/MakeModule.php.stub app/Console/Commands/MakeModule.php
|
|
fi
|
|
|
|
# Register ModuleServiceProvider in bootstrap/providers.php
|
|
if [ -f "bootstrap/providers.php" ]; then
|
|
if ! grep -q "ModuleServiceProvider" bootstrap/providers.php; then
|
|
sed -i 's/];/ App\\Providers\\ModuleServiceProvider::class,\n];/' bootstrap/providers.php
|
|
fi
|
|
fi
|
|
|
|
# Create base permission seeder
|
|
cat > database/seeders/PermissionSeeder.php << 'SEEDER'
|
|
<?php
|
|
|
|
namespace Database\Seeders;
|
|
|
|
use Illuminate\Database\Seeder;
|
|
use Spatie\Permission\Models\Permission;
|
|
use Spatie\Permission\Models\Role;
|
|
use Illuminate\Support\Facades\File;
|
|
|
|
class PermissionSeeder extends Seeder
|
|
{
|
|
public function run(): void
|
|
{
|
|
// Create default roles
|
|
$adminRole = Role::firstOrCreate(['name' => 'admin', 'guard_name' => 'web']);
|
|
$userRole = Role::firstOrCreate(['name' => 'user', 'guard_name' => 'web']);
|
|
|
|
// Load permissions from all modules
|
|
$modulesPath = app_path('Modules');
|
|
|
|
if (File::isDirectory($modulesPath)) {
|
|
foreach (File::directories($modulesPath) as $modulePath) {
|
|
$permissionsFile = "{$modulePath}/Permissions.php";
|
|
|
|
if (File::exists($permissionsFile)) {
|
|
$permissions = require $permissionsFile;
|
|
|
|
foreach ($permissions as $name => $description) {
|
|
Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']);
|
|
}
|
|
|
|
// Give admin all module permissions
|
|
$adminRole->givePermissionTo(array_keys($permissions));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SEEDER
|
|
|
|
echo ""
|
|
echo "Module system installed!"
|
|
echo ""
|
|
echo "Create modules with: php artisan make:module ModuleName"
|
|
echo "Options:"
|
|
echo " --model=Product Create a model with Filament resource"
|
|
echo " --api Include API routes"
|
|
echo " --no-filament Skip Filament integration"
|
|
fi
|
|
|
|
# ============================================
|
|
# CACHE CONFIGURATION
|
|
# ============================================
|
|
echo ""
|
|
echo "Clearing and caching configuration..."
|
|
php artisan config:clear
|
|
php artisan cache:clear
|
|
php artisan view:clear
|
|
php artisan route:clear
|
|
|
|
# ============================================
|
|
# FINAL OUTPUT
|
|
# ============================================
|
|
echo ""
|
|
echo "=========================================="
|
|
echo "Laravel Base Setup Complete!"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo "What was configured:"
|
|
echo " ✓ Authentication scaffolding (based on selection)"
|
|
echo " ✓ Filament Admin Panel (if selected)"
|
|
echo " ✓ Sanctum API authentication"
|
|
echo " ✓ Storage symlink"
|
|
echo " ✓ Security middleware"
|
|
echo " ✓ Module system (if selected)"
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. Review and customize routes in routes/"
|
|
echo " 2. Add middleware to bootstrap/app.php if needed"
|
|
echo " 3. Create your first module: php artisan make:module YourModule"
|
|
echo " 4. Start building your application!"
|
|
echo ""
|
|
echo "Useful commands:"
|
|
echo " make up - Start development server"
|
|
echo " make test - Run tests"
|
|
echo " make lint - Fix code style"
|
|
echo " make fresh - Reset database with seeders"
|
|
echo " php artisan make:module Name - Create a new module"
|
|
echo ""
|