# CFL Grant Scout Super Admin — deploy notes for Mark Jones

**App:** Capital Forge League Grant Scout Super Admin  
**Date:** 22 August 2026  
**Status:** Ready to install on the CFL server  
**Suggested live URL:** `https://grant-scout.capitalforgeleague.org`  
(Nested Super Admin. Do not mix this Node app into the existing PHP document root.)

This is a staff-only control panel. It sits **inside** Capital Forge League’s admin world — grant discovery, family-foundation scoring, draft letters, send / hold / contacted, donations, receipts, and sponsorship agreements. It is **not** a public website.

---

## What we built

| Area | What staff can do |
|---|---|
| **Catalog search** | Pulls foundation / opportunity listings. A search is **not** a mailed request. |
| **Family foundations** | Scores each funder against CFL’s mission using 990-PF grant purposes. |
| **Opportunities** | Fit score + Grok “go after?” ranking. |
| **Pipeline** | Identified → **Send** (mails and moves to Contacted) or **Hold**. |
| **What we sent** | Grantor, named contact, amount asked, date sent, latest response, letter + RFP. |
| **Drafts** | Helmet-technology letter and athlete/veteran literacy letter. Redraft with keywords. Send from `grants@`. Bounce if the address is bad. |
| **Donations** | Log a gift, issue receipt, send thank-you. DonorBox webhook at `/api/webhooks/donorbox`. |
| **Agreements** | Sponsorship / sponsored-research agreement templates. |
| **Inbox** | `donations@` and `grants@` correspondence feeding Super Admin. |

Seed data (Florida family foundations, two sample letters) loads on first run so the screens are not empty. **Sending hold** should stay on until Mark lifts it.

---

## Stack (do not try to run this as PHP)

- Node.js **22** (required)
- TanStack Start + Vite 8 + Nitro (SSR)
- Postgres **15+** (production). PGLite is preview-only — do not use it live.
- xAI Grok API for scoring and redraft (`XAI_API_KEY`)

The public CFL site can stay as it is. Put **this** app on its own subdomain and reverse-proxy to the Node process.

---

## 1. Create two mailboxes in cPanel

Create these on `capitalforgeleague.org` (Titan / cPanel Email):

| Address | Purpose |
|---|---|
| `grants@capitalforgeleague.org` | From-address for grant / donor requests |
| `donations@capitalforgeleague.org` | Inbound gifts, receipts, thank-yous |

Forward both into a mailbox you already monitor until IMAP ingest is wired.

---

## 2. Create a Postgres database

cPanel → PostgreSQL Databases (or the existing Postgres on the VPS):

```
Database: cfl_grant_scout
User:     cfl_grant
Host:     127.0.0.1
```

Grant that user ALL on the database. Schema is applied automatically by `npm run db:migrate` (files in `migrations/0002_*.sql` through `0006_*.sql`).

---

## 3. DNS + subdomain

Create **grant-scout.capitalforgeleague.org** → the same server IP as the main site.

Nginx (or Apache proxy) example:

```nginx
server {
    server_name grant-scout.capitalforgeleague.org;
    listen 443 ssl http2;
    # ssl certs via AutoSSL / Let's Encrypt

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
```

**Protect the whole host** with cPanel Directory Privacy or HTTP basic auth until staff login is turned on. Auth is currently **off** in this build (`VITE_AUTH_ENABLED=false`). This panel must not be public.

---

## 4. Install the app

Unpack the zip **outside** `public_html` (example: `/home/cfl/apps/grant-scout`).

```bash
cd /home/cfl/apps/grant-scout
# Node 22
node -v    # must be v22.x

cp .env.example .env
# edit .env — see section 5

npm install --omit=dev
NITRO_PRESET=node-server npm run build:cfl
```

`build:cfl` compiles SSR for a Node server (not Vercel) and runs migrations against `DATABASE_URL`.

Start with PM2 (preferred) or cPanel Node.js Application Manager:

```bash
pm2 start npm --name cfl-grant-scout -- start
# package.json "start" = node .output/server/index.mjs
pm2 save
pm2 startup
```

If using cPanel **Setup Node.js App**:

- Application root: `/home/cfl/apps/grant-scout`
- Application URL: the subdomain
- Application startup file: `.output/server/index.mjs`
- Node version: 22
- Environment variables: paste the `.env` values
- After each code update: `npm run build:cfl` then Restart

The process must listen on `PORT` (default 3000) on localhost. Nitro reads `PORT` and `HOST`.

```
HOST=127.0.0.1
PORT=3000
```

---

## 5. Environment variables

Copy `.env.example` to `.env` on the server. Never commit `.env`.

| Variable | Required | Notes |
|---|---|---|
| `DATABASE_URL` | **Yes** | `postgres://cfl_grant:PASSWORD@127.0.0.1:5432/cfl_grant_scout` |
| `NITRO_PRESET` | **Yes on this server** | `node-server` |
| `PORT` | Yes | `3000` |
| `HOST` | Yes | `127.0.0.1` |
| `XAI_API_KEY` | Yes for AI score / redraft | xAI API key Mark will provide |
| `DONORBOX_WEBHOOK_SECRET` | When DonorBox goes live | HMAC for `/api/webhooks/donorbox` |

Without `DATABASE_URL` the app falls back to an in-memory PGLite file — **do not ship that**.

---

## 6. Confirm it is up

1. Open `https://grant-scout.capitalforgeleague.org` — Dashboard with CFL gold chrome.
2. **Catalog search** — run once; confirm rows appear and the banner says nothing was mailed.
3. **Foundations** — mission-fit scores present.
4. **What we sent** — gold column headers: Grantor, Contact, Amount, Date sent, Response, Actions.
5. **Pipeline** — Send and Hold on Identified cards. Send moves the card to Contacted.
6. Create `grants@` and `donations@` if not already done.
7. Reply to Mark with the live URL.

---

## 7. After go-live (not blocking first deploy)

1. Wire real SMTP so **Send** delivers from `grants@` (today Send records the letter, logs `mail_sends`, and flags bounces; SMTP transport is the remaining hook).
2. IMAP or forward `donations@` / `grants@` into the Inbox screen.
3. Point DonorBox webhook to `https://grant-scout.capitalforgeleague.org/api/webhooks/donorbox`.
4. Turn on staff login (`VITE_AUTH_ENABLED=true`) once Better Auth is configured — until then keep HTTP basic auth on the subdomain.
5. Industry prospect list (NFL / MLB / NHL / NASCAR owners) is a later catalog — do not block this deploy on it.

---

## Do not

- Do not drop this into `public_html` next to WordPress / PHP.
- Do not run `npm run dev` on the live server.
- Do not skip Postgres.
- Do not expose the subdomain without a password.
- Do not auto-mail every identified foundation. Sending hold stays on until Mark says otherwise.

Questions: Mark Jones — ecinemanetworks@gmail.com / mjones@sprowtt.com
