Compare commits
14 Commits
ManualInst
...
306413ca56
| Author | SHA1 | Date | |
|---|---|---|---|
| 306413ca56 | |||
| b1453ff249 | |||
| ac11580eb3 | |||
| 0d2506f3ef | |||
| 73a4cd8c40 | |||
| e7fcaa35e1 | |||
| 83131d8432 | |||
| 97acc5e8ea | |||
| 7af4f1d14a | |||
| 3fee3917c5 | |||
| ae410ca4da | |||
| a55fafd3a9 | |||
| 1e868b3ac1 | |||
| c5260e652b |
15
.gitignore
vendored
15
.gitignore
vendored
@@ -16,10 +16,21 @@ redis_data/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Laravel (if src is committed)
|
||||
# Laravel - commit most files, ignore generated/local files
|
||||
src/.env
|
||||
src/vendor/
|
||||
src/node_modules/
|
||||
src/.env
|
||||
src/bootstrap/cache/*
|
||||
!src/bootstrap/cache/.gitignore
|
||||
src/storage/*.key
|
||||
src/storage/logs/*
|
||||
!src/storage/logs/.gitkeep
|
||||
src/storage/framework/cache/*
|
||||
!src/storage/framework/cache/.gitignore
|
||||
src/storage/framework/sessions/*
|
||||
!src/storage/framework/sessions/.gitignore
|
||||
src/storage/framework/views/*
|
||||
!src/storage/framework/views/.gitignore
|
||||
src/public/hot
|
||||
src/public/storage
|
||||
src/public/build
|
||||
|
||||
@@ -4,12 +4,17 @@ This document provides context for AI coding assistants working on projects buil
|
||||
|
||||
## Template Overview
|
||||
|
||||
This is a **Laravel Docker Development Template** with:
|
||||
- Docker-based development environment
|
||||
- Production deployment to Ubuntu 24.04 (no Docker)
|
||||
- Modular architecture with Filament admin
|
||||
- Audit trail for all data changes
|
||||
- Multi-database support (MySQL, PostgreSQL, SQLite)
|
||||
This is a **ready-to-use Laravel Docker Development Template** with everything pre-installed:
|
||||
- ✅ Laravel 11 with Breeze authentication (Blade + dark mode)
|
||||
- ✅ Filament v3.3 admin panel with user management
|
||||
- ✅ Pest testing framework
|
||||
- ✅ Laravel Pint code style
|
||||
- ✅ Docker-based development environment
|
||||
- ✅ Production deployment to Ubuntu 24.04 (no Docker)
|
||||
- ✅ Modular architecture
|
||||
- ✅ Multi-database support (MySQL, PostgreSQL, SQLite)
|
||||
|
||||
**Setup time:** 2 minutes - just run `./setup.sh` and everything is ready!
|
||||
|
||||
## Technology Stack
|
||||
|
||||
@@ -20,12 +25,51 @@ This is a **Laravel Docker Development Template** with:
|
||||
| **Admin** | Filament 3.x |
|
||||
| **Database** | MySQL 8 / PostgreSQL 15 / SQLite |
|
||||
| **Cache/Queue** | Redis |
|
||||
| **Auth** | Laravel Breeze or Jetstream (Livewire only) |
|
||||
| **Permissions** | spatie/laravel-permission |
|
||||
| **Audit** | owen-it/laravel-auditing |
|
||||
| **Error Tracking** | spatie/laravel-flare + spatie/laravel-ignition |
|
||||
| **Auth** | Laravel Breeze (Blade + Livewire) - PRE-INSTALLED |
|
||||
| **Testing** | Pest - PRE-INSTALLED |
|
||||
| **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:
|
||||
|
||||
331
DEBUGGING.md
Normal file
331
DEBUGGING.md
Normal file
@@ -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. 🔍
|
||||
272
FEATURES.md
Normal file
272
FEATURES.md
Normal file
@@ -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
|
||||
@@ -1,112 +1,129 @@
|
||||
# Getting Started
|
||||
|
||||
This guide walks you through setting up a new Laravel project using this template.
|
||||
This guide walks you through setting up your Laravel development environment using this template.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker & Docker Compose
|
||||
- Git
|
||||
- Make (optional, but recommended)
|
||||
|
||||
## Quick Start (5 minutes)
|
||||
## Quick Start (2 minutes)
|
||||
|
||||
**Everything is pre-installed!** Just clone and run:
|
||||
|
||||
```bash
|
||||
# 1. Clone the template
|
||||
git clone https://github.com/your-repo/Laravel-Docker-Dev-Template.git my-project
|
||||
cd my-project
|
||||
|
||||
# 2. Copy environment file
|
||||
cp .env.example .env
|
||||
# 2. Run setup (MySQL is default)
|
||||
./setup.sh # Linux/Mac
|
||||
setup.bat # Windows
|
||||
|
||||
# 3. Choose your database and start
|
||||
make install DB=mysql # or: pgsql, sqlite
|
||||
# Or choose a different database:
|
||||
./setup.sh pgsql # PostgreSQL
|
||||
./setup.sh sqlite # SQLite
|
||||
|
||||
# 4. Run setup scripts (interactive)
|
||||
make setup-tools # Flare, Pint, error pages
|
||||
make setup-laravel # Auth, Filament, modules, audit trail
|
||||
|
||||
# 5. Access your app
|
||||
# 3. Access your app
|
||||
# Laravel: http://localhost:8080
|
||||
# Admin: http://localhost:8080/admin
|
||||
# Admin: http://localhost:8080/admin (admin@example.com / password)
|
||||
# Mail: http://localhost:8025
|
||||
```
|
||||
|
||||
**That's it!** You now have a fully working Laravel application with:
|
||||
- ✅ Authentication (login, register, password reset)
|
||||
- ✅ Admin panel with user management
|
||||
- ✅ Testing framework (Pest)
|
||||
- ✅ Code style (Pint)
|
||||
- ✅ Email testing (Mailpit)
|
||||
|
||||
## What's Pre-Installed
|
||||
|
||||
This template comes with everything configured and ready to use:
|
||||
|
||||
### Core Framework
|
||||
- **Laravel 11** - Latest version with all features
|
||||
- **Laravel Breeze** - Authentication scaffolding (Blade + dark mode)
|
||||
- **Livewire** - Reactive components without JavaScript
|
||||
|
||||
### 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 (OPcache enabled)
|
||||
- **Queue Workers** - Background job processing (optional)
|
||||
- **Scheduler** - Task scheduling (optional)
|
||||
|
||||
## Step-by-Step Setup
|
||||
|
||||
### 1. Clone and Configure
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-repo/Laravel-Docker-Dev-Template.git my-project
|
||||
cd my-project
|
||||
|
||||
# Remove template git history and start fresh
|
||||
# Optional: Remove template git history and start fresh
|
||||
rm -rf .git
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
```
|
||||
|
||||
### 2. Choose Database
|
||||
### 2. Choose Your Database
|
||||
|
||||
| Database | Best For | Command |
|
||||
|----------|----------|---------|
|
||||
| **MySQL** | Most projects, production parity | `make install DB=mysql` |
|
||||
| **PostgreSQL** | Advanced features, JSON, full-text search | `make install DB=pgsql` |
|
||||
| **SQLite** | Simple apps, quick prototyping | `make install DB=sqlite` |
|
||||
| **MySQL** | Most projects, production parity | `./setup.sh mysql` |
|
||||
| **PostgreSQL** | Advanced features, JSON, full-text search | `./setup.sh pgsql` |
|
||||
| **SQLite** | Simple apps, quick prototyping | `./setup.sh sqlite` |
|
||||
|
||||
### 3. Install Laravel
|
||||
### 3. Run Setup Script
|
||||
|
||||
```bash
|
||||
# Start containers and create Laravel project
|
||||
make install DB=mysql
|
||||
|
||||
# This will:
|
||||
# - Build Docker images
|
||||
# - Start containers
|
||||
# - Run composer create-project laravel/laravel
|
||||
# - Copy appropriate .env file
|
||||
# - Generate app key
|
||||
# - Run initial migrations
|
||||
./setup.sh mysql # Linux/Mac
|
||||
setup.bat mysql # Windows
|
||||
```
|
||||
|
||||
### 4. Run Setup Scripts
|
||||
The script will:
|
||||
- ✅ Configure environment for chosen database
|
||||
- ✅ Install composer dependencies
|
||||
- ✅ Build and start Docker containers
|
||||
- ✅ Run database migrations
|
||||
- ✅ Create admin user automatically
|
||||
|
||||
#### Post-Install Tools
|
||||
### 4. Start Developing
|
||||
|
||||
```bash
|
||||
make setup-tools
|
||||
```
|
||||
Your application is now ready! The setup script created an admin user for you:
|
||||
|
||||
Installs:
|
||||
- ✅ Spatie Ignition (dev error pages)
|
||||
- ✅ Spatie Flare (production error tracking)
|
||||
- ✅ Laravel Pint (code style)
|
||||
- ✅ Custom error pages (404, 500, 503)
|
||||
- ❓ Laravel Telescope (optional debugging)
|
||||
**Admin Login:**
|
||||
- Email: `admin@example.com`
|
||||
- Password: `password`
|
||||
|
||||
#### Laravel Base Setup
|
||||
**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/*
|
||||
|
||||
```bash
|
||||
make setup-laravel
|
||||
```
|
||||
|
||||
Interactive prompts for:
|
||||
|
||||
1. **Authentication** - Choose one:
|
||||
- Breeze + Blade (recommended)
|
||||
- Breeze + Livewire
|
||||
- Breeze API only
|
||||
- Jetstream + Livewire
|
||||
|
||||
2. **Filament Admin** - User management dashboard
|
||||
|
||||
3. **Audit Trail** - Track all data changes
|
||||
|
||||
4. **Module System** - Modular architecture
|
||||
|
||||
5. **API (Sanctum)** - Token authentication
|
||||
|
||||
6. **Security Middleware** - HTTPS, headers
|
||||
|
||||
### 5. Create Your First Module
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
# Shell into container
|
||||
@@ -196,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
|
||||
@@ -257,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
|
||||
@@ -298,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
|
||||
|
||||
479
PRODUCTION_DEPLOYMENT.md
Normal file
479
PRODUCTION_DEPLOYMENT.md
Normal file
@@ -0,0 +1,479 @@
|
||||
# Production Deployment Guide
|
||||
|
||||
**Target**: Ubuntu 24.04 with Apache/Nginx + PHP-FPM (NO Docker)
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites on Production Server
|
||||
|
||||
### 1. Install Required Software
|
||||
|
||||
```bash
|
||||
# Update system
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
# Install PHP 8.3+ and extensions
|
||||
sudo apt install -y php8.3 php8.3-fpm php8.3-cli php8.3-common \
|
||||
php8.3-mysql php8.3-pgsql php8.3-sqlite3 \
|
||||
php8.3-redis php8.3-curl php8.3-mbstring php8.3-xml \
|
||||
php8.3-zip php8.3-bcmath php8.3-gd php8.3-intl
|
||||
|
||||
# Install web server (choose one)
|
||||
sudo apt install -y apache2 # OR nginx
|
||||
|
||||
# Install database (choose one)
|
||||
sudo apt install -y mysql-server # OR postgresql
|
||||
|
||||
# Install Redis
|
||||
sudo apt install -y redis-server
|
||||
|
||||
# Install Composer
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
sudo mv composer.phar /usr/local/bin/composer
|
||||
|
||||
# Install Node.js (for frontend assets)
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt install -y nodejs
|
||||
```
|
||||
|
||||
### 2. Verify PHP Extensions
|
||||
|
||||
```bash
|
||||
php -m | grep -E "redis|pdo_mysql|mbstring|xml|curl|zip|bcmath|gd"
|
||||
```
|
||||
|
||||
All should be listed. If not, install missing extensions.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Steps
|
||||
|
||||
### 1. Clone Repository
|
||||
|
||||
```bash
|
||||
cd /var/www
|
||||
sudo git clone https://your-repo-url.git your-domain.com
|
||||
cd your-domain.com/src
|
||||
```
|
||||
|
||||
### 2. Set Permissions
|
||||
|
||||
```bash
|
||||
sudo chown -R www-data:www-data /var/www/your-domain.com
|
||||
sudo chmod -R 775 /var/www/your-domain.com/src/storage
|
||||
sudo chmod -R 775 /var/www/your-domain.com/src/bootstrap/cache
|
||||
```
|
||||
|
||||
### 3. Install Dependencies
|
||||
|
||||
```bash
|
||||
# Composer dependencies
|
||||
composer install --no-dev --optimize-autoloader
|
||||
|
||||
# Node dependencies and build assets
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 4. Configure Environment
|
||||
|
||||
```bash
|
||||
# Copy appropriate .env template
|
||||
cp .env.mysql .env # or .env.pgsql or .env.sqlite
|
||||
|
||||
# Edit .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
**Required .env settings:**
|
||||
```env
|
||||
APP_NAME="Your App Name"
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_URL=https://your-domain.com
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=your_database
|
||||
DB_USERNAME=your_user
|
||||
DB_PASSWORD=your_password
|
||||
|
||||
CACHE_STORE=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
SESSION_DRIVER=database
|
||||
SESSION_DOMAIN=.your-domain.com
|
||||
SESSION_SECURE_COOKIE=true
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=your-smtp-host
|
||||
MAIL_PORT=587
|
||||
MAIL_USERNAME=your-email
|
||||
MAIL_PASSWORD=your-password
|
||||
MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS=noreply@your-domain.com
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
```
|
||||
|
||||
### 5. Generate Application Key
|
||||
|
||||
```bash
|
||||
php artisan key:generate --force
|
||||
```
|
||||
|
||||
### 6. Run Migrations and Seeders
|
||||
|
||||
```bash
|
||||
# Run migrations
|
||||
php artisan migrate --force
|
||||
|
||||
# CRITICAL: Run seeders to create roles, permissions, and admin user
|
||||
php artisan db:seed --force
|
||||
```
|
||||
|
||||
This creates:
|
||||
- **Admin user**: admin@example.com / password
|
||||
- **Roles**: admin, editor, viewer
|
||||
- **Permissions**: users.view, users.create, users.edit, users.delete, settings.manage
|
||||
|
||||
### 7. Optimize for Production
|
||||
|
||||
```bash
|
||||
# Cache configuration
|
||||
php artisan config:cache
|
||||
|
||||
# Cache routes
|
||||
php artisan route:cache
|
||||
|
||||
# Cache views
|
||||
php artisan view:cache
|
||||
|
||||
# Optimize autoloader
|
||||
composer dump-autoload --optimize
|
||||
```
|
||||
|
||||
### 8. Create Storage Link
|
||||
|
||||
```bash
|
||||
php artisan storage:link
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Web Server Configuration
|
||||
|
||||
### Option A: Apache with Virtual Host
|
||||
|
||||
Create `/etc/apache2/sites-available/your-domain.com.conf`:
|
||||
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
ServerName your-domain.com
|
||||
ServerAlias www.your-domain.com
|
||||
DocumentRoot /var/www/your-domain.com/src/public
|
||||
|
||||
<Directory /var/www/your-domain.com/src/public>
|
||||
Options -Indexes +FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/your-domain.com-error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/your-domain.com-access.log combined
|
||||
|
||||
# PHP-FPM
|
||||
<FilesMatch \.php$>
|
||||
SetHandler "proxy:unix:/var/run/php/php8.3-fpm.sock|fcgi://localhost"
|
||||
</FilesMatch>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
Enable site and modules:
|
||||
```bash
|
||||
sudo a2enmod rewrite proxy_fcgi setenvif
|
||||
sudo a2enconf php8.3-fpm
|
||||
sudo a2ensite your-domain.com.conf
|
||||
sudo systemctl restart apache2
|
||||
```
|
||||
|
||||
### Option B: Nginx with Server Block
|
||||
|
||||
Create `/etc/nginx/sites-available/your-domain.com`:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name your-domain.com www.your-domain.com;
|
||||
root /var/www/your-domain.com/src/public;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
error_page 404 /index.php;
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known).* {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Enable site:
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/your-domain.com /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SSL Certificate (Let's Encrypt)
|
||||
|
||||
```bash
|
||||
# Install Certbot
|
||||
sudo apt install -y certbot python3-certbot-apache # For Apache
|
||||
# OR
|
||||
sudo apt install -y certbot python3-certbot-nginx # For Nginx
|
||||
|
||||
# Get certificate
|
||||
sudo certbot --apache -d your-domain.com -d www.your-domain.com # Apache
|
||||
# OR
|
||||
sudo certbot --nginx -d your-domain.com -d www.your-domain.com # Nginx
|
||||
|
||||
# Auto-renewal is set up automatically
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Queue Worker Setup (Optional but Recommended)
|
||||
|
||||
Create `/etc/systemd/system/laravel-queue.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Laravel Queue Worker
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
Group=www-data
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/php /var/www/your-domain.com/src/artisan queue:work --sleep=3 --tries=3 --max-time=3600
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Enable and start:
|
||||
```bash
|
||||
sudo systemctl enable laravel-queue
|
||||
sudo systemctl start laravel-queue
|
||||
sudo systemctl status laravel-queue
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scheduler Setup
|
||||
|
||||
Add to crontab:
|
||||
```bash
|
||||
sudo crontab -e -u www-data
|
||||
```
|
||||
|
||||
Add this line:
|
||||
```
|
||||
* * * * * cd /var/www/your-domain.com/src && php artisan schedule:run >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Post-Deployment Checklist
|
||||
|
||||
- [ ] PHP Redis extension installed: `php -m | grep redis`
|
||||
- [ ] Database migrations run: `php artisan migrate --force`
|
||||
- [ ] **Database seeded**: `php artisan db:seed --force` ✅ CRITICAL
|
||||
- [ ] Storage permissions set: `chmod -R 775 storage bootstrap/cache`
|
||||
- [ ] Storage link created: `php artisan storage:link`
|
||||
- [ ] Config cached: `php artisan config:cache`
|
||||
- [ ] Routes cached: `php artisan route:cache`
|
||||
- [ ] Views cached: `php artisan view:cache`
|
||||
- [ ] SSL certificate installed
|
||||
- [ ] Queue worker running (if using queues)
|
||||
- [ ] Scheduler configured (if using scheduled tasks)
|
||||
- [ ] Admin user created and can login at `/admin`
|
||||
- [ ] `.env` has `APP_DEBUG=false` and `APP_ENV=production`
|
||||
|
||||
---
|
||||
|
||||
## Access Your Application
|
||||
|
||||
- **Public Site**: https://your-domain.com
|
||||
- **Admin Panel**: https://your-domain.com/admin
|
||||
- **Admin Login**: admin@example.com / password
|
||||
|
||||
**⚠️ IMPORTANT**: Change the default admin password immediately after first login!
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 500 Error - Check Logs
|
||||
|
||||
```bash
|
||||
# Laravel logs
|
||||
tail -f /var/www/your-domain.com/src/storage/logs/laravel.log
|
||||
|
||||
# Apache logs
|
||||
sudo tail -f /var/log/apache2/your-domain.com-error.log
|
||||
|
||||
# Nginx logs
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
|
||||
# PHP-FPM logs
|
||||
sudo tail -f /var/log/php8.3-fpm.log
|
||||
```
|
||||
|
||||
### Class "Redis" not found
|
||||
|
||||
```bash
|
||||
# Install PHP Redis extension
|
||||
sudo apt install php8.3-redis
|
||||
|
||||
# Restart PHP-FPM
|
||||
sudo systemctl restart php8.3-fpm
|
||||
|
||||
# Restart web server
|
||||
sudo systemctl restart apache2 # or nginx
|
||||
```
|
||||
|
||||
### 419 Page Expired (CSRF)
|
||||
|
||||
Check `.env`:
|
||||
```env
|
||||
SESSION_DOMAIN=.your-domain.com
|
||||
SESSION_SECURE_COOKIE=true
|
||||
APP_URL=https://your-domain.com
|
||||
```
|
||||
|
||||
Clear cache:
|
||||
```bash
|
||||
php artisan config:clear
|
||||
php artisan cache:clear
|
||||
```
|
||||
|
||||
### Roles Don't Exist
|
||||
|
||||
```bash
|
||||
# Run the seeder
|
||||
php artisan db:seed --class=RolePermissionSeeder
|
||||
|
||||
# Or run all seeders
|
||||
php artisan db:seed --force
|
||||
```
|
||||
|
||||
### Permission Denied Errors
|
||||
|
||||
```bash
|
||||
sudo chown -R www-data:www-data /var/www/your-domain.com
|
||||
sudo chmod -R 775 /var/www/your-domain.com/src/storage
|
||||
sudo chmod -R 775 /var/www/your-domain.com/src/bootstrap/cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Updating the Application
|
||||
|
||||
```bash
|
||||
cd /var/www/your-domain.com
|
||||
|
||||
# Pull latest code
|
||||
sudo -u www-data git pull
|
||||
|
||||
# Update dependencies
|
||||
cd src
|
||||
composer install --no-dev --optimize-autoloader
|
||||
npm install && npm run build
|
||||
|
||||
# Run migrations
|
||||
php artisan migrate --force
|
||||
|
||||
# Clear and recache
|
||||
php artisan config:clear
|
||||
php artisan cache:clear
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
|
||||
# Restart queue worker
|
||||
sudo systemctl restart laravel-queue
|
||||
|
||||
# Restart web server
|
||||
sudo systemctl restart apache2 # or nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
1. **Change default admin password** immediately
|
||||
2. **Set up firewall**: `sudo ufw enable && sudo ufw allow 80,443/tcp`
|
||||
3. **Disable directory listing** in web server config
|
||||
4. **Keep system updated**: `sudo apt update && sudo apt upgrade`
|
||||
5. **Use strong database passwords**
|
||||
6. **Enable fail2ban**: `sudo apt install fail2ban`
|
||||
7. **Regular backups** of database and uploaded files
|
||||
8. **Monitor logs** for suspicious activity
|
||||
|
||||
---
|
||||
|
||||
## Backup Strategy
|
||||
|
||||
### Database Backup
|
||||
|
||||
```bash
|
||||
# MySQL
|
||||
mysqldump -u username -p database_name > backup_$(date +%Y%m%d).sql
|
||||
|
||||
# PostgreSQL
|
||||
pg_dump -U username database_name > backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
### Files Backup
|
||||
|
||||
```bash
|
||||
# Backup storage directory
|
||||
tar -czf storage_backup_$(date +%Y%m%d).tar.gz /var/www/your-domain.com/src/storage
|
||||
```
|
||||
|
||||
### Automated Backups
|
||||
|
||||
Add to crontab:
|
||||
```bash
|
||||
0 2 * * * /path/to/backup-script.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Remember**: The template is designed for quick local development with Docker. Production deployment requires proper server setup and security hardening.
|
||||
108
README.md
108
README.md
@@ -101,60 +101,90 @@ This template supports three database engines via Docker Compose profiles:
|
||||
|
||||
### Prerequisites
|
||||
- Docker & Docker Compose
|
||||
- Make (optional, for convenience commands)
|
||||
|
||||
### Setup
|
||||
### One-Command Setup
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repo-url> my-laravel-app
|
||||
cd my-laravel-app
|
||||
|
||||
# Run setup script (MySQL is default)
|
||||
./setup.sh # Linux/Mac
|
||||
setup.bat # Windows
|
||||
|
||||
# Or specify database:
|
||||
./setup.sh mysql # MySQL (default)
|
||||
./setup.sh pgsql # PostgreSQL
|
||||
./setup.sh sqlite # SQLite
|
||||
```
|
||||
|
||||
**That's it!** The script will:
|
||||
- ✅ Configure environment for your database
|
||||
- ✅ Install composer dependencies
|
||||
- ✅ Build and start Docker containers
|
||||
- ✅ Run migrations
|
||||
- ✅ Create admin user (admin@example.com / password)
|
||||
|
||||
**Everything is pre-installed:**
|
||||
- ✅ Laravel 11 with Breeze authentication
|
||||
- ✅ Filament admin panel with user management
|
||||
- ✅ Pest testing framework
|
||||
- ✅ Laravel Pint code style
|
||||
- ✅ Queue workers & scheduler (optional)
|
||||
|
||||
**Access your app:**
|
||||
- Laravel App: http://localhost:8080
|
||||
- Admin Panel: http://localhost:8080/admin
|
||||
- Mailpit: http://localhost:8025
|
||||
|
||||
**Admin Login:**
|
||||
- Email: admin@example.com
|
||||
- Password: password
|
||||
|
||||
### Manual Setup (Alternative)
|
||||
|
||||
If you prefer manual control:
|
||||
|
||||
1. **Clone and configure**
|
||||
```bash
|
||||
git clone <repo-url> my-laravel-app
|
||||
cd my-laravel-app
|
||||
```
|
||||
|
||||
2. **Copy environment file**
|
||||
2. **Build containers**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
3. **Install Laravel with your preferred database**
|
||||
```bash
|
||||
# With MySQL (default)
|
||||
make install DB=mysql
|
||||
|
||||
# With PostgreSQL
|
||||
make install DB=pgsql
|
||||
|
||||
# With SQLite
|
||||
make install DB=sqlite
|
||||
|
||||
# Or without Make:
|
||||
docker-compose build
|
||||
docker-compose --profile mysql run --rm app composer create-project laravel/laravel .
|
||||
cp src/.env.mysql src/.env
|
||||
```
|
||||
|
||||
4. **Start the development environment**
|
||||
3. **Install Laravel**
|
||||
```bash
|
||||
# Start with your chosen database
|
||||
make up DB=mysql # or pgsql, sqlite
|
||||
|
||||
# Or: docker-compose --profile mysql up -d
|
||||
docker-compose --profile mysql run --rm app composer create-project laravel/laravel:^11.0 /tmp/new
|
||||
docker-compose --profile mysql run --rm app sh -c "cp -r /tmp/new/. /var/www/html/ && rm -rf /tmp/new"
|
||||
```
|
||||
|
||||
5. **Access your application**
|
||||
- Laravel App: http://localhost:8080
|
||||
- Mailpit: http://localhost:8025
|
||||
|
||||
6. **Run setup scripts**
|
||||
4. **Configure environment**
|
||||
```bash
|
||||
# Install Flare, Pint, error pages
|
||||
make setup-tools
|
||||
|
||||
# Configure auth, API, middleware (interactive)
|
||||
make setup-laravel
|
||||
|
||||
# Or run both
|
||||
make setup-all
|
||||
cp src/.env.mysql src/.env # For MySQL
|
||||
# OR
|
||||
cp src/.env.pgsql src/.env # For PostgreSQL
|
||||
# OR
|
||||
cp src/.env.sqlite src/.env # For SQLite
|
||||
```
|
||||
|
||||
5. **Start containers**
|
||||
```bash
|
||||
docker-compose --profile mysql up -d
|
||||
```
|
||||
|
||||
6. **Run migrations**
|
||||
```bash
|
||||
docker-compose exec app php artisan migrate --force
|
||||
```
|
||||
|
||||
7. **Run setup scripts (optional)**
|
||||
```bash
|
||||
docker-compose exec app bash scripts/laravel-setup.sh
|
||||
```
|
||||
|
||||
### Common Commands
|
||||
|
||||
420
TEST_SETUP.md
Normal file
420
TEST_SETUP.md
Normal file
@@ -0,0 +1,420 @@
|
||||
# Testing the 2-Minute Setup
|
||||
|
||||
This document verifies that the Laravel Docker Dev Template works as advertised.
|
||||
|
||||
---
|
||||
|
||||
## Test 1: Fresh Local Setup (Docker)
|
||||
|
||||
### Prerequisites
|
||||
- Docker Desktop running
|
||||
- Git installed
|
||||
- No existing containers from this project
|
||||
|
||||
### Steps
|
||||
|
||||
```bash
|
||||
# 1. Clone to a fresh directory
|
||||
cd /tmp # or C:\temp on Windows
|
||||
git clone https://git.radapps.co.za/theradcoza/Laravel-Docker-Dev-Template.git test-setup
|
||||
cd test-setup
|
||||
|
||||
# 2. Run setup (should take ~2 minutes)
|
||||
./setup.sh mysql # or setup.bat mysql on Windows
|
||||
|
||||
# 3. Verify containers are running
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
- `app` container: Up
|
||||
- `nginx` container: Up
|
||||
- `mysql` container: Up
|
||||
- `redis` container: Up
|
||||
- `mailpit` container: Up
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
**Database:**
|
||||
```bash
|
||||
docker-compose exec app php artisan tinker
|
||||
```
|
||||
```php
|
||||
// Check admin user exists
|
||||
App\Models\User::where('email', 'admin@example.com')->exists(); // Should return: true
|
||||
|
||||
// Check roles exist
|
||||
Spatie\Permission\Models\Role::count(); // Should return: 3
|
||||
|
||||
// Check permissions exist
|
||||
Spatie\Permission\Models\Permission::count(); // Should return: 5
|
||||
|
||||
// Check admin has admin role
|
||||
$admin = App\Models\User::where('email', 'admin@example.com')->first();
|
||||
$admin->hasRole('admin'); // Should return: true
|
||||
|
||||
exit
|
||||
```
|
||||
|
||||
**Web Access:**
|
||||
1. Visit http://localhost:8080 - Should show Laravel welcome page
|
||||
2. Visit http://localhost:8080/admin - Should show Filament login
|
||||
3. Login with:
|
||||
- Email: admin@example.com
|
||||
- Password: password
|
||||
4. Should successfully login and see admin dashboard
|
||||
5. Should see "Users" menu item
|
||||
6. Should see "Settings" menu item
|
||||
|
||||
**Settings Page:**
|
||||
1. Click "Settings" in admin panel
|
||||
2. Should load without errors
|
||||
3. Should show form fields:
|
||||
- Site Name
|
||||
- Site Logo (file upload)
|
||||
- Primary Color
|
||||
- Secondary Color
|
||||
- Accent Color
|
||||
- Site Description
|
||||
- Contact Email
|
||||
- Maintenance Mode (toggle)
|
||||
4. Try saving - should show success notification
|
||||
|
||||
**Users Management:**
|
||||
1. Click "Users" in admin panel
|
||||
2. Should see admin user in list
|
||||
3. Click "New User"
|
||||
4. Create test user
|
||||
5. Should save successfully
|
||||
|
||||
**Mailpit:**
|
||||
1. Visit http://localhost:8025
|
||||
2. Should show Mailpit interface
|
||||
3. Send a test email (e.g., password reset)
|
||||
4. Email should appear in Mailpit
|
||||
|
||||
### Success Criteria
|
||||
|
||||
✅ Setup completes in under 3 minutes
|
||||
✅ All containers start successfully
|
||||
✅ Admin user exists with correct credentials
|
||||
✅ 3 roles created (admin, editor, viewer)
|
||||
✅ 5 permissions created
|
||||
✅ Admin login works
|
||||
✅ Settings page loads and saves
|
||||
✅ Users management works
|
||||
✅ No 500 errors
|
||||
✅ No 419 CSRF errors
|
||||
✅ No "Class Redis not found" errors
|
||||
|
||||
---
|
||||
|
||||
## Test 2: Production Deployment Simulation
|
||||
|
||||
### Prerequisites
|
||||
- Ubuntu 24.04 server (or VM)
|
||||
- Root or sudo access
|
||||
- Clean database
|
||||
|
||||
### Steps
|
||||
|
||||
Follow `PRODUCTION_DEPLOYMENT.md` exactly:
|
||||
|
||||
```bash
|
||||
# 1. Install PHP and extensions
|
||||
sudo apt update
|
||||
sudo apt install -y php8.3 php8.3-fpm php8.3-cli php8.3-mysql \
|
||||
php8.3-redis php8.3-curl php8.3-mbstring php8.3-xml \
|
||||
php8.3-zip php8.3-bcmath php8.3-gd
|
||||
|
||||
# 2. Verify Redis extension
|
||||
php -m | grep redis # Should output: redis
|
||||
|
||||
# 3. Clone repo
|
||||
cd /var/www
|
||||
sudo git clone https://git.radapps.co.za/theradcoza/Laravel-Docker-Dev-Template.git test-app
|
||||
cd test-app/src
|
||||
|
||||
# 4. Install dependencies
|
||||
composer install --no-dev --optimize-autoloader
|
||||
npm install && npm run build
|
||||
|
||||
# 5. Configure environment
|
||||
cp .env.mysql .env
|
||||
nano .env # Set database credentials, APP_URL, etc.
|
||||
|
||||
# 6. Generate key
|
||||
php artisan key:generate --force
|
||||
|
||||
# 7. Run migrations
|
||||
php artisan migrate --force
|
||||
|
||||
# 8. CRITICAL: Run seeders
|
||||
php artisan db:seed --force
|
||||
|
||||
# 9. Set permissions
|
||||
sudo chown -R www-data:www-data storage bootstrap/cache
|
||||
sudo chmod -R 775 storage bootstrap/cache
|
||||
|
||||
# 10. Create storage link
|
||||
php artisan storage:link
|
||||
|
||||
# 11. Cache config
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
```
|
||||
|
||||
### Verification
|
||||
|
||||
```bash
|
||||
# Check admin user exists
|
||||
php artisan tinker
|
||||
```
|
||||
```php
|
||||
App\Models\User::where('email', 'admin@example.com')->exists(); // true
|
||||
exit
|
||||
```
|
||||
|
||||
**Configure web server** (Apache or Nginx) per `PRODUCTION_DEPLOYMENT.md`
|
||||
|
||||
**Test web access:**
|
||||
1. Visit https://your-domain.com
|
||||
2. Visit https://your-domain.com/admin
|
||||
3. Login with admin@example.com / password
|
||||
4. Should work without errors
|
||||
|
||||
### Success Criteria
|
||||
|
||||
✅ PHP Redis extension installed
|
||||
✅ Migrations run successfully
|
||||
✅ Seeders run successfully
|
||||
✅ Admin user created
|
||||
✅ Roles and permissions created
|
||||
✅ No "Class Redis not found" errors
|
||||
✅ No 419 CSRF errors
|
||||
✅ Admin login works
|
||||
✅ Settings page works
|
||||
✅ No 500 errors
|
||||
|
||||
---
|
||||
|
||||
## Test 3: Common Error Scenarios
|
||||
|
||||
### Scenario 1: Missing Redis Extension
|
||||
|
||||
**Simulate:**
|
||||
```bash
|
||||
# On production server, DON'T install php-redis
|
||||
composer install
|
||||
php artisan migrate
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- Should fail with clear error message
|
||||
- `PRODUCTION_DEPLOYMENT.md` should have solution
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
sudo apt install php8.3-redis
|
||||
sudo systemctl restart php8.3-fpm
|
||||
```
|
||||
|
||||
### Scenario 2: Forgot to Run Seeders
|
||||
|
||||
**Simulate:**
|
||||
```bash
|
||||
php artisan migrate --force
|
||||
# Skip: php artisan db:seed --force
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- Admin user doesn't exist
|
||||
- Roles don't exist
|
||||
- Can't login
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
php artisan db:seed --force
|
||||
```
|
||||
|
||||
### Scenario 3: Permission Issues
|
||||
|
||||
**Simulate:**
|
||||
```bash
|
||||
# Wrong permissions on storage
|
||||
sudo chmod -R 755 storage
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- 500 errors
|
||||
- Can't write logs
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
sudo chown -R www-data:www-data storage bootstrap/cache
|
||||
sudo chmod -R 775 storage bootstrap/cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automated Test Script
|
||||
|
||||
Create `test-setup.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
echo "🧪 Testing Laravel Docker Dev Template Setup..."
|
||||
echo ""
|
||||
|
||||
# Test 1: Check containers
|
||||
echo "✓ Checking containers..."
|
||||
if ! docker-compose ps | grep -q "Up"; then
|
||||
echo "❌ Containers not running"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Containers running"
|
||||
|
||||
# Test 2: Check admin user
|
||||
echo "✓ Checking admin user..."
|
||||
ADMIN_EXISTS=$(docker-compose exec -T app php artisan tinker --execute="echo App\Models\User::where('email', 'admin@example.com')->exists() ? 'true' : 'false';")
|
||||
if [[ ! "$ADMIN_EXISTS" =~ "true" ]]; then
|
||||
echo "❌ Admin user not found"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Admin user exists"
|
||||
|
||||
# Test 3: Check roles
|
||||
echo "✓ Checking roles..."
|
||||
ROLE_COUNT=$(docker-compose exec -T app php artisan tinker --execute="echo Spatie\Permission\Models\Role::count();")
|
||||
if [[ ! "$ROLE_COUNT" =~ "3" ]]; then
|
||||
echo "❌ Expected 3 roles, found: $ROLE_COUNT"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ 3 roles created"
|
||||
|
||||
# Test 4: Check permissions
|
||||
echo "✓ Checking permissions..."
|
||||
PERM_COUNT=$(docker-compose exec -T app php artisan tinker --execute="echo Spatie\Permission\Models\Permission::count();")
|
||||
if [[ ! "$PERM_COUNT" =~ "5" ]]; then
|
||||
echo "❌ Expected 5 permissions, found: $PERM_COUNT"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ 5 permissions created"
|
||||
|
||||
# Test 5: Check admin has admin role
|
||||
echo "✓ Checking admin role assignment..."
|
||||
HAS_ROLE=$(docker-compose exec -T app php artisan tinker --execute="echo App\Models\User::where('email', 'admin@example.com')->first()->hasRole('admin') ? 'true' : 'false';")
|
||||
if [[ ! "$HAS_ROLE" =~ "true" ]]; then
|
||||
echo "❌ Admin user doesn't have admin role"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Admin has admin role"
|
||||
|
||||
# Test 6: Check web access
|
||||
echo "✓ Checking web access..."
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080)
|
||||
if [ "$HTTP_CODE" != "200" ]; then
|
||||
echo "❌ Web server not responding (HTTP $HTTP_CODE)"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Web server responding"
|
||||
|
||||
# Test 7: Check admin panel
|
||||
echo "✓ Checking admin panel..."
|
||||
ADMIN_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/admin)
|
||||
if [ "$ADMIN_CODE" != "200" ]; then
|
||||
echo "❌ Admin panel not responding (HTTP $ADMIN_CODE)"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Admin panel responding"
|
||||
|
||||
echo ""
|
||||
echo "🎉 All tests passed!"
|
||||
echo ""
|
||||
echo "Admin Login:"
|
||||
echo " URL: http://localhost:8080/admin"
|
||||
echo " Email: admin@example.com"
|
||||
echo " Password: password"
|
||||
```
|
||||
|
||||
Make executable:
|
||||
```bash
|
||||
chmod +x test-setup.sh
|
||||
```
|
||||
|
||||
Run:
|
||||
```bash
|
||||
./test-setup.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Test (30 seconds)
|
||||
|
||||
If you just want to verify the basics work:
|
||||
|
||||
```bash
|
||||
# After running setup.sh
|
||||
docker-compose exec app php artisan tinker --execute="
|
||||
echo 'Admin exists: ' . (App\Models\User::where('email', 'admin@example.com')->exists() ? 'YES' : 'NO') . PHP_EOL;
|
||||
echo 'Roles count: ' . Spatie\Permission\Models\Role::count() . PHP_EOL;
|
||||
echo 'Permissions count: ' . Spatie\Permission\Models\Permission::count() . PHP_EOL;
|
||||
echo 'Admin has role: ' . (App\Models\User::where('email', 'admin@example.com')->first()->hasRole('admin') ? 'YES' : 'NO') . PHP_EOL;
|
||||
"
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
```
|
||||
Admin exists: YES
|
||||
Roles count: 3
|
||||
Permissions count: 5
|
||||
Admin has role: YES
|
||||
```
|
||||
|
||||
Then visit http://localhost:8080/admin and login.
|
||||
|
||||
---
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
If any test fails:
|
||||
|
||||
1. **Capture logs:**
|
||||
```bash
|
||||
docker-compose logs app > app-logs.txt
|
||||
docker-compose exec app cat storage/logs/laravel.log > laravel-logs.txt
|
||||
```
|
||||
|
||||
2. **Capture database state:**
|
||||
```bash
|
||||
docker-compose exec app php artisan tinker --execute="
|
||||
echo 'Users: ' . App\Models\User::count() . PHP_EOL;
|
||||
echo 'Roles: ' . Spatie\Permission\Models\Role::count() . PHP_EOL;
|
||||
echo 'Permissions: ' . Spatie\Permission\Models\Permission::count() . PHP_EOL;
|
||||
" > db-state.txt
|
||||
```
|
||||
|
||||
3. **Report with:**
|
||||
- Which test failed
|
||||
- Error messages
|
||||
- Log files
|
||||
- Steps to reproduce
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
The template is considered "working correctly" if:
|
||||
|
||||
- ✅ Fresh setup completes in under 3 minutes
|
||||
- ✅ Zero manual intervention required
|
||||
- ✅ Admin user auto-created with correct credentials
|
||||
- ✅ All roles and permissions seeded
|
||||
- ✅ Admin panel accessible immediately
|
||||
- ✅ Settings page works without errors
|
||||
- ✅ No Redis errors
|
||||
- ✅ No CSRF errors
|
||||
- ✅ Production deployment follows clear guide
|
||||
- ✅ All common errors documented with solutions
|
||||
@@ -10,9 +10,9 @@ services:
|
||||
restart: unless-stopped
|
||||
working_dir: /var/www/html
|
||||
volumes:
|
||||
- ./src:/var/www/html
|
||||
- ./src:/var/www/html:cached
|
||||
- vendor_data:/var/www/html/vendor
|
||||
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini
|
||||
- sqlite_data:/var/www/html/database
|
||||
networks:
|
||||
- laravel_network
|
||||
depends_on:
|
||||
@@ -26,7 +26,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
|
||||
@@ -125,6 +125,7 @@ services:
|
||||
working_dir: /var/www/html
|
||||
volumes:
|
||||
- ./src:/var/www/html
|
||||
- vendor_data:/var/www/html/vendor
|
||||
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini
|
||||
networks:
|
||||
- laravel_network
|
||||
@@ -144,6 +145,7 @@ services:
|
||||
working_dir: /var/www/html
|
||||
volumes:
|
||||
- ./src:/var/www/html
|
||||
- vendor_data:/var/www/html/vendor
|
||||
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini
|
||||
networks:
|
||||
- laravel_network
|
||||
@@ -170,4 +172,4 @@ volumes:
|
||||
mysql_data:
|
||||
pgsql_data:
|
||||
redis_data:
|
||||
sqlite_data:
|
||||
vendor_data:
|
||||
|
||||
@@ -43,7 +43,17 @@ RUN useradd -G www-data,root -u 1000 -d /home/devuser devuser
|
||||
RUN mkdir -p /home/devuser/.composer && \
|
||||
chown -R devuser:devuser /home/devuser
|
||||
|
||||
# Copy existing application directory permissions
|
||||
# Copy composer files first
|
||||
COPY --chown=devuser:devuser ./src/composer.json ./src/composer.lock /var/www/html/
|
||||
|
||||
# Install composer dependencies as devuser
|
||||
USER devuser
|
||||
RUN composer install --no-interaction --no-dev --optimize-autoloader --no-scripts
|
||||
|
||||
# Switch back to root to copy rest of files
|
||||
USER root
|
||||
|
||||
# Copy existing application directory
|
||||
COPY --chown=devuser:devuser ./src /var/www/html
|
||||
|
||||
# Set proper permissions
|
||||
@@ -51,6 +61,10 @@ RUN chown -R devuser:www-data /var/www/html \
|
||||
&& chmod -R 775 /var/www/html/storage 2>/dev/null || true \
|
||||
&& chmod -R 775 /var/www/html/bootstrap/cache 2>/dev/null || true
|
||||
|
||||
# Run post-install scripts
|
||||
USER devuser
|
||||
RUN composer run-script post-autoload-dump 2>/dev/null || true
|
||||
|
||||
USER devuser
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
@@ -10,6 +10,12 @@ display_errors = On
|
||||
display_startup_errors = On
|
||||
error_reporting = E_ALL
|
||||
|
||||
; Opcache settings (disabled for development for live reloading)
|
||||
opcache.enable = 0
|
||||
; Opcache settings - ENABLED for performance (even in dev on Windows/WSL2)
|
||||
opcache.enable = 1
|
||||
opcache.enable_cli = 0
|
||||
opcache.memory_consumption = 256
|
||||
opcache.interned_strings_buffer = 16
|
||||
opcache.max_accelerated_files = 20000
|
||||
opcache.validate_timestamps = 1
|
||||
opcache.revalidate_freq = 2
|
||||
opcache.fast_shutdown = 1
|
||||
|
||||
245
setup.bat
Normal file
245
setup.bat
Normal file
@@ -0,0 +1,245 @@
|
||||
@echo off
|
||||
REM Laravel Docker Development Template - Quick Setup (Windows)
|
||||
REM Everything is pre-installed! This just starts Docker and sets up the database.
|
||||
REM Usage: setup.bat [mysql|pgsql|sqlite]
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set DB=%1
|
||||
if "%DB%"=="" set DB=mysql
|
||||
|
||||
REM Validate database choice
|
||||
if not "%DB%"=="mysql" if not "%DB%"=="pgsql" if not "%DB%"=="sqlite" (
|
||||
echo Error: Invalid database '%DB%'
|
||||
echo Usage: setup.bat [mysql^|pgsql^|sqlite]
|
||||
echo Example: setup.bat mysql
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo ========================================================
|
||||
echo Laravel Docker Development Template
|
||||
echo Quick Setup - Everything Pre-Installed!
|
||||
echo ========================================================
|
||||
echo.
|
||||
echo Setting up with %DB%...
|
||||
echo.
|
||||
|
||||
REM Step 1: Check port availability and auto-assign alternatives
|
||||
echo Checking port availability...
|
||||
|
||||
REM Function to find next available port
|
||||
REM Usage: call :find_port <start_port> <result_var>
|
||||
goto :skip_functions
|
||||
|
||||
:find_port
|
||||
set /a port=%~1
|
||||
:find_port_loop
|
||||
netstat -ano | findstr ":%port% " >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
set /a port+=1
|
||||
goto :find_port_loop
|
||||
)
|
||||
set %~2=%port%
|
||||
exit /b 0
|
||||
|
||||
:skip_functions
|
||||
|
||||
REM Check and assign APP_PORT (Web Server)
|
||||
netstat -ano | findstr ":8080" >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
call :find_port 8081 APP_PORT
|
||||
echo [!] Port 8080 in use - using !APP_PORT! for Web Server
|
||||
) else (
|
||||
set APP_PORT=8080
|
||||
echo [OK] Port 8080 ^(Web Server^) - Available
|
||||
)
|
||||
|
||||
REM Check and assign MAIL_DASHBOARD_PORT (Mailpit Dashboard)
|
||||
netstat -ano | findstr ":8025" >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
call :find_port 8026 MAIL_DASHBOARD_PORT
|
||||
echo [!] Port 8025 in use - using !MAIL_DASHBOARD_PORT! for Mailpit Dashboard
|
||||
) else (
|
||||
set MAIL_DASHBOARD_PORT=8025
|
||||
echo [OK] Port 8025 ^(Mailpit Dashboard^) - Available
|
||||
)
|
||||
|
||||
REM Check and assign MAIL_PORT (Mailpit SMTP)
|
||||
netstat -ano | findstr ":1025" >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
call :find_port 1026 MAIL_PORT
|
||||
echo [!] Port 1025 in use - using !MAIL_PORT! for Mailpit SMTP
|
||||
) else (
|
||||
set MAIL_PORT=1025
|
||||
echo [OK] Port 1025 ^(Mailpit SMTP^) - Available
|
||||
)
|
||||
|
||||
REM Check and assign REDIS_PORT
|
||||
netstat -ano | findstr ":6379" >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
call :find_port 6380 REDIS_PORT
|
||||
echo [!] Port 6379 in use - using !REDIS_PORT! for Redis
|
||||
) else (
|
||||
set REDIS_PORT=6379
|
||||
echo [OK] Port 6379 ^(Redis^) - Available
|
||||
)
|
||||
|
||||
REM Check and assign DB_PORT based on database selection
|
||||
if "%DB%"=="mysql" (
|
||||
netstat -ano | findstr ":3306" >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
call :find_port 3307 DB_PORT
|
||||
echo [!] Port 3306 in use - using !DB_PORT! for MySQL
|
||||
) else (
|
||||
set DB_PORT=3306
|
||||
echo [OK] Port 3306 ^(MySQL^) - Available
|
||||
)
|
||||
)
|
||||
|
||||
if "%DB%"=="pgsql" (
|
||||
netstat -ano | findstr ":5432" >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
call :find_port 5433 DB_PORT
|
||||
echo [!] Port 5432 in use - using !DB_PORT! for PostgreSQL
|
||||
) else (
|
||||
set DB_PORT=5432
|
||||
echo [OK] Port 5432 ^(PostgreSQL^) - Available
|
||||
)
|
||||
)
|
||||
|
||||
echo.
|
||||
|
||||
REM Step 2: Configure environment
|
||||
echo Configuring environment...
|
||||
if exist "src\.env.%DB%" (
|
||||
copy /y "src\.env.%DB%" "src\.env" >nul
|
||||
|
||||
REM Append port configurations to .env
|
||||
echo APP_PORT=%APP_PORT% >> src\.env
|
||||
echo MAIL_DASHBOARD_PORT=%MAIL_DASHBOARD_PORT% >> src\.env
|
||||
echo MAIL_PORT=%MAIL_PORT% >> src\.env
|
||||
echo REDIS_PORT=%REDIS_PORT% >> src\.env
|
||||
if defined DB_PORT echo DB_PORT=%DB_PORT% >> src\.env
|
||||
|
||||
echo Environment configured for %DB%
|
||||
) else (
|
||||
echo ERROR: Template file src\.env.%DB% not found
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Step 3: Build containers
|
||||
echo Building Docker containers...
|
||||
docker-compose build
|
||||
echo Containers built
|
||||
echo.
|
||||
|
||||
REM Step 4: Start containers
|
||||
echo Starting Docker containers...
|
||||
docker-compose --profile %DB% up -d
|
||||
echo Containers started
|
||||
echo.
|
||||
|
||||
REM Step 5: Wait for app container to be ready
|
||||
echo Waiting for app container...
|
||||
timeout /t 3 /nobreak >nul
|
||||
echo App container ready
|
||||
echo.
|
||||
|
||||
REM Step 6: Wait for database
|
||||
if "%DB%"=="mysql" (
|
||||
echo Waiting for MySQL to be ready...
|
||||
set DB_READY=0
|
||||
for /L %%i in (1,1,30) do (
|
||||
docker-compose exec -T app php -r "new PDO('mysql:host=mysql;dbname=laravel', 'laravel', 'secret');" >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
set DB_READY=1
|
||||
goto :mysql_ready
|
||||
)
|
||||
timeout /t 1 /nobreak >nul
|
||||
)
|
||||
:mysql_ready
|
||||
if !DB_READY! equ 1 (
|
||||
echo MySQL ready
|
||||
) else (
|
||||
echo WARNING: MySQL may not be fully ready yet
|
||||
)
|
||||
echo.
|
||||
)
|
||||
if "%DB%"=="pgsql" (
|
||||
echo Waiting for PostgreSQL to be ready...
|
||||
set DB_READY=0
|
||||
for /L %%i in (1,1,30) do (
|
||||
docker-compose exec -T app php -r "new PDO('pgsql:host=pgsql;dbname=laravel', 'laravel', 'secret');" >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
set DB_READY=1
|
||||
goto :pgsql_ready
|
||||
)
|
||||
timeout /t 1 /nobreak >nul
|
||||
)
|
||||
:pgsql_ready
|
||||
if !DB_READY! equ 1 (
|
||||
echo PostgreSQL ready
|
||||
) else (
|
||||
echo WARNING: PostgreSQL may not be fully ready yet
|
||||
)
|
||||
echo.
|
||||
)
|
||||
|
||||
REM Step 7: Generate app key
|
||||
echo Generating application key...
|
||||
docker-compose exec -T app php artisan key:generate --force
|
||||
echo App key generated
|
||||
echo.
|
||||
|
||||
REM Step 8: Run migrations
|
||||
echo Running database migrations...
|
||||
docker-compose exec -T app php artisan migrate --force
|
||||
echo Migrations completed
|
||||
echo.
|
||||
|
||||
REM Step 9: Seed database
|
||||
echo Seeding database (roles, permissions, admin user)...
|
||||
docker-compose exec -T app php artisan db:seed --force
|
||||
echo Database seeded
|
||||
echo.
|
||||
|
||||
REM Step 10: Create storage link
|
||||
echo Creating storage symlink...
|
||||
docker-compose exec -T app php artisan storage:link
|
||||
echo Storage linked
|
||||
echo.
|
||||
|
||||
REM Done
|
||||
echo.
|
||||
echo ========================================================
|
||||
echo Setup Complete!
|
||||
echo ========================================================
|
||||
echo.
|
||||
echo Your Laravel application is ready!
|
||||
echo.
|
||||
echo Laravel App: http://localhost:%APP_PORT%
|
||||
echo Admin Panel: http://localhost:%APP_PORT%/admin
|
||||
echo Mailpit: http://localhost:%MAIL_DASHBOARD_PORT%
|
||||
echo.
|
||||
echo Admin Login:
|
||||
echo Email: admin@example.com
|
||||
echo Password: password
|
||||
echo.
|
||||
echo What's Included:
|
||||
echo - Laravel 11 with Breeze authentication
|
||||
echo - Filament admin panel with user management
|
||||
echo - Pest testing framework
|
||||
echo - Laravel Pint code style
|
||||
echo - Queue workers ^& scheduler (optional profiles)
|
||||
echo.
|
||||
echo Common Commands:
|
||||
echo docker-compose exec app php artisan [command]
|
||||
echo docker-compose exec app composer [command]
|
||||
echo docker-compose exec app ./vendor/bin/pest
|
||||
echo docker-compose logs -f app
|
||||
echo.
|
||||
echo Stop containers:
|
||||
echo docker-compose down
|
||||
echo.
|
||||
|
||||
endlocal
|
||||
218
setup.sh
Normal file
218
setup.sh
Normal file
@@ -0,0 +1,218 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Laravel Docker Development Template - Quick Setup
|
||||
# Everything is pre-installed! This just starts Docker and sets up the database.
|
||||
# Usage: ./setup.sh [mysql|pgsql|sqlite]
|
||||
|
||||
set -e
|
||||
|
||||
DB=${1:-mysql}
|
||||
VALID_DBS=("mysql" "pgsql" "sqlite")
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${GREEN}╔════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ Laravel Docker Development Template ║${NC}"
|
||||
echo -e "${GREEN}║ Quick Setup - Everything Pre-Installed! ║${NC}"
|
||||
echo -e "${GREEN}╚════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
# Validate database choice
|
||||
if [[ ! " ${VALID_DBS[@]} " =~ " ${DB} " ]]; then
|
||||
echo -e "${RED}Error: Invalid database '${DB}'${NC}"
|
||||
echo "Usage: ./setup.sh [mysql|pgsql|sqlite]"
|
||||
echo "Example: ./setup.sh mysql"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Setting up with ${DB}...${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 1: Check port availability and auto-assign alternatives
|
||||
echo -e "${YELLOW}→ Checking port availability...${NC}"
|
||||
|
||||
# Function to find next available port
|
||||
find_port() {
|
||||
local port=$1
|
||||
while lsof -Pi :$port -sTCP:LISTEN -t >/dev/null 2>&1 || netstat -tuln 2>/dev/null | grep -q ":$port "; do
|
||||
port=$((port + 1))
|
||||
done
|
||||
echo $port
|
||||
}
|
||||
|
||||
# Check and assign APP_PORT (Web Server)
|
||||
if lsof -Pi :8080 -sTCP:LISTEN -t >/dev/null 2>&1 || netstat -tuln 2>/dev/null | grep -q ":8080 "; then
|
||||
APP_PORT=$(find_port 8081)
|
||||
echo -e "${YELLOW}[!] Port 8080 in use - using $APP_PORT for Web Server${NC}"
|
||||
else
|
||||
APP_PORT=8080
|
||||
echo -e "${GREEN}[✓] Port 8080 (Web Server) - Available${NC}"
|
||||
fi
|
||||
|
||||
# Check and assign MAIL_DASHBOARD_PORT (Mailpit Dashboard)
|
||||
if lsof -Pi :8025 -sTCP:LISTEN -t >/dev/null 2>&1 || netstat -tuln 2>/dev/null | grep -q ":8025 "; then
|
||||
MAIL_DASHBOARD_PORT=$(find_port 8026)
|
||||
echo -e "${YELLOW}[!] Port 8025 in use - using $MAIL_DASHBOARD_PORT for Mailpit Dashboard${NC}"
|
||||
else
|
||||
MAIL_DASHBOARD_PORT=8025
|
||||
echo -e "${GREEN}[✓] Port 8025 (Mailpit Dashboard) - Available${NC}"
|
||||
fi
|
||||
|
||||
# Check and assign MAIL_PORT (Mailpit SMTP)
|
||||
if lsof -Pi :1025 -sTCP:LISTEN -t >/dev/null 2>&1 || netstat -tuln 2>/dev/null | grep -q ":1025 "; then
|
||||
MAIL_PORT=$(find_port 1026)
|
||||
echo -e "${YELLOW}[!] Port 1025 in use - using $MAIL_PORT for Mailpit SMTP${NC}"
|
||||
else
|
||||
MAIL_PORT=1025
|
||||
echo -e "${GREEN}[✓] Port 1025 (Mailpit SMTP) - Available${NC}"
|
||||
fi
|
||||
|
||||
# Check and assign REDIS_PORT
|
||||
if lsof -Pi :6379 -sTCP:LISTEN -t >/dev/null 2>&1 || netstat -tuln 2>/dev/null | grep -q ":6379 "; then
|
||||
REDIS_PORT=$(find_port 6380)
|
||||
echo -e "${YELLOW}[!] Port 6379 in use - using $REDIS_PORT for Redis${NC}"
|
||||
else
|
||||
REDIS_PORT=6379
|
||||
echo -e "${GREEN}[✓] Port 6379 (Redis) - Available${NC}"
|
||||
fi
|
||||
|
||||
# Check and assign DB_PORT based on database selection
|
||||
if [ "$DB" = "mysql" ]; then
|
||||
if lsof -Pi :3306 -sTCP:LISTEN -t >/dev/null 2>&1 || netstat -tuln 2>/dev/null | grep -q ":3306 "; then
|
||||
DB_PORT=$(find_port 3307)
|
||||
echo -e "${YELLOW}[!] Port 3306 in use - using $DB_PORT for MySQL${NC}"
|
||||
else
|
||||
DB_PORT=3306
|
||||
echo -e "${GREEN}[✓] Port 3306 (MySQL) - Available${NC}"
|
||||
fi
|
||||
elif [ "$DB" = "pgsql" ]; then
|
||||
if lsof -Pi :5432 -sTCP:LISTEN -t >/dev/null 2>&1 || netstat -tuln 2>/dev/null | grep -q ":5432 "; then
|
||||
DB_PORT=$(find_port 5433)
|
||||
echo -e "${YELLOW}[!] Port 5432 in use - using $DB_PORT for PostgreSQL${NC}"
|
||||
else
|
||||
DB_PORT=5432
|
||||
echo -e "${GREEN}[✓] Port 5432 (PostgreSQL) - Available${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 2: Configure environment
|
||||
echo -e "${YELLOW}→ Configuring environment...${NC}"
|
||||
if [ -f "src/.env.${DB}" ]; then
|
||||
cp "src/.env.${DB}" "src/.env"
|
||||
|
||||
# Append port configurations to .env
|
||||
echo "APP_PORT=$APP_PORT" >> src/.env
|
||||
echo "MAIL_DASHBOARD_PORT=$MAIL_DASHBOARD_PORT" >> src/.env
|
||||
echo "MAIL_PORT=$MAIL_PORT" >> src/.env
|
||||
echo "REDIS_PORT=$REDIS_PORT" >> src/.env
|
||||
[ -n "$DB_PORT" ] && echo "DB_PORT=$DB_PORT" >> src/.env
|
||||
|
||||
echo -e "${GREEN}✓ Environment configured for ${DB}${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Template file src/.env.${DB} not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 3: Build containers
|
||||
echo -e "${YELLOW}→ Building Docker containers...${NC}"
|
||||
docker-compose build
|
||||
echo -e "${GREEN}✓ Containers built${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 4: Start containers
|
||||
echo -e "${YELLOW}→ Starting Docker containers...${NC}"
|
||||
docker-compose --profile ${DB} up -d
|
||||
echo -e "${GREEN}✓ Containers started${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 5: Wait for app container to be ready
|
||||
echo -e "${YELLOW}→ Waiting for app container...${NC}"
|
||||
sleep 3
|
||||
echo -e "${GREEN}✓ App container ready${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 6: Wait for database
|
||||
if [ "$DB" = "mysql" ]; then
|
||||
echo -e "${YELLOW}→ Waiting for MySQL to be ready...${NC}"
|
||||
for i in {1..30}; do
|
||||
if docker-compose exec -T app php -r "new PDO('mysql:host=mysql;dbname=laravel', 'laravel', 'secret');" >/dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ MySQL ready${NC}"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo ""
|
||||
elif [ "$DB" = "pgsql" ]; then
|
||||
echo -e "${YELLOW}→ Waiting for PostgreSQL to be ready...${NC}"
|
||||
for i in {1..30}; do
|
||||
if docker-compose exec -T app php -r "new PDO('pgsql:host=pgsql;dbname=laravel', 'laravel', 'secret');" >/dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ PostgreSQL ready${NC}"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Step 7: Generate app key
|
||||
echo -e "${YELLOW}→ Generating application key...${NC}"
|
||||
docker-compose exec app php artisan key:generate --force
|
||||
echo -e "${GREEN}✓ App key generated${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 8: Run migrations
|
||||
echo -e "${YELLOW}→ Running database migrations...${NC}"
|
||||
docker-compose exec app php artisan migrate --force
|
||||
echo -e "${GREEN}✓ Migrations completed${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 9: Seed database (roles, permissions, admin user)
|
||||
echo -e "${YELLOW}→ Seeding database (roles, permissions, admin user)...${NC}"
|
||||
docker-compose exec app php artisan db:seed --force
|
||||
echo -e "${GREEN}✓ Database seeded${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 10: Create storage link
|
||||
echo -e "${YELLOW}→ Creating storage symlink...${NC}"
|
||||
docker-compose exec app php artisan storage:link
|
||||
echo -e "${GREEN}✓ Storage linked${NC}"
|
||||
echo ""
|
||||
|
||||
# Done!
|
||||
echo ""
|
||||
echo -e "${GREEN}╔════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ 🎉 Setup Complete! 🎉 ║${NC}"
|
||||
echo -e "${GREEN}╚════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo -e "${GREEN}Your Laravel application is ready!${NC}"
|
||||
echo ""
|
||||
echo -e " 📱 Laravel App: ${GREEN}http://localhost:$APP_PORT${NC}"
|
||||
echo -e " 🔐 Admin Panel: ${GREEN}http://localhost:$APP_PORT/admin${NC}"
|
||||
echo -e " 📧 Mailpit: ${GREEN}http://localhost:$MAIL_DASHBOARD_PORT${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Admin Login:${NC}"
|
||||
echo -e " Email: admin@example.com"
|
||||
echo -e " Password: password"
|
||||
echo ""
|
||||
echo -e "${YELLOW}What's Included:${NC}"
|
||||
echo -e " ✓ Laravel 11 with Breeze authentication"
|
||||
echo -e " ✓ Filament admin panel with user management"
|
||||
echo -e " ✓ Pest testing framework"
|
||||
echo -e " ✓ Laravel Pint code style"
|
||||
echo -e " ✓ Queue workers & scheduler (optional profiles)"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Common Commands:${NC}"
|
||||
echo -e " docker-compose exec app php artisan <command>"
|
||||
echo -e " docker-compose exec app composer <command>"
|
||||
echo -e " docker-compose exec app ./vendor/bin/pest"
|
||||
echo -e " docker-compose logs -f app"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Stop containers:${NC}"
|
||||
echo -e " docker-compose down"
|
||||
echo ""
|
||||
18
src/.editorconfig
Normal file
18
src/.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
indent_size = 4
|
||||
@@ -1,25 +1,18 @@
|
||||
# Laravel Environment Configuration
|
||||
#
|
||||
# This template supports multiple databases. Copy the appropriate file:
|
||||
# - .env.mysql - MySQL configuration
|
||||
# - .env.pgsql - PostgreSQL configuration
|
||||
# - .env.sqlite - SQLite configuration
|
||||
#
|
||||
# Example: cp .env.mysql .env
|
||||
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_TIMEZONE=UTC
|
||||
APP_URL=http://localhost:8080
|
||||
APP_URL=http://localhost
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
APP_MAINTENANCE_STORE=database
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
@@ -28,29 +21,14 @@ LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
# Database: Choose one configuration
|
||||
# ===================================
|
||||
# MySQL:
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=laravel
|
||||
DB_USERNAME=laravel
|
||||
DB_PASSWORD=secret
|
||||
|
||||
# PostgreSQL (uncomment and comment MySQL above):
|
||||
# DB_CONNECTION=pgsql
|
||||
# DB_HOST=pgsql
|
||||
# DB_PORT=5432
|
||||
DB_CONNECTION=sqlite
|
||||
# DB_HOST=127.0.0.1
|
||||
# DB_PORT=3306
|
||||
# DB_DATABASE=laravel
|
||||
# DB_USERNAME=laravel
|
||||
# DB_PASSWORD=secret
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=
|
||||
|
||||
# SQLite (uncomment and comment MySQL above):
|
||||
# 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=/
|
||||
@@ -58,33 +36,31 @@ SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=redis
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=redis
|
||||
CACHE_STORE=database
|
||||
CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=redis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=mailpit
|
||||
MAIL_PORT=1025
|
||||
MAIL_MAILER=log
|
||||
MAIL_SCHEME=null
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
# Error Logging - Flare (https://flareapp.io)
|
||||
# Get your key at: https://flareapp.io
|
||||
FLARE_KEY=
|
||||
|
||||
# Ignition Settings (Development)
|
||||
# Themes: light, dark, auto
|
||||
# Editors: vscode, phpstorm, sublime, atom, textmate
|
||||
IGNITION_THEME=auto
|
||||
IGNITION_EDITOR=vscode
|
||||
|
||||
@@ -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=/
|
||||
@@ -37,7 +37,9 @@ FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
CACHE_STORE=redis
|
||||
CACHE_PREFIX=
|
||||
CACHE_PREFIX=laravel_cache
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=redis
|
||||
@@ -53,12 +55,10 @@ MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
# Error Logging - Flare (https://flareapp.io)
|
||||
# Get your key at: https://flareapp.io
|
||||
FLARE_KEY=
|
||||
|
||||
# Ignition Settings (Development)
|
||||
IGNITION_THEME=auto
|
||||
IGNITION_EDITOR=vscode
|
||||
|
||||
@@ -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=/
|
||||
@@ -37,7 +37,9 @@ FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
CACHE_STORE=redis
|
||||
CACHE_PREFIX=
|
||||
CACHE_PREFIX=laravel_cache
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=redis
|
||||
@@ -53,12 +55,10 @@ MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
# Error Logging - Flare (https://flareapp.io)
|
||||
# Get your key at: https://flareapp.io
|
||||
FLARE_KEY=
|
||||
|
||||
# Ignition Settings (Development)
|
||||
IGNITION_THEME=auto
|
||||
IGNITION_EDITOR=vscode
|
||||
|
||||
@@ -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=/
|
||||
@@ -33,7 +33,9 @@ FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
CACHE_STORE=redis
|
||||
CACHE_PREFIX=
|
||||
CACHE_PREFIX=laravel_cache
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=redis
|
||||
@@ -49,12 +51,10 @@ MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
# Error Logging - Flare (https://flareapp.io)
|
||||
# Get your key at: https://flareapp.io
|
||||
FLARE_KEY=
|
||||
|
||||
# Ignition Settings (Development)
|
||||
IGNITION_THEME=auto
|
||||
IGNITION_EDITOR=vscode
|
||||
|
||||
11
src/.gitattributes
vendored
Normal file
11
src/.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
||||
23
src/.gitignore
vendored
Normal file
23
src/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/auth.json
|
||||
/.fleet
|
||||
/.idea
|
||||
/.nova
|
||||
/.vscode
|
||||
/.zed
|
||||
@@ -1,2 +0,0 @@
|
||||
# This directory will contain your Laravel application
|
||||
# Run: docker-compose run --rm app composer create-project laravel/laravel .
|
||||
66
src/README.md
Normal file
66
src/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
||||
</p>
|
||||
|
||||
## About Laravel
|
||||
|
||||
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
|
||||
|
||||
- [Simple, fast routing engine](https://laravel.com/docs/routing).
|
||||
- [Powerful dependency injection container](https://laravel.com/docs/container).
|
||||
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
|
||||
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
|
||||
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
|
||||
- [Robust background job processing](https://laravel.com/docs/queues).
|
||||
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
|
||||
|
||||
Laravel is accessible, powerful, and provides tools required for large, robust applications.
|
||||
|
||||
## Learning Laravel
|
||||
|
||||
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
|
||||
|
||||
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
|
||||
|
||||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
||||
|
||||
## Laravel Sponsors
|
||||
|
||||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
|
||||
|
||||
### Premium Partners
|
||||
|
||||
- **[Vehikl](https://vehikl.com/)**
|
||||
- **[Tighten Co.](https://tighten.co)**
|
||||
- **[WebReinvent](https://webreinvent.com/)**
|
||||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
||||
- **[64 Robots](https://64robots.com)**
|
||||
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
|
||||
- **[Cyber-Duck](https://cyber-duck.co.uk)**
|
||||
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
|
||||
- **[Jump24](https://jump24.co.uk)**
|
||||
- **[Redberry](https://redberry.international/laravel/)**
|
||||
- **[Active Logic](https://activelogic.com)**
|
||||
- **[byte5](https://byte5.de)**
|
||||
- **[OP.GG](https://op.gg)**
|
||||
|
||||
## Contributing
|
||||
|
||||
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
|
||||
|
||||
## Security Vulnerabilities
|
||||
|
||||
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
|
||||
|
||||
## License
|
||||
|
||||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
File diff suppressed because it is too large
Load Diff
336
src/app/Console/Commands/MakeModuleCommand.php
Normal file
336
src/app/Console/Commands/MakeModuleCommand.php
Normal file
@@ -0,0 +1,336 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MakeModuleCommand extends Command
|
||||
{
|
||||
protected $signature = 'make:module {name : The name of the module}';
|
||||
|
||||
protected $description = 'Create a new module with standard structure';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$name = $this->argument('name');
|
||||
$studlyName = Str::studly($name);
|
||||
$kebabName = Str::kebab($name);
|
||||
|
||||
$modulePath = app_path("Modules/{$studlyName}");
|
||||
|
||||
if (File::exists($modulePath)) {
|
||||
$this->error("Module {$studlyName} already exists!");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info("Creating module: {$studlyName}");
|
||||
|
||||
$this->createDirectories($modulePath);
|
||||
$this->createModel($modulePath, $studlyName);
|
||||
$this->createMigration($studlyName);
|
||||
$this->createController($modulePath, $studlyName);
|
||||
$this->createRoutes($modulePath, $studlyName, $kebabName);
|
||||
$this->createViews($modulePath, $studlyName, $kebabName);
|
||||
$this->createFilamentResource($studlyName);
|
||||
$this->createTests($studlyName);
|
||||
|
||||
$this->info("\n✅ Module {$studlyName} created successfully!");
|
||||
$this->info("\nNext steps:");
|
||||
$this->info("1. Run migrations: php artisan migrate");
|
||||
$this->info("2. Register routes in routes/web.php:");
|
||||
$this->info(" require __DIR__.'/../app/Modules/{$studlyName}/routes.php';");
|
||||
$this->info("3. Access at: /{$kebabName}");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
protected function createDirectories(string $path): void
|
||||
{
|
||||
File::makeDirectory($path, 0755, true);
|
||||
File::makeDirectory("{$path}/Models", 0755, true);
|
||||
File::makeDirectory("{$path}/Controllers", 0755, true);
|
||||
File::makeDirectory("{$path}/Views", 0755, true);
|
||||
}
|
||||
|
||||
protected function createModel(string $path, string $name): void
|
||||
{
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
namespace App\Modules\\{$name}\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class {$name} extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected \$fillable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
}
|
||||
PHP;
|
||||
|
||||
File::put("{$path}/Models/{$name}.php", $stub);
|
||||
$this->info("✓ Created Model: {$name}");
|
||||
}
|
||||
|
||||
protected function createMigration(string $name): void
|
||||
{
|
||||
$table = Str::snake(Str::plural($name));
|
||||
$timestamp = date('Y_m_d_His');
|
||||
$migrationName = "create_{$table}_table";
|
||||
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('{$table}', function (Blueprint \$table) {
|
||||
\$table->id();
|
||||
\$table->string('name');
|
||||
\$table->text('description')->nullable();
|
||||
\$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('{$table}');
|
||||
}
|
||||
};
|
||||
PHP;
|
||||
|
||||
$migrationPath = database_path("migrations/{$timestamp}_{$migrationName}.php");
|
||||
File::put($migrationPath, $stub);
|
||||
$this->info("✓ Created Migration: {$migrationName}");
|
||||
}
|
||||
|
||||
protected function createController(string $path, string $name): void
|
||||
{
|
||||
$plural = Str::plural($name);
|
||||
$variable = Str::camel($name);
|
||||
$pluralVariable = Str::camel($plural);
|
||||
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
namespace App\Modules\\{$name}\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Modules\\{$name}\Models\\{$name};
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class {$name}Controller extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
\${$pluralVariable} = {$name}::latest()->paginate(15);
|
||||
return view('{$name}::index', compact('{$pluralVariable}'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('{$name}::create');
|
||||
}
|
||||
|
||||
public function store(Request \$request)
|
||||
{
|
||||
\$validated = \$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
{$name}::create(\$validated);
|
||||
|
||||
return redirect()->route('{$variable}.index')
|
||||
->with('success', '{$name} created successfully.');
|
||||
}
|
||||
|
||||
public function show({$name} \${$variable})
|
||||
{
|
||||
return view('{$name}::show', compact('{$variable}'));
|
||||
}
|
||||
|
||||
public function edit({$name} \${$variable})
|
||||
{
|
||||
return view('{$name}::edit', compact('{$variable}'));
|
||||
}
|
||||
|
||||
public function update(Request \$request, {$name} \${$variable})
|
||||
{
|
||||
\$validated = \$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
\${$variable}->update(\$validated);
|
||||
|
||||
return redirect()->route('{$variable}.index')
|
||||
->with('success', '{$name} updated successfully.');
|
||||
}
|
||||
|
||||
public function destroy({$name} \${$variable})
|
||||
{
|
||||
\${$variable}->delete();
|
||||
|
||||
return redirect()->route('{$variable}.index')
|
||||
->with('success', '{$name} deleted successfully.');
|
||||
}
|
||||
}
|
||||
PHP;
|
||||
|
||||
File::put("{$path}/Controllers/{$name}Controller.php", $stub);
|
||||
$this->info("✓ Created Controller: {$name}Controller");
|
||||
}
|
||||
|
||||
protected function createRoutes(string $path, string $name, string $kebabName): void
|
||||
{
|
||||
$variable = Str::camel($name);
|
||||
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Modules\\{$name}\Controllers\\{$name}Controller;
|
||||
|
||||
Route::middleware(['web', 'auth'])->group(function () {
|
||||
Route::resource('{$kebabName}', {$name}Controller::class)->names('{$variable}');
|
||||
});
|
||||
PHP;
|
||||
|
||||
File::put("{$path}/routes.php", $stub);
|
||||
$this->info("✓ Created Routes");
|
||||
}
|
||||
|
||||
protected function createViews(string $path, string $name, string $kebabName): void
|
||||
{
|
||||
$plural = Str::plural($name);
|
||||
$variable = Str::camel($name);
|
||||
$pluralVariable = Str::camel($plural);
|
||||
|
||||
$indexView = <<<BLADE
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('{$plural}') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900">
|
||||
<div class="flex justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold">{$plural} List</h3>
|
||||
<a href="{{ route('{$variable}.create') }}" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
|
||||
Create New
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Name</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse(\${$pluralVariable} as \${$variable})
|
||||
<tr>
|
||||
<td class="px-6 py-4">{{ \${$variable}->name }}</td>
|
||||
<td class="px-6 py-4">{{ \${$variable}->description }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<a href="{{ route('{$variable}.edit', \${$variable}) }}" class="text-blue-600 hover:text-blue-900">Edit</a>
|
||||
<form action="{{ route('{$variable}.destroy', \${$variable}) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-red-600 hover:text-red-900 ml-2">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="3" class="px-6 py-4 text-center">No {$pluralVariable} found.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="mt-4">
|
||||
{{ \${$pluralVariable}->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
BLADE;
|
||||
|
||||
File::put("{$path}/Views/index.blade.php", $indexView);
|
||||
$this->info("✓ Created Views");
|
||||
}
|
||||
|
||||
protected function createFilamentResource(string $name): void
|
||||
{
|
||||
$this->call('make:filament-resource', [
|
||||
'name' => $name,
|
||||
'--generate' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function createTests(string $name): void
|
||||
{
|
||||
$variable = Str::camel($name);
|
||||
|
||||
$stub = <<<PHP
|
||||
<?php
|
||||
|
||||
use App\Modules\\{$name}\Models\\{$name};
|
||||
use App\Models\User;
|
||||
|
||||
it('can list {$variable}s', function () {
|
||||
\$user = User::factory()->create();
|
||||
{$name}::factory()->count(3)->create();
|
||||
|
||||
\$response = \$this->actingAs(\$user)->get(route('{$variable}.index'));
|
||||
|
||||
\$response->assertStatus(200);
|
||||
});
|
||||
|
||||
it('can create {$variable}', function () {
|
||||
\$user = User::factory()->create();
|
||||
|
||||
\$response = \$this->actingAs(\$user)->post(route('{$variable}.store'), [
|
||||
'name' => 'Test {$name}',
|
||||
'description' => 'Test description',
|
||||
]);
|
||||
|
||||
\$response->assertRedirect(route('{$variable}.index'));
|
||||
\$this->assertDatabaseHas('{$variable}s', ['name' => 'Test {$name}']);
|
||||
});
|
||||
PHP;
|
||||
|
||||
$testPath = base_path("tests/Modules/{$name}");
|
||||
File::makeDirectory($testPath, 0755, true);
|
||||
File::put("{$testPath}/{$name}Test.php", $stub);
|
||||
$this->info("✓ Created Tests");
|
||||
}
|
||||
}
|
||||
125
src/app/Filament/Pages/Settings.php
Normal file
125
src/app/Filament/Pages/Settings.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Models\Setting;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
class Settings extends Page
|
||||
{
|
||||
protected static ?string $navigationIcon = 'heroicon-o-cog-6-tooth';
|
||||
|
||||
protected static string $view = 'filament.pages.settings';
|
||||
|
||||
protected static ?string $navigationGroup = 'Settings';
|
||||
|
||||
protected static ?int $navigationSort = 99;
|
||||
|
||||
public ?array $data = [];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$siteName = Setting::get('site_name', config('app.name'));
|
||||
$siteLogo = Setting::get('site_logo');
|
||||
$primaryColor = Setting::get('primary_color', '#3b82f6');
|
||||
$secondaryColor = Setting::get('secondary_color', '#8b5cf6');
|
||||
$accentColor = Setting::get('accent_color', '#10b981');
|
||||
$siteDescription = Setting::get('site_description');
|
||||
$contactEmail = Setting::get('contact_email');
|
||||
$maintenanceMode = Setting::get('maintenance_mode', false);
|
||||
|
||||
$this->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();
|
||||
}
|
||||
}
|
||||
89
src/app/Filament/Resources/UserResource.php
Normal file
89
src/app/Filament/Resources/UserResource.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\UserResource\Pages;
|
||||
use App\Filament\Resources\UserResource\RelationManagers;
|
||||
use App\Models\User;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('email')
|
||||
->email()
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\DateTimePicker::make('email_verified_at'),
|
||||
Forms\Components\TextInput::make('password')
|
||||
->password()
|
||||
->required()
|
||||
->maxLength(255),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('email')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('email_verified_at')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListUsers::route('/'),
|
||||
'create' => Pages\CreateUser::route('/create'),
|
||||
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
12
src/app/Filament/Resources/UserResource/Pages/CreateUser.php
Normal file
12
src/app/Filament/Resources/UserResource/Pages/CreateUser.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateUser extends CreateRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
}
|
||||
19
src/app/Filament/Resources/UserResource/Pages/EditUser.php
Normal file
19
src/app/Filament/Resources/UserResource/Pages/EditUser.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditUser extends EditRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
src/app/Filament/Resources/UserResource/Pages/ListUsers.php
Normal file
19
src/app/Filament/Resources/UserResource/Pages/ListUsers.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AuthenticatedSessionController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the login view.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming authentication request.
|
||||
*/
|
||||
public function store(LoginRequest $request): RedirectResponse
|
||||
{
|
||||
$request->authenticate();
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy an authenticated session.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ConfirmablePasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the confirm password view.
|
||||
*/
|
||||
public function show(): View
|
||||
{
|
||||
return view('auth.confirm-password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the user's password.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if (! Auth::guard('web')->validate([
|
||||
'email' => $request->user()->email,
|
||||
'password' => $request->password,
|
||||
])) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('auth.password'),
|
||||
]);
|
||||
}
|
||||
|
||||
$request->session()->put('auth.password_confirmed_at', time());
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EmailVerificationNotificationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Send a new email verification notification.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
$request->user()->sendEmailVerificationNotification();
|
||||
|
||||
return back()->with('status', 'verification-link-sent');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class EmailVerificationPromptController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the email verification prompt.
|
||||
*/
|
||||
public function __invoke(Request $request): RedirectResponse|View
|
||||
{
|
||||
return $request->user()->hasVerifiedEmail()
|
||||
? redirect()->intended(route('dashboard', absolute: false))
|
||||
: view('auth.verify-email');
|
||||
}
|
||||
}
|
||||
62
src/app/Http/Controllers/Auth/NewPasswordController.php
Normal file
62
src/app/Http/Controllers/Auth/NewPasswordController.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class NewPasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the password reset view.
|
||||
*/
|
||||
public function create(Request $request): View
|
||||
{
|
||||
return view('auth.reset-password', ['request' => $request]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming new password request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'token' => ['required'],
|
||||
'email' => ['required', 'email'],
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$status = Password::reset(
|
||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function (User $user) use ($request) {
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($request->password),
|
||||
'remember_token' => Str::random(60),
|
||||
])->save();
|
||||
|
||||
event(new PasswordReset($user));
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
return $status == Password::PASSWORD_RESET
|
||||
? redirect()->route('login')->with('status', __($status))
|
||||
: back()->withInput($request->only('email'))
|
||||
->withErrors(['email' => __($status)]);
|
||||
}
|
||||
}
|
||||
29
src/app/Http/Controllers/Auth/PasswordController.php
Normal file
29
src/app/Http/Controllers/Auth/PasswordController.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Update the user's password.
|
||||
*/
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validateWithBag('updatePassword', [
|
||||
'current_password' => ['required', 'current_password'],
|
||||
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||
]);
|
||||
|
||||
$request->user()->update([
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
return back()->with('status', 'password-updated');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class PasswordResetLinkController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the password reset link request view.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('auth.forgot-password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming password reset link request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => ['required', 'email'],
|
||||
]);
|
||||
|
||||
// We will send the password reset link to this user. Once we have attempted
|
||||
// to send the link, we will examine the response then see the message we
|
||||
// need to show to the user. Finally, we'll send out a proper response.
|
||||
$status = Password::sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
return $status == Password::RESET_LINK_SENT
|
||||
? back()->with('status', __($status))
|
||||
: back()->withInput($request->only('email'))
|
||||
->withErrors(['email' => __($status)]);
|
||||
}
|
||||
}
|
||||
50
src/app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
50
src/app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the registration view.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('auth.register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return redirect(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
27
src/app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
27
src/app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class VerifyEmailController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mark the authenticated user's email address as verified.
|
||||
*/
|
||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
if ($request->user()->markEmailAsVerified()) {
|
||||
event(new Verified($request->user()));
|
||||
}
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
}
|
||||
8
src/app/Http/Controllers/Controller.php
Normal file
8
src/app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
60
src/app/Http/Controllers/ProfileController.php
Normal file
60
src/app/Http/Controllers/ProfileController.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ProfileUpdateRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the user's profile form.
|
||||
*/
|
||||
public function edit(Request $request): View
|
||||
{
|
||||
return view('profile.edit', [
|
||||
'user' => $request->user(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return Redirect::route('profile.edit')->with('status', 'profile-updated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's account.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validateWithBag('userDeletion', [
|
||||
'password' => ['required', 'current_password'],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
}
|
||||
85
src/app/Http/Requests/Auth/LoginRequest.php
Normal file
85
src/app/Http/Requests/Auth/LoginRequest.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Auth\Events\Lockout;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class LoginRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to authenticate the request's credentials.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function authenticate(): void
|
||||
{
|
||||
$this->ensureIsNotRateLimited();
|
||||
|
||||
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
|
||||
RateLimiter::hit($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.failed'),
|
||||
]);
|
||||
}
|
||||
|
||||
RateLimiter::clear($this->throttleKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the login request is not rate limited.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function ensureIsNotRateLimited(): void
|
||||
{
|
||||
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event(new Lockout($this));
|
||||
|
||||
$seconds = RateLimiter::availableIn($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.throttle', [
|
||||
'seconds' => $seconds,
|
||||
'minutes' => ceil($seconds / 60),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rate limiting throttle key for the request.
|
||||
*/
|
||||
public function throttleKey(): string
|
||||
{
|
||||
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||||
}
|
||||
}
|
||||
30
src/app/Http/Requests/ProfileUpdateRequest.php
Normal file
30
src/app/Http/Requests/ProfileUpdateRequest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ProfileUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'lowercase',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique(User::class)->ignore($this->user()->id),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
38
src/app/Models/Setting.php
Normal file
38
src/app/Models/Setting.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Setting extends Model
|
||||
{
|
||||
protected $fillable = ['key', 'value', 'type'];
|
||||
|
||||
public static function get(string $key, $default = null)
|
||||
{
|
||||
$setting = static::where('key', $key)->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]
|
||||
);
|
||||
}
|
||||
}
|
||||
50
src/app/Models/User.php
Normal file
50
src/app/Models/User.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
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, HasRoles, HasApiTokens;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
94
src/app/Modules/README.md
Normal file
94
src/app/Modules/README.md
Normal file
@@ -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
|
||||
24
src/app/Providers/AppServiceProvider.php
Normal file
24
src/app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
58
src/app/Providers/Filament/AdminPanelProvider.php
Normal file
58
src/app/Providers/Filament/AdminPanelProvider.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\AuthenticateSession;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Pages;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Widgets;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->id('admin')
|
||||
->path('admin')
|
||||
->login()
|
||||
->colors([
|
||||
'primary' => Color::Amber,
|
||||
])
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([
|
||||
Pages\Dashboard::class,
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||
->widgets([
|
||||
Widgets\AccountWidget::class,
|
||||
Widgets\FilamentInfoWidget::class,
|
||||
])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
])
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ModuleServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->registerModules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->bootModules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all modules.
|
||||
*/
|
||||
protected function registerModules(): void
|
||||
{
|
||||
$modulesPath = app_path('Modules');
|
||||
|
||||
if (!File::isDirectory($modulesPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$modules = File::directories($modulesPath);
|
||||
|
||||
foreach ($modules as $modulePath) {
|
||||
$moduleName = basename($modulePath);
|
||||
$providerClass = "App\\Modules\\{$moduleName}\\{$moduleName}ServiceProvider";
|
||||
|
||||
if (class_exists($providerClass)) {
|
||||
$this->app->register($providerClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot all modules.
|
||||
*/
|
||||
protected function bootModules(): void
|
||||
{
|
||||
$modulesPath = app_path('Modules');
|
||||
|
||||
if (!File::isDirectory($modulesPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$modules = File::directories($modulesPath);
|
||||
|
||||
foreach ($modules as $modulePath) {
|
||||
$moduleName = basename($modulePath);
|
||||
|
||||
// Load module config
|
||||
$configPath = "{$modulePath}/Config";
|
||||
if (File::isDirectory($configPath)) {
|
||||
foreach (File::files($configPath) as $file) {
|
||||
$this->mergeConfigFrom(
|
||||
$file->getPathname(),
|
||||
strtolower($moduleName) . '.' . $file->getFilenameWithoutExtension()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Load module routes
|
||||
$this->loadModuleRoutes($modulePath, $moduleName);
|
||||
|
||||
// Load module views
|
||||
$viewsPath = "{$modulePath}/Resources/views";
|
||||
if (File::isDirectory($viewsPath)) {
|
||||
$this->loadViewsFrom($viewsPath, strtolower(preg_replace('/(?<!^)[A-Z]/', '-$0', $moduleName)));
|
||||
}
|
||||
|
||||
// Load module migrations
|
||||
$migrationsPath = "{$modulePath}/Database/Migrations";
|
||||
if (File::isDirectory($migrationsPath)) {
|
||||
$this->loadMigrationsFrom($migrationsPath);
|
||||
}
|
||||
|
||||
// Load module translations
|
||||
$langPath = "{$modulePath}/Resources/lang";
|
||||
if (File::isDirectory($langPath)) {
|
||||
$this->loadTranslationsFrom($langPath, strtolower($moduleName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load module routes.
|
||||
*/
|
||||
protected function loadModuleRoutes(string $modulePath, string $moduleName): void
|
||||
{
|
||||
$routesPath = "{$modulePath}/Routes";
|
||||
|
||||
if (!File::isDirectory($routesPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$webRoutes = "{$routesPath}/web.php";
|
||||
$apiRoutes = "{$routesPath}/api.php";
|
||||
|
||||
if (File::exists($webRoutes)) {
|
||||
$this->loadRoutesFrom($webRoutes);
|
||||
}
|
||||
|
||||
if (File::exists($apiRoutes)) {
|
||||
$this->loadRoutesFrom($apiRoutes);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/app/View/Components/AppLayout.php
Normal file
17
src/app/View/Components/AppLayout.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AppLayout extends Component
|
||||
{
|
||||
/**
|
||||
* Get the view / contents that represents the component.
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('layouts.app');
|
||||
}
|
||||
}
|
||||
17
src/app/View/Components/GuestLayout.php
Normal file
17
src/app/View/Components/GuestLayout.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class GuestLayout extends Component
|
||||
{
|
||||
/**
|
||||
* Get the view / contents that represents the component.
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('layouts.guest');
|
||||
}
|
||||
}
|
||||
15
src/artisan
Normal file
15
src/artisan
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the command...
|
||||
$status = (require_once __DIR__.'/bootstrap/app.php')
|
||||
->handleCommand(new ArgvInput);
|
||||
|
||||
exit($status);
|
||||
19
src/bootstrap/app.php
Normal file
19
src/bootstrap/app.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
api: __DIR__.'/../routes/api.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
//
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
//
|
||||
})->create();
|
||||
2
src/bootstrap/cache/.gitignore
vendored
Normal file
2
src/bootstrap/cache/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
6
src/bootstrap/providers.php
Normal file
6
src/bootstrap/providers.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\Filament\AdminPanelProvider::class,
|
||||
];
|
||||
81
src/composer.json
Normal file
81
src/composer.json
Normal file
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"$schema": "https://getcomposer.org/schema.json",
|
||||
"name": "laravel/laravel",
|
||||
"type": "project",
|
||||
"description": "The skeleton application for the Laravel framework.",
|
||||
"keywords": ["laravel", "framework"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"filament/filament": "^3.2",
|
||||
"laravel/framework": "^11.31",
|
||||
"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",
|
||||
"laravel/breeze": "^2.3",
|
||||
"laravel/pail": "^1.1",
|
||||
"laravel/pint": "^1.27",
|
||||
"laravel/sail": "^1.26",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.1",
|
||||
"pestphp/pest": "^3.8",
|
||||
"pestphp/pest-plugin-laravel": "^3.2",
|
||||
"phpunit/phpunit": "^11.0.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi",
|
||||
"@php artisan filament:upgrade"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi",
|
||||
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
|
||||
"@php artisan migrate --graceful --ansi"
|
||||
],
|
||||
"dev": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true,
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
11615
src/composer.lock
generated
Normal file
11615
src/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
126
src/config/app.php
Normal file
126
src/config/app.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the name of your application, which will be used when the
|
||||
| framework needs to place the application's name in a notification or
|
||||
| other UI elements where an application name needs to be displayed.
|
||||
|
|
||||
*/
|
||||
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Environment
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the "environment" your application is currently
|
||||
| running in. This may determine how you prefer to configure various
|
||||
| services the application utilizes. Set this in your ".env" file.
|
||||
|
|
||||
*/
|
||||
|
||||
'env' => env('APP_ENV', 'production'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Debug Mode
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When your application is in debug mode, detailed error messages with
|
||||
| stack traces will be shown on every error that occurs within your
|
||||
| application. If disabled, a simple generic error page is shown.
|
||||
|
|
||||
*/
|
||||
|
||||
'debug' => (bool) env('APP_DEBUG', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This URL is used by the console to properly generate URLs when using
|
||||
| the Artisan command line tool. You should set this to the root of
|
||||
| the application so that it's available within Artisan commands.
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default timezone for your application, which
|
||||
| will be used by the PHP date and date-time functions. The timezone
|
||||
| is set to "UTC" by default as it is suitable for most use cases.
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Locale Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The application locale determines the default locale that will be used
|
||||
| by Laravel's translation / localization methods. This option can be
|
||||
| set to any locale for which you plan to have translation strings.
|
||||
|
|
||||
*/
|
||||
|
||||
'locale' => env('APP_LOCALE', 'en'),
|
||||
|
||||
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
||||
|
||||
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This key is utilized by Laravel's encryption services and should be set
|
||||
| to a random, 32 character string to ensure that all encrypted values
|
||||
| are secure. You should do this prior to deploying the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
'key' => env('APP_KEY'),
|
||||
|
||||
'previous_keys' => [
|
||||
...array_filter(
|
||||
explode(',', env('APP_PREVIOUS_KEYS', ''))
|
||||
),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Maintenance Mode Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options determine the driver used to determine and
|
||||
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||
| allow maintenance mode to be controlled across multiple machines.
|
||||
|
|
||||
| Supported drivers: "file", "cache"
|
||||
|
|
||||
*/
|
||||
|
||||
'maintenance' => [
|
||||
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
|
||||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||
],
|
||||
|
||||
];
|
||||
115
src/config/auth.php
Normal file
115
src/config/auth.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Defaults
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default authentication "guard" and password
|
||||
| reset "broker" for your application. You may change these values
|
||||
| as required, but they're a perfect start for most applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => env('AUTH_GUARD', 'web'),
|
||||
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, you may define every authentication guard for your application.
|
||||
| Of course, a great default configuration has been defined for you
|
||||
| which utilizes session storage plus the Eloquent user provider.
|
||||
|
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| Supported: "session"
|
||||
|
|
||||
*/
|
||||
|
||||
'guards' => [
|
||||
'web' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| If you have multiple user tables or models you may configure multiple
|
||||
| providers to represent the model / table. These providers may then
|
||||
| be assigned to any extra authentication guards you have defined.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => env('AUTH_MODEL', App\Models\User::class),
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options specify the behavior of Laravel's password
|
||||
| reset functionality, including the table utilized for token storage
|
||||
| and the user provider that is invoked to actually retrieve users.
|
||||
|
|
||||
| The expiry time is the number of minutes that each reset token will be
|
||||
| considered valid. This security feature keeps tokens short-lived so
|
||||
| they have less time to be guessed. You may change this as needed.
|
||||
|
|
||||
| The throttle setting is the number of seconds a user must wait before
|
||||
| generating more password reset tokens. This prevents the user from
|
||||
| quickly generating a very large amount of password reset tokens.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => [
|
||||
'users' => [
|
||||
'provider' => 'users',
|
||||
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Confirmation Timeout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the amount of seconds before a password confirmation
|
||||
| window expires and users are asked to re-enter their password via the
|
||||
| confirmation screen. By default, the timeout lasts for three hours.
|
||||
|
|
||||
*/
|
||||
|
||||
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||
|
||||
];
|
||||
108
src/config/cache.php
Normal file
108
src/config/cache.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default cache store that will be used by the
|
||||
| framework. This connection is utilized if another isn't explicitly
|
||||
| specified when running a cache operation inside the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('CACHE_STORE', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Stores
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the cache "stores" for your application as
|
||||
| well as their drivers. You may even define multiple stores for the
|
||||
| same cache driver to group types of items stored in your caches.
|
||||
|
|
||||
| Supported drivers: "array", "database", "file", "memcached",
|
||||
| "redis", "dynamodb", "octane", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'stores' => [
|
||||
|
||||
'array' => [
|
||||
'driver' => 'array',
|
||||
'serialize' => false,
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'connection' => env('DB_CACHE_CONNECTION'),
|
||||
'table' => env('DB_CACHE_TABLE', 'cache'),
|
||||
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
|
||||
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
|
||||
],
|
||||
|
||||
'file' => [
|
||||
'driver' => 'file',
|
||||
'path' => storage_path('framework/cache/data'),
|
||||
'lock_path' => storage_path('framework/cache/data'),
|
||||
],
|
||||
|
||||
'memcached' => [
|
||||
'driver' => 'memcached',
|
||||
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
|
||||
'sasl' => [
|
||||
env('MEMCACHED_USERNAME'),
|
||||
env('MEMCACHED_PASSWORD'),
|
||||
],
|
||||
'options' => [
|
||||
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||
],
|
||||
'servers' => [
|
||||
[
|
||||
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
|
||||
'port' => env('MEMCACHED_PORT', 11211),
|
||||
'weight' => 100,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
|
||||
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||
],
|
||||
|
||||
'dynamodb' => [
|
||||
'driver' => 'dynamodb',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
|
||||
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||
],
|
||||
|
||||
'octane' => [
|
||||
'driver' => 'octane',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Key Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
|
||||
| stores, there might be other applications using the same cache. For
|
||||
| that reason, you may prefix every cache key to avoid collisions.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
|
||||
|
||||
];
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CORS Configuration Example
|
||||
*
|
||||
* Copy this to config/cors.php in your Laravel app if you need
|
||||
* to customize CORS settings beyond the defaults.
|
||||
*/
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cross-Origin Resource Sharing (CORS) Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Settings for handling Cross-Origin Resource Sharing (CORS).
|
||||
| Adjust these based on your API requirements.
|
||||
|
|
||||
*/
|
||||
|
||||
'paths' => ['api/*', 'sanctum/csrf-cookie'],
|
||||
|
||||
'allowed_methods' => ['*'],
|
||||
|
||||
'allowed_origins' => [
|
||||
env('FRONTEND_URL', 'http://localhost:3000'),
|
||||
// Add other allowed origins here
|
||||
],
|
||||
|
||||
'allowed_origins_patterns' => [],
|
||||
|
||||
'allowed_headers' => ['*'],
|
||||
|
||||
'exposed_headers' => [],
|
||||
|
||||
'max_age' => 0,
|
||||
|
||||
'supports_credentials' => true,
|
||||
|
||||
];
|
||||
173
src/config/database.php
Normal file
173
src/config/database.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Database Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which of the database connections below you wish
|
||||
| to use as your default connection for database operations. This is
|
||||
| the connection which will be utilized unless another connection
|
||||
| is explicitly specified when you execute a query / statement.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('DB_CONNECTION', 'sqlite'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Below are all of the database connections defined for your application.
|
||||
| An example configuration is provided for each database system which
|
||||
| is supported by Laravel. You're free to add / remove connections.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'url' => env('DB_URL'),
|
||||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||
'busy_timeout' => null,
|
||||
'journal_mode' => null,
|
||||
'synchronous' => null,
|
||||
],
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'mariadb' => [
|
||||
'driver' => 'mariadb',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '5432'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
'sqlsrv' => [
|
||||
'driver' => 'sqlsrv',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', '1433'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Migration Repository Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This table keeps track of all the migrations that have already run for
|
||||
| your application. Using this information, we can determine which of
|
||||
| the migrations on disk haven't actually been run on the database.
|
||||
|
|
||||
*/
|
||||
|
||||
'migrations' => [
|
||||
'table' => 'migrations',
|
||||
'update_date_on_publish' => true,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redis Databases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Redis is an open source, fast, and advanced key-value store that also
|
||||
| provides a richer body of commands than a typical key-value system
|
||||
| such as Memcached. You may define your connection settings here.
|
||||
|
|
||||
*/
|
||||
|
||||
'redis' => [
|
||||
|
||||
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
|
||||
],
|
||||
|
||||
'default' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_DB', '0'),
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_CACHE_DB', '1'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
80
src/config/filesystems.php
Normal file
80
src/config/filesystems.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Filesystem Disk
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default filesystem disk that should be used
|
||||
| by the framework. The "local" disk, as well as a variety of cloud
|
||||
| based disks are available to your application for file storage.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('FILESYSTEM_DISK', 'local'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Filesystem Disks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Below you may configure as many filesystem disks as necessary, and you
|
||||
| may even configure multiple disks for the same driver. Examples for
|
||||
| most supported storage drivers are configured here for reference.
|
||||
|
|
||||
| Supported drivers: "local", "ftp", "sftp", "s3"
|
||||
|
|
||||
*/
|
||||
|
||||
'disks' => [
|
||||
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/private'),
|
||||
'serve' => true,
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
's3' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION'),
|
||||
'bucket' => env('AWS_BUCKET'),
|
||||
'url' => env('AWS_URL'),
|
||||
'endpoint' => env('AWS_ENDPOINT'),
|
||||
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Symbolic Links
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the symbolic links that will be created when the
|
||||
| `storage:link` Artisan command is executed. The array keys should be
|
||||
| the locations of the links and the values should be their targets.
|
||||
|
|
||||
*/
|
||||
|
||||
'links' => [
|
||||
public_path('storage') => storage_path('app/public'),
|
||||
],
|
||||
|
||||
];
|
||||
132
src/config/logging.php
Normal file
132
src/config/logging.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Handler\SyslogUdpHandler;
|
||||
use Monolog\Processor\PsrLogMessageProcessor;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default log channel that is utilized to write
|
||||
| messages to your logs. The value provided here should match one of
|
||||
| the channels present in the list of "channels" configured below.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('LOG_CHANNEL', 'stack'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Deprecations Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the log channel that should be used to log warnings
|
||||
| regarding deprecated PHP and library features. This allows you to get
|
||||
| your application ready for upcoming major versions of dependencies.
|
||||
|
|
||||
*/
|
||||
|
||||
'deprecations' => [
|
||||
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
|
||||
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Log Channels
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the log channels for your application. Laravel
|
||||
| utilizes the Monolog PHP logging library, which includes a variety
|
||||
| of powerful log handlers and formatters that you're free to use.
|
||||
|
|
||||
| Available drivers: "single", "daily", "slack", "syslog",
|
||||
| "errorlog", "monolog", "custom", "stack"
|
||||
|
|
||||
*/
|
||||
|
||||
'channels' => [
|
||||
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => explode(',', env('LOG_STACK', 'single')),
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
'single' => [
|
||||
'driver' => 'single',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'daily' => [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'days' => env('LOG_DAILY_DAYS', 14),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'driver' => 'slack',
|
||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
|
||||
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
|
||||
'level' => env('LOG_LEVEL', 'critical'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'papertrail' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
|
||||
'handler_with' => [
|
||||
'host' => env('PAPERTRAIL_URL'),
|
||||
'port' => env('PAPERTRAIL_PORT'),
|
||||
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
|
||||
],
|
||||
'processors' => [PsrLogMessageProcessor::class],
|
||||
],
|
||||
|
||||
'stderr' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => StreamHandler::class,
|
||||
'formatter' => env('LOG_STDERR_FORMATTER'),
|
||||
'with' => [
|
||||
'stream' => 'php://stderr',
|
||||
],
|
||||
'processors' => [PsrLogMessageProcessor::class],
|
||||
],
|
||||
|
||||
'syslog' => [
|
||||
'driver' => 'syslog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'errorlog' => [
|
||||
'driver' => 'errorlog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => NullHandler::class,
|
||||
],
|
||||
|
||||
'emergency' => [
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
116
src/config/mail.php
Normal file
116
src/config/mail.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Mailer
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default mailer that is used to send all email
|
||||
| messages unless another mailer is explicitly specified when sending
|
||||
| the message. All additional mailers can be configured within the
|
||||
| "mailers" array. Examples of each type of mailer are provided.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('MAIL_MAILER', 'log'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mailer Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure all of the mailers used by your application plus
|
||||
| their respective settings. Several examples have been configured for
|
||||
| you and you are free to add your own as your application requires.
|
||||
|
|
||||
| Laravel supports a variety of mail "transport" drivers that can be used
|
||||
| when delivering an email. You may specify which one you're using for
|
||||
| your mailers below. You may also add additional mailers if needed.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||
| "postmark", "resend", "log", "array",
|
||||
| "failover", "roundrobin"
|
||||
|
|
||||
*/
|
||||
|
||||
'mailers' => [
|
||||
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'scheme' => env('MAIL_SCHEME'),
|
||||
'url' => env('MAIL_URL'),
|
||||
'host' => env('MAIL_HOST', '127.0.0.1'),
|
||||
'port' => env('MAIL_PORT', 2525),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'timeout' => null,
|
||||
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'transport' => 'ses',
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'transport' => 'postmark',
|
||||
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
|
||||
// 'client' => [
|
||||
// 'timeout' => 5,
|
||||
// ],
|
||||
],
|
||||
|
||||
'resend' => [
|
||||
'transport' => 'resend',
|
||||
],
|
||||
|
||||
'sendmail' => [
|
||||
'transport' => 'sendmail',
|
||||
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'transport' => 'log',
|
||||
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'transport' => 'array',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'transport' => 'failover',
|
||||
'mailers' => [
|
||||
'smtp',
|
||||
'log',
|
||||
],
|
||||
],
|
||||
|
||||
'roundrobin' => [
|
||||
'transport' => 'roundrobin',
|
||||
'mailers' => [
|
||||
'ses',
|
||||
'postmark',
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Global "From" Address
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may wish for all emails sent by your application to be sent from
|
||||
| the same address. Here you may specify a name and address that is
|
||||
| used globally for all emails that are sent by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'from' => [
|
||||
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
|
||||
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||
],
|
||||
|
||||
];
|
||||
202
src/config/permission.php
Normal file
202
src/config/permission.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'models' => [
|
||||
|
||||
/*
|
||||
* 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',
|
||||
],
|
||||
];
|
||||
112
src/config/queue.php
Normal file
112
src/config/queue.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Queue Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Laravel's queue supports a variety of backends via a single, unified
|
||||
| API, giving you convenient access to each backend using identical
|
||||
| syntax for each. The default queue connection is defined below.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('QUEUE_CONNECTION', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the connection options for every queue backend
|
||||
| used by your application. An example configuration is provided for
|
||||
| each backend supported by Laravel. You're also free to add more.
|
||||
|
|
||||
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sync' => [
|
||||
'driver' => 'sync',
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'connection' => env('DB_QUEUE_CONNECTION'),
|
||||
'table' => env('DB_QUEUE_TABLE', 'jobs'),
|
||||
'queue' => env('DB_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'beanstalkd' => [
|
||||
'driver' => 'beanstalkd',
|
||||
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
|
||||
'queue' => env('BEANSTALKD_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => 0,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'sqs' => [
|
||||
'driver' => 'sqs',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
|
||||
'queue' => env('SQS_QUEUE', 'default'),
|
||||
'suffix' => env('SQS_SUFFIX'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
|
||||
'queue' => env('REDIS_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => null,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Job Batching
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following options configure the database and table that store job
|
||||
| batching information. These options can be updated to any database
|
||||
| connection and table which has been defined by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'batching' => [
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'job_batches',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Failed Queue Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options configure the behavior of failed queue job logging so you
|
||||
| can control how and where failed jobs are stored. Laravel ships with
|
||||
| support for storing failed jobs in a simple file or in a database.
|
||||
|
|
||||
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => [
|
||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'failed_jobs',
|
||||
],
|
||||
|
||||
];
|
||||
84
src/config/sanctum.php
Normal file
84
src/config/sanctum.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Stateful Domains
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Requests from the following domains / hosts will receive stateful API
|
||||
| authentication cookies. Typically, these should include your local
|
||||
| and production domains which access your API via a frontend SPA.
|
||||
|
|
||||
*/
|
||||
|
||||
'stateful' => 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,
|
||||
],
|
||||
|
||||
];
|
||||
38
src/config/services.php
Normal file
38
src/config/services.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Third Party Services
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This file is for storing the credentials for third party services such
|
||||
| as Mailgun, Postmark, AWS and more. This file provides the de facto
|
||||
| location for this type of information, allowing packages to have
|
||||
| a conventional file to locate the various service credentials.
|
||||
|
|
||||
*/
|
||||
|
||||
'postmark' => [
|
||||
'token' => env('POSTMARK_TOKEN'),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
'resend' => [
|
||||
'key' => env('RESEND_KEY'),
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'notifications' => [
|
||||
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
|
||||
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
217
src/config/session.php
Normal file
217
src/config/session.php
Normal file
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Session Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines the default session driver that is utilized for
|
||||
| incoming requests. Laravel supports a variety of storage options to
|
||||
| persist session data. Database storage is a great default choice.
|
||||
|
|
||||
| Supported: "file", "cookie", "database", "apc",
|
||||
| "memcached", "redis", "dynamodb", "array"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('SESSION_DRIVER', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Lifetime
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the number of minutes that you wish the session
|
||||
| to be allowed to remain idle before it expires. If you want them
|
||||
| to expire immediately when the browser is closed then you may
|
||||
| indicate that via the expire_on_close configuration option.
|
||||
|
|
||||
*/
|
||||
|
||||
'lifetime' => (int) env('SESSION_LIFETIME', 120),
|
||||
|
||||
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Encryption
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to easily specify that all of your session data
|
||||
| should be encrypted before it's stored. All encryption is performed
|
||||
| automatically by Laravel and you may use the session like normal.
|
||||
|
|
||||
*/
|
||||
|
||||
'encrypt' => env('SESSION_ENCRYPT', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session File Location
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the "file" session driver, the session files are placed
|
||||
| on disk. The default storage location is defined here; however, you
|
||||
| are free to provide another location where they should be stored.
|
||||
|
|
||||
*/
|
||||
|
||||
'files' => storage_path('framework/sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" or "redis" session drivers, you may specify a
|
||||
| connection that should be used to manage these sessions. This should
|
||||
| correspond to a connection in your database configuration options.
|
||||
|
|
||||
*/
|
||||
|
||||
'connection' => env('SESSION_CONNECTION'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" session driver, you may specify the table to
|
||||
| be used to store sessions. Of course, a sensible default is defined
|
||||
| for you; however, you're welcome to change this to another table.
|
||||
|
|
||||
*/
|
||||
|
||||
'table' => env('SESSION_TABLE', 'sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using one of the framework's cache driven session backends, you may
|
||||
| define the cache store which should be used to store the session data
|
||||
| between requests. This must match one of your defined cache stores.
|
||||
|
|
||||
| Affects: "apc", "dynamodb", "memcached", "redis"
|
||||
|
|
||||
*/
|
||||
|
||||
'store' => env('SESSION_STORE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Sweeping Lottery
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Some session drivers must manually sweep their storage location to get
|
||||
| rid of old sessions from storage. Here are the chances that it will
|
||||
| happen on a given request. By default, the odds are 2 out of 100.
|
||||
|
|
||||
*/
|
||||
|
||||
'lottery' => [2, 100],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may change the name of the session cookie that is created by
|
||||
| the framework. Typically, you should not need to change this value
|
||||
| since doing so does not grant a meaningful security improvement.
|
||||
|
|
||||
*/
|
||||
|
||||
'cookie' => env(
|
||||
'SESSION_COOKIE',
|
||||
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The session cookie path determines the path for which the cookie will
|
||||
| be regarded as available. Typically, this will be the root path of
|
||||
| your application, but you're free to change this when necessary.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => env('SESSION_PATH', '/'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the domain and subdomains the session cookie is
|
||||
| available to. By default, the cookie will be available to the root
|
||||
| domain and all subdomains. Typically, this shouldn't be changed.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('SESSION_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTPS Only Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By setting this option to true, session cookies will only be sent back
|
||||
| to the server if the browser has a HTTPS connection. This will keep
|
||||
| the cookie from being sent to you when it can't be done securely.
|
||||
|
|
||||
*/
|
||||
|
||||
'secure' => env('SESSION_SECURE_COOKIE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTP Access Only
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will prevent JavaScript from accessing the
|
||||
| value of the cookie and the cookie will only be accessible through
|
||||
| the HTTP protocol. It's unlikely you should disable this option.
|
||||
|
|
||||
*/
|
||||
|
||||
'http_only' => env('SESSION_HTTP_ONLY', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Same-Site Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines how your cookies behave when cross-site requests
|
||||
| take place, and can be used to mitigate CSRF attacks. By default, we
|
||||
| will set this value to "lax" to permit secure cross-site requests.
|
||||
|
|
||||
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
||||
|
|
||||
| Supported: "lax", "strict", "none", null
|
||||
|
|
||||
*/
|
||||
|
||||
'same_site' => env('SESSION_SAME_SITE', 'lax'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Partitioned Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will tie the cookie to the top-level site for
|
||||
| a cross-site context. Partitioned cookies are accepted by the browser
|
||||
| when flagged "secure" and the Same-Site attribute is set to "none".
|
||||
|
|
||||
*/
|
||||
|
||||
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
|
||||
|
||||
];
|
||||
44
src/database/factories/UserFactory.php
Normal file
44
src/database/factories/UserFactory.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The current password being used by the factory.
|
||||
*/
|
||||
protected static ?string $password;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => static::$password ??= Hash::make('password'),
|
||||
'remember_token' => Str::random(10),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the model's email address should be unverified.
|
||||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('sessions', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->longText('payload');
|
||||
$table->integer('last_activity')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('sessions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cache', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->mediumText('value');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->longText('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jobs');
|
||||
Schema::dropIfExists('job_batches');
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('settings', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$teams = config('permission.teams');
|
||||
$tableNames = config('permission.table_names');
|
||||
$columnNames = config('permission.column_names');
|
||||
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||
|
||||
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
|
||||
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||
// $table->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']);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('audits', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('audits');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
18
src/database/seeders/DatabaseSeeder.php
Normal file
18
src/database/seeders/DatabaseSeeder.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->call([
|
||||
RolePermissionSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
42
src/database/seeders/RolePermissionSeeder.php
Normal file
42
src/database/seeders/RolePermissionSeeder.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use App\Models\User;
|
||||
|
||||
class RolePermissionSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
app()[\Spatie\Permission\PermissionRegistrar::class]->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');
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/package.json
Normal file
19
src/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"alpinejs": "^3.4.2",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"axios": "^1.7.4",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-vite-plugin": "^1.2.0",
|
||||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.1.0",
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
33
src/phpunit.xml
Normal file
33
src/phpunit.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory>tests/Unit</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Feature">
|
||||
<directory>tests/Feature</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory>app</directory>
|
||||
</include>
|
||||
</source>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="CACHE_STORE" value="array"/>
|
||||
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
|
||||
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
<env name="PULSE_ENABLED" value="false"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"preset": "laravel",
|
||||
"rules": {
|
||||
"array_indentation": true,
|
||||
"array_syntax": {
|
||||
"syntax": "short"
|
||||
},
|
||||
"binary_operator_spaces": {
|
||||
"default": "single_space"
|
||||
},
|
||||
"blank_line_after_namespace": true,
|
||||
"blank_line_after_opening_tag": true,
|
||||
"blank_line_before_statement": {
|
||||
"statements": ["return", "throw", "try"]
|
||||
},
|
||||
"class_attributes_separation": {
|
||||
"elements": {
|
||||
"method": "one"
|
||||
}
|
||||
},
|
||||
"concat_space": {
|
||||
"spacing": "none"
|
||||
},
|
||||
"declare_strict_types": false,
|
||||
"fully_qualified_strict_types": true,
|
||||
"method_argument_space": {
|
||||
"on_multiline": "ensure_fully_multiline",
|
||||
"keep_multiple_spaces_after_comma": false
|
||||
},
|
||||
"no_unused_imports": true,
|
||||
"ordered_imports": {
|
||||
"sort_algorithm": "alpha"
|
||||
},
|
||||
"single_quote": true,
|
||||
"trailing_comma_in_multiline": {
|
||||
"elements": ["arrays", "arguments", "parameters"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"vendor",
|
||||
"node_modules",
|
||||
"storage",
|
||||
"bootstrap/cache"
|
||||
]
|
||||
}
|
||||
6
src/postcss.config.js
Normal file
6
src/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
25
src/public/.htaccess
Normal file
25
src/public/.htaccess
Normal file
@@ -0,0 +1,25 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
<IfModule mod_negotiation.c>
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# Handle X-XSRF-Token Header
|
||||
RewriteCond %{HTTP:x-xsrf-token} .
|
||||
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
|
||||
|
||||
# Redirect Trailing Slashes If Not A Folder...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [L,R=301]
|
||||
|
||||
# Send Requests To Front Controller...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
</IfModule>
|
||||
1
src/public/css/filament/filament/app.css
Normal file
1
src/public/css/filament/filament/app.css
Normal file
File diff suppressed because one or more lines are too long
49
src/public/css/filament/forms/forms.css
Normal file
49
src/public/css/filament/forms/forms.css
Normal file
File diff suppressed because one or more lines are too long
1
src/public/css/filament/support/support.css
Normal file
1
src/public/css/filament/support/support.css
Normal file
@@ -0,0 +1 @@
|
||||
.fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3}
|
||||
0
src/public/favicon.ico
Normal file
0
src/public/favicon.ico
Normal file
17
src/public/index.php
Normal file
17
src/public/index.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Determine if the application is in maintenance mode...
|
||||
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||
require $maintenance;
|
||||
}
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the request...
|
||||
(require_once __DIR__.'/../bootstrap/app.php')
|
||||
->handleRequest(Request::capture());
|
||||
1
src/public/js/filament/filament/app.js
Normal file
1
src/public/js/filament/filament/app.js
Normal file
File diff suppressed because one or more lines are too long
13
src/public/js/filament/filament/echo.js
Normal file
13
src/public/js/filament/filament/echo.js
Normal file
File diff suppressed because one or more lines are too long
1
src/public/js/filament/forms/components/color-picker.js
Normal file
1
src/public/js/filament/forms/components/color-picker.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
123
src/public/js/filament/forms/components/file-upload.js
Normal file
123
src/public/js/filament/forms/components/file-upload.js
Normal file
File diff suppressed because one or more lines are too long
1
src/public/js/filament/forms/components/key-value.js
Normal file
1
src/public/js/filament/forms/components/key-value.js
Normal file
@@ -0,0 +1 @@
|
||||
function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default};
|
||||
51
src/public/js/filament/forms/components/markdown-editor.js
Normal file
51
src/public/js/filament/forms/components/markdown-editor.js
Normal file
File diff suppressed because one or more lines are too long
150
src/public/js/filament/forms/components/rich-editor.js
Normal file
150
src/public/js/filament/forms/components/rich-editor.js
Normal file
File diff suppressed because one or more lines are too long
6
src/public/js/filament/forms/components/select.js
Normal file
6
src/public/js/filament/forms/components/select.js
Normal file
File diff suppressed because one or more lines are too long
1
src/public/js/filament/forms/components/tags-input.js
Normal file
1
src/public/js/filament/forms/components/tags-input.js
Normal file
@@ -0,0 +1 @@
|
||||
function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default};
|
||||
1
src/public/js/filament/forms/components/textarea.js
Normal file
1
src/public/js/filament/forms/components/textarea.js
Normal file
@@ -0,0 +1 @@
|
||||
function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init:function(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user