# Contributing to Documentation

> **[Docs Overview](./overview.md)** / Contributing

Guidelines for maintaining and extending the AMSOIL DLP documentation.

---

## Documentation Checklist

When creating or updating documentation, use this soft checklist as a guide. Not every item applies to every document-use judgment.

### Section Landing Pages (`docs/{section}/README.md`)

- [ ] **Breadcrumb** - Link back to `overview.md` at the top
- [ ] **Overview** - 2-3 sentence description of what this section covers
- [ ] **Mermaid diagram** - Visual aid if it helps understanding (flowchart, ER diagram, sequence)
- [ ] **Documentation table** - Links to related files in this section
- [ ] **Key Concepts** - Define important terms or patterns
- [ ] **Key Files table** - Map documentation to actual code locations
- [ ] **Common Tasks** - Quick answers to frequent questions
- [ ] **See Also** - Cross-links to related sections
- [ ] **Troubleshooting** - Include if failure modes are non-obvious

### Individual Documentation Files

- [ ] **Purpose clear in first paragraph** - Reader should know if this doc is relevant within 10 seconds
- [ ] **Code examples** - Include runnable examples where applicable
- [ ] **File paths accurate** - Verify paths still exist in codebase
- [ ] **No dead links** - Check all internal and external links work

---

## File Organization

```
docs/
├── overview.md              # Central index (start here)
├── CONTRIBUTING.md          # This file
│
├── auth/README.md           # Section landing pages
├── admin/README.md
├── analytics/README.md
├── billing/README.md
├── db/README.md
├── editors/README.md
├── email/README.md
├── subdomains/README.md
│
├── *.md                     # Individual topic docs
│
├── archive/                 # Superseded/historical docs
└── plans/                   # Active design plans
```

---

## When to Archive vs Delete

| Situation                                | Action                                    |
| ---------------------------------------- | ----------------------------------------- |
| Doc is superseded by newer doc           | Archive with note pointing to replacement |
| Doc describes removed feature            | Archive for historical reference          |
| Doc was never completed/draft            | Delete                                    |
| Doc is duplicate of another              | Delete, keep the better version           |
| Doc has useful historical context        | Archive                                   |
| Doc is completely outdated with no value | Delete                                    |

**To archive:** Move to `docs/archive/` with a note at the top explaining why.

**To delete:** Remove the file and update any links pointing to it.

---

## Style Guidelines

### Tone

- **Direct and practical** - Get to the point
- **Task-oriented** - Focus on what the reader needs to do
- **No fluff** - Skip unnecessary introductions

### Formatting

- Use tables for structured data (file lists, API endpoints, feature matrices)
- Use Mermaid diagrams for flows and relationships
- Use code blocks with language hints (`typescript, `bash)
- Use blockquotes for tips and warnings

### Naming

- Use `SCREAMING_SNAKE_CASE.md` for top-level docs (matches existing convention)
- Use `README.md` for section landing pages
- Be descriptive: `APPLE_OAUTH_MAINTENANCE.md` not `APPLE.md`

---

## Maintenance

### When Modifying Code

If your code change affects documented behavior:

1. Search docs for references to the file/function you changed
2. Update any affected documentation
3. Include doc updates in the same PR as code changes

### Periodic Review

- Run the documentation audit quarterly (see [DOCUMENTATION_AUDIT.md](./DOCUMENTATION_AUDIT.md))
- Check for stale file paths and dead links
- Archive docs for removed features

---

## Adding a New Section

1. Create `docs/{section}/README.md` following the checklist above
2. Add the section to the table in `docs/overview.md`
3. Add the section to the main `README.md` Developer Docs table
4. Add cross-links in related section READMEs under "See Also"

---

## Common Recipes

Walkthroughs for frequent contributor tasks. Each guide covers a single end-to-end change - pick the one that matches what you're building.

| Recipe                                                       | When to use                                                       |
| ------------------------------------------------------------ | ----------------------------------------------------------------- |
| [Add a Puck component](./editors/ADDING_A_PUCK_COMPONENT.md) | Adding a new draggable block to the CMS page editor               |
| [Add an email template](./email/ADDING_AN_EMAIL_TEMPLATE.md) | Adding or modifying a transactional email                         |
| [Add a page type](./ADDING_A_PAGE_TYPE.md)                   | Extending the CMS with a new page kind (beyond `page` and `blog`) |

### Adding an API Route (minimal recipe)

Almost every authenticated API route in this codebase follows the same shape:
session check → input validation → dealer lookup → tier gate (if relevant) → mutation → response.

**Representative routes to read first:**

| Route file                                  | Notes                                                                    |
| ------------------------------------------- | ------------------------------------------------------------------------ |
| `app/api/dealer/update/route.ts`            | Dealer-owned record: session → sanitize → validate → Prisma update       |
| `app/api/cms/pages/route.ts`                | Tier-gated CRUD: uses `canCreateCMSPages()` and Zod (`CreatePageSchema`) |
| `app/api/admin/dealers/[id]/notes/route.ts` | Admin route using `requireAdmin()` from `lib/admin-auth.ts`              |

**Canonical shape:**

```typescript
// app/api/example/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { ZodError, z } from 'zod';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { logger } from '@/lib/logger';
import { canCreateCMSPages } from '@/lib/cms/types'; // tier gate helper

const BodySchema = z.object({
  title: z.string().min(1).max(200),
});

export async function POST(request: NextRequest) {
  // 1. Auth: NextAuth session (for admin routes use `requireAdmin()` from lib/admin-auth.ts)
  const session = await getServerSession(authOptions);
  if (!session?.user?.id) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // 2. Validate input (reject early, before any DB work)
  let body;
  try {
    body = BodySchema.parse(await request.json());
  } catch (err) {
    if (err instanceof ZodError) {
      return NextResponse.json(
        { error: 'Validation failed', fieldErrors: err.flatten().fieldErrors },
        { status: 400 }
      );
    }
    return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });
  }

  // 3. Resolve dealer by userId (implicit ownership check)
  const dealer = await prisma.dealer.findUnique({
    where: { userId: session.user.id },
    select: { id: true, subscriptionTier: true },
  });
  if (!dealer) return NextResponse.json({ error: 'Dealer not found' }, { status: 404 });

  // 4. Tier gate using helpers from lib/cms/types.ts or lib/plan-features.ts
  if (!canCreateCMSPages(dealer.subscriptionTier)) {
    return NextResponse.json({ error: 'Professional tier required' }, { status: 403 });
  }

  // 5. Mutation
  try {
    const result = await prisma.page.create({
      data: { dealerId: dealer.id, title: body.title, slug: 'example', type: 'page' },
    });
    return NextResponse.json({ page: result }, { status: 201 });
  } catch (err) {
    logger.error({ err }, '[Example] Create failed');
    return NextResponse.json({ error: 'Failed to create' }, { status: 500 });
  }
}
```

**Notes:**

- **Auth:** `getServerSession(authOptions)` for dealer-owned routes; `requireAdmin()` / `requireAdminOrImpersonation()` / `requireSuperAdmin()` for admin routes (see `lib/admin-auth.ts`).
- **Validation:** Prefer Zod schemas. For dealer-info fields that also need XSS sanitization, see `sanitizeDealerData()` + `validateDealerUpdate()` in `lib/dealer-validation.ts`.
- **Ownership:** Resolve the dealer via `where: { userId: session.user.id }` - this implicitly enforces ownership; no separate check needed.
- **Tier gating:** Use the pre-built helpers - don't inline tier arrays. Feature-to-helper map:
  - `canViewCMSPages`, `canCreateCMSPages`, `canEditCMSPage` (`lib/cms/types.ts`)
  - `hasSliderEditorAccess`, `hasNavEditorAccess`, `hasLeadFormAccess`, `hasLinkEditorAccess`, `hasMediaLibraryAccess`, `hasCustomDomainAccess` (`lib/cms/types.ts`)
  - `getTierRank`, `isUpgrade`, `isDowngrade`, `PLAN_FEATURES` (`lib/plan-features.ts`)
- **Rate limiting:** Handled at the Cloudflare edge - see [RATE_LIMITING.md](./RATE_LIMITING.md). Do **not** add Redis/Upstash rate limiting to route handlers.
- **Logging:** Use `logger` from `@/lib/logger` (or the specialized `authLogger`). Never `console.log`.
- **After-write hooks:** Dealer content mutations should call `triggerISRRevalidation(subdomain)` from `lib/isr-revalidation.ts` when public-facing data changes. See `app/api/dealer/update/route.ts` for the full pattern, including OG-cache invalidation.
- **Impersonation logging:** Admin actions taken while impersonating a dealer must call `logImpersonationAction()` (see `lib/admin-auth.ts`).

**Deeper docs:**

- [ARCHITECTURE.md](./ARCHITECTURE.md) - request lifecycle
- [auth/README.md](./auth/README.md) - NextAuth config, session shape
- [PRISMA_JSON_SCHEMAS.md](./PRISMA_JSON_SCHEMAS.md) - JSON field conventions
- [RATE_LIMITING.md](./RATE_LIMITING.md) - why there's no app-level rate limiter
