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
552 lines
14 KiB
Markdown
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 |