# AllGifted Math — Laravel API

Backend for the AllGifted Math Flutter app (separate repo).
Hosts at https://mathapi.allgifted.com. SIMBA telco partnership.

Stack: Laravel · MySQL · Apache · DigitalOcean droplet
Auth: Sanctum tokens for the Flutter app (mobile + web)
Auth0 was removed entirely — do not reintroduce.

## Git workflow — read this before any commit

This repo is worked across multiple machines (work laptop, home Windows PC,
and the production server via SSH). Push-rejection / divergence incidents
have happened many times. Rules below prevent recurrence.

**Rule 1: Start every session by checking remote state.**

    git fetch origin
    git status

- "up to date" → safe to work
- "behind" → `git pull` before any local changes
- "ahead" → investigate; probably forgot to push from another machine
- "diverged" → STOP and report to user

**Rule 2: Never commit on the production server.**

Prod SSH sessions are for `git pull`, `.env` edits, cache clears, service
restarts, log reading. Don't `git add` or `git commit` on prod. Production
consumes commits; it does not author them.

**Rule 3: Long work goes on a feature branch, not master.**

For Phase 0, Phase 1A/1B, security hardening, etc., branch first:

    git checkout -b phase-X-description

Push to that branch, merge to master via PR or after explicit approval.

**Rule 4: Push before walking away from a machine.**

If work is unfinished, commit as WIP and push:

    git commit -am "wip: short description"
    git push origin <branch>

**Rule 5: Don't force-push to master.** Ever. Without explicit user
confirmation per push.

## Laravel-specific conventions

**Mail config lives in `.env`, not the `configs` DB table.**

The `configs` table has historical `mail_username` and `mail_from_address`
columns. Both must remain NULL on every environment. SMTP config is read
exclusively from `.env` (`MAIL_USERNAME`, `MAIL_PASSWORD`,
`MAIL_FROM_ADDRESS`). Reason: the configs row silently overrides .env at
runtime; a leaked or stale row caused multi-hour debugging in May 2026.

If the admin UI's SettingsController is asked to update mail config,
display read-only or refuse the write. Mail config is environment-level,
not tenant-level.

**Stripe webhook signature verification is required.**

`StripeWebhookController` must verify the signature header on every
incoming webhook. Don't add bypass paths "for testing." Local testing
uses Stripe CLI (`stripe listen --forward-to localhost:8000/...`) which
provides a valid signing secret.

**Secrets handling — never echo, never commit.**

- `.env`, `.env.save`, `.env.dev`, `.env.example` and any `*.env*` files
  must stay gitignored. The repo previously had `resources/views/admin/
  questions/mathapi11v2/` containing committed env files — do not
  recreate that pattern.
- `APP_KEY` is per-environment (local ≠ prod). Generate independently
  with `php artisan key:generate --force`.
- When rotating credentials, never `echo` or `cat` secret values to
  terminal/logs/chat. Use sed/awk patterns with values piped via stdin
  or env vars.

**No `ShouldBeEncrypted` jobs.**

The codebase has zero `Crypt::encryptString` / `Crypt::decryptString`
usage and no `implements ShouldBeEncrypted` jobs. This is intentional —
it keeps APP_KEY rotation simple (no DB ciphertext, no encrypted queue
payloads to migrate). If a future feature needs encrypted-at-rest data,
flag it explicitly as a constraint on key rotation.

**Sanctum tokens are SHA-256 hashed.**

User auth tokens are key-independent — APP_KEY rotation does not
invalidate them. Mobile users stay logged in across rotations; only
web session cookies invalidate.

## Auth guard pattern in API controllers

This codebase routes API requests through Sanctum. The default web
guard does NOT pick up Bearer-token-authenticated users; you must
specify `'sanctum'` explicitly.

**Standard pattern for API controllers** — set `$this->user` in the
constructor via middleware:

    public function __construct()
    {
        $this->middleware('auth:sanctum');
        $this->middleware(function ($request, $next) {
            $this->user = Auth::guard('sanctum')->user();
            return $next($request);
        });
    }

Then use `$this->user` throughout the controller. Examples in this
codebase: HomeController, LivesController, DiagnosticController,
KiasuController, SubscriptionController.

**FormRequests** can't use that constructor pattern. They must call
`$this->user('sanctum')` explicitly — never bare `$this->user()`.

**Helpers** — when using global helpers in API context:

    auth()->user()                    ❌ — wrong guard
    auth('sanctum')->user()           ✅
    Auth::user()                      ❌
    Auth::guard('sanctum')->user()    ✅

Bug history: Phase 1B (May 2026) shipped `StoreAnswerRequest` +
`API/AnswerController` without following this pattern. Caught by
Stage A smoke test before public traffic. See commit 2daa6c3 → fix
commit for context.

## API contract notes for the Flutter frontend

- **`X-Client-Version`** header uses semver+build format (`1.4.0+2`),
  driven by `package_info_plus`. When parsing for version-gating logic,
  split on `+` and compare only the semver portion.
- **Server-side grading at `/api/answers`** — never trust client-supplied
  `is_correct`. The endpoint grades the answer authoritatively.
- **Per-mode response shapes** — diagnostic mode returns `field_progress`;
  kiasu mode returns cursor advance state; standard mode returns the
  base shape. Don't conflate these.

## Production deploy

    ssh root@mathapi.allgifted.com
    cd /var/www/html/mathapi
    git fetch origin && git pull
    php artisan config:clear && php artisan config:cache
    php artisan route:cache
    php artisan queue:restart
    systemctl restart apache2

Tail `storage/logs/laravel.log` for 30 seconds after restart.

## Current work-in-progress

- **Stage A** (queued for deploy): Phase 0 backend security hardening +
  Phase 1A live-regression fixes + Phase 1B server-side grading
  endpoint (BE1-BE5).
- **Stage B**: Flutter FE2-FE5 (per-tap call to `/api/answers`), gated
  on Stage A landing in production.
- **Stage C**: BE6 (X-Client-Version-gated `correct_answer` strip),
  ships after Flutter clients have adopted the new version format.

## When in doubt

Ask the user before destructive operations: force-push, branch deletion,
git reset --hard, history rewrites, `php artisan migrate:fresh`,
mass file deletions, env file modifications on prod.
