# Deployment Guide — Digital Event Platform

Covers a standard cPanel/shared-hosting install. No Docker, no SSH required on the server.

---

## 1. Host requirements

Check these in cPanel before starting. **Select PHP Version → Extensions**:

**Required** — deployment fails without them:
`pdo_mysql` · `mbstring` · `openssl` · `json` · `fileinfo` · `curl`

**Optional but wanted:**
- `gd` — enables the invitation name overlay. Without it the invitation page falls back to the plain design with the guest name in HTML. Everything else works.
- `zip` — Excel import.

Also needed: PHP 8.2+, cron access, and AutoSSL or a valid certificate.

---

## 2. Build locally, then upload

The server never runs Composer. Build on your machine and upload the result.

```bash
composer install --no-dev --optimize-autoloader
npx tailwindcss -i resources/css/app.css -o public_html/assets/css/app.css --minify
```

Upload the whole tree over FTP or the cPanel File Manager.

---

## 3. Directory layout

**Preferred** — point the domain's document root at `public_html/`, with everything else one level above it:

```
/home/youruser/events-app/
├── app/  config/  database/  routes/  storage/  vendor/  bin/
├── .env
└── public_html/     ← document root
```

In cPanel: **Domains → your domain → Document Root** → set to `events-app/public_html`.

**Fallback** — if your plan won't let you move the document root, put everything inside `public_html`. The `.htaccess` deny rules shipped in `app/`, `config/`, `storage/`, `database/`, `routes/`, `bin/` and `tests/` block direct access. It works, but the first arrangement is meaningfully safer.

---

## 4. Database

1. **cPanel → MySQL Databases** → create a database and a user, grant ALL privileges.
2. **cPanel → phpMyAdmin** → select the database → **Import** → upload `database/schema.sql`.

That file is every migration concatenated in order. If you do have SSH, `php bin/migrate.php` does the same thing and tracks what it applied.

Verify: 24 tables and 36 foreign keys.

---

## 5. Configure `.env`

```bash
cp .env.example .env
php bin/key.php          # paste the output into APP_KEY
chmod 600 .env
```

Fill in `APP_URL`, the four `DB_*` values, and `MAIL_*`. Leave `TRUSTED_PROXIES` empty on standard cPanel — trusting an unverified `X-Forwarded-For` header lets any client forge its own IP past rate limiting.

---

## 6. Permissions

```
Directories  755
Files        644
storage/     775  (recursive)
.env         600
```

---

## 7. Cron jobs

**cPanel → Cron Jobs.** Both are required, not optional.

```
* * * * *    /usr/local/bin/php /home/youruser/events-app/bin/queue-work.php --max-time=55
*/15 * * * * /usr/local/bin/php /home/youruser/events-app/bin/scheduler.php
```

The first drains the message queue. Sending 500 WhatsApp messages inside a web request will hit `max_execution_time` and leave the send half-finished — the queue is what makes bulk sending work at all.

The second handles reminders, QR expiry, upload-window closure and cleanup.

Confirm your host's PHP path under **Select PHP Version**; it is not always `/usr/local/bin/php`.

---

## 8. HTTPS

Enable AutoSSL (**cPanel → SSL/TLS Status**). The shipped `.htaccess` forces HTTPS.

This is functional, not just security hygiene: browser camera access for QR scanning requires a secure context. On plain HTTP the scanner will not open the camera at all.

---

## 9. WhatsApp Business Platform

Start this early — Meta's verification and template approval run on their clock, and can take days.

**What you need from Meta:**

| Value | Where to find it |
|---|---|
| `WHATSAPP_PHONE_NUMBER_ID` | Meta app dashboard → WhatsApp → API Setup |
| `WHATSAPP_BUSINESS_ACCOUNT_ID` | Same page |
| `WHATSAPP_ACCESS_TOKEN` | System user token with `whatsapp_business_messaging` + `whatsapp_business_management` |
| `WHATSAPP_APP_SECRET` | App dashboard → Settings → Basic |
| `WHATSAPP_WEBHOOK_VERIFY_TOKEN` | Any random string you choose; paste the same value into Meta |

**Steps:**

1. Verify your business in Meta Business Manager.
2. Register a phone number that is **not** already on regular WhatsApp.
3. Submit message templates for approval (invitation, RSVP reminder, event reminder).
4. Register the webhook: `https://yourdomain.com/webhooks/whatsapp`, subscribed to `messages`.
5. Leave `WHATSAPP_DRIVER=log` until templates are approved — messages write to `storage/logs` instead of sending, so the whole flow is testable meanwhile. Switch to `meta_cloud` when ready.

Costs are per message by category. Invitations are marketing category; Nigeria is roughly $0.05 each, so 500 invitations is about $26. Messages inside a 24-hour customer service window are free, and that window opens when the *guest* messages *you* — so a "reply YES to confirm" call to action in the invitation makes the follow-ups free.

---

## 10. Object storage for guest media

Guest photos and videos go to Cloudflare R2 (recommended — no egress charges) or Backblaze B2.

1. Create a bucket, e.g. `event-memories`.
2. Create an API token with read/write access. Put the credentials in `S3_KEY` / `S3_SECRET`.
3. Set `S3_ENDPOINT` to `https://<account-id>.r2.cloudflarestorage.com` and `S3_REGION=auto`.
4. Connect a custom domain (e.g. `media.yourdomain.com`) and set it as `S3_PUBLIC_URL`.

**Bucket policy — make only the `public/` prefix world-readable.** Uploads land in `quarantine/`, which must stay private; approved media is copied to `public/`. This split is the moderation control. If the whole bucket is public, an unmoderated upload is live the moment it lands.

**CORS is required** or direct browser uploads fail silently:

```json
[
  {
    "AllowedOrigins": ["https://yourdomain.com"],
    "AllowedMethods": ["PUT", "GET", "HEAD"],
    "AllowedHeaders": ["content-type", "content-length"],
    "ExposeHeaders": ["etag"],
    "MaxAgeSeconds": 3600
  }
]
```

---

## 11. Verify

Visit `https://yourdomain.com/health`.

Every required extension, the database connection and all storage paths should be green. Optional capabilities may show amber — `gd` amber means the name overlay is off; `ghostscript` amber only affects PDF invitation designs.

Then run through: create an event → add a guest → issue an invitation → open the invitation URL → RSVP → check the QR renders → scan it → confirm the dashboard count moves.

---

## 12. Backups

cPanel's automatic backups are not a strategy — they are on the same machine as the thing that fails.

Weekly, at minimum: export the database from phpMyAdmin and store it off-server. Your guest list is the asset here; the code can be redeployed in an hour.

---

## 13. Moving to a VPS later

Nothing in the application changes. Point the document root at `public_html/`, replace the cron queue runner with a supervisor-managed worker, and optionally move application assets to object storage as well. No host-specific paths or shared-host assumptions are baked into the code.
