# Deployment — k12-edusol-api

Multi-tenant Laravel (schema-per-tenant via Stancl). This repo is the **API only**;
the React frontend (`/var/www/html/blouza/k12-edusol`) deploys **separately via
Vercel** — see [Frontend](#frontend) below. Deploy the **API first**, then the frontend.

> There is no CI pipeline; deployment is a manual run of the steps below on the
> staging/live server.

## API release steps

Run from the API directory on the target server, on your deploy branch:

```bash
git pull
composer install --no-dev --optimize-autoloader

php artisan down

# 1. Landlord schema migrations (public)  — runs ONLY database/migrations/*
php artisan migrate --path=database/migrations --force

# 2. Tenant schema migrations — runs database/migrations/tenant/* for EVERY tenant
php artisan tenants:migrate --force

# 3. Reseed the landlord add-on catalogue (upserts every row in
#    database/data/landlord/subscription_addons.csv by `code` — safe/idempotent)
php artisan db:seed --class="Database\Seeders\Landlord\SubscriptionAddonSeeder" --force

# 4. Re-apply per-tenant permission + system-role grants from the CSVs
#    (needed whenever permission_modules.csv or role_permissions.csv changed)
php artisan tenants:seed --class="Database\Seeders\Tenant\PermissionSeeder" --force
php artisan tenants:seed --class="Database\Seeders\Tenant\SystemRoleSeeder" --force

# 5. Recache config + routes
php artisan config:cache && php artisan route:cache

php artisan up

# 6. Pick up the new code in long-lived processes
sudo systemctl reload php8.2-fpm   # REQUIRED if OPcache is enabled (git pull alone won't apply new code)
php artisan queue:restart          # if any queue workers / Horizon are running
```

### Staging only

```bash
php artisan users:reset-passwords   # sets every tenant login to password123
```

Refuses on production without `--force` — **do not force it on live.**

## Frontend

Not deployed by the steps above. The frontend auto-deploys on **push to `main`**
(Vercel). Each Vercel environment sets `VITE_API_BASE_URL` (and `VITE_API_URL`) to
the matching API URL — staging FE → staging API, production FE → production API.
Order: ship the API first, then merge/push the frontend to `main`.

## Notes & gotchas

- **Migration split.** Landlord migrations live in `database/migrations/` (run via
  `migrate --path=database/migrations`); tenant migrations live in
  `database/migrations/tenant/` (run via `tenants:migrate`). Keep them separate —
  don't run a bare `php artisan migrate` (it would only do landlord and skip tenants).
- **New tenants are self-provisioning.** `institution:provision` runs the tenant
  migrations + tenant `DatabaseSeeder` (which reads the same permission/role CSVs),
  so schools created *after* a deploy already get the latest schema and grants.
- **`SystemRoleSeeder` is authoritative (`sync`).** It replaces each of the 8 system
  roles' permission sets with the CSV defaults. System roles aren't UI-editable
  (RolePolicy blocks it) and custom tenant roles are untouched, so re-running is
  safe — but it WILL overwrite any system-role grants made directly in the DB.
- **`PermissionSeeder` is a safe no-op** when no new permission *codes* were added;
  keep it in the run so new modules/permissions are always created before grants.
- **Deploying ≠ activating an add-on.** Migrations + the catalogue reseed make an
  add-on (e.g. `hostel-management`, `cbt-pro`, `website-builder`) *available*, not
  *active*. Each school must still be granted it (Blouza super-admin → Institution →
  Add-ons → enable) or subscribe + pay before its menu/feature appears.
- **CBS webhook can't reach a dev/staging box** that isn't publicly routable, so paid
  add-on/plan invoices won't auto-activate there. Use the complimentary enable, or
  reconcile manually: `POST /v1/subscription/payments/{ulid}/sync`.
- **Caching.** Default `CACHE_STORE=file`. If `.env` changed, re-run `config:cache`.
  Don't enable Redis without `php artisan cache:verify --tenant=<slug>` first.

## Rollback (quick)

```bash
php artisan down
git checkout <previous-tag-or-sha>
composer install --no-dev --optimize-autoloader
# reverse only migrations introduced by the bad release, if any:
#   php artisan tenants:rollback --step=N    (tenant)
#   php artisan migrate:rollback --path=database/migrations --step=N   (landlord)
php artisan config:cache && php artisan route:cache
php artisan up
sudo systemctl reload php8.2-fpm
```

Migrations in this codebase are written reversible (`down()`), but **reseeding and
data backfills are not auto-undone** — review the release's migrations before rolling
back tenant schema.
