# Sales Marathon — Full Setup Guide

Everything needed to run **both halves** of the system on a new PC or a
server, starting from a fresh `git clone`.

The product is split into two repositories:

| Part | Repository / folder | What it is |
|---|---|---|
| **Web + API** | `sales-marathon-backend` | Laravel 12 backend, REST API, admin web dashboard, websockets, queues, scheduler |
| **Mobile app** | `sales_marathon_app` | Flutter app for salespeople |

The mobile app is useless on its own — **always set up the backend first**,
because the app needs the API address baked in at build time.

---

## 1. Prerequisites

Install these before cloning anything.

### Required for the backend (web + API)

| Tool | Version | Notes |
|---|---|---|
| PHP | **8.3+** | with `pdo_mysql`, `openssl`, `mbstring`, `curl`, `fileinfo` |
| Composer | 2.x | PHP dependency manager |
| MySQL | **8.0+** | 8.0 is required — the ranking queries use window functions |
| Git | any | |

On Windows, **Laragon** ships PHP + MySQL + Composer together and is the
easiest option. On Ubuntu:

```bash
sudo apt update && sudo apt install -y php8.3 php8.3-cli php8.3-mysql php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip unzip mysql-server composer git
```

### Required for the mobile app

| Tool | Version | Notes |
|---|---|---|
| Flutter SDK | **3.38+** (Dart 3.10+) | |
| Android SDK | API 21+ | comes with Android Studio |
| Java JDK | 17 | needed by the Android Gradle build |

Check everything is healthy:

```bash
flutter doctor
```

### Optional

- **Node.js** — only needed for the `composer dev` shortcut (it uses
  `npx concurrently`). Without Node you can still run the four backend
  processes in separate terminals; see §2.6.
- **Redis** — recommended for queues in production instead of the database
  driver.

---

## 2. Backend setup (web + API)

### 2.1 Clone and install

```bash
git clone <your-backend-repo-url> sales-marathon-backend
```

```bash
cd sales-marathon-backend && composer install
```

### 2.2 Create the environment file

```bash
cp .env.example .env
```

On Windows use `copy .env.example .env` instead. Then generate the app key:

```bash
php artisan key:generate
```

### 2.3 Create the databases

```sql
CREATE DATABASE sales_marathon CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE sales_marathon_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

`sales_marathon_test` is only used by the automated tests, but create it now
so `php artisan test` works later.

### 2.4 Configure `.env`

Open `.env` and set at minimum:

```dotenv
APP_URL=http://127.0.0.1:8000

# IMPORTANT: the timezone admins enter marathon start/end times in.
# If this is wrong, marathons start and finish at the wrong moment.
APP_TIMEZONE=Africa/Mogadishu

DB_DATABASE=sales_marathon
DB_USERNAME=root
DB_PASSWORD=

# Websocket server credentials. Any unique strings work, but the mobile app
# must be built with the SAME REVERB_APP_KEY (see section 3.2).
REVERB_APP_ID=sales-marathon
REVERB_APP_KEY=sales-marathon-key
REVERB_APP_SECRET=sales-marathon-secret
REVERB_HOST="127.0.0.1"
REVERB_PORT=8080
REVERB_SCHEME=http
```

### 2.5 Create the tables and demo users

```bash
php artisan migrate --seed
```

This creates 1 admin + 6 salespeople. **Every seeded account uses the
password `password`:**

There are three roles: **super admin** (manages admin accounts),
**admin** (runs marathons), and **salesperson** (competes).

| Role | Sign in with |
|---|---|
| Super admin | `superadmin@salesmarathon.test` |
| Admin | `admin@salesmarathon.test` |
| Salesperson | `252611111111` (Muna Abdi) |
| Salesperson | `252612222222` (Ikran Farah) |
| Salesperson | `252613333333` (Abdishakur Ali) |
| Salesperson | `252614444444` (Mohamed Hassan) |
| Salesperson | `252615555555` (Usame Yusuf) |
| Salesperson | `252616666666` (Baasto Omar) |
| Salesperson | `252617777777` (Shukri Nuur) |
| Salesperson | `252618888888` (Nafisa Ahmed) |

**Both sign-in screens accept either an email or a mobile number.**
Admins normally use their email, salespeople their number — but the
field takes whichever you have.

> Change these before any real deployment.

### 2.6 Run it — four processes

The system needs **four** processes running at the same time. Each does a
different job, and skipping one silently breaks a feature:

| Process | Command | If you skip it |
|---|---|---|
| Web server | `php artisan serve` | Nothing loads at all |
| Websockets | `php artisan reverb:start` | Leaderboard stops updating live (needs refresh) |
| Queue worker | `php artisan queue:work` | Notifications are never created or sent |
| Scheduler | `php artisan schedule:work` | Marathons never auto-start or auto-finish |

**Option A — one command, no Node needed** (Windows):

In **PowerShell** (note the leading `.\` — PowerShell will not run a script
from the current folder without it):

```bash
.\start-dev
```

In **cmd.exe**, or by double-clicking the file, the prefix is not needed:

```bash
start-dev
```

It opens one window per process and prints both the local address and the
LAN address to use from a phone. `.\stop-dev` closes them all again.

**Option B — one command via Composer** (requires Node.js, because it
shells out to `npx concurrently`):

```bash
composer dev
```

**Option C — four terminals:**

```bash
php artisan serve
```

```bash
php artisan reverb:start
```

```bash
php artisan queue:work
```

```bash
php artisan schedule:work
```

### 2.7 Open the admin dashboard

Go to **http://127.0.0.1:8000** and log in as `admin@salesmarathon.test` /
`password`.

> The web dashboard is **admin-only**. A salesperson who tries to log in here
> is told to use the mobile app instead.

From there you can:

- **Salespeople** — register the people who compete. New accounts can log
  into the mobile app straight away and be added to marathons. Deactivating
  someone blocks their login and hides them from pickers while keeping their
  past results.
- **Marathons** — create a marathon and pick participants with the
  multi-select picker.
- **Live control** — while a marathon is running, **+ Add leads** adds the
  leads a salesperson just won to their running total (this is the everyday
  action). **Correct** is there for fixing a miscount and overwrites the
  total outright. Every change is recorded in the score history.

The leaderboard animates as people overtake each other, live over
websockets — no refresh needed.

### 2.8 Run the backend tests (optional)

```bash
php artisan test
```

120 tests should pass. They use the `sales_marathon_test` database and wipe it
on every run — never point that at real data.

### 2.9 Resetting between manual test runs

After testing by hand, clear the competition data and start fresh:

```bash
php artisan marathons:reset
```

It asks for confirmation, then deletes all marathons, participants, score
history, results, notifications and pending queued jobs — and restarts ids at
1, so your next marathon is `#1` again.

**User accounts are always kept**, so the seeded admin and salespeople still
work and the mobile app stays logged in.

| Flag | Effect |
|---|---|
| `--force` | Skip the confirmation prompt (for scripts) |
| `--device-tokens` | Also remove registered FCM device tokens |
| `--keep-ids` | Leave the id counters where they are |

The command refuses to run when `APP_ENV=production` unless you explicitly
confirm at an interactive prompt.

> Need to wipe the users too and start completely over?
> `php artisan migrate:fresh --seed` rebuilds every table and re-creates the
> demo accounts.

---

## 3. Mobile app setup

### 3.1 Clone and install

```bash
git clone <your-flutter-repo-url> sales_marathon_app
```

```bash
cd sales_marathon_app && flutter pub get
```

### 3.2 Point the app at your backend — the important part

The API address is **compiled into the APK**, so you choose it at build time
with `--dart-define`. There is no settings screen for it.

| Where you run the app | `API_HOST` to use |
|---|---|
| **Physical phone** over Wi-Fi | Your PC's LAN IP — currently the built-in default, so nothing to pass |
| Android **emulator** on the same PC as the backend | `10.0.2.2` |
| Real server | Your server domain or public IP |

Find your PC's LAN IP with `ipconfig` on Windows (look for IPv4 Address) or
`ip addr show` on Linux.

All available defines live in `lib/core/constants/app_config.dart`:
`API_HOST`, `API_PORT`, `REVERB_PORT`, `REVERB_APP_KEY`.

> `REVERB_APP_KEY` must match the backend `.env`. If you changed it there,
> pass it here too.

### 3.3 Run in development

Android emulator:

```bash
flutter run
```

Physical phone:

```bash
flutter run --dart-define=API_HOST=192.168.100.22
```

Log in as any salesperson, e.g. `ahmed@salesmarathon.test` / `password`.

### 3.4 Build a release APK

Android emulator:

```bash
flutter build apk --release
```

Physical phone on your Wi-Fi:

```bash
flutter build apk --release --dart-define=API_HOST=192.168.100.22
```

Pointing at a real server over HTTPS:

```bash
flutter build apk --release --dart-define=API_HOST=api.yourdomain.com --dart-define=API_PORT=443 --dart-define=REVERB_PORT=443
```

The APK lands at `build/app/outputs/flutter-apk/app-release.apk`. Copy it to
the phone and install it — Android will ask you to allow installs from
unknown sources.

---

## 4. Connecting a physical phone to a local backend

This is the step that trips people up most. Three things must all be true.

**1. The backend must listen on all interfaces.** `php artisan serve` binds
to `127.0.0.1` by default, which a phone cannot reach:

```bash
php artisan serve --host=0.0.0.0 --port=8000
```

The websocket server already listens on `0.0.0.0`, so it needs no change.

**2. The phone and PC must be on the same Wi-Fi network.**

**3. The firewall must allow ports 8000 and 8080.** On Windows, in
PowerShell **as Administrator**:

```powershell
New-NetFirewallRule -DisplayName "Sales Marathon HTTP" -Direction Inbound -LocalPort 8000 -Protocol TCP -Action Allow
```

```powershell
New-NetFirewallRule -DisplayName "Sales Marathon WS" -Direction Inbound -LocalPort 8080 -Protocol TCP -Action Allow
```

Quick check: open `http://<your-lan-ip>:8000` in the **phone's browser**. If
the login page appears, the app will connect too.

---

## 5. Push notifications (Firebase / FCM) — optional

The system works fully without Firebase: in-app notifications still appear on
the app's Alerts tab. Firebase only adds **push** notifications that arrive
when the app is closed.

To enable it, configure **both** sides.

### 5.1 Firebase console

1. Create a Firebase project.
2. Add an **Android** app with package name `com.mudnaantech.sales_marathon_app`.
3. Download `google-services.json`.
4. Go to **Project settings → Service accounts → Generate new private key**
   and download that JSON too.

### 5.2 Mobile app side

Put `google-services.json` in `android/app/`, then register the Gradle
plugin:

- `android/settings.gradle.kts` — add to the `plugins` block:
  ```kotlin
  id("com.google.gms.google-services") version "4.4.2" apply false
  ```
- `android/app/build.gradle.kts` — add to the `plugins` block:
  ```kotlin
  id("com.google.gms.google-services")
  ```

Then rebuild the APK.

### 5.3 Backend side

Save the **service account** JSON outside the repo (or at the project root —
it is gitignored) and point `.env` at it:

```dotenv
FIREBASE_CREDENTIALS=C:\secure\firebase-credentials.json
FIREBASE_PROJECT_ID=your-firebase-project-id
```

> Never commit the service-account key. `.gitignore` already blocks
> `firebase-credentials.json` and `*firebase-adminsdk*.json`.

---

## 6. Deploying to a real server (Linux)

### 6.1 Install and configure

```bash
git clone <your-backend-repo-url> /var/www/sales-marathon
```

```bash
cd /var/www/sales-marathon && composer install --no-dev --optimize-autoloader
```

```bash
cp .env.example .env && php artisan key:generate && php artisan migrate --force
```

Production `.env` changes:

```dotenv
APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com

APP_TIMEZONE=Africa/Mogadishu

# Use strong random values — these authorize websocket connections
REVERB_APP_KEY=<random-string>
REVERB_APP_SECRET=<random-string>
REVERB_HOST="yourdomain.com"
REVERB_PORT=443
REVERB_SCHEME=https

# Redis is recommended over the database driver under load
QUEUE_CONNECTION=redis
CACHE_STORE=redis

SANCTUM_STATEFUL_DOMAINS=yourdomain.com
```

Cache the config for speed:

```bash
php artisan config:cache && php artisan route:cache && php artisan view:cache
```

Fix permissions:

```bash
sudo chown -R www-data:www-data storage bootstrap/cache
```

### 6.2 Web server

Point your nginx/Apache document root at the **`public/`** directory — never
at the project root.

### 6.3 Keep the workers alive (Supervisor)

Create `/etc/supervisor/conf.d/sales-marathon.conf`:

```ini
[program:sales-marathon-queue]
command=php /var/www/sales-marathon/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/sales-marathon-queue.log

[program:sales-marathon-reverb]
command=php /var/www/sales-marathon/artisan reverb:start --host=0.0.0.0 --port=8080
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/sales-marathon-reverb.log
```

```bash
sudo supervisorctl reread && sudo supervisorctl update
```

### 6.4 Scheduler (cron)

This is what starts and finishes marathons on time. Add to the crontab:

```cron
* * * * * cd /var/www/sales-marathon && php artisan schedule:run >> /dev/null 2>&1
```

### 6.5 Websockets behind HTTPS

Proxy the websocket paths to the Reverb server on port 8080 so browsers can
use `wss://`. nginx example:

```nginx
location ~ ^/(app|apps) {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $host;
}
```

Then build the mobile app with `--dart-define=REVERB_PORT=443`.

---

## 7. Troubleshooting

| Symptom | Cause and fix |
|---|---|
| Leaderboard only updates after refresh | `php artisan reverb:start` isn't running |
| No notifications appear anywhere | `php artisan queue:work` isn't running |
| Marathon never starts or never ends | `php artisan schedule:work` isn't running (or cron on a server) |
| Marathon starts hours early or late | `APP_TIMEZONE` in `.env` doesn't match the timezone admins type times in |
| App shows "Cannot reach the server" | Wrong `API_HOST` baked into the APK, backend not started with `--host=0.0.0.0`, or the firewall is blocking |
| App connects but the leaderboard is frozen | `REVERB_APP_KEY` in the app build doesn't match the backend `.env` |
| `composer dev` fails | Node.js isn't installed — use `.\start-dev` instead, which needs no Node |
| `start-dev` is "not recognized" in PowerShell | PowerShell will not run a script from the current folder without a path. Use `.\start-dev` |
| Migration fails on window functions | MySQL is older than 8.0 |
| Salesperson can't log into the website | Correct behavior — the web dashboard is admin-only, they use the app |
| Changed `.env` but nothing happened | Run `php artisan config:clear` (or `config:cache` in production) |
| Old test marathons cluttering the app | `php artisan marathons:reset` (see section 2.9) |

---

## 8. Quick reference

Start everything locally (backend):

```bash
composer dev
```

Build the app for a phone on your Wi-Fi:

```bash
flutter build apk --release --dart-define=API_HOST=<your-lan-ip>
```

**Default ports:** `8000` web/API · `8080` websockets · `3306` MySQL

**Logins:** admins use the website, salespeople use the mobile app. All
seeded passwords are `password`.
