# Architecture

## Overview

North West SEO is a **unified Next.js application** with a separate worker
process, backed by PostgreSQL and Redis.

```
┌────────────────────────────┐        ┌──────────────────────────┐
│  Next.js app (port 3000)   │        │  Worker (npm run worker) │
│  • marketing pages         │        │  • BullMQ consumer       │
│  • dashboard (SSR)         │        │  • crawler engine        │
│  • API route handlers      │        │  • issue detection       │
└──────────┬─────────────────┘        └──────────┬───────────────┘
           │  Prisma                              │  Prisma
           ▼                                      ▼
      PostgreSQL  ◄──────────────────────────►  PostgreSQL
           ▲                                      ▲
           │  BullMQ producer          BullMQ consumer
           └──────────────►  Redis  ◄─────────────┘
```

Why unified: one deployable, shared types, server-side rendering of tenant
data with no CORS surface for first-party traffic. The worker is a separate
process so crawls never block web requests. A future split into a dedicated
API service remains possible because all business logic lives in `src/lib`,
not in route files.

## Folder structure

```
northwest-seo/
├── prisma/
│   ├── schema.prisma        # full multi-tenant data model
│   ├── migrations/          # formal migrations
│   └── seed.ts              # removable demo data
├── src/
│   ├── app/                 # Next.js App Router
│   │   ├── page.tsx         # public homepage
│   │   ├── login|register|forgot-password|reset-password|verify-email/
│   │   ├── dashboard/       # authenticated app (noindex)
│   │   └── api/             # route handlers (auth, orgs, websites, crawls, health)
│   ├── components/          # client components (forms, panels)
│   ├── lib/
│   │   ├── auth/            # password, tokens, session, rbac
│   │   ├── crawler/         # url normalization, robots, parse, engine
│   │   ├── security/        # ssrf: assertSafeUrl + safeFetch
│   │   ├── api.ts           # route wrappers, error envelope, tenant guard
│   │   ├── db.ts            # Prisma singleton
│   │   ├── env.ts           # zod-validated environment
│   │   ├── queue.ts         # BullMQ queues
│   │   ├── websites.ts      # tenant-checked website loader
│   │   ├── audit.ts         # audit-log writer
│   │   ├── rate-limit.ts    # fixed-window limiter
│   │   ├── mailer.ts        # mail abstraction (console in dev)
│   │   └── logger.ts        # pino with secret redaction
│   └── workers/index.ts     # worker entry point
├── tests/                   # vitest unit tests (SSRF, robots, RBAC, …)
├── Dockerfile / docker-compose.yml
└── *.md                     # project documentation
```

## Key design decisions (see DECISIONS.md for rationale)

- **Auth**: custom session auth — argon2id password hashes, random 256-bit
  session tokens stored only as SHA-256 hashes, HTTP-only secure cookies.
- **Tenancy**: every org-owned row carries `organisationId`; access flows
  through `requireOrgAccess()` / `loadWebsiteForUser()` which combine
  membership lookup + RBAC check. Routes never query tenant data directly
  without these guards.
- **RBAC**: role → action permission sets in code (`lib/auth/rbac.ts`),
  with a DB `Role`/`Permission` model reserved for future custom roles.
- **Crawler**: BFS engine in the worker; every fetch goes through `safeFetch`
  (SSRF-guarded, redirect-revalidating, byte/time-capped) and respects
  robots.txt and crawl delay. Snapshots are stored per-crawl for comparison.
- **Jobs**: BullMQ with `jobId = crawlId` for idempotency; enqueue has a
  fail-fast timeout so a down Redis produces a visible error, not a hang.
- **AI (future phases)**: provider abstraction will live in `src/lib/ai/`
  with per-organisation budgets recorded in `AIUsage` / `AgentRun` tables
  (schema already in place).

## Request lifecycle (API)

1. `publicRoute` / `protectedRoute` wrapper assigns a request ID.
2. Session cookie → hashed → DB lookup (cached per request).
3. Zod validation of body/query.
4. `requireOrgAccess(auth, orgId, action)` — 404 for non-members (no
   existence leak), 403 for insufficient role.
5. Business logic via `src/lib`.
6. Audit record for important actions.
7. Consistent JSON error envelope `{ error: { code, message }, requestId }`.

## Environments

| | APP_URL | Cookies | Mail |
|---|---|---|---|
| Development | http://localhost:3000 | non-secure | logged to console |
| Staging | https://seo-staging.northwestcar.group | secure | SMTP |
| Production | https://seo.northwestcar.group | secure + domain-scoped | SMTP |

## Future extensions

The schema and module layout already reserve space for phases 3–7 (Search
Console, GA4, AI content, publishing connectors, billing) — see
PRODUCT_SPEC.md §Roadmap. Cross-app North West Car Group integrations will
use signed API credentials (`ApiKey` model), never shared session cookies.
