templave v1

This commit is contained in:
2026-03-06 08:57:05 +02:00
commit 17dbdad28b
50 changed files with 8720 additions and 0 deletions

29
.env.example Normal file
View 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
View 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
View 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

View File

299
AI_CONTEXT.md Normal file
View 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
View 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
View 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
View 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

View 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

View 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>

View 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;
}

View 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=*
```

View 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

View 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

View 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
View 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 "=========================================="

View 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!"

View 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

View 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 ""

View 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

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

229
scripts/post-install.sh Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
# This directory will contain your Laravel application
# Run: docker-compose run --rm app composer create-project laravel/laravel .

File diff suppressed because it is too large Load Diff

View 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);
}
}
}

View 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
View 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"
]
}

View 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);
| });
|
*/