# 🎵 Virtual Sales Rep

> **The Orchard's B2B physical retail storefront — replatformed.**

[![Jira Epic](https://img.shields.io/badge/Jira-INT--2682-0052CC?logo=jira)](https://theorchard.atlassian.net/browse/INT-2682)
[![Team](https://img.shields.io/badge/Team-Integrations-blueviolet)](https://theorchard.atlassian.net)
[![Stack](https://img.shields.io/badge/Stack-React%2018%20%7C%20GraphQL%20%7C%20Snowflake-informational)](https://theorchard.atlassian.net/browse/INT-2682)

---

## 📖 What is the VSR?

The **Virtual Sales Rep** is The Orchard's online B2B storefront for US Physical Retail accounts. Authorized buyers browse a catalog of US physical products — vinyl, CDs, box sets — build an order, and submit it for pick/pack/ship/billing through Sony Music's **CDS** (Content Delivery System).

The system currently runs on an aging **HCL Domino/Notes** application at `redb2b.com`, generating ~**$8M in annual gross revenue** across ~102 active accounts.

**This repository is the full replatform and rewrite** — replacing Domino with a modern, Orchard-standard stack owned and maintained by the Integrations team.

---

## 🎯 Goals

| # | Goal |
| --- | --- |
| 1 | Replace the Domino app with a **React 18 / TypeScript** frontend on the Orchard suite toolchain |
| 2 | Move all application data (users, carts, orders) into a **`VIRTUALSALESREP` Snowflake database** |
| 3 | Serve catalog data through a **GraphQL API** backed by a local RDS instance, synced to Snowflake via Fivetran |
| 4 | Maintain full integration with **SME CDS** for order fulfillment |
| 5 | Migrate all REDB2B users with **zero data loss** |
| 6 | Execute a phased cutover: parallel run → UAT → DNS redirect |

---

## 🏗️ Tech Stack

| Layer | Technology |
| ----- | ---------- |
| **Frontend** | React 18 + TypeScript · `@theorchard/suite-frontend` |
| **API** | GraphQL · `@theorchard/graphql-server` · `@theorchard/suite-apollo` |
| **Build** | `@theorchard/frontend-cli` (Vite-based) |
| **Lint / Format** | Biome |
| **Testing** | Jest · `@theorchard/jest-config` |
| **Package manager** | pnpm v10 · Node ≥ 24 |
| **Primary DB** | RDS `VIRTUALSALESREP` (fast reads/writes) |
| **Data sync** | Fivetran → `VIRTUALSALESREP` Snowflake |
| **Snowflake** | Live reads + direct mutations via GraphQL |
| **Auth** | `@theorchard/suite-auth` · Orchard Permissions Platform |
| **Fulfillment** | SME CDS (pick / pack / ship / billing) |
| **IaC** | Terraform via `terraform-infra` |

---

## 🗺️ Epic Roadmap

> Full status in [`docs/epic-INT-2682-summary.md`](docs/epic-INT-2682-summary.md) · [Jira board](https://theorchard.atlassian.net/browse/INT-2682)

### ✅ Done

- **INT-2708** — `theorchard/virtual-sales-rep` repo provisioned via Terraform
- **INT-2694** — CDS team consulted; order submission mechanism documented
- **INT-2695** — App scaffold: pnpm monorepo, React + Vite frontend, GraphQL server package, stub server, Docker Compose, Jenkinsfile
- **INT-2696** — Application schema DDL: 4 RDS migrations (users, accounts, carts, orders, notifications), Snowflake views DDL
- **INT-2698** — GraphQL server: schema, resolvers, data sources, auth guard, pricing utils

### ⏳ In Progress

- **INT-2683** — Snowflake `VIRTUALSALESREP` database (PR #32396 merged; `VSR_DEV_SVC` + RSA keys for QA/PROD pending)

### 🔲 Next Up

- **INT-2697** — Authentication (`@theorchard/suite-auth` + Permissions Platform; open question: external B2B user support)
- **INT-2699** — Catalog data access (RDS + Snowflake hybrid; blocked by INT-2683)
- **INT-2700–2706** — Feature modules (sidelined — replacement tickets required)

---

## 🛠️ Development Setup

> **Two modes available**
>
> | Mode | Ports | When to use |
> | --- | --- | --- |
> | **Live data** | frontend :3000, GraphQL server :4001, Postgres :5432 | Default for all development |
> | **Stub / lab** | frontend :3000, mock GraphQL server :4000 | UI-only work, no Snowflake credentials needed |

---

### What you need before starting

| Requirement | Stub mode | Live data mode | How to get it |
| --- | :---: | :---: | --- |
| **Node.js ≥ 24** | ✅ | ✅ | `nvm install 24` |
| **pnpm v10** | ✅ | ✅ | `npm install -g pnpm@10` |
| **Docker Desktop** | ✅ | ✅ | [docker.com/products/docker-desktop](https://www.docker.com/products/docker-desktop/) |
| **GitHub CLI** | ✅ | ✅ | `brew install gh` |
| **`GITHUB_NPM_TOKEN`** (GitHub Classic token, `read:packages` scope, SSO-authorized for `theorchard`) | ✅ | ✅ | See [Step 1b](#step-1b--set-up-github_npm_token) |
| **Orchard VPN** — required to reach Snowflake, internal RDS, and GitHub Packages in some network environments | ✅ | ✅ | Connect via the standard Orchard VPN client before `pnpm install` and before starting the stack |
| **Snowflake access** — `VIRTUALSALESREP` database on `sme-delphi` | — | ✅ | Request from a team member with `SYSADMIN` role |
| **Snowflake RSA key pair** — generated locally, public key registered in Snowflake | — | ✅ | See [Snowflake RSA key setup](#snowflake-rsa-key-setup) |
| **`SNOWFLAKE_USER`** — your Orchard/Sony Music email (`@sonymusic.com`) | — | ✅ | Already have it |

> **Stub mode** requires only the first five rows. If you do not yet have Snowflake access, use stub mode and request access in parallel.
>
> ⚠️ **VPN required.** Connect to the Orchard VPN before running `pnpm install` or starting any service. GitHub Packages (`@theorchard/*` private packages), Snowflake (`sme-delphi`), and any internal RDS instances are all only reachable on VPN.

---

### Prerequisites

| Tool | Version | Install |
| --- | --- | --- |
| Node.js | ≥ 24 | `nvm install 24 && nvm use 24` |
| pnpm | ≥ 10 | `npm install -g pnpm@10` |
| Docker Desktop | latest | [docker.com/products/docker-desktop](https://www.docker.com/products/docker-desktop/) |
| GitHub CLI | latest | `brew install gh` |

**Snowflake access** (required for live data mode):

- Access granted to the `VIRTUALSALESREP` database in Snowflake account `sme-delphi`
- Your Orchard/Sony Music email (`@sonymusic.com`) registered as a Snowflake user in that account
- An RSA key pair generated and the public key registered for your user (see [Snowflake RSA key setup](#snowflake-rsa-key-setup) below)

---

### Step 1 — Clone and configure remotes

This repo uses fork-based development. All PRs come from personal forks; the upstream remote is push-protected.

```bash
# Fork the repo on GitHub first, then clone your fork
git clone git@github.com:<your-github-username>/virtual-sales-rep.git
cd virtual-sales-rep

# Wire up upstream (read-only)
git remote rename origin fork
git remote add upstream git@github.com:theorchard/virtual-sales-rep.git
git remote set-url --push upstream NO_PUSH

# Verify
git remote -v
# fork    git@github.com:<your-username>/virtual-sales-rep.git (fetch)
# fork    git@github.com:<your-username>/virtual-sales-rep.git (push)
# upstream git@github.com:theorchard/virtual-sales-rep.git (fetch)
# upstream NO_PUSH (push)
```

---

### Step 1b — Set up `GITHUB_NPM_TOKEN`

The frontend and GraphQL server both depend on private `@theorchard/*` packages hosted on GitHub Packages. `pnpm install` will fail without a token that has `read:packages` scope authorized for the `theorchard` org.

**Get the token:**

1. GitHub → Settings → Developer Settings → Personal access tokens → Classic
2. Create a token with **`read:packages`** scope
3. Authorize it for the **`theorchard`** organization via SAML SSO (click "Authorize" next to the org after creating the token)

**Wire it in** (add to your `~/.zshrc` or `~/.zprofile`):

```bash
export GITHUB_NPM_TOKEN=ghp_yourTokenHere
source ~/.zshrc  # reload
```

Verify it's set: `echo $GITHUB_NPM_TOKEN` (should print the token prefix, not empty).

The `.npmrc` at the repo root reads this variable automatically — you do not need to edit any project files.

---

### Step 2 — Install dependencies

```bash
pnpm install
```

---

### Step 3 — Start Docker Postgres

The compose file has safe defaults baked in (`vsr_dev_password`), so no env file editing is required for a standard setup. Just start the container and run migrations:

```bash
docker compose -f docker/docker-compose.yml up -d
bash docker/scripts/db-reset.sh
```

Expected output: `✔ Container vsr-postgres Running` followed by migration confirmations.

If you want a custom Postgres password (optional):

```bash
cp docker/.env.shadow docker/.env.docker
# Edit docker/.env.docker → POSTGRES_PASSWORD=your-chosen-password
# Then update DATABASE_URL in packages/vsr-graphql-server/.env.local to match.
```

---

### Step 4 — Set up the frontend environment

```bash
cp apps/vsr/.env.shadow apps/vsr/.env.local
```

The defaults work for local dev (`VITE_DEV_MODE=true` bypasses Auth0). Optionally update `VITE_DEV_USER_EMAIL` to your own email.

---

### Step 5 — Set up the server environment

```bash
cp packages/vsr-graphql-server/.env.shadow packages/vsr-graphql-server/.env.local
```

Edit `packages/vsr-graphql-server/.env.local` and fill in these values:

| Variable | Value |
| --- | --- |
| `DATABASE_URL` | Works as-is if you used the default Postgres password. Update only if you set a custom `POSTGRES_PASSWORD` in `docker/.env.docker`. |
| `SNOWFLAKE_USER` | Your Orchard/Sony Music email (e.g. `jsmith@sonymusic.com`) |
| `SNOWFLAKE_PRIVATE_KEY_PATH` | Path to your RSA `.p8` private key (default: `~/.ssh/snowflake/rsa_key.p8`) |
| `SNOWFLAKE_KEY_PASSPHRASE` | Passphrase for your private key (leave blank if unencrypted) |

The other Snowflake values (`SNOWFLAKE_ACCOUNT`, `SNOWFLAKE_WAREHOUSE`, `SNOWFLAKE_DATABASE`, `SNOWFLAKE_SCHEMA`) are correct as shipped; do not change them for local dev.

---

### Snowflake RSA key setup

Snowflake uses RSA key-pair authentication — no username/password. Each developer needs their own key registered in Snowflake.

**Generate a key pair** (skip if you already have one):

```bash
mkdir -p ~/.ssh/snowflake
# Generate a 2048-bit RSA key (you'll be prompted for a passphrase — remember it)
openssl genrsa -aes256 -out ~/.ssh/snowflake/rsa_key.p8 2048
# Extract the public key
openssl rsa -in ~/.ssh/snowflake/rsa_key.p8 -pubout -out ~/.ssh/snowflake/rsa_key.pub
chmod 600 ~/.ssh/snowflake/rsa_key.p8
```

**Register your public key** — ask a team member with `SYSADMIN` role to run:

```sql
-- Run in Snowflake (sme-delphi account)
ALTER USER "yourname@sonymusic.com"
  SET RSA_PUBLIC_KEY='<paste contents of ~/.ssh/snowflake/rsa_key.pub here>';
```

Strip the `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` header/footer lines before pasting; paste only the base64 body.

**Verify your connection** — start the GraphQL server and watch the startup logs:

```bash
cd packages/vsr-graphql-server && pnpm dev
# Look for: [snowflake] connected to VIRTUALSALESREP.DEV
```

---

### Step 6 — Start the full stack

```bash
./start-dev.sh
```

Select **`2) Full stack`** for live data. The script validates your env files and starts:

- PostgreSQL (Docker, already running from step 3)
- GraphQL server on `:4001`
- Frontend on `:3000`

Open [http://localhost:3000](http://localhost:3000) in your browser.

#### Manual start (alternative)

```bash
# Terminal 1
cd packages/vsr-graphql-server && pnpm dev

# Terminal 2
cd apps/vsr && pnpm dev
```

---

### Stub mode (no Snowflake credentials needed)

For UI-only work with mock data, run the stub server instead:

```bash
./start-dev.sh   # → select option 1 (Stub mode)
```

Or use the full first-time onboarding script (installs all tooling, starts stub mode):

```bash
./onboard.sh
```

---

### Key ports

| Port | Service |
| --- | --- |
| 3000 | VSR frontend (Vite dev server) |
| 4000 | GraphQL stub (mock data — lab only) |
| 4001 | GraphQL server (live Snowflake + RDS) |
| 5432 | PostgreSQL (Docker) |

### Dev mode (`VITE_DEV_MODE=true` / `DEV_MODE=true`)

When both env files have dev mode enabled, Auth0 is bypassed end-to-end. The frontend's `DevModeProvider` injects a mock user identity; the server's `authGuard.ts` reads `x-dev-*` request headers instead of JWT tokens. All code paths are otherwise identical to production.

---

## 🤝 Contributing

This repo uses **fork-based development** with upstream push protection.

Branch pattern: `feature/INT-####-<slug>`  
All PRs start as **drafts** and are marked ready only after CI passes.  
See [`.github/copilot-instructions.md`](.github/copilot-instructions.md) for full agent workflow rules.

---

## 📂 Repository Structure

```text
virtual-sales-rep/
├── .github/
│   ├── copilot-instructions.md   ← AI agent instructions (DSL)
│   ├── instructions/             ← Per-ticket context files
│   ├── prompts/                  ← Agent prompt templates
│   └── skills/                   ← Reusable agent skill packs
├── apps/
│   └── vsr/                      ← React 18 frontend (suite-frontend, Vite)
│       └── src/
│           ├── apollo/           ← Apollo Client setup + .gql queries/mutations
│           ├── components/       ← Shared UI components
│           ├── hooks/            ← Custom React hooks
│           ├── pages/            ← Route-level page components (11 stubs)
│           └── utils/            ← Frontend-only helpers
├── packages/
│   └── vsr-graphql-server/       ← GraphQL API server (Apollo Server 4)
│       └── src/
│           ├── dataSources/      ← SnowflakeDataSource, RdsDataSource, CdsAdapter, db.ts
│           ├── middleware/       ← Auth guard (DEV_MODE passthrough)
│           ├── resolvers/        ← Product, Cart/Order, User resolvers
│           ├── schema/           ← .graphql type definitions
│           └── utils/            ← Pricing computation (from Domino formulas)
├── tools/
│   └── vsr-graphql-stub/         ← Local mock GraphQL server (port 4000, not deployed)
├── infra/
│   └── sql/                      ← RDS DDL migrations + Snowflake view DDL
├── docker/
│   ├── docker-compose.yml        ← Local Postgres 16 (port 5432)
│   └── scripts/db-reset.sh       ← Drop/recreate schema + run all migrations
├── docs/                         ← Project docs (CHANGELOG, epic summary)
├── Jenkinsfile                   ← CI pipeline scaffold
├── biome.json                    ← Linter + formatter config
├── tsconfig.base.json            ← Shared TS compiler settings
├── package.json                  ← pnpm workspace root (apps/*, packages/*)
└── README.md
```

> `research/` — raw meeting notes, recordings, screenshots, reference repos — is gitignored and kept local only.

---

## 🔗 Links

| Resource | Link |
| -------- | ---- |
| Jira Epic | [INT-2682](https://theorchard.atlassian.net/browse/INT-2682) |
| Legacy VSR | [redb2b.com](https://www.redb2b.com/red/redb2b.nsf) |
| Notion Audit | [VSR Replatform UI Audit & Snowflake Plan](https://www.notion.so/VSR-Replatform-UI-Audit-Snowflake-Implementation-Plan-33497177520f815083baf40ac49258f7) |
| application_family | `integrations` |
