Compare commits
No commits in common. "release/v0.6" and "main" have entirely different histories.
release/v0
...
main
19 changed files with 463 additions and 354 deletions
123
Makefile
Normal file
123
Makefile
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
# Dish Planner - Docker Commands
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
help: ## Show this help message
|
||||||
|
@echo "Dish Planner - Docker Management"
|
||||||
|
@echo ""
|
||||||
|
@echo "Usage: make [command]"
|
||||||
|
@echo ""
|
||||||
|
@echo "Available commands:"
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
# Development Commands
|
||||||
|
.PHONY: dev
|
||||||
|
dev: ## Start development environment
|
||||||
|
docker compose up -d
|
||||||
|
@echo "Development server running at http://localhost:8000"
|
||||||
|
@echo "Mailhog available at http://localhost:8025"
|
||||||
|
|
||||||
|
.PHONY: dev-build
|
||||||
|
dev-build: ## Build and start development environment
|
||||||
|
docker compose build
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
.PHONY: dev-stop
|
||||||
|
dev-stop: ## Stop development environment
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
.PHONY: dev-clean
|
||||||
|
dev-clean: ## Stop and remove volumes (CAUTION: removes database)
|
||||||
|
docker compose down -v
|
||||||
|
|
||||||
|
.PHONY: logs
|
||||||
|
logs: ## Show application logs
|
||||||
|
docker compose logs -f app
|
||||||
|
|
||||||
|
.PHONY: logs-db
|
||||||
|
logs-db: ## Show database logs
|
||||||
|
docker compose logs -f db
|
||||||
|
|
||||||
|
# Production Commands
|
||||||
|
.PHONY: prod-build
|
||||||
|
prod-build: ## Build production image for Codeberg
|
||||||
|
./bin/build-push.sh
|
||||||
|
|
||||||
|
.PHONY: prod-build-tag
|
||||||
|
prod-build-tag: ## Build with specific tag (usage: make prod-build-tag TAG=v1.0.0)
|
||||||
|
./bin/build-push.sh $(TAG)
|
||||||
|
|
||||||
|
.PHONY: prod-login
|
||||||
|
prod-login: ## Login to Codeberg registry
|
||||||
|
podman login codeberg.org
|
||||||
|
|
||||||
|
# Laravel Commands
|
||||||
|
.PHONY: artisan
|
||||||
|
artisan: ## Run artisan command (usage: make artisan cmd="migrate")
|
||||||
|
docker compose exec app php artisan $(cmd)
|
||||||
|
|
||||||
|
.PHONY: composer
|
||||||
|
composer: ## Run composer command (usage: make composer cmd="require package")
|
||||||
|
docker compose exec app composer $(cmd)
|
||||||
|
|
||||||
|
.PHONY: npm
|
||||||
|
npm: ## Run npm command (usage: make npm cmd="install package")
|
||||||
|
docker compose exec app npm $(cmd)
|
||||||
|
|
||||||
|
.PHONY: migrate
|
||||||
|
migrate: ## Run database migrations
|
||||||
|
docker compose exec app php artisan migrate
|
||||||
|
|
||||||
|
.PHONY: seed
|
||||||
|
seed: ## Seed the database
|
||||||
|
docker compose exec app php artisan db:seed
|
||||||
|
|
||||||
|
.PHONY: fresh
|
||||||
|
fresh: ## Fresh migrate and seed
|
||||||
|
docker compose exec app php artisan migrate:fresh --seed
|
||||||
|
|
||||||
|
.PHONY: tinker
|
||||||
|
tinker: ## Start Laravel tinker
|
||||||
|
docker compose exec app php artisan tinker
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: ## Run tests
|
||||||
|
docker compose exec app php artisan test
|
||||||
|
|
||||||
|
# Utility Commands
|
||||||
|
.PHONY: shell
|
||||||
|
shell: ## Enter app container shell
|
||||||
|
docker compose exec app sh
|
||||||
|
|
||||||
|
.PHONY: db-shell
|
||||||
|
db-shell: ## Enter database shell
|
||||||
|
docker compose exec db mariadb -u dishplanner -pdishplanner dishplanner
|
||||||
|
|
||||||
|
.PHONY: clear
|
||||||
|
clear: ## Clear all Laravel caches
|
||||||
|
docker compose exec app php artisan cache:clear
|
||||||
|
docker compose exec app php artisan config:clear
|
||||||
|
docker compose exec app php artisan route:clear
|
||||||
|
docker compose exec app php artisan view:clear
|
||||||
|
|
||||||
|
.PHONY: optimize
|
||||||
|
optimize: ## Optimize Laravel for production
|
||||||
|
docker compose exec app php artisan config:cache
|
||||||
|
docker compose exec app php artisan route:cache
|
||||||
|
docker compose exec app php artisan view:cache
|
||||||
|
docker compose exec app php artisan livewire:discover
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
.PHONY: install
|
||||||
|
install: ## First time setup
|
||||||
|
@echo "Setting up Dish Planner..."
|
||||||
|
@cp -n .env.example .env || true
|
||||||
|
@echo "Generating application key..."
|
||||||
|
@docker compose build
|
||||||
|
@docker compose up -d
|
||||||
|
@sleep 5
|
||||||
|
@docker compose exec app php artisan key:generate
|
||||||
|
@docker compose exec app php artisan migrate
|
||||||
|
@echo ""
|
||||||
|
@echo "✅ Installation complete!"
|
||||||
|
@echo "Access the app at: http://localhost:8000"
|
||||||
|
@echo "Create your first planner and user to get started."
|
||||||
343
README.md
343
README.md
|
|
@ -11,118 +11,285 @@ ## ✨ Features
|
||||||
- **Dark theme UI** - Modern interface with purple/pink accents
|
- **Dark theme UI** - Modern interface with purple/pink accents
|
||||||
- **Single container deployment** - Simplified hosting with FrankenPHP
|
- **Single container deployment** - Simplified hosting with FrankenPHP
|
||||||
|
|
||||||
## 🚀 Self-hosting
|
## 🚀 Quick Start
|
||||||
|
|
||||||
The production image is available at `codeberg.org/dish-planner/app:latest`.
|
### Prerequisites
|
||||||
|
- Docker and Docker Compose
|
||||||
|
- Make (optional, for convenience commands)
|
||||||
|
|
||||||
### docker-compose.yml
|
### First Time Setup
|
||||||
|
|
||||||
```yaml
|
```bash
|
||||||
services:
|
# Clone the repository
|
||||||
app:
|
git clone https://github.com/yourusername/dish-planner.git
|
||||||
image: codeberg.org/dish-planner/app:latest
|
cd dish-planner
|
||||||
container_name: dishplanner_app
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- "8000:8000"
|
|
||||||
environment:
|
|
||||||
APP_KEY: "${APP_KEY}"
|
|
||||||
APP_URL: "${APP_URL}"
|
|
||||||
DB_DATABASE: "${DB_DATABASE}"
|
|
||||||
DB_USERNAME: "${DB_USERNAME}"
|
|
||||||
DB_PASSWORD: "${DB_PASSWORD}"
|
|
||||||
MAIL_HOST: "${MAIL_HOST:-}"
|
|
||||||
MAIL_PORT: "${MAIL_PORT:-587}"
|
|
||||||
MAIL_USERNAME: "${MAIL_USERNAME:-}"
|
|
||||||
MAIL_PASSWORD: "${MAIL_PASSWORD:-}"
|
|
||||||
MAIL_FROM_ADDRESS: "${MAIL_FROM_ADDRESS:-noreply@example.com}"
|
|
||||||
volumes:
|
|
||||||
- app_storage:/app/storage
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8000/up"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
start_period: 40s
|
|
||||||
|
|
||||||
db:
|
# Quick install with Make
|
||||||
image: mariadb:11
|
make install
|
||||||
container_name: dishplanner_db
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
MYSQL_DATABASE: "${DB_DATABASE}"
|
|
||||||
MYSQL_USER: "${DB_USERNAME}"
|
|
||||||
MYSQL_PASSWORD: "${DB_PASSWORD}"
|
|
||||||
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
|
|
||||||
volumes:
|
|
||||||
- db_data:/var/lib/mysql
|
|
||||||
|
|
||||||
volumes:
|
# Or manually:
|
||||||
db_data:
|
cp .env.example .env
|
||||||
app_storage:
|
docker compose build
|
||||||
|
docker compose up -d
|
||||||
|
docker compose exec app php artisan key:generate
|
||||||
|
docker compose exec app php artisan migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
### Environment Variables
|
The application will be available at **http://localhost:8000**
|
||||||
|
|
||||||
| Variable | Required | Description |
|
|
||||||
|----------|----------|-------------|
|
|
||||||
| `APP_KEY` | Yes | Encryption key. Generate with: `echo "base64:$(openssl rand -base64 32)"` |
|
|
||||||
| `APP_URL` | Yes | Your domain (e.g., `https://meals.example.com`) |
|
|
||||||
| `DB_DATABASE` | Yes | Database name |
|
|
||||||
| `DB_USERNAME` | Yes | Database user |
|
|
||||||
| `DB_PASSWORD` | Yes | Database password |
|
|
||||||
| `DB_ROOT_PASSWORD` | Yes | MariaDB root password |
|
|
||||||
| `MAIL_HOST` | No | SMTP host for email notifications |
|
|
||||||
| `MAIL_PORT` | No | SMTP port (default: 587) |
|
|
||||||
| `MAIL_USERNAME` | No | SMTP username |
|
|
||||||
| `MAIL_PASSWORD` | No | SMTP password |
|
|
||||||
| `MAIL_FROM_ADDRESS` | No | From address for emails |
|
|
||||||
|
|
||||||
## 🔧 Development
|
## 🔧 Development
|
||||||
|
|
||||||
### NixOS / Nix
|
### Starting the Development Environment
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://codeberg.org/dish-planner/app.git
|
# Start all services
|
||||||
cd dish-planner
|
make dev
|
||||||
nix-shell
|
|
||||||
|
# Or with Docker Compose directly
|
||||||
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
The shell will display available commands and optionally start the containers for you.
|
**Available services:**
|
||||||
|
- **App**: http://localhost:8000 (Laravel + FrankenPHP)
|
||||||
|
- **Vite**: http://localhost:5173 (Asset hot-reload)
|
||||||
|
- **Mailhog**: http://localhost:8025 (Email testing)
|
||||||
|
- **Database**: localhost:3306 (MariaDB)
|
||||||
|
|
||||||
#### Available Commands
|
### Common Development Commands
|
||||||
|
|
||||||
| Command | Description |
|
```bash
|
||||||
|---------|-------------|
|
# View logs
|
||||||
| `dev-up` | Start development environment |
|
make logs # App logs
|
||||||
| `dev-down` | Stop development environment |
|
make logs-db # Database logs
|
||||||
| `dev-restart` | Restart containers |
|
|
||||||
| `dev-rebuild` | Full rebuild (removes volumes) |
|
|
||||||
| `dev-rebuild-quick` | Quick rebuild (keeps volumes) |
|
|
||||||
| `dev-logs [service]` | Follow logs |
|
|
||||||
| `dev-shell` | Enter app container |
|
|
||||||
| `dev-artisan <cmd>` | Run artisan commands |
|
|
||||||
| `dev-fix-permissions` | Fix Docker-created file permissions |
|
|
||||||
|
|
||||||
#### Services
|
# Laravel commands
|
||||||
|
make artisan cmd="migrate" # Run artisan commands
|
||||||
|
make tinker # Start Laravel tinker
|
||||||
|
make test # Run tests
|
||||||
|
|
||||||
| Service | URL |
|
# Database
|
||||||
|---------|-----|
|
make migrate # Run migrations
|
||||||
| App | http://localhost:8000 |
|
make seed # Seed database
|
||||||
| Vite | http://localhost:5173 |
|
make fresh # Fresh migrate with seeds
|
||||||
| Mailhog | http://localhost:8025 |
|
|
||||||
| MariaDB | localhost:3306 |
|
|
||||||
|
|
||||||
### Other Platforms
|
# Testing
|
||||||
|
make test # Run tests
|
||||||
|
composer test:coverage-html # Run tests with coverage report (generates coverage/index.html)
|
||||||
|
|
||||||
Contributions welcome for development setup instructions on other platforms.
|
# Utilities
|
||||||
|
make shell # Enter app container
|
||||||
|
make db-shell # Enter database shell
|
||||||
|
make clear # Clear all caches
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
dish-planner/
|
||||||
|
├── app/
|
||||||
|
│ ├── Livewire/ # Livewire components
|
||||||
|
│ │ ├── Auth/ # Authentication
|
||||||
|
│ │ ├── Dishes/ # Dish management
|
||||||
|
│ │ ├── Schedule/ # Schedule calendar
|
||||||
|
│ │ └── Users/ # User management
|
||||||
|
│ └── Models/ # Eloquent models
|
||||||
|
├── resources/
|
||||||
|
│ └── views/
|
||||||
|
│ └── livewire/ # Livewire component views
|
||||||
|
├── docker-compose.yml # Development environment
|
||||||
|
├── docker-compose.prod.yml # Production environment
|
||||||
|
├── Dockerfile # Production image
|
||||||
|
├── Dockerfile.dev # Development image
|
||||||
|
└── Makefile # Convenience commands
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚢 Production Deployment
|
||||||
|
|
||||||
|
### Building for Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build production image
|
||||||
|
make prod-build
|
||||||
|
|
||||||
|
# Start production environment
|
||||||
|
make prod
|
||||||
|
|
||||||
|
# Or with Docker Compose
|
||||||
|
docker compose -f docker-compose.prod.yml build
|
||||||
|
docker compose -f docker-compose.prod.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Environment Variables
|
||||||
|
|
||||||
|
Required environment variables for production:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Required - Generate APP_KEY (see instructions below)
|
||||||
|
APP_KEY=base64:your-generated-key-here
|
||||||
|
APP_URL=https://your-domain.com
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
DB_DATABASE=dishplanner
|
||||||
|
DB_USERNAME=dishplanner
|
||||||
|
DB_PASSWORD=strong-password-here
|
||||||
|
DB_ROOT_PASSWORD=strong-root-password
|
||||||
|
|
||||||
|
# Optional Email Configuration
|
||||||
|
MAIL_HOST=your-smtp-host
|
||||||
|
MAIL_PORT=587
|
||||||
|
MAIL_USERNAME=your-username
|
||||||
|
MAIL_PASSWORD=your-password
|
||||||
|
MAIL_FROM_ADDRESS=noreply@your-domain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Generating APP_KEY
|
||||||
|
|
||||||
|
The APP_KEY is critical for encryption and must be kept consistent across deployments. Generate one using any of these methods:
|
||||||
|
|
||||||
|
**Option 1: Using OpenSSL (Linux/Mac/Windows with Git Bash)**
|
||||||
|
```bash
|
||||||
|
echo "base64:$(openssl rand -base64 32)"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Using Node.js (Cross-platform)**
|
||||||
|
```bash
|
||||||
|
node -e "console.log('base64:' + require('crypto').randomBytes(32).toString('base64'))"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 3: Using Python (Cross-platform)**
|
||||||
|
```bash
|
||||||
|
python -c "import base64, os; print('base64:' + base64.b64encode(os.urandom(32)).decode())"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 4: Online Generator**
|
||||||
|
Generate a random 32-character string at https://randomkeygen.com/ and prepend with `base64:`
|
||||||
|
|
||||||
|
⚠️ **Important**: Save this key securely! If lost, you won't be able to decrypt existing data.
|
||||||
|
|
||||||
|
### Deployment with DockGE
|
||||||
|
|
||||||
|
The production setup is optimized for DockGE deployment with just 2 containers:
|
||||||
|
|
||||||
|
1. **app** - Laravel application with FrankenPHP
|
||||||
|
2. **db** - MariaDB database
|
||||||
|
|
||||||
|
Simply import the `docker-compose.prod.yml` into DockGE and configure your environment variables.
|
||||||
|
|
||||||
|
## 🛠️ Technology Stack
|
||||||
|
|
||||||
|
- **Backend**: Laravel 12 with Livewire 3
|
||||||
|
- **Web Server**: FrankenPHP (PHP 8.3 + Caddy)
|
||||||
|
- **Database**: MariaDB 11
|
||||||
|
- **Frontend**: Blade + Livewire + Alpine.js
|
||||||
|
- **Styling**: TailwindCSS with custom dark theme
|
||||||
|
- **Assets**: Vite for bundling
|
||||||
|
|
||||||
|
## 📦 Docker Architecture
|
||||||
|
|
||||||
|
### Development (`docker-compose.yml`)
|
||||||
|
- **Hot reload** - Volume mounts for live code editing
|
||||||
|
- **Debug tools** - Xdebug configured for debugging
|
||||||
|
- **Email testing** - Mailhog for capturing emails
|
||||||
|
- **Asset watching** - Vite dev server for instant updates
|
||||||
|
|
||||||
|
### Production (`docker-compose.prod.yml`)
|
||||||
|
- **Optimized** - Multi-stage builds with caching
|
||||||
|
- **Secure** - No debug tools, proper permissions
|
||||||
|
- **Health checks** - Automatic container monitoring
|
||||||
|
- **Single container** - FrankenPHP serves everything
|
||||||
|
|
||||||
|
## 🔨 Make Commands Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
make dev # Start development environment
|
||||||
|
make dev-build # Build and start development
|
||||||
|
make dev-stop # Stop development environment
|
||||||
|
make dev-clean # Stop and remove volumes (CAUTION)
|
||||||
|
|
||||||
|
# Production
|
||||||
|
make prod # Start production environment
|
||||||
|
make prod-build # Build production image
|
||||||
|
make prod-stop # Stop production environment
|
||||||
|
make prod-logs # Show production logs
|
||||||
|
|
||||||
|
# Laravel
|
||||||
|
make artisan cmd="..." # Run artisan command
|
||||||
|
make composer cmd="..." # Run composer command
|
||||||
|
make npm cmd="..." # Run npm command
|
||||||
|
make migrate # Run migrations
|
||||||
|
make seed # Seed database
|
||||||
|
make fresh # Fresh migrate and seed
|
||||||
|
make tinker # Start tinker session
|
||||||
|
make test # Run tests
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
make shell # Enter app container
|
||||||
|
make db-shell # Enter database shell
|
||||||
|
make logs # Show app logs
|
||||||
|
make logs-db # Show database logs
|
||||||
|
make clear # Clear all caches
|
||||||
|
make optimize # Optimize for production
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Container won't start
|
||||||
|
```bash
|
||||||
|
# Check logs
|
||||||
|
docker compose logs app
|
||||||
|
|
||||||
|
# Rebuild containers
|
||||||
|
docker compose build --no-cache
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database connection issues
|
||||||
|
```bash
|
||||||
|
# Verify database is running
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
|
# Check database logs
|
||||||
|
docker compose logs db
|
||||||
|
|
||||||
|
# Try manual connection
|
||||||
|
make db-shell
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission issues
|
||||||
|
```bash
|
||||||
|
# Fix storage permissions
|
||||||
|
docker compose exec app chmod -R 777 storage bootstrap/cache
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clear all caches
|
||||||
|
```bash
|
||||||
|
make clear
|
||||||
|
# Or manually
|
||||||
|
docker compose exec app php artisan cache:clear
|
||||||
|
docker compose exec app php artisan config:clear
|
||||||
|
docker compose exec app php artisan route:clear
|
||||||
|
docker compose exec app php artisan view:clear
|
||||||
|
```
|
||||||
|
|
||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
||||||
This project is open-source software licensed under the [MIT license](LICENSE.md).
|
This project is open-source software licensed under the [MIT license](LICENSE.md).
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
||||||
|
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
||||||
|
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
||||||
|
5. Open a Pull Request
|
||||||
|
|
||||||
## 📞 Support
|
## 📞 Support
|
||||||
|
|
||||||
For issues and questions, please use [Codeberg Issues](https://codeberg.org/dish-planner/app/issues).
|
For issues and questions, please use the [GitHub Issues](https://github.com/yourusername/dish-planner/issues) page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Built with ❤️ using Laravel and Livewire
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Models\Planner;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
|
|
||||||
class PurgeDemoAccountsCommand extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'demo:purge';
|
|
||||||
|
|
||||||
protected $description = 'Purge demo accounts older than 24 hours';
|
|
||||||
|
|
||||||
public function handle(): int
|
|
||||||
{
|
|
||||||
if (! is_mode_demo()) {
|
|
||||||
$this->error('This command can only run in demo mode.');
|
|
||||||
|
|
||||||
return self::FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$count = Planner::where('created_at', '<', now()->subHours(24))->delete();
|
|
||||||
|
|
||||||
$this->info("Purged {$count} demo accounts.");
|
|
||||||
|
|
||||||
return self::SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,6 @@ enum AppModeEnum: string
|
||||||
{
|
{
|
||||||
case APP = 'app';
|
case APP = 'app';
|
||||||
case SAAS = 'saas';
|
case SAAS = 'saas';
|
||||||
case DEMO = 'demo';
|
|
||||||
|
|
||||||
public static function current(): self
|
public static function current(): self
|
||||||
{
|
{
|
||||||
|
|
@ -22,19 +21,4 @@ public function isSaas(): bool
|
||||||
{
|
{
|
||||||
return $this === self::SAAS;
|
return $this === self::SAAS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isDemo(): bool
|
|
||||||
{
|
|
||||||
return $this === self::DEMO;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function requiresSubscription(): bool
|
|
||||||
{
|
|
||||||
return $this === self::SAAS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function allowsLogout(): bool
|
|
||||||
{
|
|
||||||
return $this !== self::DEMO;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -34,10 +34,6 @@ public function login(Request $request)
|
||||||
|
|
||||||
public function logout(Request $request)
|
public function logout(Request $request)
|
||||||
{
|
{
|
||||||
if (is_mode_demo()) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth::logout();
|
Auth::logout();
|
||||||
|
|
||||||
$request->session()->invalidate();
|
$request->session()->invalidate();
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use App\Models\Planner;
|
|
||||||
use Closure;
|
|
||||||
use DishPlanner\Planner\Actions\SeedDemoPlannerAction;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
|
|
||||||
class DemoMiddleware
|
|
||||||
{
|
|
||||||
public function handle(Request $request, Closure $next): Response
|
|
||||||
{
|
|
||||||
if (! is_mode_demo()) {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Auth::check()) {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
$planner = DB::transaction(function () {
|
|
||||||
$planner = Planner::create([
|
|
||||||
'name' => 'Demo User',
|
|
||||||
'email' => 'demo-' . Str::uuid() . '@demo.local',
|
|
||||||
'password' => Hash::make(Str::random(32)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
resolve(SeedDemoPlannerAction::class)->execute($planner);
|
|
||||||
|
|
||||||
return $planner;
|
|
||||||
});
|
|
||||||
|
|
||||||
Auth::login($planner);
|
|
||||||
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use App\Enums\AppModeEnum;
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
@ -11,7 +10,7 @@ class RequireSubscription
|
||||||
{
|
{
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
if (! AppModeEnum::current()->requiresSubscription()) {
|
if (is_mode_app()) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
|
@ -13,9 +12,7 @@
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property static PlannerFactory factory($count = null, $state = [])
|
* @property static PlannerFactory factory($count = null, $state = [])
|
||||||
* @property Collection<User> $users
|
|
||||||
* @method static first()
|
* @method static first()
|
||||||
* @method static create(array $array)
|
|
||||||
*/
|
*/
|
||||||
class Planner extends Authenticatable
|
class Planner extends Authenticatable
|
||||||
{
|
{
|
||||||
|
|
@ -37,9 +34,4 @@ public function schedules(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Schedule::class);
|
return $this->hasMany(Schedule::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function users(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(User::class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,3 @@ function is_mode_saas(): bool
|
||||||
return AppModeEnum::current()->isSaas();
|
return AppModeEnum::current()->isSaas();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('is_mode_demo')) {
|
|
||||||
function is_mode_demo(): bool
|
|
||||||
{
|
|
||||||
return AppModeEnum::current()->isDemo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! function_exists('allows_logout')) {
|
|
||||||
function allows_logout(): bool
|
|
||||||
{
|
|
||||||
return AppModeEnum::current()->allowsLogout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
29
bin/build-push.sh
Executable file
29
bin/build-push.sh
Executable file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Build and push production image to Codeberg
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
REGISTRY="codeberg.org"
|
||||||
|
NAMESPACE="lvl0"
|
||||||
|
IMAGE_NAME="dish-planner"
|
||||||
|
TAG="${1:-latest}"
|
||||||
|
|
||||||
|
echo "🔨 Building production image..."
|
||||||
|
podman build -f Dockerfile -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG} .
|
||||||
|
|
||||||
|
echo "📤 Pushing to Codeberg registry..."
|
||||||
|
echo "Please ensure you're logged in to Codeberg:"
|
||||||
|
echo " podman login codeberg.org"
|
||||||
|
|
||||||
|
podman push ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG}
|
||||||
|
|
||||||
|
echo "✅ Done! Image pushed to ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG}"
|
||||||
|
echo ""
|
||||||
|
echo "To deploy in production:"
|
||||||
|
echo "1. Copy docker-compose.prod.yml to your server"
|
||||||
|
echo "2. Set required environment variables:"
|
||||||
|
echo " - APP_KEY (generate with: openssl rand -base64 32)"
|
||||||
|
echo " - APP_URL"
|
||||||
|
echo " - DB_DATABASE, DB_USERNAME, DB_PASSWORD, DB_ROOT_PASSWORD"
|
||||||
|
echo "3. Run: docker-compose -f docker-compose.prod.yml up -d"
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Middleware\DemoMiddleware;
|
|
||||||
use App\Http\Middleware\ForceJsonResponse;
|
use App\Http\Middleware\ForceJsonResponse;
|
||||||
use App\Http\Middleware\RequireSaasMode;
|
use App\Http\Middleware\RequireSaasMode;
|
||||||
use App\Http\Middleware\RequireSubscription;
|
use App\Http\Middleware\RequireSubscription;
|
||||||
|
|
@ -28,7 +27,7 @@
|
||||||
->withMiddleware(function (Middleware $middleware) {
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
// Apply ForceJsonResponse only to API routes
|
// Apply ForceJsonResponse only to API routes
|
||||||
$middleware->api(ForceJsonResponse::class);
|
$middleware->api(ForceJsonResponse::class);
|
||||||
$middleware->web(DemoMiddleware::class);
|
|
||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
'subscription' => RequireSubscription::class,
|
'subscription' => RequireSubscription::class,
|
||||||
'saas' => RequireSaasMode::class,
|
'saas' => RequireSaasMode::class,
|
||||||
|
|
|
||||||
29
build-push.sh
Executable file
29
build-push.sh
Executable file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Build and push production image to Codeberg
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
REGISTRY="codeberg.org"
|
||||||
|
NAMESPACE="lvl0"
|
||||||
|
IMAGE_NAME="dish-planner"
|
||||||
|
TAG="${1:-latest}"
|
||||||
|
|
||||||
|
echo "🔨 Building production image..."
|
||||||
|
podman build -f Dockerfile -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG} .
|
||||||
|
|
||||||
|
echo "📤 Pushing to Codeberg registry..."
|
||||||
|
echo "Please ensure you're logged in to Codeberg:"
|
||||||
|
echo " podman login codeberg.org"
|
||||||
|
|
||||||
|
podman push ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG}
|
||||||
|
|
||||||
|
echo "✅ Done! Image pushed to ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG}"
|
||||||
|
echo ""
|
||||||
|
echo "To deploy in production:"
|
||||||
|
echo "1. Copy docker-compose.prod.yml to your server"
|
||||||
|
echo "2. Set required environment variables:"
|
||||||
|
echo " - APP_KEY (generate with: openssl rand -base64 32)"
|
||||||
|
echo " - APP_URL"
|
||||||
|
echo " - DB_DATABASE, DB_USERNAME, DB_PASSWORD, DB_ROOT_PASSWORD"
|
||||||
|
echo "3. Run: docker-compose -f docker-compose.prod.yml up -d"
|
||||||
|
|
@ -39,7 +39,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'mode' => env('APP_MODE', 'app'),
|
'mode' => env('APP_MODE', 'app'),
|
||||||
'demo_subscribe_url' => env('APP_DEMO_SUBSCRIBE_URL', 'https://dishplanner.app'),
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Production Docker Compose
|
# Production Docker Compose
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: codeberg.org/dish-planner/app:latest
|
image: codeberg.org/lvl0/dish-planner:latest
|
||||||
container_name: dishplanner_app
|
container_name: dishplanner_app
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,6 @@
|
||||||
</head>
|
</head>
|
||||||
<body class="font-sans antialiased bg-gray-600 text-gray-100" x-data="{ mobileMenuOpen: false }">
|
<body class="font-sans antialiased bg-gray-600 text-gray-100" x-data="{ mobileMenuOpen: false }">
|
||||||
<div class="min-h-screen">
|
<div class="min-h-screen">
|
||||||
@if(is_mode_demo())
|
|
||||||
<!-- Demo Banner -->
|
|
||||||
<div class="bg-primary text-white text-center py-2 px-4 text-sm sticky top-0 z-[60]">
|
|
||||||
DEMO MODE.
|
|
||||||
<a href="{{ config('app.demo_subscribe_url') }}" target="_blank" rel="noopener noreferrer" class="underline font-semibold hover:text-gray-200 ml-1">
|
|
||||||
Subscribe for only €1 / month →
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="border-b-2 border-secondary shadow-sm z-50 mb-8 bg-gray-700">
|
<nav class="border-b-2 border-secondary shadow-sm z-50 mb-8 bg-gray-700">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
|
@ -80,14 +70,12 @@ class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-gray-
|
||||||
Billing
|
Billing
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@if(allows_logout())
|
<form method="POST" action="{{ route('logout') }}">
|
||||||
<form method="POST" action="{{ route('logout') }}">
|
@csrf
|
||||||
@csrf
|
<button type="submit" class="block w-full text-left px-4 py-2 text-sm text-gray-100 hover:bg-gray-600 hover:text-accent-blue">
|
||||||
<button type="submit" class="block w-full text-left px-4 py-2 text-sm text-gray-100 hover:bg-gray-600 hover:text-accent-blue">
|
Logout
|
||||||
Logout
|
</button>
|
||||||
</button>
|
</form>
|
||||||
</form>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -162,14 +150,12 @@ class="block text-2xl font-medium {{ request()->routeIs('schedule.*') ? 'text-ac
|
||||||
Billing
|
Billing
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@if(allows_logout())
|
<form method="POST" action="{{ route('logout') }}">
|
||||||
<form method="POST" action="{{ route('logout') }}">
|
@csrf
|
||||||
@csrf
|
<button type="submit" class="text-xl text-danger">
|
||||||
<button type="submit" class="text-xl text-danger">
|
Logout
|
||||||
Logout
|
</button>
|
||||||
</button>
|
</form>
|
||||||
</form>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="space-y-6 text-center">
|
<div class="space-y-6 text-center">
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,7 @@
|
||||||
|
|
||||||
use Illuminate\Foundation\Inspiring;
|
use Illuminate\Foundation\Inspiring;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Schedule;
|
|
||||||
|
|
||||||
Artisan::command('inspire', function () {
|
Artisan::command('inspire', function () {
|
||||||
$this->comment(Inspiring::quote());
|
$this->comment(Inspiring::quote());
|
||||||
})->purpose('Display an inspiring quote')->hourly();
|
})->purpose('Display an inspiring quote')->hourly();
|
||||||
|
|
||||||
Schedule::command('demo:purge')
|
|
||||||
->dailyAt('03:00')
|
|
||||||
->when(fn () => is_mode_demo());
|
|
||||||
|
|
|
||||||
16
shell.nix
16
shell.nix
|
|
@ -86,11 +86,11 @@ pkgs.mkShell {
|
||||||
prod-build() {
|
prod-build() {
|
||||||
local TAG="''${1:-latest}"
|
local TAG="''${1:-latest}"
|
||||||
local REGISTRY="codeberg.org"
|
local REGISTRY="codeberg.org"
|
||||||
local NAMESPACE="dish-planner"
|
local NAMESPACE="lvl0"
|
||||||
local IMAGE_NAME="app"
|
local IMAGE_NAME="dish-planner"
|
||||||
|
|
||||||
echo "🔨 Building production image..."
|
echo "🔨 Building production image..."
|
||||||
podman build --format docker -f Dockerfile -t ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG} .
|
podman build -f Dockerfile -t ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG} .
|
||||||
|
|
||||||
echo "✅ Build complete: ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG}"
|
echo "✅ Build complete: ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG}"
|
||||||
echo "Run 'prod-push' to push to Codeberg"
|
echo "Run 'prod-push' to push to Codeberg"
|
||||||
|
|
@ -99,8 +99,8 @@ pkgs.mkShell {
|
||||||
prod-push() {
|
prod-push() {
|
||||||
local TAG="''${1:-latest}"
|
local TAG="''${1:-latest}"
|
||||||
local REGISTRY="codeberg.org"
|
local REGISTRY="codeberg.org"
|
||||||
local NAMESPACE="dish-planner"
|
local NAMESPACE="lvl0"
|
||||||
local IMAGE_NAME="app"
|
local IMAGE_NAME="dish-planner"
|
||||||
|
|
||||||
echo "📤 Pushing to Codeberg registry..."
|
echo "📤 Pushing to Codeberg registry..."
|
||||||
if podman push ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG}; then
|
if podman push ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG}; then
|
||||||
|
|
@ -115,11 +115,11 @@ pkgs.mkShell {
|
||||||
prod-build-nc() {
|
prod-build-nc() {
|
||||||
local TAG="''${1:-latest}"
|
local TAG="''${1:-latest}"
|
||||||
local REGISTRY="codeberg.org"
|
local REGISTRY="codeberg.org"
|
||||||
local NAMESPACE="dish-planner"
|
local NAMESPACE="lvl0"
|
||||||
local IMAGE_NAME="app"
|
local IMAGE_NAME="dish-planner"
|
||||||
|
|
||||||
echo "🔨 Building production image (no cache)..."
|
echo "🔨 Building production image (no cache)..."
|
||||||
podman build --format docker --no-cache -f Dockerfile -t ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG} .
|
podman build --no-cache -f Dockerfile -t ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG} .
|
||||||
|
|
||||||
echo "✅ Build complete: ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG}"
|
echo "✅ Build complete: ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG}"
|
||||||
echo "Run 'prod-push' to push to Codeberg"
|
echo "Run 'prod-push' to push to Codeberg"
|
||||||
|
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace DishPlanner\Planner\Actions;
|
|
||||||
|
|
||||||
use App\Models\Dish;
|
|
||||||
use App\Models\Planner;
|
|
||||||
use App\Models\User;
|
|
||||||
use DishPlanner\Schedule\Actions\GenerateScheduleForPeriodAction;
|
|
||||||
|
|
||||||
class SeedDemoPlannerAction
|
|
||||||
{
|
|
||||||
private array $dishNames = [
|
|
||||||
'Spaghetti Bolognese',
|
|
||||||
'Chicken Curry',
|
|
||||||
'Caesar Salad',
|
|
||||||
'Beef Stir Fry',
|
|
||||||
'Vegetable Lasagna',
|
|
||||||
'Fish Tacos',
|
|
||||||
'Mushroom Risotto',
|
|
||||||
'BBQ Ribs',
|
|
||||||
'Greek Salad',
|
|
||||||
'Pad Thai',
|
|
||||||
'Margherita Pizza',
|
|
||||||
'Beef Burger',
|
|
||||||
'Chicken Fajitas',
|
|
||||||
'Vegetable Soup',
|
|
||||||
'Salmon Teriyaki',
|
|
||||||
'Lamb Chops',
|
|
||||||
'Shrimp Scampi',
|
|
||||||
'Pulled Pork Sandwich',
|
|
||||||
'Caprese Salad',
|
|
||||||
'Beef Tacos',
|
|
||||||
'Chicken Alfredo',
|
|
||||||
'Vegetable Curry',
|
|
||||||
'Pork Schnitzel',
|
|
||||||
'Tuna Poke Bowl',
|
|
||||||
'Beef Stroganoff',
|
|
||||||
'Chicken Parmesan',
|
|
||||||
'Ratatouille',
|
|
||||||
'Fish and Chips',
|
|
||||||
'Lamb Kebabs',
|
|
||||||
'Shrimp Fried Rice',
|
|
||||||
'Pork Tenderloin',
|
|
||||||
'Nicoise Salad',
|
|
||||||
'Beef Burritos',
|
|
||||||
'Chicken Tikka Masala',
|
|
||||||
'Eggplant Parmesan',
|
|
||||||
'Grilled Salmon',
|
|
||||||
'Lamb Tagine',
|
|
||||||
'Lobster Roll',
|
|
||||||
'Pork Belly',
|
|
||||||
'Waldorf Salad',
|
|
||||||
'Beef Wellington',
|
|
||||||
'Chicken Satay',
|
|
||||||
'Stuffed Peppers',
|
|
||||||
'Miso Glazed Cod',
|
|
||||||
'Lamb Shanks',
|
|
||||||
'Crab Cakes',
|
|
||||||
'Pork Carnitas',
|
|
||||||
'Cobb Salad',
|
|
||||||
'Beef Enchiladas',
|
|
||||||
'Chicken Shawarma',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function execute(Planner $planner): void
|
|
||||||
{
|
|
||||||
$users = $this->createUsers($planner);
|
|
||||||
$this->createDishes($planner, $users);
|
|
||||||
$this->generateSchedule($planner);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createUsers(Planner $planner): array
|
|
||||||
{
|
|
||||||
$names = ['Alice', 'Bob', 'Charlie'];
|
|
||||||
|
|
||||||
return array_map(
|
|
||||||
fn (string $name) => User::create([
|
|
||||||
'planner_id' => $planner->id,
|
|
||||||
'name' => $name,
|
|
||||||
]),
|
|
||||||
$names
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createDishes(Planner $planner, array $users): void
|
|
||||||
{
|
|
||||||
foreach ($this->dishNames as $dishName) {
|
|
||||||
$dish = Dish::create([
|
|
||||||
'planner_id' => $planner->id,
|
|
||||||
'name' => $dishName,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Randomly assign dish to 1-3 users
|
|
||||||
$count = rand(1, count($users));
|
|
||||||
$userIds = collect($users)->random($count)->pluck('id');
|
|
||||||
$dish->users()->attach($userIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function generateSchedule(Planner $planner): void
|
|
||||||
{
|
|
||||||
resolve(GenerateScheduleForPeriodAction::class)->execute($planner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,9 +10,9 @@ class RegenerateScheduleDayAction
|
||||||
{
|
{
|
||||||
public function execute(Planner $planner, Schedule $schedule, bool $overwrite = false): void
|
public function execute(Planner $planner, Schedule $schedule, bool $overwrite = false): void
|
||||||
{
|
{
|
||||||
/** @var RegenerateScheduleDayForUserAction $action */
|
User::all()
|
||||||
$action = resolve(RegenerateScheduleDayForUserAction::class);
|
->each(fn (User $user) => resolve(RegenerateScheduleDayForUserAction::class)
|
||||||
|
->execute($planner, $schedule, $user, $overwrite)
|
||||||
$planner->users->each(fn (User $user) => $action->execute($planner, $schedule, $user, $overwrite));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue