generated from theradcoza/Laravel-Docker-Dev-Template
Initial commit
This commit is contained in:
29
.env.example
Normal file
29
.env.example
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Docker Compose Configuration
|
||||||
|
APP_PORT=8080
|
||||||
|
REDIS_PORT=6379
|
||||||
|
MAIL_PORT=1025
|
||||||
|
MAIL_DASHBOARD_PORT=8025
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# DATABASE CONFIGURATION
|
||||||
|
# ============================================
|
||||||
|
# Choose one: mysql, pgsql, sqlite
|
||||||
|
# Start Docker with: docker-compose --profile mysql up -d
|
||||||
|
# docker-compose --profile pgsql up -d
|
||||||
|
# docker-compose --profile sqlite up -d
|
||||||
|
|
||||||
|
# Common settings
|
||||||
|
DB_DATABASE=laravel
|
||||||
|
DB_USERNAME=laravel
|
||||||
|
DB_PASSWORD=secret
|
||||||
|
|
||||||
|
# MySQL specific
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_ROOT_PASSWORD=rootsecret
|
||||||
|
|
||||||
|
# PostgreSQL specific (uses DB_PORT=5432 by default)
|
||||||
|
# Uncomment if using PostgreSQL:
|
||||||
|
# DB_PORT=5432
|
||||||
|
|
||||||
|
# SQLite specific
|
||||||
|
# No additional settings needed - uses database/database.sqlite
|
||||||
145
.github/workflows/ci.yml
vendored
Normal file
145
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, develop]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
env:
|
||||||
|
MYSQL_ROOT_PASSWORD: password
|
||||||
|
MYSQL_DATABASE: laravel_test
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
||||||
|
options: >-
|
||||||
|
--health-cmd="mysqladmin ping"
|
||||||
|
--health-interval=10s
|
||||||
|
--health-timeout=5s
|
||||||
|
--health-retries=3
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
options: >-
|
||||||
|
--health-cmd="redis-cli ping"
|
||||||
|
--health-interval=10s
|
||||||
|
--health-timeout=5s
|
||||||
|
--health-retries=3
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.2'
|
||||||
|
extensions: mbstring, dom, fileinfo, mysql, redis
|
||||||
|
coverage: pcov
|
||||||
|
|
||||||
|
- name: Get Composer cache directory
|
||||||
|
id: composer-cache
|
||||||
|
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache Composer dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||||
|
restore-keys: ${{ runner.os }}-composer-
|
||||||
|
|
||||||
|
- name: Install Composer dependencies
|
||||||
|
working-directory: ./src
|
||||||
|
run: composer install --no-progress --prefer-dist --optimize-autoloader
|
||||||
|
|
||||||
|
- name: Copy .env
|
||||||
|
working-directory: ./src
|
||||||
|
run: |
|
||||||
|
cp .env.example .env
|
||||||
|
php artisan key:generate
|
||||||
|
|
||||||
|
- name: Configure test environment
|
||||||
|
working-directory: ./src
|
||||||
|
run: |
|
||||||
|
echo "DB_CONNECTION=mysql" >> .env
|
||||||
|
echo "DB_HOST=127.0.0.1" >> .env
|
||||||
|
echo "DB_PORT=3306" >> .env
|
||||||
|
echo "DB_DATABASE=laravel_test" >> .env
|
||||||
|
echo "DB_USERNAME=root" >> .env
|
||||||
|
echo "DB_PASSWORD=password" >> .env
|
||||||
|
echo "REDIS_HOST=127.0.0.1" >> .env
|
||||||
|
echo "CACHE_DRIVER=redis" >> .env
|
||||||
|
echo "QUEUE_CONNECTION=redis" >> .env
|
||||||
|
|
||||||
|
- name: Run migrations
|
||||||
|
working-directory: ./src
|
||||||
|
run: php artisan migrate --force
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
working-directory: ./src
|
||||||
|
run: php artisan test --parallel
|
||||||
|
|
||||||
|
- name: Check code style
|
||||||
|
working-directory: ./src
|
||||||
|
run: ./vendor/bin/pint --test
|
||||||
|
|
||||||
|
deploy-staging:
|
||||||
|
needs: tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Deploy to staging
|
||||||
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.STAGING_HOST }}
|
||||||
|
username: ${{ secrets.STAGING_USER }}
|
||||||
|
key: ${{ secrets.STAGING_SSH_KEY }}
|
||||||
|
script: |
|
||||||
|
cd /var/www/staging
|
||||||
|
git pull origin develop
|
||||||
|
composer install --no-dev --optimize-autoloader
|
||||||
|
php artisan migrate --force
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
php artisan queue:restart
|
||||||
|
|
||||||
|
deploy-production:
|
||||||
|
needs: tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||||
|
environment: production
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Deploy to production
|
||||||
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.PRODUCTION_HOST }}
|
||||||
|
username: ${{ secrets.PRODUCTION_USER }}
|
||||||
|
key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||||
|
script: |
|
||||||
|
cd /var/www/production
|
||||||
|
php artisan down
|
||||||
|
git pull origin main
|
||||||
|
composer install --no-dev --optimize-autoloader
|
||||||
|
php artisan migrate --force
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
php artisan queue:restart
|
||||||
|
php artisan up
|
||||||
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Docker volumes
|
||||||
|
mysql_data/
|
||||||
|
redis_data/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Laravel - commit most files, ignore generated/local files
|
||||||
|
src/.env
|
||||||
|
src/vendor/
|
||||||
|
src/node_modules/
|
||||||
|
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
|
||||||
0
.windsurf/workflows/js.md
Normal file
0
.windsurf/workflows/js.md
Normal file
343
AI_CONTEXT.md
Normal file
343
AI_CONTEXT.md
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
# AI Assistant Context
|
||||||
|
|
||||||
|
This document provides context for AI coding assistants working on projects built with this template.
|
||||||
|
|
||||||
|
## Template Overview
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|-------|------------|
|
||||||
|
| **Backend** | Laravel 11+, PHP 8.2+ |
|
||||||
|
| **Frontend** | Blade, Livewire (NO Vue/React/Inertia) |
|
||||||
|
| **Admin** | Filament 3.x |
|
||||||
|
| **Database** | MySQL 8 / PostgreSQL 15 / SQLite |
|
||||||
|
| **Cache/Queue** | Redis |
|
||||||
|
| **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:
|
||||||
|
- Blade templates
|
||||||
|
- Livewire for reactivity
|
||||||
|
- Alpine.js (included with Livewire)
|
||||||
|
- Tailwind CSS
|
||||||
|
|
||||||
|
Do NOT suggest or implement Vue/React/Inertia solutions.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Module Structure
|
||||||
|
|
||||||
|
Features are organized as **modules** in `app/Modules/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
app/Modules/
|
||||||
|
└── ModuleName/
|
||||||
|
├── Config/
|
||||||
|
│ └── module_name.php # Module config (incl. audit settings)
|
||||||
|
├── Database/
|
||||||
|
│ ├── Migrations/
|
||||||
|
│ └── Seeders/
|
||||||
|
├── Filament/
|
||||||
|
│ └── Resources/ # Admin CRUD + Audit Log
|
||||||
|
├── Http/
|
||||||
|
│ ├── Controllers/
|
||||||
|
│ ├── Middleware/
|
||||||
|
│ └── Requests/
|
||||||
|
├── Models/ # With ModuleAuditable trait
|
||||||
|
├── Policies/
|
||||||
|
├── Services/ # Business logic goes here
|
||||||
|
├── Routes/
|
||||||
|
│ ├── web.php
|
||||||
|
│ └── api.php
|
||||||
|
├── Resources/
|
||||||
|
│ ├── views/
|
||||||
|
│ ├── css/
|
||||||
|
│ └── lang/
|
||||||
|
├── Permissions.php # Module permissions
|
||||||
|
└── ModuleNameServiceProvider.php
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating Modules
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic module
|
||||||
|
php artisan make:module ModuleName
|
||||||
|
|
||||||
|
# With model + Filament resource + audit
|
||||||
|
php artisan make:module ModuleName --model=ModelName
|
||||||
|
|
||||||
|
# With API routes
|
||||||
|
php artisan make:module ModuleName --model=ModelName --api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Traits and Interfaces
|
||||||
|
|
||||||
|
**For auditable models:**
|
||||||
|
```php
|
||||||
|
use App\Traits\ModuleAuditable;
|
||||||
|
use OwenIt\Auditing\Contracts\Auditable;
|
||||||
|
|
||||||
|
class YourModel extends Model implements Auditable
|
||||||
|
{
|
||||||
|
use ModuleAuditable;
|
||||||
|
|
||||||
|
// Exclude sensitive fields from audit
|
||||||
|
protected $auditExclude = ['password', 'secret_key'];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Locations
|
||||||
|
|
||||||
|
| What | Where |
|
||||||
|
|------|-------|
|
||||||
|
| Laravel app | `src/` |
|
||||||
|
| Docker configs | `docker/` |
|
||||||
|
| Production deploy | `deploy/` |
|
||||||
|
| Setup scripts | `scripts/` |
|
||||||
|
| Documentation | `docs/` |
|
||||||
|
| Module code | `src/app/Modules/` |
|
||||||
|
| Filament resources | `src/app/Modules/*/Filament/Resources/` |
|
||||||
|
| Module views | `src/app/Modules/*/Resources/views/` |
|
||||||
|
| Module routes | `src/app/Modules/*/Routes/` |
|
||||||
|
| Module migrations | `src/app/Modules/*/Database/Migrations/` |
|
||||||
|
|
||||||
|
## Common Tasks
|
||||||
|
|
||||||
|
### Add a New Feature
|
||||||
|
|
||||||
|
1. Create module: `php artisan make:module FeatureName --model=MainModel`
|
||||||
|
2. Edit migration: `app/Modules/FeatureName/Database/Migrations/`
|
||||||
|
3. Update model fillables: `app/Modules/FeatureName/Models/`
|
||||||
|
4. Customize Filament resource: `app/Modules/FeatureName/Filament/Resources/`
|
||||||
|
5. Run migrations: `php artisan migrate`
|
||||||
|
6. Seed permissions: `php artisan db:seed --class=PermissionSeeder`
|
||||||
|
|
||||||
|
### Add a Model to Existing Module
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create model manually
|
||||||
|
php artisan make:model Modules/ModuleName/Models/NewModel -m
|
||||||
|
|
||||||
|
# Move migration to module folder
|
||||||
|
mv database/migrations/*_create_new_models_table.php \
|
||||||
|
app/Modules/ModuleName/Database/Migrations/
|
||||||
|
|
||||||
|
# Add audit trait to model
|
||||||
|
# Create Filament resource manually or use:
|
||||||
|
php artisan make:filament-resource NewModel \
|
||||||
|
--model=App\\Modules\\ModuleName\\Models\\NewModel
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add API Endpoint
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Modules/ModuleName/Routes/api.php
|
||||||
|
Route::prefix('api/module-name')
|
||||||
|
->middleware(['api', 'auth:sanctum'])
|
||||||
|
->group(function () {
|
||||||
|
Route::get('/items', [ItemController::class, 'index']);
|
||||||
|
Route::post('/items', [ItemController::class, 'store']);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add Permission
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Modules/ModuleName/Permissions.php
|
||||||
|
return [
|
||||||
|
'module_name.view' => 'View Module',
|
||||||
|
'module_name.create' => 'Create records',
|
||||||
|
'module_name.edit' => 'Edit records',
|
||||||
|
'module_name.delete' => 'Delete records',
|
||||||
|
'module_name.new_action' => 'New custom action', // Add this
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run: `php artisan db:seed --class=PermissionSeeder`
|
||||||
|
|
||||||
|
### Customize Filament Resource
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Modules/ModuleName/Filament/Resources/ModelResource.php
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form->schema([
|
||||||
|
Forms\Components\TextInput::make('name')->required(),
|
||||||
|
Forms\Components\Select::make('status')
|
||||||
|
->options(['active' => 'Active', 'inactive' => 'Inactive']),
|
||||||
|
// Add more fields
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table->columns([
|
||||||
|
Tables\Columns\TextColumn::make('name')->searchable(),
|
||||||
|
Tables\Columns\BadgeColumn::make('status'),
|
||||||
|
// Add more columns
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
- Run commands via `make shell` then `php artisan ...`
|
||||||
|
- Or use `make artisan cmd='...'`
|
||||||
|
- Database: accessible at `localhost:3306` (MySQL) or `localhost:5432` (PostgreSQL)
|
||||||
|
- Mail: caught by Mailpit at `localhost:8025`
|
||||||
|
|
||||||
|
### Production
|
||||||
|
|
||||||
|
- No Docker - native PHP on Ubuntu 24.04
|
||||||
|
- Web server: Nginx or Apache
|
||||||
|
- Use `deploy/scripts/server-setup.sh` for initial setup
|
||||||
|
- Use `deploy/scripts/deploy.sh` for deployments
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- **Follow Laravel conventions**
|
||||||
|
- **Use Pint for formatting**: `make lint`
|
||||||
|
- **Business logic in Services**, not Controllers
|
||||||
|
- **Use Form Requests** for validation
|
||||||
|
- **Use Policies** for authorization
|
||||||
|
- **Use Resources** for API responses
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests
|
||||||
|
make test
|
||||||
|
|
||||||
|
# With coverage
|
||||||
|
make test-coverage
|
||||||
|
|
||||||
|
# Create test
|
||||||
|
php artisan make:test Modules/ModuleName/FeatureTest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
1. **Check Laravel logs**: `storage/logs/laravel.log`
|
||||||
|
2. **Check container logs**: `make logs`
|
||||||
|
3. **Use Telescope** (if installed): `/telescope`
|
||||||
|
4. **Use Tinker**: `make tinker`
|
||||||
|
5. **Ignition** shows errors in browser (dev only)
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
1. **Module views use namespace**: `view('module-slug::viewname')`
|
||||||
|
2. **Module routes are prefixed**: `/module-slug/...`
|
||||||
|
3. **Permissions use snake_case**: `module_name.action`
|
||||||
|
4. **Filament resources auto-discover** from `Filament/Resources/`
|
||||||
|
5. **Migrations auto-load** from `Database/Migrations/`
|
||||||
|
6. **Always run PermissionSeeder** after adding permissions
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Artisan Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan make:module Name # Create module
|
||||||
|
php artisan make:module Name --model=M # With model
|
||||||
|
php artisan make:filament-resource Name # Filament resource
|
||||||
|
php artisan make:model Name -m # Model + migration
|
||||||
|
php artisan make:controller Name # Controller
|
||||||
|
php artisan make:request Name # Form request
|
||||||
|
php artisan make:policy Name # Policy
|
||||||
|
php artisan migrate # Run migrations
|
||||||
|
php artisan db:seed # Run seeders
|
||||||
|
php artisan config:clear # Clear config cache
|
||||||
|
php artisan route:list # List routes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Make Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make up # Start containers
|
||||||
|
make down # Stop containers
|
||||||
|
make shell # Shell into app
|
||||||
|
make artisan cmd='...' # Run artisan
|
||||||
|
make composer cmd='...' # Run composer
|
||||||
|
make test # Run tests
|
||||||
|
make lint # Fix code style
|
||||||
|
make fresh # Reset database
|
||||||
|
make logs # View logs
|
||||||
|
make queue-start # Start queue worker
|
||||||
|
make queue-logs # View queue logs
|
||||||
|
make scheduler-start # Start scheduler
|
||||||
|
make backup # Backup database
|
||||||
|
make restore file=... # Restore database
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Links
|
||||||
|
|
||||||
|
- [GETTING_STARTED.md](GETTING_STARTED.md) - Setup walkthrough
|
||||||
|
- [README.md](README.md) - Overview
|
||||||
|
- [docs/modules.md](docs/modules.md) - Module system
|
||||||
|
- [docs/audit-trail.md](docs/audit-trail.md) - Audit configuration
|
||||||
|
- [docs/filament-admin.md](docs/filament-admin.md) - Admin panel
|
||||||
|
- [docs/laravel-setup.md](docs/laravel-setup.md) - Laravel setup options
|
||||||
|
- [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) - GitHub Actions
|
||||||
|
- [docs/backup.md](docs/backup.md) - Database backup
|
||||||
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
|
||||||
364
GETTING_STARTED.md
Normal file
364
GETTING_STARTED.md
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
# Getting Started
|
||||||
|
|
||||||
|
This guide walks you through setting up your Laravel development environment using this template.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Docker & Docker Compose
|
||||||
|
- Git
|
||||||
|
|
||||||
|
## 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. Run setup (MySQL is default)
|
||||||
|
./setup.sh # Linux/Mac
|
||||||
|
setup.bat # Windows
|
||||||
|
|
||||||
|
# Or choose a different database:
|
||||||
|
./setup.sh pgsql # PostgreSQL
|
||||||
|
./setup.sh sqlite # SQLite
|
||||||
|
|
||||||
|
# 3. Access your app
|
||||||
|
# Laravel: http://localhost:8080
|
||||||
|
# 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 the Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/your-repo/Laravel-Docker-Dev-Template.git my-project
|
||||||
|
cd my-project
|
||||||
|
|
||||||
|
# Optional: Remove template git history and start fresh
|
||||||
|
rm -rf .git
|
||||||
|
git init
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial commit"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Choose Your Database
|
||||||
|
|
||||||
|
| Database | Best For | Command |
|
||||||
|
|----------|----------|---------|
|
||||||
|
| **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. Run Setup Script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./setup.sh mysql # Linux/Mac
|
||||||
|
setup.bat mysql # Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
- ✅ Configure environment for chosen database
|
||||||
|
- ✅ Install composer dependencies
|
||||||
|
- ✅ Build and start Docker containers
|
||||||
|
- ✅ Run database migrations
|
||||||
|
- ✅ Create admin user automatically
|
||||||
|
|
||||||
|
### 4. Start Developing
|
||||||
|
|
||||||
|
Your application is now ready! The setup script created an admin user for you:
|
||||||
|
|
||||||
|
**Admin Login:**
|
||||||
|
- Email: `admin@example.com`
|
||||||
|
- Password: `password`
|
||||||
|
|
||||||
|
**Access Points:**
|
||||||
|
- Public site: http://localhost:8080
|
||||||
|
- Admin panel: http://localhost:8080/admin
|
||||||
|
- Site settings: http://localhost:8080/admin/settings
|
||||||
|
- Email testing: http://localhost:8025
|
||||||
|
- API endpoints: http://localhost:8080/api/*
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Shell into container
|
||||||
|
make shell
|
||||||
|
|
||||||
|
# Create a module with a model
|
||||||
|
php artisan make:module Inventory --model=Product
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
php artisan migrate
|
||||||
|
|
||||||
|
# Seed permissions
|
||||||
|
php artisan db:seed --class=PermissionSeeder
|
||||||
|
```
|
||||||
|
|
||||||
|
Your module is now at:
|
||||||
|
- Frontend: http://localhost:8080/inventory
|
||||||
|
- Admin: http://localhost:8080/admin → Inventory section
|
||||||
|
|
||||||
|
## Project Structure After Setup
|
||||||
|
|
||||||
|
```
|
||||||
|
my-project/
|
||||||
|
├── src/ # Laravel application
|
||||||
|
│ ├── app/
|
||||||
|
│ │ ├── Modules/ # Your feature modules
|
||||||
|
│ │ │ └── Inventory/
|
||||||
|
│ │ │ ├── Config/
|
||||||
|
│ │ │ ├── Database/
|
||||||
|
│ │ │ ├── Filament/ # Admin resources
|
||||||
|
│ │ │ ├── Http/
|
||||||
|
│ │ │ ├── Models/
|
||||||
|
│ │ │ └── Routes/
|
||||||
|
│ │ ├── Providers/
|
||||||
|
│ │ └── Traits/
|
||||||
|
│ │ └── ModuleAuditable.php
|
||||||
|
│ ├── config/
|
||||||
|
│ ├── database/
|
||||||
|
│ ├── resources/
|
||||||
|
│ │ └── views/
|
||||||
|
│ │ └── errors/ # Custom error pages
|
||||||
|
│ └── routes/
|
||||||
|
├── docker/ # Docker configuration
|
||||||
|
├── deploy/ # Production deployment
|
||||||
|
└── docs/ # Documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `make up` | Start containers |
|
||||||
|
| `make down` | Stop containers |
|
||||||
|
| `make shell` | Shell into app container |
|
||||||
|
| `make logs` | View container logs |
|
||||||
|
| `make artisan cmd='...'` | Run artisan command |
|
||||||
|
| `make composer cmd='...'` | Run composer command |
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `make migrate` | Run migrations |
|
||||||
|
| `make fresh` | Fresh migrate + seed |
|
||||||
|
| `make tinker` | Laravel Tinker REPL |
|
||||||
|
|
||||||
|
### Testing & Quality
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `make test` | Run tests |
|
||||||
|
| `make lint` | Fix code style (Pint) |
|
||||||
|
| `make lint-check` | Check code style |
|
||||||
|
|
||||||
|
### Queues & Backup
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `make queue-start` | Start background job worker |
|
||||||
|
| `make queue-logs` | View queue worker logs |
|
||||||
|
| `make scheduler-start` | Start Laravel scheduler |
|
||||||
|
| `make backup` | Backup database |
|
||||||
|
| `make restore file=...` | Restore from backup |
|
||||||
|
|
||||||
|
### Modules
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a complete module with CRUD, views, tests, and Filament resource
|
||||||
|
php artisan make:module ProductCatalog
|
||||||
|
|
||||||
|
# This creates:
|
||||||
|
# - Model, Controller, Routes, Views
|
||||||
|
# - Migration and Filament Resource
|
||||||
|
# - Pest tests
|
||||||
|
# - See app/Modules/README.md for details
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `.env` | Docker Compose config |
|
||||||
|
| `src/.env` | Laravel app config |
|
||||||
|
| `src/.env.mysql` | MySQL preset |
|
||||||
|
| `src/.env.pgsql` | PostgreSQL preset |
|
||||||
|
| `src/.env.sqlite` | SQLite preset |
|
||||||
|
|
||||||
|
### Key Environment Variables
|
||||||
|
|
||||||
|
```env
|
||||||
|
# App
|
||||||
|
APP_NAME="My App"
|
||||||
|
APP_ENV=local
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_URL=http://localhost:8080
|
||||||
|
|
||||||
|
# Database (set by profile)
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=mysql
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=laravel
|
||||||
|
DB_USERNAME=laravel
|
||||||
|
DB_PASSWORD=secret
|
||||||
|
|
||||||
|
# Error Tracking
|
||||||
|
FLARE_KEY=your_key_here
|
||||||
|
|
||||||
|
# Ignition (dev)
|
||||||
|
IGNITION_THEME=auto
|
||||||
|
IGNITION_EDITOR=vscode
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ports
|
||||||
|
|
||||||
|
| Service | Port | URL |
|
||||||
|
|---------|------|-----|
|
||||||
|
| Laravel | 8080 | http://localhost:8080 |
|
||||||
|
| MySQL | 3306 | localhost:3306 |
|
||||||
|
| PostgreSQL | 5432 | localhost:5432 |
|
||||||
|
| Redis | 6379 | localhost:6379 |
|
||||||
|
| Mailpit UI | 8025 | http://localhost:8025 |
|
||||||
|
| Mailpit SMTP | 1025 | localhost:1025 |
|
||||||
|
|
||||||
|
## 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
|
||||||
|
# Check logs
|
||||||
|
make logs
|
||||||
|
|
||||||
|
# Reset everything
|
||||||
|
make clean
|
||||||
|
make install DB=mysql
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make shell
|
||||||
|
chmod -R 775 storage bootstrap/cache
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database connection refused
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Wait for database to be ready
|
||||||
|
docker-compose exec mysql mysqladmin ping -h localhost
|
||||||
|
|
||||||
|
# Or restart
|
||||||
|
make down
|
||||||
|
make up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Artisan commands fail
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Make sure you're in the container
|
||||||
|
make shell
|
||||||
|
|
||||||
|
# Then run artisan
|
||||||
|
php artisan migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
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
|
||||||
|
- [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
|
||||||
176
Makefile
Normal file
176
Makefile
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# Laravel Docker Development Makefile
|
||||||
|
|
||||||
|
.PHONY: help install up down restart build logs shell composer artisan npm test fresh
|
||||||
|
|
||||||
|
# Database profile (default: mysql)
|
||||||
|
DB ?= mysql
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
help:
|
||||||
|
@echo "Laravel Docker Development Commands"
|
||||||
|
@echo "===================================="
|
||||||
|
@echo ""
|
||||||
|
@echo "Setup:"
|
||||||
|
@echo " make install DB=mysql - First-time setup with MySQL"
|
||||||
|
@echo " make install DB=pgsql - First-time setup with PostgreSQL"
|
||||||
|
@echo " make install DB=sqlite - First-time setup with SQLite"
|
||||||
|
@echo " make build - Build Docker images"
|
||||||
|
@echo ""
|
||||||
|
@echo "Docker (use DB=mysql|pgsql|sqlite):"
|
||||||
|
@echo " make up - Start containers (default: MySQL)"
|
||||||
|
@echo " make up DB=pgsql - Start with PostgreSQL"
|
||||||
|
@echo " make up DB=sqlite - Start with SQLite"
|
||||||
|
@echo " make down - Stop all containers"
|
||||||
|
@echo " make restart - Restart all containers"
|
||||||
|
@echo " make logs - View container logs"
|
||||||
|
@echo ""
|
||||||
|
@echo "Development:"
|
||||||
|
@echo " make shell - Open shell in app container"
|
||||||
|
@echo " make composer - Run composer (use: make composer cmd='install')"
|
||||||
|
@echo " make artisan - Run artisan (use: make artisan cmd='migrate')"
|
||||||
|
@echo " make npm - Run npm (use: make npm cmd='install')"
|
||||||
|
@echo " make tinker - Open Laravel Tinker"
|
||||||
|
@echo ""
|
||||||
|
@echo "Database:"
|
||||||
|
@echo " make migrate - Run migrations"
|
||||||
|
@echo " make fresh - Fresh migrate with seeders"
|
||||||
|
@echo " make seed - Run database seeders"
|
||||||
|
@echo ""
|
||||||
|
@echo "Testing:"
|
||||||
|
@echo " make test - Run PHPUnit tests"
|
||||||
|
@echo " make test-coverage - Run tests with coverage"
|
||||||
|
@echo ""
|
||||||
|
@echo "Frontend:"
|
||||||
|
@echo " make vite - Start Vite dev server"
|
||||||
|
@echo " make build-assets - Build frontend assets"
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
install: build
|
||||||
|
@echo "Creating Laravel project..."
|
||||||
|
docker-compose --profile $(DB) run --rm app composer create-project laravel/laravel .
|
||||||
|
@echo "Setting up environment for $(DB)..."
|
||||||
|
cp src/.env.$(DB) src/.env || cp src/.env.example src/.env
|
||||||
|
docker-compose --profile $(DB) run --rm app php artisan key:generate
|
||||||
|
@echo ""
|
||||||
|
@echo "Installation complete!"
|
||||||
|
@echo "Run 'make up DB=$(DB)' to start with $(DB) database."
|
||||||
|
|
||||||
|
build:
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# Docker commands
|
||||||
|
up:
|
||||||
|
docker-compose --profile $(DB) up -d
|
||||||
|
@echo "Started with $(DB) database profile."
|
||||||
|
|
||||||
|
down:
|
||||||
|
docker-compose --profile mysql --profile pgsql --profile sqlite down
|
||||||
|
|
||||||
|
restart:
|
||||||
|
docker-compose --profile $(DB) restart
|
||||||
|
|
||||||
|
logs:
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Development
|
||||||
|
shell:
|
||||||
|
docker-compose exec app bash
|
||||||
|
|
||||||
|
composer:
|
||||||
|
docker-compose exec app composer $(cmd)
|
||||||
|
|
||||||
|
artisan:
|
||||||
|
docker-compose exec app php artisan $(cmd)
|
||||||
|
|
||||||
|
npm:
|
||||||
|
docker-compose run --rm node npm $(cmd)
|
||||||
|
|
||||||
|
tinker:
|
||||||
|
docker-compose exec app php artisan tinker
|
||||||
|
|
||||||
|
# Database
|
||||||
|
migrate:
|
||||||
|
docker-compose exec app php artisan migrate
|
||||||
|
|
||||||
|
fresh:
|
||||||
|
docker-compose exec app php artisan migrate:fresh --seed
|
||||||
|
|
||||||
|
seed:
|
||||||
|
docker-compose exec app php artisan db:seed
|
||||||
|
|
||||||
|
# Testing (Pest)
|
||||||
|
test:
|
||||||
|
docker-compose exec app php artisan test
|
||||||
|
|
||||||
|
test-coverage:
|
||||||
|
docker-compose exec app php artisan test --coverage
|
||||||
|
|
||||||
|
test-filter:
|
||||||
|
docker-compose exec app php artisan test --filter=$(filter)
|
||||||
|
|
||||||
|
test-module:
|
||||||
|
docker-compose exec app php artisan test tests/Modules/$(module)
|
||||||
|
|
||||||
|
test-parallel:
|
||||||
|
docker-compose exec app php artisan test --parallel
|
||||||
|
|
||||||
|
# Code Quality
|
||||||
|
lint:
|
||||||
|
docker-compose exec app ./vendor/bin/pint
|
||||||
|
|
||||||
|
lint-check:
|
||||||
|
docker-compose exec app ./vendor/bin/pint --test
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
vite:
|
||||||
|
docker-compose run --rm --service-ports node npm run dev
|
||||||
|
|
||||||
|
build-assets:
|
||||||
|
docker-compose run --rm node npm run build
|
||||||
|
|
||||||
|
# Post-install (run after Laravel is installed)
|
||||||
|
setup-tools:
|
||||||
|
docker-compose exec app bash -c "cd /var/www/html && bash /var/www/html/../scripts/post-install.sh"
|
||||||
|
|
||||||
|
# Laravel base setup (auth, API, middleware)
|
||||||
|
setup-laravel:
|
||||||
|
docker-compose exec app bash -c "cd /var/www/html && bash /var/www/html/../scripts/laravel-setup.sh"
|
||||||
|
|
||||||
|
# Full setup (tools + laravel)
|
||||||
|
setup-all: setup-tools setup-laravel
|
||||||
|
|
||||||
|
# Queue Worker
|
||||||
|
queue-start:
|
||||||
|
docker-compose --profile queue up -d queue
|
||||||
|
|
||||||
|
queue-stop:
|
||||||
|
docker-compose stop queue
|
||||||
|
|
||||||
|
queue-restart:
|
||||||
|
docker-compose restart queue
|
||||||
|
|
||||||
|
queue-logs:
|
||||||
|
docker-compose logs -f queue
|
||||||
|
|
||||||
|
# Scheduler
|
||||||
|
scheduler-start:
|
||||||
|
docker-compose --profile scheduler up -d scheduler
|
||||||
|
|
||||||
|
scheduler-stop:
|
||||||
|
docker-compose stop scheduler
|
||||||
|
|
||||||
|
# Database Backup/Restore
|
||||||
|
backup:
|
||||||
|
@bash scripts/backup.sh
|
||||||
|
|
||||||
|
restore:
|
||||||
|
@bash scripts/restore.sh $(file)
|
||||||
|
|
||||||
|
# Health Check
|
||||||
|
health:
|
||||||
|
docker-compose exec app php artisan health:check
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
clean:
|
||||||
|
docker-compose down -v --remove-orphans
|
||||||
|
docker system prune -f
|
||||||
570
README.md
Normal file
570
README.md
Normal file
@@ -0,0 +1,570 @@
|
|||||||
|
# Laravel Docker Development Template
|
||||||
|
|
||||||
|
A comprehensive Laravel development environment with Docker for local development and deployment configurations for Ubuntu 24.04 with Nginx Proxy Manager or Apache.
|
||||||
|
|
||||||
|
> **New here?** Start with [GETTING_STARTED.md](GETTING_STARTED.md) for a step-by-step setup guide.
|
||||||
|
>
|
||||||
|
> **AI Assistant?** See [AI_CONTEXT.md](AI_CONTEXT.md) for project context and conventions.
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ DEVELOPMENT (Docker) │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ ┌─────────┐ ┌─────────┐ ┌───────────────┐ ┌─────┐ ┌─────┐ │
|
||||||
|
│ │ Nginx │──│ PHP │──│ MySQL/PgSQL/ │ │Redis│ │Mail │ │
|
||||||
|
│ │ :8080 │ │ FPM │ │ SQLite │ │:6379│ │:8025│ │
|
||||||
|
│ └─────────┘ └─────────┘ └───────────────┘ └─────┘ └─────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ PRODUCTION (Ubuntu 24.04 - No Docker) │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ Option A: Nginx Proxy Manager │
|
||||||
|
│ ┌───────────────┐ ┌─────────┐ ┌─────────┐ │
|
||||||
|
│ │ NPM (SSL/443) │───▶│ Nginx │───▶│ PHP-FPM │───▶ Laravel │
|
||||||
|
│ └───────────────┘ └─────────┘ └─────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ Option B: Apache Virtual Host │
|
||||||
|
│ ┌─────────────────┐ ┌─────────┐ │
|
||||||
|
│ │ Apache + SSL │───▶│ PHP-FPM │───▶ Laravel │
|
||||||
|
│ │ (Certbot) │ └─────────┘ │
|
||||||
|
│ └─────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
├── docker/
|
||||||
|
│ ├── nginx/default.conf # Dev Nginx config
|
||||||
|
│ ├── php/
|
||||||
|
│ │ ├── Dockerfile # PHP-FPM image
|
||||||
|
│ │ └── local.ini # PHP dev settings
|
||||||
|
│ └── mysql/my.cnf # MySQL config
|
||||||
|
├── deploy/
|
||||||
|
│ ├── nginx/
|
||||||
|
│ │ ├── laravel-site.conf # Production Nginx config
|
||||||
|
│ │ └── nginx-proxy-manager-notes.md
|
||||||
|
│ ├── apache/
|
||||||
|
│ │ ├── laravel-site.conf # Apache virtual host
|
||||||
|
│ │ └── apache-setup.md
|
||||||
|
│ ├── production/
|
||||||
|
│ │ ├── .env.mysql.production # MySQL env template
|
||||||
|
│ │ ├── .env.pgsql.production # PostgreSQL env template
|
||||||
|
│ │ └── .env.sqlite.production # SQLite env template
|
||||||
|
│ └── scripts/
|
||||||
|
│ ├── server-setup.sh # Ubuntu server setup (DB selection)
|
||||||
|
│ ├── deploy.sh # Deployment script
|
||||||
|
│ ├── fix-permissions.sh # Permission fixer
|
||||||
|
│ ├── supervisor-worker.conf # Queue worker config
|
||||||
|
│ ├── supervisor-scheduler.conf # Scheduler config
|
||||||
|
│ └── laravel-scheduler.cron # Cron job template
|
||||||
|
├── src/
|
||||||
|
│ ├── .env.mysql # MySQL dev config
|
||||||
|
│ ├── .env.pgsql # PostgreSQL dev config
|
||||||
|
│ ├── .env.sqlite # SQLite dev config
|
||||||
|
│ └── pint.json # Code style config
|
||||||
|
├── scripts/
|
||||||
|
│ ├── post-install.sh # Flare/Telescope/Pint setup
|
||||||
|
│ └── laravel-setup.sh # Auth/API/Middleware setup
|
||||||
|
├── docs/
|
||||||
|
│ ├── error-logging.md # Error logging docs
|
||||||
|
│ ├── laravel-setup.md # Laravel setup guide
|
||||||
|
│ ├── filament-admin.md # Admin panel docs
|
||||||
|
│ ├── modules.md # Modular architecture guide
|
||||||
|
│ ├── audit-trail.md # Audit trail docs
|
||||||
|
│ ├── site-settings.md # Appearance settings
|
||||||
|
│ ├── testing.md # Pest testing guide
|
||||||
|
│ ├── queues.md # Background jobs
|
||||||
|
│ ├── ci-cd.md # GitHub Actions pipeline
|
||||||
|
│ └── backup.md # Database backup/restore
|
||||||
|
├── docker-compose.yml # Multi-DB profiles
|
||||||
|
├── Makefile
|
||||||
|
├── README.md
|
||||||
|
├── GETTING_STARTED.md # Step-by-step setup guide
|
||||||
|
└── AI_CONTEXT.md # Context for AI assistants
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Options
|
||||||
|
|
||||||
|
This template supports three database engines via Docker Compose profiles:
|
||||||
|
|
||||||
|
| Database | Profile | Port | Use Case |
|
||||||
|
|----------|---------|------|----------|
|
||||||
|
| MySQL 8.0 | `mysql` | 3306 | Production-grade, most Laravel tutorials |
|
||||||
|
| PostgreSQL 16 | `pgsql` | 5432 | Advanced features, JSON, full-text search |
|
||||||
|
| SQLite | `sqlite` | - | Lightweight, no server, great for testing |
|
||||||
|
|
||||||
|
## Quick Start (Development)
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Docker & Docker Compose
|
||||||
|
|
||||||
|
### One-Command Setup
|
||||||
|
|
||||||
|
```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. **Build containers**
|
||||||
|
```bash
|
||||||
|
docker-compose build
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Install Laravel**
|
||||||
|
```bash
|
||||||
|
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"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Configure environment**
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `make up DB=mysql` | Start with MySQL |
|
||||||
|
| `make up DB=pgsql` | Start with PostgreSQL |
|
||||||
|
| `make up DB=sqlite` | Start with SQLite |
|
||||||
|
| `make down` | Stop all containers |
|
||||||
|
| `make shell` | Shell into app container |
|
||||||
|
| `make artisan cmd='migrate'` | Run Artisan commands |
|
||||||
|
| `make composer cmd='require package'` | Run Composer |
|
||||||
|
| `make logs` | View logs |
|
||||||
|
| `make fresh` | Fresh migrate + seed |
|
||||||
|
| `make lint` | Fix code style (Pint) |
|
||||||
|
| `make lint-check` | Check code style |
|
||||||
|
| `make test` | Run tests |
|
||||||
|
| `make setup-tools` | Install Flare, Pint, error pages |
|
||||||
|
| `make setup-laravel` | Configure auth, API, middleware |
|
||||||
|
| `make setup-all` | Run both setup scripts |
|
||||||
|
|
||||||
|
## Laravel Setup (Auth, API, Middleware)
|
||||||
|
|
||||||
|
After installing Laravel, run the interactive setup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make setup-laravel
|
||||||
|
```
|
||||||
|
|
||||||
|
This configures:
|
||||||
|
|
||||||
|
| Feature | Options |
|
||||||
|
|---------|---------|
|
||||||
|
| **Authentication** | Breeze (Blade/Livewire/API) or Jetstream + Livewire |
|
||||||
|
| **Admin Panel** | Filament with user management |
|
||||||
|
| **Site Settings** | Logo, favicon, color scheme management |
|
||||||
|
| **Modules** | Modular architecture with `make:module` command |
|
||||||
|
| **Audit Trail** | Track all data changes with user, old/new values |
|
||||||
|
| **Testing** | Pest framework with module test generation |
|
||||||
|
| **Queues** | Redis-powered background jobs |
|
||||||
|
| **CI/CD** | GitHub Actions for tests + deploy |
|
||||||
|
| **Backup** | Database backup/restore scripts |
|
||||||
|
| **API** | Sanctum token authentication |
|
||||||
|
| **Middleware** | ForceHttps, SecurityHeaders |
|
||||||
|
| **Storage** | Public storage symlink |
|
||||||
|
|
||||||
|
> **Note:** This template focuses on Blade and Livewire (no Vue/React/Inertia). Server-side rendering keeps debugging simple.
|
||||||
|
|
||||||
|
### Admin Panel (Filament)
|
||||||
|
|
||||||
|
The setup includes optional [Filament](https://filamentphp.com/) admin panel:
|
||||||
|
|
||||||
|
- **User management** - List, create, edit, delete users
|
||||||
|
- **Dashboard** - Stats widgets, charts
|
||||||
|
- **Extensible** - Add resources for any model
|
||||||
|
|
||||||
|
Access at: `http://localhost:8080/admin`
|
||||||
|
|
||||||
|
See [docs/filament-admin.md](docs/filament-admin.md) for customization.
|
||||||
|
|
||||||
|
### Site Settings (Appearance)
|
||||||
|
|
||||||
|
Manage logo, favicon, and colors from admin panel:
|
||||||
|
|
||||||
|
```
|
||||||
|
/admin → Settings → Appearance
|
||||||
|
```
|
||||||
|
|
||||||
|
Use in Blade templates:
|
||||||
|
|
||||||
|
```blade
|
||||||
|
{{-- In your <head> --}}
|
||||||
|
<x-site-head :title="$title" />
|
||||||
|
|
||||||
|
{{-- Logo --}}
|
||||||
|
<img src="{{ site_logo() }}" alt="{{ site_name() }}">
|
||||||
|
|
||||||
|
{{-- Colors available as CSS variables --}}
|
||||||
|
<div class="bg-primary">Uses --primary-color</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs/site-settings.md](docs/site-settings.md) for configuration.
|
||||||
|
|
||||||
|
### Modular Architecture
|
||||||
|
|
||||||
|
Build features as self-contained modules:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a module with model and admin panel
|
||||||
|
php artisan make:module StockManagement --model=Product
|
||||||
|
|
||||||
|
# Creates:
|
||||||
|
# - app/Modules/StockManagement/
|
||||||
|
# - Routes, controllers, views
|
||||||
|
# - Filament admin resources
|
||||||
|
# - Permissions for role-based access
|
||||||
|
```
|
||||||
|
|
||||||
|
Each module gets:
|
||||||
|
- **Landing page** at `/{module-slug}`
|
||||||
|
- **Admin section** in Filament panel
|
||||||
|
- **Permissions** auto-registered with roles
|
||||||
|
|
||||||
|
See [docs/modules.md](docs/modules.md) for full documentation.
|
||||||
|
|
||||||
|
### Audit Trail
|
||||||
|
|
||||||
|
Every module includes an **Audit Log** page showing all data changes:
|
||||||
|
|
||||||
|
- **Who** changed what
|
||||||
|
- **Old → New** values
|
||||||
|
- **When** and from which **IP**
|
||||||
|
- Filterable by user, event type, date
|
||||||
|
|
||||||
|
Configure per module in `Config/module_name.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
'audit' => [
|
||||||
|
'enabled' => true,
|
||||||
|
'strategy' => 'all', // 'all', 'include', 'exclude', 'none'
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs/audit-trail.md](docs/audit-trail.md) for configuration options.
|
||||||
|
|
||||||
|
See [docs/laravel-setup.md](docs/laravel-setup.md) for detailed configuration.
|
||||||
|
|
||||||
|
## Production Deployment
|
||||||
|
|
||||||
|
### Ubuntu 24.04 Server Setup
|
||||||
|
|
||||||
|
1. **Run server setup script**
|
||||||
|
```bash
|
||||||
|
sudo bash deploy/scripts/server-setup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This installs:
|
||||||
|
- PHP 8.3 + extensions (MySQL, PostgreSQL, SQLite drivers)
|
||||||
|
- Composer
|
||||||
|
- Node.js 20
|
||||||
|
- Database server (MySQL, PostgreSQL, or SQLite - your choice)
|
||||||
|
- Redis
|
||||||
|
- Nginx or Apache (your choice)
|
||||||
|
|
||||||
|
2. **Create database** (based on your selection during setup)
|
||||||
|
|
||||||
|
**MySQL:**
|
||||||
|
```bash
|
||||||
|
sudo mysql_secure_installation
|
||||||
|
sudo mysql
|
||||||
|
CREATE DATABASE your_app;
|
||||||
|
CREATE USER 'your_user'@'localhost' IDENTIFIED BY 'secure_password';
|
||||||
|
GRANT ALL PRIVILEGES ON your_app.* TO 'your_user'@'localhost';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
EXIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
**PostgreSQL:**
|
||||||
|
```bash
|
||||||
|
sudo -u postgres psql
|
||||||
|
CREATE DATABASE your_app;
|
||||||
|
CREATE USER your_user WITH ENCRYPTED PASSWORD 'secure_password';
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE your_app TO your_user;
|
||||||
|
\q
|
||||||
|
```
|
||||||
|
|
||||||
|
**SQLite:**
|
||||||
|
```bash
|
||||||
|
touch /var/www/your-app/database/database.sqlite
|
||||||
|
chmod 664 /var/www/your-app/database/database.sqlite
|
||||||
|
chown www-data:www-data /var/www/your-app/database/database.sqlite
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option A: Nginx + Nginx Proxy Manager
|
||||||
|
|
||||||
|
1. **Deploy your app**
|
||||||
|
```bash
|
||||||
|
cd /var/www
|
||||||
|
git clone <repo-url> your-app
|
||||||
|
cd your-app/src
|
||||||
|
composer install --no-dev --optimize-autoloader
|
||||||
|
|
||||||
|
# Copy the appropriate .env file for your database:
|
||||||
|
cp ../deploy/production/.env.mysql.production .env # For MySQL
|
||||||
|
cp ../deploy/production/.env.pgsql.production .env # For PostgreSQL
|
||||||
|
cp ../deploy/production/.env.sqlite.production .env # For SQLite
|
||||||
|
|
||||||
|
# Edit .env with your settings
|
||||||
|
php artisan key:generate
|
||||||
|
php artisan migrate
|
||||||
|
npm ci && npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configure Nginx**
|
||||||
|
```bash
|
||||||
|
sudo cp deploy/nginx/laravel-site.conf /etc/nginx/sites-available/your-app
|
||||||
|
# Edit: server_name, root, log paths
|
||||||
|
sudo ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Configure NPM**
|
||||||
|
- See `deploy/nginx/nginx-proxy-manager-notes.md` for NPM setup
|
||||||
|
|
||||||
|
4. **Fix permissions**
|
||||||
|
```bash
|
||||||
|
sudo bash deploy/scripts/fix-permissions.sh /var/www/your-app/src
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option B: Apache Virtual Host
|
||||||
|
|
||||||
|
1. **Deploy your app** (same as above)
|
||||||
|
|
||||||
|
2. **Configure Apache**
|
||||||
|
```bash
|
||||||
|
sudo cp deploy/apache/laravel-site.conf /etc/apache2/sites-available/your-app.conf
|
||||||
|
# Edit: ServerName, DocumentRoot, paths
|
||||||
|
sudo a2ensite your-app.conf
|
||||||
|
sudo apache2ctl configtest
|
||||||
|
sudo systemctl reload apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **SSL with Certbot**
|
||||||
|
```bash
|
||||||
|
sudo certbot --apache -d your-domain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
See `deploy/apache/apache-setup.md` for detailed instructions.
|
||||||
|
|
||||||
|
### Automated Deployments
|
||||||
|
|
||||||
|
Use the deployment script for updates:
|
||||||
|
```bash
|
||||||
|
sudo bash deploy/scripts/deploy.sh /var/www/your-app/src main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Queue Workers (Optional)
|
||||||
|
|
||||||
|
If using queues:
|
||||||
|
```bash
|
||||||
|
sudo cp deploy/scripts/supervisor-worker.conf /etc/supervisor/conf.d/laravel-worker.conf
|
||||||
|
# Edit paths in the config
|
||||||
|
sudo supervisorctl reread
|
||||||
|
sudo supervisorctl update
|
||||||
|
sudo supervisorctl start laravel-worker:*
|
||||||
|
```
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
| Service | Port | Description |
|
||||||
|
|---------|------|-------------|
|
||||||
|
| Nginx | 8080 | Web server |
|
||||||
|
| MySQL | 3306 | Database |
|
||||||
|
| Redis | 6379 | Cache/Queue |
|
||||||
|
| Mailpit | 8025 | Email testing UI |
|
||||||
|
| Mailpit SMTP | 1025 | SMTP server |
|
||||||
|
|
||||||
|
### Connecting to MySQL from host
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mysql -h 127.0.0.1 -P 3306 -u laravel -p
|
||||||
|
# Password: secret (from .env)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Logging
|
||||||
|
|
||||||
|
This template uses **Flare + Ignition** by Spatie for error tracking.
|
||||||
|
|
||||||
|
| Environment | Tool | What You Get |
|
||||||
|
|-------------|------|--------------|
|
||||||
|
| Development | Ignition | Rich error pages, AI explanations, click-to-open in VS Code |
|
||||||
|
| Production | Flare | Remote error tracking, clean user-facing error pages |
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
After installing Laravel, run the post-install script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make setup-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
This installs:
|
||||||
|
- Flare for production error tracking
|
||||||
|
- Custom error pages (404, 500, 503)
|
||||||
|
- Optional: Laravel Telescope for debugging
|
||||||
|
|
||||||
|
Get your Flare API key at [flareapp.io](https://flareapp.io) and add to `.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
FLARE_KEY=your_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs/error-logging.md](docs/error-logging.md) for full documentation.
|
||||||
|
|
||||||
|
## Code Style (Laravel Pint)
|
||||||
|
|
||||||
|
This template includes [Laravel Pint](https://laravel.com/docs/pint) for code style enforcement.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Fix code style
|
||||||
|
make lint
|
||||||
|
|
||||||
|
# Check without fixing
|
||||||
|
make lint-check
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration: `src/pint.json` (uses Laravel preset with sensible defaults).
|
||||||
|
|
||||||
|
## Scheduler (Production)
|
||||||
|
|
||||||
|
Laravel's task scheduler needs to run every minute. Two options:
|
||||||
|
|
||||||
|
### Option 1: Cron (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add to crontab
|
||||||
|
sudo crontab -e -u www-data
|
||||||
|
|
||||||
|
# Add this line:
|
||||||
|
* * * * * cd /var/www/your-app && php artisan schedule:run >> /dev/null 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Supervisor
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp deploy/scripts/supervisor-scheduler.conf /etc/supervisor/conf.d/
|
||||||
|
# Edit paths in the config
|
||||||
|
sudo supervisorctl reread && sudo supervisorctl update
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
### Changing PHP Version
|
||||||
|
|
||||||
|
Edit `docker/php/Dockerfile`:
|
||||||
|
```dockerfile
|
||||||
|
FROM php:8.2-fpm # Change version here
|
||||||
|
```
|
||||||
|
|
||||||
|
Then rebuild: `docker-compose build app`
|
||||||
|
|
||||||
|
### Adding PHP Extensions
|
||||||
|
|
||||||
|
Edit `docker/php/Dockerfile` and add to the install list, then rebuild.
|
||||||
|
|
||||||
|
### Using PostgreSQL
|
||||||
|
|
||||||
|
1. Uncomment PostgreSQL in `docker-compose.yml`
|
||||||
|
2. Update `src/.env`:
|
||||||
|
```
|
||||||
|
DB_CONNECTION=pgsql
|
||||||
|
DB_HOST=pgsql
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Permission Issues
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
docker-compose exec app chmod -R 775 storage bootstrap/cache
|
||||||
|
|
||||||
|
# Production
|
||||||
|
sudo bash deploy/scripts/fix-permissions.sh /var/www/your-app/src
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container won't start
|
||||||
|
```bash
|
||||||
|
docker-compose logs app # Check for errors
|
||||||
|
docker-compose down -v # Reset volumes
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database connection refused
|
||||||
|
- Ensure MySQL container is running: `docker-compose ps`
|
||||||
|
- Check `DB_HOST=mysql` in `src/.env` (not `localhost`)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
92
deploy/apache/apache-setup.md
Normal file
92
deploy/apache/apache-setup.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Apache Virtual Host Setup for Laravel
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Ubuntu 24.04 with Apache2 and PHP-FPM installed.
|
||||||
|
|
||||||
|
## Required Apache Modules
|
||||||
|
|
||||||
|
Enable the necessary modules:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo a2enmod rewrite
|
||||||
|
sudo a2enmod headers
|
||||||
|
sudo a2enmod ssl
|
||||||
|
sudo a2enmod proxy_fcgi
|
||||||
|
sudo a2enmod deflate
|
||||||
|
sudo a2enmod expires
|
||||||
|
sudo a2enmod setenvif
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation Steps
|
||||||
|
|
||||||
|
### 1. Copy Virtual Host Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp deploy/apache/laravel-site.conf /etc/apache2/sites-available/your-app.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Edit Configuration
|
||||||
|
|
||||||
|
Update the following in the config file:
|
||||||
|
- `ServerName` - Your domain name
|
||||||
|
- `DocumentRoot` - Path to Laravel's public folder
|
||||||
|
- `Directory` - Same path as DocumentRoot
|
||||||
|
- Log file names
|
||||||
|
|
||||||
|
### 3. Enable Site
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo a2ensite your-app.conf
|
||||||
|
sudo a2dissite 000-default.conf # Disable default site if needed
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Test Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apache2ctl configtest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Restart Apache
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSL with Certbot
|
||||||
|
|
||||||
|
Install Certbot and obtain SSL certificate:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install certbot python3-certbot-apache
|
||||||
|
sudo certbot --apache -d your-domain.com -d www.your-domain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Certbot will automatically modify your Apache configuration for SSL.
|
||||||
|
|
||||||
|
## File Permissions
|
||||||
|
|
||||||
|
Set correct permissions for Laravel:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo chown -R www-data:www-data /var/www/your-app
|
||||||
|
sudo chmod -R 755 /var/www/your-app
|
||||||
|
sudo chmod -R 775 /var/www/your-app/storage
|
||||||
|
sudo chmod -R 775 /var/www/your-app/bootstrap/cache
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### 403 Forbidden
|
||||||
|
- Check directory permissions
|
||||||
|
- Ensure `AllowOverride All` is set
|
||||||
|
- Verify `mod_rewrite` is enabled
|
||||||
|
|
||||||
|
### 500 Internal Server Error
|
||||||
|
- Check Laravel logs: `storage/logs/laravel.log`
|
||||||
|
- Check Apache error logs: `/var/log/apache2/your-app-error.log`
|
||||||
|
- Ensure `.env` file exists and has correct permissions
|
||||||
|
|
||||||
|
### PHP Not Processing
|
||||||
|
- Verify PHP-FPM is running: `sudo systemctl status php8.3-fpm`
|
||||||
|
- Check socket path matches in Apache config
|
||||||
115
deploy/apache/laravel-site.conf
Normal file
115
deploy/apache/laravel-site.conf
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName your-domain.com
|
||||||
|
ServerAlias www.your-domain.com
|
||||||
|
ServerAdmin webmaster@your-domain.com
|
||||||
|
|
||||||
|
DocumentRoot /var/www/your-app/public
|
||||||
|
|
||||||
|
<Directory /var/www/your-app/public>
|
||||||
|
Options -Indexes +FollowSymLinks
|
||||||
|
AllowOverride All
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
# PHP-FPM configuration
|
||||||
|
<FilesMatch \.php$>
|
||||||
|
SetHandler "proxy:unix:/var/run/php/php8.3-fpm.sock|fcgi://localhost"
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
Header always set X-Frame-Options "SAMEORIGIN"
|
||||||
|
Header always set X-Content-Type-Options "nosniff"
|
||||||
|
Header always set X-XSS-Protection "1; mode=block"
|
||||||
|
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
|
||||||
|
# Disable server signature
|
||||||
|
ServerSignature Off
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/your-app-error.log
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/your-app-access.log combined
|
||||||
|
|
||||||
|
# Compression
|
||||||
|
<IfModule mod_deflate.c>
|
||||||
|
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
|
||||||
|
AddOutputFilterByType DEFLATE application/javascript application/json
|
||||||
|
AddOutputFilterByType DEFLATE application/xml application/xhtml+xml
|
||||||
|
AddOutputFilterByType DEFLATE image/svg+xml
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Static file caching
|
||||||
|
<IfModule mod_expires.c>
|
||||||
|
ExpiresActive On
|
||||||
|
ExpiresByType image/jpeg "access plus 1 month"
|
||||||
|
ExpiresByType image/png "access plus 1 month"
|
||||||
|
ExpiresByType image/gif "access plus 1 month"
|
||||||
|
ExpiresByType image/svg+xml "access plus 1 month"
|
||||||
|
ExpiresByType text/css "access plus 1 month"
|
||||||
|
ExpiresByType application/javascript "access plus 1 month"
|
||||||
|
ExpiresByType font/woff2 "access plus 1 month"
|
||||||
|
ExpiresByType font/woff "access plus 1 month"
|
||||||
|
</IfModule>
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
# SSL Configuration (use with Let's Encrypt / Certbot)
|
||||||
|
<VirtualHost *:443>
|
||||||
|
ServerName your-domain.com
|
||||||
|
ServerAlias www.your-domain.com
|
||||||
|
ServerAdmin webmaster@your-domain.com
|
||||||
|
|
||||||
|
DocumentRoot /var/www/your-app/public
|
||||||
|
|
||||||
|
<Directory /var/www/your-app/public>
|
||||||
|
Options -Indexes +FollowSymLinks
|
||||||
|
AllowOverride All
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
# PHP-FPM configuration
|
||||||
|
<FilesMatch \.php$>
|
||||||
|
SetHandler "proxy:unix:/var/run/php/php8.3-fpm.sock|fcgi://localhost"
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
# SSL Configuration (Certbot will fill these in)
|
||||||
|
SSLEngine on
|
||||||
|
SSLCertificateFile /etc/letsencrypt/live/your-domain.com/fullchain.pem
|
||||||
|
SSLCertificateKeyFile /etc/letsencrypt/live/your-domain.com/privkey.pem
|
||||||
|
|
||||||
|
# Modern SSL configuration
|
||||||
|
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
|
||||||
|
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
|
||||||
|
SSLHonorCipherOrder off
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
Header always set X-Frame-Options "SAMEORIGIN"
|
||||||
|
Header always set X-Content-Type-Options "nosniff"
|
||||||
|
Header always set X-XSS-Protection "1; mode=block"
|
||||||
|
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||||
|
|
||||||
|
ServerSignature Off
|
||||||
|
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/your-app-ssl-error.log
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/your-app-ssl-access.log combined
|
||||||
|
|
||||||
|
# Compression
|
||||||
|
<IfModule mod_deflate.c>
|
||||||
|
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
|
||||||
|
AddOutputFilterByType DEFLATE application/javascript application/json
|
||||||
|
AddOutputFilterByType DEFLATE application/xml application/xhtml+xml
|
||||||
|
AddOutputFilterByType DEFLATE image/svg+xml
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Static file caching
|
||||||
|
<IfModule mod_expires.c>
|
||||||
|
ExpiresActive On
|
||||||
|
ExpiresByType image/jpeg "access plus 1 month"
|
||||||
|
ExpiresByType image/png "access plus 1 month"
|
||||||
|
ExpiresByType image/gif "access plus 1 month"
|
||||||
|
ExpiresByType image/svg+xml "access plus 1 month"
|
||||||
|
ExpiresByType text/css "access plus 1 month"
|
||||||
|
ExpiresByType application/javascript "access plus 1 month"
|
||||||
|
ExpiresByType font/woff2 "access plus 1 month"
|
||||||
|
ExpiresByType font/woff "access plus 1 month"
|
||||||
|
</IfModule>
|
||||||
|
</VirtualHost>
|
||||||
77
deploy/nginx/laravel-site.conf
Normal file
77
deploy/nginx/laravel-site.conf
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Nginx site configuration for Laravel on Ubuntu 24.04
|
||||||
|
# This config is for native Nginx (not Docker) behind Nginx Proxy Manager
|
||||||
|
# Place this in /etc/nginx/sites-available/ and symlink to sites-enabled
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
|
||||||
|
# Replace with your domain or internal IP
|
||||||
|
# Nginx Proxy Manager will forward traffic here
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
root /var/www/your-app/public;
|
||||||
|
index index.php index.html index.htm;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
charset utf-8;
|
||||||
|
|
||||||
|
# Laravel routing
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Favicon and robots
|
||||||
|
location = /favicon.ico { access_log off; log_not_found off; }
|
||||||
|
location = /robots.txt { access_log off; log_not_found off; }
|
||||||
|
|
||||||
|
# Error pages
|
||||||
|
error_page 404 /index.php;
|
||||||
|
|
||||||
|
# PHP-FPM configuration
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_hide_header X-Powered-By;
|
||||||
|
|
||||||
|
# Timeouts
|
||||||
|
fastcgi_connect_timeout 60;
|
||||||
|
fastcgi_send_timeout 180;
|
||||||
|
fastcgi_read_timeout 180;
|
||||||
|
fastcgi_buffer_size 128k;
|
||||||
|
fastcgi_buffers 4 256k;
|
||||||
|
fastcgi_busy_buffers_size 256k;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to hidden files
|
||||||
|
location ~ /\.(?!well-known).* {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Static file caching
|
||||||
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|txt|woff|woff2|ttf|svg)$ {
|
||||||
|
expires 30d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml application/xml+rss application/x-font-ttf font/opentype image/svg+xml;
|
||||||
|
|
||||||
|
client_max_body_size 100M;
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
access_log /var/log/nginx/your-app-access.log;
|
||||||
|
error_log /var/log/nginx/your-app-error.log;
|
||||||
|
}
|
||||||
79
deploy/nginx/nginx-proxy-manager-notes.md
Normal file
79
deploy/nginx/nginx-proxy-manager-notes.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Nginx Proxy Manager Configuration Notes
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet → Nginx Proxy Manager (Docker/Host) → Native Nginx → PHP-FPM → Laravel
|
||||||
|
↓
|
||||||
|
SSL Termination
|
||||||
|
(Let's Encrypt)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nginx Proxy Manager Setup
|
||||||
|
|
||||||
|
### Option 1: NPM on Same Server
|
||||||
|
If running NPM on the same Ubuntu 24.04 server:
|
||||||
|
|
||||||
|
1. **NPM listens on ports 80/443** (public)
|
||||||
|
2. **Native Nginx listens on port 8080** (internal only)
|
||||||
|
3. NPM forwards traffic to `localhost:8080`
|
||||||
|
|
||||||
|
Modify `laravel-site.conf`:
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:8080; # Only accept local connections
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: NPM on Separate Server
|
||||||
|
If running NPM on a separate server:
|
||||||
|
|
||||||
|
1. Configure firewall to allow NPM server IP
|
||||||
|
2. NPM forwards to `http://your-laravel-server-ip:80`
|
||||||
|
|
||||||
|
## NPM Proxy Host Configuration
|
||||||
|
|
||||||
|
In Nginx Proxy Manager web UI:
|
||||||
|
|
||||||
|
1. **Domain Names**: your-domain.com
|
||||||
|
2. **Scheme**: http
|
||||||
|
3. **Forward Hostname/IP**: 127.0.0.1 (or server IP)
|
||||||
|
4. **Forward Port**: 8080 (or 80)
|
||||||
|
5. **Enable**: Block Common Exploits
|
||||||
|
6. **SSL Tab**:
|
||||||
|
- Request new SSL Certificate
|
||||||
|
- Force SSL
|
||||||
|
- HTTP/2 Support
|
||||||
|
|
||||||
|
## Custom NPM Configuration
|
||||||
|
|
||||||
|
Add to "Advanced" tab if needed:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
|
||||||
|
# WebSocket support (if using Laravel Echo/Reverb)
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Laravel Trusted Proxies
|
||||||
|
|
||||||
|
Update `app/Http/Middleware/TrustProxies.php` or configure in Laravel 11+:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// In bootstrap/app.php or config
|
||||||
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
|
$middleware->trustProxies(at: '*');
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Or set in `.env`:
|
||||||
|
```
|
||||||
|
TRUSTED_PROXIES=*
|
||||||
|
```
|
||||||
62
deploy/production/.env.mysql.production
Normal file
62
deploy/production/.env.mysql.production
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
APP_NAME="Your App Name"
|
||||||
|
APP_ENV=production
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=false
|
||||||
|
APP_TIMEZONE=UTC
|
||||||
|
APP_URL=https://your-domain.com
|
||||||
|
|
||||||
|
APP_LOCALE=en
|
||||||
|
APP_FALLBACK_LOCALE=en
|
||||||
|
APP_FAKER_LOCALE=en_US
|
||||||
|
|
||||||
|
APP_MAINTENANCE_DRIVER=file
|
||||||
|
APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=single
|
||||||
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
|
LOG_LEVEL=error
|
||||||
|
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=your_database
|
||||||
|
DB_USERNAME=your_db_user
|
||||||
|
DB_PASSWORD=your_secure_password
|
||||||
|
|
||||||
|
SESSION_DRIVER=redis
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
SESSION_ENCRYPT=true
|
||||||
|
SESSION_PATH=/
|
||||||
|
SESSION_DOMAIN=your-domain.com
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=log
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
|
||||||
|
CACHE_STORE=redis
|
||||||
|
CACHE_PREFIX=
|
||||||
|
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=smtp.your-provider.com
|
||||||
|
MAIL_PORT=587
|
||||||
|
MAIL_USERNAME=your_email_username
|
||||||
|
MAIL_PASSWORD=your_email_password
|
||||||
|
MAIL_ENCRYPTION=tls
|
||||||
|
MAIL_FROM_ADDRESS="noreply@your-domain.com"
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
TRUSTED_PROXIES=*
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
# Error Logging - Flare (https://flareapp.io)
|
||||||
|
# REQUIRED for production error tracking
|
||||||
|
FLARE_KEY=your_flare_key_here
|
||||||
62
deploy/production/.env.pgsql.production
Normal file
62
deploy/production/.env.pgsql.production
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
APP_NAME="Your App Name"
|
||||||
|
APP_ENV=production
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=false
|
||||||
|
APP_TIMEZONE=UTC
|
||||||
|
APP_URL=https://your-domain.com
|
||||||
|
|
||||||
|
APP_LOCALE=en
|
||||||
|
APP_FALLBACK_LOCALE=en
|
||||||
|
APP_FAKER_LOCALE=en_US
|
||||||
|
|
||||||
|
APP_MAINTENANCE_DRIVER=file
|
||||||
|
APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=single
|
||||||
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
|
LOG_LEVEL=error
|
||||||
|
|
||||||
|
DB_CONNECTION=pgsql
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_DATABASE=your_database
|
||||||
|
DB_USERNAME=your_db_user
|
||||||
|
DB_PASSWORD=your_secure_password
|
||||||
|
|
||||||
|
SESSION_DRIVER=redis
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
SESSION_ENCRYPT=true
|
||||||
|
SESSION_PATH=/
|
||||||
|
SESSION_DOMAIN=your-domain.com
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=log
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
|
||||||
|
CACHE_STORE=redis
|
||||||
|
CACHE_PREFIX=
|
||||||
|
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=smtp.your-provider.com
|
||||||
|
MAIL_PORT=587
|
||||||
|
MAIL_USERNAME=your_email_username
|
||||||
|
MAIL_PASSWORD=your_email_password
|
||||||
|
MAIL_ENCRYPTION=tls
|
||||||
|
MAIL_FROM_ADDRESS="noreply@your-domain.com"
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
TRUSTED_PROXIES=*
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
# Error Logging - Flare (https://flareapp.io)
|
||||||
|
# REQUIRED for production error tracking
|
||||||
|
FLARE_KEY=your_flare_key_here
|
||||||
58
deploy/production/.env.sqlite.production
Normal file
58
deploy/production/.env.sqlite.production
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
APP_NAME="Your App Name"
|
||||||
|
APP_ENV=production
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=false
|
||||||
|
APP_TIMEZONE=UTC
|
||||||
|
APP_URL=https://your-domain.com
|
||||||
|
|
||||||
|
APP_LOCALE=en
|
||||||
|
APP_FALLBACK_LOCALE=en
|
||||||
|
APP_FAKER_LOCALE=en_US
|
||||||
|
|
||||||
|
APP_MAINTENANCE_DRIVER=file
|
||||||
|
APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=single
|
||||||
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
|
LOG_LEVEL=error
|
||||||
|
|
||||||
|
DB_CONNECTION=sqlite
|
||||||
|
DB_DATABASE=/var/www/your-app/database/database.sqlite
|
||||||
|
|
||||||
|
SESSION_DRIVER=redis
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
SESSION_ENCRYPT=true
|
||||||
|
SESSION_PATH=/
|
||||||
|
SESSION_DOMAIN=your-domain.com
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=log
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
|
||||||
|
CACHE_STORE=redis
|
||||||
|
CACHE_PREFIX=
|
||||||
|
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=smtp.your-provider.com
|
||||||
|
MAIL_PORT=587
|
||||||
|
MAIL_USERNAME=your_email_username
|
||||||
|
MAIL_PASSWORD=your_email_password
|
||||||
|
MAIL_ENCRYPTION=tls
|
||||||
|
MAIL_FROM_ADDRESS="noreply@your-domain.com"
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
TRUSTED_PROXIES=*
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
# Error Logging - Flare (https://flareapp.io)
|
||||||
|
# REQUIRED for production error tracking
|
||||||
|
FLARE_KEY=your_flare_key_here
|
||||||
75
deploy/scripts/deploy.sh
Normal file
75
deploy/scripts/deploy.sh
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Laravel Deployment Script
|
||||||
|
# Usage: ./deploy.sh /var/www/your-app [branch]
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
APP_PATH="${1:-/var/www/laravel}"
|
||||||
|
BRANCH="${2:-main}"
|
||||||
|
|
||||||
|
if [ ! -d "$APP_PATH" ]; then
|
||||||
|
echo "Error: Directory $APP_PATH does not exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$APP_PATH"
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Deploying Laravel Application"
|
||||||
|
echo "Path: $APP_PATH"
|
||||||
|
echo "Branch: $BRANCH"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# Enable maintenance mode
|
||||||
|
echo "[1/9] Enabling maintenance mode..."
|
||||||
|
php artisan down --retry=60 || true
|
||||||
|
|
||||||
|
# Pull latest code
|
||||||
|
echo "[2/9] Pulling latest changes..."
|
||||||
|
git fetch origin
|
||||||
|
git reset --hard origin/$BRANCH
|
||||||
|
|
||||||
|
# Install PHP dependencies
|
||||||
|
echo "[3/9] Installing Composer dependencies..."
|
||||||
|
composer install --no-dev --optimize-autoloader --no-interaction
|
||||||
|
|
||||||
|
# Install and build frontend assets
|
||||||
|
echo "[4/9] Installing Node dependencies..."
|
||||||
|
npm ci --production=false
|
||||||
|
|
||||||
|
echo "[5/9] Building frontend assets..."
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
echo "[6/9] Running database migrations..."
|
||||||
|
php artisan migrate --force
|
||||||
|
|
||||||
|
# Clear and optimize
|
||||||
|
echo "[7/9] Optimizing application..."
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
php artisan event:cache
|
||||||
|
|
||||||
|
# Fix permissions
|
||||||
|
echo "[8/9] Fixing permissions..."
|
||||||
|
sudo chown -R www-data:www-data storage bootstrap/cache
|
||||||
|
sudo chmod -R 775 storage bootstrap/cache
|
||||||
|
|
||||||
|
# Restart services
|
||||||
|
echo "[9/9] Restarting services..."
|
||||||
|
sudo systemctl restart php8.3-fpm
|
||||||
|
|
||||||
|
# Restart queue workers if using Supervisor
|
||||||
|
if [ -f /etc/supervisor/conf.d/laravel-worker.conf ]; then
|
||||||
|
sudo supervisorctl restart laravel-worker:*
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Disable maintenance mode
|
||||||
|
php artisan up
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Deployment complete!"
|
||||||
|
echo "=========================================="
|
||||||
43
deploy/scripts/fix-permissions.sh
Normal file
43
deploy/scripts/fix-permissions.sh
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Fix Laravel file permissions
|
||||||
|
# Usage: ./fix-permissions.sh /var/www/your-app
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: $0 /path/to/laravel/app"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
APP_PATH="$1"
|
||||||
|
|
||||||
|
if [ ! -d "$APP_PATH" ]; then
|
||||||
|
echo "Error: Directory $APP_PATH does not exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Fixing permissions for: $APP_PATH"
|
||||||
|
|
||||||
|
# Set ownership
|
||||||
|
sudo chown -R www-data:www-data "$APP_PATH"
|
||||||
|
|
||||||
|
# Set directory permissions
|
||||||
|
sudo find "$APP_PATH" -type d -exec chmod 755 {} \;
|
||||||
|
|
||||||
|
# Set file permissions
|
||||||
|
sudo find "$APP_PATH" -type f -exec chmod 644 {} \;
|
||||||
|
|
||||||
|
# Make storage and cache writable
|
||||||
|
sudo chmod -R 775 "$APP_PATH/storage"
|
||||||
|
sudo chmod -R 775 "$APP_PATH/bootstrap/cache"
|
||||||
|
|
||||||
|
# Set ACL for current user to maintain access
|
||||||
|
if command -v setfacl &> /dev/null; then
|
||||||
|
sudo setfacl -Rm u:$(whoami):rwx "$APP_PATH/storage"
|
||||||
|
sudo setfacl -Rm u:$(whoami):rwx "$APP_PATH/bootstrap/cache"
|
||||||
|
sudo setfacl -dRm u:$(whoami):rwx "$APP_PATH/storage"
|
||||||
|
sudo setfacl -dRm u:$(whoami):rwx "$APP_PATH/bootstrap/cache"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Permissions fixed successfully!"
|
||||||
6
deploy/scripts/laravel-scheduler.cron
Normal file
6
deploy/scripts/laravel-scheduler.cron
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Laravel Scheduler Cron Job
|
||||||
|
# Add this to crontab: sudo crontab -e -u www-data
|
||||||
|
# Or copy to /etc/cron.d/laravel-scheduler
|
||||||
|
|
||||||
|
# Run Laravel scheduler every minute
|
||||||
|
* * * * * www-data cd /var/www/your-app && php artisan schedule:run >> /dev/null 2>&1
|
||||||
202
deploy/scripts/server-setup.sh
Normal file
202
deploy/scripts/server-setup.sh
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Laravel Server Setup Script for Ubuntu 24.04
|
||||||
|
# Run as root or with sudo
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Laravel Server Setup for Ubuntu 24.04"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# Update system
|
||||||
|
echo "[1/8] Updating system packages..."
|
||||||
|
apt update && apt upgrade -y
|
||||||
|
|
||||||
|
# Install essential packages
|
||||||
|
echo "[2/8] Installing essential packages..."
|
||||||
|
apt install -y \
|
||||||
|
software-properties-common \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
unzip \
|
||||||
|
supervisor \
|
||||||
|
acl
|
||||||
|
|
||||||
|
# Add PHP repository and install PHP 8.3
|
||||||
|
echo "[3/8] Installing PHP 8.3 and extensions..."
|
||||||
|
add-apt-repository -y ppa:ondrej/php
|
||||||
|
apt update
|
||||||
|
apt install -y \
|
||||||
|
php8.3-fpm \
|
||||||
|
php8.3-cli \
|
||||||
|
php8.3-mysql \
|
||||||
|
php8.3-pgsql \
|
||||||
|
php8.3-sqlite3 \
|
||||||
|
php8.3-redis \
|
||||||
|
php8.3-mbstring \
|
||||||
|
php8.3-xml \
|
||||||
|
php8.3-curl \
|
||||||
|
php8.3-zip \
|
||||||
|
php8.3-bcmath \
|
||||||
|
php8.3-intl \
|
||||||
|
php8.3-gd \
|
||||||
|
php8.3-imagick \
|
||||||
|
php8.3-opcache
|
||||||
|
|
||||||
|
# Install Composer
|
||||||
|
echo "[4/8] Installing Composer..."
|
||||||
|
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
|
|
||||||
|
# Install Node.js 20.x
|
||||||
|
echo "[5/8] Installing Node.js 20.x..."
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
||||||
|
apt install -y nodejs
|
||||||
|
|
||||||
|
# Database selection
|
||||||
|
echo "[6/8] Database Installation..."
|
||||||
|
echo ""
|
||||||
|
echo "Select database server to install:"
|
||||||
|
echo "1) MySQL 8.0"
|
||||||
|
echo "2) PostgreSQL 16"
|
||||||
|
echo "3) SQLite only (no server needed)"
|
||||||
|
echo "4) Both MySQL and PostgreSQL"
|
||||||
|
read -p "Enter choice [1-4]: " DB_CHOICE
|
||||||
|
|
||||||
|
case $DB_CHOICE in
|
||||||
|
1)
|
||||||
|
apt install -y mysql-server
|
||||||
|
systemctl enable mysql
|
||||||
|
systemctl start mysql
|
||||||
|
echo "MySQL 8.0 installed."
|
||||||
|
echo ""
|
||||||
|
echo "To secure MySQL, run: sudo mysql_secure_installation"
|
||||||
|
echo "To create database:"
|
||||||
|
echo " sudo mysql"
|
||||||
|
echo " CREATE DATABASE your_app;"
|
||||||
|
echo " CREATE USER 'your_user'@'localhost' IDENTIFIED BY 'password';"
|
||||||
|
echo " GRANT ALL PRIVILEGES ON your_app.* TO 'your_user'@'localhost';"
|
||||||
|
echo " FLUSH PRIVILEGES;"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
apt install -y postgresql postgresql-contrib
|
||||||
|
systemctl enable postgresql
|
||||||
|
systemctl start postgresql
|
||||||
|
echo "PostgreSQL 16 installed."
|
||||||
|
echo ""
|
||||||
|
echo "To create database:"
|
||||||
|
echo " sudo -u postgres psql"
|
||||||
|
echo " CREATE DATABASE your_app;"
|
||||||
|
echo " CREATE USER your_user WITH ENCRYPTED PASSWORD 'password';"
|
||||||
|
echo " GRANT ALL PRIVILEGES ON DATABASE your_app TO your_user;"
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
echo "SQLite selected - no server installation needed."
|
||||||
|
echo "SQLite is included with PHP (php8.3-sqlite3 already installed)."
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
apt install -y mysql-server postgresql postgresql-contrib
|
||||||
|
systemctl enable mysql postgresql
|
||||||
|
systemctl start mysql postgresql
|
||||||
|
echo "Both MySQL and PostgreSQL installed."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Install Redis
|
||||||
|
echo "[7/8] Installing Redis..."
|
||||||
|
apt install -y redis-server
|
||||||
|
systemctl enable redis-server
|
||||||
|
systemctl start redis-server
|
||||||
|
|
||||||
|
# Web server selection
|
||||||
|
echo "[8/8] Web Server Installation..."
|
||||||
|
echo ""
|
||||||
|
echo "Select web server to install:"
|
||||||
|
echo "1) Nginx (recommended for Nginx Proxy Manager setup)"
|
||||||
|
echo "2) Apache"
|
||||||
|
echo "3) Both"
|
||||||
|
echo "4) Skip (install manually later)"
|
||||||
|
read -p "Enter choice [1-4]: " WEB_SERVER_CHOICE
|
||||||
|
|
||||||
|
case $WEB_SERVER_CHOICE in
|
||||||
|
1)
|
||||||
|
apt install -y nginx
|
||||||
|
systemctl enable nginx
|
||||||
|
systemctl start nginx
|
||||||
|
echo "Nginx installed."
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
apt install -y apache2 libapache2-mod-fcgid
|
||||||
|
a2enmod rewrite headers ssl proxy_fcgi deflate expires setenvif
|
||||||
|
systemctl enable apache2
|
||||||
|
systemctl start apache2
|
||||||
|
echo "Apache installed with required modules."
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
apt install -y nginx apache2 libapache2-mod-fcgid
|
||||||
|
a2enmod rewrite headers ssl proxy_fcgi deflate expires setenvif
|
||||||
|
systemctl enable nginx apache2
|
||||||
|
# Stop Apache by default to avoid port conflict
|
||||||
|
systemctl stop apache2
|
||||||
|
systemctl start nginx
|
||||||
|
echo "Both installed. Nginx is running. Apache is stopped (start manually when needed)."
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
echo "Skipping web server installation."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Configure PHP-FPM
|
||||||
|
echo ""
|
||||||
|
echo "Configuring PHP-FPM..."
|
||||||
|
cat > /etc/php/8.3/fpm/conf.d/99-laravel.ini << 'EOF'
|
||||||
|
upload_max_filesize = 100M
|
||||||
|
post_max_size = 100M
|
||||||
|
max_execution_time = 300
|
||||||
|
memory_limit = 512M
|
||||||
|
opcache.enable = 1
|
||||||
|
opcache.memory_consumption = 256
|
||||||
|
opcache.interned_strings_buffer = 16
|
||||||
|
opcache.max_accelerated_files = 10000
|
||||||
|
opcache.validate_timestamps = 0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl restart php8.3-fpm
|
||||||
|
|
||||||
|
# Create web directory
|
||||||
|
echo ""
|
||||||
|
echo "Creating web directory structure..."
|
||||||
|
mkdir -p /var/www
|
||||||
|
chown -R www-data:www-data /var/www
|
||||||
|
|
||||||
|
# Setup firewall
|
||||||
|
echo ""
|
||||||
|
echo "Configuring UFW firewall..."
|
||||||
|
ufw allow OpenSSH
|
||||||
|
ufw allow 'Nginx Full' 2>/dev/null || true
|
||||||
|
ufw allow 'Apache Full' 2>/dev/null || true
|
||||||
|
ufw --force enable
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Server setup complete!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Installed:"
|
||||||
|
echo " - PHP 8.3 with FPM and extensions (MySQL, PostgreSQL, SQLite drivers)"
|
||||||
|
echo " - Composer"
|
||||||
|
echo " - Node.js 20.x"
|
||||||
|
echo " - Database server (based on selection)"
|
||||||
|
echo " - Redis"
|
||||||
|
echo " - Web server (based on selection)"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Create database and user for your selected database"
|
||||||
|
echo " 2. Clone your Laravel app to /var/www/your-app"
|
||||||
|
echo " 3. Copy appropriate .env file from deploy/production/"
|
||||||
|
echo " - .env.mysql.production"
|
||||||
|
echo " - .env.pgsql.production"
|
||||||
|
echo " - .env.sqlite.production"
|
||||||
|
echo " 4. Configure web server (use configs from deploy/nginx or deploy/apache)"
|
||||||
|
echo " 5. Set permissions: deploy/scripts/fix-permissions.sh"
|
||||||
|
echo ""
|
||||||
14
deploy/scripts/supervisor-scheduler.conf
Normal file
14
deploy/scripts/supervisor-scheduler.conf
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Laravel Scheduler via Supervisor (alternative to cron)
|
||||||
|
# Copy to: /etc/supervisor/conf.d/laravel-scheduler.conf
|
||||||
|
# This runs the scheduler in a loop instead of using cron
|
||||||
|
|
||||||
|
[program:laravel-scheduler]
|
||||||
|
process_name=%(program_name)s
|
||||||
|
command=/bin/bash -c "while [ true ]; do php /var/www/your-app/artisan schedule:run --verbose --no-interaction >> /var/www/your-app/storage/logs/scheduler.log 2>&1; sleep 60; done"
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
user=www-data
|
||||||
|
numprocs=1
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/var/www/your-app/storage/logs/supervisor-scheduler.log
|
||||||
|
stopwaitsecs=60
|
||||||
12
deploy/scripts/supervisor-worker.conf
Normal file
12
deploy/scripts/supervisor-worker.conf
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[program:laravel-worker]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=php /var/www/your-app/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
user=www-data
|
||||||
|
numprocs=2
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/var/www/your-app/storage/logs/worker.log
|
||||||
|
stopwaitsecs=3600
|
||||||
171
docker-compose.yml
Normal file
171
docker-compose.yml
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# PHP-FPM Application Server
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/php/Dockerfile
|
||||||
|
container_name: laravel_app
|
||||||
|
restart: unless-stopped
|
||||||
|
working_dir: /var/www/html
|
||||||
|
volumes:
|
||||||
|
- ./src:/var/www/html:cached
|
||||||
|
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini
|
||||||
|
networks:
|
||||||
|
- laravel_network
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
|
||||||
|
# Nginx Web Server
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: laravel_nginx
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${APP_PORT:-8080}:80"
|
||||||
|
volumes:
|
||||||
|
- ./src:/var/www/html:cached
|
||||||
|
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
networks:
|
||||||
|
- laravel_network
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# DATABASE OPTIONS (use profiles to select)
|
||||||
|
# Start with: docker-compose --profile mysql up
|
||||||
|
# Or: docker-compose --profile pgsql up
|
||||||
|
# Or: docker-compose --profile sqlite up
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# MySQL Database
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: laravel_mysql
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${DB_PORT:-3306}:3306"
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: ${DB_DATABASE:-laravel}
|
||||||
|
MYSQL_USER: ${DB_USERNAME:-laravel}
|
||||||
|
MYSQL_PASSWORD: ${DB_PASSWORD:-secret}
|
||||||
|
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-rootsecret}
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
|
||||||
|
networks:
|
||||||
|
- laravel_network
|
||||||
|
profiles:
|
||||||
|
- mysql
|
||||||
|
|
||||||
|
# PostgreSQL Database
|
||||||
|
pgsql:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: laravel_pgsql
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${DB_PORT:-5432}:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${DB_DATABASE:-laravel}
|
||||||
|
POSTGRES_USER: ${DB_USERNAME:-laravel}
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
|
||||||
|
volumes:
|
||||||
|
- pgsql_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- laravel_network
|
||||||
|
profiles:
|
||||||
|
- pgsql
|
||||||
|
|
||||||
|
# SQLite (no container needed, just volume for persistence)
|
||||||
|
# SQLite runs inside the app container
|
||||||
|
# This is a dummy service to enable the sqlite profile
|
||||||
|
sqlite:
|
||||||
|
image: alpine:latest
|
||||||
|
container_name: laravel_sqlite_init
|
||||||
|
volumes:
|
||||||
|
- ./src/database:/data
|
||||||
|
command: sh -c "touch /data/database.sqlite && chmod 666 /data/database.sqlite"
|
||||||
|
profiles:
|
||||||
|
- sqlite
|
||||||
|
|
||||||
|
# Redis Cache
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
container_name: laravel_redis
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${REDIS_PORT:-6379}:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- laravel_network
|
||||||
|
|
||||||
|
# Node.js for frontend assets (Vite/Mix)
|
||||||
|
node:
|
||||||
|
image: node:20-alpine
|
||||||
|
container_name: laravel_node
|
||||||
|
working_dir: /var/www/html
|
||||||
|
volumes:
|
||||||
|
- ./src:/var/www/html
|
||||||
|
networks:
|
||||||
|
- laravel_network
|
||||||
|
command: sh -c "npm install && npm run dev"
|
||||||
|
profiles:
|
||||||
|
- frontend
|
||||||
|
|
||||||
|
# Queue Worker (Laravel Horizon alternative for dev)
|
||||||
|
queue:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/php/Dockerfile
|
||||||
|
container_name: laravel_queue
|
||||||
|
restart: unless-stopped
|
||||||
|
working_dir: /var/www/html
|
||||||
|
volumes:
|
||||||
|
- ./src:/var/www/html
|
||||||
|
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini
|
||||||
|
networks:
|
||||||
|
- laravel_network
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
command: php artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
|
||||||
|
profiles:
|
||||||
|
- queue
|
||||||
|
|
||||||
|
# Scheduler (runs Laravel scheduler every minute)
|
||||||
|
scheduler:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/php/Dockerfile
|
||||||
|
container_name: laravel_scheduler
|
||||||
|
restart: unless-stopped
|
||||||
|
working_dir: /var/www/html
|
||||||
|
volumes:
|
||||||
|
- ./src:/var/www/html
|
||||||
|
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini
|
||||||
|
networks:
|
||||||
|
- laravel_network
|
||||||
|
command: sh -c "while true; do php artisan schedule:run --verbose --no-interaction & sleep 60; done"
|
||||||
|
profiles:
|
||||||
|
- scheduler
|
||||||
|
|
||||||
|
# Mailpit for local email testing
|
||||||
|
mailpit:
|
||||||
|
image: axllent/mailpit
|
||||||
|
container_name: laravel_mailpit
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${MAIL_PORT:-1025}:1025"
|
||||||
|
- "${MAIL_DASHBOARD_PORT:-8025}:8025"
|
||||||
|
networks:
|
||||||
|
- laravel_network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
laravel_network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
|
pgsql_data:
|
||||||
|
redis_data:
|
||||||
9
docker/mysql/my.cnf
Normal file
9
docker/mysql/my.cnf
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[mysqld]
|
||||||
|
general_log = 1
|
||||||
|
general_log_file = /var/lib/mysql/general.log
|
||||||
|
character-set-server = utf8mb4
|
||||||
|
collation-server = utf8mb4_unicode_ci
|
||||||
|
default-authentication-plugin = mysql_native_password
|
||||||
|
|
||||||
|
[client]
|
||||||
|
default-character-set = utf8mb4
|
||||||
42
docker/nginx/default.conf
Normal file
42
docker/nginx/default.conf
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
root /var/www/html/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 app:9000;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_hide_header X-Powered-By;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.(?!well-known).* {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_proxied expired no-cache no-store private auth;
|
||||||
|
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript;
|
||||||
|
gzip_disable "MSIE [1-6]\.";
|
||||||
|
|
||||||
|
client_max_body_size 100M;
|
||||||
|
}
|
||||||
57
docker/php/Dockerfile
Normal file
57
docker/php/Dockerfile
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
FROM php:8.3-fpm
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
libpng-dev \
|
||||||
|
libonig-dev \
|
||||||
|
libxml2-dev \
|
||||||
|
libzip-dev \
|
||||||
|
libpq-dev \
|
||||||
|
libicu-dev \
|
||||||
|
zip \
|
||||||
|
unzip \
|
||||||
|
supervisor \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install PHP extensions
|
||||||
|
RUN docker-php-ext-configure intl \
|
||||||
|
&& docker-php-ext-install \
|
||||||
|
pdo_mysql \
|
||||||
|
pdo_pgsql \
|
||||||
|
mbstring \
|
||||||
|
exif \
|
||||||
|
pcntl \
|
||||||
|
bcmath \
|
||||||
|
gd \
|
||||||
|
zip \
|
||||||
|
intl \
|
||||||
|
opcache
|
||||||
|
|
||||||
|
# Install Redis extension
|
||||||
|
RUN pecl install redis && docker-php-ext-enable redis
|
||||||
|
|
||||||
|
# Install Composer
|
||||||
|
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
# Create system user to run Composer and Artisan commands
|
||||||
|
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 --chown=devuser:devuser ./src /var/www/html
|
||||||
|
|
||||||
|
# Set proper permissions
|
||||||
|
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
|
||||||
|
|
||||||
|
USER devuser
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
CMD ["php-fpm"]
|
||||||
21
docker/php/local.ini
Normal file
21
docker/php/local.ini
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
; PHP Configuration for Development
|
||||||
|
|
||||||
|
upload_max_filesize = 100M
|
||||||
|
post_max_size = 100M
|
||||||
|
max_execution_time = 600
|
||||||
|
memory_limit = 512M
|
||||||
|
|
||||||
|
; Error reporting
|
||||||
|
display_errors = On
|
||||||
|
display_startup_errors = On
|
||||||
|
error_reporting = E_ALL
|
||||||
|
|
||||||
|
; 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
|
||||||
286
docs/audit-trail.md
Normal file
286
docs/audit-trail.md
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
# Audit Trail
|
||||||
|
|
||||||
|
This template includes a comprehensive audit trail system using [owen-it/laravel-auditing](https://github.com/owen-it/laravel-auditing) with Filament UI integration.
|
||||||
|
|
||||||
|
## What Gets Tracked
|
||||||
|
|
||||||
|
For every audited model:
|
||||||
|
- **Who** - User who made the change
|
||||||
|
- **What** - Model and record ID
|
||||||
|
- **When** - Timestamp
|
||||||
|
- **Changes** - Old values → New values
|
||||||
|
- **Where** - IP address, user agent
|
||||||
|
- **Module** - Which module the change belongs to
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Audit trail is set up during `make setup-laravel`. To add auditing to a model:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use App\Traits\ModuleAuditable;
|
||||||
|
use OwenIt\Auditing\Contracts\Auditable;
|
||||||
|
|
||||||
|
class Product extends Model implements Auditable
|
||||||
|
{
|
||||||
|
use ModuleAuditable;
|
||||||
|
|
||||||
|
// Your model code...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! All create, update, and delete operations are now logged.
|
||||||
|
|
||||||
|
## Viewing Audit Logs
|
||||||
|
|
||||||
|
Each module has an **Audit Log** page in its admin section:
|
||||||
|
|
||||||
|
```
|
||||||
|
📦 Stock Management
|
||||||
|
├── Dashboard
|
||||||
|
├── Products
|
||||||
|
└── Audit Log ← Click here
|
||||||
|
```
|
||||||
|
|
||||||
|
The audit log shows:
|
||||||
|
- Date/Time
|
||||||
|
- User
|
||||||
|
- Event type (created/updated/deleted)
|
||||||
|
- Model
|
||||||
|
- Old → New values
|
||||||
|
|
||||||
|
Click any entry to see full details including IP address and all changed fields.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Per-Module Configuration
|
||||||
|
|
||||||
|
Each module has audit settings in its config file:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Config/stock_management.php
|
||||||
|
'audit' => [
|
||||||
|
'enabled' => true, // Enable/disable for entire module
|
||||||
|
'strategy' => 'all', // 'all', 'include', 'exclude', 'none'
|
||||||
|
'exclude' => [ // Fields to never audit
|
||||||
|
'password',
|
||||||
|
'remember_token',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
### Strategies
|
||||||
|
|
||||||
|
| Strategy | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `all` | Audit all fields (default) |
|
||||||
|
| `include` | Only audit fields in `$auditInclude` |
|
||||||
|
| `exclude` | Audit all except fields in `$auditExclude` |
|
||||||
|
| `none` | Disable auditing |
|
||||||
|
|
||||||
|
### Per-Model Configuration
|
||||||
|
|
||||||
|
Override in your model:
|
||||||
|
|
||||||
|
```php
|
||||||
|
class Product extends Model implements Auditable
|
||||||
|
{
|
||||||
|
use ModuleAuditable;
|
||||||
|
|
||||||
|
// Only audit these fields
|
||||||
|
protected $auditInclude = [
|
||||||
|
'name',
|
||||||
|
'price',
|
||||||
|
'quantity',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Or exclude specific fields
|
||||||
|
protected $auditExclude = [
|
||||||
|
'internal_notes',
|
||||||
|
'cache_data',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Audit Events
|
||||||
|
|
||||||
|
By default, these events are tracked:
|
||||||
|
- `created` - New record created
|
||||||
|
- `updated` - Record modified
|
||||||
|
- `deleted` - Record deleted
|
||||||
|
|
||||||
|
### Custom Events
|
||||||
|
|
||||||
|
Log custom events:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// In your model or service
|
||||||
|
$product->auditEvent = 'approved';
|
||||||
|
$product->isCustomEvent = true;
|
||||||
|
$product->auditCustomOld = ['status' => 'pending'];
|
||||||
|
$product->auditCustomNew = ['status' => 'approved'];
|
||||||
|
$product->save();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Querying Audits
|
||||||
|
|
||||||
|
### Get audits for a record
|
||||||
|
|
||||||
|
```php
|
||||||
|
$product = Product::find(1);
|
||||||
|
$audits = $product->audits;
|
||||||
|
|
||||||
|
// With user info
|
||||||
|
$audits = $product->audits()->with('user')->get();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get audits by user
|
||||||
|
|
||||||
|
```php
|
||||||
|
use OwenIt\Auditing\Models\Audit;
|
||||||
|
|
||||||
|
$userAudits = Audit::where('user_id', $userId)->get();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get audits by module
|
||||||
|
|
||||||
|
```php
|
||||||
|
$moduleAudits = Audit::where('tags', 'like', '%module:StockManagement%')->get();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get recent changes
|
||||||
|
|
||||||
|
```php
|
||||||
|
$recentChanges = Audit::latest()->take(50)->get();
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI Customization
|
||||||
|
|
||||||
|
### Modify Audit Log Table
|
||||||
|
|
||||||
|
Edit `Filament/Resources/AuditLogResource.php` in your module:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
// Add or modify columns
|
||||||
|
Tables\Columns\TextColumn::make('custom_field'),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
// Add custom filters
|
||||||
|
Tables\Filters\Filter::make('today')
|
||||||
|
->query(fn ($query) => $query->whereDate('created_at', today())),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add Audit Tab to Resource
|
||||||
|
|
||||||
|
Add audit history tab to any Filament resource:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Tapp\FilamentAuditing\RelationManagers\AuditsRelationManager;
|
||||||
|
|
||||||
|
class ProductResource extends Resource
|
||||||
|
{
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
AuditsRelationManager::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Retention
|
||||||
|
|
||||||
|
### Pruning Old Audits
|
||||||
|
|
||||||
|
Add to `app/Console/Kernel.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
protected function schedule(Schedule $schedule)
|
||||||
|
{
|
||||||
|
// Delete audits older than 90 days
|
||||||
|
$schedule->command('audit:prune --days=90')->daily();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or run manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan audit:prune --days=90
|
||||||
|
```
|
||||||
|
|
||||||
|
### Archive Before Delete
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Export to CSV before pruning
|
||||||
|
Audit::where('created_at', '<', now()->subDays(90))
|
||||||
|
->each(function ($audit) {
|
||||||
|
// Write to archive file
|
||||||
|
Storage::append('audits/archive.csv', $audit->toJson());
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disabling Auditing
|
||||||
|
|
||||||
|
### Temporarily
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Disable for a single operation
|
||||||
|
$product->disableAuditing();
|
||||||
|
$product->update(['price' => 99.99]);
|
||||||
|
$product->enableAuditing();
|
||||||
|
|
||||||
|
// Or use without auditing
|
||||||
|
Product::withoutAuditing(function () {
|
||||||
|
Product::where('category', 'sale')->update(['on_sale' => true]);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### For Specific Model
|
||||||
|
|
||||||
|
```php
|
||||||
|
class CacheModel extends Model implements Auditable
|
||||||
|
{
|
||||||
|
use ModuleAuditable;
|
||||||
|
|
||||||
|
// Disable auditing for this model
|
||||||
|
public $auditEvents = [];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### For Entire Module
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Config/module_name.php
|
||||||
|
'audit' => [
|
||||||
|
'enabled' => false,
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Audits not being created
|
||||||
|
1. Model implements `Auditable` interface
|
||||||
|
2. Model uses `ModuleAuditable` trait
|
||||||
|
3. Check module config `audit.enabled` is true
|
||||||
|
4. Run `php artisan config:clear`
|
||||||
|
|
||||||
|
### User not being recorded
|
||||||
|
1. Ensure user is authenticated when changes are made
|
||||||
|
2. Check `config/audit.php` for user resolver settings
|
||||||
|
|
||||||
|
### Performance concerns
|
||||||
|
1. Use `$auditInclude` to limit tracked fields
|
||||||
|
2. Set up audit pruning for old records
|
||||||
|
3. Consider async audit processing for high-volume apps
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Audit records are **read-only** in the admin panel
|
||||||
|
- No create/edit/delete actions available
|
||||||
|
- Access controlled by module permissions
|
||||||
|
- Sensitive fields (password, tokens) excluded by default
|
||||||
238
docs/backup.md
Normal file
238
docs/backup.md
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
# Database Backup & Restore
|
||||||
|
|
||||||
|
Scripts for backing up and restoring your database.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create backup
|
||||||
|
make backup
|
||||||
|
|
||||||
|
# List backups
|
||||||
|
ls -la backups/
|
||||||
|
|
||||||
|
# Restore from backup
|
||||||
|
make restore file=backups/laravel_20240306_120000.sql.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup
|
||||||
|
|
||||||
|
The backup script automatically:
|
||||||
|
- Detects database type (MySQL, PostgreSQL, SQLite)
|
||||||
|
- Creates timestamped backup
|
||||||
|
- Compresses with gzip
|
||||||
|
- Keeps only last 10 backups
|
||||||
|
|
||||||
|
### Manual Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
==========================================
|
||||||
|
Database Backup
|
||||||
|
==========================================
|
||||||
|
Connection: mysql
|
||||||
|
Database: laravel
|
||||||
|
|
||||||
|
Creating MySQL backup...
|
||||||
|
|
||||||
|
✓ Backup created successfully!
|
||||||
|
File: backups/laravel_20240306_120000.sql.gz
|
||||||
|
Size: 2.5M
|
||||||
|
|
||||||
|
Recent backups:
|
||||||
|
-rw-r--r-- 1 user user 2.5M Mar 6 12:00 laravel_20240306_120000.sql.gz
|
||||||
|
-rw-r--r-- 1 user user 2.4M Mar 5 12:00 laravel_20240305_120000.sql.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup Location
|
||||||
|
|
||||||
|
```
|
||||||
|
backups/
|
||||||
|
├── laravel_20240306_120000.sql.gz
|
||||||
|
├── laravel_20240305_120000.sql.gz
|
||||||
|
└── laravel_20240304_120000.sql.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
## Restore
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# With make
|
||||||
|
make restore file=backups/laravel_20240306_120000.sql.gz
|
||||||
|
|
||||||
|
# Or directly
|
||||||
|
./scripts/restore.sh backups/laravel_20240306_120000.sql.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
**Warning:** Restore will overwrite the current database!
|
||||||
|
|
||||||
|
## Automated Backups
|
||||||
|
|
||||||
|
### Using Scheduler
|
||||||
|
|
||||||
|
Add to your Laravel scheduler (`routes/console.php`):
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Illuminate\Support\Facades\Schedule;
|
||||||
|
|
||||||
|
// Daily backup at 2 AM
|
||||||
|
Schedule::exec('bash scripts/backup.sh')
|
||||||
|
->dailyAt('02:00')
|
||||||
|
->sendOutputTo(storage_path('logs/backup.log'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Cron (Production)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Edit crontab
|
||||||
|
crontab -e
|
||||||
|
|
||||||
|
# Add daily backup at 2 AM
|
||||||
|
0 2 * * * cd /var/www/html && bash scripts/backup.sh >> /var/log/laravel-backup.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Remote Backup Storage
|
||||||
|
|
||||||
|
### Copy to S3
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install AWS CLI
|
||||||
|
pip install awscli
|
||||||
|
|
||||||
|
# Configure
|
||||||
|
aws configure
|
||||||
|
|
||||||
|
# Upload backup
|
||||||
|
LATEST=$(ls -t backups/*.gz | head -1)
|
||||||
|
aws s3 cp "$LATEST" s3://your-bucket/backups/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automate S3 Upload
|
||||||
|
|
||||||
|
Add to `scripts/backup.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# After backup creation
|
||||||
|
if command -v aws &> /dev/null; then
|
||||||
|
echo "Uploading to S3..."
|
||||||
|
aws s3 cp "$BACKUP_FILE" "s3://${S3_BUCKET}/backups/"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Laravel Backup Package
|
||||||
|
|
||||||
|
For more features, use [spatie/laravel-backup](https://github.com/spatie/laravel-backup):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require spatie/laravel-backup
|
||||||
|
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"
|
||||||
|
```
|
||||||
|
|
||||||
|
```php
|
||||||
|
// config/backup.php
|
||||||
|
'destination' => [
|
||||||
|
'disks' => ['local', 's3'],
|
||||||
|
],
|
||||||
|
|
||||||
|
// Schedule
|
||||||
|
Schedule::command('backup:run')->daily();
|
||||||
|
Schedule::command('backup:clean')->daily();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database-Specific Notes
|
||||||
|
|
||||||
|
### MySQL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Manual backup
|
||||||
|
docker-compose exec mysql mysqldump -u laravel -p laravel > backup.sql
|
||||||
|
|
||||||
|
# Manual restore
|
||||||
|
docker-compose exec -T mysql mysql -u laravel -p laravel < backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Manual backup
|
||||||
|
docker-compose exec pgsql pg_dump -U laravel laravel > backup.sql
|
||||||
|
|
||||||
|
# Manual restore
|
||||||
|
docker-compose exec -T pgsql psql -U laravel laravel < backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### SQLite
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Manual backup (just copy the file)
|
||||||
|
cp src/database/database.sqlite backups/database_backup.sqlite
|
||||||
|
|
||||||
|
# Manual restore
|
||||||
|
cp backups/database_backup.sqlite src/database/database.sqlite
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup Strategy
|
||||||
|
|
||||||
|
### Development
|
||||||
|
- Manual backups before major changes
|
||||||
|
- Keep last 5 backups
|
||||||
|
|
||||||
|
### Staging
|
||||||
|
- Daily automated backups
|
||||||
|
- Keep last 7 days
|
||||||
|
|
||||||
|
### Production
|
||||||
|
- Hourly incremental (if supported)
|
||||||
|
- Daily full backup
|
||||||
|
- Weekly backup to offsite storage
|
||||||
|
- Keep 30 days of backups
|
||||||
|
- Test restores monthly
|
||||||
|
|
||||||
|
## Testing Restores
|
||||||
|
|
||||||
|
**Important:** Regularly test your backups!
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create test database
|
||||||
|
docker-compose exec mysql mysql -u root -p -e "CREATE DATABASE restore_test;"
|
||||||
|
|
||||||
|
# Restore to test database
|
||||||
|
gunzip -c backups/latest.sql.gz | docker-compose exec -T mysql mysql -u root -p restore_test
|
||||||
|
|
||||||
|
# Verify data
|
||||||
|
docker-compose exec mysql mysql -u root -p restore_test -e "SELECT COUNT(*) FROM users;"
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
docker-compose exec mysql mysql -u root -p -e "DROP DATABASE restore_test;"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Backup fails with permission error
|
||||||
|
```bash
|
||||||
|
chmod +x scripts/backup.sh scripts/restore.sh
|
||||||
|
mkdir -p backups
|
||||||
|
chmod 755 backups
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restore fails - database locked
|
||||||
|
```bash
|
||||||
|
# Stop queue workers
|
||||||
|
make queue-stop
|
||||||
|
|
||||||
|
# Run restore
|
||||||
|
make restore file=backups/backup.sql.gz
|
||||||
|
|
||||||
|
# Restart queue workers
|
||||||
|
make queue-start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Large database backup timeout
|
||||||
|
```bash
|
||||||
|
# Increase timeout in docker-compose.yml
|
||||||
|
environment:
|
||||||
|
MYSQL_CONNECT_TIMEOUT: 600
|
||||||
|
```
|
||||||
263
docs/ci-cd.md
Normal file
263
docs/ci-cd.md
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
# CI/CD Pipeline
|
||||||
|
|
||||||
|
This template includes a GitHub Actions workflow for continuous integration and deployment.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
|
||||||
|
│ Push to │────▶│ Run Tests │────▶│ Deploy Staging │
|
||||||
|
│ develop │ │ + Lint │ │ (automatic) │
|
||||||
|
└─────────────┘ └─────────────┘ └─────────────────┘
|
||||||
|
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
|
||||||
|
│ Push to │────▶│ Run Tests │────▶│ Deploy Prod │
|
||||||
|
│ main │ │ + Lint │ │ (with approval)│
|
||||||
|
└─────────────┘ └─────────────┘ └─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow File
|
||||||
|
|
||||||
|
Located at `.github/workflows/ci.yml`
|
||||||
|
|
||||||
|
### Jobs
|
||||||
|
|
||||||
|
| Job | Trigger | Description |
|
||||||
|
|-----|---------|-------------|
|
||||||
|
| **tests** | All pushes/PRs | Run Pest tests + Pint lint |
|
||||||
|
| **deploy-staging** | Push to `develop` | Auto-deploy to staging |
|
||||||
|
| **deploy-production** | Push to `main` | Deploy with approval |
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Create GitHub Secrets
|
||||||
|
|
||||||
|
Go to: Repository → Settings → Secrets and variables → Actions
|
||||||
|
|
||||||
|
**For Staging:**
|
||||||
|
```
|
||||||
|
STAGING_HOST - Staging server IP/hostname
|
||||||
|
STAGING_USER - SSH username
|
||||||
|
STAGING_SSH_KEY - Private SSH key (full content)
|
||||||
|
```
|
||||||
|
|
||||||
|
**For Production:**
|
||||||
|
```
|
||||||
|
PRODUCTION_HOST - Production server IP/hostname
|
||||||
|
PRODUCTION_USER - SSH username
|
||||||
|
PRODUCTION_SSH_KEY - Private SSH key (full content)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Generate SSH Key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate a new key pair
|
||||||
|
ssh-keygen -t ed25519 -C "github-actions" -f github-actions-key
|
||||||
|
|
||||||
|
# Add public key to server
|
||||||
|
cat github-actions-key.pub >> ~/.ssh/authorized_keys
|
||||||
|
|
||||||
|
# Copy private key to GitHub secret
|
||||||
|
cat github-actions-key
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Configure Server
|
||||||
|
|
||||||
|
On your server, ensure:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create deployment directory
|
||||||
|
sudo mkdir -p /var/www/staging
|
||||||
|
sudo mkdir -p /var/www/production
|
||||||
|
sudo chown -R $USER:www-data /var/www/staging /var/www/production
|
||||||
|
|
||||||
|
# Clone repository
|
||||||
|
cd /var/www/staging
|
||||||
|
git clone git@github.com:your-repo.git .
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
composer install
|
||||||
|
npm install && npm run build
|
||||||
|
|
||||||
|
# Configure environment
|
||||||
|
cp .env.example .env
|
||||||
|
php artisan key:generate
|
||||||
|
# Edit .env with production values
|
||||||
|
|
||||||
|
# Set permissions
|
||||||
|
chmod -R 775 storage bootstrap/cache
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Environment Protection (Optional)
|
||||||
|
|
||||||
|
For production deployments with approval:
|
||||||
|
|
||||||
|
1. Go to Repository → Settings → Environments
|
||||||
|
2. Create `production` environment
|
||||||
|
3. Enable "Required reviewers"
|
||||||
|
4. Add team members who can approve
|
||||||
|
|
||||||
|
## Workflow Customization
|
||||||
|
|
||||||
|
### Add More Tests
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Run security audit
|
||||||
|
working-directory: ./src
|
||||||
|
run: composer audit
|
||||||
|
|
||||||
|
- name: Run static analysis
|
||||||
|
working-directory: ./src
|
||||||
|
run: ./vendor/bin/phpstan analyse
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add Notifications
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Notify Slack
|
||||||
|
uses: 8398a7/action-slack@v3
|
||||||
|
with:
|
||||||
|
status: ${{ job.status }}
|
||||||
|
fields: repo,commit,author,action
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
|
||||||
|
if: always()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add Database Migrations Check
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Check pending migrations
|
||||||
|
working-directory: ./src
|
||||||
|
run: |
|
||||||
|
PENDING=$(php artisan migrate:status | grep -c "No" || true)
|
||||||
|
if [ "$PENDING" -gt 0 ]; then
|
||||||
|
echo "::warning::There are pending migrations"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Deployment
|
||||||
|
|
||||||
|
If you prefer manual deployments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On your server
|
||||||
|
cd /var/www/production
|
||||||
|
|
||||||
|
# Enable maintenance mode
|
||||||
|
php artisan down
|
||||||
|
|
||||||
|
# Pull latest code
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
composer install --no-dev --optimize-autoloader
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
php artisan migrate --force
|
||||||
|
|
||||||
|
# Clear and cache
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
|
||||||
|
# Restart queue workers
|
||||||
|
php artisan queue:restart
|
||||||
|
|
||||||
|
# Disable maintenance mode
|
||||||
|
php artisan up
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment Script
|
||||||
|
|
||||||
|
Create `deploy/scripts/deploy.sh` for reusable deployment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Starting deployment..."
|
||||||
|
|
||||||
|
# Enter maintenance mode
|
||||||
|
php artisan down
|
||||||
|
|
||||||
|
# Pull latest changes
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
composer install --no-dev --optimize-autoloader
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
php artisan migrate --force
|
||||||
|
|
||||||
|
# Build assets
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Clear caches
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
php artisan event:cache
|
||||||
|
|
||||||
|
# Restart queue
|
||||||
|
php artisan queue:restart
|
||||||
|
|
||||||
|
# Exit maintenance mode
|
||||||
|
php artisan up
|
||||||
|
|
||||||
|
echo "✅ Deployment complete!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
If deployment fails:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Revert to previous commit
|
||||||
|
git reset --hard HEAD~1
|
||||||
|
|
||||||
|
# Or specific commit
|
||||||
|
git reset --hard <commit-hash>
|
||||||
|
|
||||||
|
# Re-run caching
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:cache
|
||||||
|
|
||||||
|
# Restart services
|
||||||
|
php artisan queue:restart
|
||||||
|
php artisan up
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Locally
|
||||||
|
|
||||||
|
Test the CI workflow locally with [act](https://github.com/nektos/act):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install act
|
||||||
|
brew install act # macOS
|
||||||
|
# or
|
||||||
|
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
|
||||||
|
|
||||||
|
# Run tests job
|
||||||
|
act -j tests
|
||||||
|
|
||||||
|
# Run with secrets
|
||||||
|
act -j tests --secret-file .secrets
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### SSH Connection Failed
|
||||||
|
- Verify SSH key is correct (no extra newlines)
|
||||||
|
- Check server firewall allows port 22
|
||||||
|
- Ensure key is added to `~/.ssh/authorized_keys`
|
||||||
|
|
||||||
|
### Permission Denied
|
||||||
|
- Check file ownership: `chown -R www-data:www-data /var/www`
|
||||||
|
- Check directory permissions: `chmod -R 775 storage bootstrap/cache`
|
||||||
|
|
||||||
|
### Composer/NPM Fails
|
||||||
|
- Ensure sufficient memory on server
|
||||||
|
- Check PHP extensions are installed
|
||||||
|
- Verify Node.js version matches requirements
|
||||||
177
docs/error-logging.md
Normal file
177
docs/error-logging.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Error Logging Setup
|
||||||
|
|
||||||
|
This template uses **Flare + Ignition** by Spatie for error logging.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
| Environment | Tool | Purpose |
|
||||||
|
|-------------|------|---------|
|
||||||
|
| Development | **Ignition** | Rich in-browser error pages with AI explanations |
|
||||||
|
| Development | **Telescope** (optional) | Request/query/job debugging dashboard |
|
||||||
|
| Production | **Flare** | Remote error tracking, notifications |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Development:
|
||||||
|
Error → Ignition → Beautiful error page with:
|
||||||
|
- Stack trace with code context
|
||||||
|
- AI-powered explanations
|
||||||
|
- Click-to-open in VS Code
|
||||||
|
- Suggested solutions
|
||||||
|
|
||||||
|
Production:
|
||||||
|
Error → Flare (remote) → Notifications (Slack/Email)
|
||||||
|
↓
|
||||||
|
User sees clean 500.blade.php error page
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Run Post-Install Script
|
||||||
|
|
||||||
|
After creating your Laravel project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In Docker
|
||||||
|
make setup-tools
|
||||||
|
|
||||||
|
# Or manually
|
||||||
|
cd src
|
||||||
|
bash ../scripts/post-install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Get Flare API Key
|
||||||
|
|
||||||
|
1. Sign up at [flareapp.io](https://flareapp.io)
|
||||||
|
2. Create a new project
|
||||||
|
3. Copy your API key
|
||||||
|
|
||||||
|
### 3. Configure Environment
|
||||||
|
|
||||||
|
**Development (.env):**
|
||||||
|
```env
|
||||||
|
FLARE_KEY=your_flare_key_here
|
||||||
|
IGNITION_THEME=auto
|
||||||
|
IGNITION_EDITOR=vscode
|
||||||
|
```
|
||||||
|
|
||||||
|
**Production (.env):**
|
||||||
|
```env
|
||||||
|
APP_DEBUG=false
|
||||||
|
FLARE_KEY=your_flare_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ignition Features (Development)
|
||||||
|
|
||||||
|
### AI Error Explanations
|
||||||
|
Ignition can explain errors using AI. Click "AI" button on any error page.
|
||||||
|
|
||||||
|
### Click-to-Open in Editor
|
||||||
|
Errors link directly to the file and line in your editor.
|
||||||
|
|
||||||
|
Supported editors (set via `IGNITION_EDITOR`):
|
||||||
|
- `vscode` - Visual Studio Code
|
||||||
|
- `phpstorm` - PhpStorm
|
||||||
|
- `sublime` - Sublime Text
|
||||||
|
- `atom` - Atom
|
||||||
|
- `textmate` - TextMate
|
||||||
|
|
||||||
|
### Runnable Solutions
|
||||||
|
Ignition suggests fixes for common issues that you can apply with one click.
|
||||||
|
|
||||||
|
### Share Error Context
|
||||||
|
Click "Share" to create a shareable link for debugging with teammates.
|
||||||
|
|
||||||
|
## Telescope (Optional)
|
||||||
|
|
||||||
|
Telescope provides a debug dashboard at `/telescope` with:
|
||||||
|
|
||||||
|
- **Requests** - All HTTP requests with timing
|
||||||
|
- **Exceptions** - All caught exceptions
|
||||||
|
- **Logs** - Log entries
|
||||||
|
- **Queries** - Database queries with timing
|
||||||
|
- **Jobs** - Queue job processing
|
||||||
|
- **Mail** - Sent emails
|
||||||
|
- **Notifications** - All notifications
|
||||||
|
- **Cache** - Cache operations
|
||||||
|
|
||||||
|
### Installing Telescope
|
||||||
|
|
||||||
|
The post-install script offers to install Telescope. To install manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require laravel/telescope --dev
|
||||||
|
php artisan telescope:install
|
||||||
|
php artisan migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Telescope in Production
|
||||||
|
|
||||||
|
Telescope is installed as a dev dependency. For production debugging:
|
||||||
|
|
||||||
|
1. Install without `--dev`
|
||||||
|
2. Configure authorization in `app/Providers/TelescopeServiceProvider.php`
|
||||||
|
3. Access via `/telescope` (requires authentication)
|
||||||
|
|
||||||
|
## Custom Error Pages
|
||||||
|
|
||||||
|
The post-install script creates custom error pages:
|
||||||
|
|
||||||
|
- `resources/views/errors/404.blade.php` - Not Found
|
||||||
|
- `resources/views/errors/500.blade.php` - Server Error
|
||||||
|
- `resources/views/errors/503.blade.php` - Maintenance Mode
|
||||||
|
|
||||||
|
These are shown to users in production while Flare captures the full error details.
|
||||||
|
|
||||||
|
## Flare Dashboard
|
||||||
|
|
||||||
|
In your Flare dashboard you can:
|
||||||
|
|
||||||
|
- View all errors with full stack traces
|
||||||
|
- See request data, session, user info
|
||||||
|
- Group errors by type
|
||||||
|
- Track error frequency over time
|
||||||
|
- Set up notifications (Slack, Email, Discord)
|
||||||
|
- Mark errors as resolved
|
||||||
|
|
||||||
|
## Testing Error Logging
|
||||||
|
|
||||||
|
### Test in Development
|
||||||
|
|
||||||
|
Add a test route in `routes/web.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
Route::get('/test-error', function () {
|
||||||
|
throw new \Exception('Test error for Flare!');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Visit `/test-error` to see:
|
||||||
|
- Ignition error page (development)
|
||||||
|
- Error logged in Flare dashboard
|
||||||
|
|
||||||
|
### Test in Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan down # Enable maintenance mode
|
||||||
|
# Visit site - should see 503 page
|
||||||
|
|
||||||
|
php artisan up # Disable maintenance mode
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Errors not appearing in Flare
|
||||||
|
1. Check `FLARE_KEY` is set correctly
|
||||||
|
2. Verify `APP_ENV=production` and `APP_DEBUG=false`
|
||||||
|
3. Check network connectivity from server
|
||||||
|
|
||||||
|
### Ignition not showing AI explanations
|
||||||
|
1. Requires OpenAI API key in Flare settings
|
||||||
|
2. Available on paid Flare plans
|
||||||
|
|
||||||
|
### Telescope not loading
|
||||||
|
1. Run `php artisan telescope:install`
|
||||||
|
2. Run `php artisan migrate`
|
||||||
|
3. Clear cache: `php artisan config:clear`
|
||||||
220
docs/filament-admin.md
Normal file
220
docs/filament-admin.md
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# Filament Admin Panel
|
||||||
|
|
||||||
|
This template includes optional [Filament](https://filamentphp.com/) admin panel setup for user management and administration.
|
||||||
|
|
||||||
|
## What is Filament?
|
||||||
|
|
||||||
|
Filament is a full-stack admin panel framework for Laravel built on Livewire. It provides:
|
||||||
|
|
||||||
|
- **Admin Panel** - Beautiful, responsive dashboard
|
||||||
|
- **Form Builder** - Dynamic forms with validation
|
||||||
|
- **Table Builder** - Sortable, searchable, filterable tables
|
||||||
|
- **User Management** - CRUD for users out of the box
|
||||||
|
- **Widgets** - Dashboard stats and charts
|
||||||
|
- **Notifications** - Toast notifications
|
||||||
|
- **Actions** - Bulk actions, row actions
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Filament is installed via `make setup-laravel` when you select "Yes" for admin panel.
|
||||||
|
|
||||||
|
Manual installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require filament/filament:"^3.2" -W
|
||||||
|
php artisan filament:install --panels
|
||||||
|
php artisan make:filament-user
|
||||||
|
php artisan make:filament-resource User --generate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing Admin Panel
|
||||||
|
|
||||||
|
- **URL**: `http://localhost:8080/admin`
|
||||||
|
- **Login**: Use credentials created during setup
|
||||||
|
|
||||||
|
## User Management
|
||||||
|
|
||||||
|
The setup creates a `UserResource` at `app/Filament/Resources/UserResource.php`.
|
||||||
|
|
||||||
|
This provides:
|
||||||
|
- List all users with search/filter
|
||||||
|
- Create new users
|
||||||
|
- Edit existing users
|
||||||
|
- Delete users
|
||||||
|
|
||||||
|
### Customizing User Resource
|
||||||
|
|
||||||
|
Edit `app/Filament/Resources/UserResource.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
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()
|
||||||
|
->unique(ignoreRecord: true),
|
||||||
|
Forms\Components\DateTimePicker::make('email_verified_at'),
|
||||||
|
Forms\Components\TextInput::make('password')
|
||||||
|
->password()
|
||||||
|
->dehydrateStateUsing(fn ($state) => Hash::make($state))
|
||||||
|
->dehydrated(fn ($state) => filled($state))
|
||||||
|
->required(fn (string $context): bool => $context === 'create'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
Tables\Actions\DeleteAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding Roles & Permissions
|
||||||
|
|
||||||
|
For role-based access, add Spatie Permission:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require spatie/laravel-permission
|
||||||
|
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
|
||||||
|
php artisan migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
Then install Filament Shield for admin UI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require bezhansalleh/filament-shield
|
||||||
|
php artisan shield:install
|
||||||
|
```
|
||||||
|
|
||||||
|
This adds:
|
||||||
|
- Role management in admin
|
||||||
|
- Permission management
|
||||||
|
- Protect resources by role
|
||||||
|
|
||||||
|
## Creating Additional Resources
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate resource for a model
|
||||||
|
php artisan make:filament-resource Post --generate
|
||||||
|
|
||||||
|
# Generate with soft deletes
|
||||||
|
php artisan make:filament-resource Post --generate --soft-deletes
|
||||||
|
|
||||||
|
# Generate simple (modal-based, no separate pages)
|
||||||
|
php artisan make:filament-resource Post --simple
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dashboard Widgets
|
||||||
|
|
||||||
|
Create a stats widget:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan make:filament-widget StatsOverview --stats-overview
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `app/Filament/Widgets/StatsOverview.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
protected function getStats(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Stat::make('Total Users', User::count()),
|
||||||
|
Stat::make('Verified Users', User::whereNotNull('email_verified_at')->count()),
|
||||||
|
Stat::make('New Today', User::whereDate('created_at', today())->count()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Restricting Admin Access
|
||||||
|
|
||||||
|
### By Email Domain
|
||||||
|
|
||||||
|
In `app/Providers/Filament/AdminPanelProvider.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
->authGuard('web')
|
||||||
|
->login()
|
||||||
|
->registration(false) // Disable public registration
|
||||||
|
```
|
||||||
|
|
||||||
|
### By User Method
|
||||||
|
|
||||||
|
Add to `User` model:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function canAccessPanel(Panel $panel): bool
|
||||||
|
{
|
||||||
|
return str_ends_with($this->email, '@yourcompany.com');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with roles:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function canAccessPanel(Panel $panel): bool
|
||||||
|
{
|
||||||
|
return $this->hasRole('admin');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customizing Theme
|
||||||
|
|
||||||
|
Publish and customize:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan vendor:publish --tag=filament-config
|
||||||
|
php artisan vendor:publish --tag=filament-panels-translations
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit colors in `app/Providers/Filament/AdminPanelProvider.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
->colors([
|
||||||
|
'primary' => Color::Indigo,
|
||||||
|
'danger' => Color::Rose,
|
||||||
|
'success' => Color::Emerald,
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Considerations
|
||||||
|
|
||||||
|
1. **Disable registration** in admin panel
|
||||||
|
2. **Use strong passwords** for admin users
|
||||||
|
3. **Enable 2FA** if using Jetstream
|
||||||
|
4. **Restrict by IP** in production if possible
|
||||||
|
5. **Monitor admin actions** via activity logging
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Filament Documentation](https://filamentphp.com/docs)
|
||||||
|
- [Filament Plugins](https://filamentphp.com/plugins)
|
||||||
|
- [Filament Shield (Roles)](https://github.com/bezhanSalleh/filament-shield)
|
||||||
263
docs/laravel-setup.md
Normal file
263
docs/laravel-setup.md
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
# Laravel Base Setup Guide
|
||||||
|
|
||||||
|
This guide covers setting up authentication, API, and base middleware for your Laravel application.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
After installing Laravel and running `make setup-tools`, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make setup-laravel
|
||||||
|
```
|
||||||
|
|
||||||
|
This interactive script will:
|
||||||
|
1. Set up authentication (Breeze or Jetstream)
|
||||||
|
2. Configure Sanctum for API authentication
|
||||||
|
3. Create security middleware
|
||||||
|
4. Set up storage symlink
|
||||||
|
|
||||||
|
## Authentication Options
|
||||||
|
|
||||||
|
> **This template focuses on Blade and Livewire** - no JavaScript frameworks (Vue/React/Inertia). This keeps debugging simple and server-side.
|
||||||
|
|
||||||
|
### Laravel Breeze + Blade (Recommended)
|
||||||
|
|
||||||
|
Best for: Most applications. Simple, fast, easy to debug.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Login, registration, password reset
|
||||||
|
- Email verification
|
||||||
|
- Profile editing
|
||||||
|
- Tailwind CSS styling
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require laravel/breeze --dev
|
||||||
|
php artisan breeze:install blade
|
||||||
|
php artisan migrate
|
||||||
|
npm install && npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Laravel Breeze + Livewire
|
||||||
|
|
||||||
|
Best for: Apps needing reactive UI without JavaScript frameworks.
|
||||||
|
|
||||||
|
Same features as Blade, but with dynamic updates via Livewire.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require laravel/breeze --dev
|
||||||
|
php artisan breeze:install livewire
|
||||||
|
php artisan migrate
|
||||||
|
npm install && npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Laravel Breeze API Only
|
||||||
|
|
||||||
|
Best for: When you want to build your own Blade views.
|
||||||
|
|
||||||
|
Provides API authentication endpoints, you build the frontend.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require laravel/breeze --dev
|
||||||
|
php artisan breeze:install api
|
||||||
|
php artisan migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Laravel Jetstream + Livewire (Full-featured)
|
||||||
|
|
||||||
|
Best for: SaaS applications needing teams, 2FA, API tokens.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Profile management with photo upload
|
||||||
|
- Two-factor authentication
|
||||||
|
- API token management
|
||||||
|
- Team management (optional)
|
||||||
|
- Session management
|
||||||
|
- Browser session logout
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require laravel/jetstream
|
||||||
|
php artisan jetstream:install livewire --teams
|
||||||
|
php artisan migrate
|
||||||
|
npm install && npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Authentication (Sanctum)
|
||||||
|
|
||||||
|
Laravel Sanctum provides:
|
||||||
|
- SPA authentication (cookie-based)
|
||||||
|
- API token authentication
|
||||||
|
- Mobile app authentication
|
||||||
|
|
||||||
|
### Creating Tokens
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Create a token
|
||||||
|
$token = $user->createToken('api-token')->plainTextToken;
|
||||||
|
|
||||||
|
// Create with abilities
|
||||||
|
$token = $user->createToken('api-token', ['posts:read', 'posts:write'])->plainTextToken;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authenticating Requests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using token
|
||||||
|
curl -H "Authorization: Bearer YOUR_TOKEN" https://your-app.com/api/user
|
||||||
|
|
||||||
|
# Using cookie (SPA)
|
||||||
|
# First get CSRF token from /sanctum/csrf-cookie
|
||||||
|
```
|
||||||
|
|
||||||
|
### Token Abilities
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Check ability
|
||||||
|
if ($user->tokenCan('posts:write')) {
|
||||||
|
// Can write posts
|
||||||
|
}
|
||||||
|
|
||||||
|
// In route middleware
|
||||||
|
Route::post('/posts', [PostController::class, 'store'])
|
||||||
|
->middleware('ability:posts:write');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Middleware
|
||||||
|
|
||||||
|
The setup script creates two middleware files:
|
||||||
|
|
||||||
|
### ForceHttps
|
||||||
|
|
||||||
|
Redirects HTTP to HTTPS in production.
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Register in bootstrap/app.php
|
||||||
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
|
$middleware->append(\App\Http\Middleware\ForceHttps::class);
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### SecurityHeaders
|
||||||
|
|
||||||
|
Adds security headers to all responses:
|
||||||
|
- X-Frame-Options
|
||||||
|
- X-Content-Type-Options
|
||||||
|
- X-XSS-Protection
|
||||||
|
- Referrer-Policy
|
||||||
|
- Permissions-Policy
|
||||||
|
- Strict-Transport-Security (production only)
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Register in bootstrap/app.php
|
||||||
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
|
$middleware->append(\App\Http\Middleware\SecurityHeaders::class);
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Routes Template
|
||||||
|
|
||||||
|
An example API routes file is provided at `src/routes/api.example.php`.
|
||||||
|
|
||||||
|
Key patterns:
|
||||||
|
- Health check endpoint
|
||||||
|
- Protected routes with `auth:sanctum`
|
||||||
|
- Token management endpoints
|
||||||
|
- API versioning structure
|
||||||
|
|
||||||
|
## CORS Configuration
|
||||||
|
|
||||||
|
If your API is consumed by a separate frontend:
|
||||||
|
|
||||||
|
1. Copy `src/config/cors.php.example` to `config/cors.php`
|
||||||
|
2. Update `allowed_origins` with your frontend URL
|
||||||
|
3. Set `FRONTEND_URL` in `.env`
|
||||||
|
|
||||||
|
```env
|
||||||
|
FRONTEND_URL=https://your-frontend.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### After Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start development
|
||||||
|
make up DB=mysql
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
make artisan cmd='migrate'
|
||||||
|
|
||||||
|
# Create a user (tinker)
|
||||||
|
make tinker
|
||||||
|
# User::factory()->create(['email' => 'test@example.com'])
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
make test
|
||||||
|
|
||||||
|
# Fix code style
|
||||||
|
make lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Tasks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create controller
|
||||||
|
make artisan cmd='make:controller Api/PostController --api'
|
||||||
|
|
||||||
|
# Create model with migration
|
||||||
|
make artisan cmd='make:model Post -m'
|
||||||
|
|
||||||
|
# Create form request
|
||||||
|
make artisan cmd='make:request StorePostRequest'
|
||||||
|
|
||||||
|
# Create resource
|
||||||
|
make artisan cmd='make:resource PostResource'
|
||||||
|
|
||||||
|
# Create policy
|
||||||
|
make artisan cmd='make:policy PostPolicy --model=Post'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing API
|
||||||
|
|
||||||
|
### With curl
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Register (if using Breeze API)
|
||||||
|
curl -X POST http://localhost:8080/api/register \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"Test","email":"test@test.com","password":"password","password_confirmation":"password"}'
|
||||||
|
|
||||||
|
# Login
|
||||||
|
curl -X POST http://localhost:8080/api/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"email":"test@test.com","password":"password"}'
|
||||||
|
|
||||||
|
# Use token
|
||||||
|
curl http://localhost:8080/api/user \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Postman/Insomnia
|
||||||
|
|
||||||
|
1. Import the API collection (create from routes)
|
||||||
|
2. Set base URL to `http://localhost:8080/api`
|
||||||
|
3. Add Authorization header with Bearer token
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### CORS Errors
|
||||||
|
|
||||||
|
1. Check `config/cors.php` includes your frontend origin
|
||||||
|
2. Ensure `supports_credentials` is `true` if using cookies
|
||||||
|
3. Clear config cache: `php artisan config:clear`
|
||||||
|
|
||||||
|
### 401 Unauthorized
|
||||||
|
|
||||||
|
1. Check token is valid and not expired
|
||||||
|
2. Ensure `auth:sanctum` middleware is applied
|
||||||
|
3. For SPA: ensure CSRF cookie is set
|
||||||
|
|
||||||
|
### Session Issues
|
||||||
|
|
||||||
|
1. Check `SESSION_DOMAIN` matches your domain
|
||||||
|
2. For subdomains, use `.yourdomain.com`
|
||||||
|
3. Ensure Redis is running for session storage
|
||||||
313
docs/modules.md
Normal file
313
docs/modules.md
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
# Modular Architecture
|
||||||
|
|
||||||
|
This template uses a modular architecture to organize features into self-contained modules. Each module has its own admin panel, routes, views, and permissions.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a new module
|
||||||
|
php artisan make:module StockManagement
|
||||||
|
|
||||||
|
# Create with a model
|
||||||
|
php artisan make:module StockManagement --model=Product
|
||||||
|
|
||||||
|
# Create with API routes
|
||||||
|
php artisan make:module StockManagement --api
|
||||||
|
|
||||||
|
# Skip Filament admin
|
||||||
|
php artisan make:module StockManagement --no-filament
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
app/Modules/StockManagement/
|
||||||
|
├── Config/
|
||||||
|
│ └── stock_management.php # Module configuration
|
||||||
|
├── Database/
|
||||||
|
│ ├── Migrations/ # Module-specific migrations
|
||||||
|
│ └── Seeders/
|
||||||
|
│ └── StockManagementPermissionSeeder.php
|
||||||
|
├── Filament/
|
||||||
|
│ └── Resources/ # Admin panel resources
|
||||||
|
│ ├── StockManagementDashboardResource.php
|
||||||
|
│ └── ProductResource.php # If --model used
|
||||||
|
├── Http/
|
||||||
|
│ ├── Controllers/
|
||||||
|
│ │ └── StockManagementController.php
|
||||||
|
│ ├── Middleware/
|
||||||
|
│ └── Requests/
|
||||||
|
├── Models/
|
||||||
|
│ └── Product.php # If --model used
|
||||||
|
├── Policies/
|
||||||
|
├── Services/ # Business logic
|
||||||
|
├── Routes/
|
||||||
|
│ ├── web.php # Frontend routes
|
||||||
|
│ └── api.php # API routes (if --api)
|
||||||
|
├── Resources/
|
||||||
|
│ ├── views/
|
||||||
|
│ │ ├── index.blade.php # Module landing page
|
||||||
|
│ │ ├── layouts/
|
||||||
|
│ │ └── filament/
|
||||||
|
│ ├── css/
|
||||||
|
│ │ └── stock-management.css # Module-specific styles
|
||||||
|
│ └── lang/en/
|
||||||
|
├── Permissions.php # Module permissions
|
||||||
|
└── StockManagementServiceProvider.php
|
||||||
|
```
|
||||||
|
|
||||||
|
## How Modules Work
|
||||||
|
|
||||||
|
### Auto-Loading
|
||||||
|
|
||||||
|
The `ModuleServiceProvider` automatically:
|
||||||
|
- Discovers all modules in `app/Modules/`
|
||||||
|
- Registers each module's service provider
|
||||||
|
- Loads routes, views, migrations, and translations
|
||||||
|
|
||||||
|
### Routes
|
||||||
|
|
||||||
|
Module routes are prefixed and named automatically:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Routes/web.php
|
||||||
|
Route::prefix('stock-management')
|
||||||
|
->name('stock-management.')
|
||||||
|
->middleware(['web', 'auth'])
|
||||||
|
->group(function () {
|
||||||
|
Route::get('/', [StockManagementController::class, 'index'])
|
||||||
|
->name('index');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Access: `http://localhost:8080/stock-management`
|
||||||
|
|
||||||
|
### Views
|
||||||
|
|
||||||
|
Module views use a namespace based on the module slug:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// In controller
|
||||||
|
return view('stock-management::index');
|
||||||
|
|
||||||
|
// In Blade
|
||||||
|
@include('stock-management::partials.header')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filament Admin
|
||||||
|
|
||||||
|
Each module gets its own navigation group in the admin panel:
|
||||||
|
|
||||||
|
```
|
||||||
|
📦 Stock Management
|
||||||
|
├── Dashboard
|
||||||
|
├── Products
|
||||||
|
└── Inventory
|
||||||
|
```
|
||||||
|
|
||||||
|
Resources are automatically discovered from `Filament/Resources/`.
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
### Defining Permissions
|
||||||
|
|
||||||
|
Each module has a `Permissions.php` file:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Modules/StockManagement/Permissions.php
|
||||||
|
return [
|
||||||
|
'stock_management.view' => 'View Stock Management',
|
||||||
|
'stock_management.create' => 'Create stock records',
|
||||||
|
'stock_management.edit' => 'Edit stock records',
|
||||||
|
'stock_management.delete' => 'Delete stock records',
|
||||||
|
'stock_management.export' => 'Export stock data',
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Seeding Permissions
|
||||||
|
|
||||||
|
After creating a module, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan db:seed --class=PermissionSeeder
|
||||||
|
```
|
||||||
|
|
||||||
|
This registers all module permissions and assigns them to the admin role.
|
||||||
|
|
||||||
|
### Using Permissions
|
||||||
|
|
||||||
|
In Blade:
|
||||||
|
```blade
|
||||||
|
@can('stock_management.view')
|
||||||
|
<a href="{{ route('stock-management.index') }}">Stock Management</a>
|
||||||
|
@endcan
|
||||||
|
```
|
||||||
|
|
||||||
|
In Controllers:
|
||||||
|
```php
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->authorize('stock_management.view');
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In Filament Resources:
|
||||||
|
```php
|
||||||
|
public static function canAccess(): bool
|
||||||
|
{
|
||||||
|
return auth()->user()?->can('stock_management.view') ?? false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Assets
|
||||||
|
|
||||||
|
### App-Wide CSS/JS
|
||||||
|
|
||||||
|
Use the main `resources/css/app.css` and `resources/js/app.js` for shared styles.
|
||||||
|
|
||||||
|
### Module-Specific Styles
|
||||||
|
|
||||||
|
Each module has its own CSS file at `Resources/css/{module-slug}.css`.
|
||||||
|
|
||||||
|
Include in Blade:
|
||||||
|
```blade
|
||||||
|
@push('module-styles')
|
||||||
|
<link href="{{ asset('modules/stock-management/css/stock-management.css') }}" rel="stylesheet">
|
||||||
|
@endpush
|
||||||
|
```
|
||||||
|
|
||||||
|
Or inline in the view:
|
||||||
|
```blade
|
||||||
|
@push('module-styles')
|
||||||
|
<style>
|
||||||
|
.stock-table { /* ... */ }
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creating Models
|
||||||
|
|
||||||
|
### With Module Command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan make:module StockManagement --model=Product
|
||||||
|
```
|
||||||
|
|
||||||
|
Creates:
|
||||||
|
- `Models/Product.php`
|
||||||
|
- Migration in `Database/Migrations/`
|
||||||
|
- Filament resource with CRUD pages
|
||||||
|
|
||||||
|
### Adding Models Later
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From project root
|
||||||
|
php artisan make:model Modules/StockManagement/Models/Inventory -m
|
||||||
|
```
|
||||||
|
|
||||||
|
Then create the Filament resource:
|
||||||
|
```bash
|
||||||
|
php artisan make:filament-resource Inventory \
|
||||||
|
--model=App\\Modules\\StockManagement\\Models\\Inventory
|
||||||
|
```
|
||||||
|
|
||||||
|
Move the resource to your module's `Filament/Resources/` directory.
|
||||||
|
|
||||||
|
## Example: Stock Management Module
|
||||||
|
|
||||||
|
### 1. Create the Module
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan make:module StockManagement --model=Product --api
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Edit the Migration
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Database/Migrations/xxxx_create_products_table.php
|
||||||
|
Schema::create('products', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('sku')->unique();
|
||||||
|
$table->text('description')->nullable();
|
||||||
|
$table->decimal('price', 10, 2);
|
||||||
|
$table->integer('quantity')->default(0);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Update the Model
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Models/Product.php
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'sku',
|
||||||
|
'description',
|
||||||
|
'price',
|
||||||
|
'quantity',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'price' => 'decimal:2',
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Update Filament Resource
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Filament/Resources/ProductResource.php
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form->schema([
|
||||||
|
Forms\Components\TextInput::make('name')->required(),
|
||||||
|
Forms\Components\TextInput::make('sku')->required()->unique(ignoreRecord: true),
|
||||||
|
Forms\Components\Textarea::make('description'),
|
||||||
|
Forms\Components\TextInput::make('price')->numeric()->prefix('$'),
|
||||||
|
Forms\Components\TextInput::make('quantity')->numeric()->default(0),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Run Migrations & Seed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan migrate
|
||||||
|
php artisan db:seed --class=PermissionSeeder
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Access
|
||||||
|
|
||||||
|
- Frontend: `http://localhost:8080/stock-management`
|
||||||
|
- Admin: `http://localhost:8080/admin` → Stock Management section
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Keep modules independent** - Avoid tight coupling between modules
|
||||||
|
2. **Use services** - Put business logic in `Services/` not controllers
|
||||||
|
3. **Define clear permissions** - One permission per action
|
||||||
|
4. **Use policies** - For complex authorization rules
|
||||||
|
5. **Module-specific migrations** - Keep data schema with the module
|
||||||
|
6. **Test modules** - Create tests in `tests/Modules/ModuleName/`
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Module not loading
|
||||||
|
1. Check service provider exists and is named correctly
|
||||||
|
2. Clear cache: `php artisan config:clear && php artisan cache:clear`
|
||||||
|
3. Check `ModuleServiceProvider` is in `bootstrap/providers.php`
|
||||||
|
|
||||||
|
### Views not found
|
||||||
|
1. Verify view namespace matches module slug (kebab-case)
|
||||||
|
2. Check views are in `Resources/views/`
|
||||||
|
|
||||||
|
### Permissions not working
|
||||||
|
1. Run `php artisan db:seed --class=PermissionSeeder`
|
||||||
|
2. Clear permission cache: `php artisan permission:cache-reset`
|
||||||
|
3. Verify user has role with permissions
|
||||||
|
|
||||||
|
### Filament resources not showing
|
||||||
|
1. Check resource is in `Filament/Resources/`
|
||||||
|
2. Verify `canAccess()` returns true for your user
|
||||||
|
3. Clear Filament cache: `php artisan filament:cache-components`
|
||||||
275
docs/queues.md
Normal file
275
docs/queues.md
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
# Queues & Background Jobs
|
||||||
|
|
||||||
|
This template includes Redis-powered queues for background processing.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start queue worker
|
||||||
|
make queue-start
|
||||||
|
|
||||||
|
# View queue logs
|
||||||
|
make queue-logs
|
||||||
|
|
||||||
|
# Stop queue worker
|
||||||
|
make queue-stop
|
||||||
|
|
||||||
|
# Restart after code changes
|
||||||
|
make queue-restart
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Queue is configured to use Redis in `.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PORT=6379
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creating Jobs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan make:job ProcessOrder
|
||||||
|
```
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Order;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ProcessOrder implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public Order $order
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
// Process the order
|
||||||
|
$this->order->update(['status' => 'processing']);
|
||||||
|
|
||||||
|
// Send notification, generate invoice, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function failed(\Throwable $exception): void
|
||||||
|
{
|
||||||
|
// Handle failure - log, notify admin, etc.
|
||||||
|
logger()->error('Order processing failed', [
|
||||||
|
'order_id' => $this->order->id,
|
||||||
|
'error' => $exception->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dispatching Jobs
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Dispatch immediately to queue
|
||||||
|
ProcessOrder::dispatch($order);
|
||||||
|
|
||||||
|
// Dispatch with delay
|
||||||
|
ProcessOrder::dispatch($order)->delay(now()->addMinutes(5));
|
||||||
|
|
||||||
|
// Dispatch to specific queue
|
||||||
|
ProcessOrder::dispatch($order)->onQueue('orders');
|
||||||
|
|
||||||
|
// Dispatch after response sent
|
||||||
|
ProcessOrder::dispatchAfterResponse($order);
|
||||||
|
|
||||||
|
// Chain jobs
|
||||||
|
Bus::chain([
|
||||||
|
new ProcessOrder($order),
|
||||||
|
new SendOrderConfirmation($order),
|
||||||
|
new NotifyWarehouse($order),
|
||||||
|
])->dispatch();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Job Queues
|
||||||
|
|
||||||
|
Use different queues for different priorities:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// High priority
|
||||||
|
ProcessPayment::dispatch($payment)->onQueue('high');
|
||||||
|
|
||||||
|
// Default
|
||||||
|
SendEmail::dispatch($email)->onQueue('default');
|
||||||
|
|
||||||
|
// Low priority
|
||||||
|
GenerateReport::dispatch($report)->onQueue('low');
|
||||||
|
```
|
||||||
|
|
||||||
|
Run workers for specific queues:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Process high priority first
|
||||||
|
php artisan queue:work --queue=high,default,low
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scheduled Jobs
|
||||||
|
|
||||||
|
Add to `app/Console/Kernel.php` or `routes/console.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// routes/console.php (Laravel 11+)
|
||||||
|
use Illuminate\Support\Facades\Schedule;
|
||||||
|
|
||||||
|
Schedule::job(new CleanupOldRecords)->daily();
|
||||||
|
Schedule::job(new SendDailyReport)->dailyAt('08:00');
|
||||||
|
Schedule::job(new ProcessPendingOrders)->everyFiveMinutes();
|
||||||
|
|
||||||
|
// With queue
|
||||||
|
Schedule::job(new GenerateBackup)->daily()->onQueue('backups');
|
||||||
|
```
|
||||||
|
|
||||||
|
Start scheduler:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make scheduler-start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Jobs
|
||||||
|
|
||||||
|
When creating module jobs, place them in the module's directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
app/Modules/Inventory/
|
||||||
|
├── Jobs/
|
||||||
|
│ ├── SyncStock.php
|
||||||
|
│ └── GenerateInventoryReport.php
|
||||||
|
```
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Modules\Inventory\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class SyncStock implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $tries = 3;
|
||||||
|
public $backoff = [60, 300, 900]; // Retry after 1min, 5min, 15min
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
// Sync stock levels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Job Middleware
|
||||||
|
|
||||||
|
Rate limit jobs:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Illuminate\Queue\Middleware\RateLimited;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
|
||||||
|
class ProcessOrder implements ShouldQueue
|
||||||
|
{
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new RateLimited('orders'),
|
||||||
|
new WithoutOverlapping($this->order->id),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### View Failed Jobs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan queue:failed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Retry Failed Jobs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Retry specific job
|
||||||
|
php artisan queue:retry <job-id>
|
||||||
|
|
||||||
|
# Retry all failed jobs
|
||||||
|
php artisan queue:retry all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clear Failed Jobs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan queue:flush
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Setup
|
||||||
|
|
||||||
|
For production, use Supervisor instead of Docker:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# /etc/supervisor/conf.d/laravel-worker.conf
|
||||||
|
[program:laravel-worker]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
user=www-data
|
||||||
|
numprocs=2
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/var/www/html/storage/logs/worker.log
|
||||||
|
stopwaitsecs=3600
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo supervisorctl reread
|
||||||
|
sudo supervisorctl update
|
||||||
|
sudo supervisorctl start laravel-worker:*
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Jobs
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
|
||||||
|
it('dispatches order processing job', function () {
|
||||||
|
Queue::fake();
|
||||||
|
|
||||||
|
$order = Order::factory()->create();
|
||||||
|
|
||||||
|
// Trigger action that dispatches job
|
||||||
|
$order->markAsPaid();
|
||||||
|
|
||||||
|
Queue::assertPushed(ProcessOrder::class, function ($job) use ($order) {
|
||||||
|
return $job->order->id === $order->id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('processes order correctly', function () {
|
||||||
|
$order = Order::factory()->create(['status' => 'pending']);
|
||||||
|
|
||||||
|
// Run job synchronously
|
||||||
|
(new ProcessOrder($order))->handle();
|
||||||
|
|
||||||
|
expect($order->fresh()->status)->toBe('processing');
|
||||||
|
});
|
||||||
|
```
|
||||||
286
docs/site-settings.md
Normal file
286
docs/site-settings.md
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
# Site Settings
|
||||||
|
|
||||||
|
Manage your site's appearance (logo, favicon, colors) from the admin panel.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Site settings are stored in the database using [spatie/laravel-settings](https://github.com/spatie/laravel-settings) and managed via Filament.
|
||||||
|
|
||||||
|
**Admin Location:** `/admin` → Settings → Appearance
|
||||||
|
|
||||||
|
## Available Settings
|
||||||
|
|
||||||
|
| Setting | Type | Description |
|
||||||
|
|---------|------|-------------|
|
||||||
|
| `site_name` | String | Site name (used in title, emails) |
|
||||||
|
| `logo` | Image | Header logo |
|
||||||
|
| `favicon` | Image | Browser favicon (32x32) |
|
||||||
|
| `primary_color` | Color | Primary brand color |
|
||||||
|
| `secondary_color` | Color | Secondary/accent color |
|
||||||
|
| `dark_mode` | Boolean | Enable dark mode toggle |
|
||||||
|
| `footer_text` | Text | Footer copyright text |
|
||||||
|
|
||||||
|
## Usage in Blade Templates
|
||||||
|
|
||||||
|
### Helper Functions
|
||||||
|
|
||||||
|
```blade
|
||||||
|
{{-- Site name --}}
|
||||||
|
<h1>{{ site_name() }}</h1>
|
||||||
|
|
||||||
|
{{-- Logo with fallback --}}
|
||||||
|
@if(site_logo())
|
||||||
|
<img src="{{ site_logo() }}" alt="{{ site_name() }}">
|
||||||
|
@else
|
||||||
|
<span>{{ site_name() }}</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Favicon --}}
|
||||||
|
<link rel="icon" href="{{ site_favicon() }}">
|
||||||
|
|
||||||
|
{{-- Colors --}}
|
||||||
|
<div style="background: {{ primary_color() }}">Primary</div>
|
||||||
|
<div style="background: {{ secondary_color() }}">Secondary</div>
|
||||||
|
|
||||||
|
{{-- Get any setting --}}
|
||||||
|
{{ site_settings('footer_text') }}
|
||||||
|
{{ site_settings('dark_mode') ? 'Dark' : 'Light' }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Site Head Component
|
||||||
|
|
||||||
|
Include in your layout's `<head>`:
|
||||||
|
|
||||||
|
```blade
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
{{-- This adds title, favicon, and CSS variables --}}
|
||||||
|
<x-site-head :title="$title ?? null" />
|
||||||
|
|
||||||
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
|
</head>
|
||||||
|
```
|
||||||
|
|
||||||
|
The component automatically:
|
||||||
|
- Sets the page title with site name
|
||||||
|
- Adds favicon link
|
||||||
|
- Creates CSS custom properties for colors
|
||||||
|
|
||||||
|
### CSS Custom Properties
|
||||||
|
|
||||||
|
After including `<x-site-head />`, these CSS variables are available:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--primary-color: #3b82f6;
|
||||||
|
--secondary-color: #64748b;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use in your CSS:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.button {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-accent {
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with Tailwind arbitrary values:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="bg-[var(--primary-color)] text-white">
|
||||||
|
Click Me
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Utility Classes
|
||||||
|
|
||||||
|
The component also creates utility classes:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="bg-primary text-white">Primary background</div>
|
||||||
|
<div class="text-primary">Primary text</div>
|
||||||
|
<div class="border-primary">Primary border</div>
|
||||||
|
|
||||||
|
<div class="bg-secondary">Secondary background</div>
|
||||||
|
<button class="btn-primary">Styled Button</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage in PHP
|
||||||
|
|
||||||
|
```php
|
||||||
|
use App\Settings\SiteSettings;
|
||||||
|
|
||||||
|
// Via dependency injection
|
||||||
|
public function __construct(private SiteSettings $settings)
|
||||||
|
{
|
||||||
|
$name = $this->settings->site_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Via helper
|
||||||
|
$name = site_settings('site_name');
|
||||||
|
$logo = site_logo();
|
||||||
|
|
||||||
|
// Via app container
|
||||||
|
$settings = app(SiteSettings::class);
|
||||||
|
$color = $settings->primary_color;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customizing the Settings Page
|
||||||
|
|
||||||
|
Edit `app/Filament/Pages/ManageSiteSettings.php`:
|
||||||
|
|
||||||
|
### Add New Settings
|
||||||
|
|
||||||
|
1. Add property to `app/Settings/SiteSettings.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
class SiteSettings extends Settings
|
||||||
|
{
|
||||||
|
// ... existing properties
|
||||||
|
public ?string $contact_email;
|
||||||
|
public ?string $phone_number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create migration in `database/settings/`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
return new class extends SettingsMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->migrator->add('site.contact_email', null);
|
||||||
|
$this->migrator->add('site.phone_number', null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Add to form in `ManageSiteSettings.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
Forms\Components\Section::make('Contact')
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('contact_email')
|
||||||
|
->email(),
|
||||||
|
Forms\Components\TextInput::make('phone_number')
|
||||||
|
->tel(),
|
||||||
|
]),
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run migration: `php artisan migrate`
|
||||||
|
|
||||||
|
### Add Social Links
|
||||||
|
|
||||||
|
```php
|
||||||
|
// In SiteSettings.php
|
||||||
|
public ?array $social_links;
|
||||||
|
|
||||||
|
// In migration
|
||||||
|
$this->migrator->add('site.social_links', []);
|
||||||
|
|
||||||
|
// In form
|
||||||
|
Forms\Components\Repeater::make('social_links')
|
||||||
|
->schema([
|
||||||
|
Forms\Components\Select::make('platform')
|
||||||
|
->options([
|
||||||
|
'facebook' => 'Facebook',
|
||||||
|
'twitter' => 'Twitter/X',
|
||||||
|
'instagram' => 'Instagram',
|
||||||
|
'linkedin' => 'LinkedIn',
|
||||||
|
]),
|
||||||
|
Forms\Components\TextInput::make('url')
|
||||||
|
->url(),
|
||||||
|
])
|
||||||
|
->columns(2),
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caching
|
||||||
|
|
||||||
|
Settings are cached automatically. Clear cache after direct database changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan cache:clear
|
||||||
|
```
|
||||||
|
|
||||||
|
Or in code:
|
||||||
|
|
||||||
|
```php
|
||||||
|
app(SiteSettings::class)->refresh();
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Storage
|
||||||
|
|
||||||
|
Logo and favicon are stored in `storage/app/public/site/`.
|
||||||
|
|
||||||
|
Make sure the storage link exists:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan storage:link
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: Complete Layout
|
||||||
|
|
||||||
|
```blade
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|
||||||
|
<x-site-head :title="$title ?? null" />
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||||
|
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||||
|
|
||||||
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
|
</head>
|
||||||
|
<body class="font-sans antialiased">
|
||||||
|
<div class="min-h-screen bg-gray-100">
|
||||||
|
{{-- Header with logo --}}
|
||||||
|
<header class="bg-white shadow">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 py-4">
|
||||||
|
@if(site_logo())
|
||||||
|
<img src="{{ site_logo() }}" alt="{{ site_name() }}" class="h-10">
|
||||||
|
@else
|
||||||
|
<span class="text-xl font-bold text-primary">{{ site_name() }}</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{{-- Content --}}
|
||||||
|
<main>
|
||||||
|
{{ $slot }}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{{-- Footer --}}
|
||||||
|
<footer class="bg-gray-800 text-white py-8">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 text-center">
|
||||||
|
{!! site_settings('footer_text') ?? '© ' . date('Y') . ' ' . site_name() !!}
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Settings not updating
|
||||||
|
1. Clear cache: `php artisan cache:clear`
|
||||||
|
2. Check file permissions on storage directory
|
||||||
|
|
||||||
|
### Logo not showing
|
||||||
|
1. Run `php artisan storage:link`
|
||||||
|
2. Check logo exists in `storage/app/public/site/`
|
||||||
|
|
||||||
|
### Colors not applying
|
||||||
|
1. Make sure `<x-site-head />` is in your layout's `<head>`
|
||||||
|
2. Check browser dev tools for CSS variable values
|
||||||
386
docs/testing.md
Normal file
386
docs/testing.md
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
# Testing with Pest
|
||||||
|
|
||||||
|
This template uses [Pest](https://pestphp.com/) for testing - a modern PHP testing framework with elegant syntax.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
make test
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
make test-coverage
|
||||||
|
|
||||||
|
# Run specific module tests
|
||||||
|
make test-module module=Inventory
|
||||||
|
|
||||||
|
# Run filtered tests
|
||||||
|
make test-filter filter="can create"
|
||||||
|
|
||||||
|
# Run in parallel (faster)
|
||||||
|
make test-parallel
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── Feature/ # HTTP/integration tests
|
||||||
|
│ └── ExampleTest.php
|
||||||
|
├── Unit/ # Isolated unit tests
|
||||||
|
│ └── ExampleTest.php
|
||||||
|
├── Modules/ # Module-specific tests
|
||||||
|
│ └── Inventory/
|
||||||
|
│ ├── InventoryTest.php
|
||||||
|
│ └── ProductTest.php
|
||||||
|
├── Pest.php # Global configuration
|
||||||
|
├── TestCase.php # Base test class
|
||||||
|
└── TestHelpers.php # Custom helpers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing Tests
|
||||||
|
|
||||||
|
### Basic Syntax
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Simple test
|
||||||
|
it('has a welcome page', function () {
|
||||||
|
$this->get('/')->assertStatus(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
// With description
|
||||||
|
test('users can login', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->post('/login', [
|
||||||
|
'email' => $user->email,
|
||||||
|
'password' => 'password',
|
||||||
|
])->assertRedirect('/dashboard');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Expectations
|
||||||
|
|
||||||
|
```php
|
||||||
|
it('can create a product', function () {
|
||||||
|
$product = Product::create(['name' => 'Widget', 'price' => 99.99]);
|
||||||
|
|
||||||
|
expect($product)
|
||||||
|
->toBeInstanceOf(Product::class)
|
||||||
|
->name->toBe('Widget')
|
||||||
|
->price->toBe(99.99);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grouped Tests
|
||||||
|
|
||||||
|
```php
|
||||||
|
describe('Product Model', function () {
|
||||||
|
|
||||||
|
it('can be created', function () {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a price', function () {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
it('belongs to a category', function () {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup/Teardown
|
||||||
|
|
||||||
|
```php
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->user = User::factory()->create();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
// Cleanup if needed
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a user', function () {
|
||||||
|
expect($this->user)->toBeInstanceOf(User::class);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Global Helpers
|
||||||
|
|
||||||
|
Defined in `tests/Pest.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Create a regular user
|
||||||
|
$user = createUser();
|
||||||
|
$user = createUser(['name' => 'John']);
|
||||||
|
|
||||||
|
// Create an admin user
|
||||||
|
$admin = createAdmin();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Testing
|
||||||
|
|
||||||
|
When you create a module with `php artisan make:module`, tests are auto-generated:
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/Modules/Inventory/
|
||||||
|
├── InventoryTest.php # Route and permission tests
|
||||||
|
└── ProductTest.php # Model tests (if --model used)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Module Test
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Modules\Inventory\Models\Product;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->user = User::factory()->create();
|
||||||
|
$this->user->givePermissionTo([
|
||||||
|
'inventory.view',
|
||||||
|
'inventory.create',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Inventory Module', function () {
|
||||||
|
|
||||||
|
it('allows authenticated users to view page', function () {
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->get('/inventory')
|
||||||
|
->assertStatus(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('redirects guests to login', function () {
|
||||||
|
$this->get('/inventory')
|
||||||
|
->assertRedirect('/login');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Product Model', function () {
|
||||||
|
|
||||||
|
it('can be created', function () {
|
||||||
|
$product = Product::create([
|
||||||
|
'name' => 'Test Product',
|
||||||
|
'price' => 29.99,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($product->name)->toBe('Test Product');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is audited on create', function () {
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
$product = Product::create(['name' => 'Audited']);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('audits', [
|
||||||
|
'auditable_type' => Product::class,
|
||||||
|
'auditable_id' => $product->id,
|
||||||
|
'event' => 'created',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Helpers
|
||||||
|
|
||||||
|
Defined in `tests/TestHelpers.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Tests\TestHelpers;
|
||||||
|
|
||||||
|
uses(TestHelpers::class)->in('Modules');
|
||||||
|
|
||||||
|
// In your test
|
||||||
|
it('allows users with permission', function () {
|
||||||
|
$user = $this->userWithPermissions(['inventory.view']);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get('/inventory')
|
||||||
|
->assertStatus(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows module access', function () {
|
||||||
|
$user = $this->userWithModuleAccess('inventory');
|
||||||
|
// User has view, create, edit, delete permissions
|
||||||
|
});
|
||||||
|
|
||||||
|
it('audits changes', function () {
|
||||||
|
$product = Product::create(['name' => 'Test']);
|
||||||
|
|
||||||
|
$this->assertAudited($product, 'created');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Livewire Components
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Livewire\Livewire;
|
||||||
|
use App\Livewire\CreateProduct;
|
||||||
|
|
||||||
|
it('can create product via Livewire', function () {
|
||||||
|
$this->actingAs(createUser());
|
||||||
|
|
||||||
|
Livewire::test(CreateProduct::class)
|
||||||
|
->set('name', 'New Product')
|
||||||
|
->set('price', 49.99)
|
||||||
|
->call('save')
|
||||||
|
->assertHasNoErrors()
|
||||||
|
->assertRedirect('/products');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('products', [
|
||||||
|
'name' => 'New Product',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates required fields', function () {
|
||||||
|
Livewire::test(CreateProduct::class)
|
||||||
|
->set('name', '')
|
||||||
|
->call('save')
|
||||||
|
->assertHasErrors(['name' => 'required']);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Filament Resources
|
||||||
|
|
||||||
|
```php
|
||||||
|
use App\Modules\Inventory\Models\Product;
|
||||||
|
use function Pest\Livewire\livewire;
|
||||||
|
|
||||||
|
it('can list products in admin', function () {
|
||||||
|
$user = createAdmin();
|
||||||
|
$products = Product::factory()->count(5)->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get('/admin/inventory/products')
|
||||||
|
->assertSuccessful()
|
||||||
|
->assertSeeText($products->first()->name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create product from admin', function () {
|
||||||
|
$user = createAdmin();
|
||||||
|
|
||||||
|
livewire(\App\Modules\Inventory\Filament\Resources\ProductResource\Pages\CreateProduct::class)
|
||||||
|
->fillForm([
|
||||||
|
'name' => 'Admin Product',
|
||||||
|
'price' => 100,
|
||||||
|
])
|
||||||
|
->call('create')
|
||||||
|
->assertHasNoFormErrors();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('products', [
|
||||||
|
'name' => 'Admin Product',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Testing
|
||||||
|
|
||||||
|
Tests use SQLite in-memory for speed. The `LazilyRefreshDatabase` trait only runs migrations when needed.
|
||||||
|
|
||||||
|
### Factories
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Create model
|
||||||
|
$product = Product::factory()->create();
|
||||||
|
|
||||||
|
// Create multiple
|
||||||
|
$products = Product::factory()->count(5)->create();
|
||||||
|
|
||||||
|
// With attributes
|
||||||
|
$product = Product::factory()->create([
|
||||||
|
'name' => 'Special Product',
|
||||||
|
'price' => 999.99,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// With state
|
||||||
|
$product = Product::factory()->active()->create();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Assertions
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Database has record
|
||||||
|
$this->assertDatabaseHas('products', [
|
||||||
|
'name' => 'Widget',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Database missing record
|
||||||
|
$this->assertDatabaseMissing('products', [
|
||||||
|
'name' => 'Deleted Product',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Count records
|
||||||
|
$this->assertDatabaseCount('products', 5);
|
||||||
|
```
|
||||||
|
|
||||||
|
## HTTP Testing
|
||||||
|
|
||||||
|
```php
|
||||||
|
// GET request
|
||||||
|
$this->get('/products')->assertStatus(200);
|
||||||
|
|
||||||
|
// POST with data
|
||||||
|
$this->post('/products', [
|
||||||
|
'name' => 'New Product',
|
||||||
|
])->assertRedirect('/products');
|
||||||
|
|
||||||
|
// JSON API
|
||||||
|
$this->getJson('/api/products')
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertJsonCount(5, 'data');
|
||||||
|
|
||||||
|
// With auth
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get('/admin')
|
||||||
|
->assertStatus(200);
|
||||||
|
|
||||||
|
// Assert view
|
||||||
|
$this->get('/products')
|
||||||
|
->assertViewIs('products.index')
|
||||||
|
->assertViewHas('products');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Specific Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Single file
|
||||||
|
php artisan test tests/Feature/ExampleTest.php
|
||||||
|
|
||||||
|
# Single test by name
|
||||||
|
php artisan test --filter="can create product"
|
||||||
|
|
||||||
|
# Module tests only
|
||||||
|
php artisan test tests/Modules/Inventory
|
||||||
|
|
||||||
|
# With verbose output
|
||||||
|
php artisan test --verbose
|
||||||
|
|
||||||
|
# Stop on first failure
|
||||||
|
php artisan test --stop-on-failure
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coverage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate coverage report
|
||||||
|
make test-coverage
|
||||||
|
|
||||||
|
# Requires Xdebug or PCOV
|
||||||
|
# Coverage report saved to coverage/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
1. **Use factories** - Don't manually create models in tests
|
||||||
|
2. **Test behavior, not implementation** - Focus on what, not how
|
||||||
|
3. **One assertion per test** - Keep tests focused
|
||||||
|
4. **Use descriptive names** - `it('shows error when email is invalid')`
|
||||||
|
5. **Test edge cases** - Empty values, boundaries, errors
|
||||||
|
6. **Run tests often** - Before commits, after changes
|
||||||
85
scripts/backup.sh
Normal file
85
scripts/backup.sh
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Database Backup Script
|
||||||
|
# Usage: ./scripts/backup.sh
|
||||||
|
# Creates timestamped backup in backups/ directory
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
BACKUP_DIR="backups"
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
# Create backup directory
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# Detect database type from .env
|
||||||
|
if [ -f "src/.env" ]; then
|
||||||
|
DB_CONNECTION=$(grep "^DB_CONNECTION=" src/.env | cut -d '=' -f2)
|
||||||
|
DB_DATABASE=$(grep "^DB_DATABASE=" src/.env | cut -d '=' -f2)
|
||||||
|
DB_USERNAME=$(grep "^DB_USERNAME=" src/.env | cut -d '=' -f2)
|
||||||
|
DB_PASSWORD=$(grep "^DB_PASSWORD=" src/.env | cut -d '=' -f2)
|
||||||
|
else
|
||||||
|
echo "Error: src/.env not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Database Backup"
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Connection: $DB_CONNECTION"
|
||||||
|
echo "Database: $DB_DATABASE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
case "$DB_CONNECTION" in
|
||||||
|
mysql)
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/${DB_DATABASE}_${TIMESTAMP}.sql"
|
||||||
|
echo "Creating MySQL backup..."
|
||||||
|
docker-compose exec -T mysql mysqldump \
|
||||||
|
-u"$DB_USERNAME" \
|
||||||
|
-p"$DB_PASSWORD" \
|
||||||
|
"$DB_DATABASE" > "$BACKUP_FILE"
|
||||||
|
;;
|
||||||
|
pgsql)
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/${DB_DATABASE}_${TIMESTAMP}.sql"
|
||||||
|
echo "Creating PostgreSQL backup..."
|
||||||
|
docker-compose exec -T pgsql pg_dump \
|
||||||
|
-U "$DB_USERNAME" \
|
||||||
|
"$DB_DATABASE" > "$BACKUP_FILE"
|
||||||
|
;;
|
||||||
|
sqlite)
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/${DB_DATABASE}_${TIMESTAMP}.sqlite"
|
||||||
|
echo "Creating SQLite backup..."
|
||||||
|
cp "src/database/database.sqlite" "$BACKUP_FILE"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Error: Unknown database connection: $DB_CONNECTION"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Compress backup
|
||||||
|
if [ -f "$BACKUP_FILE" ]; then
|
||||||
|
gzip "$BACKUP_FILE"
|
||||||
|
BACKUP_FILE="${BACKUP_FILE}.gz"
|
||||||
|
|
||||||
|
# Get file size
|
||||||
|
SIZE=$(ls -lh "$BACKUP_FILE" | awk '{print $5}')
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✓ Backup created successfully!"
|
||||||
|
echo " File: $BACKUP_FILE"
|
||||||
|
echo " Size: $SIZE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Cleanup old backups (keep last 10)
|
||||||
|
echo "Cleaning up old backups (keeping last 10)..."
|
||||||
|
ls -t "$BACKUP_DIR"/*.gz 2>/dev/null | tail -n +11 | xargs -r rm --
|
||||||
|
|
||||||
|
# List recent backups
|
||||||
|
echo ""
|
||||||
|
echo "Recent backups:"
|
||||||
|
ls -lht "$BACKUP_DIR"/*.gz 2>/dev/null | head -5
|
||||||
|
else
|
||||||
|
echo "Error: Backup file not created"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
1006
scripts/laravel-setup.sh
Normal file
1006
scripts/laravel-setup.sh
Normal file
File diff suppressed because it is too large
Load Diff
229
scripts/post-install.sh
Normal file
229
scripts/post-install.sh
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Post-install script for Laravel project
|
||||||
|
# Run this after 'composer create-project laravel/laravel'
|
||||||
|
# Usage: ./scripts/post-install.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Laravel Post-Install Setup"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# Check if we're in a Laravel project
|
||||||
|
if [ ! -f "artisan" ]; then
|
||||||
|
echo "Error: Not in a Laravel project directory"
|
||||||
|
echo "Run this from your Laravel project root (src/)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install Ignition (already included in Laravel, but ensure latest)
|
||||||
|
echo ""
|
||||||
|
echo "[1/5] Ensuring Ignition is installed..."
|
||||||
|
composer require spatie/laravel-ignition --dev
|
||||||
|
|
||||||
|
# Install Flare for production error tracking
|
||||||
|
echo ""
|
||||||
|
echo "[2/5] Installing Flare for production error tracking..."
|
||||||
|
composer require spatie/laravel-flare
|
||||||
|
|
||||||
|
# Install Telescope for development debugging (optional)
|
||||||
|
echo ""
|
||||||
|
read -p "Install Laravel Telescope for development debugging? [y/N]: " INSTALL_TELESCOPE
|
||||||
|
if [[ "$INSTALL_TELESCOPE" =~ ^[Yy]$ ]]; then
|
||||||
|
composer require laravel/telescope --dev
|
||||||
|
php artisan telescope:install
|
||||||
|
php artisan migrate
|
||||||
|
echo "Telescope installed. Access at: /telescope"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Publish Flare config
|
||||||
|
echo ""
|
||||||
|
echo "[3/5] Publishing Flare configuration..."
|
||||||
|
php artisan vendor:publish --tag=flare-config
|
||||||
|
|
||||||
|
# Setup Laravel Pint (code style)
|
||||||
|
echo ""
|
||||||
|
echo "[4/5] Setting up Laravel Pint..."
|
||||||
|
if [ -f "../src/pint.json" ]; then
|
||||||
|
cp ../pint.json ./pint.json 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
echo "Pint configured. Run: ./vendor/bin/pint"
|
||||||
|
|
||||||
|
# Create custom error pages
|
||||||
|
echo ""
|
||||||
|
echo "[5/5] Creating custom error pages..."
|
||||||
|
|
||||||
|
mkdir -p resources/views/errors
|
||||||
|
|
||||||
|
# 500 Error Page
|
||||||
|
cat > resources/views/errors/500.blade.php << 'EOF'
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Server Error</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 48px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
h1 { color: #1f2937; font-size: 72px; margin: 0; }
|
||||||
|
h2 { color: #4b5563; font-weight: 500; margin: 16px 0; }
|
||||||
|
p { color: #6b7280; line-height: 1.6; }
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
a:hover { background: #5a67d8; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>500</h1>
|
||||||
|
<h2>Something went wrong</h2>
|
||||||
|
<p>We're sorry, but something unexpected happened. Our team has been notified and is working on it.</p>
|
||||||
|
<a href="/">Go Home</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 404 Error Page
|
||||||
|
cat > resources/views/errors/404.blade.php << 'EOF'
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Page Not Found</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 48px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
h1 { color: #1f2937; font-size: 72px; margin: 0; }
|
||||||
|
h2 { color: #4b5563; font-weight: 500; margin: 16px 0; }
|
||||||
|
p { color: #6b7280; line-height: 1.6; }
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
a:hover { background: #5a67d8; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>404</h1>
|
||||||
|
<h2>Page not found</h2>
|
||||||
|
<p>The page you're looking for doesn't exist or has been moved.</p>
|
||||||
|
<a href="/">Go Home</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 503 Maintenance Page
|
||||||
|
cat > resources/views/errors/503.blade.php << 'EOF'
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Maintenance Mode</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 48px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
h1 { color: #1f2937; font-size: 48px; margin: 0; }
|
||||||
|
h2 { color: #4b5563; font-weight: 500; margin: 16px 0; }
|
||||||
|
p { color: #6b7280; line-height: 1.6; }
|
||||||
|
.icon { font-size: 64px; margin-bottom: 16px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="icon">🔧</div>
|
||||||
|
<h1>Be Right Back</h1>
|
||||||
|
<h2>We're doing some maintenance</h2>
|
||||||
|
<p>We're making some improvements and will be back shortly. Thanks for your patience!</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Post-install setup complete!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Get your Flare key at: https://flareapp.io"
|
||||||
|
echo " 2. Add FLARE_KEY to your .env file"
|
||||||
|
echo " 3. Customize error pages in resources/views/errors/"
|
||||||
|
echo ""
|
||||||
|
echo "Ignition features (development):"
|
||||||
|
echo " - AI error explanations"
|
||||||
|
echo " - Click-to-open in VS Code"
|
||||||
|
echo " - Runnable solution suggestions"
|
||||||
|
echo ""
|
||||||
|
if [[ "$INSTALL_TELESCOPE" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Telescope dashboard: http://localhost:8080/telescope"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
101
scripts/restore.sh
Normal file
101
scripts/restore.sh
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Database Restore Script
|
||||||
|
# Usage: ./scripts/restore.sh <backup_file>
|
||||||
|
# Example: ./scripts/restore.sh backups/laravel_20240306_120000.sql.gz
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: ./scripts/restore.sh <backup_file>"
|
||||||
|
echo ""
|
||||||
|
echo "Available backups:"
|
||||||
|
ls -lht backups/*.gz 2>/dev/null || echo " No backups found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_FILE="$1"
|
||||||
|
|
||||||
|
if [ ! -f "$BACKUP_FILE" ]; then
|
||||||
|
echo "Error: Backup file not found: $BACKUP_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect database type from .env
|
||||||
|
if [ -f "src/.env" ]; then
|
||||||
|
DB_CONNECTION=$(grep "^DB_CONNECTION=" src/.env | cut -d '=' -f2)
|
||||||
|
DB_DATABASE=$(grep "^DB_DATABASE=" src/.env | cut -d '=' -f2)
|
||||||
|
DB_USERNAME=$(grep "^DB_USERNAME=" src/.env | cut -d '=' -f2)
|
||||||
|
DB_PASSWORD=$(grep "^DB_PASSWORD=" src/.env | cut -d '=' -f2)
|
||||||
|
else
|
||||||
|
echo "Error: src/.env not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Database Restore"
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Connection: $DB_CONNECTION"
|
||||||
|
echo "Database: $DB_DATABASE"
|
||||||
|
echo "File: $BACKUP_FILE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "⚠️ This will OVERWRITE the current database. Continue? [y/N]: " CONFIRM
|
||||||
|
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Restore cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Decompress if needed
|
||||||
|
if [[ "$BACKUP_FILE" == *.gz ]]; then
|
||||||
|
echo "Decompressing backup..."
|
||||||
|
TEMP_FILE=$(mktemp)
|
||||||
|
gunzip -c "$BACKUP_FILE" > "$TEMP_FILE"
|
||||||
|
RESTORE_FILE="$TEMP_FILE"
|
||||||
|
else
|
||||||
|
RESTORE_FILE="$BACKUP_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$DB_CONNECTION" in
|
||||||
|
mysql)
|
||||||
|
echo "Restoring MySQL database..."
|
||||||
|
docker-compose exec -T mysql mysql \
|
||||||
|
-u"$DB_USERNAME" \
|
||||||
|
-p"$DB_PASSWORD" \
|
||||||
|
"$DB_DATABASE" < "$RESTORE_FILE"
|
||||||
|
;;
|
||||||
|
pgsql)
|
||||||
|
echo "Restoring PostgreSQL database..."
|
||||||
|
# Drop and recreate database
|
||||||
|
docker-compose exec -T pgsql psql \
|
||||||
|
-U "$DB_USERNAME" \
|
||||||
|
-c "DROP DATABASE IF EXISTS $DB_DATABASE;"
|
||||||
|
docker-compose exec -T pgsql psql \
|
||||||
|
-U "$DB_USERNAME" \
|
||||||
|
-c "CREATE DATABASE $DB_DATABASE;"
|
||||||
|
docker-compose exec -T pgsql psql \
|
||||||
|
-U "$DB_USERNAME" \
|
||||||
|
-d "$DB_DATABASE" < "$RESTORE_FILE"
|
||||||
|
;;
|
||||||
|
sqlite)
|
||||||
|
echo "Restoring SQLite database..."
|
||||||
|
cp "$RESTORE_FILE" "src/database/database.sqlite"
|
||||||
|
chmod 666 "src/database/database.sqlite"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Error: Unknown database connection: $DB_CONNECTION"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Cleanup temp file
|
||||||
|
if [ -n "$TEMP_FILE" ] && [ -f "$TEMP_FILE" ]; then
|
||||||
|
rm "$TEMP_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✓ Database restored successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "You may want to run:"
|
||||||
|
echo " make artisan cmd='cache:clear'"
|
||||||
|
echo " make artisan cmd='config:clear'"
|
||||||
123
setup.bat
Normal file
123
setup.bat
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
@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: Configure environment
|
||||||
|
echo Configuring environment...
|
||||||
|
if exist "src\.env.%DB%" (
|
||||||
|
copy /y "src\.env.%DB%" "src\.env" >nul
|
||||||
|
echo Environment configured for %DB%
|
||||||
|
) else (
|
||||||
|
echo ERROR: Template file src\.env.%DB% not found
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Step 2: Install composer dependencies
|
||||||
|
echo Installing composer dependencies...
|
||||||
|
docker-compose build
|
||||||
|
docker-compose --profile %DB% run --rm app composer install --no-interaction
|
||||||
|
echo Dependencies installed
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Step 3: Start containers
|
||||||
|
echo Starting Docker containers...
|
||||||
|
docker-compose --profile %DB% up -d
|
||||||
|
echo Containers started
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Step 4: Wait for database
|
||||||
|
if "%DB%"=="mysql" (
|
||||||
|
echo Waiting for database...
|
||||||
|
timeout /t 5 /nobreak >nul
|
||||||
|
echo Database ready
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
if "%DB%"=="pgsql" (
|
||||||
|
echo Waiting for database...
|
||||||
|
timeout /t 5 /nobreak >nul
|
||||||
|
echo Database ready
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Step 5: Generate app key
|
||||||
|
echo Generating application key...
|
||||||
|
docker-compose exec app php artisan key:generate --force
|
||||||
|
echo App key generated
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Step 6: Run migrations
|
||||||
|
echo Running database migrations...
|
||||||
|
docker-compose exec app php artisan migrate --force
|
||||||
|
echo Migrations completed
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Step 7: Create storage link
|
||||||
|
echo Creating storage symlink...
|
||||||
|
docker-compose exec app php artisan storage:link
|
||||||
|
echo Storage linked
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Step 8: Create admin user
|
||||||
|
echo Creating admin user...
|
||||||
|
echo Email: admin@example.com
|
||||||
|
echo Password: password
|
||||||
|
docker-compose exec -T app php artisan make:filament-user --name=Admin --email=admin@example.com --password=password 2>nul || echo (Admin user may already exist)
|
||||||
|
echo Admin user ready
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Done
|
||||||
|
echo.
|
||||||
|
echo ========================================================
|
||||||
|
echo Setup Complete!
|
||||||
|
echo ========================================================
|
||||||
|
echo.
|
||||||
|
echo Your Laravel application is ready!
|
||||||
|
echo.
|
||||||
|
echo Laravel App: http://localhost:8080
|
||||||
|
echo Admin Panel: http://localhost:8080/admin
|
||||||
|
echo Mailpit: http://localhost:8025
|
||||||
|
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
|
||||||
123
setup.sh
Normal file
123
setup.sh
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/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: Configure environment
|
||||||
|
echo -e "${YELLOW}→ Configuring environment...${NC}"
|
||||||
|
if [ -f "src/.env.${DB}" ]; then
|
||||||
|
cp "src/.env.${DB}" "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 2: Install composer dependencies
|
||||||
|
echo -e "${YELLOW}→ Installing composer dependencies...${NC}"
|
||||||
|
docker-compose build
|
||||||
|
docker-compose --profile ${DB} run --rm app composer install --no-interaction
|
||||||
|
echo -e "${GREEN}✓ Dependencies installed${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 3: Start containers
|
||||||
|
echo -e "${YELLOW}→ Starting Docker containers...${NC}"
|
||||||
|
docker-compose --profile ${DB} up -d
|
||||||
|
echo -e "${GREEN}✓ Containers started${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 4: Wait for database
|
||||||
|
if [ "$DB" = "mysql" ] || [ "$DB" = "pgsql" ]; then
|
||||||
|
echo -e "${YELLOW}→ Waiting for database...${NC}"
|
||||||
|
sleep 5
|
||||||
|
echo -e "${GREEN}✓ Database ready${NC}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 5: Generate app key if needed
|
||||||
|
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 6: 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 7: 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 ""
|
||||||
|
|
||||||
|
# Step 8: Create admin user
|
||||||
|
echo -e "${YELLOW}→ Creating admin user...${NC}"
|
||||||
|
echo -e "${YELLOW} Email: admin@example.com${NC}"
|
||||||
|
echo -e "${YELLOW} Password: password${NC}"
|
||||||
|
docker-compose exec -T app php artisan make:filament-user --name=Admin --email=admin@example.com --password=password 2>/dev/null || echo -e "${YELLOW} (Admin user may already exist)${NC}"
|
||||||
|
echo -e "${GREEN}✓ Admin user ready${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:8080${NC}"
|
||||||
|
echo -e " 🔐 Admin Panel: ${GREEN}http://localhost:8080/admin${NC}"
|
||||||
|
echo -e " 📧 Mailpit: ${GREEN}http://localhost:8025${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
|
||||||
66
src/.env.example
Normal file
66
src/.env.example
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_TIMEZONE=UTC
|
||||||
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
APP_LOCALE=en
|
||||||
|
APP_FALLBACK_LOCALE=en
|
||||||
|
APP_FAKER_LOCALE=en_US
|
||||||
|
|
||||||
|
APP_MAINTENANCE_DRIVER=file
|
||||||
|
# APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
|
PHP_CLI_SERVER_WORKERS=4
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=single
|
||||||
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
DB_CONNECTION=sqlite
|
||||||
|
# DB_HOST=127.0.0.1
|
||||||
|
# DB_PORT=3306
|
||||||
|
# DB_DATABASE=laravel
|
||||||
|
# DB_USERNAME=root
|
||||||
|
# DB_PASSWORD=
|
||||||
|
|
||||||
|
SESSION_DRIVER=database
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
SESSION_ENCRYPT=false
|
||||||
|
SESSION_PATH=/
|
||||||
|
SESSION_DOMAIN=null
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=log
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=database
|
||||||
|
|
||||||
|
CACHE_STORE=database
|
||||||
|
CACHE_PREFIX=
|
||||||
|
|
||||||
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=log
|
||||||
|
MAIL_SCHEME=null
|
||||||
|
MAIL_HOST=127.0.0.1
|
||||||
|
MAIL_PORT=2525
|
||||||
|
MAIL_USERNAME=null
|
||||||
|
MAIL_PASSWORD=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}"
|
||||||
64
src/.env.mysql
Normal file
64
src/.env.mysql
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_TIMEZONE=UTC
|
||||||
|
APP_URL=http://localhost:8080
|
||||||
|
|
||||||
|
APP_LOCALE=en
|
||||||
|
APP_FALLBACK_LOCALE=en
|
||||||
|
APP_FAKER_LOCALE=en_US
|
||||||
|
|
||||||
|
APP_MAINTENANCE_DRIVER=file
|
||||||
|
APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=single
|
||||||
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=mysql
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=laravel
|
||||||
|
DB_USERNAME=laravel
|
||||||
|
DB_PASSWORD=secret
|
||||||
|
|
||||||
|
SESSION_DRIVER=database
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
SESSION_ENCRYPT=false
|
||||||
|
SESSION_PATH=/
|
||||||
|
SESSION_DOMAIN=null
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=log
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
|
||||||
|
CACHE_STORE=redis
|
||||||
|
CACHE_PREFIX=laravel_cache
|
||||||
|
|
||||||
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=mailpit
|
||||||
|
MAIL_PORT=1025
|
||||||
|
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}"
|
||||||
64
src/.env.pgsql
Normal file
64
src/.env.pgsql
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_TIMEZONE=UTC
|
||||||
|
APP_URL=http://localhost:8080
|
||||||
|
|
||||||
|
APP_LOCALE=en
|
||||||
|
APP_FALLBACK_LOCALE=en
|
||||||
|
APP_FAKER_LOCALE=en_US
|
||||||
|
|
||||||
|
APP_MAINTENANCE_DRIVER=file
|
||||||
|
APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=single
|
||||||
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
DB_CONNECTION=pgsql
|
||||||
|
DB_HOST=pgsql
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_DATABASE=laravel
|
||||||
|
DB_USERNAME=laravel
|
||||||
|
DB_PASSWORD=secret
|
||||||
|
|
||||||
|
SESSION_DRIVER=database
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
SESSION_ENCRYPT=false
|
||||||
|
SESSION_PATH=/
|
||||||
|
SESSION_DOMAIN=null
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=log
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
|
||||||
|
CACHE_STORE=redis
|
||||||
|
CACHE_PREFIX=laravel_cache
|
||||||
|
|
||||||
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=mailpit
|
||||||
|
MAIL_PORT=1025
|
||||||
|
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}"
|
||||||
60
src/.env.sqlite
Normal file
60
src/.env.sqlite
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_TIMEZONE=UTC
|
||||||
|
APP_URL=http://localhost:8080
|
||||||
|
|
||||||
|
APP_LOCALE=en
|
||||||
|
APP_FALLBACK_LOCALE=en
|
||||||
|
APP_FAKER_LOCALE=en_US
|
||||||
|
|
||||||
|
APP_MAINTENANCE_DRIVER=file
|
||||||
|
APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=single
|
||||||
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
DB_CONNECTION=sqlite
|
||||||
|
DB_DATABASE=/var/www/html/database/database.sqlite
|
||||||
|
|
||||||
|
SESSION_DRIVER=database
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
SESSION_ENCRYPT=false
|
||||||
|
SESSION_PATH=/
|
||||||
|
SESSION_DOMAIN=null
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=log
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
|
||||||
|
CACHE_STORE=redis
|
||||||
|
CACHE_PREFIX=laravel_cache
|
||||||
|
|
||||||
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=mailpit
|
||||||
|
MAIL_PORT=1025
|
||||||
|
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}"
|
||||||
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
|
||||||
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).
|
||||||
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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
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_'),
|
||||||
|
|
||||||
|
];
|
||||||
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');
|
||||||
|
}
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user