- RegisterSubscriberOnPage: persist subscriber then dispatch integrations - IssueWeeztixCouponForSubscriber on weeztix queue; dispatches Mailwizz after coupon attempt (idempotent if coupon_code already set); failed() fallback - SyncSubscriberToMailwizz implements ShouldQueueAfterCommit - Deployment: worker listens weeztix,mailwizz,default; warn against sync in prod - .env.example: QUEUE_CONNECTION notes for subscribe UX Made-with: Cursor
14 KiB
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
# 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)
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):
# 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)
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:
# 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:
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
# 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
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)
cd ~
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
composer --version
4.4 Create the application directory
# 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
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.
- Production: set
QUEUE_CONNECTION=database(orredis) — never rely onsync, or a Mailwizz/Weeztix failure can return HTTP 5xx to the visitor while the subscriber is already stored (confusing UX). - Queues: coupon jobs use
weeztix; Mailwizz usesmailwizz. The worker should listen to both (order below prioritisesweeztixso 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
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
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):
#!/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:
chmod +x deploy.sh
7. Deployment Workflow (day-to-day)
When a release is ready:
On your Mac:
# 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):
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:
# 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:
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:
.envconfigured with production values - VPS:
php artisan key:generaterun - VPS:
deploy.shpath updated (APP_DIR) - VPS: First
./deploy.shrun 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