Phase 7+8: logo swap + deploy README

Logo:
- src/components/layout/sidebar.tsx — replaced text wordmark with the Dow
  navbar-logo.png in both desktop (collapsed-aware) and mobile sidebar
  variants. Logo links to /dashboard. Sized at h-7 (28px) — fits the
  400×48 source aspect ratio comfortably.

Deploy docs:
- DEPLOY.md — focused deployment guide for optical-dev.oliver.solutions.
  Highlights the CLAUDE.md shared-server safety rules (compose `name:`
  field + `-p` flag), env var checklist, first-time setup, update flow,
  the seven-step verification list, rollback, and common-issue triage.
  This is the doc you hand a new ops person along with the deploy.sh.
- README.md — top intro rewritten for the actual Dow product (Excel/
  Planner replacement, 11-stage pipeline, OMG + XLSX ingest, per-team
  visibility) instead of the inherited HP CG copy. Points at DEPLOY.md.

Verified: tsc --noEmit ✓ zero errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
DJP 2026-04-20 19:27:31 -04:00
parent 4361d4cd2a
commit a4107ae23d
3 changed files with 237 additions and 17 deletions

206
DEPLOY.md Normal file
View file

@ -0,0 +1,206 @@
# Deploying dow-prod-tracker
Target: **`https://optical-dev.oliver.solutions/dow-prod-tracker`**, hosted on the
shared Oliver Agency dev box alongside `hp-prod-tracker`.
Run the deploy script from the repo root on the server:
```bash
cd /srv/dow-prod-tracker # or wherever you cloned it
./deploy.sh
```
The first run will install Docker, Compose, Apache and UFW if missing, prompt
you to fill in `.env` from `.env.example`, then bring everything up. Subsequent
runs are idempotent — safe to re-run for updates.
---
## Why this deploy is different from the rest
The shared server runs multiple apps from sibling deploy directories. **Two
things must hold or those apps will silently corrupt each other** (this has
bitten us before — see CLAUDE.md):
1. **`docker-compose.yml` MUST pin a top-level `name:` field.**
We pin `name: dow-prod-tracker`. Without this, Compose defaults the project
name to the parent directory name. If two apps both live under `deploy/`,
they collapse onto the same project and fight over containers
(`deploy-db-1`) and volumes (`deploy_pgdata`). Deploying one would silently
evict the other and destroy its data.
2. **Every `docker compose` invocation in `deploy.sh` passes `-p
dow-prod-tracker` as belt-and-braces.**
This is redundant with `name:` today, but if anyone moves the `name:` line
out of the compose file, or runs commands by hand from a different cwd,
the `-p` flag is the safety net.
The deploy script also enforces:
| Concern | Value | Where |
|---|---|---|
| Compose project name | `dow-prod-tracker` | `name:` in `docker-compose.yml` + `-p` in `deploy.sh` |
| App port (host) | `3002` | `docker-compose.yml` ports + `deploy.sh` `APP_PORT` + `apache/dow-prod-tracker.conf` |
| DB port (host) | `5492` | `docker-compose.yml` ports |
| DB name | `dow_prod_tracker` | `docker-compose.yml` env + `.env` `DATABASE_URL` |
| App URL path | `/dow-prod-tracker` | `next.config.ts` `basePath` |
| Apache reverse proxy | `→ 127.0.0.1:3002/dow-prod-tracker` | `apache/dow-prod-tracker.conf` |
If you change any of these, change them in **all** the listed locations or
the deploy will silently fail (or, worse, route Dow traffic to HP's app).
`hp-prod-tracker` runs on **port 3001**. Don't reuse it.
---
## .env.production checklist
Required env vars on the server:
```
DATABASE_URL=postgresql://postgres:<DB_PASSWORD>@db:5432/dow_prod_tracker?schema=public
DB_PASSWORD=<long random>
AUTH_SECRET=<openssl rand -base64 32>
# Auth — local credentials in MVP. Flip to true post-MVP once Entra is wired.
NEXT_PUBLIC_AUTH_ENTRA_ENABLED=false
# Entra (only required when NEXT_PUBLIC_AUTH_ENTRA_ENABLED=true)
AZURE_CLIENT_ID=
AZURE_TENANT_ID=
AZURE_REDIRECT_URI=https://optical-dev.oliver.solutions/dow-prod-tracker/login
# OMG webhook receiver
OMG_WEBHOOK_SECRET=<openssl rand -hex 32> # share with Shashank
OMG_WEBHOOK_ALLOW_INSECURE=false
# AI chat (optional — chat features degrade gracefully if absent)
ANTHROPIC_API_KEY=
OLLAMA_HOST=http://10.24.42.219:11434
OLLAMA_CHAT_MODEL=gemma4:latest
OLLAMA_EMBED_MODEL=nomic-embed-text
# Cron + API key (used for scheduled tasks + machine-to-machine API access)
CRON_SECRET=<openssl rand -hex 32>
API_KEY=<openssl rand -hex 32>
# Initial admin (optional — overrides the default admin@dowjones.com / random pw)
DOW_ADMIN_EMAIL=admin@dowjones.com
DOW_ADMIN_PASSWORD= # leave blank → seed prints a random one
```
---
## First-time setup on a fresh server
```bash
# 1. Clone
sudo mkdir -p /srv && sudo chown $USER:$USER /srv
cd /srv
git clone git@bitbucket.org:zlalani/dow-prod-tracker.git
cd dow-prod-tracker
# 2. Configure
cp .env.example .env
$EDITOR .env # fill in real secrets
# 3. Deploy (installs deps, builds, runs migrations, configures Apache + UFW)
./deploy.sh
# 4. Seed the Dow tenant + initial admin (one-time)
docker compose -p dow-prod-tracker exec app npm run db:seed
# → save the printed admin email + temp password
```
---
## Updating an existing deployment
```bash
cd /srv/dow-prod-tracker
./deploy.sh # pulls, rebuilds, restarts
```
The Prisma migration in `prisma/migrations/20260420000000_init/` runs on
container startup (via the standalone Next.js entrypoint). New migrations land
automatically.
---
## Verification — must pass before declaring deploy done
1. **Volume isolation** — both apps' volumes must be distinct:
```bash
docker volume ls | grep -E "hp-prod-tracker|dow-prod-tracker"
```
Expect to see `hp-prod-tracker_pgdata`, `hp-prod-tracker_uploads_data`,
`dow-prod-tracker_pgdata`, `dow-prod-tracker_uploads_data` — four distinct
volumes.
2. **HP unaffected** — hit HP's URL in a browser and confirm it loads. If it
fails, the new deploy collided with it (don't continue — fix first).
3. **Health check**`curl https://optical-dev.oliver.solutions/dow-prod-tracker/api/health`
should return 200.
4. **Login** — open the URL in a browser, sign in with the seed admin
credentials, get redirected to `/change-password?first=1`, set a real
password, land on `/dashboard`.
5. **XLSX ingest** — upload `Dow Jones_Studio Tracker_Example_*.xlsx` via
`POST /api/projects/bulk-import?commit=false` (curl or via the UI button).
Expect a preview JSON with normalized rows + any row errors. Then commit
with `?commit=true` and spot-check a project in psql:
```bash
docker compose -p dow-prod-tracker exec db \
psql -U postgres -d dow_prod_tracker \
-c "select \"omgJobNumber\", name, status, \"clientTeamId\" from projects limit 5;"
```
6. **OMG webhook** — when Shashank confirms the payload shape, share
`OMG_WEBHOOK_SECRET` and have OMG POST to
`https://optical-dev.oliver.solutions/dow-prod-tracker/api/webhooks/omg`
with header `X-OMG-Signature: sha256=<hex hmac of body>`.
7. **Per-team visibility** — invite a test user as `CLIENT_VIEWER`, assign
them to one client team only (Settings → Client Teams), accept the invite
link, sign in, confirm they only see that team's projects.
---
## Rollback
If a deploy goes wrong:
```bash
cd /srv/dow-prod-tracker
git log --oneline -5 # find previous good commit
git checkout <previous-commit>
./deploy.sh --skip-pull # rebuild from that commit
```
If the database itself is in a bad state (rare — Prisma migrations are
forward-only), restore from the latest postgres dump in `/srv/backups/`
(set up your own cron for these — not yet automated).
---
## Common issues
**"port is already allocated"** — something else is on 3002 or 5492. Check
with `sudo ss -tlnp | grep -E '3002|5492'`. If it's a stale dow-prod-tracker
container from a botched deploy, `docker compose -p dow-prod-tracker down`
will release it.
**Apache returns 502** — the app container isn't running or its health check
is failing. `docker compose -p dow-prod-tracker logs app --tail 50`.
**"Project with that OMG number already exists in a different organization"**
— a previous import landed projects under a different org (e.g. dev seed).
Either delete those projects, or recreate the org with the canonical
`dowjones.com` domain (the seed and webhook both look up the org by domain).
**`/api/webhooks/omg` returns 401** — signature mismatch. Verify both ends
share the exact `OMG_WEBHOOK_SECRET` and that OMG sends the header as
`X-OMG-Signature: sha256=<hex>`. For local stub testing only, set
`OMG_WEBHOOK_ALLOW_INSECURE=true` in `.env` and restart.

View file

@ -1,8 +1,17 @@
# Dow Jones Studio Tracker
A purpose-built production tracking tool for the HP CG department — replacing the fragmented workflow of Workfront, Excel spreadsheets, and the OMG platform with a single, unified web application.
A production tracking tool for the Dow Jones studio team at Oliver Agency —
replacing Excel/Planner with a single web app: per-team-scoped project visibility,
the 11-stage Dow pipeline (Pipeline → New → Copywriter → Client Review → In
Progress Creative → Internal Review → Client Feedback → Final Approval →
Completed + On Hold + Canceled), and ingest from both the OMG webhook and the
Studio Tracker XLSX.
Built for producers and CG artists who need real-time visibility into where every deliverable stands across every stage of the pipeline.
Forked from `hp-prod-tracker`, customized for Dow Jones. See [DEPLOY.md](./DEPLOY.md)
for deployment to `optical-dev.oliver.solutions/dow-prod-tracker`.
Built for producers and creative team members who need real-time visibility into
where every deliverable stands across every stage of the pipeline.
---

View file

@ -1,6 +1,7 @@
"use client";
import Link from "next/link";
import Image from "next/image";
import { usePathname } from "next/navigation";
import {
LayoutDashboard,
@ -175,14 +176,16 @@ export function Sidebar() {
isCollapsed ? "justify-center px-2" : "px-4"
)}>
{!isCollapsed && (
<div className="flex flex-col">
<span className="font-heading text-[11px] font-black tracking-[0.15em] uppercase text-[var(--foreground)]">
Dow Jones Studio
</span>
<span className="text-[9px] font-semibold tracking-[0.12em] uppercase text-[var(--muted-foreground)]">
by Oliver Agency
</span>
</div>
<Link href="/dashboard" className="flex items-center" aria-label="Dow Jones Studio Tracker">
<Image
src={`${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/navbar-logo.png`}
alt="Dow Jones Studio Tracker"
width={400}
height={48}
className="h-7 w-auto"
priority
/>
</Link>
)}
<Button
variant="ghost"
@ -229,13 +232,15 @@ export function MobileSidebar() {
<Sheet open={isMobileOpen} onOpenChange={setMobileOpen}>
<SheetContent side="left" className="w-60 p-0">
<SheetTitle className="sr-only">Navigation</SheetTitle>
<div className="flex h-14 flex-col justify-center border-b px-4">
<span className="font-heading text-[11px] font-black tracking-[0.15em] uppercase text-[var(--foreground)]">
Dow Jones Studio
</span>
<span className="text-[9px] font-semibold tracking-[0.12em] uppercase text-[var(--muted-foreground)]">
by Oliver Agency
</span>
<div className="flex h-14 items-center border-b px-4">
<Image
src={`${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/navbar-logo.png`}
alt="Dow Jones Studio Tracker"
width={400}
height={48}
className="h-7 w-auto"
priority
/>
</div>
<div className="flex h-[calc(100vh-3.5rem)] flex-col">
<NavLinks onNavigate={() => setMobileOpen(false)} />