Files
preregister/documentation/DEPLOYMENT-STRATEGY.md
bert.hausmans de83a6fb76 fix: isolate public subscribe from integration job failures
Queue Weeztix/Mailwizz after the HTTP response and catch dispatch errors.
Jobs log Mailwizz/Weeztix API failures without rethrowing so sync driver
and terminating callbacks do not surface 500s after a successful create.

Add JS fallback for non-JSON error responses, deployment note, and a
regression test for failing Mailwizz under QUEUE_CONNECTION=sync.

Made-with: Cursor
2026-04-05 11:47:59 +02:00

552 lines
14 KiB
Markdown

# PreRegister — Development & Deployment Strategy
## Overview
```
┌──────────────┐ git push ┌──────────────┐ deploy.sh ┌──────────────────┐
│ Local Mac │ ──────────────► │ Gitea │ ─────────────► │ VPS DirectAdmin │
│ (Cursor) │ │ (Git remote) │ │ (Production) │
│ │ │ │ │ │
│ Docker: │ │ Tags: │ │ Apache/PHP-FPM │
│ - MySQL │ │ v1.0.0 │ │ MySQL/MariaDB │
│ - phpMyAdmin│ │ v1.0.1 │ │ Node (fnm) │
│ - Mailpit │ │ v1.1.0 │ │ │
└──────────────┘ └──────────────┘ └──────────────────┘
```
---
## 1. Local Development (Mac + Cursor)
### Directory structure
```
~/Projects/preregister/ ← Laravel project root
├── .cursorrules
├── .env ← local dev config (NOT committed)
├── .env.example ← template (committed)
├── .gitignore
├── docker-compose.yml ← local dev services
├── Makefile
├── deploy.sh ← deployment script (committed)
├── PreRegister-Development-Prompt.md
└── ...
```
### Daily workflow
```bash
# Start of day
cd ~/Projects/preregister
docker compose up -d # Start MySQL, phpMyAdmin, Mailpit
make dev # Start Laravel + Vite dev servers
# Open Cursor, code away...
# End of day
git add -A
git commit -m "feat: description of what you built"
git push origin main
```
---
## 2. Git Setup with Gitea
### Initial setup (once)
```bash
cd ~/Projects/preregister
# Initialize git
git init
git branch -M main
# Add your Gitea remote
git remote add origin https://your-gitea-server.nl/bert/preregister.git
# First push
git add -A
git commit -m "feat: initial project setup"
git push -u origin main
```
### .gitignore additions
Make sure these are in `.gitignore` (Laravel ships most of these, but verify):
```gitignore
# Laravel defaults
/vendor/
/node_modules/
/public/build/
/public/hot
/public/storage
/storage/*.key
.env
.env.backup
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
# Docker
mysql_data/
# IDE
.idea/
.vscode/
*.swp
# OS
.DS_Store
Thumbs.db
# Compiled assets are built on the server
/public/build/
```
### .env.example (committed, safe)
```env
APP_NAME=PreRegister
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3307
DB_DATABASE=preregister
DB_USERNAME=preregister
DB_PASSWORD=preregister
QUEUE_CONNECTION=database
MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1026
```
---
## 3. Versioning Strategy
Use **Semantic Versioning** (SemVer): `vMAJOR.MINOR.PATCH`
| Version part | When to bump | Example |
|---|---|---|
| PATCH | Bug fixes, small tweaks | v1.0.0 → v1.0.1 |
| MINOR | New feature, backward compatible | v1.0.1 → v1.1.0 |
| MAJOR | Breaking changes, major rewrite | v1.1.0 → v2.0.0 |
### Creating a release
When a version is ready to deploy:
```bash
# 1. Make sure everything is committed and pushed
git add -A
git commit -m "fix: final changes for v1.0.0"
git push origin main
# 2. Tag the release
git tag -a v1.0.0 -m "v1.0.0 - Initial release with pre-registration pages and Mailwizz sync"
git push origin v1.0.0
```
In Gitea you can also create releases via the web UI (Releases → New Release) which lets you add release notes.
### Branch strategy (keep it simple)
```
main ← production-ready code, always deployable
```
You're the sole developer — one branch is fine. If you ever want to experiment without risk:
```bash
git checkout -b feature/some-experiment
# ... work ...
git checkout main
git merge feature/some-experiment
git branch -d feature/some-experiment
```
---
## 4. VPS Server Setup (one-time)
### 4.1 SSH access
```bash
# From your Mac, set up SSH key access (if not already)
ssh-copy-id user@your-vps.nl
# Test
ssh user@your-vps.nl
```
### 4.2 Install Node.js via fnm
```bash
ssh user@your-vps.nl
# Install fnm
curl -fsSL https://fnm.vercel.app/install | bash
# Add to shell profile (add to ~/.bashrc or ~/.bash_profile)
echo 'eval "$(fnm env)"' >> ~/.bashrc
source ~/.bashrc
# Install Node 20 LTS
fnm install 20
fnm default 20
# Verify
node -v # v20.x.x
npm -v # 10.x.x
```
### 4.3 Install Composer (if not present)
```bash
cd ~
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
composer --version
```
### 4.4 Create the application directory
```bash
# Project lives OUTSIDE public_html
mkdir -p /home/username/preregister
# Symlink public/ to the domain's document root
# Adjust the path to match your DirectAdmin domain structure
ln -sfn /home/username/preregister/public /home/username/domains/preregister.yourdomain.nl/public_html
```
### 4.5 Clone the repository
```bash
cd /home/username
git clone https://your-gitea-server.nl/bert/preregister.git
cd preregister
# Install dependencies
composer install --no-dev --optimize-autoloader
npm ci
# Build frontend assets
npm run build
# Create .env for production
cp .env.example .env
nano .env # Edit with production values (see section 5)
# Generate app key
php artisan key:generate
# Run migrations
php artisan migrate --force
# Storage link
php artisan storage:link
# Cache everything
php artisan config:cache
php artisan route:cache
php artisan view:cache
```
### 4.6 Apache .htaccess
Laravel ships with a `public/.htaccess` that works with Apache. Verify `mod_rewrite` is enabled (it usually is on DirectAdmin).
### 4.7 Set up cron for queue worker + scheduler
Public registration saves the subscriber in the database first, then queues **Weeztix** (coupon code) and **Mailwizz** sync jobs. Those jobs must be processed by a worker.
- **Visitor-facing behaviour:** the public subscribe endpoint returns **HTTP 200 with `success: true`** as soon as the subscriber row is stored. Failures in Mailwizz or Weeztix are **logged** (and visible via failed jobs when using a real queue); they do **not** change the JSON shown to the visitor. Use logs and admin resync to diagnose integration issues.
- **Production:** set `QUEUE_CONNECTION=database` (or `redis`) so retries and `queue:failed` work as intended. `sync` is acceptable for small installs but runs integration work in-process; still, the visitor should not see 5xx from a broken Mailwizz/Weeztix API after subscribe.
- **Queues:** coupon jobs use `weeztix`; Mailwizz uses `mailwizz`. The worker should listen to both (order below prioritises `weeztix` so coupon creation tends to run before sync when both are pending).
In DirectAdmin → Cron Jobs, add:
```
# Laravel scheduler (every minute)
* * * * * cd /home/username/preregister && php artisan schedule:run >> /dev/null 2>&1
# Queue worker - process one job per run (every minute)
* * * * * cd /home/username/preregister && php artisan queue:work --once --queue=weeztix,mailwizz,default >> /dev/null 2>&1
```
For higher throughput, use a persistent supervisor-managed worker instead of `--once` in cron; keep the same `--queue=weeztix,mailwizz,default` order.
### 4.8 Directory permissions
```bash
cd /home/username/preregister
# Storage and cache must be writable by the web server
chmod -R 775 storage bootstrap/cache
chown -R username:username .
# If Apache runs as a different user (e.g., apache, www-data, nobody):
# Check with: ps aux | grep apache
# Then set group accordingly:
# chgrp -R apache storage bootstrap/cache
```
---
## 5. Production .env
```env
APP_NAME=PreRegister
APP_ENV=production
APP_KEY= # generated with php artisan key:generate
APP_DEBUG=false # NEVER true in production
APP_URL=https://preregister.yourdomain.nl
LOG_CHANNEL=daily
LOG_LEVEL=warning
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=preregister_prod
DB_USERNAME=preregister_prod
DB_PASSWORD=STRONG_PASSWORD_HERE
QUEUE_CONNECTION=database
SESSION_DRIVER=database
SESSION_LIFETIME=120
MAIL_MAILER=smtp
MAIL_HOST=your-smtp-server
MAIL_PORT=587
MAIL_USERNAME=your-smtp-user
MAIL_PASSWORD=your-smtp-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="noreply@yourdomain.nl"
MAIL_FROM_NAME="${APP_NAME}"
```
Create the production database via DirectAdmin → MySQL Management:
- Database: `preregister_prod`
- User: `preregister_prod`
- Strong password
---
## 6. Deployment Script
This is the script you run on the VPS to deploy a new version.
Save this as `deploy.sh` in the project root (committed to git):
```bash
#!/bin/bash
set -e
# ──────────────────────────────────────────
# PreRegister Deploy Script
# Run on VPS: ./deploy.sh [tag]
# Examples:
# ./deploy.sh → deploys latest main
# ./deploy.sh v1.2.0 → deploys specific tag
# ──────────────────────────────────────────
APP_DIR="/home/username/preregister"
TAG="${1:-}"
echo "══════════════════════════════════════"
echo " PreRegister — Deploy"
echo "══════════════════════════════════════"
cd "$APP_DIR"
# 1. Maintenance mode
echo "→ Enabling maintenance mode..."
php artisan down --retry=30 || true
# 2. Pull latest code
echo "→ Pulling from Gitea..."
git fetch --all --tags
if [ -n "$TAG" ]; then
echo "→ Checking out tag: $TAG"
git checkout "$TAG"
else
echo "→ Checking out latest main"
git checkout main
git pull origin main
fi
# 3. Install PHP dependencies
echo "→ Installing Composer dependencies..."
composer install --no-dev --optimize-autoloader --no-interaction
# 4. Install Node dependencies and build
echo "→ Installing npm packages..."
npm ci --production=false
echo "→ Building frontend assets..."
npm run build
# 5. Run migrations
echo "→ Running migrations..."
php artisan migrate --force
# 6. Clear and rebuild caches
echo "→ Clearing caches..."
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
# 7. Restart queue (process any pending jobs with new code)
echo "→ Restarting queue workers..."
php artisan queue:restart
# 8. Storage link (idempotent)
php artisan storage:link 2>/dev/null || true
# 9. Disable maintenance mode
echo "→ Going live!"
php artisan up
echo ""
echo "══════════════════════════════════════"
if [ -n "$TAG" ]; then
echo " Deployed: $TAG"
else
echo " Deployed: main (latest)"
fi
echo "══════════════════════════════════════"
```
Make it executable:
```bash
chmod +x deploy.sh
```
---
## 7. Deployment Workflow (day-to-day)
### When a release is ready:
**On your Mac:**
```bash
# 1. Commit and push all changes
git add -A
git commit -m "feat: add subscriber export"
git push origin main
# 2. Tag the release
git tag -a v1.0.0 -m "Initial release"
git push origin v1.0.0
```
**On the VPS (via SSH):**
```bash
ssh user@your-vps.nl
cd /home/username/preregister
# Deploy a specific version
./deploy.sh v1.0.0
# Or deploy latest main (for quick fixes)
./deploy.sh
```
That's it. One command deploys everything: pulls code, installs dependencies, builds assets, runs migrations, clears caches, and brings the site back up.
### Quick reference
```
Local Gitea VPS
───── ───── ───
code in Cursor
git commit + push ───► main branch
git tag v1.x.x ───► tag stored
ssh into VPS
./deploy.sh v1.x.x
git pull + composer + npm + build + migrate
live! ✓
```
---
## 8. Rollback Strategy
If a deploy goes wrong:
```bash
# Deploy the previous known-good tag
./deploy.sh v1.0.0
# If migrations need to be rolled back (careful!)
php artisan migrate:rollback --step=1
```
To see available tags:
```bash
git tag -l --sort=-version:refname
```
---
## 9. File Overview
| File | Committed | Location | Purpose |
|---|---|---|---|
| `.env` | NO | local + VPS | Environment-specific config |
| `.env.example` | YES | repo | Template for .env |
| `.cursorrules` | YES | repo | Cursor AI coding rules |
| `.gitignore` | YES | repo | Files excluded from git |
| `docker-compose.yml` | YES | repo | Local dev services |
| `Makefile` | YES | repo | Local dev shortcuts |
| `deploy.sh` | YES | repo | VPS deployment script |
| `PreRegister-Development-Prompt.md` | YES | repo | Full app specification |
| `public/build/` | NO | built on VPS | Compiled CSS/JS |
| `vendor/` | NO | installed on VPS | PHP dependencies |
| `node_modules/` | NO | installed on VPS | Node dependencies |
---
## 10. Checklist: First Production Deploy
- [ ] VPS: SSH key access configured
- [ ] VPS: PHP 8.2+ selected in DirectAdmin MultiPHP
- [ ] VPS: Node.js 20 installed via fnm
- [ ] VPS: Composer installed globally
- [ ] VPS: Production database created in DirectAdmin
- [ ] VPS: Project directory created, public_html symlinked
- [ ] VPS: Repository cloned from Gitea
- [ ] VPS: `.env` configured with production values
- [ ] VPS: `php artisan key:generate` run
- [ ] VPS: `deploy.sh` path updated (APP_DIR)
- [ ] VPS: First `./deploy.sh` run successfully
- [ ] VPS: Cron jobs added (scheduler + queue worker)
- [ ] VPS: Permissions set on storage/ and bootstrap/cache/
- [ ] VPS: SSL certificate active (Let's Encrypt via DirectAdmin)
- [ ] VPS: Test the public URL in browser
- [ ] VPS: Test login at /admin
- [ ] VPS: Change the default superadmin password