# Pulse — Repo Guide for Claude Pulse is an internal PSA management dashboard for Wulf Consulting. It syncs Autotask data into Postgres and adds dashboards, workflows, and analytics around it. Single Next.js 16 app — not a monorepo. `README.md` covers the human-facing overview. **Trust this file** for the details that matter to coding decisions. ## Stack - Next.js 16 + React 19 (App Router, `reactCompiler: true`, `output: 'standalone'`) - TypeScript strict, path alias `@/*` - Postgres 16 via `pg` (no ORM), Redis (caching), Better Auth 1.4 - Tailwind 4, shadcn/ui (`components/ui/`), recharts, sonner, lucide - Forms: react-hook-form + Zod resolver — admin/auth forms only - Runs on **port 3100** (Docker exposes 3100; `BETTER_AUTH_URL=http://localhost:3100`) ## Layout - `app/` — App Router pages + `app/api/**/route.ts` handlers - `lib/services/` — integration clients, sync services, scheduler (~50 files) - `lib/types/.ts` — shared types (autotask, sync, veeam, workflow, …) - `lib/auth.ts`, `lib/auth-utils.ts`, `lib/permissions.ts` — auth wiring - `components/ui/` — shadcn primitives; sibling dirs are feature components - `migrations/NNN_*.sql` — numbered SQL, applied in alphabetical order on Postgres init. Use `IF NOT EXISTS` + `ON CONFLICT DO NOTHING` for seed data. - `docs/` — long-form integration/sync guides; reference these, don't duplicate. - `scripts/` — one-off ops scripts, not tests. ## Database - All columns are **`snake_case`**. API responses are **`camelCase`** — handlers transform manually (no ORM). - Use the `postgresClient` singleton from `lib/services/postgres-client.ts`: `postgresClient.query(sql, params)`, `.transaction()`, `.upsert()`, `.bulkUpsert()`. - Audit columns convention: `created_at`, `updated_at`, `synced_at`, `is_deleted`, `deleted_at`. - Adding a migration: next number, `IF NOT EXISTS`, no destructive ops on existing data without a guard. Postgres init applies them on first boot only — for an existing DB, run via `scripts/apply-migrations` (check first; behavior varies). ## API routes - Pattern: `app/api//route.ts` exporting `GET`/`POST`/etc. - No Zod validation in route handlers today. Validate inputs explicitly when it matters; don't add a framework just to validate one field. - Errors: `try/catch`, return `NextResponse.json({ error, message }, { status })`. Convention: 503 for missing/bad config, 401/403 from auth helpers, 500 for runtime. - **Auth in API routes**: import from `lib/auth-utils.ts`: ```ts const { session, error } = await requireAuth(); // or requireAdmin() / requireSuperAdmin() / requirePermission(resource, action) if (error) return error; ``` `middleware.ts` only verifies a session cookie exists — role checks happen here. - No `'use server'` actions in this codebase. Everything is API routes called from client components via `fetch`. ## Frontend - Most pages are `'use client'` with `useState`/`useEffect`/`fetch('/api/...')`. No SWR/react-query — don't introduce one for one-off fetches; match the surrounding code. - Server components are fine for static shells; data calls live on the client today. - Toasts: `sonner`. Tables: `@tanstack/react-table` via `components/admin/DataTable.tsx`. Modals: `components/admin/DetailModal.tsx`. Navigation: `components/navigation/app-navigation.tsx`. ## External integrations All clients live in `lib/services/` with a factory + `isConfigured()` helper. Examples: `getAutotaskClient()`, `getMsgraphClient()`, `getDattoRmmClient()`, `getVeeamClient()`. Credentials always come from env vars; clients throw if missing. | Service | Env prefix | |---|---| | Autotask | `AUTOTASK_*` (incl. `AUTOTASK_WEBHOOK_SECRET`) | | MS Graph (app) | `MSGRAPH_*` (specific tenant, not `common`) | | Microsoft OAuth (login) | `MICROSOFT_*` | | Datto RMM | `DATTO_RMM_*` | | Veeam VSPC | `VEEAM_VSPC_*` | | Auvik / Addigy / IT Glue / Mimecast / S1 / Duo / Zoom / QBO / Zabbix / Salesbldr | `_*` | | Anthropic | `ANTHROPIC_API_KEY` (used in `ai-triage-service.ts`, `llm-analyzer.ts`) | | Postgres / Redis | `POSTGRES_*` or `DATABASE_URL`, `REDIS_URL` | ## Sync & scheduling - `lib/services/entity-sync.ts` — per-entity Autotask → Postgres sync (incremental via `lastTrackedModificationDateTime` when supported, else full upsert). - `lib/services/sync-scheduler.ts` — node-cron singleton. **Self-initializes on first server-side import** (side effect at the bottom of the file). Schedules live in DB, admin-editable at `/admin`. - Webhooks (`/api/webhooks/...`, `/api/zabbix/webhook`) are public per `middleware.ts`; they verify HMAC themselves. ## Auth - Better Auth with magic link + TOTP 2FA + Microsoft OAuth. Roles: `user`, `admin`, `super-admin`. Tables created in migration `012`. - Default admin bootstrapped from `DEFAULT_ADMIN_EMAIL` via `lib/bootstrap.ts`. - `middleware.ts` redirects unauth'd page requests to `/auth/sign-in`. Public routes (webhooks, sync, health, mobile, openclaw, kiosk, legal, qbo callbacks) are hardcoded there — add to that list when introducing a new public endpoint. ## Build / run / verify - Dev: `npm run dev` → http://localhost:3100 - Build: `npm run build` (turbopack via Next 16) - Type check: `npx tsc --noEmit --pretty` - Tests: `npm test` (vitest) — currently scoped to `lib/services/analyzer/**` only. No CI yet; tests are local-only. Other parts of the codebase have no tests — if you touch them, type-check is the only safety net. - Docker: `docker compose up` from repo root. Postgres applies `migrations/*.sql` on init only (existing volumes won't re-run them). ## Conventions to follow - Files: kebab-case. Components: `PascalCase` exports from kebab-case files. - Don't introduce ORMs, server actions, or alternative state libraries unless asked — match the existing pattern. - New SQL: numbered migration; never edit a committed one. - Long-form per-feature documentation belongs in `docs/`. Don't duplicate it here. ## Watch out for - A `.env` file is committed to the repo. Treat secrets as potentially real; don't log/echo them, and flag this if it comes up. - Duplicate migration numbers exist (002, 004, 009) — alphabetical apply order. - Sync scheduler runs as a side effect of importing `sync-scheduler.ts` on the server. Be careful adding eager imports of that module. ## Useful existing docs - `AUTOTASK_API_GUIDE.md`, `ADDIGY_API_GUIDE.md` — credential setup - `POSTGRES_SYNC_SETUP.md`, `DOCKER_README.md` - `PULSE_DATABASE_SKILL.md` — diagnostic queries - `docs/` — sync behavior, webhook setup, workflow editor, per-integration guides