From ae410ca4da084cd1b2a5dbbfbac7c705d304facc Mon Sep 17 00:00:00 2001 From: theRADcozaDEV Date: Mon, 9 Mar 2026 09:34:10 +0200 Subject: [PATCH] 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 --- AI_CONTEXT.md | 44 +- DEBUGGING.md | 331 ++++++++++ FEATURES.md | 272 ++++++++ GETTING_STARTED.md | 88 ++- docker-compose.yml | 4 +- src/.env.mysql | 2 +- src/.env.pgsql | 2 +- src/.env.sqlite | 2 +- .../Console/Commands/MakeModuleCommand.php | 336 ++++++++++ src/app/Filament/Pages/Settings.php | 125 ++++ src/app/Models/Setting.php | 38 ++ src/app/Models/User.php | 4 +- src/app/Modules/README.md | 94 +++ src/bootstrap/app.php | 1 + src/composer.json | 7 +- src/composer.lock | 614 +++++++++++++++++- src/config/permission.php | 202 ++++++ src/config/sanctum.php | 84 +++ ...026_03_09_022522_create_settings_table.php | 30 + ..._03_09_023752_create_permission_tables.php | 134 ++++ .../2026_03_09_024743_create_audits_table.php | 27 + ...35_create_personal_access_tokens_table.php | 33 + src/database/seeders/RolePermissionSeeder.php | 42 ++ .../views/filament/pages/settings.blade.php | 9 + .../admin-resource/pages/settings.blade.php | 3 + src/routes/api.php | 8 + 26 files changed, 2501 insertions(+), 35 deletions(-) create mode 100644 DEBUGGING.md create mode 100644 FEATURES.md create mode 100644 src/app/Console/Commands/MakeModuleCommand.php create mode 100644 src/app/Filament/Pages/Settings.php create mode 100644 src/app/Models/Setting.php create mode 100644 src/app/Modules/README.md create mode 100644 src/config/permission.php create mode 100644 src/config/sanctum.php create mode 100644 src/database/migrations/2026_03_09_022522_create_settings_table.php create mode 100644 src/database/migrations/2026_03_09_023752_create_permission_tables.php create mode 100644 src/database/migrations/2026_03_09_024743_create_audits_table.php create mode 100644 src/database/migrations/2026_03_09_030435_create_personal_access_tokens_table.php create mode 100644 src/database/seeders/RolePermissionSeeder.php create mode 100644 src/resources/views/filament/pages/settings.blade.php create mode 100644 src/resources/views/filament/resources/admin-resource/pages/settings.blade.php create mode 100644 src/routes/api.php diff --git a/AI_CONTEXT.md b/AI_CONTEXT.md index d654c75..78308fe 100644 --- a/AI_CONTEXT.md +++ b/AI_CONTEXT.md @@ -27,11 +27,49 @@ This is a **ready-to-use Laravel Docker Development Template** with everything p | **Cache/Queue** | Redis | | **Auth** | Laravel Breeze (Blade + Livewire) - PRE-INSTALLED | | **Testing** | Pest - PRE-INSTALLED | -| **Permissions** | spatie/laravel-permission | -| **Audit** | owen-it/laravel-auditing | -| **Error Tracking** | spatie/laravel-flare + spatie/laravel-ignition | +| **Permissions** | spatie/laravel-permission - PRE-INSTALLED | +| **Audit** | owen-it/laravel-auditing - PRE-INSTALLED | +| **Error Tracking** | spatie/laravel-flare + spatie/laravel-ignition - PRE-INSTALLED | +| **API Auth** | Laravel Sanctum - PRE-INSTALLED | | **Code Style** | Laravel Pint | +## 🚨 CRITICAL: Debugging Strategy + +**ALWAYS CHECK LOGS FIRST - NEVER GUESS AT SOLUTIONS** + +When encountering errors: + +### Step 1: Check Laravel Logs +```bash +docker-compose exec app cat storage/logs/laravel.log | grep -A 20 "Error" +``` + +### Step 2: Identify Root Cause +- Read the full stack trace +- Find the exact file and line number +- Understand what the code is trying to do + +### Step 3: Fix and Verify +- Make targeted fix to root cause +- Clear relevant caches +- Test the specific scenario + +### Common Commands: +```bash +# View recent errors +docker-compose exec app tail -n 100 storage/logs/laravel.log + +# Check container logs +docker-compose logs --tail=50 app +docker-compose logs --tail=50 nginx + +# Clear caches after fixes +docker-compose exec app php artisan optimize:clear +docker-compose exec app php artisan permission:cache-reset +``` + +**See [DEBUGGING.md](DEBUGGING.md) for complete debugging guide.** + ## Important: No JavaScript Frameworks **This template deliberately avoids JavaScript frameworks** (Vue, React, Inertia) to keep debugging simple. All frontend is: diff --git a/DEBUGGING.md b/DEBUGGING.md new file mode 100644 index 0000000..f8745d3 --- /dev/null +++ b/DEBUGGING.md @@ -0,0 +1,331 @@ +# Debugging Strategy + +**CRITICAL**: This template includes enhanced error tracking. Always check logs FIRST before guessing at solutions. + +## 🚨 Golden Rule: Logs First, Guessing Never + +When encountering errors: + +### ❌ WRONG Approach: +1. See error message +2. Guess at the cause +3. Try random fixes +4. Waste time + +### βœ… CORRECT Approach: +1. **Check Laravel logs immediately** +2. Read the full stack trace +3. Identify the exact file and line +4. Fix the root cause + +--- + +## Error Tracking Suite + +This template includes: + +- **Spatie Laravel Ignition** v2.11.0 - Enhanced error pages +- **Spatie Flare Client** v1.10.1 - Error tracking +- **Laravel Logs** - Full context and stack traces + +--- + +## How to Debug Errors + +### 1. Check Laravel Logs (PRIMARY METHOD) + +```bash +# View recent errors +docker-compose exec app tail -n 100 storage/logs/laravel.log + +# Search for specific error +docker-compose exec app cat storage/logs/laravel.log | grep "ErrorType" + +# Watch logs in real-time +docker-compose exec app tail -f storage/logs/laravel.log +``` + +**What you'll see:** +- Exact file path and line number +- Full stack trace +- Variable values at time of error +- User context (if logged in) + +### 2. Check Container Logs + +```bash +# Application logs +docker-compose logs --tail=50 app + +# Nginx logs (for 502/504 errors) +docker-compose logs --tail=50 nginx + +# All services +docker-compose logs --tail=50 +``` + +### 3. Use Ignition Error Pages (Development) + +When `APP_DEBUG=true`, Laravel shows beautiful error pages with: +- Code context (lines around the error) +- Stack trace with clickable frames +- Solution suggestions for common errors +- Request data and environment info + +**Access**: Errors automatically show in browser during development + +--- + +## Common Error Patterns + +### TypeError: htmlspecialchars() expects string, array given + +**Log Location**: `storage/logs/laravel.log` + +**What to look for**: +``` +htmlspecialchars(): Argument #1 ($string) must be of type string, array given +at /var/www/html/path/to/file.blade.php:LINE +``` + +**Root Cause**: Blade template trying to echo an array with `{{ }}` + +**Fix**: Use proper component or `@json()` directive + +--- + +### 504 Gateway Timeout + +**Log Location**: +- `docker-compose logs nginx` +- `storage/logs/laravel.log` + +**What to look for**: +``` +upstream timed out (110: Operation timed out) +``` + +**Common Causes**: +- Stale cache after package installation +- Infinite loop in code +- Database query timeout +- Permission cache issues + +**Fix**: +```bash +docker-compose exec app php artisan cache:clear +docker-compose exec app php artisan config:clear +docker-compose exec app php artisan view:clear +docker-compose exec app php artisan permission:cache-reset +docker-compose restart app nginx +``` + +--- + +### Database Connection Errors + +**Log Location**: `storage/logs/laravel.log` + +**What to look for**: +``` +SQLSTATE[HY000] [2002] Connection refused +``` + +**Fix**: +```bash +# Check if database is running +docker-compose ps + +# Check database logs +docker-compose logs mysql + +# Restart database +docker-compose restart mysql + +# Wait for database to be ready +docker-compose exec mysql mysqladmin ping -h localhost +``` + +--- + +### Redis Connection Errors + +**Log Location**: `storage/logs/laravel.log` + +**What to look for**: +``` +php_network_getaddresses: getaddrinfo for redis failed +``` + +**Fix**: +```bash +# Check Redis is running +docker-compose ps redis + +# Restart Redis +docker-compose restart redis + +# Clear config cache +docker-compose exec app php artisan config:clear +``` + +--- + +## Debugging Workflow for AI Agents + +When a user reports an error, follow this exact sequence: + +### Step 1: Get the Full Error +```bash +docker-compose exec app cat storage/logs/laravel.log | grep -A 20 "ErrorType" +``` + +### Step 2: Identify Root Cause +- Read the stack trace +- Find the originating file and line +- Understand what the code is trying to do + +### Step 3: Verify the Fix +- Make the change +- Clear relevant caches +- Test the specific scenario that caused the error + +### Step 4: Document +- Explain what was wrong +- Show the fix +- Explain why it works + +--- + +## Cache Clearing Commands + +Always clear caches after: +- Installing new packages +- Changing configuration +- Modifying service providers +- Updating permissions/roles + +```bash +# Clear all caches +docker-compose exec app php artisan optimize:clear + +# Or individually +docker-compose exec app php artisan cache:clear +docker-compose exec app php artisan config:clear +docker-compose exec app php artisan route:clear +docker-compose exec app php artisan view:clear +docker-compose exec app php artisan permission:cache-reset + +# Regenerate autoloader +docker-compose exec app composer dump-autoload +``` + +--- + +## Performance Debugging + +### Slow Page Loads + +**Check**: +1. Laravel Debugbar (if installed) +2. Query count and time +3. OPcache status +4. Redis connection + +**Commands**: +```bash +# Check OPcache status +docker-compose exec app php -i | grep opcache + +# Monitor Redis +docker-compose exec redis redis-cli monitor + +# Check query logs +# Enable in config/database.php: 'log_queries' => true +``` + +--- + +## Production Error Tracking + +For production, consider: + +1. **Flare** (https://flareapp.io) + - Add `FLARE_KEY` to `.env` + - Automatic error reporting + - Error grouping and notifications + +2. **Sentry** (alternative) + ```bash + composer require sentry/sentry-laravel + ``` + +3. **Log Aggregation** + - Papertrail + - Loggly + - CloudWatch + +--- + +## Best Practices + +### βœ… DO: +- Check logs before making changes +- Read the full stack trace +- Clear caches after changes +- Test the specific error scenario +- Document the fix + +### ❌ DON'T: +- Guess at solutions without checking logs +- Make multiple changes at once +- Skip cache clearing +- Assume the error message tells the whole story +- Leave debugging code in production + +--- + +## Quick Reference + +| Error Type | First Check | Quick Fix | +|------------|-------------|-----------| +| TypeError | Laravel logs | Check variable types | +| 504 Timeout | Nginx logs | Clear caches, restart | +| Database | MySQL logs | Check connection, restart DB | +| Redis | Laravel logs | Restart Redis, clear config | +| Permission | Laravel logs | `permission:cache-reset` | +| View | Laravel logs | `view:clear` | +| Route | Laravel logs | `route:clear` | + +--- + +## Log File Locations + +| Service | Log Location | +|---------|--------------| +| Laravel | `storage/logs/laravel.log` | +| Nginx | Docker logs: `docker-compose logs nginx` | +| PHP-FPM | Docker logs: `docker-compose logs app` | +| MySQL | Docker logs: `docker-compose logs mysql` | +| Redis | Docker logs: `docker-compose logs redis` | + +--- + +## Emergency Commands + +When everything is broken: + +```bash +# Nuclear option - reset everything +docker-compose down +docker-compose up -d +docker-compose exec app php artisan optimize:clear +docker-compose exec app composer dump-autoload +docker-compose exec app php artisan migrate:fresh --seed +``` + +**⚠️ WARNING**: `migrate:fresh` will delete all data! + +--- + +**Remember**: Logs are your friend. Always check them first. πŸ” diff --git a/FEATURES.md b/FEATURES.md new file mode 100644 index 0000000..a3ddb91 --- /dev/null +++ b/FEATURES.md @@ -0,0 +1,272 @@ +# Installed Features + +This document lists all features installed in this Laravel Docker Development Template. + +## βœ… Complete Feature List + +### 1. **Permissions & Roles** (spatie/laravel-permission) +- **Version**: 6.24.1 +- **Features**: + - Role-based access control + - Pre-configured roles: admin, editor, viewer + - Permission system for granular access + - User model integration with `HasRoles` trait +- **Usage**: + ```php + // Assign role + $user->assignRole('admin'); + + // Check permission + if ($user->can('users.edit')) { } + + // Check role + if ($user->hasRole('admin')) { } + ``` +- **Database Tables**: `roles`, `permissions`, `model_has_roles`, `model_has_permissions`, `role_has_permissions` + +--- + +### 2. **Audit Trail** (owen-it/laravel-auditing) +- **Version**: 14.0.0 +- **Features**: + - Track all model changes (create, update, delete) + - Record user who made changes + - Store old and new values + - Audit log with timestamps +- **Usage**: + ```php + use OwenIt\Auditing\Contracts\Auditable; + + class Product extends Model implements Auditable + { + use \OwenIt\Auditing\Auditable; + } + + // View audits + $audits = $product->audits; + ``` +- **Database Table**: `audits` + +--- + +### 3. **Error Tracking** (spatie/laravel-ignition + spatie/flare-client-php) +- **Versions**: + - spatie/laravel-ignition: 2.11.0 + - spatie/flare-client-php: 1.10.1 + - spatie/ignition: 1.15.1 +- **Features**: + - Beautiful error pages in development + - Stack trace with code context + - Solution suggestions for common errors + - Optional Flare integration for production error tracking +- **Configuration**: Already active in development mode + +--- + +### 4. **API Authentication** (laravel/sanctum) +- **Version**: 4.3.1 +- **Features**: + - Token-based API authentication + - SPA authentication + - Mobile app authentication + - API token management +- **Usage**: + ```php + // Generate token + $token = $user->createToken('api-token')->plainTextToken; + + // In routes/api.php + Route::middleware('auth:sanctum')->get('/user', function (Request $request) { + return $request->user(); + }); + ``` +- **User Model**: Updated with `HasApiTokens` trait +- **Database Table**: `personal_access_tokens` + +--- + +### 5. **Site Settings** +- **Features**: + - Logo upload + - Color scheme (primary, secondary, accent) + - Site name and description + - Contact email + - Maintenance mode toggle +- **Location**: `/admin/settings` +- **Usage**: + ```php + // Get setting + $siteName = Setting::get('site_name', 'Default'); + + // Set setting + Setting::set('primary_color', '#3b82f6'); + ``` +- **Files**: + - Model: `app/Models/Setting.php` + - Page: `app/Filament/Pages/Settings.php` + - Migration: `database/migrations/2026_03_09_022522_create_settings_table.php` + +--- + +### 6. **Module System** +- **Features**: + - Artisan command to scaffold complete modules + - Auto-generates: Model, Controller, Routes, Views, Migration, Tests, Filament Resource + - Modular architecture for organizing features + - Blade templates with Tailwind CSS +- **Usage**: + ```bash + php artisan make:module ProductCatalog + ``` +- **Documentation**: `app/Modules/README.md` +- **Command**: `app/Console/Commands/MakeModuleCommand.php` + +--- + +### 7. **Filament Admin Panel** +- **Version**: 3.3 +- **Features**: + - User management resource + - Site settings page + - Dashboard with widgets + - Form and table builders + - Dark mode support +- **Access**: http://localhost:8080/admin +- **Credentials**: admin@example.com / password + +--- + +### 8. **Laravel Breeze** +- **Version**: 2.3 +- **Features**: + - Login, register, password reset + - Email verification + - Profile management + - Blade templates with Tailwind CSS + - Dark mode support + +--- + +### 9. **Pest Testing Framework** +- **Version**: 3.8 +- **Features**: + - Modern testing syntax + - Laravel integration + - Example tests included + - Test helpers for permissions and modules +- **Usage**: + ```bash + php artisan test + # or + ./vendor/bin/pest + ``` + +--- + +### 10. **Performance Optimizations** +- **OPcache**: Enabled with development-friendly settings +- **Redis**: Configured for cache and queues +- **Volume Mounts**: Optimized with `:cached` flag for WSL2 +- **Config**: + - `CACHE_STORE=redis` + - `SESSION_DRIVER=database` + - `QUEUE_CONNECTION=redis` + +--- + +## Pre-Configured Roles & Permissions + +### Roles +1. **Admin** - Full access to all features +2. **Editor** - Can view and edit users +3. **Viewer** - Read-only access to users + +### Permissions +- `users.view` +- `users.create` +- `users.edit` +- `users.delete` +- `settings.manage` + +--- + +## Database Tables Created + +1. `users` - User accounts +2. `sessions` - User sessions +3. `cache` - Cache storage +4. `jobs` - Queue jobs +5. `failed_jobs` - Failed queue jobs +6. `password_reset_tokens` - Password resets +7. `settings` - Site configuration +8. `roles` - User roles +9. `permissions` - Access permissions +10. `model_has_roles` - User-role assignments +11. `model_has_permissions` - User-permission assignments +12. `role_has_permissions` - Role-permission assignments +13. `audits` - Audit trail logs +14. `personal_access_tokens` - API tokens + +--- + +## Access Points + +| Feature | URL | Credentials | +|---------|-----|-------------| +| Public Site | http://localhost:8080 | - | +| Admin Panel | http://localhost:8080/admin | admin@example.com / password | +| Site Settings | http://localhost:8080/admin/settings | Admin access required | +| Email Testing | http://localhost:8025 | - | +| API Endpoints | http://localhost:8080/api/* | Requires Sanctum token | + +--- + +## Next Steps + +1. **Customize Site Settings** - Set your logo and brand colors +2. **Create Modules** - Use `php artisan make:module` to build features +3. **Assign Roles** - Give users appropriate access levels +4. **Build API** - Create API endpoints with Sanctum authentication +5. **Write Tests** - Add tests for your custom features +6. **Enable Auditing** - Add `Auditable` interface to models you want to track +7. **Deploy** - See production deployment guide in README.md + +--- + +## Documentation + +- [GETTING_STARTED.md](GETTING_STARTED.md) - Setup and configuration +- [README.md](README.md) - Overview and commands +- [app/Modules/README.md](src/app/Modules/README.md) - Module system guide +- [AI_CONTEXT.md](AI_CONTEXT.md) - AI assistant context + +--- + +## Package Versions + +All packages are installed and configured: + +```json +{ + "require": { + "php": "^8.2", + "filament/filament": "^3.2", + "laravel/framework": "^11.31", + "laravel/sanctum": "^4.3", + "laravel/tinker": "^2.9", + "owen-it/laravel-auditing": "^14.0", + "spatie/flare-client-php": "^1.10", + "spatie/laravel-ignition": "^2.11", + "spatie/laravel-permission": "^6.24" + }, + "require-dev": { + "laravel/breeze": "^2.3", + "pestphp/pest": "^3.8", + "pestphp/pest-plugin-laravel": "^3.2" + } +} +``` + +--- + +**Last Updated**: March 9, 2026 diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 241c1b0..52ef31b 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -49,17 +49,25 @@ This template comes with everything configured and ready to use: ### Admin & Management - **Filament v3.3** - Full-featured admin panel - **User Management** - CRUD interface for users +- **Site Settings** - Logo, color scheme, and site configuration +- **Spatie Permissions** - Role-based access control (admin, editor, viewer) - **Dashboard** - Admin dashboard with widgets +### Security & Tracking +- **Laravel Auditing** - Track all data changes and user actions +- **Laravel Sanctum** - API authentication with tokens +- **Spatie Ignition** - Enhanced error pages and debugging + ### Development Tools - **Pest** - Modern testing framework with elegant syntax - **Laravel Pint** - Opinionated code style fixer - **Mailpit** - Email testing tool +- **Module System** - Artisan command to scaffold new features ### Infrastructure - **Docker** - Containerized development environment - **MySQL/PostgreSQL/SQLite** - Choose your database -- **Redis** - Caching and queues +- **Redis** - Caching and queues (OPcache enabled) - **Queue Workers** - Background job processing (optional) - **Scheduler** - Task scheduling (optional) @@ -111,7 +119,9 @@ Your application is now ready! The setup script created an admin user for you: **Access Points:** - Public site: http://localhost:8080 - Admin panel: http://localhost:8080/admin +- Site settings: http://localhost:8080/admin/settings - Email testing: http://localhost:8025 +- API endpoints: http://localhost:8080/api/* ## Common Commands @@ -203,17 +213,14 @@ my-project/ ### Modules ```bash -# Create module -php artisan make:module ModuleName +# Create a complete module with CRUD, views, tests, and Filament resource +php artisan make:module ProductCatalog -# With model -php artisan make:module ModuleName --model=ModelName - -# With API -php artisan make:module ModuleName --api - -# Without Filament -php artisan make:module ModuleName --no-filament +# This creates: +# - Model, Controller, Routes, Views +# - Migration and Filament Resource +# - Pest tests +# - See app/Modules/README.md for details ``` ## Environment Files @@ -264,6 +271,41 @@ IGNITION_EDITOR=vscode ## Troubleshooting +### 🚨 FIRST STEP: Check Logs + +**Always check logs before trying fixes:** + +```bash +# Laravel application logs (MOST IMPORTANT) +docker-compose exec app tail -n 100 storage/logs/laravel.log + +# Search for specific error +docker-compose exec app cat storage/logs/laravel.log | grep "ErrorType" + +# Container logs +docker-compose logs --tail=50 app +docker-compose logs --tail=50 nginx +``` + +**See [DEBUGGING.md](DEBUGGING.md) for complete debugging guide.** + +--- + +### Application Errors + +When you see errors in the browser: + +1. **Check Laravel logs** (see above) +2. **Read the full stack trace** - it shows exact file and line +3. **Clear caches** after making changes: + +```bash +docker-compose exec app php artisan optimize:clear +docker-compose exec app php artisan permission:cache-reset +``` + +--- + ### Container won't start ```bash @@ -305,20 +347,18 @@ php artisan migrate ## Next Steps -1. **Configure Flare** - Get API key at [flareapp.io](https://flareapp.io) -2. **Create modules** - `php artisan make:module YourFeature` -3. **Add models** - `php artisan make:module YourFeature --model=YourModel` -4. **Write tests** - `make test` -5. **Deploy** - See [Production Deployment](README.md#production-deployment) +1. **Configure Site Settings** - Visit `/admin/settings` to set logo and colors +2. **Set Up Roles** - Assign users to admin/editor/viewer roles in Filament +3. **Create Modules** - `php artisan make:module YourFeature` +4. **Build API** - Use Sanctum tokens for API authentication +5. **Write Tests** - `php artisan test` or `./vendor/bin/pest` +6. **Configure Flare** - Optional: Get API key at [flareapp.io](https://flareapp.io) +7. **Deploy** - See [Production Deployment](README.md#production-deployment) ## Documentation - [README.md](README.md) - Overview and commands -- [docs/laravel-setup.md](docs/laravel-setup.md) - Detailed setup options -- [docs/modules.md](docs/modules.md) - Module architecture -- [docs/audit-trail.md](docs/audit-trail.md) - Audit configuration -- [docs/filament-admin.md](docs/filament-admin.md) - Admin panel -- [docs/error-logging.md](docs/error-logging.md) - Error tracking -- [docs/queues.md](docs/queues.md) - Background jobs -- [docs/ci-cd.md](docs/ci-cd.md) - CI/CD pipeline -- [docs/backup.md](docs/backup.md) - Database backup +- [DEBUGGING.md](DEBUGGING.md) - **Debugging strategy (READ THIS FIRST)** +- [FEATURES.md](FEATURES.md) - Complete feature reference +- [AI_CONTEXT.md](AI_CONTEXT.md) - Context for AI assistants +- [app/Modules/README.md](src/app/Modules/README.md) - Module system guide diff --git a/docker-compose.yml b/docker-compose.yml index 809ba6a..8d24b8f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: restart: unless-stopped working_dir: /var/www/html volumes: - - ./src:/var/www/html + - ./src:/var/www/html:cached - ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini networks: - laravel_network @@ -25,7 +25,7 @@ services: ports: - "${APP_PORT:-8080}:80" volumes: - - ./src:/var/www/html + - ./src:/var/www/html:cached - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf networks: - laravel_network diff --git a/src/.env.mysql b/src/.env.mysql index 2305d63..626cb42 100644 --- a/src/.env.mysql +++ b/src/.env.mysql @@ -26,7 +26,7 @@ DB_DATABASE=laravel DB_USERNAME=laravel DB_PASSWORD=secret -SESSION_DRIVER=redis +SESSION_DRIVER=database SESSION_LIFETIME=120 SESSION_ENCRYPT=false SESSION_PATH=/ diff --git a/src/.env.pgsql b/src/.env.pgsql index 435a01c..1d16314 100644 --- a/src/.env.pgsql +++ b/src/.env.pgsql @@ -26,7 +26,7 @@ DB_DATABASE=laravel DB_USERNAME=laravel DB_PASSWORD=secret -SESSION_DRIVER=redis +SESSION_DRIVER=database SESSION_LIFETIME=120 SESSION_ENCRYPT=false SESSION_PATH=/ diff --git a/src/.env.sqlite b/src/.env.sqlite index efcace2..0f6b1b2 100644 --- a/src/.env.sqlite +++ b/src/.env.sqlite @@ -22,7 +22,7 @@ LOG_LEVEL=debug DB_CONNECTION=sqlite DB_DATABASE=/var/www/html/database/database.sqlite -SESSION_DRIVER=redis +SESSION_DRIVER=database SESSION_LIFETIME=120 SESSION_ENCRYPT=false SESSION_PATH=/ diff --git a/src/app/Console/Commands/MakeModuleCommand.php b/src/app/Console/Commands/MakeModuleCommand.php new file mode 100644 index 0000000..e7e709b --- /dev/null +++ b/src/app/Console/Commands/MakeModuleCommand.php @@ -0,0 +1,336 @@ +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 = <<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 = <<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 = <<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 = <<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 = << + +

+ {{ __('{$plural}') }} +

+
+ +
+
+
+
+
+

{$plural} List

+ + Create New + +
+ + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + + + + + + + + + + + @forelse(\${$pluralVariable} as \${$variable}) + + + + + + @empty + + + + @endforelse + +
NameDescriptionActions
{{ \${$variable}->name }}{{ \${$variable}->description }} + Edit +
+ @csrf + @method('DELETE') + +
+
No {$pluralVariable} found.
+ +
+ {{ \${$pluralVariable}->links() }} +
+
+
+
+
+ +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 = <<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"); + } +} diff --git a/src/app/Filament/Pages/Settings.php b/src/app/Filament/Pages/Settings.php new file mode 100644 index 0000000..4e275a6 --- /dev/null +++ b/src/app/Filament/Pages/Settings.php @@ -0,0 +1,125 @@ +form->fill([ + 'site_name' => is_string($siteName) ? $siteName : config('app.name'), + 'site_logo' => is_string($siteLogo) || is_null($siteLogo) ? $siteLogo : null, + 'primary_color' => is_string($primaryColor) ? $primaryColor : '#3b82f6', + 'secondary_color' => is_string($secondaryColor) ? $secondaryColor : '#8b5cf6', + 'accent_color' => is_string($accentColor) ? $accentColor : '#10b981', + 'site_description' => is_string($siteDescription) || is_null($siteDescription) ? $siteDescription : '', + 'contact_email' => is_string($contactEmail) || is_null($contactEmail) ? $contactEmail : '', + 'maintenance_mode' => is_bool($maintenanceMode) ? $maintenanceMode : false, + ]); + } + + public function form(Form $form): Form + { + return $form + ->schema([ + Forms\Components\Section::make('General Settings') + ->schema([ + Forms\Components\TextInput::make('site_name') + ->label('Site Name') + ->required() + ->maxLength(255), + + Forms\Components\FileUpload::make('site_logo') + ->label('Site Logo') + ->image() + ->directory('logos') + ->visibility('public'), + + Forms\Components\Textarea::make('site_description') + ->label('Site Description') + ->rows(3) + ->maxLength(500), + + Forms\Components\TextInput::make('contact_email') + ->label('Contact Email') + ->email() + ->maxLength(255), + ]), + + Forms\Components\Section::make('Color Scheme') + ->schema([ + Forms\Components\ColorPicker::make('primary_color') + ->label('Primary Color'), + + Forms\Components\ColorPicker::make('secondary_color') + ->label('Secondary Color'), + + Forms\Components\ColorPicker::make('accent_color') + ->label('Accent Color'), + ]) + ->columns(3), + + Forms\Components\Section::make('System') + ->schema([ + Forms\Components\Toggle::make('maintenance_mode') + ->label('Maintenance Mode') + ->helperText('Enable to put the site in maintenance mode'), + ]), + ]) + ->statePath('data'); + } + + protected function getFormActions(): array + { + return [ + Action::make('save') + ->label('Save Settings') + ->submit('save'), + ]; + } + + public function save(): void + { + $data = $this->form->getState(); + + Setting::set('site_name', $data['site_name'] ?? ''); + Setting::set('site_logo', $data['site_logo'] ?? ''); + Setting::set('primary_color', $data['primary_color'] ?? '#3b82f6'); + Setting::set('secondary_color', $data['secondary_color'] ?? '#8b5cf6'); + Setting::set('accent_color', $data['accent_color'] ?? '#10b981'); + Setting::set('site_description', $data['site_description'] ?? ''); + Setting::set('contact_email', $data['contact_email'] ?? ''); + Setting::set('maintenance_mode', $data['maintenance_mode'] ?? false, 'boolean'); + + Notification::make() + ->title('Settings saved successfully') + ->success() + ->send(); + } +} diff --git a/src/app/Models/Setting.php b/src/app/Models/Setting.php new file mode 100644 index 0000000..6b2b5c8 --- /dev/null +++ b/src/app/Models/Setting.php @@ -0,0 +1,38 @@ +first(); + + if (!$setting) { + return $default; + } + + return match ($setting->type) { + 'boolean' => filter_var($setting->value, FILTER_VALIDATE_BOOLEAN), + 'integer' => (int) $setting->value, + 'array', 'json' => json_decode($setting->value, true), + default => $setting->value, + }; + } + + public static function set(string $key, $value, string $type = 'string'): void + { + if (in_array($type, ['array', 'json'])) { + $value = json_encode($value); + } + + static::updateOrCreate( + ['key' => $key], + ['value' => $value, 'type' => $type] + ); + } +} diff --git a/src/app/Models/User.php b/src/app/Models/User.php index 749c7b7..fdc3f44 100644 --- a/src/app/Models/User.php +++ b/src/app/Models/User.php @@ -6,11 +6,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Laravel\Sanctum\HasApiTokens; +use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable; + use HasFactory, Notifiable, HasRoles, HasApiTokens; /** * The attributes that are mass assignable. diff --git a/src/app/Modules/README.md b/src/app/Modules/README.md new file mode 100644 index 0000000..c0bf467 --- /dev/null +++ b/src/app/Modules/README.md @@ -0,0 +1,94 @@ +# Module System + +This Laravel application uses a modular architecture to organize features into self-contained modules. + +## Creating a New Module + +Use the artisan command to scaffold a complete module: + +```bash +php artisan make:module ProductCatalog +``` + +This creates: +- **Model**: `app/Modules/ProductCatalog/Models/ProductCatalog.php` +- **Controller**: `app/Modules/ProductCatalog/Controllers/ProductCatalogController.php` +- **Routes**: `app/Modules/ProductCatalog/routes.php` +- **Views**: `app/Modules/ProductCatalog/Views/` +- **Migration**: `database/migrations/YYYY_MM_DD_HHMMSS_create_product_catalogs_table.php` +- **Filament Resource**: `app/Filament/Resources/ProductCatalogResource.php` +- **Tests**: `tests/Modules/ProductCatalog/ProductCatalogTest.php` + +## Module Structure + +``` +app/Modules/ +└── ProductCatalog/ + β”œβ”€β”€ Models/ + β”‚ └── ProductCatalog.php + β”œβ”€β”€ Controllers/ + β”‚ └── ProductCatalogController.php + β”œβ”€β”€ Views/ + β”‚ β”œβ”€β”€ index.blade.php + β”‚ β”œβ”€β”€ create.blade.php + β”‚ β”œβ”€β”€ edit.blade.php + β”‚ └── show.blade.php + └── routes.php +``` + +## Registering Module Routes + +After creating a module, register its routes in `routes/web.php`: + +```php +require __DIR__.'/../app/Modules/ProductCatalog/routes.php'; +``` + +## Module Features + +Each module includes: + +βœ… **CRUD Operations** - Full Create, Read, Update, Delete functionality +βœ… **Authentication** - Routes protected by auth middleware +βœ… **Filament Admin** - Auto-generated admin panel resource +βœ… **Blade Views** - Responsive Tailwind CSS templates +βœ… **Tests** - Pest test suite for module functionality +βœ… **Permissions** - Ready for role-based access control + +## Example Usage + +```bash +# Create an Inventory module +php artisan make:module Inventory + +# Run migrations +php artisan migrate + +# Register routes in routes/web.php +require __DIR__.'/../app/Modules/Inventory/routes.php'; + +# Access at: http://localhost:8080/inventory +# Admin panel: http://localhost:8080/admin/inventories +``` + +## Adding Permissions + +To add module-specific permissions: + +```php +// In database/seeders/RolePermissionSeeder.php +$permissions = [ + 'inventory.view', + 'inventory.create', + 'inventory.edit', + 'inventory.delete', +]; +``` + +## Best Practices + +1. **Keep modules self-contained** - Each module should be independent +2. **Use namespaces** - Follow the `App\Modules\{ModuleName}` pattern +3. **Test your modules** - Run `php artisan test` after creating +4. **Document changes** - Update module README if you modify structure +5. **Use Filament** - Leverage the admin panel for quick CRUD interfaces diff --git a/src/bootstrap/app.php b/src/bootstrap/app.php index 7b162da..d654276 100644 --- a/src/bootstrap/app.php +++ b/src/bootstrap/app.php @@ -7,6 +7,7 @@ return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', + api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) diff --git a/src/composer.json b/src/composer.json index e8860cc..13a86e0 100644 --- a/src/composer.json +++ b/src/composer.json @@ -9,7 +9,12 @@ "php": "^8.2", "filament/filament": "^3.2", "laravel/framework": "^11.31", - "laravel/tinker": "^2.9" + "laravel/sanctum": "^4.0", + "laravel/tinker": "^2.9", + "owen-it/laravel-auditing": "^14.0", + "spatie/flare-client-php": "^1.10", + "spatie/laravel-ignition": "^2.11", + "spatie/laravel-permission": "^6.24" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/src/composer.lock b/src/composer.lock index 5b9de71..a06361f 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b298a6d9065deaecdbd05c7bc703b231", + "content-hash": "1f84406b8f4e254677813bb043dd37c3", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -2296,6 +2296,69 @@ }, "time": "2026-02-06T12:17:10+00:00" }, + { + "name": "laravel/sanctum", + "version": "v4.3.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", + "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^11.0|^12.0|^13.0", + "illuminate/contracts": "^11.0|^12.0|^13.0", + "illuminate/database": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "php": "^8.2", + "symfony/console": "^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "orchestra/testbench": "^9.15|^10.8|^11.0", + "phpstan/phpstan": "^1.10" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2026-02-07T17:19:31+00:00" + }, { "name": "laravel/serializable-closure", "version": "v2.0.10", @@ -3820,6 +3883,90 @@ ], "time": "2025-09-03T16:03:54+00:00" }, + { + "name": "owen-it/laravel-auditing", + "version": "v14.0.0", + "source": { + "type": "git", + "url": "https://github.com/owen-it/laravel-auditing.git", + "reference": "f92602d1b3f53df29ddd577290e9d735ea707c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/owen-it/laravel-auditing/zipball/f92602d1b3f53df29ddd577290e9d735ea707c53", + "reference": "f92602d1b3f53df29ddd577290e9d735ea707c53", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^11.0|^12.0", + "illuminate/database": "^11.0|^12.0", + "illuminate/filesystem": "^11.0|^12.0", + "php": "^8.2" + }, + "require-dev": { + "mockery/mockery": "^1.5.1", + "orchestra/testbench": "^9.0|^10.0", + "phpunit/phpunit": "^11.0" + }, + "type": "package", + "extra": { + "laravel": { + "providers": [ + "OwenIt\\Auditing\\AuditingServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "v14-dev" + } + }, + "autoload": { + "psr-4": { + "OwenIt\\Auditing\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "AntΓ©rio Vieira", + "email": "anteriovieira@gmail.com" + }, + { + "name": "Raphael FranΓ§a", + "email": "raphaelfrancabsb@gmail.com" + }, + { + "name": "Morten D. Hansen", + "email": "morten@visia.dk" + } + ], + "description": "Audit changes of your Eloquent models in Laravel", + "homepage": "https://laravel-auditing.com", + "keywords": [ + "Accountability", + "Audit", + "auditing", + "changes", + "eloquent", + "history", + "laravel", + "log", + "logging", + "lumen", + "observer", + "record", + "revision", + "tracking" + ], + "support": { + "issues": "https://github.com/owen-it/laravel-auditing/issues", + "source": "https://github.com/owen-it/laravel-auditing" + }, + "time": "2025-02-26T16:40:54+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.5", @@ -4711,6 +4858,70 @@ ], "time": "2025-02-25T09:09:36+00:00" }, + { + "name": "spatie/backtrace", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/backtrace.git", + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110", + "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "ext-json": "*", + "laravel/serializable-closure": "^1.3 || ^2.0", + "phpunit/phpunit": "^9.3 || ^11.4.3", + "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6", + "symfony/var-dumper": "^5.1 || ^6.0 || ^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Backtrace\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van de Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A better backtrace", + "homepage": "https://github.com/spatie/backtrace", + "keywords": [ + "Backtrace", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/backtrace/issues", + "source": "https://github.com/spatie/backtrace/tree/1.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2025-08-26T08:22:30+00:00" + }, { "name": "spatie/color", "version": "1.8.0", @@ -4770,6 +4981,232 @@ ], "time": "2025-02-10T09:22:41+00:00" }, + { + "name": "spatie/error-solutions", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/error-solutions.git", + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "illuminate/broadcasting": "^10.0|^11.0|^12.0", + "illuminate/cache": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "livewire/livewire": "^2.11|^3.5.20", + "openai-php/client": "^0.10.1", + "orchestra/testbench": "8.22.3|^9.0|^10.0", + "pestphp/pest": "^2.20|^3.0", + "phpstan/phpstan": "^2.1", + "psr/simple-cache": "^3.0", + "psr/simple-cache-implementation": "^3.0", + "spatie/ray": "^1.28", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "simple-cache-implementation": "To cache solutions from OpenAI" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Ignition\\": "legacy/ignition", + "Spatie\\ErrorSolutions\\": "src", + "Spatie\\LaravelIgnition\\": "legacy/laravel-ignition" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ruben Van Assche", + "email": "ruben@spatie.be", + "role": "Developer" + } + ], + "description": "This is my package error-solutions", + "homepage": "https://github.com/spatie/error-solutions", + "keywords": [ + "error-solutions", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/error-solutions/issues", + "source": "https://github.com/spatie/error-solutions/tree/1.1.3" + }, + "funding": [ + { + "url": "https://github.com/Spatie", + "type": "github" + } + ], + "time": "2025-02-14T12:29:50+00:00" + }, + { + "name": "spatie/flare-client-php", + "version": "1.10.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/flare-client-php.git", + "reference": "bf1716eb98bd689451b071548ae9e70738dce62f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/bf1716eb98bd689451b071548ae9e70738dce62f", + "reference": "bf1716eb98bd689451b071548ae9e70738dce62f", + "shasum": "" + }, + "require": { + "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^8.0", + "spatie/backtrace": "^1.6.1", + "symfony/http-foundation": "^5.2|^6.0|^7.0", + "symfony/mime": "^5.2|^6.0|^7.0", + "symfony/process": "^5.2|^6.0|^7.0", + "symfony/var-dumper": "^5.2|^6.0|^7.0" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.5.0", + "pestphp/pest": "^1.20|^2.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "spatie/pest-plugin-snapshots": "^1.0|^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.3.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\FlareClient\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Send PHP errors to Flare", + "homepage": "https://github.com/spatie/flare-client-php", + "keywords": [ + "exception", + "flare", + "reporting", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/flare-client-php/issues", + "source": "https://github.com/spatie/flare-client-php/tree/1.10.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-02-14T13:42:06+00:00" + }, + { + "name": "spatie/ignition", + "version": "1.15.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/ignition.git", + "reference": "31f314153020aee5af3537e507fef892ffbf8c85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/ignition/zipball/31f314153020aee5af3537e507fef892ffbf8c85", + "reference": "31f314153020aee5af3537e507fef892ffbf8c85", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.0", + "spatie/error-solutions": "^1.0", + "spatie/flare-client-php": "^1.7", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "require-dev": { + "illuminate/cache": "^9.52|^10.0|^11.0|^12.0", + "mockery/mockery": "^1.4", + "pestphp/pest": "^1.20|^2.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "psr/simple-cache-implementation": "*", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "simple-cache-implementation": "To cache solutions from OpenAI" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Spatie\\Ignition\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Spatie", + "email": "info@spatie.be", + "role": "Developer" + } + ], + "description": "A beautiful error page for PHP applications.", + "homepage": "https://flareapp.io/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], + "support": { + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/spatie/ignition/issues", + "source": "https://github.com/spatie/ignition" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-02-21T14:31:39+00:00" + }, { "name": "spatie/invade", "version": "2.1.0", @@ -4829,6 +5266,98 @@ ], "time": "2024-05-17T09:06:10+00:00" }, + { + "name": "spatie/laravel-ignition", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-ignition.git", + "reference": "11f38d1ff7abc583a61c96bf3c1b03610a69cccd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/11f38d1ff7abc583a61c96bf3c1b03610a69cccd", + "reference": "11f38d1ff7abc583a61c96bf3c1b03610a69cccd", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/support": "^11.0|^12.0|^13.0", + "nesbot/carbon": "^2.72|^3.0", + "php": "^8.2", + "spatie/ignition": "^1.15.1", + "symfony/console": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "require-dev": { + "livewire/livewire": "^3.7.0|^4.0|dev-josh/v3-laravel-13-support", + "mockery/mockery": "^1.6.12", + "openai-php/client": "^0.10.3|^0.19", + "orchestra/testbench": "^v9.16.0|^10.6|^11.0", + "pestphp/pest": "^3.7|^4.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.8", + "vlucas/phpdotenv": "^5.6.2" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "psr/simple-cache-implementation": "Needed to cache solutions from OpenAI" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Flare": "Spatie\\LaravelIgnition\\Facades\\Flare" + }, + "providers": [ + "Spatie\\LaravelIgnition\\IgnitionServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\LaravelIgnition\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Spatie", + "email": "info@spatie.be", + "role": "Developer" + } + ], + "description": "A beautiful error page for Laravel applications.", + "homepage": "https://flareapp.io/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], + "support": { + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/spatie/laravel-ignition/issues", + "source": "https://github.com/spatie/laravel-ignition" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-02-22T19:14:05+00:00" + }, { "name": "spatie/laravel-package-tools", "version": "1.93.0", @@ -4890,6 +5419,89 @@ ], "time": "2026-02-21T12:49:54+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "6.24.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "eefc9d17eba80d023d6bff313f882cb2bcd691a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/eefc9d17eba80d023d6bff313f882cb2bcd691a3", + "reference": "eefc9d17eba80d023d6bff313f882cb2bcd691a3", + "shasum": "" + }, + "require": { + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0|^12.0", + "php": "^8.0" + }, + "require-dev": { + "laravel/passport": "^11.0|^12.0", + "laravel/pint": "^1.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.4|^10.1|^11.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 8.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/6.24.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-02-09T21:10:03+00:00" + }, { "name": "symfony/clock", "version": "v7.4.0", diff --git a/src/config/permission.php b/src/config/permission.php new file mode 100644 index 0000000..f39f6b5 --- /dev/null +++ b/src/config/permission.php @@ -0,0 +1,202 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Spatie\Permission\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Spatie\Permission\Models\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, // default 'role_id', + 'permission_pivot_key' => null, // default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered + * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated + * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. + */ + 'register_octane_reset_listener' => false, + + /* + * Events will fire when a role or permission is assigned/unassigned: + * \Spatie\Permission\Events\RoleAttached + * \Spatie\Permission\Events\RoleDetached + * \Spatie\Permission\Events\PermissionAttached + * \Spatie\Permission\Events\PermissionDetached + * + * To enable, set to true, and then create listeners to watch these events. + */ + 'events_enabled' => false, + + /* + * Teams Feature. + * When set to true the package implements teams using the 'team_foreign_key'. + * If you want the migrations to register the 'team_foreign_key', you must + * set this to true before doing the migration. + * If you already did the migration then you must make a new migration to also + * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' + * (view the latest version of this package's migration file) + */ + + 'teams' => false, + + /* + * The class to use to resolve the permissions team id + */ + 'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class, + + /* + * Passport Client Credentials Grant + * When set to true the package will use Passports Client to check permissions + */ + + 'use_passport_client_credentials' => false, + + /* + * When set to true, the required permission names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + * See documentation to understand supported syntax. + */ + + 'enable_wildcard_permission' => false, + + /* + * The class to use for interpreting wildcard permissions. + * If you need to modify delimiters, override the class and specify its name here. + */ + // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class, + + /* Cache-specific settings */ + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/src/config/sanctum.php b/src/config/sanctum.php new file mode 100644 index 0000000..44527d6 --- /dev/null +++ b/src/config/sanctum.php @@ -0,0 +1,84 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + Sanctum::currentApplicationUrlWithPort(), + // Sanctum::currentRequestHost(), + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. This will override any values set in the token's + | "expires_at" attribute, but first-party sessions are not affected. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Token Prefix + |-------------------------------------------------------------------------- + | + | Sanctum can prefix new tokens in order to take advantage of numerous + | security scanning initiatives maintained by open source platforms + | that notify developers if they commit tokens into repositories. + | + | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning + | + */ + + 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, + 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, + 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, + ], + +]; diff --git a/src/database/migrations/2026_03_09_022522_create_settings_table.php b/src/database/migrations/2026_03_09_022522_create_settings_table.php new file mode 100644 index 0000000..9a7951e --- /dev/null +++ b/src/database/migrations/2026_03_09_022522_create_settings_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('key')->unique(); + $table->text('value')->nullable(); + $table->string('type')->default('string'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('settings'); + } +}; diff --git a/src/database/migrations/2026_03_09_023752_create_permission_tables.php b/src/database/migrations/2026_03_09_023752_create_permission_tables.php new file mode 100644 index 0000000..66ce1f9 --- /dev/null +++ b/src/database/migrations/2026_03_09_023752_create_permission_tables.php @@ -0,0 +1,134 @@ +engine('InnoDB'); + $table->bigIncrements('id'); // permission id + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + // $table->engine('InnoDB'); + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + + }); + + Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +}; diff --git a/src/database/migrations/2026_03_09_024743_create_audits_table.php b/src/database/migrations/2026_03_09_024743_create_audits_table.php new file mode 100644 index 0000000..2aa8573 --- /dev/null +++ b/src/database/migrations/2026_03_09_024743_create_audits_table.php @@ -0,0 +1,27 @@ +id(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('audits'); + } +}; diff --git a/src/database/migrations/2026_03_09_030435_create_personal_access_tokens_table.php b/src/database/migrations/2026_03_09_030435_create_personal_access_tokens_table.php new file mode 100644 index 0000000..40ff706 --- /dev/null +++ b/src/database/migrations/2026_03_09_030435_create_personal_access_tokens_table.php @@ -0,0 +1,33 @@ +id(); + $table->morphs('tokenable'); + $table->text('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable()->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('personal_access_tokens'); + } +}; diff --git a/src/database/seeders/RolePermissionSeeder.php b/src/database/seeders/RolePermissionSeeder.php new file mode 100644 index 0000000..951e70b --- /dev/null +++ b/src/database/seeders/RolePermissionSeeder.php @@ -0,0 +1,42 @@ +forgetCachedPermissions(); + + $permissions = [ + 'users.view', + 'users.create', + 'users.edit', + 'users.delete', + 'settings.manage', + ]; + + foreach ($permissions as $permission) { + Permission::create(['name' => $permission]); + } + + $adminRole = Role::create(['name' => 'admin']); + $adminRole->givePermissionTo(Permission::all()); + + $editorRole = Role::create(['name' => 'editor']); + $editorRole->givePermissionTo(['users.view', 'users.edit']); + + $viewerRole = Role::create(['name' => 'viewer']); + $viewerRole->givePermissionTo(['users.view']); + + $admin = User::where('email', 'admin@example.com')->first(); + if ($admin) { + $admin->assignRole('admin'); + } + } +} diff --git a/src/resources/views/filament/pages/settings.blade.php b/src/resources/views/filament/pages/settings.blade.php new file mode 100644 index 0000000..3554775 --- /dev/null +++ b/src/resources/views/filament/pages/settings.blade.php @@ -0,0 +1,9 @@ + +
+ {{ $this->form }} + + + +
diff --git a/src/resources/views/filament/resources/admin-resource/pages/settings.blade.php b/src/resources/views/filament/resources/admin-resource/pages/settings.blade.php new file mode 100644 index 0000000..cc61477 --- /dev/null +++ b/src/resources/views/filament/resources/admin-resource/pages/settings.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/src/routes/api.php b/src/routes/api.php new file mode 100644 index 0000000..ccc387f --- /dev/null +++ b/src/routes/api.php @@ -0,0 +1,8 @@ +user(); +})->middleware('auth:sanctum');