# PRP-006 — Public Tracking

- **Status:** Draft
- **Plan:** Lite
- **Depends on:** PRP-004
- **Estimated effort:** ~0.5 day
- **Pro KB ref:** §42.3 (public tracking)

> **Audience:** junior dev. The public tracking endpoint and `/track` page already exist. This PRP
> **hardens privacy** (no money/PII leakage), adds **basic rate limiting**, and polishes the page
> (Shiprex-branded, language-aware later via PRP-008, footer upgrade CTA + locked SMS toggle).

---

## 1. Goal / Why
End customers track a parcel without an account — the only customer-facing surface and a free brand
touchpoint that reinforces the funnel.

## 2. Scope
**In:** `GET /api/tracking/:reference` returns a minimal safe view; generic 404; basic abuse rate-limit;
public `/track` page polish + locked "Get SMS updates" (Pro) + footer CTA.
**Out (Pro):** SMS/email proactive updates, map view, branded-per-tenant tracking, signature/photo POD.

## 3. Prerequisites
- PRP-004 done (orders + statuses + actions). An order with a known `reference` to test.

## 4. Step-by-step implementation

### Step 1 — Audit the response shape (privacy)
Open **`server/src/modules/tracking/tracking.routes.js`**. Confirm the response includes **only**:
`reference, status, city, receiver_name, timeline[]` (each timeline item: `name, description,
created_at`). It must **NOT** include `cod`, `fees`, full address, phone, `company_id`, or internal ids.
If any leak exists, remove it from the `ok(res, {...})` object.

> Optional privacy hardening (recommended): mask `receiver_name` to first name + initial. Keep simple:
> ```js
> function maskName(n){ const [a, ...r] = String(n||'').trim().split(' '); return r.length ? `${a} ${r[0][0]}.` : a; }
> ```
> Use `receiver_name: maskName(order.receiver_name)`.

### Step 2 — Add a lightweight rate limiter for public routes
Public endpoints can be scraped. Add a tiny in-memory limiter (no new dependency).

Create **`server/src/middleware/rateLimit.js`**:
```js
// Minimal fixed-window limiter keyed by IP+bucket. For a single cPanel Node process this is fine;
// if you later run multiple processes, move to a shared store.
const buckets = new Map();

function rateLimit({ windowMs = 60_000, max = 30, key = 'default' } = {}) {
  return (req, res, next) => {
    const id = `${key}:${req.ip}`;
    const now = Date.now();
    const entry = buckets.get(id) || { count: 0, reset: now + windowMs };
    if (now > entry.reset) { entry.count = 0; entry.reset = now + windowMs; }
    entry.count += 1;
    buckets.set(id, entry);
    if (entry.count > max) {
      return res.status(429).json({ success: false, code: 'RATE_LIMITED', message: 'Too many requests' });
    }
    return next();
  };
}

module.exports = { rateLimit };
```
Apply it in **`tracking.routes.js`**:
```js
const { rateLimit } = require('../../middleware/rateLimit');
router.get('/:reference', rateLimit({ max: 30, key: 'tracking' }), asyncHandler(/* existing */));
```

### Step 3 — Frontend polish (`client/src/pages/Track.tsx`)
- Keep the reference input + result (status badge + timeline).
- Add a **locked "Get SMS updates"** toggle (disabled, PRO badge) and a footer line
  *"Powered by Shiprex — get Pro"* linking to `upgradeUrl` (from `getFeatures()`).
- Ensure all copy uses translation keys (PRP-008 will fill catalogs; use plain strings now if PRP-008
  not yet done, but prefer keys).

### Step 4 — Build & test
```bash
npm run build:client
```
Create an order, advance a couple of statuses, then `GET /api/tracking/<ref>` → confirm timeline and
**no** `cod`/`fees` in the JSON.

---

## 5. API endpoints & Postman documentation

### GET `/api/tracking/:reference` (public, no auth) — hardened
- **Headers:** none. **Rate limit:** 30/min per IP (`429 RATE_LIMITED` when exceeded).
- **200**
  ```json
  { "success": true, "data": {
      "reference": "SX1-00001", "status": "on_route", "city": "Cairo",
      "receiver_name": "Mona M.",
      "timeline": [ { "name": "created", "description": "Order created...", "created_at": "..." },
                    { "name": "status: picked_up", "description": "...", "created_at": "..." } ] } }
  ```
- **404** `{ "success": false, "message": "Order not found" }` (generic — no enumeration hints).

**Postman:** the **Tracking (public)** folder already has `GET /api/tracking/:reference` using
`{{trackingRef}}` (auto-saved when you create an order). Add a **Tests** assertion that money fields are
absent:
```js
const d = pm.response.json().data || {};
pm.test('no money fields leaked', () => {
  pm.expect(d).to.not.have.property('cod');
  pm.expect(d).to.not.have.property('fees');
});
```

---

## 6. Manual test / acceptance verification
```bash
B=http://localhost:3000/api
curl -s $B/tracking/SX1-00001 | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{const o=JSON.parse(d).data||{};console.log('has cod?',('cod' in o),'has fees?',('fees' in o));})"
curl -s -o /dev/null -w "%{http_code}\n" $B/tracking/DOES-NOT-EXIST   # expect 404
```

**Acceptance criteria**
- [ ] Valid reference → status + ordered timeline.
- [ ] Unknown reference → 404 generic.
- [ ] Response has no `cod`/`fees`/phone/address/company_id.
- [ ] >30 requests/min from one IP → `429 RATE_LIMITED`.
- [ ] `/track` page shows locked SMS toggle + upgrade footer.

---

## 7. Git commits (follow in order)
**Commit 1 — rate limiter + tracking privacy hardening**
```bash
git add server/src/middleware/rateLimit.js server/src/modules/tracking/tracking.routes.js
git commit -m "feat(tracking): PRP-006 privacy hardening + rate limit

Adds a minimal in-memory rate limiter and applies it to public tracking;
masks receiver name and confirms no money/PII fields are returned.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
```

**Commit 2 — tracking page polish**
```bash
git add client/src/pages/Track.tsx
git commit -m "feat(web): PRP-006 tracking page Pro teasers + branding

Adds locked 'Get SMS updates' (Pro) and a 'Powered by Shiprex' upgrade footer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
```

**Commit 3 — Postman + PRP status**
```bash
git add PRPs/postman/shiprex-lite.postman_collection.json PRPs/PRP-006-tracking.md PRPs/README.md
git commit -m "docs(prp): PRP-006 postman assertion + mark done"
```

---

## 8. Done checklist
- [x] Rate limiter added + applied; privacy verified.
- [x] Tracking page polished with Pro teasers.
- [x] Postman assertion added; build passes; tests pass.
- [ ] Commits made; PRP marked Done.
