templave v1
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
|
||||||
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Docker volumes
|
||||||
|
mysql_data/
|
||||||
|
redis_data/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Laravel (if src is committed)
|
||||||
|
src/vendor/
|
||||||
|
src/node_modules/
|
||||||
|
src/.env
|
||||||
|
src/storage/*.key
|
||||||
|
src/storage/logs/*
|
||||||
|
!src/storage/logs/.gitkeep
|
||||||
0
.windsurf/workflows/js.md
Normal file
0
.windsurf/workflows/js.md
Normal file
299
AI_CONTEXT.md
Normal file
299
AI_CONTEXT.md
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
# AI Assistant Context
|
||||||
|
|
||||||
|
This document provides context for AI coding assistants working on projects built with this template.
|
||||||
|
|
||||||
|
## Template Overview
|
||||||
|
|
||||||
|
This is a **Laravel Docker Development Template** with:
|
||||||
|
- Docker-based development environment
|
||||||
|
- Production deployment to Ubuntu 24.04 (no Docker)
|
||||||
|
- Modular architecture with Filament admin
|
||||||
|
- Audit trail for all data changes
|
||||||
|
- Multi-database support (MySQL, PostgreSQL, SQLite)
|
||||||
|
|
||||||
|
## 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 or Jetstream (Livewire only) |
|
||||||
|
| **Permissions** | spatie/laravel-permission |
|
||||||
|
| **Audit** | owen-it/laravel-auditing |
|
||||||
|
| **Error Tracking** | spatie/laravel-flare + spatie/laravel-ignition |
|
||||||
|
| **Code Style** | Laravel Pint |
|
||||||
|
|
||||||
|
## 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
|
||||||
317
GETTING_STARTED.md
Normal file
317
GETTING_STARTED.md
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
# Getting Started
|
||||||
|
|
||||||
|
This guide walks you through setting up a new Laravel project using this template.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Docker & Docker Compose
|
||||||
|
- Git
|
||||||
|
- Make (optional, but recommended)
|
||||||
|
|
||||||
|
## Quick Start (5 minutes)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Clone the template
|
||||||
|
git clone https://github.com/your-repo/Laravel-Docker-Dev-Template.git my-project
|
||||||
|
cd my-project
|
||||||
|
|
||||||
|
# 2. Copy environment file
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# 3. Choose your database and start
|
||||||
|
make install DB=mysql # or: pgsql, sqlite
|
||||||
|
|
||||||
|
# 4. Run setup scripts (interactive)
|
||||||
|
make setup-tools # Flare, Pint, error pages
|
||||||
|
make setup-laravel # Auth, Filament, modules, audit trail
|
||||||
|
|
||||||
|
# 5. Access your app
|
||||||
|
# Laravel: http://localhost:8080
|
||||||
|
# Admin: http://localhost:8080/admin
|
||||||
|
# Mail: http://localhost:8025
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step-by-Step Setup
|
||||||
|
|
||||||
|
### 1. Clone and Configure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/your-repo/Laravel-Docker-Dev-Template.git my-project
|
||||||
|
cd my-project
|
||||||
|
|
||||||
|
# Remove template git history and start fresh
|
||||||
|
rm -rf .git
|
||||||
|
git init
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Choose Database
|
||||||
|
|
||||||
|
| Database | Best For | Command |
|
||||||
|
|----------|----------|---------|
|
||||||
|
| **MySQL** | Most projects, production parity | `make install DB=mysql` |
|
||||||
|
| **PostgreSQL** | Advanced features, JSON, full-text search | `make install DB=pgsql` |
|
||||||
|
| **SQLite** | Simple apps, quick prototyping | `make install DB=sqlite` |
|
||||||
|
|
||||||
|
### 3. Install Laravel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start containers and create Laravel project
|
||||||
|
make install DB=mysql
|
||||||
|
|
||||||
|
# This will:
|
||||||
|
# - Build Docker images
|
||||||
|
# - Start containers
|
||||||
|
# - Run composer create-project laravel/laravel
|
||||||
|
# - Copy appropriate .env file
|
||||||
|
# - Generate app key
|
||||||
|
# - Run initial migrations
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Run Setup Scripts
|
||||||
|
|
||||||
|
#### Post-Install Tools
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make setup-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
Installs:
|
||||||
|
- ✅ Spatie Ignition (dev error pages)
|
||||||
|
- ✅ Spatie Flare (production error tracking)
|
||||||
|
- ✅ Laravel Pint (code style)
|
||||||
|
- ✅ Custom error pages (404, 500, 503)
|
||||||
|
- ❓ Laravel Telescope (optional debugging)
|
||||||
|
|
||||||
|
#### Laravel Base Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make setup-laravel
|
||||||
|
```
|
||||||
|
|
||||||
|
Interactive prompts for:
|
||||||
|
|
||||||
|
1. **Authentication** - Choose one:
|
||||||
|
- Breeze + Blade (recommended)
|
||||||
|
- Breeze + Livewire
|
||||||
|
- Breeze API only
|
||||||
|
- Jetstream + Livewire
|
||||||
|
|
||||||
|
2. **Filament Admin** - User management dashboard
|
||||||
|
|
||||||
|
3. **Audit Trail** - Track all data changes
|
||||||
|
|
||||||
|
4. **Module System** - Modular architecture
|
||||||
|
|
||||||
|
5. **API (Sanctum)** - Token authentication
|
||||||
|
|
||||||
|
6. **Security Middleware** - HTTPS, headers
|
||||||
|
|
||||||
|
### 5. Create Your First Module
|
||||||
|
|
||||||
|
```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 module
|
||||||
|
php artisan make:module ModuleName
|
||||||
|
|
||||||
|
# With model
|
||||||
|
php artisan make:module ModuleName --model=ModelName
|
||||||
|
|
||||||
|
# With API
|
||||||
|
php artisan make:module ModuleName --api
|
||||||
|
|
||||||
|
# Without Filament
|
||||||
|
php artisan make:module ModuleName --no-filament
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
### 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 Flare** - Get API key at [flareapp.io](https://flareapp.io)
|
||||||
|
2. **Create modules** - `php artisan make:module YourFeature`
|
||||||
|
3. **Add models** - `php artisan make:module YourFeature --model=YourModel`
|
||||||
|
4. **Write tests** - `make test`
|
||||||
|
5. **Deploy** - See [Production Deployment](README.md#production-deployment)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [README.md](README.md) - Overview and commands
|
||||||
|
- [docs/laravel-setup.md](docs/laravel-setup.md) - Detailed setup options
|
||||||
|
- [docs/modules.md](docs/modules.md) - Module architecture
|
||||||
|
- [docs/audit-trail.md](docs/audit-trail.md) - Audit configuration
|
||||||
|
- [docs/filament-admin.md](docs/filament-admin.md) - Admin panel
|
||||||
|
- [docs/error-logging.md](docs/error-logging.md) - Error tracking
|
||||||
|
- [docs/queues.md](docs/queues.md) - Background jobs
|
||||||
|
- [docs/ci-cd.md](docs/ci-cd.md) - CI/CD pipeline
|
||||||
|
- [docs/backup.md](docs/backup.md) - Database backup
|
||||||
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
|
||||||
540
README.md
Normal file
540
README.md
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
# 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
|
||||||
|
- Make (optional, for convenience commands)
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. **Clone the repository**
|
||||||
|
```bash
|
||||||
|
git clone <repo-url> my-laravel-app
|
||||||
|
cd my-laravel-app
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Copy environment file**
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Install Laravel with your preferred database**
|
||||||
|
```bash
|
||||||
|
# With MySQL (default)
|
||||||
|
make install DB=mysql
|
||||||
|
|
||||||
|
# With PostgreSQL
|
||||||
|
make install DB=pgsql
|
||||||
|
|
||||||
|
# With SQLite
|
||||||
|
make install DB=sqlite
|
||||||
|
|
||||||
|
# Or without Make:
|
||||||
|
docker-compose build
|
||||||
|
docker-compose --profile mysql run --rm app composer create-project laravel/laravel .
|
||||||
|
cp src/.env.mysql src/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Start the development environment**
|
||||||
|
```bash
|
||||||
|
# Start with your chosen database
|
||||||
|
make up DB=mysql # or pgsql, sqlite
|
||||||
|
|
||||||
|
# Or: docker-compose --profile mysql up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Access your application**
|
||||||
|
- Laravel App: http://localhost:8080
|
||||||
|
- Mailpit: http://localhost:8025
|
||||||
|
|
||||||
|
6. **Run setup scripts**
|
||||||
|
```bash
|
||||||
|
# Install Flare, Pint, error pages
|
||||||
|
make setup-tools
|
||||||
|
|
||||||
|
# Configure auth, API, middleware (interactive)
|
||||||
|
make setup-laravel
|
||||||
|
|
||||||
|
# Or run both
|
||||||
|
make setup-all
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
173
docker-compose.yml
Normal file
173
docker-compose.yml
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
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
|
||||||
|
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini
|
||||||
|
- sqlite_data:/var/www/html/database
|
||||||
|
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
|
||||||
|
- ./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:
|
||||||
|
sqlite_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"]
|
||||||
15
docker/php/local.ini
Normal file
15
docker/php/local.ini
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
; 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 (disabled for development for live reloading)
|
||||||
|
opcache.enable = 0
|
||||||
|
opcache.enable_cli = 0
|
||||||
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'"
|
||||||
90
src/.env.example
Normal file
90
src/.env.example
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Laravel Environment Configuration
|
||||||
|
#
|
||||||
|
# This template supports multiple databases. Copy the appropriate file:
|
||||||
|
# - .env.mysql - MySQL configuration
|
||||||
|
# - .env.pgsql - PostgreSQL configuration
|
||||||
|
# - .env.sqlite - SQLite configuration
|
||||||
|
#
|
||||||
|
# Example: cp .env.mysql .env
|
||||||
|
|
||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_TIMEZONE=UTC
|
||||||
|
APP_URL=http://localhost:8080
|
||||||
|
|
||||||
|
APP_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
|
||||||
|
|
||||||
|
# Database: Choose one configuration
|
||||||
|
# ===================================
|
||||||
|
# MySQL:
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=mysql
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=laravel
|
||||||
|
DB_USERNAME=laravel
|
||||||
|
DB_PASSWORD=secret
|
||||||
|
|
||||||
|
# PostgreSQL (uncomment and comment MySQL above):
|
||||||
|
# DB_CONNECTION=pgsql
|
||||||
|
# DB_HOST=pgsql
|
||||||
|
# DB_PORT=5432
|
||||||
|
# DB_DATABASE=laravel
|
||||||
|
# DB_USERNAME=laravel
|
||||||
|
# DB_PASSWORD=secret
|
||||||
|
|
||||||
|
# SQLite (uncomment and comment MySQL above):
|
||||||
|
# DB_CONNECTION=sqlite
|
||||||
|
# DB_DATABASE=/var/www/html/database/database.sqlite
|
||||||
|
|
||||||
|
SESSION_DRIVER=redis
|
||||||
|
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=
|
||||||
|
|
||||||
|
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}"
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
# Error Logging - Flare (https://flareapp.io)
|
||||||
|
# Get your key at: https://flareapp.io
|
||||||
|
FLARE_KEY=
|
||||||
|
|
||||||
|
# Ignition Settings (Development)
|
||||||
|
# Themes: light, dark, auto
|
||||||
|
# Editors: vscode, phpstorm, sublime, atom, textmate
|
||||||
|
IGNITION_THEME=auto
|
||||||
|
IGNITION_EDITOR=vscode
|
||||||
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=redis
|
||||||
|
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=
|
||||||
|
|
||||||
|
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}"
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
# Error Logging - Flare (https://flareapp.io)
|
||||||
|
# Get your key at: https://flareapp.io
|
||||||
|
FLARE_KEY=
|
||||||
|
|
||||||
|
# Ignition Settings (Development)
|
||||||
|
IGNITION_THEME=auto
|
||||||
|
IGNITION_EDITOR=vscode
|
||||||
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=redis
|
||||||
|
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=
|
||||||
|
|
||||||
|
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}"
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
# Error Logging - Flare (https://flareapp.io)
|
||||||
|
# Get your key at: https://flareapp.io
|
||||||
|
FLARE_KEY=
|
||||||
|
|
||||||
|
# Ignition Settings (Development)
|
||||||
|
IGNITION_THEME=auto
|
||||||
|
IGNITION_EDITOR=vscode
|
||||||
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=redis
|
||||||
|
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=
|
||||||
|
|
||||||
|
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}"
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
# Error Logging - Flare (https://flareapp.io)
|
||||||
|
# Get your key at: https://flareapp.io
|
||||||
|
FLARE_KEY=
|
||||||
|
|
||||||
|
# Ignition Settings (Development)
|
||||||
|
IGNITION_THEME=auto
|
||||||
|
IGNITION_EDITOR=vscode
|
||||||
2
src/.gitkeep
Normal file
2
src/.gitkeep
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# This directory will contain your Laravel application
|
||||||
|
# Run: docker-compose run --rm app composer create-project laravel/laravel .
|
||||||
1287
src/app/Console/Commands/MakeModule.php.stub
Normal file
1287
src/app/Console/Commands/MakeModule.php.stub
Normal file
File diff suppressed because it is too large
Load Diff
121
src/app/Providers/ModuleServiceProvider.php.stub
Normal file
121
src/app/Providers/ModuleServiceProvider.php.stub
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class ModuleServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->registerModules();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
$this->bootModules();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register all modules.
|
||||||
|
*/
|
||||||
|
protected function registerModules(): void
|
||||||
|
{
|
||||||
|
$modulesPath = app_path('Modules');
|
||||||
|
|
||||||
|
if (!File::isDirectory($modulesPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$modules = File::directories($modulesPath);
|
||||||
|
|
||||||
|
foreach ($modules as $modulePath) {
|
||||||
|
$moduleName = basename($modulePath);
|
||||||
|
$providerClass = "App\\Modules\\{$moduleName}\\{$moduleName}ServiceProvider";
|
||||||
|
|
||||||
|
if (class_exists($providerClass)) {
|
||||||
|
$this->app->register($providerClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boot all modules.
|
||||||
|
*/
|
||||||
|
protected function bootModules(): void
|
||||||
|
{
|
||||||
|
$modulesPath = app_path('Modules');
|
||||||
|
|
||||||
|
if (!File::isDirectory($modulesPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$modules = File::directories($modulesPath);
|
||||||
|
|
||||||
|
foreach ($modules as $modulePath) {
|
||||||
|
$moduleName = basename($modulePath);
|
||||||
|
|
||||||
|
// Load module config
|
||||||
|
$configPath = "{$modulePath}/Config";
|
||||||
|
if (File::isDirectory($configPath)) {
|
||||||
|
foreach (File::files($configPath) as $file) {
|
||||||
|
$this->mergeConfigFrom(
|
||||||
|
$file->getPathname(),
|
||||||
|
strtolower($moduleName) . '.' . $file->getFilenameWithoutExtension()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load module routes
|
||||||
|
$this->loadModuleRoutes($modulePath, $moduleName);
|
||||||
|
|
||||||
|
// Load module views
|
||||||
|
$viewsPath = "{$modulePath}/Resources/views";
|
||||||
|
if (File::isDirectory($viewsPath)) {
|
||||||
|
$this->loadViewsFrom($viewsPath, strtolower(preg_replace('/(?<!^)[A-Z]/', '-$0', $moduleName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load module migrations
|
||||||
|
$migrationsPath = "{$modulePath}/Database/Migrations";
|
||||||
|
if (File::isDirectory($migrationsPath)) {
|
||||||
|
$this->loadMigrationsFrom($migrationsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load module translations
|
||||||
|
$langPath = "{$modulePath}/Resources/lang";
|
||||||
|
if (File::isDirectory($langPath)) {
|
||||||
|
$this->loadTranslationsFrom($langPath, strtolower($moduleName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load module routes.
|
||||||
|
*/
|
||||||
|
protected function loadModuleRoutes(string $modulePath, string $moduleName): void
|
||||||
|
{
|
||||||
|
$routesPath = "{$modulePath}/Routes";
|
||||||
|
|
||||||
|
if (!File::isDirectory($routesPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$webRoutes = "{$routesPath}/web.php";
|
||||||
|
$apiRoutes = "{$routesPath}/api.php";
|
||||||
|
|
||||||
|
if (File::exists($webRoutes)) {
|
||||||
|
$this->loadRoutesFrom($webRoutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File::exists($apiRoutes)) {
|
||||||
|
$this->loadRoutesFrom($apiRoutes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/config/cors.php.example
Normal file
41
src/config/cors.php.example
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CORS Configuration Example
|
||||||
|
*
|
||||||
|
* Copy this to config/cors.php in your Laravel app if you need
|
||||||
|
* to customize CORS settings beyond the defaults.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cross-Origin Resource Sharing (CORS) Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Settings for handling Cross-Origin Resource Sharing (CORS).
|
||||||
|
| Adjust these based on your API requirements.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'paths' => ['api/*', 'sanctum/csrf-cookie'],
|
||||||
|
|
||||||
|
'allowed_methods' => ['*'],
|
||||||
|
|
||||||
|
'allowed_origins' => [
|
||||||
|
env('FRONTEND_URL', 'http://localhost:3000'),
|
||||||
|
// Add other allowed origins here
|
||||||
|
],
|
||||||
|
|
||||||
|
'allowed_origins_patterns' => [],
|
||||||
|
|
||||||
|
'allowed_headers' => ['*'],
|
||||||
|
|
||||||
|
'exposed_headers' => [],
|
||||||
|
|
||||||
|
'max_age' => 0,
|
||||||
|
|
||||||
|
'supports_credentials' => true,
|
||||||
|
|
||||||
|
];
|
||||||
45
src/pint.json
Normal file
45
src/pint.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"preset": "laravel",
|
||||||
|
"rules": {
|
||||||
|
"array_indentation": true,
|
||||||
|
"array_syntax": {
|
||||||
|
"syntax": "short"
|
||||||
|
},
|
||||||
|
"binary_operator_spaces": {
|
||||||
|
"default": "single_space"
|
||||||
|
},
|
||||||
|
"blank_line_after_namespace": true,
|
||||||
|
"blank_line_after_opening_tag": true,
|
||||||
|
"blank_line_before_statement": {
|
||||||
|
"statements": ["return", "throw", "try"]
|
||||||
|
},
|
||||||
|
"class_attributes_separation": {
|
||||||
|
"elements": {
|
||||||
|
"method": "one"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"concat_space": {
|
||||||
|
"spacing": "none"
|
||||||
|
},
|
||||||
|
"declare_strict_types": false,
|
||||||
|
"fully_qualified_strict_types": true,
|
||||||
|
"method_argument_space": {
|
||||||
|
"on_multiline": "ensure_fully_multiline",
|
||||||
|
"keep_multiple_spaces_after_comma": false
|
||||||
|
},
|
||||||
|
"no_unused_imports": true,
|
||||||
|
"ordered_imports": {
|
||||||
|
"sort_algorithm": "alpha"
|
||||||
|
},
|
||||||
|
"single_quote": true,
|
||||||
|
"trailing_comma_in_multiline": {
|
||||||
|
"elements": ["arrays", "arguments", "parameters"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"vendor",
|
||||||
|
"node_modules",
|
||||||
|
"storage",
|
||||||
|
"bootstrap/cache"
|
||||||
|
]
|
||||||
|
}
|
||||||
94
src/routes/api.example.php
Normal file
94
src/routes/api.example.php
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Routes Example
|
||||||
|
*
|
||||||
|
* This file provides a starting template for API routes with Sanctum.
|
||||||
|
* Copy/merge this into routes/api.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Public API Routes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
Route::get('/health', function () {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'ok',
|
||||||
|
'timestamp' => now()->toISOString(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Routes (if using Breeze API or custom)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Route::post('/register', [AuthController::class, 'register']);
|
||||||
|
// Route::post('/login', [AuthController::class, 'login']);
|
||||||
|
// Route::post('/forgot-password', [AuthController::class, 'forgotPassword']);
|
||||||
|
// Route::post('/reset-password', [AuthController::class, 'resetPassword']);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Protected API Routes (require authentication)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
|
||||||
|
// Current user
|
||||||
|
Route::get('/user', function (Request $request) {
|
||||||
|
return $request->user();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
Route::post('/logout', function (Request $request) {
|
||||||
|
$request->user()->currentAccessToken()->delete();
|
||||||
|
return response()->json(['message' => 'Logged out']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// User tokens management
|
||||||
|
Route::get('/tokens', function (Request $request) {
|
||||||
|
return $request->user()->tokens;
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::delete('/tokens/{tokenId}', function (Request $request, $tokenId) {
|
||||||
|
$request->user()->tokens()->where('id', $tokenId)->delete();
|
||||||
|
return response()->json(['message' => 'Token revoked']);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Your API Resources
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Add your protected API routes here
|
||||||
|
|
|
||||||
|
| Example:
|
||||||
|
| Route::apiResource('posts', PostController::class);
|
||||||
|
| Route::apiResource('comments', CommentController::class);
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| API Versioning Example
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Uncomment to use versioned API routes
|
||||||
|
|
|
||||||
|
| Route::prefix('v1')->group(function () {
|
||||||
|
| Route::apiResource('posts', Api\V1\PostController::class);
|
||||||
|
| });
|
||||||
|
|
|
||||||
|
| Route::prefix('v2')->group(function () {
|
||||||
|
| Route::apiResource('posts', Api\V2\PostController::class);
|
||||||
|
| });
|
||||||
|
|
|
||||||
|
*/
|
||||||
Reference in New Issue
Block a user