# CLAUDE.md

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

## Commands

```bash
pnpm install            # Install dependencies
pnpm run dev            # Development server (nodemon, HTTP, port from .env)
pnpm run prod           # Production server (HTTPS when NODE_ENV=PRODUCTION)
pnpm run migrate        # Apply pending db-migrate migrations to intarAPI
pnpm run migrate:create # Create a new timestamped migration (pnpm run migrate:create Name)
pnpm run sync-endpoints # Upsert the real Express routes into the api_endpoint catalog
```

No test runner or linter is configured in this project.

## Architecture

Express 5 REST API integrating multiple enterprise systems (JDE/Oracle, MySQL, Odoo, Kiconex, WebDAV/Nextcloud). Package manager is **pnpm**.

### Request lifecycle

Every request goes through a shared middleware stack in `src/app.js`:
1. `decryptRequest` — transparently decrypts AES ECB `encData` payloads from the request body (bypassed via `?n8n` query param or specific referers)
2. `encryptResponse` — wraps all JSON responses in AES ECB encryption (bypassed for `/api-docs`, `?n8n` and Odoo webhook routes via the `query: odoo` header; the `/docs/:slug` pages use `res.end()` and are never encrypted)
3. `apiGuard` (route-level) before reaching controllers

### Auth: everything is a table token (`apiGuard`)

**There are no static auth tokens in `.env` and no JWT verification anymore.** All protected routes (`/jde`, `/clientUsers`, `/crm`, `/traking`, `/ilab`, `/kiconex`, `/odoo`, `/calc`) are mounted with the single middleware `src/middleware/apiGuard.js`, which per request:

1. Checks active blocks (`api_block`) by IP/user → 429 with `Retry-After`.
2. Authenticates the opaque API key (`<clientPrefix>_<random>`, where the prefix is the client's exclusive `api_user.prefix`) sent as `Authorization: Bearer`, `?token=` or the `x-odoo-webhook-token` header (the last two exist so Odoo webhooks work without Bearer). The token is SHA-256 hashed and looked up in `api_token` (never stored in clear).
3. Resolves permission: `api_endpoint` catalog match (exact > `:param` > trailing `/*`), then explicit `api_user_endpoint` allow/deny (priority) or granted `api_user_scope`. Admin users (`api_user.is_admin`, e.g. INTARCON) bypass permissions and rate limits.
4. Applies per-user rate limits (req/min-hour-day, `NULL` = global defaults from `api_setting`) with `X-RateLimit-*` headers; creates short `rate_limit` blocks on excess. Failed auth attempts are recorded in `api_auth_attempt`; exceeding the brute-force threshold creates a temporary IP block.
5. Attaches `req.apiUser` / `req.apiToken` / `req.apiEndpoint` and, on `res.finish`, logs the request (status, latency, IP, sanitized body) into `api_request_log`.

Kiconex, Coreco, Odoo and INTARCON are **rows** in `api_user`/`api_token` (seeded from the old `.env` values). INTARCON is `is_root = 1` + `is_admin = 1` with a fixed seed uuid and cannot be deleted/deactivated. Utilities live in `src/utils/apiToken.js` (`generateToken`/`hashToken`, SHA-256 — **must stay hash-identical to the copy in client360**) and `src/models/apiAccess.js` (data layer over `queryAPI`, in-memory TTL caches for settings/endpoint catalog).

Swagger (`/api-docs`) uses HTTP Basic where the *password* is a table token of an admin `api_user`.

**Administration does NOT live here.** There are no `/adminapi/*` routes: users, tokens, permissions, docs, limits, blocks, settings and tracking/stats are managed from the client360 backend, which connects directly to `intarAPI`. This API only *consumes* that database at runtime.

### Database connections

- **`src/connections/oracle.js`** — Oracle pool for JDE (pool size 20). Queries via `src/utils/oracleAdapter.js` → `queryJde()`.
- **`src/connections/mysql.js`** — Four MySQL pools: `CLIENT360` (main), `CRM`, `IntarLAB`, and `IntarAPI` (`DB_DATABASE_API = intarAPI`, same host/user/pass as the main DB). Wrapped by `src/utils/mysqlAdapter.js`: `query()`, `queryCRM()`, `queryLAB()`, `queryAPI()`. Only IntarLAB pool exposes transaction support (`getConnectionLAB()`).

### `intarAPI` schema (managed via migrations)

Key convention (applies to every table): internal `id` BIGINT AI primary key **never exposed**, public `uuid` CHAR(36) UNIQUE used for **all FKs and API/UI references**, plus `created_at`/`updated_at` on every table.

| Table | Purpose |
|---|---|
| `api_user` | API clients (INTARCON is_root/is_admin, rate limits, active) |
| `api_token` | Hashed API keys per user (prefix + SHA-256, expiry, revocation) |
| `api_scope` / `api_endpoint` | Endpoint catalog grouped in scopes; auto-synced by `scripts/sync-endpoints.js` |
| `api_user_scope` / `api_user_endpoint` | Permissions (scope grants + per-endpoint allow/deny) |
| `api_request_log` | Full usage tracking (sanitized body, status, latency, IP) |
| `api_auth_attempt` / `api_block` | Brute-force detection and temporary blocks |
| `api_setting` | Global key/value config (rate defaults, brute-force policy, `log_body`, `log_retention_days`) |
| `api_doc` / `api_doc_access` | Publishable documentations served at `/docs/:slug` and who can view each |

### Migrations

`migrations/*.js` uses **db-migrate** (`db-migrate` + `db-migrate-mysql`, same format as `client360/backend/migrations`), configured by `database.json` (points at `DB_DATABASE_API`). db-migrate keeps its own `migrations` version table. Log retention is purged daily from `src/index.js` (`apiAccess.purgeOldLogs`).

### Publishable documentations (`/docs/:slug`)

`src/routes/docs.js` + `src/controllers/docs.js` serve HTML documentations from `api_doc` (file via `html_path` or inline `html_body`), guarded by a corporate password screen where the password is the client's table token, validated against `api_doc_access` (admins see all). The used token is injected into `"__DOC_TOKEN__"`. The old `/calc/coldroom/api-docs` URL redirects to `/docs/coldroom`. The coldroom HTML is regenerated with `scripts/build-coldroom-doc.js`.

### Odoo integration

`src/utils/Odoo.js` exports a class with methods for the Odoo JSON2 RPC API (`json2Call`, `json2SearchRead`, `json2CreateMany`, `json2Write`, `json2CallMethod`). Two environments are supported (production + test), selected via env vars. Controllers instantiate this class directly. Odoo webhooks authenticate through `apiGuard` with the "Odoo" table token (Bearer, `?token=` or `x-odoo-webhook-token`).

### Encryption

`src/utils/functions.js` exports `decryptRequest` and `encryptResponse` middleware using AES ECB mode keyed by `CRYPTO_SEED`. The same file contains `checkMySqlError()` and `generateOptionalCodeFromOdoo()`.

### Logging

`src/utils/logger.js` uses Winston with daily rotating files under `./logs/`. Initialize per-module with `require('./utils/logger').init()`. Retention is 4 days.

### API documentation

Swagger UI is served at `/api-docs` (Basic Auth: password = admin table token). Specs are defined as JSDoc comments in `src/docs/*.js` and loaded automatically via `swagger-jsdoc`.

### PDF generation

Some endpoints shell out to a Python script (`src/python/generatePDF.py`) via `child_process`. The Python executable path is configured via the `PYTHON` env var.

## Environment

Copy `.env.sample` to `.env`. Required variable groups:
- **Server**: `PORT`, `NODE_ENV`, `CRYPTO_SEED`
- **MySQL (main)**: `DB_HOST`, `DB_USER`, `DB_PASS`, `DB_DATABASE`
- **MySQL (IntarAPI)**: `DB_DATABASE_API` (= `intarAPI`; same host/user/pass as main)
- **MySQL (CRM)**: `DB_HOST_CRM`, `DB_USER_CRM`, `DB_PASS_CRM`, `DB_DATABASE_CRM`
- **MySQL (IntarLAB)**: `DB_HOST_LAB`, `DB_USER_LAB`, `DB_PASS_LAB`, `DB_DATABASE_LAB`
- **Oracle JDE**: `DB_HOST_JDE`, `DB_USER_JDE`, `DB_PASS_JDE`, `DB_DATABASE_JDE`
- **Odoo**: `ODOO_HOST_LAB`, `ODOO_DB_LAB`, `ODOO_API_KEY_LAB`
- **Nextcloud**: `SECRET_CLOUD_EMAIL`, `SECRET_CLOUD_KEY`

**Obsolete auth variables** (migrated to `api_token` rows by the `SeedApiUsers` migration; no longer read by the code): `PERMANENT_TOKEN`, `CORECO_TOKEN`, `KICONEX_TOKEN`, `ODOO_WEBHOOK_TOKEN`, and `JWT_SECRET`/`ODOO_API_KEY` as auth secrets.
