# ECStores — Operations Cheatsheet

Quick command reference for day-to-day platform management.  
All commands run from the project root (`d:\#WORK\EC_WebCraft\~PRIVATE_HTML\ecstores`) unless noted.  
Laragon must be running (Apache + MySQL green) before any `php artisan` or `mysql` command.

---

## Table of Contents

1. [Every-Day Artisan Commands](#1-every-day-artisan-commands)
2. [Tenant Operations](#2-tenant-operations)
3. [Wipe Test Data & Start Fresh](#3-wipe-test-data--start-fresh)
4. [Database — Backup & Restore](#4-database--backup--restore)
5. [Logs & Debugging](#5-logs--debugging)
6. [Queue Worker](#6-queue-worker)
7. [Stripe CLI](#7-stripe-cli)
8. [MySQL Quick Reference](#8-mysql-quick-reference)
9. [Deploying New Code (Local → Production)](#9-deploying-new-code-local--production)

---

## 1. Every-Day Artisan Commands

Laravel caches config, routes, and compiled Blade views for speed. That's great in production, but during development it means changes sometimes don't show up until you clear the right cache. These are the commands you'll reach for most often.

```powershell
# YOUR FIRST RESORT when something looks wrong or stale.
# Clears config cache, route cache, view cache, and compiled files all at once.
# Use when: storefront shows old content, a route change isn't being picked up,
# a .env change isn't applying, or you just want a clean slate after a code change.
php artisan optimize:clear

# Clears only the compiled Blade view cache.
# Use when: you edited a .blade.php file but the browser still shows the old version.
# Faster than optimize:clear if you only changed templates.
php artisan view:clear

# Clears only the config cache.
# Use when: you edited .env (added a key, changed a value) but the app still has the old value.
# You MUST run this after every .env change — Laravel reads .env once and caches it.
php artisan config:clear

# Prints every registered route: method, URI, controller, middleware.
# Use when: you're getting a 404 and need to confirm the route is actually registered,
# or when you can't remember the exact URL for a page.
php artisan route:list

# Opens an interactive PHP prompt running inside your Laravel app.
# Use when: you need to run a one-off database query, test a class method,
# create a record manually, or inspect a value — without writing a controller.
# Exit with Ctrl+C or type 'exit'.
php artisan tinker

# Creates the symlink from public/storage → storage/app/public.
# Use when: product images or branding uploads show as broken links.
# Run once per machine after cloning the repo. If you wipe storage/ you'll need to run it again.
php artisan storage:link

# Runs any pending landlord (central DB) migrations.
# Use when: you've written a new migration file (or pulled code that includes one)
# and need to apply the schema change to ecstores_central.
php artisan migrate

# Shows which landlord migrations have run vs. which are pending.
# Use when: you're not sure if a migration ran, or you're debugging a "column not found" error.
php artisan migrate:status
```

---

## 2. Tenant Operations

Because each merchant has their own database, you can't just run `php artisan migrate` to update their schemas — that only touches the landlord DB. These commands let you work across all tenant databases at once, or target one specific store.

> **Shared hosting (production) — tenant database creation:**  
> On inMotion hosting, MySQL cannot auto-create databases. Before provisioning any tenant, Denis must manually create the database in cPanel:  
> 1. cPanel → **MySQL Databases** → Create database named `{slug}` (cPanel prefixes it as `n777909_{slug}`)  
> 2. Add user `n777909_ecstore_usr` to that database with ALL PRIVILEGES  
> The ECStores app will then run migrations into the pre-existing database on provisioning.  
> **Locally (Laragon):** Laragon creates databases automatically — no manual step needed.

```powershell
# Runs pending migrations on ALL tenant databases in one go.
# Use when: you've added a new column or table to the tenant schema
# (e.g. added a new settings field) and need every store updated at once.
# This is the command you run after 'php artisan migrate' on deployment.
php artisan tenants:migrate

# Runs pending migrations on ONE tenant only.
# Use when: you want to test a new migration on one store before rolling it out
# to everyone, or when one specific tenant's DB is behind for some reason.
php artisan tenants:migrate --tenants=acme

# Run any artisan command inside a specific tenant's database context.
# Use when: you need to check migration status, seed data, or run something
# that needs to operate as if that tenant is the active one.
php artisan tenants:run "migrate:status" --tenants=acme

# Provision a new tenant from tinker (alternative to the super-admin wizard).
# Use when: the super-admin UI isn't available, or you want to script provisioning.
php artisan tinker
# Then inside tinker:
app(\App\Services\TenantProvisioningService::class)->provision([
    'name'        => 'New Store',
    'subdomain'   => 'newstore',
    'owner_name'  => 'Jane Smith',
    'owner_email' => 'jane@example.com',
    'password'    => 'temporarypassword',
]);

# List all tenants and their suspension status.
# Use when: you need a quick view of who's provisioned, from the command line.
\App\Models\Tenant::all(['id', 'name', 'suspended_at']);

# Suspend a tenant so their storefront shows "temporarily unavailable".
# Use when: a merchant hasn't paid, you need to take a store offline,
# or a store is being misused. Their admin panel still works so they can log in.
\App\Models\Tenant::find('acme')->suspend();

# Reactivate a suspended tenant — storefront comes back online immediately.
\App\Models\Tenant::find('acme')->reactivate();
```

---

## 3. Wipe Test Data & Start Fresh

You'll use this when you're done testing and want a clean slate before onboarding your first real merchant, or when test data has gotten messy and you want to start over. Pick the option that matches how far back you want to go.

---

### Option A — Full wipe (everything gone, start completely clean)

**When to use:** You want to reset the entire platform as if it was just installed. All tenants, all databases, all data — gone. Use this before you go live with real merchants, or when local test data has gotten too chaotic to work with.

**Step 1 — Drop all databases**

Open PowerShell and connect to MySQL:

```sql
mysql -u root

-- See all tenant databases so you know what to drop
SHOW DATABASES LIKE 'tenant_%';

-- Drop each tenant database (repeat for every tenant_* you see)
DROP DATABASE tenant_acme;
DROP DATABASE tenant_beta;

-- Drop the landlord database
DROP DATABASE ecstores_central;

-- Recreate the empty landlord database
CREATE DATABASE ecstores_central CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

EXIT;
```

**Step 2 — Re-run landlord migrations** (rebuilds the central tables)

```powershell
php artisan migrate
```

**Step 3 — Recreate your super-admin account**

```powershell
php artisan tinker
\App\Models\SuperAdmin::create([
    'name'     => 'Denis Gallant',
    'email'    => 'denis.gallant@gmail.com',
    'password' => 'your-password',
]);
```

> **Do NOT wrap the password in `bcrypt()`** — the model has a `hashed` cast that hashes automatically. Using `bcrypt()` here double-hashes the password and login will fail.

**Step 4 — Provision fresh tenants** via the super-admin panel at `ecstores.test/super`, or via tinker (see Section 2).

---

### Option B — Soft wipe (reset one specific tenant, leave all others alone)

**When to use:** One tenant's data is broken or messy and you want to reset just that store — without touching any others. The tenant gets a brand new database as if they were just provisioned.

```sql
mysql -u root
DROP DATABASE tenant_acme;
EXIT;
```

Then remove the tenant record from the landlord DB so you can re-provision with the same subdomain:

```powershell
php artisan tinker

$t = \App\Models\Tenant::find('acme');
$t->domains()->delete();
$t->delete();
```

Now provision fresh via the super-admin wizard or tinker (Section 2).

---

### Option C — Data-only wipe (clear orders/products/customers, keep store config)

**When to use:** You want to erase test orders, products, and customer accounts but keep the store's settings (Stripe connection, shipping methods, branding, admin login) intact. Useful when you've finished testing a feature and want clean data without having to reconfigure the whole store.

```powershell
php artisan tinker

# This runs inside each tenant's database context
\App\Models\Tenant::all()->each(function ($tenant) {
    $tenant->run(function () {
        \DB::table('orders')->truncate();
        \DB::table('order_items')->truncate();
        \DB::table('order_payments')->truncate();
        \DB::table('wishlists')->truncate();
        \DB::table('products')->truncate();
        \DB::table('product_variant_groups')->truncate();
        \DB::table('product_variant_options')->truncate();
        \DB::table('product_variant_combinations')->truncate();
        \DB::table('product_variant_combination_options')->truncate();
        \DB::table('categories')->truncate();
        \DB::table('users')->truncate();
        \DB::table('user_profiles')->truncate();
    });
});

# Intentionally NOT truncated: admins, site_settings, shipping_methods,
# suppliers — so the store stays configured and usable immediately after.
```

---

## 4. Database — Backup & Restore

**When to back up:**
- Before every deployment to production (in case a migration goes wrong)
- Before running any `migrate` or `tenants:migrate` command on live data
- Before making any manual change to the database
- On a scheduled basis in production (daily at minimum)

### Backup a single tenant database

```powershell
# Replace 'acme' with the tenant slug. Adjust the output path as needed.
mysqldump -u root tenant_acme > "C:\Backups\tenant_acme_2026-05-07.sql"
```

### Backup the landlord database

```powershell
# This contains all tenant records, plans, and subscriptions — back it up separately.
mysqldump -u root ecstores_central > "C:\Backups\ecstores_central_2026-05-07.sql"
```

### Backup ALL databases at once

```powershell
# Quickest option when you want everything backed up before a deployment.
mysqldump -u root --all-databases > "C:\Backups\ecstores_all_2026-05-07.sql"
```

### Restore a database from a backup

```powershell
# Use when: a migration broke something and you need to roll back to a known-good state,
# or when you're restoring a tenant's data after something went wrong.

# Create the database first if it doesn't exist
mysql -u root -e "CREATE DATABASE IF NOT EXISTS tenant_acme CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

# Restore — this overwrites everything in the database with the backup
mysql -u root tenant_acme < "C:\Backups\tenant_acme_2026-05-07.sql"
```

---

## 5. Logs & Debugging

**When to use:** Something is broken — the storefront shows a 500 error, a feature isn't working, or something behaves unexpectedly. The log file is always your first stop. It shows the actual exception and stack trace that Laravel swallowed and replaced with a generic error page.

### View the Laravel log

```powershell
# Show the last 50 lines — good for catching the most recent error
Get-Content storage\logs\laravel.log -Tail 50

# Watch the log live as new entries come in — use while reproducing a bug
# (keep this running in one PowerShell window, trigger the error in the browser)
Get-Content storage\logs\laravel.log -Wait -Tail 20
```

### Clear the log file

```powershell
# Use when: the log has grown huge with old test errors and you want a clean file
# so it's easier to spot new ones.
Clear-Content storage\logs\laravel.log
```

### Laravel Telescope (dev only)

Visit `http://acme.ecstores.test/telescope` in the browser.

Telescope is a visual debugger built into the app. It shows every request, database query, mail message, queued job, and exception in real time — much easier to read than raw log files.

**Use Telescope when:**
- A page is slow — check the Queries tab to see if there's an N+1 problem
- An email isn't sending — check the Mail tab to see if it was triggered
- A job isn't running — check the Jobs tab to see if it was dispatched and what error it threw
- A route is behaving unexpectedly — check Requests to see exactly what was received

Disable in production (it records everything and uses storage):

```
# In .env:
TELESCOPE_ENABLED=false
```

### Check which PHP version is active

```powershell
# Use when: something works locally but not on the server — version mismatch is a common cause.
php -v
```

### Dump a config value from tinker

```powershell
# Use when: you're not sure if .env was read correctly, or which config value is active.
php artisan tinker
config('app.url');
config('tenancy.database.prefix');
```

---

## 6. Queue Worker

A queue worker is a background process that watches for queued jobs and runs them — things like sending order confirmation emails. Without a running worker, queued jobs pile up and never execute.

**In local development:** you can run the worker manually in a second PowerShell window while you're testing mail.  
**In production:** the worker runs as a persistent background process (configured by your host) that restarts automatically if it crashes.

```powershell
# Start the queue worker — it runs continuously, processing jobs as they arrive.
# Use when: you're testing any feature that sends mail or runs a background job.
# Keep this running in a separate terminal window during development.
php artisan queue:work

# Process only one job then stop — useful when debugging a specific failing job
# without running the full worker loop.
php artisan queue:work --once

# Show all jobs that failed (with error message and when they failed).
# Use when: an email wasn't sent or a background task didn't run — check here first.
php artisan queue:failed

# Retry a specific failed job by its ID (shown in queue:failed output).
php artisan queue:retry {job-id}

# Retry ALL failed jobs at once.
# Use when: a temporary error (mail API down, DB timeout) caused a batch of failures
# and you want to re-process everything now that the issue is resolved.
php artisan queue:retry all

# Delete all failed jobs without retrying them.
# Use when: the failures are from test runs or bad test data you don't want to retry.
php artisan queue:flush

# Signal the worker to restart after it finishes its current job.
# Use when: you've deployed new code to production and the worker is still running
# the old version in memory. Run this on the server after every deployment.
php artisan queue:restart
```

---

## 7. Stripe CLI

Stripe sends webhook events (like "payment succeeded" or "subscription renewed") to your app via HTTP. In production, Stripe can reach your server. In local development, your machine isn't publicly accessible, so the Stripe CLI acts as a tunnel — it receives the webhook from Stripe's servers and forwards it to your local URL.

**You need the Stripe CLI running whenever you're testing anything payment-related locally.** Without it, your app never gets the webhook and won't know the payment succeeded.

```powershell
# Forward webhooks to your local landlord webhook endpoint (for Cashier / subscription billing).
# Run this in a separate terminal window whenever testing billing features.
stripe listen --forward-to ecstores.test/stripe/webhook

# Forward webhooks to a specific tenant's endpoint (for Connect / customer payments).
stripe listen --forward-to acme.ecstores.test/stripe/webhook

# Manually trigger a test webhook event — useful for testing your webhook handler
# without having to go through a full checkout or billing flow.
stripe trigger invoice.payment_succeeded   # subscription payment went through
stripe trigger invoice.payment_failed      # subscription payment failed
stripe trigger payment_intent.succeeded    # customer checkout payment succeeded

# Confirm you're logged into the correct Stripe account.
stripe whoami

# Log in (opens a browser window to authenticate).
stripe login
```

**Test cards for checkout:**

| Card number | What it simulates |
|---|---|
| `4242 4242 4242 4242` | Successful payment |
| `4000 0000 0000 0002` | Card declined |
| `4000 0025 0000 3155` | Requires 3D Secure authentication |

Use any future expiry date (e.g. `12/34`) and any 3-digit CVC.

---

## 8. MySQL Quick Reference

Direct database access is useful when you need to check or fix data that the admin UI doesn't expose, reset a password, or inspect what's actually in the database when something isn't adding up.

```sql
-- Connect to MySQL
mysql -u root

-- List all databases (to find which tenant databases exist)
SHOW DATABASES;
SHOW DATABASES LIKE 'tenant_%';

-- Switch to a specific database
USE tenant_acme;

-- List all tables in the current database
SHOW TABLES;

-- See the most recent orders for a tenant
SELECT id, contact_name, total, status, created_at
FROM orders
ORDER BY created_at DESC
LIMIT 20;

-- Check what branding/settings a tenant has saved
SELECT company_name, primary_color, font_heading, button_style, logo_path
FROM site_settings
LIMIT 1;

-- Check the admin account for a tenant (to verify it was provisioned correctly)
SELECT id, name, email, created_at FROM admins;

-- Switch to the landlord DB to check tenant and domain records
USE ecstores_central;
SELECT id, name, suspended_at, created_at FROM tenants;
SELECT domain, tenant_id FROM domains;

-- Reset an admin's password when they're locked out.
-- Step 1: generate the bcrypt hash in tinker first:
--   php artisan tinker → bcrypt('newpassword') → copy the output
-- Step 2: paste the hash here
USE tenant_acme;
UPDATE admins
SET password = '$2y$12$PASTE_HASH_HERE'
WHERE email = 'admin@acme.test';
```

---

## 9. Deploying New Code (Local → Production)

*(inMotion shared hosting via SSH — actual production environment)*

> **Server details:**  
> - SSH: `ssh -p 2222 n777909@eastcoastweb.ca` (port 2222, key auth)  
> - PHP 8.3: `/opt/cpanel/ea-php83/root/usr/bin/php` — use `php83` alias if configured, otherwise use full path  
> - Project root: `/home/n777909/ecstores`  
> - Public directory: `/home/n777909/ecstores/public` (Laragon uses `public/` same structure)

Every time you push a fix or new feature to the live server, run these steps in order. Skipping steps — especially migrations and cache clearing — is the most common cause of post-deployment breakage.

```bash
# 1. SSH into the server
ssh -p 2222 n777909@eastcoastweb.ca

# 2. Navigate to the project directory
cd /home/n777909/ecstores

# 3. Pull the latest code from GitHub
git pull origin main

# 4. Install/update Composer dependencies.
# --no-dev skips dev tools (Telescope, etc.) that shouldn't run in production.
# --optimize-autoloader pre-generates the class map for faster load times.
php83 composer install --no-dev --optimize-autoloader

# 5. Run landlord migrations.
# --force is required in production — Laravel asks for confirmation otherwise.
php83 artisan migrate --force

# 6. Run tenant migrations across ALL stores.
# Run this after step 5 on every deployment that includes a tenant schema change.
php83 artisan tenants:migrate --force

# 7. Clear and rebuild all caches.
# 'optimize' is the production version of 'optimize:clear' — it clears then
# immediately rebuilds the caches so the first request isn't slow.
php83 artisan optimize

# 8. Signal the queue worker to restart (if using queues).
php83 artisan queue:restart
```

### After editing `.env` on the server

```bash
php83 artisan config:clear
php83 artisan config:cache
php83 artisan route:cache
```

### ECW config.php — update after ECStores URL changes

If you change the ECStores domain (e.g. from `eastcoastweb.ca` to `ecstores.ca`), also update `eastcoastwebcraft.ca/includes/config.php`:

```php
define('ECSTORES_API_URL', 'https://ecstores.ca/api/v1');
```

Then upload `config.php` to the ECW server.

### Quick sanity check after deployment

```bash
# Shows app environment, PHP version, key packages, and driver config.
# Good for confirming the deployment went as expected.
php83 artisan about
```

### If something goes wrong after deployment

1. Check the log: `tail -f /home/n777909/ecstores/storage/logs/laravel.log`
2. Roll back the code: `git revert HEAD` or `git reset --hard HEAD~1` then re-run steps 4–7
3. Restore the database from the backup you made before deploying (see Section 4)
4. Full disaster recovery: see `SHARED_HOSTING_DEPLOYMENT.md`

---

*See ADMIN_GUIDE.md for full context on each operation. See SETUP.md for first-time environment setup.*

---

## Changelog

| Date | Change |
|---|---|
| 2026-05-13 | Added shared hosting tenant database note to **Section 2** (cPanel pre-creation requirement, contrast with local auto-creation) |
| 2026-05-13 | Rewrote **Section 9** to reflect actual production environment: inMotion shared hosting, SSH port 2222, `php83` command, correct project path, ECW `config.php` update step, disaster recovery reference |
