# AGENTS.md

This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.

## Project shape

This repo is the `@servicelabsco/slabs-access-manager` package. It is **both** a publishable npm library (consumed by other Finnoto services — `main: dist/index.js`, `types: dist/index.d.ts`, `files: ["dist/**/*"]`) **and** a runnable NestJS HTTP server. Any change to public exports, controllers, DTOs, or entities ships to downstream services on the next `npm publish` — treat the surface as a contract.

Bump `version` in `package.json` and tag (`v1.0.x`) on release; recent commits follow the `v1.0.NN` pattern.

## Common commands

```bash
# dev / build / run
npm run start:dev          # nest start --watch (HTTP server on $SERVER_PORT, default 4000)
npm run build              # nest build → dist/ (also what publishing ships)
npm run start:prod         # node dist/main

# tests (jest, rootDir=src, *.spec.ts)
npm test
npm test -- path/to/file.spec.ts                     # run a single file
npm test -- -t "matches test name"                   # run by test name
npm run test:e2e                                     # uses test/jest-e2e.json
npm run test:cov

# lint / format
npm run lint               # eslint --fix on {src,apps,libs,test}/**/*.ts
npm run format             # prettier --write

# database migrations (TypeORM CLI, datasource = src/config/orm.config.ts)
npm run m:g -- src/migrations/<Name>    # generate from entity diff
npm run m:c -- src/migrations/<Name>    # create empty
npm run m:r                             # run pending migrations
npm run m:rev                           # revert last migration

# alternate process entrypoints (same AppModule, different bootstrap)
npm run command -- <name>  # nestjs-command CLI (src/cli.ts)
npm run console            # nestjs-console (src/console.ts) — requires built dist/
```

Migrations table is `sys_migrations` (not the TypeORM default). Migrations are loaded from both `src/migrations/**` *and* `node_modules/@servicelabsco/**/migrations/**` — sibling packages contribute migrations that run inside this app.

## Architecture

### Three feature modules, all aggregated through `es6.classes.ts`

`AppModule` (`src/app.module.ts`) composes three feature modules:

- `AccessModule` (`src/access/`) — the bulk of the domain: business users/roles/groups, menus, dashboards, reports, currencies, scripts, webhooks, notifications (email/FCM/Slack/GChat/WhatsApp), email rules, choice lists, custom fields, listings, integrations (Gmail/Slack/Zoho/Amazon/WhatsApp).
- `AccessUtilityModule` (`src/accessUtility/`) — bulk upload, file upload (S3/Lambda), PDF generation, UI policy, data access.
- `AccessWorkflowModule` (`src/accessWorkflow/`) — approval workflow steps, authority delegation, limit config. Depends on `AccessModule`; `AccessUtilityModule` uses `forwardRef(() => AccessModule)` to break the cycle.

Each module follows the same pattern: an `es6.classes.ts` barrel file in the module imports every controller/service/job/subscriber/entity/dto/library and exports a single `{ controllers, services, jobs, subscribers, entities, dtos, libraries, commands, middlewares }` object. The module file just spreads those arrays into `@Module({...})`. **When you add a new class, register it in that module's `es6.classes.ts` — TypeORM/Nest will not pick it up otherwise.** `AccessModule`'s barrel currently lists ~40 controllers, ~100 jobs, ~100 subscribers, ~95 entities.

`controllers/`, `services/`, `jobs/`, `subscribers/`, `entities/`, `dtos/`, `enums/`, `libraries/`, `middlewares/` are the canonical subfolders. `libraries/` holds reusable use-case classes (`process.*.ts`, `send.*.ts`) — heavier than helpers, lighter than full services, often the actual implementation behind a controller endpoint.

### Two TypeORM datasources

`AppModule` registers two `TypeOrmModule.forRoot(...)` calls:

- `default` (`src/config/typeorm.config.ts`) — primary Postgres connection used for writes and entity registration. Entities glob: `**/*.entity.{ts,js}` plus `node_modules/@servicelabsco/**/*.entity.{ts,js}`. `synchronize: false`; always use migrations.
- `read` (`src/config/read.typeorm.config.ts`) — read replica (`PG_DB_READ_HOST`). Has **no entities array** — it exists so services can opt into read-replica queries via the connection name `'read'`.

Both configs install pg parsers that convert `bigint` (oid 20) → `number` and `numeric` (oid 1700) → `float`. Snake-case naming strategy (`typeorm-naming-strategies`). The same migration glob is used as the entity glob — sibling `@servicelabsco/*` packages contribute migrations that run here.

### Auth & middleware routing

`AppModule.configure()` chains middleware from `@servicelabsco/nestjs-utility-services` plus three local ones. Each route prefix gets a different auth model:

| Prefix         | Middleware chain                                                              | Auth model |
|----------------|-------------------------------------------------------------------------------|------------|
| `*` (all)      | `JwtMiddleware`, `BasicAuthMiddleware`                                         | parses creds if present |
| `api/*`        | + `RestrictedMiddleware`, `BusinessMiddleware`                                 | JWT user; loads business roles into `Auth.user().roles` |
| `internal/*`   | + `InternalMiddleware`                                                         | server-to-server |
| `v1/*`         | + `ExternalAccessMiddleware`                                                   | API-key (`x-client-id`/`x-client-secret`) → resolves to a synthetic user (id 2) |
| `ai-server/*`  | + `ClientConnectMiddleware`                                                    | client-connect protocol |

Inside any handler, `Auth.user()` and `Auth.check()` (from the utility-services package) are the canonical way to read the current principal; never trust `req.user` directly. `business_id` lives at `Auth.user().auth_attributes.business_id`.

### Queue & realtime

BullMQ is wired via `BullModule.forRoot(queueConfig)` (`src/config/queue.config.ts`, queue name from `BULL_QUEUE_NAME`). Job classes under each module's `jobs/` folder are providers — dispatch via the utility-services `QueueService`. `AppController` exposes ops endpoints (`/queue`, `/queue/:id`, `/failed-jobs`, `/clean-jobs`, `/refresh-cache`) for queue inspection.

WebSockets use `RedisIoAdapter` from utility-services so events fan out across instances (`main.ts`).

### Subscribers

Every entity has a matching TypeORM subscriber in `subscribers/`. Subscribers are registered as providers (not via the `subscribers:` datasource option, which is commented out) and rely on Nest DI. When adding an entity, add its subscriber and register both in `es6.classes.ts`.

## Conventions worth knowing

- File naming is **dot-separated lowercase**: `business.email.controller.ts`, `process.email.notification.ts`. Match the existing pattern when adding files.
- Entities use `SnakeNamingStrategy`, so TS property `businessId` ↔ column `business_id`. Don't add explicit `@Column({ name: ... })` unless the column name doesn't follow the convention.
- DTOs use `class-validator` / `class-transformer`; the global `ValidationPipe` (`main.ts`) is configured with `whitelist: true, transform: true` — properties not on the DTO are stripped.
- Bodies up to 50mb are accepted (`bodyParser.json({ limit: '50mb' })`); helmet and a global rate limiter (`rate.limiter.config.ts`) are on.
- New Relic is optional, enabled when `NEWRELIC_KEY` is set in env.
- Sentry is optional on the HTTP process: set `SENTRY_DSN` (see NU `docs/sentry-reporting.md`). Do not init from `cli.ts` / `console.ts`.
- The `'use cases'` layer lives in `libraries/` (`process.*.ts`, `send.*.ts`). Prefer adding one there over bloating a service when logic spans multiple entities.

## Required env

See `.env` for the local profile. Minimum to boot: `SERVER_PORT`, `APP_KEY`, `SERVER_URL`, `PG_DB_*` (host, read_host, port, username, password, database), `REDIS_HOST`/`REDIS_PORT`, `BULL_QUEUE_NAME`, `JWT_SECRET`, `JWT_EXPIRY`. `PG_DB_LOGGING=true` will write SQL to a file logger (not stdout).
