server/AGENTS.md
# server/AGENTS.md
## Server role
The server is the source of truth for:
- authentication
- usage enforcement
- AI model routing
- audit generation
- caching
- billing integration
- report/share permissions
- audit history persistence
If the client and server disagree, the server wins.
---
## Critical backend files
- `src/server.ts` — route wiring and middleware chain
- `src/middleware/securityMiddleware.ts` — Helmet, CSP nonce, DOMPurify sanitizer, Zod schemas, URL validator
- `src/services/aiProviders.ts` — provider selection, fallback, token budgets
- `src/services/webSearch.ts` — DuckDuckGo HTML + Bing web search scrapers (free, no API keys)
- `src/services/duckDuckGoSearch.ts` — DDG Instant Answer API (knowledge graph)
- `src/services/citationTester.ts` — citation test orchestration (all 3 search engines in parallel)
- `src/services/mentionTracker.ts` — brand mention scanner (15 free public sources, no API keys)
- `src/controllers/mentions.controllers.ts` — mention scan, history, timeline handlers
- `src/routes/mentions.ts` — `/api/mentions` routes (Alignment+ tier gate)
- `src/controllers/competitors.controllers.ts` — competitor CRUD, comparison logic, opportunity detection
- `src/services/postgresql.ts` — migrations and DB bootstrap
---
## Web search verification engines
Citation testing uses 3 free web search engines — no API keys required:
| Engine | Source key | Module | Method |
| --- | --- | --- | --- |
| DuckDuckGo HTML | `ddg_web` | `webSearch.ts` | `checkWebSearchPresence()` |
| Bing HTML | `bing_web` | `webSearch.ts` | `checkBingSearchPresence()` |
| DDG Instant Answer | `ddg_instant` | `duckDuckGoSearch.ts` | `checkDDGPresence()` |
All 3 run in parallel per citation query via `Promise.all` in `citationTester.ts`.
Source union type in `shared/types.ts`: `'ddg_web' | 'bing_web' | 'ddg_instant'`
No paid search APIs. No API keys. Bing and DDG HTML are scraped with rotating browser user agents.
- `src/services/scraper.ts` — crawl/extraction
- `src/services/AnalysisCacheService*` — cache strategy
- `src/services/emailService.ts` — all transactional + broadcast email (Resend API)
- `../shared/types.ts` — canonical cross-layer contract
---
## Security middleware
`src/middleware/securityMiddleware.ts` provides:
- **Helmet** — hardened HTTP headers applied to all responses
- **CSP nonce** — per-request nonce injected into Content-Security-Policy for inline scripts
- **DOMPurify sanitizer** — `sanitizeInput()` strips XSS from user-supplied strings
- **Zod schemas** — `loginSchema`, `registerSchema`, `supportTicketSchema` for request validation
- **URL validator** — `isSafeExternalUrl()` rejects private/loopback/internal targets
- **Bootstrap escaper** — `escapeBootstrapState()` prevents JSON injection in SSR payloads
Wired into `server.ts` (global Helmet + CSP), `authControllerFixed.ts` (Zod + sanitize on login/register), and `supportTicketController.ts` (Zod + sanitize on ticket creation).
All user-facing string inputs on protected routes must pass through `sanitizeInput()` before persistence or rendering.
---
## Competitor tracking
`src/controllers/competitors.controllers.ts` handles:
- CRUD for `competitor_tracking` table
- Comparison logic: dynamic opportunity detection across schema types, content depth, heading structure, FAQ, meta description, canonical tag, viewport meta, page speed
- Advantages calculation filtered to audited competitors only
- Auto-discovery from audit history
- Monitoring toggle and frequency config
Opportunity detection loops all `schema_types[]` dynamically — do not hardcode individual schema checks.
---
## Required route behavior
### Protected audit route order
Preserve this order for audit execution:
```ts
app.post("/api/analyze", authRequired, usageGate, incrementUsage, handler);
Admin endpoints
All admin routes require x-admin-key header validated via requireAdminKey() (timing-safe comparison against ADMIN_KEY env var). Rate limited via adminLimiter (5 req / 30s).
Key admin endpoints:
POST /api/admin/cache/clear— clear analysis cachePOST /api/admin/indexnow/ping— submit URLs to IndexNowPOST /api/admin/verify-user— force-verify a userPOST /api/admin/set-tier— manually set canonical tierPOST /api/admin/newsletter/preview— preview/test newsletter emailGET/POST /api/admin/newsletter/settings— newsletter configPOST /api/admin/newsletter/dispatch— trigger newsletter sendGET /api/admin/newsletter/editions— list newsletter editionsPOST /api/admin/newsletter/editions— create/upsert newsletter editionPOST /api/admin/broadcast/preview— preview/test broadcast emailPOST /api/admin/broadcast/send— send broadcast to eligible usersGET /api/admin/db/stats— database table sizes and row countsPOST /api/admin/db/cleanup— trigger immediate DB cleanupGET /api/admin/health-deep— deep health check (DB, memory, uptime)
Full documentation: docs/ADMIN_PORTAL.md